mirror of
https://github.com/larksuite/cli.git
synced 2026-07-03 14:02:43 +08:00
feat(base): add dashboard block data shortcut and workflow docs (#1067)
Change-Id: I52c471886bdb2d4b7be021ce86c34bbb78385017
This commit is contained in:
committed by
GitHub
parent
e18ea9a2e8
commit
0e6274d947
@@ -268,6 +268,39 @@ func TestBaseDashboardBlockExecuteGet(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// TestBaseDashboardBlockExecuteGetData tests the +dashboard-block-get-data command.
|
||||
func TestBaseDashboardBlockExecuteGetData(t *testing.T) {
|
||||
factory, stdout, reg := newExecuteFactory(t)
|
||||
reg.Register(&httpmock.Stub{
|
||||
Method: "GET",
|
||||
URL: "/open-apis/base/v3/bases/app_x/dashboards/blocks/blk_chart/data",
|
||||
Body: map[string]interface{}{
|
||||
"code": 0,
|
||||
"data": map[string]interface{}{
|
||||
"dimensions": []interface{}{
|
||||
map[string]interface{}{"field_name": "文本", "alias": "dim_text"},
|
||||
},
|
||||
"measures": []interface{}{
|
||||
map[string]interface{}{"field_name": "Bitable_Dashboard_Count", "aggregation": "count_all", "alias": "me_count"},
|
||||
},
|
||||
"main_data": []interface{}{
|
||||
map[string]interface{}{
|
||||
"dim_text": map[string]interface{}{"value": "A"},
|
||||
"me_count": map[string]interface{}{"value": 3},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err := runShortcut(t, BaseDashboardBlockGetData, []string{"+dashboard-block-get-data", "--base-token", "app_x", "--block-id", "blk_chart"}, factory, stdout); err != nil {
|
||||
t.Fatalf("err=%v", err)
|
||||
}
|
||||
got := stdout.String()
|
||||
if !strings.Contains(got, `"dimensions"`) || !strings.Contains(got, `"main_data"`) || !strings.Contains(got, `"dim_text"`) {
|
||||
t.Fatalf("stdout=%s", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestBaseDashboardBlockExecuteCreate tests the +dashboard-block-create command.
|
||||
func TestBaseDashboardBlockExecuteCreate(t *testing.T) {
|
||||
t.Run("with data-config", func(t *testing.T) {
|
||||
@@ -537,6 +570,19 @@ func TestBaseDashboardBlockDryRun_Get(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestBaseDashboardBlockDryRun_GetData tests the +dashboard-block-get-data --dry-run flag.
|
||||
func TestBaseDashboardBlockDryRun_GetData(t *testing.T) {
|
||||
factory, stdout, _ := newExecuteFactory(t)
|
||||
args := []string{"+dashboard-block-get-data", "--base-token", "app_x", "--block-id", "blk_a", "--dry-run", "--format", "pretty"}
|
||||
if err := runShortcut(t, BaseDashboardBlockGetData, args, factory, stdout); err != nil {
|
||||
t.Fatalf("err=%v", err)
|
||||
}
|
||||
got := stdout.String()
|
||||
if !strings.Contains(got, "GET /open-apis/base/v3/bases/app_x/dashboards/blocks/blk_a/data") || !strings.Contains(got, "blk_a") {
|
||||
t.Fatalf("stdout=%s", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestBaseDashboardBlockDryRun_Create tests the +dashboard-block-create --dry-run flag.
|
||||
func TestBaseDashboardBlockDryRun_Create(t *testing.T) {
|
||||
factory, stdout, _ := newExecuteFactory(t)
|
||||
|
||||
@@ -146,7 +146,7 @@ func TestShortcutsCatalog(t *testing.T) {
|
||||
"+form-questions-create", "+form-questions-delete", "+form-questions-update", "+form-questions-list",
|
||||
"+form-submit",
|
||||
"+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-block-list", "+dashboard-block-get", "+dashboard-block-get-data", "+dashboard-block-create", "+dashboard-block-update", "+dashboard-block-delete",
|
||||
}
|
||||
if len(shortcuts) != len(want) {
|
||||
t.Fatalf("len(shortcuts)=%d want=%d", len(shortcuts), len(want))
|
||||
|
||||
36
shortcuts/base/dashboard_block_get_data.go
Normal file
36
shortcuts/base/dashboard_block_get_data.go
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package base
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/larksuite/cli/shortcuts/common"
|
||||
)
|
||||
|
||||
var BaseDashboardBlockGetData = common.Shortcut{
|
||||
Service: "base",
|
||||
Command: "+dashboard-block-get-data",
|
||||
Description: "Get computed data for a dashboard chart block",
|
||||
Risk: "read",
|
||||
Scopes: []string{"base:dashboard:read"},
|
||||
AuthTypes: authTypes(),
|
||||
HasFormat: true,
|
||||
Flags: []common.Flag{
|
||||
baseTokenFlag(true),
|
||||
blockIDFlag(true),
|
||||
},
|
||||
Tips: []string{
|
||||
"lark-cli base +dashboard-block-get-data --base-token <base_token> --block-id <block_id>",
|
||||
"Use +dashboard-block-get first when you need block metadata like name, type, or data_config.",
|
||||
"This command returns computed chart protocol JSON directly, not wrapped block metadata.",
|
||||
"Text blocks do not have computed chart data; this shortcut is for chart/statistics blocks.",
|
||||
},
|
||||
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
|
||||
return dryRunDashboardBlockGetData(ctx, runtime)
|
||||
},
|
||||
Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
|
||||
return executeDashboardBlockGetData(runtime)
|
||||
},
|
||||
}
|
||||
@@ -104,6 +104,14 @@ func dryRunDashboardBlockGet(_ context.Context, runtime *common.RuntimeContext)
|
||||
Params(params)
|
||||
}
|
||||
|
||||
// dryRunDashboardBlockGetData returns a DryRunAPI for getting computed data for a dashboard block.
|
||||
func dryRunDashboardBlockGetData(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
|
||||
return common.NewDryRunAPI().
|
||||
GET("/open-apis/base/v3/bases/:base_token/dashboards/blocks/:block_id/data").
|
||||
Set("base_token", runtime.Str("base-token")).
|
||||
Set("block_id", runtime.Str("block-id"))
|
||||
}
|
||||
|
||||
// dryRunDashboardBlockCreate returns a DryRunAPI for creating a dashboard block.
|
||||
func dryRunDashboardBlockCreate(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
|
||||
pc := newParseCtx(runtime)
|
||||
@@ -261,6 +269,16 @@ func executeDashboardBlockGet(runtime *common.RuntimeContext) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// executeDashboardBlockGetData retrieves computed data for a dashboard chart block.
|
||||
func executeDashboardBlockGetData(runtime *common.RuntimeContext) error {
|
||||
data, err := baseV3Call(runtime, "GET", baseV3Path("bases", runtime.Str("base-token"), "dashboards", "blocks", runtime.Str("block-id"), "data"), nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
runtime.Out(data, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
// executeDashboardBlockCreate creates a new dashboard block.
|
||||
func executeDashboardBlockCreate(runtime *common.RuntimeContext) error {
|
||||
pc := newParseCtx(runtime)
|
||||
|
||||
@@ -84,6 +84,7 @@ func Shortcuts() []common.Shortcut {
|
||||
BaseDashboardArrange,
|
||||
BaseDashboardBlockList,
|
||||
BaseDashboardBlockGet,
|
||||
BaseDashboardBlockGetData,
|
||||
BaseDashboardBlockCreate,
|
||||
BaseDashboardBlockUpdate,
|
||||
BaseDashboardBlockDelete,
|
||||
|
||||
@@ -97,6 +97,14 @@ func TestRegisterShortcutsMountsBaseCommands(t *testing.T) {
|
||||
if workspaceCmd == nil || workspaceCmd.Name() != "+base-get" {
|
||||
t.Fatalf("base workspace shortcut not mounted: %#v", workspaceCmd)
|
||||
}
|
||||
|
||||
blockDataCmd, _, err := program.Find([]string{"base", "+dashboard-block-get-data"})
|
||||
if err != nil {
|
||||
t.Fatalf("find dashboard block get-data shortcut: %v", err)
|
||||
}
|
||||
if blockDataCmd == nil || blockDataCmd.Name() != "+dashboard-block-get-data" {
|
||||
t.Fatalf("base dashboard block get-data shortcut not mounted: %#v", blockDataCmd)
|
||||
}
|
||||
}
|
||||
|
||||
// Service-level cobra commands created by RegisterShortcuts must carry
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: lark-base
|
||||
version: 1.2.1
|
||||
version: 1.2.2
|
||||
description: "当需要用 lark-cli 操作飞书多维表格(Base)时调用:搜索 Base、建表、字段管理、记录读写、记录分享链接、视图配置、历史查询,以及角色/表单/仪表盘管理/工作流;也适用于把旧的 +table / +field / +record 写法改成当前命令写法。涉及字段设计、公式字段、查找引用、跨表计算、行级派生指标、数据分析需求时也必须使用本 skill。"
|
||||
metadata:
|
||||
requires:
|
||||
@@ -178,6 +178,7 @@ metadata:
|
||||
| `+dashboard-list / +dashboard-get` | 列出仪表盘,或获取仪表盘详情 | [`lark-base-dashboard-list.md`](references/lark-base-dashboard-list.md)、[`lark-base-dashboard-get.md`](references/lark-base-dashboard-get.md)、[`lark-base-dashboard.md`](references/lark-base-dashboard.md) | 进入仪表盘语义后先读 guide;`+dashboard-list` 只能串行执行 |
|
||||
| `+dashboard-create / +dashboard-update / +dashboard-delete` | 创建、更新或删除仪表盘 | [`lark-base-dashboard-create.md`](references/lark-base-dashboard-create.md)、[`lark-base-dashboard-update.md`](references/lark-base-dashboard-update.md)、[`lark-base-dashboard-delete.md`](references/lark-base-dashboard-delete.md)、[`lark-base-dashboard.md`](references/lark-base-dashboard.md) | 创建前先明确看板目标和展示场景;更新前先读取当前配置;删除前先确认目标 |
|
||||
| `+dashboard-block-list / +dashboard-block-get` | 列出图表组件,或获取单个 block 详情 | [`lark-base-dashboard-block-list.md`](references/lark-base-dashboard-block-list.md)、[`lark-base-dashboard-block-get.md`](references/lark-base-dashboard-block-get.md)、[`lark-base-dashboard.md`](references/lark-base-dashboard.md)、[`dashboard-block-data-config.md`](references/dashboard-block-data-config.md) | `+dashboard-block-list` 只能串行执行;查看配置细节时读 block config 文档 |
|
||||
| `+dashboard-block-get-data` | 获取图表组件的计算结果 | [`lark-base-dashboard-block-get-data.md`](references/lark-base-dashboard-block-get-data.md)、[`lark-base-dashboard.md`](references/lark-base-dashboard.md)、[`dashboard-block-data-config.md`](references/dashboard-block-data-config.md) | 适合读取图表组件的最终计算结果;此命令不返回 block 元数据,只返回计算结果 |
|
||||
| `+dashboard-block-create / +dashboard-block-update / +dashboard-block-delete` | 创建、更新或删除图表组件 | [`lark-base-dashboard-block-create.md`](references/lark-base-dashboard-block-create.md)、[`lark-base-dashboard-block-update.md`](references/lark-base-dashboard-block-update.md)、[`lark-base-dashboard-block-delete.md`](references/lark-base-dashboard-block-delete.md)、[`lark-base-dashboard.md`](references/lark-base-dashboard.md)、[`dashboard-block-data-config.md`](references/dashboard-block-data-config.md) | 涉及 `data_config`、图表类型、filter 时要读 block config 文档;删除前先确认目标 |
|
||||
|
||||
### 2.8 表单模块
|
||||
|
||||
@@ -0,0 +1,717 @@
|
||||
# base +dashboard-block-get-data
|
||||
|
||||
> **前置条件:** 先阅读 [lark-base-dashboard.md](lark-base-dashboard.md) 了解 dashboard 整体工作流。
|
||||
|
||||
获取仪表盘图表组件(block)的**最终计算结果**,返回一份适合 AI 直接消费的图表协议 JSON。
|
||||
|
||||
这个命令适合以下场景:
|
||||
|
||||
1. 读取柱状图 / 条形图 / 折线图 / 饼图 / 环形图 / 面积图 / 组合图 / 散点图 / 漏斗图 / 雷达图 / 词云 / 指标卡的**实际计算结果**;
|
||||
2. 把图表结果交给 AI 做后续总结、趋势解释、同比/环比说明、异常点提取;
|
||||
3. 在**不读取原始记录**的前提下,直接消费图表层已经聚合好的结果;
|
||||
4. 验证某个图表当前展示的数据是否符合预期。
|
||||
|
||||
> [!IMPORTANT]
|
||||
> - 本命令返回的是**图表结果协议**,不是 block 元数据;
|
||||
> - 如果你需要 `name`、`type`、`layout`、`data_config` 等配置,请先用 [`+dashboard-block-get`](lark-base-dashboard-block-get.md);
|
||||
> - 文本组件(`text`)不涉及计算,不适用本命令;
|
||||
|
||||
## 一句话理解
|
||||
|
||||
`+dashboard-block-get-data` = **拿图表“算出来的结果”**,而不是拿图表“怎么配置的”。
|
||||
|
||||
---
|
||||
|
||||
## 支持的图表类型
|
||||
|
||||
当前支持以下图表类型的数据计算与返回:
|
||||
|
||||
### 二维图表(10 种)
|
||||
|
||||
- 柱状图
|
||||
- 条形图
|
||||
- 折线图
|
||||
- 饼图
|
||||
- 环形图
|
||||
- 面积图
|
||||
- 组合图
|
||||
- 散点图
|
||||
- 漏斗图
|
||||
- 雷达图
|
||||
|
||||
### 特殊类型(2 种)
|
||||
|
||||
- 词云
|
||||
- 指标卡(statistics)
|
||||
|
||||
> [!CAUTION]
|
||||
> 文本组件虽然也属于 dashboard block,但它不产生可计算数据,因此不会返回本协议。
|
||||
|
||||
---
|
||||
|
||||
## 推荐命令
|
||||
|
||||
```bash
|
||||
lark-cli base +dashboard-block-get-data \
|
||||
--base-token bascn***************CtadY \
|
||||
--block-id chtxxxxxxxx
|
||||
```
|
||||
|
||||
如果你还不知道目标 block 的 ID,典型顺序是:
|
||||
|
||||
```bash
|
||||
# 先看仪表盘里有哪些组件
|
||||
lark-cli base +dashboard-block-list \
|
||||
--base-token bascn***************CtadY \
|
||||
--dashboard-id blkxxxxxxxx
|
||||
|
||||
# 再读取某个组件的最终计算结果
|
||||
lark-cli base +dashboard-block-get-data \
|
||||
--base-token bascn***************CtadY \
|
||||
--block-id chtxxxxxxxx
|
||||
```
|
||||
|
||||
如果你需要先确认组件类型、名称或 `data_config`,请先执行:
|
||||
|
||||
```bash
|
||||
lark-cli base +dashboard-block-get \
|
||||
--base-token bascn***************CtadY \
|
||||
--dashboard-id blkxxxxxxxx \
|
||||
--block-id chtxxxxxxxx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `--base-token <token>` | 是 | Base Token,标识目标多维表格 |
|
||||
| `--block-id <id>` | 是 | 图表 Block ID,即目标组件的唯一标识 |
|
||||
| `--format <fmt>` | 否 | 输出格式,遵循 CLI 全局输出格式规则 |
|
||||
| `--dry-run` | 否 | 只预览 API 调用,不真正执行 |
|
||||
|
||||
> [!TIP]
|
||||
> 这个命令**不需要** `--dashboard-id`。只要 `base_token + block_id` 即可定位并读取图表结果。
|
||||
|
||||
---
|
||||
|
||||
## 返回结构总览
|
||||
|
||||
服务端响应外层仍然是标准 OpenAPI 包装:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"dimensions": [...],
|
||||
"measures": [...],
|
||||
"main_data": [...]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
其中 `data` 就是 CLI 图表协议本体。不同图表类型的 `data` 结构略有不同:
|
||||
|
||||
| 图表类型 | 一定有 | 可能有 |
|
||||
|----------|--------|--------|
|
||||
| 二维图表 | `dimensions` / `measures` / `main_data` | 无 |
|
||||
| 词云 | `dimensions` / `measures` / `main_data` | 无 |
|
||||
| 指标卡 | `dimensions` / `measures` / `main_data` | `comparison_data` / `trend_data` |
|
||||
|
||||
---
|
||||
|
||||
## 协议字段说明
|
||||
|
||||
### 1) `dimensions`
|
||||
|
||||
维度定义数组,告诉你主结果里每个 `dim_*` key 代表什么字段。
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"field_name": "文本",
|
||||
"alias": "dim_5bKp"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
字段含义:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `field_name` | 维度字段显示名称 |
|
||||
| `alias` | 维度别名,在 `main_data` / `trend_data` 中作为 key 使用 |
|
||||
|
||||
### 2) `measures`
|
||||
|
||||
指标定义数组,告诉你每个 `me_*` key 代表什么聚合指标。
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"field_name": "Count",
|
||||
"aggregation": "count_all",
|
||||
"alias": "me_Y291bnRfYWxsX0NvdW50"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
字段含义:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `field_name` | 统计该指标时所使用的字段名称;当 `aggregation = count_all` 时固定为 `Count`,表示统计记录总数 |
|
||||
| `aggregation` | 聚合方式,常见值:`count_all` / `count` / `sum` / `avg` / `min` / `max` |
|
||||
| `alias` | 指标别名,在 `main_data` / `comparison_data` / `trend_data` 中作为 key 使用 |
|
||||
|
||||
例如:
|
||||
|
||||
- 如果统计“销售额”的求和,则 `field_name = 销售额`、`aggregation = sum`
|
||||
- 如果统计记录总数,则 `field_name = Count`、`aggregation = count_all`
|
||||
|
||||
### 3) `main_data`
|
||||
|
||||
主结果集。每一行都是一个对象,key 不是字段名本身,而是 `dimensions` / `measures` 中声明过的 `alias`。
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"dim_5bKp": {"value": "A"},
|
||||
"me_Y291bnRfYWxsX0NvdW50": {"value": 3}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### 4) `comparison_data`
|
||||
|
||||
仅指标卡可能返回。表示同/环比的两个值,顺序固定为:
|
||||
|
||||
1. 当前周期值
|
||||
2. 对比周期值
|
||||
|
||||
> [!NOTE]
|
||||
> 原始协议里通常**不直接展示周期名称**,只提供对应的值。因此解释“同比”还是“环比”、以及比较窗口具体是什么,通常要结合组件配置或 UI 上下文理解。
|
||||
|
||||
### 5) `trend_data`
|
||||
|
||||
仅指标卡可能返回。表示时间序列趋势,每一行通常包含一个时间维度和一个指标值。
|
||||
|
||||
---
|
||||
|
||||
## alias 规则与读取方式
|
||||
|
||||
你不应该把 alias 当成人类可读字段名,而应把它视为**结果表里的列 ID**。
|
||||
|
||||
常见生成规则:
|
||||
|
||||
- 维度 alias:`dim_` + `base64(field_name)`
|
||||
- 指标 alias:`me_` + `base64(aggregation + "_" + field_name)`
|
||||
|
||||
> [!NOTE]
|
||||
> 为了便于阅读,本文档中的部分示例会使用**简化后的 alias**(例如 `dim_xxx`、`me_xxx` 或较短的示例值),不保证和真实返回值逐字符一致。
|
||||
> 在实际读取结果时,应始终以 `dimensions` / `measures` 中声明的 alias 为准,而不要假设所有示例都严格展开成完整编码值。
|
||||
|
||||
例如:
|
||||
|
||||
```json
|
||||
{
|
||||
"dimensions": [
|
||||
{"field_name": "文本", "alias": "dim_5bKp"}
|
||||
],
|
||||
"measures": [
|
||||
{"field_name": "Count", "aggregation": "count_all", "alias": "me_xxx"}
|
||||
],
|
||||
"main_data": [
|
||||
{
|
||||
"dim_5bKp": {"value": "A"},
|
||||
"me_xxx": {"value": 3}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
应解读为:
|
||||
|
||||
- `dim_5bKp` 对应字段“文本”,取值是 `A`
|
||||
- `me_xxx` 对应指标 `count_all(Count)`,取值是 `3`
|
||||
|
||||
> [!TIP]
|
||||
> 读取结果时,**先看 `dimensions` / `measures`,再解 `main_data`**。不要仅凭 alias 名字猜含义。
|
||||
|
||||
---
|
||||
|
||||
## 各图表类型的协议细节
|
||||
|
||||
### 一、二维图表
|
||||
|
||||
适用于:柱状图、条形图、折线图、饼图、环形图、面积图、组合图、散点图、漏斗图、雷达图。
|
||||
|
||||
#### 结构特征
|
||||
|
||||
- `dimensions`:通常有 `1~2` 个维度
|
||||
- 不分组聚合时:通常 1 个维度
|
||||
- 开启分组聚合时:通常 2 个维度
|
||||
- `measures`:指标定义数组
|
||||
- `main_data`:按“维度组合”展开后的行数据
|
||||
|
||||
#### 这类数据代表什么
|
||||
|
||||
二维图表返回的本质上是一张**聚合结果表**:
|
||||
|
||||
- 每一行代表一个维度值,或一组维度组合;
|
||||
- 每一个 measure 值代表该维度下算出来的指标结果;
|
||||
- 如果图表开启了分组聚合,那么每一行表示“主维度 + 分组维度”的一个组合结果;
|
||||
- 如果图表是折线图、面积图这类带时间轴的图,通常可以把第一维理解为横轴、把 measure 理解为纵轴数值;
|
||||
- 如果图表是饼图、环形图这类占比图,通常可以把每一行理解为一个扇区对应的分类及其数值。
|
||||
|
||||
换句话说,AI 在读取这类结果时,可以把它当作“按某些维度聚合后的统计明细表”,适合进一步做排序、Top N、占比解释、分组对比和趋势总结。
|
||||
|
||||
#### 示例 1:普通二维图表(无分组聚合)
|
||||
|
||||
```json
|
||||
{
|
||||
"dimensions": [
|
||||
{
|
||||
"field_name": "文本",
|
||||
"alias": "dim_5bKp"
|
||||
}
|
||||
],
|
||||
"measures": [
|
||||
{
|
||||
"aggregation": "count_all",
|
||||
"field_name": "Count",
|
||||
"alias": "me_Y291bnRfYWxsX0NvdW50"
|
||||
}
|
||||
],
|
||||
"main_data": [
|
||||
{
|
||||
"dim_5bKp": {"value": "A"},
|
||||
"me_Y291bnRfYWxsX0NvdW50": {"value": 3}
|
||||
},
|
||||
{
|
||||
"dim_5bKp": {"value": "B"},
|
||||
"me_Y291bnRfYWxsX0NvdW50": {"value": 2}
|
||||
},
|
||||
{
|
||||
"dim_5bKp": {"value": "C"},
|
||||
"me_Y291bnRfYWxsX0NvdW50": {"value": 2}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
可解读为:
|
||||
|
||||
- 维度字段是“文本”
|
||||
- 指标是“按记录总数统计”
|
||||
- 当“文本”字段为 `A` 时,对应的 `Count` 指标值是 `3`
|
||||
- 当“文本”字段为 `B` 时,对应的 `Count` 指标值是 `2`
|
||||
- 当“文本”字段为 `C` 时,对应的 `Count` 指标值是 `2`
|
||||
|
||||
#### 示例 2:二维图表(开启分组聚合)
|
||||
|
||||
```json
|
||||
{
|
||||
"dimensions": [
|
||||
{
|
||||
"field_name": "文本",
|
||||
"alias": "dim_5bKp"
|
||||
},
|
||||
{
|
||||
"field_name": "单选",
|
||||
"alias": "dim_5aSl"
|
||||
}
|
||||
],
|
||||
"measures": [
|
||||
{
|
||||
"aggregation": "count_all",
|
||||
"field_name": "Count",
|
||||
"alias": "me_YW91bnR"
|
||||
}
|
||||
],
|
||||
"main_data": [
|
||||
{
|
||||
"dim_5bKp": {"value": "A"},
|
||||
"dim_5aSl": {"value": "a-1"},
|
||||
"me_YW91bnR": {"value": 2}
|
||||
},
|
||||
{
|
||||
"dim_5bKp": {"value": "A"},
|
||||
"dim_5aSl": {"value": "a-2"},
|
||||
"me_YW91bnR": {"value": 1}
|
||||
},
|
||||
{
|
||||
"dim_5bKp": {"value": "B"},
|
||||
"dim_5aSl": {"value": "b-1"},
|
||||
"me_YW91bnR": {"value": 1}
|
||||
},
|
||||
{
|
||||
"dim_5bKp": {"value": "C"},
|
||||
"dim_5aSl": {"value": "c-1"},
|
||||
"me_YW91bnR": {"value": 2}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
可解读为:
|
||||
|
||||
- 第一维是“文本”,第二维是“单选”,指标是“按记录总数统计”
|
||||
- 当“文本”字段为 `A`、且“单选”字段为 `a-1` 时,对应的指标值是 `2`
|
||||
- 当“文本”字段为 `A`、且“单选”字段为 `a-2` 时,对应的指标值是 `1`
|
||||
- 当“文本”字段为 `B`、且“单选”字段为 `b-1` 时,对应的指标值是 `1`
|
||||
- 当“文本”字段为 `C`、且“单选”字段为 `c-1` 时,对应的指标值是 `2`
|
||||
- 如果按“文本”字段汇总,那么“文本”字段为 `A` 时总指标值是 `3`;为 `B` 时总指标值是 `1`;为 `C` 时总指标值是 `2`
|
||||
|
||||
---
|
||||
|
||||
### 二、词云
|
||||
|
||||
#### 结构特征
|
||||
|
||||
词云协议仍然沿用 `dimensions + measures + main_data` 的结构,但语义稍有不同:
|
||||
|
||||
- `dimensions` 对应被分词的字段;
|
||||
- `main_data` 每一行代表一个词;
|
||||
- `measure` 的 value 表示按该词分组后计算出来的统计值。
|
||||
|
||||
#### 这类数据代表什么
|
||||
|
||||
词云返回的不是“原文列表”,而是**按词分组后的聚合统计结果**:
|
||||
|
||||
- `dimensions` 定义的是被分词的来源字段;
|
||||
- `measure` 对应的是该词在当前图表统计范围内对应的统计值,具体含义取决于聚合方式和指标字段;
|
||||
- `main_data` 的每一行都可以理解成“某个词 + 该词对应的统计结果”,其中该维度的具体 value 就是拆分出来的词;
|
||||
- 返回结果通常已经结合图表当前过滤条件、时间范围、数据权限等上下文计算完成。
|
||||
|
||||
因此,AI 读取词云数据时,更适合做“关键词排序”“热点词解释”“按词聚合结果分析”“主题归纳”,而不是把它当成逐条文本记录去理解。
|
||||
|
||||
#### 示例
|
||||
|
||||
```json
|
||||
{
|
||||
"dimensions": [
|
||||
{
|
||||
"field_name": "文本",
|
||||
"alias": "dim_5bKp"
|
||||
}
|
||||
],
|
||||
"measures": [
|
||||
{
|
||||
"aggregation": "count_all",
|
||||
"field_name": "Count",
|
||||
"alias": "me_YW91bnR"
|
||||
}
|
||||
],
|
||||
"main_data": [
|
||||
{
|
||||
"dim_5bKp": {"value": "A"},
|
||||
"me_YW91bnR": {"value": 3}
|
||||
},
|
||||
{
|
||||
"dim_5bKp": {"value": "B"},
|
||||
"me_YW91bnR": {"value": 2}
|
||||
},
|
||||
{
|
||||
"dim_5bKp": {"value": "C"},
|
||||
"me_YW91bnR": {"value": 2}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
可解读为:
|
||||
|
||||
- 被统计的分词字段是“文本”
|
||||
- 当前示例里的 measure 是 `count_all(Count)`,所以这里的统计值可以理解为“按词分组后的记录总数”
|
||||
- 当分词结果为 `A` 时,对应的统计值是 `3`
|
||||
- 当分词结果为 `B` 时,对应的统计值是 `2`
|
||||
- 当分词结果为 `C` 时,对应的统计值是 `2`
|
||||
- 按统计值排序,分词结果 `A` 对应的值最高
|
||||
- 分词结果 `B` 和 `C` 的统计值相同,说明它们处于同一梯队
|
||||
|
||||
---
|
||||
|
||||
### 三、指标卡(statistics)
|
||||
|
||||
指标卡除了主值外,还可能包含同/环比与趋势结果,是本命令里结构最特殊的一类。
|
||||
|
||||
#### 结构特征
|
||||
|
||||
- `measures`:**有且仅有一个指标**
|
||||
- `main_data`:通常只有一行,表示总指标值
|
||||
- `comparison_data`:可选,表示当前周期值与对比周期值
|
||||
- `trend_data`:可选,表示趋势序列
|
||||
- `dimensions`:可能包含同/环比日期字段、趋势日期字段
|
||||
|
||||
#### 这类数据代表什么
|
||||
|
||||
指标卡返回的核心是一个**主指标摘要**,外加可选的比较信息和趋势信息:
|
||||
|
||||
- `main_data` 表示当前卡片最核心、最醒目的那个主值;它通常是某个表的记录总数,或某个字段的聚合值,本身**不带时间周期概念**;
|
||||
- `comparison_data` 表示用于同/环比展示的两个数值,通常是“当前周期值”和“对比周期值”;它们表示某个时间周期下的记录总数,或某个字段的聚合值;
|
||||
- `trend_data` 表示这个指标在一段时间内的变化轨迹,用来支持走势判断;
|
||||
- `dimensions` 在指标卡里通常不是拿来做主分组展示,而是给 `trend_data` 或同/环比相关日期字段提供语义说明。
|
||||
|
||||
例如:
|
||||
|
||||
- `main_data = 7` 可以理解为当前卡片展示的主数据,比如某张表当前总记录数是 `7`;
|
||||
- `comparison_data[0] = 6` 则表示某个比较周期下的当前值,比如“本月记录总数 = 6”;
|
||||
- 因此,`main_data` 与 `comparison_data[0]` **不一定相等**,因为两者表达的口径并不完全相同。
|
||||
|
||||
因此,AI 在解读指标卡时,应该优先回答这几个问题:
|
||||
|
||||
1. 当前主值是多少;
|
||||
2. 和对比周期相比是上升、下降还是持平;
|
||||
3. 趋势整体是增长、波动还是下滑;
|
||||
4. 是否存在明显的异常峰值或低谷。
|
||||
|
||||
> [!NOTE]
|
||||
> 当指标卡**同时指定同/环比和趋势**时,`dimensions` 中日期维度的顺序是固定的:
|
||||
> 1. 第一个元素是**趋势**对应的日期维度;
|
||||
> 2. 第二个元素是**同/环比**对应的日期维度。
|
||||
>
|
||||
> 另外要注意:`comparison_data` 自身通常**不直接携带日期字段**,它只给出“当前周期值 / 对比周期值”。
|
||||
> `dimensions` 中的第一个日期维度会直接出现在 `trend_data` 中,作为趋势序列的时间列;
|
||||
> 第二个日期维度则主要用于补充“该卡片配置了哪类比较相关日期字段”的语义。
|
||||
|
||||
#### 示例
|
||||
|
||||
```json
|
||||
{
|
||||
"dimensions": [
|
||||
{
|
||||
"field_name": "日期",
|
||||
"alias": "dim_ZGF0ZQ"
|
||||
},
|
||||
{
|
||||
"field_name": "日期2",
|
||||
"alias": "dim_ZGF0ZTI"
|
||||
}
|
||||
],
|
||||
"measures": [
|
||||
{
|
||||
"aggregation": "count_all",
|
||||
"field_name": "Count",
|
||||
"alias": "me_YW91b"
|
||||
}
|
||||
],
|
||||
"main_data": [
|
||||
{
|
||||
"me_YW91b": {"value": 7}
|
||||
}
|
||||
],
|
||||
"comparison_data": [
|
||||
{
|
||||
"me_YW91b": {"value": 6}
|
||||
},
|
||||
{
|
||||
"me_YW91b": {"value": 0}
|
||||
}
|
||||
],
|
||||
"trend_data": [
|
||||
{
|
||||
"dim_ZGF0ZQ": {"value": "2026-01-15"},
|
||||
"me_YW91b": {"value": 1}
|
||||
},
|
||||
{
|
||||
"dim_ZGF0ZQ": {"value": "2026-01-17"},
|
||||
"me_YW91b": {"value": 1}
|
||||
},
|
||||
{
|
||||
"dim_ZGF0ZQ": {"value": "2026-03-22"},
|
||||
"me_YW91b": {"value": 1}
|
||||
},
|
||||
{
|
||||
"dim_ZGF0ZQ": {"value": "2026-04-24"},
|
||||
"me_YW91b": {"value": 2}
|
||||
},
|
||||
{
|
||||
"dim_ZGF0ZQ": {"value": "2026-05-01"},
|
||||
"me_YW91b": {"value": 1}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
可解读为:
|
||||
|
||||
- 当前主指标值 = `7`
|
||||
- 当前主指标值不带时间周期概念,可理解为当前卡片主数据
|
||||
- comparison_data[0] = 当前周期值 `6`,例如某个时间周期(如本月)下的统计值
|
||||
- comparison_data[1] = 对比周期值 `0`
|
||||
- `dimensions[0]` 对应趋势日期维度,因此实际出现在 `trend_data` 里
|
||||
- `dimensions[1]` 对应同/环比相关的日期维度,用来补充比较语义
|
||||
- trend_data 展示该指标随时间的变化序列
|
||||
- 从 comparison_data 看,当前周期相较对比周期是上升的,并且对比周期值为 0
|
||||
- 从 trend_data 看,这个指标并不是每天都有值,而是在若干离散日期出现
|
||||
- 趋势序列里的最高点出现在 `2026-04-24`,值为 `2`
|
||||
- 其余出现的日期大多为 `1`,说明整体上有波动,但暂时没有持续快速增长的趋势
|
||||
|
||||
> [!NOTE]
|
||||
> `comparison_data` 只告诉你“当前值 / 对比值”,**不额外标出日期区间文本**。如果用户需要完整说明“和上周比”还是“和上月比”,通常要结合组件配置或界面上下文进一步判断。
|
||||
|
||||
---
|
||||
|
||||
## 如何正确解读返回值
|
||||
|
||||
建议按下面顺序阅读:
|
||||
|
||||
1. **先看 `dimensions`**:确认每个 `dim_*` alias 对应哪个字段;
|
||||
2. **再看 `measures`**:确认每个 `me_*` alias 是什么聚合方式;
|
||||
3. **最后读 `main_data` / `comparison_data` / `trend_data`**:把 alias 还原成“字段名 + 指标名”再做解释。
|
||||
|
||||
### 推荐解释模板
|
||||
|
||||
如果要把结果转成自然语言,建议不要只“复述数值”,而应尽量覆盖下面几个层次:
|
||||
|
||||
1. **先解释指标含义**:说明 measure 代表“记录总数”“某字段求和”“平均值”等;
|
||||
2. **再给出核心结果**:明确当前主值、主要分类、主要组合或主要词项;
|
||||
3. **做排序或 Top N 提炼**:指出最高、最低、前几名、同一梯队;
|
||||
4. **补充分组/对比关系**:如果有第二维或 comparison_data,就说明比较对象和差异;
|
||||
5. **分析趋势或异常点**:如果有时间序列,指出上升、下降、波动、峰值、低谷;
|
||||
6. **最后给一句结论**:总结最值得关注的信息。
|
||||
|
||||
可参考下面模板:
|
||||
|
||||
- 二维图表:
|
||||
- 基础模板:`按 <维度字段> 统计,当前指标 <指标含义>;其中 <维度值1>=<指标值1>,<维度值2>=<指标值2> ...`
|
||||
- 增强模板:`按 <维度字段> 统计,当前指标表示 <指标含义>。从结果看,<Top1维度值> 的值最高,为 <Top1值>;<Top2维度值> 和 <Top3维度值> 紧随其后。若按 Top N 看,前 <N> 项合计贡献了 ...;若看低值项,<低值维度值> 最低,为 <低值>。整体上,<一句总结>`
|
||||
|
||||
- 分组聚合图表:
|
||||
- 基础模板:`按 <维度1> 统计,并以 <维度2> 分组,得到 <组合1>=<值1>,<组合2>=<值2> ...`
|
||||
- 增强模板:`当前指标表示 <指标含义>。按 <维度1> 拆分后,不同 <维度2> 组之间存在明显差异:例如 <组合1> = <值1>,<组合2> = <值2>。如果按 <维度1> 汇总,<Top1维度1值> 总值最高,为 <汇总值>;如果看组内对比,<某组> 在 <某维度1值> 下表现最强 / 最弱。整体说明 <一句总结>`
|
||||
|
||||
- 词云:
|
||||
- 基础模板:`按分词结果统计,当前指标表示 <指标含义>;其中 <词1>=<统计值1>,<词2>=<统计值2> ...`
|
||||
- 增强模板:`当前词云反映的是“按词分组后的 <指标含义>”。从结果看,<Top1词> 的值最高,为 <值1>,说明它是当前最突出的关键词;<Top2词>、<Top3词> 处于第二梯队。如果按 Top N 看,主要关注词集中在 <主题A>、<主题B>;如果有多个词数值接近,可归为同一热点层级。整体上,这组词更适合用来总结 <主题/热点/关注点>`
|
||||
|
||||
- 指标卡:
|
||||
- 基础模板:`当前主指标值为 <main_data>;当前周期值为 <comparison_data[0]>;对比周期值为 <comparison_data[1]>;趋势上 ...`
|
||||
- 增强模板:`当前主指标表示 <指标含义>,主值为 <main_data>。若看周期比较,当前周期值为 <comparison_data[0]>,对比周期值为 <comparison_data[1]>,因此整体表现为 <上升/下降/持平>。若看趋势序列,最高点出现在 <日期>,值为 <峰值>;最低点出现在 <日期>,值为 <低值>;整体走势表现为 <持续增长/阶段波动/明显回落>。如果需要给出结论,可总结为:<一句总结>`
|
||||
|
||||
> [!TIP]
|
||||
> 当用户明确要求“帮我分析”“帮我总结”“帮我找异常 / Top N / 趋势”时,优先采用增强模板,而不是只逐条复述原始数值。
|
||||
|
||||
---
|
||||
|
||||
## 常见工作流
|
||||
|
||||
### 场景 1:用户要“拿这个图表当前展示的数据”
|
||||
|
||||
```bash
|
||||
# 如果已知 block_id,直接读结果
|
||||
lark-cli base +dashboard-block-get-data \
|
||||
--base-token xxx \
|
||||
--block-id chtxxxxxxxx
|
||||
```
|
||||
|
||||
### 场景 2:用户说“帮我分析这个图表”,但你还不知道它是什么组件
|
||||
|
||||
```bash
|
||||
# 先看组件配置,确认它是不是支持计算的图表类型
|
||||
lark-cli base +dashboard-block-get \
|
||||
--base-token xxx \
|
||||
--dashboard-id blk_xxx \
|
||||
--block-id chtxxxxxxxx
|
||||
|
||||
# 再读最终计算结果
|
||||
lark-cli base +dashboard-block-get-data \
|
||||
--base-token xxx \
|
||||
--block-id chtxxxxxxxx
|
||||
```
|
||||
|
||||
### 场景 3:用户要找“仪表盘里哪个图的结果异常”
|
||||
|
||||
```bash
|
||||
# 先列组件
|
||||
lark-cli base +dashboard-block-list \
|
||||
--base-token xxx \
|
||||
--dashboard-id blk_xxx
|
||||
|
||||
# 再针对可疑 block 逐个取结果
|
||||
lark-cli base +dashboard-block-get-data \
|
||||
--base-token xxx \
|
||||
--block-id chtxxxxxxxx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 何时优先用这个命令
|
||||
|
||||
- 用户说“帮我拿这个图表算出来的数据 / 结果 / 指标”
|
||||
- 用户已经知道 `block_id`,目标是**读取结果**而不是看配置
|
||||
- 用户后续还要让 AI 对图表结果做解释、归纳、比较、总结
|
||||
- 你只关心图表层的聚合产出,不需要回到底表逐条读记录
|
||||
|
||||
## 何时不要误用
|
||||
|
||||
- 想看 block 的 `data_config`、名称、类型、布局 → 用 `+dashboard-block-get`
|
||||
- 想列出仪表盘里有哪些组件 → 用 `+dashboard-block-list`
|
||||
- 想修改或新建组件 → 用 `+dashboard-block-update` / `+dashboard-block-create`
|
||||
- 想看原始记录明细,而不是图表聚合结果 → 回到 `record-*`
|
||||
- 目标是文本组件 → 本命令不适用
|
||||
|
||||
---
|
||||
|
||||
## 常见误区
|
||||
|
||||
### 误区 1:把这个命令当成“获取 block 详情”
|
||||
|
||||
不是。这个命令不返回:
|
||||
|
||||
- block 名称
|
||||
- block 类型
|
||||
- layout
|
||||
- `data_config`
|
||||
- 所属 dashboard 信息
|
||||
|
||||
这些都应该通过 [`+dashboard-block-get`](lark-base-dashboard-block-get.md) 获取。
|
||||
|
||||
### 误区 2:以为它返回的是原始记录
|
||||
|
||||
不是。它返回的是**图表聚合后的最终结果**。如果图表本身做了过滤、分组、聚合、时间窗口限制,返回值反映的是图表视角,不是原始表全量明细。
|
||||
|
||||
### 误区 3:直接把 alias 当真实字段名读
|
||||
|
||||
不应该。alias 只是协议里的键,必须结合 `dimensions` / `measures` 还原语义。
|
||||
|
||||
### 误区 4:看到指标卡的 `comparison_data` 就以为已经知道“同比/环比周期文本”
|
||||
|
||||
不一定。它只给出比较值,不一定给出周期标签。若要精确解释比较窗口,通常还需要组件配置或 UI 上下文。
|
||||
|
||||
---
|
||||
|
||||
## dry-run 用途
|
||||
|
||||
可用来确认最终会调用的接口路径:
|
||||
|
||||
```bash
|
||||
lark-cli base +dashboard-block-get-data \
|
||||
--base-token bascn_example_token \
|
||||
--block-id chtxxxxxxxx \
|
||||
--dry-run \
|
||||
--format pretty
|
||||
```
|
||||
|
||||
你应能看到类似:
|
||||
|
||||
```text
|
||||
GET /open-apis/base/v3/bases/bascn_example_token/dashboards/blocks/chtxxxxxxxx/data
|
||||
```
|
||||
|
||||
适合在以下场景使用:
|
||||
|
||||
- 校验 `base_token` / `block_id` 是否传对;
|
||||
- 调试 agent 生成的命令;
|
||||
- 编写自动化测试时确认请求结构。
|
||||
|
||||
---
|
||||
|
||||
## 参考
|
||||
|
||||
- [lark-base-dashboard.md](lark-base-dashboard.md) — dashboard 模块总指引
|
||||
- [lark-base-dashboard-block-get.md](lark-base-dashboard-block-get.md) — 获取 block 元数据
|
||||
- [dashboard-block-data-config.md](dashboard-block-data-config.md) — data_config 结构和组件类型说明
|
||||
@@ -18,6 +18,7 @@ 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-block-get-data` | [lark-base-dashboard-block-get-data.md](lark-base-dashboard-block-get-data.md) |
|
||||
| 智能重排组件布局 | `+dashboard-arrange` | [lark-base-dashboard-arrange.md](lark-base-dashboard-arrange.md) |
|
||||
|
||||
## 典型场景工作流
|
||||
@@ -151,6 +152,7 @@ lark-cli base +dashboard-arrange \
|
||||
- 想看仪表盘整体结构(含主题、所有组件名称和类型)→ 用 **方式 A**
|
||||
- 只想快速查看有哪些组件 → 用 **方式 B**
|
||||
- 想看某个组件的详细 data_config 配置 → 用 **方式 C**
|
||||
- 想看某个图表/指标卡实际算出来的数据 → 用 **方式 D**
|
||||
|
||||
```bash
|
||||
# 第 1 步:列出仪表盘,定位到当前仪表盘
|
||||
@@ -167,6 +169,9 @@ lark-cli base +dashboard-block-list --base-token xxx --dashboard-id blk_xxx
|
||||
# 方式 C:查看某个组件的详细配置
|
||||
lark-cli base +dashboard-block-get --base-token xxx --dashboard-id blk_xxx --block-id chtxxxxxxxx
|
||||
|
||||
# 方式 D:查看某个图表组件的计算结果(AI 友好的 chart protocol)
|
||||
lark-cli base +dashboard-block-get-data --base-token xxx --block-id chtxxxxxxxx
|
||||
|
||||
# 最后:把获取到的现状信息整理好告诉用户
|
||||
```
|
||||
|
||||
@@ -223,6 +228,9 @@ A: 在「添加新组件」或「编辑组件」前查看已有组件可以:
|
||||
- 避免重复创建相似的组件
|
||||
- 参考已有组件的 data_config 结构作为模板
|
||||
|
||||
**Q: 我想直接拿图表算好的结果给 AI 分析,应该用什么?**
|
||||
A: 用 `+dashboard-block-get-data`。它直接返回图表协议 JSON(`dimensions / measures / main_data`,指标卡还可能有 `comparison_data / trend_data`),适合让 AI 做后续总结、比对和解释。注意它不返回 block 名称、类型、layout、data_config 等元数据;如果你还需要这些信息,先用 `+dashboard-block-get`。
|
||||
|
||||
## 命令详细文档
|
||||
|
||||
| CLI 命令 | 说明 | 详细文档 |
|
||||
@@ -235,6 +243,7 @@ A: 在「添加新组件」或「编辑组件」前查看已有组件可以:
|
||||
| `+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-get-data` | 获取图表组件计算结果 | [lark-base-dashboard-block-get-data.md](lark-base-dashboard-block-get-data.md) |
|
||||
| `+dashboard-block-create` | 创建组件 | [lark-base-dashboard-block-create.md](lark-base-dashboard-block-create.md) |
|
||||
| `+dashboard-block-update` | 更新组件 | [lark-base-dashboard-block-update.md](lark-base-dashboard-block-update.md) |
|
||||
| `+dashboard-block-delete` | 删除组件 | [lark-base-dashboard-block-delete.md](lark-base-dashboard-block-delete.md) |
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
| 场景 | 步骤组合 | 示例 |
|
||||
|------|---------|------|
|
||||
| 新增触发+通知 | AddRecordTrigger → LarkMessageAction | [下方](#示例1-新增记录触发--发送消息) |
|
||||
| 按钮点击+调用外部接口+写入日志 | ButtonTrigger → HTTPClientAction → AddRecordAction | [下方](#示例-6-按钮触发--调用外部接口--写入同步日志) |
|
||||
| 定时+循环 | TimerTrigger → FindRecordAction → Loop → LarkMessageAction | [下方](#示例2-定时触发--查找记录--循环遍历--发送消息) |
|
||||
| 条件判断 | ... → IfElseBranch → 分支处理 | [下方](#示例3-条件分支-ifelsebranch) |
|
||||
| 多路分类 | ... → SwitchBranch → 多分支处理 | [下方](#示例4-多路分支-switchbranch) |
|
||||
@@ -628,6 +629,119 @@
|
||||
|
||||
---
|
||||
|
||||
### 示例 6: 按钮触发 + 调用外部接口 + 写入同步日志
|
||||
|
||||
**场景**: 在「客户线索表」里给每条记录配置一个“同步到 CRM”按钮。销售点击按钮后,Workflow 调用外部 CRM 接口同步当前线索,再在「同步日志表」新增一条记录,方便后续审计和排查。
|
||||
|
||||
```json
|
||||
{
|
||||
"client_token": "1704067206",
|
||||
"title": "线索一键同步到 CRM",
|
||||
"steps": [
|
||||
{
|
||||
"id": "step_button_trigger",
|
||||
"type": "ButtonTrigger",
|
||||
"title": "点击同步到 CRM 按钮时触发",
|
||||
"next": "step_call_crm_api",
|
||||
"data": {
|
||||
"button_type": "buttonField",
|
||||
"table_name": "客户线索表"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "step_call_crm_api",
|
||||
"type": "HTTPClientAction",
|
||||
"title": "调用 CRM 同步接口",
|
||||
"next": "step_add_sync_log",
|
||||
"data": {
|
||||
"method": "POST",
|
||||
"url": [
|
||||
{ "value_type": "text", "value": "https://api.example-crm.com/v1/leads/sync" }
|
||||
],
|
||||
"headers": [
|
||||
{ "key": "Content-Type", "value": [{ "value_type": "text", "value": "application/json" }] },
|
||||
{ "key": "X-System", "value": [{ "value_type": "text", "value": "lark_base_workflow" }] }
|
||||
],
|
||||
"body_type": "raw",
|
||||
"raw_body": [
|
||||
{ "value_type": "text", "value": "{\"lead_name\":\"" },
|
||||
{ "value_type": "ref", "value": "$.step_button_trigger.fldLeadName" },
|
||||
{ "value_type": "text", "value": "\",\"mobile\":\"" },
|
||||
{ "value_type": "ref", "value": "$.step_button_trigger.fldMobile" },
|
||||
{ "value_type": "text", "value": "\",\"company\":\"" },
|
||||
{ "value_type": "ref", "value": "$.step_button_trigger.fldCompany" },
|
||||
{ "value_type": "text", "value": "\",\"owner\":\"" },
|
||||
{ "value_type": "ref", "value": "$.step_button_trigger.fldOwner" },
|
||||
{ "value_type": "text", "value": "\",\"source_record_id\":\"" },
|
||||
{ "value_type": "ref", "value": "$.step_button_trigger.recordId" },
|
||||
{ "value_type": "text", "value": "\"}" }
|
||||
],
|
||||
"response_type": "json",
|
||||
"response_value": "{\"success\":true,\"message\":\"lead synced successfully\"}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "step_add_sync_log",
|
||||
"type": "AddRecordAction",
|
||||
"title": "写入同步日志",
|
||||
"next": null,
|
||||
"data": {
|
||||
"table_name": "同步日志表",
|
||||
"field_values": [
|
||||
{
|
||||
"field_name": "线索名称",
|
||||
"value": [{ "value_type": "ref", "value": "$.step_button_trigger.fldLeadName" }]
|
||||
},
|
||||
{
|
||||
"field_name": "手机号",
|
||||
"value": [{ "value_type": "ref", "value": "$.step_button_trigger.fldMobile" }]
|
||||
},
|
||||
{
|
||||
"field_name": "公司名称",
|
||||
"value": [{ "value_type": "ref", "value": "$.step_button_trigger.fldCompany" }]
|
||||
},
|
||||
{
|
||||
"field_name": "负责人",
|
||||
"value": [{ "value_type": "ref", "value": "$.step_button_trigger.fldOwner" }]
|
||||
},
|
||||
{
|
||||
"field_name": "来源记录ID",
|
||||
"value": [{ "value_type": "ref", "value": "$.step_button_trigger.recordId" }]
|
||||
},
|
||||
{
|
||||
"field_name": "同步状态",
|
||||
"value": [{ "value_type": "text", "value": "已提交 CRM 同步" }]
|
||||
},
|
||||
{
|
||||
"field_name": "同步是否成功",
|
||||
"value": [{ "value_type": "ref", "value": "$.step_call_crm_api.body.success" }]
|
||||
},
|
||||
{
|
||||
"field_name": "同步结果说明",
|
||||
"value": [{ "value_type": "ref", "value": "$.step_call_crm_api.body.message" }]
|
||||
},
|
||||
{
|
||||
"field_name": "备注",
|
||||
"value": [{ "value_type": "text", "value": "由按钮触发自动发起同步请求" }]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**关键点**:
|
||||
- `ButtonTrigger` 适合“人工确认后再执行”的场景,比如同步 CRM、推送 ERP、发起审批等
|
||||
- `button_type: "buttonField"` 表示按钮挂在记录上,因此可以直接引用当前记录的字段和值
|
||||
- `HTTPClientAction.raw_body` 可以通过 `text + ref + text` 的方式动态拼接 JSON 请求体
|
||||
- `HTTPClientAction` 的输出引用规则是:`response_type=none` 时不可引用;`response_type=text` 时只能用 `$.stepId` 引整个文本;`response_type=json` 时用 `$.stepId.body` 引整个 body、用 `$.stepId.body.字段名` 引 body 中字段,同时 `$.stepId.status_code` 表示 HTTP 返回状态码
|
||||
- `HTTPClientAction.response_value` 中声明了哪些字段,后续节点就只能引用这些字段;例如 `$.step_call_crm_api.body.success`、`$.step_call_crm_api.body.message`
|
||||
- `AddRecordAction` 常用于写日志表、操作审计表、同步结果表,便于追踪谁在什么时候触发了外部调用
|
||||
- 示例里的 `fldLeadName` / `fldMobile` / `fldCompany` / `fldOwner` 只是占位的 fieldId,请以实际表字段 ID 为准
|
||||
|
||||
---
|
||||
|
||||
## 构造技巧
|
||||
|
||||
### Loop 构造要点
|
||||
|
||||
@@ -98,6 +98,7 @@
|
||||
| `ChangeRecordTrigger` | 记录满足条件时触发 |
|
||||
| `TimerTrigger` | 定时触发 |
|
||||
| `ReminderTrigger` | 日期提醒触发 |
|
||||
| `ButtonTrigger` | 按钮点击触发 |
|
||||
| `LarkMessageTrigger` | 接收飞书消息触发 |
|
||||
|
||||
> 所有 Trigger 节点**请勿设置** `children` ,通过 `next` 串联后继。
|
||||
@@ -120,6 +121,7 @@
|
||||
| `AddRecordAction` | 新增记录 |
|
||||
| `SetRecordAction` | 更新记录 |
|
||||
| `FindRecordAction` | 查找记录 |
|
||||
| `HTTPClientAction` | HTTP 请求 |
|
||||
| `Delay` | 延迟 |
|
||||
| `LarkMessageAction` | 发送飞书消息 |
|
||||
| `GenerateAiTextAction` | AI 生成文本 |
|
||||
@@ -256,6 +258,23 @@
|
||||
| `condition_list` | 否 | 过滤条件数组,数组中每个元素为 AndCondition 结构,多个 AndCondition 之间为 OR 关系 |
|
||||
|
||||
|
||||
### ButtonTrigger
|
||||
|
||||
```json
|
||||
{
|
||||
"button_type": "buttonField",
|
||||
"table_name": "审批表"
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `button_type` | 是 | 按钮类型:`buttonField`(表格里的按钮,可操作当前记录数据)/ `buttonElement`(仪表盘、应用页面上的按钮,可执行整体操作) |
|
||||
| `table_name` | 否 | 绑定的数据表名,仅 `button_type=buttonField` 时填写 |
|
||||
|
||||
> `buttonField` 和 `buttonElement` 的输出能力不同,详见下方「ButtonTrigger(按钮触发器)」输出说明。
|
||||
|
||||
|
||||
### LarkMessageTrigger
|
||||
|
||||
```json
|
||||
@@ -351,6 +370,48 @@
|
||||
| `filter_info` | 否* | RecordFilterInfo(与 `ref_info` 互斥) |
|
||||
| `ref_info` | 否* | RefInfo(与 `filter_info` 互斥) |
|
||||
|
||||
### HTTPClientAction
|
||||
|
||||
```json
|
||||
{
|
||||
"method": "POST",
|
||||
"url": [{ "value_type": "text", "value": "https://api.example.com/webhook" }],
|
||||
"queries": [
|
||||
{ "key": "source", "value": [{ "value_type": "text", "value": "workflow" }] }
|
||||
],
|
||||
"headers": [
|
||||
{ "key": "Content-Type", "value": [{ "value_type": "text", "value": "application/json" }] }
|
||||
],
|
||||
"body_type": "raw",
|
||||
"raw_body": [
|
||||
{ "value_type": "text", "value": "{\"record_id\":\"" },
|
||||
{ "value_type": "ref", "value": "$.step_1.recordId" },
|
||||
{ "value_type": "text", "value": "\"}" }
|
||||
],
|
||||
"response_type": "json",
|
||||
"response_value": "{\"success\":true,\"message\":\"data fetched successfully\"}"
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 必填 | 说明 |
|
||||
|------|-----|------|
|
||||
| `method` | 否 | 请求方法:`GET` / `POST` / `PUT` / `PATCH` / `DELETE`,默认 `POST` |
|
||||
| `url` | 是 | ValueInfo[],请求 URL,支持 `text` / `ref` 拼接 |
|
||||
| `queries` | 否 | KeyValue[],查询参数 |
|
||||
| `headers` | 否 | KeyValue[],请求头 |
|
||||
| `body_type` | 否 | 请求体类型:`none` / `raw` / `form-data` / `form-urlencoded`,默认 `raw` |
|
||||
| `raw_body` | 否 | ValueInfo[],原始请求体,仅 `body_type=raw` 时使用 |
|
||||
| `form_body` | 否 | KeyValue[],表单数据,仅 `body_type=form-data` 或 `body_type=form-urlencoded` 时使用 |
|
||||
| `response_type` | 否 | 响应类型:`none` / `text` / `json`,默认 `json` |
|
||||
| `response_value` | 否 | string,JSON 字符串形式的响应结果示例;仅当 `response_type=json` 时必填 |
|
||||
|
||||
`KeyValue`:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `key` | string | 参数名 / 请求头名 |
|
||||
| `value` | ValueInfo[] | 参数值 / 请求头值,支持 `text` / `ref` |
|
||||
|
||||
### Delay
|
||||
|
||||
```json
|
||||
@@ -564,8 +625,8 @@ $.{stepId}.{pathId}.{childPathId}.{grandChildPathId}
|
||||
| pathId | 说明 | 引用示例 |
|
||||
|--------|------|----------|
|
||||
| `{fieldId}` | 字段id,从配置表的所有字段或者指定字段id生成,可下钻字段属性 | `$.{stepId}.{fieldId}` |
|
||||
| `{fieldId}.fieldId` | 字段id属性 | `$.{stepId}.{fieldId}.fieldId}` |
|
||||
| `{fieldId}.fieldName` | 字段名属性 | `$.{stepId}.{fieldId}.fieldName}` |
|
||||
| `{fieldId}.fieldId` | 字段id属性 | `$.{stepId}.{fieldId}.fieldId` |
|
||||
| `{fieldId}.fieldName` | 字段名属性 | `$.{stepId}.{fieldId}.fieldName` |
|
||||
| `startTime` | 触发时间戳 | `$.{stepId}.startTime` |
|
||||
| `recordId` | 记录 ID | `$.{stepId}.recordId` |
|
||||
| `recordLink` | 记录链接 | `$.{stepId}.recordLink` |
|
||||
@@ -583,6 +644,34 @@ $.{stepId}.{pathId}.{childPathId}.{grandChildPathId}
|
||||
|
||||
**recordLink 的 children**:如果配置了数据表,则为该表所有视图的列表,每个视图 `{ pathId: viewId, pathName: viewName, pathType: 'string' }`。引用示例:`$.{stepId}.recordLink.{viewId}`。
|
||||
|
||||
##### ButtonTrigger(按钮触发器)
|
||||
|
||||
`ButtonTrigger` 的输出取决于 `button_type`:
|
||||
|
||||
#### `button_type = buttonField`
|
||||
|
||||
| pathId | 说明 | 引用示例 |
|
||||
|--------|------|----------|
|
||||
| `{fieldId}` | 字段id,从配置表的所有字段或者指定字段id生成,可下钻字段属性 | `$.{stepId}.{fieldId}` |
|
||||
| `{fieldId}.fieldId` | 字段id属性 | `$.{stepId}.{fieldId}.fieldId` |
|
||||
| `{fieldId}.fieldName` | 字段名属性 | `$.{stepId}.{fieldId}.fieldName` |
|
||||
| `recordId` | 记录 ID | `$.{stepId}.recordId` |
|
||||
| `recordLink` | 记录链接 | `$.{stepId}.recordLink` |
|
||||
| `recordCreatedUser` | 记录创建者 | `$.{stepId}.recordCreatedUser` |
|
||||
| `recordModifiedUser` | 最后修改者 | `$.{stepId}.recordModifiedUser` |
|
||||
| `recordModifiedTime` | 最后修改时间 | `$.{stepId}.recordModifiedTime` |
|
||||
| `time` | 触发时间 | `$.{stepId}.time` |
|
||||
| `user` | 触发人 | `$.{stepId}.user` |
|
||||
| `buttonName` | 触发的按钮名称 | `$.{stepId}.buttonName` |
|
||||
|
||||
#### `button_type = buttonElement`
|
||||
|
||||
| pathId | 说明 | 引用示例 |
|
||||
|--------|------|----------|
|
||||
| `time` | 触发时间 | `$.{stepId}.time` |
|
||||
| `user` | 触发人 | `$.{stepId}.user` |
|
||||
| `buttonName` | 触发的按钮名称 | `$.{stepId}.buttonName` |
|
||||
|
||||
##### TimerTrigger(定时触发器)
|
||||
|
||||
| pathId | 说明 | 引用示例 |
|
||||
@@ -633,8 +722,8 @@ $.{stepId}.{pathId}.{childPathId}.{grandChildPathId}
|
||||
| pathId | 说明 | 引用示例 |
|
||||
|--------|------|----------|
|
||||
| `{fieldId}` | 用户配置的字段值,可下钻字段属性 | `$.{stepId}.{fieldId}` |
|
||||
| `{fieldId}.fieldId` | 用户配置的字段id | `$.{stepId}.{fieldId}.fieldId}` |
|
||||
| `{fieldId}.fieldName` | 用户配置的字段名 | `$.{stepId}.{fieldId}.fieldName}` |
|
||||
| `{fieldId}.fieldId` | 用户配置的字段id | `$.{stepId}.{fieldId}.fieldId` |
|
||||
| `{fieldId}.fieldName` | 用户配置的字段名 | `$.{stepId}.{fieldId}.fieldName` |
|
||||
| `recordId` | 新增的记录 ID | `$.{stepId}.recordId` |
|
||||
| `recordLink` | 新增的记录 URL | `$.{stepId}.recordLink` |
|
||||
|
||||
@@ -643,10 +732,56 @@ $.{stepId}.{pathId}.{childPathId}.{grandChildPathId}
|
||||
| pathId | 说明 | 引用示例 |
|
||||
|--------|------|----------|
|
||||
| `{fieldId}` | 用户配置的字段值,可下钻字段属性 | `$.{stepId}.{fieldId}` |
|
||||
| `{fieldId}.fieldId` | 用户配置的字段id | `$.{stepId}.{fieldId}.fieldId}` |
|
||||
| `{fieldId}.fieldName` | 用户配置的字段名 | `$.{stepId}.{fieldId}.fieldName}` |
|
||||
| `{fieldId}.fieldId` | 用户配置的字段id | `$.{stepId}.{fieldId}.fieldId` |
|
||||
| `{fieldId}.fieldName` | 用户配置的字段名 | `$.{stepId}.{fieldId}.fieldName` |
|
||||
| `recordId` | 记录 ID 数组(因可能更新多条记录) | `$.{stepId}.recordId` |
|
||||
|
||||
##### HTTPClientAction(HTTP 请求)
|
||||
|
||||
HTTPClientAction 的输出取决于 `response_type`:
|
||||
|
||||
| response_type | 是否可引用 | 输出说明 | 引用示例 |
|
||||
|--------------|-----------|----------|----------|
|
||||
| `none` | 否 | 无任何可引用输出 | 不支持引用 |
|
||||
| `text` | 是 | 整个响应文本作为节点整体输出 | `$.{stepId}` |
|
||||
| `json` | 是 | 响应体整体挂在 `body` 下,同时返回 `status_code`;仅可引用 `response_value` 中声明的字段 | `$.{stepId}.body`、`$.{stepId}.body.success`、`$.{stepId}.body.message`、`$.{stepId}.status_code` |
|
||||
|
||||
**补充说明**:
|
||||
|
||||
- 当 `response_type = none` 时,后续节点无法引用 HTTPClientAction 的任何输出
|
||||
- 当 `response_type = text` 时,`$.{stepId}` 表示整个响应文本
|
||||
- 当 `response_type = json` 时,`$.{stepId}.body` 表示整个 JSON body,`$.{stepId}.body.字段名` 表示 body 中某个字段
|
||||
- 仅当 `response_type = json` 时,`$.{stepId}.status_code` 表示请求该 HTTP URL 后返回的 HTTP 状态码
|
||||
- 仅当 `response_type = json` 时,`response_value` 必填
|
||||
- 当 `response_type = json` 时,后续节点只能引用 `response_value` 中声明过的字段
|
||||
|
||||
**案例**:
|
||||
|
||||
假设某个 `HTTPClientAction` 的配置如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "step_http_1",
|
||||
"type": "HTTPClientAction",
|
||||
"data": {
|
||||
"response_type": "json",
|
||||
"response_value": "{\"success\":true,\"message\":\"ok\"}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
则后续节点仅可以引用:
|
||||
|
||||
- `$.step_http_1.body`
|
||||
- `$.step_http_1.body.success`
|
||||
- `$.step_http_1.body.message`
|
||||
- `$.step_http_1.status_code`
|
||||
|
||||
但**不能**引用未在 `response_value` 中声明的字段,例如:
|
||||
|
||||
- `$.step_http_1.body.data`
|
||||
- `$.step_http_1.body.request_id`
|
||||
|
||||
##### GenerateAiTextAction(AI 生成文本)
|
||||
|
||||
| pathId | 说明 | 引用示例 |
|
||||
@@ -744,11 +879,13 @@ $.{stepId}.{fieldId}.fileToken → 文件 Token 列表(array<string>,仅
|
||||
| ChangeRecordTrigger | 触发器 | ✅ | 动态(表字段 + 记录属性) |
|
||||
| SetRecordTrigger | 触发器 | ✅ | 动态(表字段 + 记录属性) |
|
||||
| ReminderTrigger | 触发器 | ✅ | 动态(表字段 + 记录属性) |
|
||||
| ButtonTrigger | 触发器 | ✅ | 动态(表字段 + 记录属性;buttonElement 仅基础触发属性) |
|
||||
| TimerTrigger | 触发器 | ✅ | 静态(仅 scheduleTime) |
|
||||
| LarkMessageTrigger | 触发器 | ✅ | 静态(消息属性列表) |
|
||||
| FindRecordAction | 动作 | ✅ | 动态(用户选择的字段) |
|
||||
| AddRecordAction | 动作 | ✅ | 动态(用户配置的字段) |
|
||||
| SetRecordAction | 动作 | ✅ | 动态(用户配置的字段) |
|
||||
| HTTPClientAction | 动作 | ✅ | 动态(取决于用户配置的 HTTP 响应输出) |
|
||||
| GenerateAiTextAction | 动作 | ✅ | 静态(单 string) |
|
||||
| Delay | 动作 | ❌ | 无输出 |
|
||||
| LarkMessageAction | 动作 | ❌ | 无输出 |
|
||||
|
||||
@@ -151,7 +151,7 @@ PUT /open-apis/base/v3/bases/:base_token/workflows/:workflow_id
|
||||
|
||||
## 坑点
|
||||
|
||||
- ⚠️ **PUT 是全量覆盖**:传什么就写什么;如果只传 `title` 不传 `steps`,原有 steps 会被清空;如需只改标题,使用 PATCH 接口(目前无对应 shortcut,可参考 API 文档直接调用)
|
||||
- ⚠️ **PUT 是全量覆盖**:传什么就写什么;如果只传 `title` 不传 `steps`,原有 steps 会被清空
|
||||
- ⚠️ **workflow_id 前缀**:以 `wkf` 开头,从 URL 的 `?table=wkf...` 提取;和 table_id(`tbl` 开头)混淆会导致 `[2200] Internal Error`
|
||||
- ⚠️ **steps 中 id 字段必须唯一**:每个步骤的 `id` 在同一工作流内必须唯一;`next` 和 `children.links[].to` 引用的 ID 必须在 steps 数组中存在
|
||||
- ⚠️ **更新不影响 enabled 状态**:`+workflow-update` 不会改变工作流的 `enabled/disabled` 状态;需要另外调用 `+workflow-enable` / `+workflow-disable`
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package base
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
clie2e "github.com/larksuite/cli/tests/cli_e2e"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBaseDashboardBlockGetDataDryRun(t *testing.T) {
|
||||
setBaseDryRunConfigEnv(t)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
result, err := clie2e.RunCmd(ctx, clie2e.Request{
|
||||
Args: []string{
|
||||
"base", "+dashboard-block-get-data",
|
||||
"--base-token", "app_x",
|
||||
"--block-id", "blk_chart",
|
||||
"--dry-run",
|
||||
},
|
||||
BinaryPath: "../../../lark-cli",
|
||||
DefaultAs: "bot",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
result.AssertExitCode(t, 0)
|
||||
|
||||
output := strings.TrimSpace(result.Stdout)
|
||||
assert.Contains(t, output, "/open-apis/base/v3/bases/app_x/dashboards/blocks/blk_chart/data")
|
||||
assert.Contains(t, output, `"method": "GET"`)
|
||||
assert.Contains(t, output, `"block_id": "blk_chart"`)
|
||||
assert.Contains(t, output, `"base_token": "app_x"`)
|
||||
}
|
||||
|
||||
func TestBaseDashboardBlockGetDataDryRun_MissingRequiredFlags(t *testing.T) {
|
||||
setBaseDryRunConfigEnv(t)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
result, err := clie2e.RunCmd(ctx, clie2e.Request{
|
||||
Args: []string{
|
||||
"base", "+dashboard-block-get-data",
|
||||
"--dry-run",
|
||||
},
|
||||
BinaryPath: "../../../lark-cli",
|
||||
DefaultAs: "bot",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.NotEqual(t, 0, result.ExitCode)
|
||||
assert.Contains(t, result.Stderr, "base-token")
|
||||
assert.Contains(t, result.Stderr, "block-id")
|
||||
}
|
||||
Reference in New Issue
Block a user