@guandata/guanwf 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +10 -0
- package/bin/run.js +26 -14
- package/binaries/guanwf-darwin-arm64 +0 -0
- package/binaries/guanwf-darwin-x64 +0 -0
- package/binaries/guanwf-linux-arm64 +0 -0
- package/binaries/guanwf-linux-x64 +0 -0
- package/binaries/guanwf-win32-x64.exe +0 -0
- package/package.json +1 -1
- package/skills/guanwf/SKILL.md +114 -129
- package/skills/guanwf/references/ETL_AI_DEVELOP.md +197 -4
- package/skills/guanwf/references/WORKFLOW_DSL.md +286 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## @guandata/guanwf 0.1.6 - 2026-06-24
|
|
4
|
+
|
|
5
|
+
- `install-skill` 适配 WorkBuddy 配置目录,提升本机编码助手安装兼容性。
|
|
6
|
+
|
|
7
|
+
## @guandata/guanwf 0.1.5 - 2026-06-15
|
|
8
|
+
|
|
9
|
+
- 工作流数据流支持 `workflow.go` DSL 模式,统一数据流创建、编辑、导出、预览、保存和运行流程。
|
|
10
|
+
- 新增 Python 节点 DSL 支持和本地校验,保存时采用三方合并以减少覆盖线上已有数据流配置的风险。
|
|
11
|
+
- 补充工作流 DSL 参考文档和测试覆盖,提升复杂数据流生成与发布链路稳定性。
|
|
12
|
+
|
|
3
13
|
## @guandata/guanwf 0.1.4 - 2026-06-03
|
|
4
14
|
|
|
5
15
|
- `install-skill` 增加 WorkBuddy skill 安装路径支持。
|
package/README.md
CHANGED
|
@@ -33,6 +33,16 @@ guanwf install-skill
|
|
|
33
33
|
|
|
34
34
|
## 版本更新
|
|
35
35
|
|
|
36
|
+
### @guandata/guanwf 0.1.6
|
|
37
|
+
|
|
38
|
+
- `install-skill` 适配 WorkBuddy 配置目录,提升本机编码助手安装兼容性。
|
|
39
|
+
|
|
40
|
+
### @guandata/guanwf 0.1.5
|
|
41
|
+
|
|
42
|
+
- 工作流数据流支持 `workflow.go` DSL 模式,统一数据流创建、编辑、导出、预览、保存和运行流程。
|
|
43
|
+
- 新增 Python 节点 DSL 支持和本地校验,保存时采用三方合并以减少覆盖线上已有数据流配置的风险。
|
|
44
|
+
- 补充工作流 DSL 参考文档和测试覆盖,提升复杂数据流生成与发布链路稳定性。
|
|
45
|
+
|
|
36
46
|
### @guandata/guanwf 0.1.4
|
|
37
47
|
|
|
38
48
|
- `install-skill` 增加 WorkBuddy skill 安装路径支持。
|
package/bin/run.js
CHANGED
|
@@ -51,20 +51,32 @@ function copyDirectory(src, dest) {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
function
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
54
|
+
function installBuddySkills(pkgRoot, skill) {
|
|
55
|
+
const srcDir = path.join(pkgRoot, "skills", skill);
|
|
56
|
+
if (!fs.existsSync(path.join(srcDir, "SKILL.md"))) {
|
|
57
|
+
console.warn(`Warning: skipping buddy install; skill not found: ${srcDir}`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
61
60
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
61
|
+
const targets = [
|
|
62
|
+
{
|
|
63
|
+
name: "CodeBuddy",
|
|
64
|
+
configDir: process.env.CODEBUDDY_CONFIG_DIR || path.join(os.homedir(), ".codebuddy"),
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: "WorkBuddy",
|
|
68
|
+
configDir: process.env.WORKBUDDY_CONFIG_DIR || path.join(os.homedir(), ".workbuddy"),
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
for (const target of targets) {
|
|
73
|
+
try {
|
|
74
|
+
const destDir = path.join(target.configDir, "skills", skill);
|
|
75
|
+
copyDirectory(srcDir, destDir);
|
|
76
|
+
console.log(`Installed ${skill} to ${target.name}: ${destDir}`);
|
|
77
|
+
} catch (err) {
|
|
78
|
+
console.warn(`Warning: ${target.name} install failed for ${skill}: ${err.message}`);
|
|
79
|
+
}
|
|
68
80
|
}
|
|
69
81
|
}
|
|
70
82
|
|
|
@@ -91,7 +103,7 @@ if (process.argv[2] === "install-skill") {
|
|
|
91
103
|
const result = spawnSync("npx", args, { stdio: "inherit", env: process.env, shell: true });
|
|
92
104
|
if (result.error) throw result.error;
|
|
93
105
|
const status = result.status || 0;
|
|
94
|
-
if (status === 0)
|
|
106
|
+
if (status === 0) installBuddySkills(pkgRoot, "guanwf");
|
|
95
107
|
process.exit(status);
|
|
96
108
|
}
|
|
97
109
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
package/skills/guanwf/SKILL.md
CHANGED
|
@@ -1,77 +1,74 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: guanwf
|
|
3
|
-
description:
|
|
3
|
+
description: 当用户要创建、编辑、保存工作流引擎中的工作流(含数据流节点、Python 节点、参数赋值等多节点 DAG),或需要预览数据流节点、校验/试运行 Python 脚本、运行工作流,或需要查询工作流/数据流列表和详情时使用。数据流是 BI ETL 的扩展,运行在独立的工作流引擎中。适用于用户说"创建一个数据流""创建一个带 Python 节点的工作流""编辑这个工作流""保存数据流""预览数据流节点""运行工作流""验证这个 Python 脚本能不能跑""列出所有数据流""查看这个工作流的定义"等场景。
|
|
4
4
|
compatibility: "Requires Node.js 14+. Install via npm link (local) or npm install -g @guandata/guanwf (from internal Nexus registry). CLI command: guanwf."
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# guanwf
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
工作流引擎工作流(ProcessDefinition)的编辑闭环工具。一个工作流是一个节点 DAG,
|
|
10
|
+
节点可以是数据流(Dataflow)、Python 脚本、参数赋值等类型。
|
|
10
11
|
|
|
11
12
|
这是一个执行型 skill,不是只读分析 skill。
|
|
12
13
|
|
|
13
14
|
- `guancli workflow` 负责查询工作流、数据流、目录和线上结构。
|
|
14
|
-
- `guanwf`
|
|
15
|
+
- `guanwf` 负责把目标工作流拉到本地工作目录,修改事实源,再完成 `export -> preview/validate -> save-draft/save -> run`。
|
|
15
16
|
|
|
16
17
|
## 核心概念
|
|
17
18
|
|
|
18
19
|
- **工作流 (Workflow / PROCESS)**: 顶层容器,包含任务节点 DAG
|
|
19
20
|
- **数据流 (Dataflow / DATAFLOW)**: 嵌入在工作流中的 ETL 流程,actions 结构与 BI ETL 相同
|
|
20
21
|
- **数据库数据流 (DB_DATAFLOW)**: 数据流的变体,支持 SQL 下推
|
|
21
|
-
-
|
|
22
|
+
- **Python 节点 (SECURE_PYTHON)**: 工作流级 Python 任务,沙箱内运行脚本,可读输入数据集并创建输出数据集
|
|
23
|
+
- **节点依赖**: 节点间用 `After`(成功后)/ `AfterFailure`(失败后)/ `AfterAny`(完成后)声明执行顺序
|
|
22
24
|
|
|
23
25
|
## 关键不变式
|
|
24
26
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
**保存工作流时,必须携带完整的工作流上下文(所有 tasks + 所有 dataflowJson)。**
|
|
28
|
+
`guanwf save` 内部会以服务端最新版本为基底做字段级合并,AI 不需要也不应该手拼保存 payload。
|
|
27
29
|
|
|
28
30
|
## Harness 工作法:源文件驱动,不手写最终 JSON
|
|
29
31
|
|
|
30
|
-
|
|
32
|
+
先改可读、可复现的源文件,再用明确命令重新生成派生产物。不要为了让保存请求看起来正确而直接改最终 JSON。
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
**如果任务是生成或修改工作流,但本轮没有修改下面任一事实源,不要汇报完成,也不要直接 save:**
|
|
33
35
|
|
|
34
|
-
- `
|
|
35
|
-
- `etl
|
|
36
|
-
- `
|
|
36
|
+
- `workflow.go`(工作流结构:节点 + 依赖)
|
|
37
|
+
- `nodes/<节点ID>/etl.go`、`nodes/<节点ID>/*.sql`(数据流节点)
|
|
38
|
+
- `nodes/<节点ID>/script.py`(Python 节点脚本)
|
|
39
|
+
- `nodes/<节点ID>/task.json`(RAW 透传节点)
|
|
40
|
+
- `nodes/<节点ID>/node_*.json`(被 etl.go 里 `LoadNodeFromFile(...)` 引用的透传节点)
|
|
37
41
|
|
|
38
|
-
|
|
42
|
+
查询、预览、运行、排查已有导出结果时可以不修改源文件;但只要目标是"生成/调整工作流逻辑",最终变更必须落在上述事实源中。
|
|
39
43
|
|
|
40
|
-
### Artifact contract
|
|
44
|
+
### 工作目录结构(Artifact contract)
|
|
41
45
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
- 数据集输入:在 `etl/etl.go` 里用 `BasicInputDataset(..., dsId, []Field{...})` 声明数据集 ID 和字段 schema。
|
|
60
|
-
- 数据库直连输入:优先在 `etl/etl.go` 里用 `BasicInputDatasource(...)` 声明账号、连接和 SQL;复杂导入节点才编辑被 `LoadNodeFromFile(...)` 引用的 `etl/node_*.json`。
|
|
61
|
-
- SQL 转换:SQL 放在 `etl/*.sql`,并由 `ReadSQLFile(...)` 引用。
|
|
62
|
-
- `_input.json` 是导入快照,不参与新建数据流配置。
|
|
46
|
+
```
|
|
47
|
+
<workdir>/
|
|
48
|
+
workflow.go # 可编辑事实源:工作流 DAG(节点 + 依赖)
|
|
49
|
+
nodes/<节点ID>/
|
|
50
|
+
etl.go # 可编辑事实源:DATAFLOW/DB_DATAFLOW 节点(guanetl DSL)
|
|
51
|
+
*.sql # 可编辑事实源:SQL 节点外置文件(ReadSQLFile 引用)
|
|
52
|
+
node_*.json # 可编辑事实源:LoadNodeFromFile 透传节点配置
|
|
53
|
+
script.py # 可编辑事实源:PYTHON 节点脚本正文
|
|
54
|
+
python.json # 服务端字段透传(edit 生成,勿手改;改配置去 workflow.go 的 PythonConfig)
|
|
55
|
+
task.json # 可编辑事实源:RAW 透传节点完整 TaskNode JSON
|
|
56
|
+
_input.json / meta.json # 导入快照/派生产物,不手改
|
|
57
|
+
_layout.json # 画布坐标(edit 保留线上坐标;新节点自动布局补位)
|
|
58
|
+
_wf_state.json # 系统状态,不手改
|
|
59
|
+
_parent_snapshot.json # 服务端快照,不手改;save 时重新拉取最新版本合并
|
|
60
|
+
_exported_workflow.json # 派生产物,不手改;由 export 重新生成
|
|
61
|
+
```
|
|
63
62
|
|
|
64
63
|
### 禁止的偷懒路径
|
|
65
64
|
|
|
66
|
-
|
|
65
|
+
- 不要直接编辑 `_exported_workflow.json` 来"修好"预览或保存。
|
|
66
|
+
- 不要直接编辑 `_parent_snapshot.json` 来拼保存 payload。
|
|
67
|
+
- 不要手改 `python.json`;输入/输出/资源配置改 `workflow.go` 的 `PythonConfig`。
|
|
68
|
+
- 不要跳过 `export`,拿上一次的导出结果去 `preview` 或 `save`。
|
|
67
69
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
- 不要把 `guancli workflow get --raw` 的结果复制成最终 JSON 后手改。
|
|
71
|
-
- 不要把新建/修改需求写进 `etl/actions.json`。
|
|
72
|
-
- 不要跳过 `export`,拿上一次的 `_exported.json` 去 `preview` 或 `save`。
|
|
73
|
-
|
|
74
|
-
如果 `export`、`preview` 或 `save` 失败,修复顺序固定为:读失败输出 -> 判断是 `etl.go`、SQL、透传节点 JSON、认证/网络还是线上任务问题 -> 修改 `etl/` 中最小责任源文件 -> 从失败步骤往后重跑。不要通过手改根目录 JSON 绕过失败。
|
|
70
|
+
如果 `export`、`preview`、`save` 或 `run` 失败,修复顺序固定为:读失败输出 -> 判断是
|
|
71
|
+
`workflow.go`、节点源文件、认证/网络还是线上任务问题 -> 修改最小责任源文件 -> 从失败步骤往后重跑。
|
|
75
72
|
|
|
76
73
|
## 认证
|
|
77
74
|
|
|
@@ -86,8 +83,8 @@ guancli auth status # 检查连接状态
|
|
|
86
83
|
### 0. 先分场景
|
|
87
84
|
|
|
88
85
|
- `只读查询`: 用 `guancli workflow`,不要创建工作目录。
|
|
89
|
-
-
|
|
90
|
-
-
|
|
86
|
+
- `新建工作流`: `guanwf create --name ... --parent-dir ...`(`--type DATAFLOW/DB_DATAFLOW/PYTHON` 控制初始节点骨架),然后编辑 `workflow.go` 和 `nodes/`。
|
|
87
|
+
- `编辑已有工作流`: `guanwf edit <workflowId>` 导入整个工作流(数据流节点 → etl.go,Python 节点 → script.py)。
|
|
91
88
|
- `修复失败`: 先定位失败发生在 `export`、`preview`、`save` 还是 `run`,只修最小责任源文件。
|
|
92
89
|
|
|
93
90
|
### 1. 缺上下文先查,不要猜
|
|
@@ -111,134 +108,115 @@ guancli workflow get <id> -f json # JSON 格式输出
|
|
|
111
108
|
数据流以 SUB_PROCESS 节点形式内嵌在工作流中(`dataFlowNode=true`),不作为独立记录存在。
|
|
112
109
|
`list` 默认展示内嵌数据流(遍历每个工作流并汇总),使用 `--show-embedded=false` 可关闭。
|
|
113
110
|
|
|
114
|
-
`get` 详情输出包含:工作流节点 DAG(区分 DATAFLOW/DB_DATAFLOW/SUB_PROCESS 节点类型)、数据流摘要(节点数/类型分布/输入输出),以及各节点的详细信息(Sources/UsedBy 依赖关系、字段、SQL 等),按拓扑排序输出(INPUT → 中间处理 → OUTPUT)。
|
|
115
|
-
|
|
116
111
|
### 2. 真正改文件前先读这些
|
|
117
112
|
|
|
118
113
|
至少先读:
|
|
119
114
|
|
|
120
|
-
- `
|
|
121
|
-
-
|
|
122
|
-
- `
|
|
123
|
-
- `references/ETL_AI_DEVELOP.md
|
|
124
|
-
- `references/WORKFLOW_NODES.md
|
|
115
|
+
- `workflow.go`
|
|
116
|
+
- 目标节点的源文件(`nodes/<节点ID>/etl.go` / `script.py` / `task.json`)
|
|
117
|
+
- `references/WORKFLOW_DSL.md`(workflow.go 写法、PythonConfig、依赖声明)
|
|
118
|
+
- `references/ETL_AI_DEVELOP.md`(数据流节点 etl.go 写法)
|
|
119
|
+
- `references/WORKFLOW_NODES.md`(数据流扩展节点)
|
|
125
120
|
|
|
126
121
|
必要时再读:
|
|
127
122
|
|
|
128
|
-
- `_input.json
|
|
129
|
-
- `
|
|
130
|
-
- `guancli workflow get <
|
|
131
|
-
|
|
132
|
-
### 3. 只改 `etl/`
|
|
133
|
-
|
|
134
|
-
只允许修改这些文件:
|
|
135
|
-
|
|
136
|
-
- `etl/etl.go`
|
|
137
|
-
- `etl/*.sql`
|
|
138
|
-
- `etl/node_*.json`
|
|
139
|
-
|
|
140
|
-
不要手改这些系统文件:
|
|
141
|
-
|
|
142
|
-
- `_workspace_state.json`
|
|
143
|
-
- `_wf_state.json`
|
|
144
|
-
- `_parent_snapshot.json`
|
|
145
|
-
- `_input.json`
|
|
146
|
-
- `_exported.json`
|
|
123
|
+
- `nodes/<节点ID>/_input.json`(只读,排查导入前结构)
|
|
124
|
+
- `_exported_workflow.json`(只读,对比导出结果)
|
|
125
|
+
- `guancli workflow get <workflowId> -f json` 的线上结构
|
|
147
126
|
|
|
148
|
-
###
|
|
127
|
+
### 3. 改完永远按这个顺序
|
|
149
128
|
|
|
150
129
|
1. `guanwf export --dir <workdir>`
|
|
151
|
-
2.
|
|
130
|
+
2. 数据流节点要看数据结果时 `guanwf preview --dir <workdir>`;Python 节点要校验时
|
|
131
|
+
`guanwf run --node "<节点名>" --validate --dir <workdir>`(需先 save-draft)
|
|
152
132
|
3. 确认无误后才 `guanwf save-draft --dir <workdir>` 或 `guanwf save --dir <workdir>`
|
|
153
133
|
4. 需要执行时再 `guanwf run --wait --dir <workdir>`
|
|
154
134
|
|
|
155
135
|
如果 `export` 没过,不要直接 `preview` 或 `save`。
|
|
156
136
|
|
|
157
|
-
##
|
|
137
|
+
## 默认流程
|
|
158
138
|
|
|
159
|
-
###
|
|
139
|
+
### 新建工作流
|
|
160
140
|
|
|
161
141
|
```bash
|
|
162
|
-
|
|
163
|
-
guanwf
|
|
164
|
-
guanwf edit <parentWorkflowId> --dataflow-id <dfId> # 指定数据流
|
|
142
|
+
guanwf create --name "我的工作流" --parent-dir <dirId> --dir <workdir> # 数据流节点骨架(默认)
|
|
143
|
+
guanwf create --name "Python清洗" --type PYTHON --parent-dir <dirId> --dir <workdir> # Python 节点骨架
|
|
165
144
|
|
|
166
|
-
#
|
|
167
|
-
# 不要改 _input.json、_parent_snapshot.json 或 _exported.json
|
|
168
|
-
|
|
169
|
-
# 导出验证(有 etl.go 时自动走 yaegi runner)
|
|
145
|
+
# 编辑 workflow.go(节点 + 依赖)和 nodes/ 下源文件后:
|
|
170
146
|
guanwf export --dir <workdir>
|
|
171
|
-
|
|
172
|
-
# 预览节点
|
|
173
|
-
guanwf preview --dir <workdir>
|
|
174
|
-
guanwf preview <nodeId> --dir <workdir>
|
|
175
|
-
|
|
176
|
-
# 保存草稿或发布
|
|
177
147
|
guanwf save-draft --dir <workdir>
|
|
178
|
-
guanwf
|
|
148
|
+
guanwf preview --dir <workdir> # 数据流节点:预览数据
|
|
149
|
+
guanwf run --node "<节点名>" --validate --dir <workdir> # Python 节点:校验(不创建输出数据集)
|
|
150
|
+
guanwf save --dir <workdir> # 正式发布
|
|
179
151
|
```
|
|
180
152
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
### 新建数据流
|
|
153
|
+
### 编辑已有工作流
|
|
184
154
|
|
|
185
155
|
```bash
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
guanwf create --name "DB数据流" --type DB_DATAFLOW --parent-dir <dirId>
|
|
156
|
+
guanwf edit <workflowId> --dir <workdir>
|
|
157
|
+
# 生成 workflow.go + nodes/<节点ID>/(数据流→etl.go,Python→script.py+python.json,其他→task.json)
|
|
189
158
|
|
|
190
|
-
#
|
|
191
|
-
|
|
192
|
-
# 导出 -> 保存
|
|
193
|
-
guanwf export --dir <workdir>
|
|
194
|
-
guanwf save-draft --dir <workdir>
|
|
159
|
+
# 修改源文件后与新建流程相同:export → preview/validate → save-draft/save
|
|
195
160
|
```
|
|
196
161
|
|
|
197
|
-
|
|
162
|
+
`edit` 会把数据流节点的线上 actions 经 guanetl json2go 导入为 `etl.go`(SQL 外置为 `*.sql`,
|
|
163
|
+
复杂节点外置为 `node_*.json` + `LoadNodeFromFile(...)`),这些都是可编辑源文件。
|
|
198
164
|
|
|
199
165
|
### 修复失败
|
|
200
166
|
|
|
201
167
|
1. 先确认失败发生在 `export`、`preview`、`save` 还是 `run`。
|
|
202
|
-
2. 如果失败来自 `export`,只改 `
|
|
203
|
-
3. 如果失败来自 `preview`,先确认 `
|
|
204
|
-
4. 如果失败来自 `save`,不要手写保存 payload
|
|
168
|
+
2. 如果失败来自 `export`,只改 `workflow.go` 或节点源文件。
|
|
169
|
+
3. 如果失败来自 `preview`,先确认 `_exported_workflow.json` 是否由最新源文件生成;必要时先重跑 `export`。
|
|
170
|
+
4. 如果失败来自 `save`,不要手写保存 payload;检查工作流是否能重新拉取、本地导出是否来自最新源文件。
|
|
205
171
|
5. 从失败步骤往后重跑,不要把整个工作目录推倒重来。
|
|
206
172
|
|
|
207
173
|
### 运行工作流
|
|
208
174
|
|
|
209
175
|
```bash
|
|
210
|
-
guanwf run --dir <workdir> #
|
|
176
|
+
guanwf run --dir <workdir> # 触发运行(已发布版本)
|
|
211
177
|
guanwf run --wait --dir <workdir> # 等待完成
|
|
212
178
|
guanwf run --wait --timeout 600 --dir <workdir> # 自定义超时
|
|
179
|
+
guanwf run --wait --logs --dir <workdir> # 完成后输出任务日志
|
|
180
|
+
guanwf run --node "<节点名>" --validate --dir <workdir> # 校验 Python 脚本(不创建输出数据集)
|
|
181
|
+
guanwf run --node "<节点名>" --draft --wait --logs --dir <workdir> # 单节点真实运行草稿
|
|
213
182
|
```
|
|
214
183
|
|
|
215
|
-
|
|
184
|
+
`--node` 按节点显示名只运行指定节点(TASK_ONLY);`--draft` 运行 save-draft 的草稿版本;
|
|
185
|
+
`--logs` 输出任务实例日志(失败时自动拉取)。
|
|
216
186
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
*.sql # SQL 节点的独立 SQL 文件
|
|
227
|
-
node_*.json # LoadNodeFromFile 透传节点配置
|
|
228
|
-
actions.json # 历史兼容输入;新建/修改不要编辑
|
|
187
|
+
**`--validate` 是验证 Python 脚本的默认方法**:临时草稿中把 `save_outputN` 替换为只打印
|
|
188
|
+
dtypes/head 的 stub,脚本完整执行但不写输出文件,服务端跳过数据集注册(不创建真实数据集),
|
|
189
|
+
结束后自动恢复原始草稿。日志中可看到输出 schema 预览。详见 `references/WORKFLOW_DSL.md`。
|
|
190
|
+
|
|
191
|
+
### 预览数据流节点
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
guanwf preview --dir <workdir> # 只有一个数据流节点时自动选中,预览其终端 action
|
|
195
|
+
guanwf preview <actionId> --dir <workdir> # 多个数据流节点时按 action id 定位
|
|
229
196
|
```
|
|
230
197
|
|
|
198
|
+
`preview` 基于 export 的本地定义直接发起预览,无需先 save。Python 节点没有 preview API,
|
|
199
|
+
校验用 `run --node "<节点名>" --validate`。
|
|
200
|
+
|
|
231
201
|
## save 的工作机制
|
|
232
202
|
|
|
233
203
|
`save` / `save-draft` 不是简单把本地 JSON 上传。内部流程是:
|
|
234
204
|
|
|
235
|
-
1. 读取本地 `
|
|
236
|
-
2.
|
|
237
|
-
3.
|
|
238
|
-
4.
|
|
239
|
-
|
|
205
|
+
1. 读取本地 `_exported_workflow.json`(必须由 `export` 重新生成)
|
|
206
|
+
2. 从服务端重新拉取工作流最新版本作为基底
|
|
207
|
+
3. tasks 按节点 id 做字段级合并:本地定义的字段覆盖,服务端独有字段(dsId、运行时注册信息等)保留
|
|
208
|
+
4. 节点成员按三方判断(服务端最新 + 本地 `_parent_snapshot.json` + 本地导出):
|
|
209
|
+
本地快照里有、导出里删了的节点 → 删除;服务端有、本地快照不知道的节点(他人并发新增)→
|
|
210
|
+
保留并打印提示
|
|
211
|
+
5. dataflowJson 按 dataflow id 经 etlmerge 做 actions 字段级合并(成员判断同上)
|
|
212
|
+
6. 调用 `/process/save-draft` 或 `/process/save`,成功后刷新本地快照并把服务端回写的
|
|
213
|
+
Python 参数同步进 `python.json`
|
|
214
|
+
|
|
215
|
+
因此用户和 AI 都不需要手写服务端保存 payload。多人/多端并发修改同一工作流时,以服务端
|
|
216
|
+
最新版本为基底合并,不会把别人新加的节点冲掉;但同一节点的并发修改仍是后保存者覆盖。
|
|
240
217
|
|
|
241
|
-
|
|
218
|
+
若他人并发新增的节点依赖了本地这次删除/改名的节点,保存会检测到断链冲突并中止,
|
|
219
|
+
此时按提示重新 `guanwf edit <工作流ID>` 同步最新版本后再修改。
|
|
242
220
|
|
|
243
221
|
## API 路由
|
|
244
222
|
|
|
@@ -254,7 +232,12 @@ guanwf run --wait --timeout 600 --dir <workdir> # 自定义超时
|
|
|
254
232
|
| 查询预览任务 | GET | `/api/offline-dev/dataflow/task/{taskId}` |
|
|
255
233
|
| 取消预览 | POST | `/api/offline-dev/dataflow/task/{taskId}/cancel` |
|
|
256
234
|
| 运行工作流 | POST | `/api/offline-dev/process/{id}/start-process-instance` |
|
|
257
|
-
| 查询运行实例 | GET | `/api/offline-dev/process/instance/{id}/select-by-id
|
|
235
|
+
| 查询运行实例 | GET | `/api/offline-dev/process/instance/{id}/select-by-id?domId=<domId>` |
|
|
236
|
+
| 实例任务列表 | GET | `/api/offline-dev/process/instance/{id}/task-list-by-process-id?domId=<domId>` |
|
|
237
|
+
| 任务日志 | GET | `/api/offline-dev/log/detail?taskInstId=<id>&logType=UI&domId=<domId>` |
|
|
238
|
+
|
|
239
|
+
实例/日志查询接口要求 `domId` 参数(工作流详情 `select-by-id` 响应中的 `domId` 字段),
|
|
240
|
+
`guanwf run` 内部自动处理。`list-paging` 请求体的搜索字段是 `searchVal`。
|
|
258
241
|
|
|
259
242
|
## 与其他 skill 的关系
|
|
260
243
|
|
|
@@ -262,12 +245,13 @@ guanwf run --wait --timeout 600 --dir <workdir> # 自定义超时
|
|
|
262
245
|
|------|------|
|
|
263
246
|
| BI ETL 编辑 | guanetl |
|
|
264
247
|
| 工作流/数据流只读查询 | guancli workflow(隐藏子命令,需手动输入) |
|
|
265
|
-
|
|
|
248
|
+
| 工作流编辑闭环 | guanwf(本工具) |
|
|
266
249
|
| 数据集管理 | guands |
|
|
250
|
+
| workflow.go DSL / Python 节点参考 | guanwf/references/WORKFLOW_DSL.md |
|
|
267
251
|
| 通用 ETL 节点函数参考 | guanwf/references/ETL_AI_DEVELOP.md(symlink → guanetl) |
|
|
268
252
|
| 工作流扩展节点参考 | guanwf/references/WORKFLOW_NODES.md |
|
|
269
253
|
|
|
270
|
-
##
|
|
254
|
+
## 工作流特有节点(数据流节点内部)
|
|
271
255
|
|
|
272
256
|
工作流数据流是 BI ETL 的超集,额外支持以下节点类型:
|
|
273
257
|
|
|
@@ -289,8 +273,9 @@ guanwf run --wait --timeout 600 --dir <workdir> # 自定义超时
|
|
|
289
273
|
|
|
290
274
|
## 注意事项
|
|
291
275
|
|
|
292
|
-
-
|
|
293
|
-
- actions 结构与 BI ETL 相同,节点函数和约束复用 guanetl 的 framework
|
|
276
|
+
- 编辑时自动获取并保存工作流 snapshot,save 时从服务端重新获取最新版本合并
|
|
277
|
+
- 数据流 actions 结构与 BI ETL 相同,节点函数和约束复用 guanetl 的 framework
|
|
278
|
+
- 节点显示名在工作流内必须唯一(依赖声明与 `run --node` 都按名字引用)
|
|
294
279
|
- DB_DATAFLOW 涉及 SQL 下推和单账号约束,后续可能需要额外处理
|
|
295
280
|
- `guancli workflow` 子命令在 `guancli --help` 中不显示(隐藏命令),但手动输入可正常使用
|
|
296
|
-
-
|
|
281
|
+
- 旧版(单数据流 `etl/` 格式)工作目录已不支持,遇到时用 `guanwf edit` 在新目录重新拉取
|
|
@@ -62,6 +62,53 @@ orders := BasicInputDataset("id_1001", "订单", "ds_orders", []Field{
|
|
|
62
62
|
- `meta.json` 主要保留 ETL 名称等元信息。
|
|
63
63
|
- 字段相关报错时,优先检查 `[]Field`,不要先怀疑 `meta.json`。
|
|
64
64
|
|
|
65
|
+
## ODS 展示格式字符串先解析再声明数值
|
|
66
|
+
|
|
67
|
+
`guancli ds get` 只能告诉你字段声明类型;ODS / mock / 平台导出表可能把 UI 展示值原样存成 `STRING`,例如 `"13小时31分"`、`"27.69%"`、`"90"`。写 DWD 前先执行 `guancli ds preview <dsId>` 看样本;如果 preview 提示 STRING 样本像展示格式,不能在 `BasicInputDataset` 里直接把该字段声明成 `DOUBLE` / `LONG` 后聚合。
|
|
68
|
+
|
|
69
|
+
`guanetl export` 会通过数据集详情接口校验输入字段 schema:服务端字段是 `STRING` 时,`BasicInputDataset` 不能把它声明成 `DOUBLE` / `LONG` / `DATE` 等非 STRING 类型。
|
|
70
|
+
|
|
71
|
+
推荐先用 `BasicCalculator` 或 SQL 显式解析成新的数值列,再让下游聚合引用新列:
|
|
72
|
+
|
|
73
|
+
```go
|
|
74
|
+
calc := BasicCalculator("id_1002", "解析展示格式度量", inputA, []Formula{
|
|
75
|
+
{
|
|
76
|
+
Name: "下单转化率_数值",
|
|
77
|
+
Type: "DOUBLE",
|
|
78
|
+
Expr: "CAST(REPLACE([下单转化率], \"%\", \"\") AS DOUBLE) / 100",
|
|
79
|
+
Key: "formula_order_rate_value",
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
Name: "营业时长_分钟",
|
|
83
|
+
Type: "DOUBLE",
|
|
84
|
+
Expr: "CAST(IF(REGEXP_EXTRACT([营业时长], \"([0-9.]+)小时\", 1) = \"\", \"0\", REGEXP_EXTRACT([营业时长], \"([0-9.]+)小时\", 1)) AS DOUBLE) * 60 + CAST(IF(REGEXP_EXTRACT([营业时长], \"([0-9.]+)分\", 1) = \"\", \"0\", REGEXP_EXTRACT([营业时长], \"([0-9.]+)分\", 1)) AS DOUBLE)",
|
|
85
|
+
Key: "formula_open_minutes",
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
Name: "店铺分_数值",
|
|
89
|
+
Type: "DOUBLE",
|
|
90
|
+
Expr: "CAST([店铺分] AS DOUBLE)",
|
|
91
|
+
Key: "formula_store_score_value",
|
|
92
|
+
},
|
|
93
|
+
}, Position{X: 320, Y: 100})
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
- 百分比字符串先去 `%`,再确认业务语义是 `27.69` 还是 `0.2769`。
|
|
97
|
+
- 中文时长要统一成明确单位,例如分钟或秒。
|
|
98
|
+
- 纯数字字符串可能是编码 / ID;确认不是维度编码后再 cast。
|
|
99
|
+
|
|
100
|
+
## 字段 name / alias / showName 规则
|
|
101
|
+
|
|
102
|
+
后端 ETL 的展示名语义主要来自字段 `alias`,有些接口或前端上下文会叫 `displayName` / `showName`。
|
|
103
|
+
|
|
104
|
+
- 新版 ETL 默认使用输入字段别名:如果数据集字段有 `alias`,进入 ETL 算子后的列名通常是 `alias`;没有 `alias` 时才是原始 `name`。
|
|
105
|
+
- 输入字段别名的事实源是数据集字段元数据;可用 `guands dataset fields <dsId>` 回读,用 `guands dataset alias <dsId> --fd-id <fdId> --alias "展示名"` 更新。`BasicInputDataset(..., []Field{...})` 中的字段 schema 主要用于导入/导出和本地 lint,不会把一个未设置过数据集 alias 的字段强制变成展示名列。
|
|
106
|
+
- 本地校验按 `alias -> displayName/showName/title -> name` 解析有效字段名;同一来源下两个字段解析成相同有效名会被视为歧义,建模前应先改名或明确上游输出。
|
|
107
|
+
- SQL 节点使用上游表 `input1`、`input2` 的当前列名。字段有中文展示名时,SQL 里应写展示名并用反引号,例如 ``SELECT `门店ID` FROM input1``。
|
|
108
|
+
- `SELECT_COLUMNS`、`FILTER_ROWS`、`REMOVE_DUPLICATES`、`GROUP_BY` 等直接列名算子,也以当前上游列名为准。
|
|
109
|
+
- 底层数据集读取和数据集元数据仍保留原始 `name`,所以 `guancli` / `guands` 输出字段时要同时关注 `name` 和 `alias`。
|
|
110
|
+
- 不确定时先执行 `guanetl lint`;它会提示疑似把 raw name 写进新版 ETL 算子的情况。
|
|
111
|
+
|
|
65
112
|
## 最常用函数
|
|
66
113
|
|
|
67
114
|
### 输入 / 输出
|
|
@@ -74,6 +121,13 @@ BasicOutputDatasetInDir(id, name string, source Node, outputDsName, parentDirId
|
|
|
74
121
|
|
|
75
122
|
`BasicOutputDatasetInDir` 允许指定输出数据集的父目录 ID,避免落到根目录。目录 ID 可通过 `guancli ds tree` 获取。
|
|
76
123
|
|
|
124
|
+
新建 ETL 时有两类目录 id,不能混用:
|
|
125
|
+
|
|
126
|
+
- `guanetl create --parent-dir` 使用 ETL 目录树 id,可通过 `guancli etl tree` 获取。
|
|
127
|
+
- `BasicOutputDatasetInDir(..., parentDirId, ...)` / `guanetl create --output-parent-dir` 使用 DATA_SET 目录树 id,可通过 `guancli ds tree` 获取。
|
|
128
|
+
- 同名目录在两棵树中的 id 通常不同;需要成对创建时优先使用 `guanetl mkdir-pair`。
|
|
129
|
+
- `create` / `save` 默认会校验目录 id 类型,确认 id 正确但账号无法读取目录树时,可加 `--skip-dir-check` 跳过。
|
|
130
|
+
|
|
77
131
|
### 选列
|
|
78
132
|
|
|
79
133
|
```go
|
|
@@ -94,7 +148,8 @@ BasicFilterRows(id, name string, source Node, combineType string, conditions []F
|
|
|
94
148
|
```
|
|
95
149
|
|
|
96
150
|
- `combineType` 只能是 `AND` 或 `OR`
|
|
97
|
-
- 高频操作符:`EQ` `NE` `LT` `LE` `GT` `GE` `IN` `BT` `
|
|
151
|
+
- 高频操作符:`EQ` `NE` `LT` `LE` `GT` `GE` `IN` `BT` `CONTAINS` `NOT_CONTAINS` `STARTSWITH` `NOT_STARTSWITH` `ENDSWITH` `NOT_ENDSWITH`
|
|
152
|
+
- 当前 `BasicFilterRows` 不支持 `IS_NULL` / `NOT_NULL`;null 过滤请改用 SQL 节点(如 ``WHERE `列名` IS NULL`` / ``IS NOT NULL``)。
|
|
98
153
|
|
|
99
154
|
### 等值 JOIN
|
|
100
155
|
|
|
@@ -112,6 +167,66 @@ BasicGroupBy(id, name string, source Node, groupByColumns []GroupByColumn, aggre
|
|
|
112
167
|
|
|
113
168
|
- 聚合类型只用:`SUM` `COUNT` `COUNT_DISTINCT` `MIN` `MAX` `AVG` `FIRST_NOT_NULL`
|
|
114
169
|
|
|
170
|
+
### 计算列
|
|
171
|
+
|
|
172
|
+
```go
|
|
173
|
+
BasicCalculator(id, name string, source Node, formulas []Formula, position Position)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
`Formula` 必须写完整四个字段:
|
|
177
|
+
|
|
178
|
+
```go
|
|
179
|
+
Formula{
|
|
180
|
+
Name: "订单金额等级",
|
|
181
|
+
Type: "STRING",
|
|
182
|
+
Expr: "IF([金额] >= 1000, \"大额\", \"普通\")",
|
|
183
|
+
Key: "formula_amount_level",
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
- `Name` 是新增列名。
|
|
188
|
+
- `Type` 是新增列类型,只用:`INT` `DOUBLE` `STRING` `TIMESTAMP` `LONG` `SHORT` `FLOAT` `DATE` `BOOL` `DECIMAL` `ARRAY`。
|
|
189
|
+
- `Expr` 是服务端公式表达式,字段引用必须写成 `[字段名]`。
|
|
190
|
+
- `Key` 在同一个 `CALCULATOR` 节点内必须唯一;建议用稳定字符串,不要留空。
|
|
191
|
+
|
|
192
|
+
`Expr` 规则:
|
|
193
|
+
|
|
194
|
+
- 字段名遵循“字段 name / alias / showName 规则”:上游字段有 `alias` 时,公式里通常写展示名,例如 `[门店ID]`,不要写 raw name `[store_id]`。
|
|
195
|
+
- 字符串字面量用双引号,例如 `"大额"`。
|
|
196
|
+
- 数值、比较、逻辑可以按 Spark SQL 表达式写,例如 `[金额] * 1.13`、`[数量] > 0 AND [金额] > 0`。
|
|
197
|
+
- 空值判断优先用 `IS NULL` / `IS NOT NULL` 或 `COALESCE`,不要写 `= NULL`。
|
|
198
|
+
- 多个公式之间尽量不要互相依赖;如果需要依赖前一个新增列,拆成两个 `BasicCalculator` 节点更稳。
|
|
199
|
+
|
|
200
|
+
常用函数(按 Spark SQL 表达式执行,函数名大小写不敏感;这里列 AI 写 ETL 时最常用、最稳的集合):
|
|
201
|
+
|
|
202
|
+
| 类别 | 函数 / 写法 | 示例 |
|
|
203
|
+
|---|---|---|
|
|
204
|
+
| 文本截取 | `LEFT` `RIGHT` `SUBSTRING` | `LEFT([门店编码], 2)` |
|
|
205
|
+
| 文本处理 | `CONCAT` `UPPER` `LOWER` `TRIM` `LENGTH` `REPLACE` | `CONCAT([省份], "-", [城市])` |
|
|
206
|
+
| 数值处理 | `ABS` `ROUND` `CEIL` `FLOOR` | `ROUND([金额], 2)` |
|
|
207
|
+
| 空值处理 | `COALESCE` | `COALESCE([金额], 0)` |
|
|
208
|
+
| 条件判断 | `IF`、`CASE WHEN ... THEN ... ELSE ... END` | `IF([金额] > 0, "有效", "无效")` |
|
|
209
|
+
| 日期时间 | `CURRENT_DATE` `CURRENT_TIMESTAMP` `DATE_FORMAT` | `DATE_FORMAT([下单日期], "yyyy-MM")` |
|
|
210
|
+
|
|
211
|
+
示例:
|
|
212
|
+
|
|
213
|
+
```go
|
|
214
|
+
calc := BasicCalculator("id_1002", "新增计算列", inputA, []Formula{
|
|
215
|
+
{
|
|
216
|
+
Name: "含税金额",
|
|
217
|
+
Type: "DOUBLE",
|
|
218
|
+
Expr: "ROUND([金额] * 1.13, 2)",
|
|
219
|
+
Key: "formula_tax_amount",
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
Name: "门店前缀",
|
|
223
|
+
Type: "STRING",
|
|
224
|
+
Expr: "LEFT([门店ID], 2)",
|
|
225
|
+
Key: "formula_store_prefix",
|
|
226
|
+
},
|
|
227
|
+
}, Position{X: 320, Y: 100})
|
|
228
|
+
```
|
|
229
|
+
|
|
115
230
|
### SQL
|
|
116
231
|
|
|
117
232
|
```go
|
|
@@ -133,7 +248,8 @@ node := BasicSqlScript(
|
|
|
133
248
|
SQL 规则:
|
|
134
249
|
|
|
135
250
|
- 上游节点在 SQL 中用 `input1`、`input2` 这类名字引用。
|
|
136
|
-
-
|
|
251
|
+
- 特殊列名、中文列名、带空格列名,以及 `AS` 输出别名中的非 ASCII 标识符,都必须用反引号。
|
|
252
|
+
- 输入数据集字段存在 `alias` / `displayName` / `showName` 时,优先使用展示名而不是 raw name。
|
|
137
253
|
- 新增 SQL 节点时,要同步新增 `.sql` 文件。
|
|
138
254
|
|
|
139
255
|
## 推荐骨架
|
|
@@ -162,6 +278,52 @@ func DefineETL() []Node {
|
|
|
162
278
|
}
|
|
163
279
|
```
|
|
164
280
|
|
|
281
|
+
### 多输出 ETL
|
|
282
|
+
|
|
283
|
+
`DefineETL()` 可以返回多个叶子输出节点。适合从同一批输入派生多张下游表,例如同时产出明细表和汇总表。
|
|
284
|
+
|
|
285
|
+
```go
|
|
286
|
+
package main
|
|
287
|
+
|
|
288
|
+
import . "guanetl/internal/framework"
|
|
289
|
+
|
|
290
|
+
func DefineETL() []Node {
|
|
291
|
+
orders := BasicInputDataset("id_1001", "订单", "ds_orders", []Field{
|
|
292
|
+
BasicField("门店", "STRING"),
|
|
293
|
+
BasicField("订单号", "STRING"),
|
|
294
|
+
BasicField("销售额", "DOUBLE"),
|
|
295
|
+
}, Position{X: 100, Y: 120})
|
|
296
|
+
|
|
297
|
+
detail := BasicSelectColumns("id_1002", "订单明细", orders, []ColumnSetting{
|
|
298
|
+
{Name: "门店"},
|
|
299
|
+
{Name: "订单号"},
|
|
300
|
+
{Name: "销售额"},
|
|
301
|
+
}, Position{X: 340, Y: 40})
|
|
302
|
+
|
|
303
|
+
summary := BasicGroupBy(
|
|
304
|
+
"id_1003",
|
|
305
|
+
"门店汇总",
|
|
306
|
+
orders,
|
|
307
|
+
[]GroupByColumn{
|
|
308
|
+
BasicGroupByColumn("门店", "STRING"),
|
|
309
|
+
},
|
|
310
|
+
[]AggregationColumn{
|
|
311
|
+
BasicAggregationColumnWithAlias("销售额", "DOUBLE", "SUM", "销售额合计"),
|
|
312
|
+
},
|
|
313
|
+
Position{X: 340, Y: 200},
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
outDetail := BasicOutputDatasetInDir("id_1004", "输出明细", detail, "订单明细表", "ds_dir_id", Position{X: 600, Y: 40})
|
|
317
|
+
outSummary := BasicOutputDatasetInDir("id_1005", "输出汇总", summary, "门店销售汇总表", "ds_dir_id", Position{X: 600, Y: 200})
|
|
318
|
+
|
|
319
|
+
return []Node{outDetail, outSummary}
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
- 每个输出都要有独立的 `OUTPUT_DATASET` 节点 id 和独立的 `outputDsName`。
|
|
324
|
+
- 多输出共享同一个 ETL 的 `save`、`run` 和调度配置;任何一个输出分支变更后,都应重新验收本 ETL 的全部输出。
|
|
325
|
+
- 如果多个输出需要独立调度、独立发布、独立回滚,或希望隔离某个输出分支的变更影响,应拆成多个单输出 ETL。
|
|
326
|
+
|
|
165
327
|
## 每次改完都检查
|
|
166
328
|
|
|
167
329
|
1. `DefineETL()` 返回的是叶子节点吗?
|
|
@@ -170,6 +332,7 @@ func DefineETL() []Node {
|
|
|
170
332
|
4. `JOIN_DATA` / `GROUP_BY` / `FILTER_ROWS` 的枚举值合法吗?
|
|
171
333
|
5. 新增 SQL 文件了吗?文件名和代码引用一致吗?
|
|
172
334
|
6. 输入字段 schema 足够支撑后续节点吗?
|
|
335
|
+
7. `BasicCalculator` 的每个 `Formula` 都显式填写 `Type` 了吗?缺失会在 `export` 后提示 warning,并可能导致下游原生 `GROUP_BY` / `JOIN_DATA` 静态检查出现类型不匹配。
|
|
173
336
|
|
|
174
337
|
## 验证顺序
|
|
175
338
|
|
|
@@ -177,10 +340,24 @@ func DefineETL() []Node {
|
|
|
177
340
|
|
|
178
341
|
1. `go run ./cmd/guanetl export --dir <work_dir>`
|
|
179
342
|
2. 需要看结果时:`go run ./cmd/guanetl preview <node_id> --dir <work_dir>`
|
|
180
|
-
3.
|
|
343
|
+
3. 保存前先看影响:`go run ./cmd/guanetl save --dir <work_dir> --dry-run`
|
|
344
|
+
4. 确认无误后:`go run ./cmd/guanetl save --dir <work_dir>`
|
|
181
345
|
|
|
182
346
|
如果 `export` 没过,不要直接 `save`。
|
|
183
347
|
|
|
348
|
+
## save 输出数据集冲突
|
|
349
|
+
|
|
350
|
+
`save` 会先拉取服务端当前 ETL,再把本地 `_exported.json` 合并进去。修改已保存 ETL 时,输出节点必须保留服务端已有输出数据集绑定,否则 direct-save 可能把它当成“创建新输出数据集”,触发“输出数据集目录中存在同名文件”。
|
|
351
|
+
|
|
352
|
+
- 再次修改已保存 ETL,优先重新执行 `guanetl edit <etl_id> --dir <新目录>`,不要长期复用旧工作目录。
|
|
353
|
+
- 只追加输出列时,可以原地 `save`:新增列,不改名、不删已有列、不改已有列类型,并保持原 `OUTPUT_DATASET` 节点 id 和 `outputDsName`。
|
|
354
|
+
- 修改已有输出 schema 时,保持原 `OUTPUT_DATASET` 节点 id;改列名、删列、改已有列类型、换输入数据集或重接输出链路都可能影响下游绑定,保存前必须重新 `preview` 并评估下游。
|
|
355
|
+
- 如果用 `guands dataset rename` 改过 ETL 输出数据集名称,必须同步 `etl.go` 中 `BasicOutputDataset(..., outputDsName, ...)` 或 `BasicOutputDatasetInDir(..., outputDsName, ...)` 的名称。
|
|
356
|
+
- 不要为了绕过同名错误手动删除旧输出数据集;旧输出通常仍被 ETL 依赖。
|
|
357
|
+
- 如果确实要创建新输出数据集,必须同时更换 `OUTPUT_DATASET` 节点 id,并设置新的 `outputDsName` 或 `parentDirId`;只改名称会被视为已有输出绑定风险。
|
|
358
|
+
- `save` 如果在 direct-save 前提示输出绑定风险,先修本地 `etl.go` / `meta.json`,再重新 `export -> preview -> save`。
|
|
359
|
+
- `save --dry-run` 只生成保存影响报告,不调用 direct-save;需要机器可读结果时加 `--format json`。报告里出现阻断级输出绑定风险时,必须先修本地定义,不能继续真实 `save`。
|
|
360
|
+
|
|
184
361
|
## Appendix: Framework Surface
|
|
185
362
|
|
|
186
363
|
这部分只列 AI 写 ETL 时最值得记住的公开函数和合法枚举。
|
|
@@ -239,6 +416,17 @@ BasicFilterColumnValue(columnName string)
|
|
|
239
416
|
ReadSQLFile(filename string)
|
|
240
417
|
```
|
|
241
418
|
|
|
419
|
+
### Formula 结构
|
|
420
|
+
|
|
421
|
+
```go
|
|
422
|
+
type Formula struct {
|
|
423
|
+
Name string `json:"name"`
|
|
424
|
+
Type string `json:"type"`
|
|
425
|
+
Expr string `json:"expr"`
|
|
426
|
+
Key string `json:"key"`
|
|
427
|
+
}
|
|
428
|
+
```
|
|
429
|
+
|
|
242
430
|
### 合法枚举
|
|
243
431
|
|
|
244
432
|
筛选组合:
|
|
@@ -252,11 +440,14 @@ ReadSQLFile(filename string)
|
|
|
252
440
|
- `LT` `LE` `GT` `GE`
|
|
253
441
|
- `IN`
|
|
254
442
|
- `BT`
|
|
255
|
-
- `IS_NULL` `NOT_NULL`
|
|
256
443
|
- `CONTAINS` `NOT_CONTAINS`
|
|
257
444
|
- `STARTSWITH` `NOT_STARTSWITH`
|
|
258
445
|
- `ENDSWITH` `NOT_ENDSWITH`
|
|
259
446
|
|
|
447
|
+
当前限制:
|
|
448
|
+
|
|
449
|
+
- `BasicFilterRows` 暂不支持 `IS_NULL` / `NOT_NULL`;null 过滤请使用 `BasicSqlScript`。
|
|
450
|
+
|
|
260
451
|
JOIN 类型:
|
|
261
452
|
|
|
262
453
|
- `INNER`
|
|
@@ -279,3 +470,5 @@ APPEND_ROWS unionType:
|
|
|
279
470
|
- `INCLUDE_SHARED`
|
|
280
471
|
- `INCLUDE_ALL`
|
|
281
472
|
- `INCLUDE_FROM`
|
|
473
|
+
|
|
474
|
+
`BasicAppendRows` 会按列名对齐各输入源。参与拼接的同名列类型必须一致;例如一侧为 `DATE`、另一侧为 `LONG` 会在 `export` 阶段报错。遇到冲突时,先用 `BasicCalculator` / `BasicSelectColumns` 把各源字段统一类型或移除不用的冲突列,再执行行拼接。
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
# 工作流 DSL(workflow.go)
|
|
2
|
+
|
|
3
|
+
`guanwf` 用一个 Go 源文件 `workflow.go` 描述整个工作流(ProcessDefinition)的节点 DAG:
|
|
4
|
+
节点类型、依赖关系、执行条件、重试与超时。各节点的具体内容放在 `nodes/<节点ID>/` 下的
|
|
5
|
+
源文件中。`workflow.go` 由 yaegi 解释执行,不需要本地 Go 编译环境。
|
|
6
|
+
|
|
7
|
+
这是 guanwf 唯一的工作目录格式:单数据流工作流就是只含一个 `DataflowNode` 的 workflow.go,
|
|
8
|
+
多节点工作流(多数据流、数据流 + Python、参数赋值等混合 DAG)按同样方式扩展节点列表。
|
|
9
|
+
|
|
10
|
+
## 工作目录结构
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
<workdir>/
|
|
14
|
+
workflow.go # 工作流结构事实源(节点 + 依赖)
|
|
15
|
+
nodes/
|
|
16
|
+
<节点ID>/
|
|
17
|
+
etl.go # DATAFLOW/DB_DATAFLOW 节点:guanetl DSL(可附 *.sql、node_*.json)
|
|
18
|
+
script.py # PYTHON 节点:脚本正文
|
|
19
|
+
python.json # PYTHON 节点:服务端字段透传(edit 生成,勿手改)
|
|
20
|
+
task.json # RAW 透传节点:完整 TaskNode JSON
|
|
21
|
+
_wf_state.json # 系统状态(mode=workflow),不手改
|
|
22
|
+
_parent_snapshot.json # 服务端快照,不手改
|
|
23
|
+
_layout.json # 节点画布坐标(edit 时保留线上坐标),可不存在
|
|
24
|
+
_exported_workflow.json # export 派生产物,不手改
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
可编辑事实源:`workflow.go`、`nodes/<id>/etl.go`、`nodes/<id>/*.sql`、`nodes/<id>/script.py`、
|
|
28
|
+
`nodes/<id>/task.json`。其余根目录 JSON 都是系统状态或派生产物。
|
|
29
|
+
|
|
30
|
+
## workflow.go 写法
|
|
31
|
+
|
|
32
|
+
文件必须是 `package main`,导入 `guanwf/wfdsl`,并定义 `DefineWorkflow() Workflow`:
|
|
33
|
+
|
|
34
|
+
```go
|
|
35
|
+
package main
|
|
36
|
+
|
|
37
|
+
import . "guanwf/wfdsl"
|
|
38
|
+
|
|
39
|
+
func DefineWorkflow() Workflow {
|
|
40
|
+
// 数据流节点:actions 写在 nodes/dataflow_1/etl.go
|
|
41
|
+
df := DataflowNode("dataflow_1", "清洗销售数据", DataflowConfig{})
|
|
42
|
+
|
|
43
|
+
// Python 节点:脚本写在 nodes/python_1/script.py
|
|
44
|
+
py := PythonNode("python_1", "汇总分析", PythonConfig{
|
|
45
|
+
InputDatasets: []string{"<输入数据集ID>"},
|
|
46
|
+
Outputs: []PythonOutput{
|
|
47
|
+
{Name: "销售汇总", ParentDirID: "<数据集目录ID>", UpdateMode: "OVERWRITE"},
|
|
48
|
+
},
|
|
49
|
+
}, After(df)) // 依赖:df 成功后运行
|
|
50
|
+
|
|
51
|
+
return Workflow{
|
|
52
|
+
Name: "销售数据加工",
|
|
53
|
+
Nodes: []*WfNode{df, py},
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 节点构造函数
|
|
59
|
+
|
|
60
|
+
| 函数 | 任务类型 | 节点源文件 |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `DataflowNode(id, name, DataflowConfig, deps...)` | SUB_PROCESS(数据流) | `nodes/<id>/etl.go` |
|
|
63
|
+
| `DBDataflowNode(id, name, DataflowConfig, deps...)` | SUB_PROCESS(DB 数据流,SQL 下推) | `nodes/<id>/etl.go` |
|
|
64
|
+
| `PythonNode(id, name, PythonConfig, deps...)` | SECURE_PYTHON | `nodes/<id>/script.py`(+ `python.json` 透传) |
|
|
65
|
+
| `RawTask(id, deps...)` | 任意(透传) | `nodes/<id>/task.json` |
|
|
66
|
+
|
|
67
|
+
- `id`:节点目录名,也是 TaskNode 的 id。同一工作流内唯一,建议用 `python_1`、`dataflow_1`
|
|
68
|
+
这类稳定短名;edit 回读的节点保留线上原 id。
|
|
69
|
+
- `name`:节点显示名。**同一工作流内必须唯一**(后端 preTasks 依赖按名字引用)。
|
|
70
|
+
- `RawTask` 不带 name 参数,名称从 `task.json` 的 `name` 字段读取。用于 DSL 未结构化建模的
|
|
71
|
+
节点类型(PARAMETER_ASSIGNMENT、SHELL、HTTP、SWITCH、旧版 PYTHON 等),除依赖外全部透传。
|
|
72
|
+
|
|
73
|
+
### 依赖与执行条件
|
|
74
|
+
|
|
75
|
+
```go
|
|
76
|
+
After(n) // n 成功后运行(SUCCESS)
|
|
77
|
+
AfterFailure(n) // n 失败后运行(FAILURE)
|
|
78
|
+
AfterAny(n) // n 结束后运行,无论成败(ALL)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
依赖作为节点构造函数的可变参数传入,可写多个:
|
|
82
|
+
|
|
83
|
+
```go
|
|
84
|
+
merge := DataflowNode("merge", "合并", DataflowConfig{}, After(a), After(b))
|
|
85
|
+
notify := RawTask("notify_fail", AfterFailure(merge))
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
导出时依赖会生成 `preTasks`(按节点名)+ `preTaskScheduleTypes`(带条件)+ `connects`(画布连线)。
|
|
89
|
+
|
|
90
|
+
### DataflowConfig / PythonConfig 公共字段
|
|
91
|
+
|
|
92
|
+
```go
|
|
93
|
+
DataflowConfig{
|
|
94
|
+
Desc: "描述", // 可选
|
|
95
|
+
Retry: RetryConfig{Max: 2, IntervalMinutes: 5}, // 可选,Max=0 不重试
|
|
96
|
+
TimeoutMinutes: 60, // 可选,>0 启用超时告警+失败策略
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`PythonConfig` 同样支持 `Desc` / `Retry` / `TimeoutMinutes`,另有 Python 专属字段见下节。
|
|
101
|
+
|
|
102
|
+
## Python 节点
|
|
103
|
+
|
|
104
|
+
### PythonConfig
|
|
105
|
+
|
|
106
|
+
```go
|
|
107
|
+
PythonConfig{
|
|
108
|
+
InputDatasets: []string{"dsId1", "dsId2"}, // 输入数据集 ID,顺序对应 input1, input2, ...
|
|
109
|
+
Outputs: []PythonOutput{ // 输出数据集,顺序对应 output1, output2, ...
|
|
110
|
+
{
|
|
111
|
+
Name: "输出数据集名", // 必填,目录内唯一
|
|
112
|
+
ParentDirID: "<数据集目录ID>", // 必填,guancli dir tree 可查
|
|
113
|
+
UpdateMode: "OVERWRITE", // OVERWRITE(覆盖)/ APPEND(追加),默认 OVERWRITE
|
|
114
|
+
PrimaryKeys: []string{"id"}, // 可选,按主键去重
|
|
115
|
+
Desc: "描述", // 可选
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
ImageID: "", // 可选,自定义 Python 镜像;默认平台镜像
|
|
119
|
+
CPU: 1, // 可选,沙箱 CPU 核数,默认 1
|
|
120
|
+
MemoryGB: 2, // 可选,沙箱内存 GB,默认 2
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
输出数据集**不需要也不应该手填 dsId**:首次运行时 BI 服务端在 `ParentDirID` 下创建并注册
|
|
125
|
+
数据集,生成的 dsId 落在服务端定义里。之后 `guanwf edit` 回读时这些字段进入
|
|
126
|
+
`nodes/<id>/python.json` 透传层,`save` 时自动保留,不会丢失。
|
|
127
|
+
|
|
128
|
+
### script.py 沙箱约定
|
|
129
|
+
|
|
130
|
+
脚本运行在平台 Python 沙箱容器中(默认镜像带 pandas),沙箱注入了数据集读写函数:
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
import pandas as pd
|
|
134
|
+
|
|
135
|
+
df = load_input1() # 读第 1 个输入数据集(InputDatasets[0]),返回 pandas DataFrame
|
|
136
|
+
df2 = load_input2() # 读第 2 个输入(如有)
|
|
137
|
+
|
|
138
|
+
result = df.groupby("大类", as_index=False).agg(总数量=("数量合计", "sum"))
|
|
139
|
+
|
|
140
|
+
save_output1(result) # 写第 1 个输出数据集(Outputs[0]),DataFrame 的列即数据集字段
|
|
141
|
+
print("rows:", len(result)) # print 输出会出现在任务日志里,可用于排查
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
约定:
|
|
145
|
+
|
|
146
|
+
- `load_inputN()` / `save_outputN(df)` 的序号 N 与 `PythonConfig` 中数组顺序一一对应(从 1 开始)。
|
|
147
|
+
- 输出数据集的字段 schema 由首次运行时 `save_outputN` 写出的 DataFrame 推断,不需要预先声明。
|
|
148
|
+
- 每个客户环境的 Python 版本和库可能不同,**生成脚本后先用单节点试运行验证**(见下节),
|
|
149
|
+
不要假设任意第三方库可用;默认镜像保证 pandas 可用。
|
|
150
|
+
|
|
151
|
+
### python.json 透传
|
|
152
|
+
|
|
153
|
+
`guanwf edit` 回读 Python 节点时会生成 `nodes/<id>/python.json`,内容是服务端 TaskNode 中
|
|
154
|
+
除脚本正文之外的完整参数(含服务端生成的 dsId、fieldMappings、k8sConfig 等)。导出时以它为
|
|
155
|
+
基底,`PythonConfig` 中显式设置的字段覆盖其上,脚本正文始终来自 `script.py`。
|
|
156
|
+
|
|
157
|
+
不要手改 `python.json`。要改输入/输出/资源配置,改 `workflow.go` 的 `PythonConfig`。
|
|
158
|
+
|
|
159
|
+
## Python 脚本环境校验(run --validate)
|
|
160
|
+
|
|
161
|
+
平台没有 Python 的静态校验接口。**校验脚本的默认方法是 `--validate` 模式**,它完整执行
|
|
162
|
+
脚本但不创建真实输出数据集:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
guanwf export --dir <workdir> # 1. 导出验证 DSL
|
|
166
|
+
guanwf save-draft --dir <workdir> # 2. 保存草稿(不影响已发布版本)
|
|
167
|
+
guanwf run --dir <workdir> --node "<节点名>" --validate # 3. 校验运行
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
`--validate` 的工作机制:
|
|
171
|
+
|
|
172
|
+
1. 在临时草稿中给所有 Python 节点脚本头部注入 stub,把 `save_outputN` 替换为只
|
|
173
|
+
`print(df.dtypes)` + `print(df.head(20))` 的实现(BI 服务端把真实 `save_outputN`
|
|
174
|
+
定义在用户脚本之前,用户脚本中的重定义会覆盖它)
|
|
175
|
+
2. 单节点试运行草稿(TASK_ONLY + DRAFT),等待完成并拉取日志
|
|
176
|
+
3. stub 不写输出文件,服务端发现无 parquet 会跳过输出数据集注册——**不创建、不导入任何
|
|
177
|
+
真实数据集**
|
|
178
|
+
4. 运行结束后自动把原始草稿(无 stub)重新保存回服务端
|
|
179
|
+
|
|
180
|
+
校验日志中能看到(`--- 脚本输出 (DETAIL) ---` 段):
|
|
181
|
+
|
|
182
|
+
- 脚本所有 `print` 输出和 Python 异常栈
|
|
183
|
+
- `[VALIDATE] save_outputN called: rows=..., cols=...`
|
|
184
|
+
- `[VALIDATE] outputN dtypes:` 输出 DataFrame 的字段类型(即未来数据集的 schema 预览)
|
|
185
|
+
- `[VALIDATE] outputN head(20):` 样例数据
|
|
186
|
+
|
|
187
|
+
AI 应根据 dtypes/head 检查输出 schema 是否符合预期,确认后用 `save` 发布并正式
|
|
188
|
+
`run`(正式运行才会真实创建输出数据集)。
|
|
189
|
+
|
|
190
|
+
如果校验中途断掉导致草稿仍带 stub,重新执行 `guanwf save-draft` 即可恢复(命令结束时
|
|
191
|
+
会有显式警告)。
|
|
192
|
+
|
|
193
|
+
`--validate` 必须搭配 `--node`(节点**显示名**不是 id),隐含 `--draft --wait --logs`。
|
|
194
|
+
不带 `--validate` 的 `run --node --draft --wait --logs` 是真实单节点运行,**会创建
|
|
195
|
+
输出数据集**,仅在确认要产出数据时使用。
|
|
196
|
+
|
|
197
|
+
试运行失败时,根据日志区分:脚本语法/库缺失问题改 `script.py`;输入输出配置问题改
|
|
198
|
+
`workflow.go`;平台/环境错误(如输出数据集注册失败)须排查环境,不要改本地源文件硬绕。
|
|
199
|
+
|
|
200
|
+
## 命令闭环
|
|
201
|
+
|
|
202
|
+
### 新建工作流
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
guanwf create --name "我的工作流" --parent-dir <工作流目录ID> --dir <workdir>
|
|
206
|
+
# 默认生成数据流节点骨架: workflow.go + nodes/dataflow_1/etl.go
|
|
207
|
+
# --type PYTHON 生成 Python 节点骨架: workflow.go + nodes/python_1/script.py
|
|
208
|
+
# --type DB_DATAFLOW 生成数据库数据流骨架
|
|
209
|
+
|
|
210
|
+
# 编辑 workflow.go 和节点源文件后:
|
|
211
|
+
guanwf export --dir <workdir>
|
|
212
|
+
guanwf save-draft --dir <workdir>
|
|
213
|
+
guanwf run --dir <workdir> --node "<节点名>" --validate # 校验(不创建真实输出数据集)
|
|
214
|
+
guanwf save --dir <workdir> # 正式发布
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
新增数据流节点时:`mkdir -p nodes/<节点ID>`,创建 `nodes/<节点ID>/etl.go`(guanetl DSL,
|
|
218
|
+
写法见 `references/ETL_AI_DEVELOP.md`),再在 `workflow.go` 中加 `DataflowNode(...)`。
|
|
219
|
+
|
|
220
|
+
### 编辑线上工作流(回读)
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
guanwf edit <workflowId> --dir <workdir>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
回读生成:
|
|
227
|
+
|
|
228
|
+
- `workflow.go`:节点 + 依赖结构(PARAMETER_ASSIGNMENT 等未建模类型生成 `RawTask` + 注释)
|
|
229
|
+
- 数据流节点 → `nodes/<id>/etl.go`(经 guanetl json2go,SQL 外置为 `*.sql`)
|
|
230
|
+
- Python 节点 → `nodes/<id>/script.py` + `nodes/<id>/python.json`
|
|
231
|
+
- 其他节点 → `nodes/<id>/task.json`
|
|
232
|
+
- `_layout.json`:线上画布坐标,导出时原样保留;新增节点自动分层布局补位
|
|
233
|
+
|
|
234
|
+
之后与新建流程相同:改源文件 → `export` → `save-draft` / `save`。
|
|
235
|
+
|
|
236
|
+
### 运行
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
guanwf run --dir <workdir> # 触发整个工作流(已发布版本)
|
|
240
|
+
guanwf run --dir <workdir> --wait # 等待完成
|
|
241
|
+
guanwf run --dir <workdir> --wait --logs # 完成后输出任务日志
|
|
242
|
+
guanwf run --dir <workdir> --node "<节点名>" --validate # 校验运行(不创建输出数据集)
|
|
243
|
+
guanwf run --dir <workdir> --node "<节点名>" --draft --wait --logs # 单节点真实运行草稿
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### 预览数据流节点
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
guanwf preview --dir <workdir> # 只有一个数据流节点时自动选中,预览其终端 action
|
|
250
|
+
guanwf preview <actionId> --dir <workdir> # 多个数据流节点时按 action id 定位
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
`preview` 针对**数据流节点内部的 action**(基于 export 的本地定义,无需先 save)。
|
|
254
|
+
Python 节点没有 preview API,校验用 `run --node "<节点名>" --validate`。
|
|
255
|
+
|
|
256
|
+
## save 的合并语义
|
|
257
|
+
|
|
258
|
+
`save-draft` / `save` 不是整体覆盖上传:
|
|
259
|
+
|
|
260
|
+
1. 读取 `_exported_workflow.json`(必须由 `export` 重新生成)
|
|
261
|
+
2. 从服务端拉取父工作流最新版本作为基底
|
|
262
|
+
3. tasks 按节点 id 做字段级合并:本地定义的字段覆盖,服务端独有字段(dsId、运行时注册信息等)保留
|
|
263
|
+
4. 节点成员按三方判断(服务端最新 + 本地 `_parent_snapshot.json` + 本地导出):
|
|
264
|
+
本地快照里有、导出里删了的节点 → 删除;服务端有、本地快照不知道的节点(他人并发新增)→
|
|
265
|
+
保留并打印提示
|
|
266
|
+
5. dataflowJson 按 dataflow id 经 etlmerge 做 actions 字段级合并(成员判断同上)
|
|
267
|
+
6. 保存成功后刷新本地 `_parent_snapshot.json`,并把服务端回写的 Python 参数同步进 `python.json`
|
|
268
|
+
|
|
269
|
+
因此多人/多端并发修改同一工作流时,以服务端最新版本为基底合并,不会把别人新加的节点冲掉;
|
|
270
|
+
但同一节点的并发修改仍是后保存者覆盖。
|
|
271
|
+
|
|
272
|
+
保留他人并发新增节点时会做冲突校验,任一命中即中止保存(exit 1)并提示重新
|
|
273
|
+
`guanwf edit <工作流ID>` 同步后再修改:
|
|
274
|
+
|
|
275
|
+
- **断链**:并发新增的节点依赖(`preTasks` / `preTaskScheduleTypes`)了本地这次删除/改名的节点
|
|
276
|
+
- **重名**:并发新增的节点与本地节点显示名相同(name 是依赖引用键,必须全局唯一)
|
|
277
|
+
|
|
278
|
+
## 注意事项
|
|
279
|
+
|
|
280
|
+
- 节点显示名在工作流内必须唯一(`preTasks` 与 `--node` 都按名字引用)。
|
|
281
|
+
- `RawTask` 改名要改 `task.json` 的 `name` 字段,同时注意其他节点对它的依赖按名字解析。
|
|
282
|
+
- `workflow.go` 删除某节点后,对应 `nodes/<id>/` 目录可以保留(导出时忽略),但 save 会从
|
|
283
|
+
服务端定义中删除该节点;删除是真实生效的,操作前确认。
|
|
284
|
+
- 查询实例/日志的 API 需要 `domId`,`run` 命令自动从快照或服务端解析,无需手动处理。
|
|
285
|
+
- 列表查询用 `guancli workflow list`;workflow 引擎的 `list-paging` 请求体关键字段是
|
|
286
|
+
`searchVal`(不是 keyword)。
|