mirror of
https://github.com/larksuite/cli.git
synced 2026-07-03 14:02:43 +08:00
Revert "Add +dashboard-arrange command for auto-arranging dashboard blocks …" (#386)
This reverts commit b8fa2b3f80.
This commit is contained in:
committed by
GitHub
parent
b8fa2b3f80
commit
d30a9472c3
@@ -12,7 +12,6 @@ import (
|
||||
|
||||
// ── Dashboard CRUD ──────────────────────────────────────────────────
|
||||
|
||||
// TestBaseDashboardExecuteList tests the +dashboard-list command.
|
||||
func TestBaseDashboardExecuteList(t *testing.T) {
|
||||
t.Run("single page", func(t *testing.T) {
|
||||
factory, stdout, reg := newExecuteFactory(t)
|
||||
@@ -42,7 +41,6 @@ func TestBaseDashboardExecuteList(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
// TestBaseDashboardExecuteGet tests the +dashboard-get command.
|
||||
func TestBaseDashboardExecuteGet(t *testing.T) {
|
||||
factory, stdout, reg := newExecuteFactory(t)
|
||||
reg.Register(&httpmock.Stub{
|
||||
@@ -69,7 +67,6 @@ func TestBaseDashboardExecuteGet(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestBaseDashboardExecuteCreate tests the +dashboard-create command.
|
||||
func TestBaseDashboardExecuteCreate(t *testing.T) {
|
||||
t.Run("name only", func(t *testing.T) {
|
||||
factory, stdout, reg := newExecuteFactory(t)
|
||||
@@ -117,7 +114,6 @@ func TestBaseDashboardExecuteCreate(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// TestBaseDashboardExecuteUpdate tests the +dashboard-update command.
|
||||
func TestBaseDashboardExecuteUpdate(t *testing.T) {
|
||||
t.Run("update name", func(t *testing.T) {
|
||||
factory, stdout, reg := newExecuteFactory(t)
|
||||
@@ -165,7 +161,6 @@ func TestBaseDashboardExecuteUpdate(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// TestBaseDashboardExecuteDelete tests the +dashboard-delete command.
|
||||
func TestBaseDashboardExecuteDelete(t *testing.T) {
|
||||
factory, stdout, reg := newExecuteFactory(t)
|
||||
reg.Register(&httpmock.Stub{
|
||||
@@ -184,7 +179,6 @@ func TestBaseDashboardExecuteDelete(t *testing.T) {
|
||||
|
||||
// ── Dashboard Block CRUD ────────────────────────────────────────────
|
||||
|
||||
// TestBaseDashboardBlockExecuteList tests the +dashboard-block-list command.
|
||||
func TestBaseDashboardBlockExecuteList(t *testing.T) {
|
||||
t.Run("single page", func(t *testing.T) {
|
||||
factory, stdout, reg := newExecuteFactory(t)
|
||||
@@ -214,7 +208,6 @@ func TestBaseDashboardBlockExecuteList(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
// TestBaseDashboardBlockExecuteGet tests the +dashboard-block-get command.
|
||||
func TestBaseDashboardBlockExecuteGet(t *testing.T) {
|
||||
t.Run("basic", func(t *testing.T) {
|
||||
factory, stdout, reg := newExecuteFactory(t)
|
||||
@@ -268,7 +261,6 @@ func TestBaseDashboardBlockExecuteGet(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// TestBaseDashboardBlockExecuteCreate tests the +dashboard-block-create command.
|
||||
func TestBaseDashboardBlockExecuteCreate(t *testing.T) {
|
||||
t.Run("with data-config", func(t *testing.T) {
|
||||
factory, stdout, reg := newExecuteFactory(t)
|
||||
@@ -362,7 +354,6 @@ func TestBaseDashboardBlockExecuteCreate(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// TestBaseDashboardBlockExecuteUpdate tests the +dashboard-block-update command.
|
||||
func TestBaseDashboardBlockExecuteUpdate(t *testing.T) {
|
||||
t.Run("update name and data-config", func(t *testing.T) {
|
||||
factory, stdout, reg := newExecuteFactory(t)
|
||||
@@ -429,7 +420,6 @@ func TestBaseDashboardBlockExecuteUpdate(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// TestBaseDashboardBlockExecuteDelete tests the +dashboard-block-delete command.
|
||||
func TestBaseDashboardBlockExecuteDelete(t *testing.T) {
|
||||
factory, stdout, reg := newExecuteFactory(t)
|
||||
reg.Register(&httpmock.Stub{
|
||||
@@ -448,7 +438,6 @@ func TestBaseDashboardBlockExecuteDelete(t *testing.T) {
|
||||
|
||||
// ── Dry Run: Dashboard & Blocks ──────────────────────────────────────
|
||||
|
||||
// TestBaseDashboardDryRun_List tests the +dashboard-list --dry-run flag.
|
||||
func TestBaseDashboardDryRun_List(t *testing.T) {
|
||||
factory, stdout, _ := newExecuteFactory(t)
|
||||
if err := runShortcut(t, BaseDashboardList, []string{"+dashboard-list", "--base-token", "app_x", "--page-size", "50", "--dry-run", "--format", "pretty"}, factory, stdout); err != nil {
|
||||
@@ -460,7 +449,6 @@ func TestBaseDashboardDryRun_List(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestBaseDashboardDryRun_Get tests the +dashboard-get --dry-run flag.
|
||||
func TestBaseDashboardDryRun_Get(t *testing.T) {
|
||||
factory, stdout, _ := newExecuteFactory(t)
|
||||
if err := runShortcut(t, BaseDashboardGet, []string{"+dashboard-get", "--base-token", "app_x", "--dashboard-id", "dsh_1", "--dry-run", "--format", "pretty"}, factory, stdout); err != nil {
|
||||
@@ -472,7 +460,6 @@ func TestBaseDashboardDryRun_Get(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestBaseDashboardDryRun_Create tests the +dashboard-create --dry-run flag.
|
||||
func TestBaseDashboardDryRun_Create(t *testing.T) {
|
||||
factory, stdout, _ := newExecuteFactory(t)
|
||||
args := []string{"+dashboard-create", "--base-token", "app_x", "--name", "新报表", "--theme-style", "default", "--dry-run", "--format", "pretty"}
|
||||
@@ -485,7 +472,6 @@ func TestBaseDashboardDryRun_Create(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestBaseDashboardDryRun_Update tests the +dashboard-update --dry-run flag.
|
||||
func TestBaseDashboardDryRun_Update(t *testing.T) {
|
||||
factory, stdout, _ := newExecuteFactory(t)
|
||||
args := []string{"+dashboard-update", "--base-token", "app_x", "--dashboard-id", "dsh_1", "--name", "更新名", "--dry-run", "--format", "pretty"}
|
||||
@@ -498,7 +484,6 @@ func TestBaseDashboardDryRun_Update(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestBaseDashboardDryRun_Delete tests the +dashboard-delete --dry-run flag.
|
||||
func TestBaseDashboardDryRun_Delete(t *testing.T) {
|
||||
factory, stdout, _ := newExecuteFactory(t)
|
||||
args := []string{"+dashboard-delete", "--base-token", "app_x", "--dashboard-id", "dsh_1", "--dry-run", "--format", "pretty"}
|
||||
@@ -511,7 +496,6 @@ func TestBaseDashboardDryRun_Delete(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestBaseDashboardBlockDryRun_List tests the +dashboard-block-list --dry-run flag.
|
||||
func TestBaseDashboardBlockDryRun_List(t *testing.T) {
|
||||
factory, stdout, _ := newExecuteFactory(t)
|
||||
args := []string{"+dashboard-block-list", "--base-token", "app_x", "--dashboard-id", "dsh_1", "--page-size", "10", "--dry-run", "--format", "pretty"}
|
||||
@@ -524,7 +508,6 @@ func TestBaseDashboardBlockDryRun_List(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestBaseDashboardBlockDryRun_Get tests the +dashboard-block-get --dry-run flag.
|
||||
func TestBaseDashboardBlockDryRun_Get(t *testing.T) {
|
||||
factory, stdout, _ := newExecuteFactory(t)
|
||||
args := []string{"+dashboard-block-get", "--base-token", "app_x", "--dashboard-id", "dsh_1", "--block-id", "blk_a", "--user-id-type", "union_id", "--dry-run", "--format", "pretty"}
|
||||
@@ -537,7 +520,6 @@ func TestBaseDashboardBlockDryRun_Get(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestBaseDashboardBlockDryRun_Create tests the +dashboard-block-create --dry-run flag.
|
||||
func TestBaseDashboardBlockDryRun_Create(t *testing.T) {
|
||||
factory, stdout, _ := newExecuteFactory(t)
|
||||
args := []string{"+dashboard-block-create", "--base-token", "app_x", "--dashboard-id", "dsh_1", "--name", "订单趋势", "--type", "column", "--data-config", `{"table_name":"订单表","count_all":true}`, "--user-id-type", "open_id", "--dry-run", "--format", "pretty"}
|
||||
@@ -550,7 +532,6 @@ func TestBaseDashboardBlockDryRun_Create(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestBaseDashboardBlockDryRun_Update tests the +dashboard-block-update --dry-run flag.
|
||||
func TestBaseDashboardBlockDryRun_Update(t *testing.T) {
|
||||
factory, stdout, _ := newExecuteFactory(t)
|
||||
args := []string{"+dashboard-block-update", "--base-token", "app_x", "--dashboard-id", "dsh_1", "--block-id", "blk_a", "--name", "订单趋势v2", "--data-config", `{"table_name":"订单表2","count_all":true}`, "--dry-run", "--format", "pretty"}
|
||||
@@ -563,7 +544,6 @@ func TestBaseDashboardBlockDryRun_Update(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestBaseDashboardBlockDryRun_Delete tests the +dashboard-block-delete --dry-run flag.
|
||||
func TestBaseDashboardBlockDryRun_Delete(t *testing.T) {
|
||||
factory, stdout, _ := newExecuteFactory(t)
|
||||
args := []string{"+dashboard-block-delete", "--base-token", "app_x", "--dashboard-id", "dsh_1", "--block-id", "blk_a", "--dry-run", "--format", "pretty"}
|
||||
@@ -578,7 +558,6 @@ func TestBaseDashboardBlockDryRun_Delete(t *testing.T) {
|
||||
|
||||
// ── Validator: data_config ───────────────────────────────────────────
|
||||
|
||||
// TestBaseDashboardBlockCreate_ValidateFails tests that data_config validation catches missing table_name.
|
||||
func TestBaseDashboardBlockCreate_ValidateFails(t *testing.T) {
|
||||
factory, stdout, _ := newExecuteFactory(t)
|
||||
// 缺 table_name 且 series 与 count_all 同时存在
|
||||
@@ -595,7 +574,6 @@ func TestBaseDashboardBlockCreate_ValidateFails(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestBaseDashboardBlockCreate_NoValidateFlagAllocs tests that --no-validate flag skips client-side validation.
|
||||
func TestBaseDashboardBlockCreate_NoValidateFlagAllocs(t *testing.T) {
|
||||
factory, stdout, reg := newExecuteFactory(t)
|
||||
reg.Register(&httpmock.Stub{Method: "POST", URL: "/open-apis/base/v3/bases/app_x/dashboards/dsh_1/blocks",
|
||||
@@ -613,7 +591,6 @@ func TestBaseDashboardBlockCreate_NoValidateFlagAllocs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestBaseDashboardBlockCreate_InvalidRollup tests that invalid rollup values are rejected during validation.
|
||||
func TestBaseDashboardBlockCreate_InvalidRollup(t *testing.T) {
|
||||
factory, stdout, _ := newExecuteFactory(t)
|
||||
// 合法 JSON,但 rollup=COUNTA(不支持)
|
||||
@@ -629,186 +606,3 @@ func TestBaseDashboardBlockCreate_InvalidRollup(t *testing.T) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Text Block Tests ────────────────────────────────────────────────
|
||||
|
||||
// TestBaseDashboardBlockExecuteCreate_TextType tests creating text blocks with markdown content.
|
||||
func TestBaseDashboardBlockExecuteCreate_TextType(t *testing.T) {
|
||||
t.Run("valid text block", func(t *testing.T) {
|
||||
factory, stdout, reg := newExecuteFactory(t)
|
||||
reg.Register(&httpmock.Stub{
|
||||
Method: "POST",
|
||||
URL: "/open-apis/base/v3/bases/app_x/dashboards/dsh_001/blocks",
|
||||
Body: map[string]interface{}{
|
||||
"code": 0,
|
||||
"data": map[string]interface{}{
|
||||
"block_id": "blk_text",
|
||||
"name": "说明文字",
|
||||
"type": "text",
|
||||
"data_config": map[string]interface{}{
|
||||
"text": "# 标题\n**加粗**",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
args := []string{"+dashboard-block-create", "--base-token", "app_x", "--dashboard-id", "dsh_001",
|
||||
"--name", "说明文字", "--type", "text",
|
||||
"--data-config", `{"text":"# 标题\n**加粗**"}`,
|
||||
}
|
||||
if err := runShortcut(t, BaseDashboardBlockCreate, args, factory, stdout); err != nil {
|
||||
t.Fatalf("err=%v", err)
|
||||
}
|
||||
got := stdout.String()
|
||||
if !strings.Contains(got, `"blk_text"`) || !strings.Contains(got, `"created": true`) {
|
||||
t.Fatalf("stdout=%s", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("text block missing text field", func(t *testing.T) {
|
||||
factory, stdout, _ := newExecuteFactory(t)
|
||||
args := []string{"+dashboard-block-create", "--base-token", "app_x", "--dashboard-id", "dsh_001",
|
||||
"--name", "Bad", "--type", "text",
|
||||
"--data-config", `{}`,
|
||||
}
|
||||
err := runShortcut(t, BaseDashboardBlockCreate, args, factory, stdout)
|
||||
if err == nil {
|
||||
t.Fatalf("expected validation error for missing text field")
|
||||
}
|
||||
if got := err.Error(); !strings.Contains(got, "text") || !strings.Contains(got, "data_config 校验失败") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestBaseDashboardBlockExecuteUpdate_TextType tests updating text block content and name.
|
||||
func TestBaseDashboardBlockExecuteUpdate_TextType(t *testing.T) {
|
||||
t.Run("update text content", func(t *testing.T) {
|
||||
factory, stdout, reg := newExecuteFactory(t)
|
||||
reg.Register(&httpmock.Stub{
|
||||
Method: "PATCH",
|
||||
URL: "/open-apis/base/v3/bases/app_x/dashboards/dsh_001/blocks/blk_text",
|
||||
Body: map[string]interface{}{
|
||||
"code": 0,
|
||||
"data": map[string]interface{}{
|
||||
"block_id": "blk_text",
|
||||
"name": "更新后的标题",
|
||||
"type": "text",
|
||||
"data_config": map[string]interface{}{
|
||||
"text": "# 新内容",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
args := []string{"+dashboard-block-update", "--base-token", "app_x", "--dashboard-id", "dsh_001", "--block-id", "blk_text",
|
||||
"--name", "更新后的标题",
|
||||
"--data-config", `{"text":"# 新内容"}`,
|
||||
}
|
||||
if err := runShortcut(t, BaseDashboardBlockUpdate, args, factory, stdout); err != nil {
|
||||
t.Fatalf("err=%v", err)
|
||||
}
|
||||
got := stdout.String()
|
||||
if !strings.Contains(got, `"updated": true`) || !strings.Contains(got, "新内容") {
|
||||
t.Fatalf("stdout=%s", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("update without type skips strict validation", func(t *testing.T) {
|
||||
factory, stdout, reg := newExecuteFactory(t)
|
||||
// update 不传 type,不做强类型校验,直接透传给后端
|
||||
reg.Register(&httpmock.Stub{
|
||||
Method: "PATCH",
|
||||
URL: "/open-apis/base/v3/bases/app_x/dashboards/dsh_001/blocks/blk_text",
|
||||
Body: map[string]interface{}{
|
||||
"code": 0,
|
||||
"data": map[string]interface{}{
|
||||
"block_id": "blk_text",
|
||||
"type": "text",
|
||||
},
|
||||
},
|
||||
})
|
||||
args := []string{"+dashboard-block-update", "--base-token", "app_x", "--dashboard-id", "dsh_001", "--block-id", "blk_text",
|
||||
"--data-config", `{"content":"xxx"}`,
|
||||
}
|
||||
// 不传 type,本地不做强校验,让后端处理
|
||||
err := runShortcut(t, BaseDashboardBlockUpdate, args, factory, stdout)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got := stdout.String(); !strings.Contains(got, `"updated": true`) {
|
||||
t.Fatalf("stdout=%s", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ── Dashboard Arrange ────────────────────────────────────────────────
|
||||
|
||||
// TestBaseDashboardExecuteArrange tests the +dashboard-arrange command for auto-arranging dashboard blocks.
|
||||
func TestBaseDashboardExecuteArrange(t *testing.T) {
|
||||
t.Run("arrange dashboard blocks", func(t *testing.T) {
|
||||
factory, stdout, reg := newExecuteFactory(t)
|
||||
reg.Register(&httpmock.Stub{
|
||||
Method: "POST",
|
||||
URL: "/open-apis/base/v3/bases/app_x/dashboards/dsh_001/arrange",
|
||||
Body: map[string]interface{}{
|
||||
"code": 0,
|
||||
"data": map[string]interface{}{
|
||||
"dashboard_id": "dsh_001",
|
||||
"name": "测试仪表盘",
|
||||
"blocks": []interface{}{
|
||||
map[string]interface{}{
|
||||
"block_id": "cht_xxx",
|
||||
"block_name": "组件1",
|
||||
"block_type": "column",
|
||||
"layout": map[string]interface{}{
|
||||
"x": 0, "y": 0, "w": 500, "h": 400,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
args := []string{"+dashboard-arrange", "--base-token", "app_x", "--dashboard-id", "dsh_001"}
|
||||
if err := runShortcut(t, BaseDashboardArrange, args, factory, stdout); err != nil {
|
||||
t.Fatalf("err=%v", err)
|
||||
}
|
||||
got := stdout.String()
|
||||
if !strings.Contains(got, `"arranged": true`) || !strings.Contains(got, `"dashboard_id"`) {
|
||||
t.Fatalf("stdout=%s", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("arrange with user-id-type", func(t *testing.T) {
|
||||
factory, stdout, reg := newExecuteFactory(t)
|
||||
reg.Register(&httpmock.Stub{
|
||||
Method: "POST",
|
||||
URL: "user_id_type=union_id",
|
||||
Body: map[string]interface{}{
|
||||
"code": 0,
|
||||
"data": map[string]interface{}{
|
||||
"dashboard_id": "dsh_001",
|
||||
"blocks": []interface{}{},
|
||||
},
|
||||
},
|
||||
})
|
||||
args := []string{"+dashboard-arrange", "--base-token", "app_x", "--dashboard-id", "dsh_001", "--user-id-type", "union_id"}
|
||||
if err := runShortcut(t, BaseDashboardArrange, args, factory, stdout); err != nil {
|
||||
t.Fatalf("err=%v", err)
|
||||
}
|
||||
if got := stdout.String(); !strings.Contains(got, `"arranged": true`) || !strings.Contains(got, `"dashboard_id"`) {
|
||||
t.Fatalf("stdout=%s", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestBaseDashboardDryRun_Arrange tests the +dashboard-arrange --dry-run flag includes empty body.
|
||||
func TestBaseDashboardDryRun_Arrange(t *testing.T) {
|
||||
factory, stdout, _ := newExecuteFactory(t)
|
||||
args := []string{"+dashboard-arrange", "--base-token", "app_x", "--dashboard-id", "dsh_001", "--user-id-type", "union_id", "--dry-run", "--format", "pretty"}
|
||||
if err := runShortcut(t, BaseDashboardArrange, args, factory, stdout); err != nil {
|
||||
t.Fatalf("err=%v", err)
|
||||
}
|
||||
got := stdout.String()
|
||||
if !strings.Contains(got, "POST /open-apis/base/v3/bases/app_x/dashboards/dsh_001/arrange") || !strings.Contains(got, "union_id") || !strings.Contains(got, "{}") {
|
||||
t.Fatalf("stdout=%s", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ func TestShortcutsCatalog(t *testing.T) {
|
||||
"+data-query",
|
||||
"+form-create", "+form-delete", "+form-list", "+form-update", "+form-get",
|
||||
"+form-questions-create", "+form-questions-delete", "+form-questions-update", "+form-questions-list",
|
||||
"+dashboard-list", "+dashboard-get", "+dashboard-create", "+dashboard-update", "+dashboard-delete", "+dashboard-arrange",
|
||||
"+dashboard-list", "+dashboard-get", "+dashboard-create", "+dashboard-update", "+dashboard-delete",
|
||||
"+dashboard-block-list", "+dashboard-block-get", "+dashboard-block-create", "+dashboard-block-update", "+dashboard-block-delete",
|
||||
}
|
||||
if len(shortcuts) != len(want) {
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package base
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/larksuite/cli/shortcuts/common"
|
||||
)
|
||||
|
||||
var BaseDashboardArrange = common.Shortcut{
|
||||
Service: "base",
|
||||
Command: "+dashboard-arrange",
|
||||
Description: "Auto-arrange dashboard blocks layout (server-side smart layout)",
|
||||
Risk: "write",
|
||||
Scopes: []string{"base:dashboard:update"},
|
||||
AuthTypes: authTypes(),
|
||||
HasFormat: true,
|
||||
Flags: []common.Flag{
|
||||
baseTokenFlag(true),
|
||||
dashboardIDFlag(true),
|
||||
{Name: "user-id-type", Desc: "user ID type: open_id / union_id / user_id"},
|
||||
},
|
||||
DryRun: dryRunDashboardArrange,
|
||||
Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
|
||||
return executeDashboardArrange(runtime)
|
||||
},
|
||||
}
|
||||
@@ -6,7 +6,6 @@ package base
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/larksuite/cli/shortcuts/common"
|
||||
@@ -24,7 +23,7 @@ var BaseDashboardBlockCreate = common.Shortcut{
|
||||
baseTokenFlag(true),
|
||||
dashboardIDFlag(true),
|
||||
{Name: "name", Desc: "block name", Required: true},
|
||||
{Name: "type", Desc: "block type: column(柱状图)|bar(条形图)|line(折线图)|pie(饼图)|ring(环形图)|area(面积图)|combo(组合图)|scatter(散点图)|funnel(漏斗图)|wordCloud(词云)|radar(雷达图)|statistics(指标卡)|text(文本). Read dashboard-block-data-config.md before creating.", Required: true},
|
||||
{Name: "type", Desc: "block type: column(柱状图)|bar(条形图)|line(折线图)|pie(饼图)|ring(环形图)|area(面积图)|combo(组合图)|scatter(散点图)|funnel(漏斗图)|wordCloud(词云)|radar(雷达图)|statistics(指标卡). Read dashboard-block-data-config.md before creating.", Required: true},
|
||||
{Name: "data-config", Desc: "data config JSON object (table_name, series, count_all, group_by, filter, etc.)"},
|
||||
{Name: "user-id-type", Desc: "user ID type: open_id / union_id / user_id"},
|
||||
{Name: "no-validate", Type: "bool", Desc: "skip local data_config validation"},
|
||||
@@ -36,11 +35,7 @@ var BaseDashboardBlockCreate = common.Shortcut{
|
||||
}
|
||||
raw := runtime.Str("data-config")
|
||||
if strings.TrimSpace(raw) == "" {
|
||||
// text 类型必须提供 data-config(含 text 内容)
|
||||
if strings.ToLower(runtime.Str("type")) == "text" {
|
||||
return fmt.Errorf("text 类型组件必须提供 data-config,包含必填字段 text")
|
||||
}
|
||||
return nil
|
||||
return nil // 允许无 data_config 的创建(某些类型可先创建后配置)
|
||||
}
|
||||
cfg, err := parseJSONObject(pc, raw, "data-config")
|
||||
if err != nil {
|
||||
|
||||
@@ -24,7 +24,7 @@ var BaseDashboardBlockUpdate = common.Shortcut{
|
||||
dashboardIDFlag(true),
|
||||
blockIDFlag(true),
|
||||
{Name: "name", Desc: "new block name"},
|
||||
{Name: "data-config", Desc: "data config JSON. For chart types: table_name, series|count_all, group_by, filter. For text type: text (markdown supported). See dashboard-block-data-config.md for details."},
|
||||
{Name: "data-config", Desc: "data config JSON: table_name, series|count_all (mutually exclusive), group_by, filter. See dashboard-block-data-config.md for details."},
|
||||
{Name: "user-id-type", Desc: "user ID type: open_id / union_id / user_id"},
|
||||
{Name: "no-validate", Type: "bool", Desc: "skip local data_config validation"},
|
||||
},
|
||||
@@ -42,7 +42,9 @@ var BaseDashboardBlockUpdate = common.Shortcut{
|
||||
return err
|
||||
}
|
||||
norm := normalizeDataConfig(cfg)
|
||||
// update 时不做强类型校验(不传 type),让后端验证具体字段
|
||||
if errs := validateBlockDataConfig("", norm); len(errs) > 0 { // update 时不强校验类型特性
|
||||
return formatDataConfigErrors(errs)
|
||||
}
|
||||
b, _ := json.Marshal(norm)
|
||||
_ = runtime.Cmd.Flags().Set("data-config", string(b))
|
||||
return nil
|
||||
|
||||
@@ -10,17 +10,14 @@ import (
|
||||
"github.com/larksuite/cli/shortcuts/common"
|
||||
)
|
||||
|
||||
// dashboardIDFlag returns a Flag for dashboard ID.
|
||||
func dashboardIDFlag(required bool) common.Flag {
|
||||
return common.Flag{Name: "dashboard-id", Desc: "dashboard ID", Required: required}
|
||||
}
|
||||
|
||||
// blockIDFlag returns a Flag for dashboard block ID.
|
||||
func blockIDFlag(required bool) common.Flag {
|
||||
return common.Flag{Name: "block-id", Desc: "dashboard block ID", Required: required}
|
||||
}
|
||||
|
||||
// dryRunDashboardBase returns a base DryRunAPI with common dashboard parameters set.
|
||||
func dryRunDashboardBase(runtime *common.RuntimeContext) *common.DryRunAPI {
|
||||
return common.NewDryRunAPI().
|
||||
Set("base_token", runtime.Str("base-token")).
|
||||
@@ -28,7 +25,6 @@ func dryRunDashboardBase(runtime *common.RuntimeContext) *common.DryRunAPI {
|
||||
Set("block_id", runtime.Str("block-id"))
|
||||
}
|
||||
|
||||
// dryRunDashboardList returns a DryRunAPI for listing dashboards.
|
||||
func dryRunDashboardList(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
|
||||
params := map[string]interface{}{}
|
||||
if pageSize := strings.TrimSpace(runtime.Str("page-size")); pageSize != "" {
|
||||
@@ -42,13 +38,11 @@ func dryRunDashboardList(_ context.Context, runtime *common.RuntimeContext) *com
|
||||
Params(params)
|
||||
}
|
||||
|
||||
// dryRunDashboardGet returns a DryRunAPI for getting a dashboard.
|
||||
func dryRunDashboardGet(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
|
||||
return dryRunDashboardBase(runtime).
|
||||
GET("/open-apis/base/v3/bases/:base_token/dashboards/:dashboard_id")
|
||||
}
|
||||
|
||||
// dryRunDashboardCreate returns a DryRunAPI for creating a dashboard.
|
||||
func dryRunDashboardCreate(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
|
||||
body := map[string]interface{}{"name": runtime.Str("name")}
|
||||
if themeStyle := strings.TrimSpace(runtime.Str("theme-style")); themeStyle != "" {
|
||||
@@ -59,7 +53,6 @@ func dryRunDashboardCreate(_ context.Context, runtime *common.RuntimeContext) *c
|
||||
Body(body)
|
||||
}
|
||||
|
||||
// dryRunDashboardUpdate returns a DryRunAPI for updating a dashboard.
|
||||
func dryRunDashboardUpdate(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
|
||||
body := map[string]interface{}{}
|
||||
if name := strings.TrimSpace(runtime.Str("name")); name != "" {
|
||||
@@ -73,13 +66,11 @@ func dryRunDashboardUpdate(_ context.Context, runtime *common.RuntimeContext) *c
|
||||
Body(body)
|
||||
}
|
||||
|
||||
// dryRunDashboardDelete returns a DryRunAPI for deleting a dashboard.
|
||||
func dryRunDashboardDelete(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
|
||||
return dryRunDashboardBase(runtime).
|
||||
DELETE("/open-apis/base/v3/bases/:base_token/dashboards/:dashboard_id")
|
||||
}
|
||||
|
||||
// dryRunDashboardBlockList returns a DryRunAPI for listing dashboard blocks.
|
||||
func dryRunDashboardBlockList(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
|
||||
params := map[string]interface{}{}
|
||||
if pageSize := strings.TrimSpace(runtime.Str("page-size")); pageSize != "" {
|
||||
@@ -93,7 +84,6 @@ func dryRunDashboardBlockList(_ context.Context, runtime *common.RuntimeContext)
|
||||
Params(params)
|
||||
}
|
||||
|
||||
// dryRunDashboardBlockGet returns a DryRunAPI for getting a dashboard block.
|
||||
func dryRunDashboardBlockGet(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
|
||||
params := map[string]interface{}{}
|
||||
if userIDType := strings.TrimSpace(runtime.Str("user-id-type")); userIDType != "" {
|
||||
@@ -104,7 +94,6 @@ func dryRunDashboardBlockGet(_ context.Context, runtime *common.RuntimeContext)
|
||||
Params(params)
|
||||
}
|
||||
|
||||
// dryRunDashboardBlockCreate returns a DryRunAPI for creating a dashboard block.
|
||||
func dryRunDashboardBlockCreate(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
|
||||
pc := newParseCtx(runtime)
|
||||
body := map[string]interface{}{}
|
||||
@@ -130,7 +119,6 @@ func dryRunDashboardBlockCreate(_ context.Context, runtime *common.RuntimeContex
|
||||
Body(body)
|
||||
}
|
||||
|
||||
// dryRunDashboardBlockUpdate returns a DryRunAPI for updating a dashboard block.
|
||||
func dryRunDashboardBlockUpdate(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
|
||||
pc := newParseCtx(runtime)
|
||||
body := map[string]interface{}{}
|
||||
@@ -152,7 +140,6 @@ func dryRunDashboardBlockUpdate(_ context.Context, runtime *common.RuntimeContex
|
||||
Body(body)
|
||||
}
|
||||
|
||||
// dryRunDashboardBlockDelete returns a DryRunAPI for deleting a dashboard block.
|
||||
func dryRunDashboardBlockDelete(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
|
||||
return dryRunDashboardBase(runtime).
|
||||
DELETE("/open-apis/base/v3/bases/:base_token/dashboards/:dashboard_id/blocks/:block_id")
|
||||
@@ -160,7 +147,6 @@ func dryRunDashboardBlockDelete(_ context.Context, runtime *common.RuntimeContex
|
||||
|
||||
// ── Dashboard CRUD ──────────────────────────────────────────────────
|
||||
|
||||
// executeDashboardList lists all dashboards in a base.
|
||||
func executeDashboardList(runtime *common.RuntimeContext) error {
|
||||
params := map[string]interface{}{}
|
||||
if pageSize := strings.TrimSpace(runtime.Str("page-size")); pageSize != "" {
|
||||
@@ -177,7 +163,6 @@ func executeDashboardList(runtime *common.RuntimeContext) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// executeDashboardGet retrieves a dashboard by ID.
|
||||
func executeDashboardGet(runtime *common.RuntimeContext) error {
|
||||
data, err := baseV3Call(runtime, "GET", baseV3Path("bases", runtime.Str("base-token"), "dashboards", runtime.Str("dashboard-id")), nil, nil)
|
||||
if err != nil {
|
||||
@@ -187,7 +172,6 @@ func executeDashboardGet(runtime *common.RuntimeContext) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// executeDashboardCreate creates a new dashboard.
|
||||
func executeDashboardCreate(runtime *common.RuntimeContext) error {
|
||||
body := map[string]interface{}{"name": runtime.Str("name")}
|
||||
if themeStyle := strings.TrimSpace(runtime.Str("theme-style")); themeStyle != "" {
|
||||
@@ -201,7 +185,6 @@ func executeDashboardCreate(runtime *common.RuntimeContext) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// executeDashboardUpdate updates an existing dashboard.
|
||||
func executeDashboardUpdate(runtime *common.RuntimeContext) error {
|
||||
body := map[string]interface{}{}
|
||||
if name := strings.TrimSpace(runtime.Str("name")); name != "" {
|
||||
@@ -218,7 +201,6 @@ func executeDashboardUpdate(runtime *common.RuntimeContext) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// executeDashboardDelete deletes a dashboard by ID.
|
||||
func executeDashboardDelete(runtime *common.RuntimeContext) error {
|
||||
_, err := baseV3Call(runtime, "DELETE", baseV3Path("bases", runtime.Str("base-token"), "dashboards", runtime.Str("dashboard-id")), nil, nil)
|
||||
if err != nil {
|
||||
@@ -230,7 +212,6 @@ func executeDashboardDelete(runtime *common.RuntimeContext) error {
|
||||
|
||||
// ── Dashboard Block CRUD ────────────────────────────────────────────
|
||||
|
||||
// executeDashboardBlockList lists all blocks in a dashboard.
|
||||
func executeDashboardBlockList(runtime *common.RuntimeContext) error {
|
||||
params := map[string]interface{}{}
|
||||
if pageSize := strings.TrimSpace(runtime.Str("page-size")); pageSize != "" {
|
||||
@@ -247,7 +228,6 @@ func executeDashboardBlockList(runtime *common.RuntimeContext) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// executeDashboardBlockGet retrieves a dashboard block by ID.
|
||||
func executeDashboardBlockGet(runtime *common.RuntimeContext) error {
|
||||
params := map[string]interface{}{}
|
||||
if userIDType := strings.TrimSpace(runtime.Str("user-id-type")); userIDType != "" {
|
||||
@@ -261,7 +241,6 @@ func executeDashboardBlockGet(runtime *common.RuntimeContext) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// executeDashboardBlockCreate creates a new dashboard block.
|
||||
func executeDashboardBlockCreate(runtime *common.RuntimeContext) error {
|
||||
pc := newParseCtx(runtime)
|
||||
body := map[string]interface{}{}
|
||||
@@ -292,7 +271,6 @@ func executeDashboardBlockCreate(runtime *common.RuntimeContext) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// executeDashboardBlockUpdate updates an existing dashboard block.
|
||||
func executeDashboardBlockUpdate(runtime *common.RuntimeContext) error {
|
||||
pc := newParseCtx(runtime)
|
||||
body := map[string]interface{}{}
|
||||
@@ -319,7 +297,6 @@ func executeDashboardBlockUpdate(runtime *common.RuntimeContext) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// executeDashboardBlockDelete deletes a dashboard block by ID.
|
||||
func executeDashboardBlockDelete(runtime *common.RuntimeContext) error {
|
||||
_, err := baseV3Call(runtime, "DELETE", baseV3Path("bases", runtime.Str("base-token"), "dashboards", runtime.Str("dashboard-id"), "blocks", runtime.Str("block-id")), nil, nil)
|
||||
if err != nil {
|
||||
@@ -328,36 +305,3 @@ func executeDashboardBlockDelete(runtime *common.RuntimeContext) error {
|
||||
runtime.Out(map[string]interface{}{"deleted": true, "block_id": runtime.Str("block-id")}, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ── Dashboard Arrange ────────────────────────────────────────────────
|
||||
|
||||
// dryRunDashboardArrange returns a DryRunAPI for the dashboard arrange endpoint.
|
||||
func dryRunDashboardArrange(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
|
||||
params := map[string]interface{}{}
|
||||
if userIDType := strings.TrimSpace(runtime.Str("user-id-type")); userIDType != "" {
|
||||
params["user_id_type"] = userIDType
|
||||
}
|
||||
return dryRunDashboardBase(runtime).
|
||||
POST("/open-apis/base/v3/bases/:base_token/dashboards/:dashboard_id/arrange").
|
||||
Params(params).
|
||||
Body(map[string]interface{}{})
|
||||
}
|
||||
|
||||
// executeDashboardArrange sends a POST request to auto-arrange dashboard blocks layout.
|
||||
func executeDashboardArrange(runtime *common.RuntimeContext) error {
|
||||
params := map[string]interface{}{}
|
||||
if userIDType := strings.TrimSpace(runtime.Str("user-id-type")); userIDType != "" {
|
||||
params["user_id_type"] = userIDType
|
||||
}
|
||||
// 请求体为空对象,由服务端智能重排
|
||||
data, err := baseV3Call(runtime, "POST", baseV3Path("bases", runtime.Str("base-token"), "dashboards", runtime.Str("dashboard-id"), "arrange"), params, map[string]interface{}{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if data == nil {
|
||||
data = map[string]interface{}{}
|
||||
}
|
||||
data["arranged"] = true
|
||||
runtime.Out(data, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -945,8 +945,6 @@ func sleepBetweenBatches(index int, total int) {
|
||||
|
||||
// ── Dashboard Block data_config normalization & validation ───────────
|
||||
|
||||
// normalizeDataConfig normalizes data_config fields for dashboard blocks.
|
||||
// It converts series[].rollup to uppercase and group_by[].sort fields to lowercase.
|
||||
func normalizeDataConfig(cfg map[string]interface{}) map[string]interface{} {
|
||||
if cfg == nil {
|
||||
return nil
|
||||
@@ -988,21 +986,8 @@ func normalizeDataConfig(cfg map[string]interface{}) map[string]interface{} {
|
||||
return out
|
||||
}
|
||||
|
||||
// validateBlockDataConfig validates data_config based on block type.
|
||||
// For text type, it checks for the presence of text field.
|
||||
// For chart types, it validates table_name, series/count_all, group_by, and filter fields.
|
||||
func validateBlockDataConfig(blockType string, cfg map[string]interface{}) []string {
|
||||
var errs []string
|
||||
|
||||
// text 类型特殊校验:只需要有 text 字段即可
|
||||
if strings.ToLower(blockType) == "text" {
|
||||
if txt, _ := cfg["text"].(string); strings.TrimSpace(txt) == "" {
|
||||
errs = append(errs, "text 类型组件缺少必填字段 text")
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
// 图表类型通用校验
|
||||
// table_name 必填
|
||||
if tn, _ := cfg["table_name"].(string); strings.TrimSpace(tn) == "" {
|
||||
errs = append(errs, "缺少必填字段 table_name")
|
||||
|
||||
@@ -71,7 +71,6 @@ func Shortcuts() []common.Shortcut {
|
||||
BaseDashboardCreate,
|
||||
BaseDashboardUpdate,
|
||||
BaseDashboardDelete,
|
||||
BaseDashboardArrange,
|
||||
BaseDashboardBlockList,
|
||||
BaseDashboardBlockGet,
|
||||
BaseDashboardBlockCreate,
|
||||
|
||||
@@ -312,4 +312,4 @@ https://{domain}/base/{base-token}?table={table-id}&view={view-id}
|
||||
| [`form commands`](references/lark-base-form-create.md) | `+form-list / +form-get / +form-create / +form-update / +form-delete` |
|
||||
| [`form questions commands`](references/lark-base-form-questions-create.md) | `+form-questions-list / +form-questions-create / +form-questions-update / +form-questions-delete` |
|
||||
| [`workflow commands`](references/lark-base-workflow.md) | `+workflow-list / +workflow-get / +workflow-create / +workflow-update / +workflow-enable / +workflow-disable` |
|
||||
| [`dashboard / dashboard-block commands`](references/lark-base-dashboard.md) | `+dashboard-list / +dashboard-get / +dashboard-create / +dashboard-update / +dashboard-delete / +dashboard-arrange / +dashboard-block-list / +dashboard-block-get / +dashboard-block-create / +dashboard-block-update / +dashboard-block-delete` |
|
||||
| [`dashboard / dashboard-block commands`](references/lark-base-dashboard.md) | `+dashboard-list / +dashboard-get / +dashboard-create / +dashboard-update / +dashboard-delete / +dashboard-block-list / +dashboard-block-get / +dashboard-block-create / +dashboard-block-update / +dashboard-block-delete` |
|
||||
|
||||
@@ -18,7 +18,6 @@ Block 的 `data_config` 字段因 `type` 不同而变化。本文档描述所有
|
||||
| `wordCloud` | 词云 |
|
||||
| `radar` | 雷达图 |
|
||||
| `statistics` | 指标卡 |
|
||||
| `text` | 文本(支持 Markdown) |
|
||||
|
||||
## 字段类型与操作符速查(AI 决策用)
|
||||
|
||||
@@ -46,29 +45,6 @@ Block 的 `data_config` 字段因 `type` 不同而变化。本文档描述所有
|
||||
| `filter.conjunction` | `"and"` / `"or"` | 筛选逻辑 |
|
||||
| `filter.conditions` | `[{ "field_name", "operator", "value" }]` | 筛选条件数组,value 类型因字段类型而异(见下方 filter 格式规则) |
|
||||
|
||||
### text 类型特殊结构
|
||||
|
||||
`text` 类型组件用于展示富文本内容,**不需要数据源配置**(无 `table_name`、`series`、`group_by`、`filter`)。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `text` | string | **必填**。支持 Markdown 语法,详见下方说明 |
|
||||
|
||||
**支持的 Markdown 语法:**
|
||||
|
||||
| 语法 | 示例 | 效果 |
|
||||
|------|------|------|
|
||||
| 一级标题 | `# 标题` | 大标题 |
|
||||
| 二级标题 | `## 标题` | 中标题 |
|
||||
| 三级标题 | `### 标题` | 小标题 |
|
||||
| 加粗 | `**文字**` | **文字** |
|
||||
| 斜体 | `*文字*` | *文字* |
|
||||
| 删除线 | `~~文字~~` | ~~文字~~ |
|
||||
| 有序列表 | `1. 项目` | 1. 项目 |
|
||||
| 无序列表 | `- 项目` | - 项目 |
|
||||
|
||||
> **注意**:以上未提及的 Markdown 语法(如链接、图片、代码块、表格等)均不支持。
|
||||
|
||||
## group_by 详细说明
|
||||
|
||||
### mode 枚举
|
||||
@@ -162,10 +138,8 @@ Block 的 `data_config` 字段因 `type` 不同而变化。本文档描述所有
|
||||
## 约束与本地校验
|
||||
|
||||
- 必填与互斥
|
||||
- 图表类型必填:`table_name`
|
||||
- text 类型必填:`text`
|
||||
- 互斥:`series` 与 `count_all` 二选一,且至少提供其一(仅图表类型)
|
||||
- text 类型**不支持**:`series`、`count_all`、`group_by`、`filter`
|
||||
- 必填:`table_name`
|
||||
- 互斥:`series` 与 `count_all` 二选一,且至少提供其一
|
||||
- 长度/结构
|
||||
- `group_by` 最多 2 个;每项 `field_name` 必填
|
||||
- `group_by[].sort.type` 取值 `group|value|view`;`order` 取值 `asc|desc`
|
||||
@@ -173,8 +147,7 @@ Block 的 `data_config` 字段因 `type` 不同而变化。本文档描述所有
|
||||
- `series[].rollup` 自动转成大写(如 `sum` → `SUM`)
|
||||
- `group_by[].sort.type/order` 自动转成小写
|
||||
- 本地校验(可通过 `--no-validate` 跳过)
|
||||
- `+dashboard-block-create` 默认对 `data_config` 做轻量校验;失败会聚合错误并给出修复建议
|
||||
- `+dashboard-block-update` 不做强类型校验,由后端验证具体字段
|
||||
- `+dashboard-block-create/update` 默认对 `data_config` 做轻量校验;失败会聚合错误并给出修复建议
|
||||
- 仅需传入合法 JSON;CLI 不会擅自改写你的业务含义
|
||||
|
||||
## 可复制模板
|
||||
@@ -314,16 +287,6 @@ Block 的 `data_config` 字段因 `type` 不同而变化。本文档描述所有
|
||||
}
|
||||
```
|
||||
|
||||
文本组件(Markdown 富文本):
|
||||
|
||||
```json
|
||||
{
|
||||
"text": "# 🚀 一级标题\n这是一个 **加粗** *斜体* ~~删除线~~ 的示例。\n\n## 📌 二级标题\n1. 有序列表项 1\n2. 有序列表项 2\n\n### 📌 三级标题\n- 无序列表项 1\n- 无序列表项 2"
|
||||
}
|
||||
```
|
||||
|
||||
> **注意**:text 类型组件不需要 `table_name`、`series`、`group_by`、`filter` 等数据源相关字段。
|
||||
|
||||
## 常见错误与修复
|
||||
|
||||
- 同时存在 `series` 与 `count_all`
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
# base +dashboard-arrange
|
||||
|
||||
> **前置条件:** 先阅读 [lark-base-dashboard.md](lark-base-dashboard.md) 了解整体工作流
|
||||
|
||||
自动重新排列仪表盘组件布局。服务端根据组件数量和类型进行智能布局优化。
|
||||
|
||||
## 使用场景
|
||||
|
||||
| 场景 | 说明 |
|
||||
|------|------|
|
||||
| **从 0 到 1 搭建后** | 使用 `+dashboard-create` 和 `+dashboard-block-create` 创建仪表盘后,默认布局可能不够工整美观,推荐使用本命令做一次整体重排 |
|
||||
| **用户明确要求** | 用户主动要求对已有仪表盘进行布局重排或美化时 |
|
||||
|
||||
> [!CAUTION]
|
||||
> - **不建议**在已有仪表盘上自动调用此命令,除非用户明确要求
|
||||
> - 排列结果是**服务端智能推荐**,不一定完全符合用户预期
|
||||
> - 无法指定具体位置(如"第一排放 A,第二排放 B"),排列逻辑是**自适应**的
|
||||
|
||||
## 推荐命令
|
||||
|
||||
```bash
|
||||
# 基础用法
|
||||
lark-cli base +dashboard-arrange \
|
||||
--base-token xxx \
|
||||
--dashboard-id blk_xxx
|
||||
```
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `--base-token <token>` | 是 | Base Token |
|
||||
| `--dashboard-id <id>` | 是 | 仪表盘 ID |
|
||||
| `--user-id-type <type>` | 否 | 用户 ID 类型:open_id / union_id / user_id |
|
||||
| `--dry-run` | 否 | 预览 API 调用,不执行 |
|
||||
|
||||
## 返回示例
|
||||
|
||||
```json
|
||||
{
|
||||
"dashboard_id": "blk_xxx",
|
||||
"name": "数据分析",
|
||||
"blocks": [
|
||||
{
|
||||
"block_id": "chtbxxxx",
|
||||
"block_name": "总销售额",
|
||||
"block_type": "statistics",
|
||||
"layout": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 6,
|
||||
"h": 6
|
||||
}
|
||||
},
|
||||
{
|
||||
"block_id": "chtbcrxxxx",
|
||||
"block_name": "月度趋势",
|
||||
"block_type": "column",
|
||||
"layout": {
|
||||
"x": 6,
|
||||
"y": 0,
|
||||
"w": 6,
|
||||
"h": 6
|
||||
}
|
||||
}
|
||||
],
|
||||
"arranged": true
|
||||
}
|
||||
```
|
||||
|
||||
## 返回重点
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `blocks[].layout` | 重排后的布局信息,包含 x/y/w/h |
|
||||
| `arranged` | 是否重排成功 |
|
||||
|
||||
> [!CAUTION]
|
||||
> 这是**写入操作** — 执行前必须向用户确认。
|
||||
|
||||
## 参考
|
||||
|
||||
- [lark-base-dashboard.md](lark-base-dashboard.md) — dashboard 模块指引
|
||||
@@ -22,14 +22,6 @@ lark-cli base +dashboard-block-create \
|
||||
--type statistics \
|
||||
--data-config '{"table_name":"订单表","count_all":true}'
|
||||
|
||||
# 文本组件示例(Markdown 富文本)
|
||||
lark-cli base +dashboard-block-create \
|
||||
--base-token xxx \
|
||||
--dashboard-id blk_xxx \
|
||||
--name "说明文字" \
|
||||
--type text \
|
||||
--data-config '{"text":"# 标题\n## 副标题\n**加粗** *斜体* ~~删除~~\n1. 列表1\n2. 列表2"}'
|
||||
|
||||
# 复杂配置用文件传入
|
||||
lark-cli base +dashboard-block-create \
|
||||
--base-token xxx \
|
||||
@@ -48,8 +40,8 @@ lark-cli base +dashboard-block-create \
|
||||
| `--base-token <token>` | 是 | Base Token |
|
||||
| `--dashboard-id <id>` | 是 | 仪表盘 ID(从 `+dashboard-list/get` 获取) |
|
||||
| `--name <name>` | **是** | 组件名称(允许重名) |
|
||||
| `--type <type>` | **是** | 组件类型,见下方枚举值。**不同 type 对应不同的 data_config 结构**,常用:`column`(柱状图)、`line`(折线图)、`pie`(饼图)、`statistics`(指标卡)、`text`(文本) |
|
||||
| `--data-config <json>` | 否 | 数据配置 JSON,**结构随 type 变化**。**⚠️ 必须阅读 [dashboard-block-data-config.md](dashboard-block-data-config.md) 了解如何构造**。创建时会做本地校验,更新时由后端校验 |
|
||||
| `--type <type>` | **是** | 组件类型,见下方枚举值。**不同 type 对应不同的 data_config 结构**,常用:`column`(柱状图)、`line`(折线图)、`pie`(饼图)、`statistics`(指标卡) |
|
||||
| `--data-config <json>` | 否 | 数据配置 JSON,**结构随 type 变化**。**⚠️ 必须阅读 [dashboard-block-data-config.md](dashboard-block-data-config.md) 了解如何构造** |
|
||||
| `--user-id-type <type>` | 否 | 用户 ID 类型,filter 涉及人员字段时使用 |
|
||||
| `--dry-run` | 否 | 预览 API 调用,不执行 |
|
||||
|
||||
@@ -69,7 +61,6 @@ lark-cli base +dashboard-block-create \
|
||||
| `wordCloud` | 词云 |
|
||||
| `radar` | 雷达图 |
|
||||
| `statistics` | 指标卡 |
|
||||
| `text` | 文本(支持 Markdown) |
|
||||
|
||||
## 返回示例
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ Dashboard 是 Base 中的数据可视化看板,可以把表格数据变成**
|
||||
| 在仪表盘里添加组件 | `+dashboard-block-create` | 先读 [lark-base-dashboard-block-create.md](lark-base-dashboard-block-create.md),再读 [dashboard-block-data-config.md](dashboard-block-data-config.md) |
|
||||
| 修改组件 | `+dashboard-block-update` | 先读 [lark-base-dashboard-block-update.md](lark-base-dashboard-block-update.md),再读 [dashboard-block-data-config.md](dashboard-block-data-config.md) |
|
||||
| 查看仪表盘有哪些组件 | `+dashboard-get` 或 `+dashboard-block-list` | 本页下方「查看仪表盘」 |
|
||||
| 智能重排组件布局 | `+dashboard-arrange` | [lark-base-dashboard-arrange.md](lark-base-dashboard-arrange.md) |
|
||||
|
||||
## 典型场景工作流
|
||||
|
||||
@@ -59,12 +58,6 @@ lark-cli base +dashboard-block-create \
|
||||
--data-config '{"table_name":"订单表","series":[{"field_name":"金额","rollup":"SUM"}],"group_by":[{"field_name":"月份","mode":"integrated"}]}'
|
||||
|
||||
# 继续创建其他组件...
|
||||
|
||||
# 第 5 步:组件创建完成后,使用 arrange 命令智能重排布局(可选但推荐)
|
||||
# 默认布局可能不够美观,arrange 会根据组件数量和类型自动优化布局
|
||||
lark-cli base +dashboard-arrange \
|
||||
--base-token xxx \
|
||||
--dashboard-id blk_xxx
|
||||
```
|
||||
|
||||
### 场景 2:在已有仪表盘上添加新组件
|
||||
@@ -126,26 +119,7 @@ lark-cli base +dashboard-block-update \
|
||||
--data-config '{...}'
|
||||
```
|
||||
|
||||
### 场景 4:重排仪表盘布局
|
||||
|
||||
当用户明确要求对已有仪表盘进行布局重排或美化时使用。
|
||||
|
||||
> [!CAUTION]
|
||||
> - 排列结果是**服务端智能推荐**,不一定完全符合用户预期
|
||||
> - 无法指定具体位置(如"第一排放 A,第二排放 B"),排列逻辑是**自适应**的
|
||||
> - **不建议**在已有仪表盘上自动调用,除非用户明确要求
|
||||
|
||||
```bash
|
||||
# 第 1 步:列出仪表盘,定位到目标仪表盘
|
||||
lark-cli base +dashboard-list --base-token xxx
|
||||
|
||||
# 第 2 步:执行智能重排
|
||||
lark-cli base +dashboard-arrange \
|
||||
--base-token xxx \
|
||||
--dashboard-id blk_xxx
|
||||
```
|
||||
|
||||
### 场景 5:读取仪表盘或组件现状
|
||||
### 场景 4:读取仪表盘或组件现状
|
||||
|
||||
**选择查询方式:**
|
||||
- 想看仪表盘整体结构(含主题、所有组件名称和类型)→ 用 **方式 A**
|
||||
@@ -180,7 +154,6 @@ lark-cli base +dashboard-block-get --base-token xxx --dashboard-id blk_xxx --blo
|
||||
| 类别比较(谁高谁低) | column | 柱状图组件 |
|
||||
| 占比分布(各部分比例) | pie | 饼图组件 |
|
||||
| 单个关键指标 | statistics | 指标卡组件 |
|
||||
| 富文本说明/标题/注释 | text | 文本组件(支持 Markdown) |
|
||||
|
||||
详细组件类型和 data_config 完整规则:[dashboard-block-data-config.md](dashboard-block-data-config.md)
|
||||
|
||||
@@ -232,7 +205,6 @@ A: 在「添加新组件」或「编辑组件」前查看已有组件可以:
|
||||
| `+dashboard-create` | 创建仪表盘 | [lark-base-dashboard-create.md](lark-base-dashboard-create.md) |
|
||||
| `+dashboard-update` | 修改仪表盘 | [lark-base-dashboard-update.md](lark-base-dashboard-update.md) |
|
||||
| `+dashboard-delete` | 删除仪表盘 | [lark-base-dashboard-delete.md](lark-base-dashboard-delete.md) |
|
||||
| `+dashboard-arrange` | 智能重排布局 | [lark-base-dashboard-arrange.md](lark-base-dashboard-arrange.md) |
|
||||
| `+dashboard-block-list` | 列出组件 | [lark-base-dashboard-block-list.md](lark-base-dashboard-block-list.md) |
|
||||
| `+dashboard-block-get` | 获取单个组件详情 | [lark-base-dashboard-block-get.md](lark-base-dashboard-block-get.md) |
|
||||
| `+dashboard-block-create` | 创建组件 | [lark-base-dashboard-block-create.md](lark-base-dashboard-block-create.md) |
|
||||
|
||||
Reference in New Issue
Block a user