From 936e30c38cb8295bfdd0e3475310eb4b7b77b323 Mon Sep 17 00:00:00 2001 From: wwenrr Date: Fri, 3 Apr 2026 15:08:47 +0000 Subject: [PATCH 1/3] fix(base): resolve record-list --view-id by view name --- shortcuts/base/base_execute_test.go | 42 +++++++++++++++++++++++++++++ shortcuts/base/record_ops.go | 26 ++++++++++++++++-- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/shortcuts/base/base_execute_test.go b/shortcuts/base/base_execute_test.go index 34128a93..16006478 100644 --- a/shortcuts/base/base_execute_test.go +++ b/shortcuts/base/base_execute_test.go @@ -1045,3 +1045,45 @@ func TestBaseViewExecutePropertyGettersAndExtendedSetters(t *testing.T) { } }) } + +func TestBaseRecordExecuteListWithViewNameResolvesToViewID(t *testing.T) { + factory, stdout, reg := newExecuteFactory(t) + registerTokenStub(reg) + + reg.Register(&httpmock.Stub{ + Method: "GET", + URL: "/open-apis/base/v3/bases/app_x/tables/tbl_x/views", + Body: map[string]interface{}{ + "code": 0, + "data": map[string]interface{}{ + "views": []interface{}{ + map[string]interface{}{"view_id": "vew_x", "view_name": "Main"}, + }, + "total": 1, + }, + }, + }) + + reg.Register(&httpmock.Stub{ + Method: "GET", + URL: "view_id=vew_x", + Body: map[string]interface{}{ + "code": 0, + "data": map[string]interface{}{ + "records": map[string]interface{}{ + "schema": []interface{}{"Name"}, + "record_ids": []interface{}{"rec_1"}, + "rows": []interface{}{[]interface{}{"Alpha"}}, + }, + "total": 1, + }, + }, + }) + + if err := runShortcut(t, BaseRecordList, []string{"+record-list", "--base-token", "app_x", "--table-id", "tbl_x", "--view-id", "Main", "--limit", "1"}, factory, stdout); err != nil { + t.Fatalf("err=%v", err) + } + if got := stdout.String(); !strings.Contains(got, "\"rec_1\"") { + t.Fatalf("stdout=%s", got) + } +} diff --git a/shortcuts/base/record_ops.go b/shortcuts/base/record_ops.go index 280b1c58..c3036835 100644 --- a/shortcuts/base/record_ops.go +++ b/shortcuts/base/record_ops.go @@ -26,6 +26,24 @@ func dryRunRecordList(_ context.Context, runtime *common.RuntimeContext) *common Set("table_id", baseTableID(runtime)) } +func resolveRecordListViewID(runtime *common.RuntimeContext, viewRef string) (string, error) { + if viewRef == "" { + return "", nil + } + if len(viewRef) >= 4 && viewRef[:4] == "vew_" { + return viewRef, nil + } + views, _, err := listAllViews(runtime, runtime.Str("base-token"), baseTableID(runtime), 0, 200) + if err != nil { + return "", err + } + view, err := resolveViewRef(views, viewRef) + if err != nil { + return "", err + } + return viewID(view), nil +} + func dryRunRecordGet(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { return common.NewDryRunAPI(). GET("/open-apis/base/v3/bases/:base_token/tables/:table_id/records/:record_id"). @@ -85,8 +103,12 @@ func executeRecordList(runtime *common.RuntimeContext) error { } limit := common.ParseIntBounded(runtime, "limit", 1, 200) params := map[string]interface{}{"offset": offset, "limit": limit} - if viewID := runtime.Str("view-id"); viewID != "" { - params["view_id"] = viewID + if viewRef := runtime.Str("view-id"); viewRef != "" { + resolvedViewID, err := resolveRecordListViewID(runtime, viewRef) + if err != nil { + return err + } + params["view_id"] = resolvedViewID } data, err := baseV3Call(runtime, "GET", baseV3Path("bases", runtime.Str("base-token"), "tables", baseTableID(runtime), "records"), params, nil) if err != nil { From 9f2841765d60f5093010e9b0ef570aedb76ca947 Mon Sep 17 00:00:00 2001 From: wwenrr Date: Fri, 3 Apr 2026 15:21:09 +0000 Subject: [PATCH 2/3] fix(base): align dry-run view ref and paginate view resolution --- shortcuts/base/base_dryrun_ops_test.go | 15 ++++++++ shortcuts/base/base_execute_test.go | 53 ++++++++++++++++++++++++++ shortcuts/base/record_ops.go | 49 ++++++++++++++++++------ 3 files changed, 106 insertions(+), 11 deletions(-) diff --git a/shortcuts/base/base_dryrun_ops_test.go b/shortcuts/base/base_dryrun_ops_test.go index 3898826b..2c03148d 100644 --- a/shortcuts/base/base_dryrun_ops_test.go +++ b/shortcuts/base/base_dryrun_ops_test.go @@ -218,3 +218,18 @@ func TestDryRunViewOps(t *testing.T) { assertDryRunContains(t, dryRunViewGetProperty(listRT, "a/b"), "GET /open-apis/base/v3/bases/app_x/tables/tbl_1/views/viw_1/a%2Fb") } + +func TestDryRunRecordListWithViewName(t *testing.T) { + ctx := context.Background() + rt := newBaseTestRuntime( + map[string]string{"base-token": "app_x", "table-id": "tbl_1", "view-id": "Main"}, + nil, + map[string]int{"offset": 0, "limit": 20}, + ) + assertDryRunContains( + t, + dryRunRecordList(ctx, rt), + "GET /open-apis/base/v3/bases/app_x/tables/tbl_1/records", + "view_id=%3Cresolved+from+view+name%3A+Main%3E", + ) +} diff --git a/shortcuts/base/base_execute_test.go b/shortcuts/base/base_execute_test.go index 16006478..b8430051 100644 --- a/shortcuts/base/base_execute_test.go +++ b/shortcuts/base/base_execute_test.go @@ -1087,3 +1087,56 @@ func TestBaseRecordExecuteListWithViewNameResolvesToViewID(t *testing.T) { t.Fatalf("stdout=%s", got) } } + +func TestBaseRecordResolveViewIDAcrossPages(t *testing.T) { + factory, stdout, reg := newExecuteFactory(t) + registerTokenStub(reg) + + reg.Register(&httpmock.Stub{ + Method: "GET", + URL: "/open-apis/base/v3/bases/app_x/tables/tbl_x/views?limit=200&offset=0", + Body: map[string]interface{}{ + "code": 0, + "data": map[string]interface{}{ + "views": []interface{}{}, + "total": 201, + }, + }, + }) + reg.Register(&httpmock.Stub{ + Method: "GET", + URL: "/open-apis/base/v3/bases/app_x/tables/tbl_x/views?limit=200&offset=200", + Body: map[string]interface{}{ + "code": 0, + "data": map[string]interface{}{ + "views": []interface{}{ + map[string]interface{}{"view_id": "vew_target", "view_name": "Target View"}, + }, + "total": 201, + }, + }, + }) + + reg.Register(&httpmock.Stub{ + Method: "GET", + URL: "view_id=vew_target", + Body: map[string]interface{}{ + "code": 0, + "data": map[string]interface{}{ + "records": map[string]interface{}{ + "schema": []interface{}{"Name"}, + "record_ids": []interface{}{"rec_last"}, + "rows": []interface{}{[]interface{}{"Tail"}}, + }, + "total": 1, + }, + }, + }) + + if err := runShortcut(t, BaseRecordList, []string{"+record-list", "--base-token", "app_x", "--table-id", "tbl_x", "--view-id", "Target View", "--limit", "1"}, factory, stdout); err != nil { + t.Fatalf("err=%v", err) + } + if got := stdout.String(); !strings.Contains(got, "\"rec_last\"") { + t.Fatalf("stdout=%s", got) + } +} diff --git a/shortcuts/base/record_ops.go b/shortcuts/base/record_ops.go index c3036835..b92dbc7b 100644 --- a/shortcuts/base/record_ops.go +++ b/shortcuts/base/record_ops.go @@ -5,10 +5,18 @@ package base import ( "context" + "fmt" + "strings" "github.com/larksuite/cli/shortcuts/common" ) +const recordListViewResolvePageLimit = 200 + +func isViewIDRef(viewRef string) bool { + return strings.HasPrefix(viewRef, "vew_") || strings.HasPrefix(viewRef, "viw_") +} + func dryRunRecordList(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { offset := runtime.Int("offset") if offset < 0 { @@ -16,8 +24,12 @@ func dryRunRecordList(_ context.Context, runtime *common.RuntimeContext) *common } limit := common.ParseIntBounded(runtime, "limit", 1, 200) params := map[string]interface{}{"offset": offset, "limit": limit} - if viewID := runtime.Str("view-id"); viewID != "" { - params["view_id"] = viewID + if viewRef := runtime.Str("view-id"); viewRef != "" { + if isViewIDRef(viewRef) { + params["view_id"] = viewRef + } else { + params["view_id"] = fmt.Sprintf("", viewRef) + } } return common.NewDryRunAPI(). GET("/open-apis/base/v3/bases/:base_token/tables/:table_id/records"). @@ -30,18 +42,33 @@ func resolveRecordListViewID(runtime *common.RuntimeContext, viewRef string) (st if viewRef == "" { return "", nil } - if len(viewRef) >= 4 && viewRef[:4] == "vew_" { + if isViewIDRef(viewRef) { return viewRef, nil } - views, _, err := listAllViews(runtime, runtime.Str("base-token"), baseTableID(runtime), 0, 200) - if err != nil { - return "", err - } - view, err := resolveViewRef(views, viewRef) - if err != nil { - return "", err + + offset := 0 + for { + views, total, err := listAllViews(runtime, runtime.Str("base-token"), baseTableID(runtime), offset, recordListViewResolvePageLimit) + if err != nil { + return "", err + } + if view, err := resolveViewRef(views, viewRef); err == nil { + return viewID(view), nil + } + if len(views) == 0 { + if total > 0 && offset+recordListViewResolvePageLimit < total { + offset += recordListViewResolvePageLimit + continue + } + break + } + offset += len(views) + if total > 0 && offset >= total { + break + } } - return viewID(view), nil + + return "", fmt.Errorf("view %q not found", viewRef) } func dryRunRecordGet(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { From 317511af1a6db357ab592661e3dd8a4b18d2d356 Mon Sep 17 00:00:00 2001 From: wwenrr Date: Fri, 3 Apr 2026 15:25:38 +0000 Subject: [PATCH 3/3] fix(base): guard missing canonical id in view resolution --- shortcuts/base/base_execute_test.go | 24 ++++++++++++++++++++++++ shortcuts/base/record_ops.go | 6 +++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/shortcuts/base/base_execute_test.go b/shortcuts/base/base_execute_test.go index b8430051..c87b1072 100644 --- a/shortcuts/base/base_execute_test.go +++ b/shortcuts/base/base_execute_test.go @@ -1140,3 +1140,27 @@ func TestBaseRecordResolveViewIDAcrossPages(t *testing.T) { t.Fatalf("stdout=%s", got) } } + +func TestBaseRecordResolveViewIDMissingCanonicalID(t *testing.T) { + factory, stdout, reg := newExecuteFactory(t) + registerTokenStub(reg) + + reg.Register(&httpmock.Stub{ + Method: "GET", + URL: "/open-apis/base/v3/bases/app_x/tables/tbl_x/views", + Body: map[string]interface{}{ + "code": 0, + "data": map[string]interface{}{ + "views": []interface{}{ + map[string]interface{}{"view_name": "BrokenView"}, + }, + "total": 1, + }, + }, + }) + + err := runShortcut(t, BaseRecordList, []string{"+record-list", "--base-token", "app_x", "--table-id", "tbl_x", "--view-id", "BrokenView", "--limit", "1"}, factory, stdout) + if err == nil || !strings.Contains(err.Error(), "has no canonical id") { + t.Fatalf("err=%v", err) + } +} diff --git a/shortcuts/base/record_ops.go b/shortcuts/base/record_ops.go index b92dbc7b..aac6ff39 100644 --- a/shortcuts/base/record_ops.go +++ b/shortcuts/base/record_ops.go @@ -53,7 +53,11 @@ func resolveRecordListViewID(runtime *common.RuntimeContext, viewRef string) (st return "", err } if view, err := resolveViewRef(views, viewRef); err == nil { - return viewID(view), nil + resolvedID := viewID(view) + if resolvedID == "" { + return "", fmt.Errorf("view %q has no canonical id", viewRef) + } + return resolvedID, nil } if len(views) == 0 { if total > 0 && offset+recordListViewResolvePageLimit < total {