fix(workflow): sendNotification skip-on-empty-toUserId + error precision + doc unification
- toUserId 解析为空时静默跳过 (succeeded, sentCount=0, skipped:true) 而非 failed
→ 支持'仅有重复订单时才通知'的条件通知编排
- 必填校验只列出实际缺失字段 (message),不再误报已解析的字段
- 文档统一变量格式: {{nodeId.path}} 为主,{{index.path}} 兼容
- spec 6/6,线上实测: 空收件人 skip + 非空正常发送均验证
This commit is contained in:
@@ -132,7 +132,7 @@ These differ from official teable and from the (stale) `docs/automation/triggers
|
||||
2. **Read [trigger-types.md](api-reference/trigger-types.md) before configuring a trigger** — contains exact config keys per trigger; guessing leads to silent misfires.
|
||||
3. **Build the full chain then activate** — `workflow create` → `trigger set` → `action add` → `activate`. Editing nodes after activation requires re-`activate` to snapshot.
|
||||
4. **For `buttonClick`, ensure the field is a Button field bound to this workflow** — create via `field create --type button` with `options.workflow.id` (the `workflow` value is an **object** `{id, name?}`, NOT a bare `workflowId` string — matching the DB shape), or the field won't render/click. Note: field `options` captures a stale `isActive` snapshot at creation; the API validates the DB `isActive` at click time instead, so a deactivated workflow returns 400.
|
||||
5. **Read [action-types.md](api-reference/action-types.md) before writing scripts** — covers sandbox globals, variable references `{{nodeIndex.path}}`, and the `_accessToken`/`_apiBaseUrl` back-channel.
|
||||
5. **Read [action-types.md](api-reference/action-types.md) before writing scripts** — covers sandbox globals, variable references `{{nodeId.path}}`, and the `_accessToken`/`_apiBaseUrl` back-channel.
|
||||
|
||||
## 5. Common Errors & Recovery
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ inkreach-teable action add <wfId> --type sendEmail \
|
||||
|
||||
**Output:** `{ "sent": true, "to": "..." }`
|
||||
|
||||
Variable reference: `--body '{{1.record.fields.Name}}'` injects upstream data.
|
||||
Variable reference: `--body '{{n0.record.fields.Name}}'` injects upstream data.
|
||||
|
||||
---
|
||||
|
||||
@@ -180,14 +180,16 @@ inkreach-teable action add <wfId> --type sendNotification \
|
||||
"severity": "info"
|
||||
}
|
||||
```
|
||||
- `toUserId`: recipient user ID (required). Can reference the trigger user with `{{n0.user.id}}` (submitter/operator — only populated on user-initiated triggers; scheduled/webhook resolve to empty).
|
||||
- `title` + `message`: notification text; both support `{{nodeId.path}}` variables. If title set, rendered as `title\nmessage`.
|
||||
- `toUserId`: recipient user ID. Can reference the trigger user with `{{n0.user.id}}` (submitter/operator — only populated on user-initiated triggers; scheduled/webhook resolve to empty).
|
||||
- `title` + `message`: notification text; both support `{{nodeId.path}}` variables (or `{{index.path}}` — both formats resolve identically). If title set, rendered as `title\nmessage`.
|
||||
- `path` (optional): click-through URL in the notification center.
|
||||
- `severity`: `info` (default) | `warning` | `critical`.
|
||||
|
||||
**Empty-value semantics**: `toUserId` is required, but if it resolves to an **empty string** the node **silently skips** — status `succeeded`, `sentCount: 0`, output `{ sentCount: 0, invalidUserIds: [], toUserId: '', skipped: true, skipReason: 'toUserId is empty' }`, and `sendCommonNotify` is NOT called. This enables conditional-notify patterns: a script action that returns an empty recipient implements "notify only when X". An empty `message` still fails the node with `Missing required fields: message`.
|
||||
|
||||
**IMPORTANT**: this is a **system in-app notification** (bell badge + notification center + realtime socket push). It calls `NotificationService.sendCommonNotify` with `type=system` and **no email config — no email is sent**. Prefer it over `sendEmail` unless an actual email is required.
|
||||
|
||||
**Output:** `{ "sentCount": 1, "invalidUserIds": [], "toUserId": "usr..." }`
|
||||
**Output:** `{ "sentCount": 1, "invalidUserIds": [], "toUserId": "usr..." }` (skipped case: see empty-value semantics above)
|
||||
|
||||
---
|
||||
|
||||
@@ -196,7 +198,7 @@ inkreach-teable action add <wfId> --type sendNotification \
|
||||
```bash
|
||||
inkreach-teable action add <wfId> --type createRecord \
|
||||
--target-table tblXXX \
|
||||
--records '[{"fields":{"Name":"New","Source":"{{1.record.id}}"}}]'
|
||||
--records '[{"fields":{"Name":"New","Source":"{{n0.record.id}}"}}]'
|
||||
```
|
||||
|
||||
**Config:**
|
||||
@@ -264,17 +266,20 @@ inkreach-teable action add <wfId> --type getRecords \
|
||||
|
||||
## Variable References
|
||||
|
||||
In any action's **string** config field, use `{{nodeIndex.path}}` to inject upstream output:
|
||||
In any action's **string** config field, use `{{nodeId.path}}` to inject upstream output. The positional `{{index.path}}` form is also accepted — both resolve identically:
|
||||
|
||||
| Reference | Resolves to |
|
||||
|-----------|-------------|
|
||||
| `{{1.record.id}}` | trigger record id (trigger = node 1) |
|
||||
| `{{1.record.fields.fldStatus}}` | trigger record's Status field |
|
||||
| `{{2.result}}` | script action's return value (node 2) |
|
||||
| `{{3.records}}` | getRecords output array (node 3) |
|
||||
| `{{2.statusCode}}` | httpRequest status (node 2) |
|
||||
| `{{n0.record.id}}` (≡ `{{1.record.id}}`) | trigger record id (trigger = node 1) |
|
||||
| `{{n0.record.fields.fldStatus}}` | trigger record's Status field |
|
||||
| `{{n2.result}}` (≡ `{{2.result}}`) | script action's return value |
|
||||
| `{{n3.records}}` (≡ `{{3.records}}`) | getRecords output array |
|
||||
| `{{n2.statusCode}}` (≡ `{{2.statusCode}}`) | httpRequest status |
|
||||
|
||||
**Node index** = topological execution order (trigger is always 1, then actions in chain order).
|
||||
- **`{{nodeId.path}}`** — node ID form (teable generates ids like `n0`, `n1`, `n2`…). Preferred for clarity.
|
||||
- **`{{index.path}}`** — numeric form; **index = topological execution order** (trigger is always 1, then actions in chain order).
|
||||
|
||||
**Empty resolution**: when a referenced value is missing/undefined, the variable resolves to `''` (see sendNotification §4 for skip semantics).
|
||||
|
||||
Script actions can also read everything via `triggerInput` (the merged upstream output):
|
||||
```js
|
||||
|
||||
@@ -163,13 +163,15 @@ trigger or use the queue. Two caveats:
|
||||
|
||||
## Variable References (cross-node data)
|
||||
|
||||
An action can reference upstream nodes' output in string fields via `{{nodeIndex.path}}`:
|
||||
An action can reference upstream nodes' output in string fields via `{{nodeId.path}}` (or the compatible `{{index.path}}` positional form):
|
||||
|
||||
- `{{1.record.id}}` — trigger record's id (trigger = node 1)
|
||||
- `{{1.record.fields.Status}}` — trigger record's Status field
|
||||
- `{{2.records}}` — output of the first action (node 2)
|
||||
- `{{n0.record.id}}` — trigger record's id (n0 = trigger node)
|
||||
- `{{n0.record.fields.Status}}` — trigger record's Status field
|
||||
- `{{2.result}}` — output of the first action (numeric index form; index = topological execution order, trigger is always 1)
|
||||
|
||||
Node index follows **topological execution order** (trigger is always 1). Script actions can also read the merged upstream output directly via `triggerInput`.
|
||||
Both forms resolve to the same value — `{{n0.record.id}}` ≡ `{{1.record.id}}`. Use `{{nodeId.path}}` in new workflows for clarity.
|
||||
|
||||
> **Empty-value semantics**: if a variable resolves to an empty string, the node sees the literal empty value. For `sendNotification`, an empty `toUserId` makes the node **silently skip** (status `succeeded`, `sentCount: 0`) instead of failing — so a script action that returns an empty recipient implements "notify only when X" conditional patterns. An empty `message` still fails with `Missing required fields: message`.
|
||||
|
||||
## Common Patterns
|
||||
|
||||
@@ -179,7 +181,7 @@ inkreach-teable workflow create --name "Status→email"
|
||||
inkreach-teable trigger set wflXXX --type recordUpdated --table-id tblXXX --field-ids '["fldStatus"]'
|
||||
inkreach-teable action add wflXXX --type sendEmail \
|
||||
--to ops@x.com --subject 'Status changed' \
|
||||
--body '{{1.record.fields.Status}}'
|
||||
--body '{{n0.record.fields.Status}}'
|
||||
inkreach-teable workflow activate wflXXX
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user