flowbase-cli 0.1.7__tar.gz → 0.1.9__tar.gz
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.
- {flowbase_cli-0.1.7 → flowbase_cli-0.1.9}/PKG-INFO +24 -6
- {flowbase_cli-0.1.7 → flowbase_cli-0.1.9}/README.md +23 -5
- {flowbase_cli-0.1.7 → flowbase_cli-0.1.9}/pyproject.toml +1 -1
- {flowbase_cli-0.1.7 → flowbase_cli-0.1.9}/src/flowbase_cli/__init__.py +1 -1
- {flowbase_cli-0.1.7 → flowbase_cli-0.1.9}/src/flowbase_cli/endpoints.py +172 -7
- {flowbase_cli-0.1.7 → flowbase_cli-0.1.9}/src/flowbase_cli/main.py +90 -1
- {flowbase_cli-0.1.7 → flowbase_cli-0.1.9}/src/flowbase_cli.egg-info/PKG-INFO +24 -6
- {flowbase_cli-0.1.7 → flowbase_cli-0.1.9}/tests/test_cli.py +224 -0
- {flowbase_cli-0.1.7 → flowbase_cli-0.1.9}/setup.cfg +0 -0
- {flowbase_cli-0.1.7 → flowbase_cli-0.1.9}/src/flowbase_cli.egg-info/SOURCES.txt +0 -0
- {flowbase_cli-0.1.7 → flowbase_cli-0.1.9}/src/flowbase_cli.egg-info/dependency_links.txt +0 -0
- {flowbase_cli-0.1.7 → flowbase_cli-0.1.9}/src/flowbase_cli.egg-info/entry_points.txt +0 -0
- {flowbase_cli-0.1.7 → flowbase_cli-0.1.9}/src/flowbase_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flowbase-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.9
|
|
4
4
|
Summary: Command line client for the FlowBase OpenAPI
|
|
5
5
|
Author: DeepFlowAI
|
|
6
6
|
License: Apache-2.0
|
|
@@ -9,7 +9,7 @@ Description-Content-Type: text/markdown
|
|
|
9
9
|
|
|
10
10
|
# FlowBase CLI
|
|
11
11
|
|
|
12
|
-
`flowbase` is the official command line client for the FlowBase OpenAPI. It manages apps, objects, fields, views, custom pages, workflows, dashboards, business records, roles, and configuration versions across the three FlowBase environments.
|
|
12
|
+
`flowbase` is the official command line client for the FlowBase OpenAPI. It manages apps, objects, fields, views, custom pages, workflows, approval flows, dashboards, business records, roles, and configuration versions across the three FlowBase environments.
|
|
13
13
|
|
|
14
14
|
## 1. Install and connect
|
|
15
15
|
|
|
@@ -47,7 +47,7 @@ Most commands run on every Host; `roles` is production-only and `changes commit/
|
|
|
47
47
|
|
|
48
48
|
### 3.1 Metadata vs records
|
|
49
49
|
|
|
50
|
-
- **Metadata** (`metadata` scope): apps, objects, fields, views, pages, workflows, dashboards, roles, versions.
|
|
50
|
+
- **Metadata** (`metadata` scope): apps, objects, fields, views, pages, workflows, approvals, dashboards, roles, versions.
|
|
51
51
|
- **Business records** (`records` scope): rows inside an object.
|
|
52
52
|
|
|
53
53
|
Metadata changes are **drafts** against the current environment's configuration repository. They do not affect the running front-end until they are committed and released.
|
|
@@ -77,9 +77,11 @@ flowbase
|
|
|
77
77
|
│ add-dashboards | remove-dashboard | set-dashboard-group | reorder-members
|
|
78
78
|
├── groups create | reorder | update | delete App groups (under an app)
|
|
79
79
|
├── objects list | get | create | update | delete | restore
|
|
80
|
+
│ cli-edit-policy get | update
|
|
80
81
|
├── fields create | update | delete | restore Fields under an object
|
|
81
82
|
├── views list | create | update | reorder | set-enabled | delete
|
|
82
83
|
├── workflows list | get | create | update | reorder | set-enabled | delete | executions
|
|
84
|
+
├── approvals list | get | create | update | reorder | set-enabled | delete
|
|
83
85
|
├── pages list | options | get | create | update | set-default | delete | restore
|
|
84
86
|
├── dashboards list | get | create | update | copy | delete
|
|
85
87
|
├── changes commit | release | direct-release
|
|
@@ -114,6 +116,7 @@ flowbase docs catalogs field-types # field type names + allowed constrain
|
|
|
114
116
|
flowbase docs catalogs workflow-nodes # node types with in/out edge rules
|
|
115
117
|
flowbase docs catalogs permissions # function permission points + data scopes
|
|
116
118
|
flowbase docs catalogs dashboard-filters # filter types + date-range presets
|
|
119
|
+
flowbase docs catalogs page-components # page region component kinds (field / relatedList / approval)
|
|
117
120
|
flowbase docs catalogs id-prefixes # resource ID prefix meanings
|
|
118
121
|
flowbase docs catalogs icons # icon guidance + examples
|
|
119
122
|
```
|
|
@@ -138,9 +141,11 @@ Do not compose update bodies from scratch when an existing resource exists — `
|
|
|
138
141
|
flowbase objects create --label Customer --key customer --description "Customer records"
|
|
139
142
|
flowbase fields create obj_xxx --json '{"label":"Name","key":"name","description":"Customer name","type":"text.singleLine","permission":"required"}'
|
|
140
143
|
flowbase records create obj_xxx --json '{"values":{"name":"Acme Corp"}}'
|
|
144
|
+
flowbase objects cli-edit-policy get obj_xxx
|
|
145
|
+
flowbase objects cli-edit-policy update obj_xxx --json '{"baseRevision":4,"policies":{"fld_xxx":"user_approval"}}' --yes
|
|
141
146
|
```
|
|
142
147
|
|
|
143
|
-
Record `values` keys are **field keys** (`name`), not internal IDs (`fld_...`). IDs are `obj_`/`fld_`/`dsh_`/`wfl_`/`pag_`-prefixed; see `docs catalogs id-prefixes`.
|
|
148
|
+
Record `values` keys are **field keys** (`name`), not internal IDs (`fld_...`). IDs are `obj_`/`fld_`/`dsh_`/`wfl_`/`pag_`/`reg_`/`cmp_`-prefixed; see `docs catalogs id-prefixes`.
|
|
144
149
|
|
|
145
150
|
### 6.2 Edit and publish configuration on a test environment
|
|
146
151
|
|
|
@@ -167,19 +172,32 @@ flowbase changes direct-release --message "Fix customer field" --yes
|
|
|
167
172
|
|
|
168
173
|
`direct-release` always runs a server-side preflight, prints the current version, diff summary, checks and risk, and binds a one-time confirmation token. `--yes` accepts that reviewed result; it never skips validation.
|
|
169
174
|
|
|
170
|
-
### 6.4 Workflows and pages
|
|
175
|
+
### 6.4 Workflows, approvals, and pages
|
|
171
176
|
|
|
172
177
|
A workflow graph must satisfy structural rules: exactly one `trigger` node, at least one `end` node, a DAG, and valid per-type edges. See `docs catalogs workflow-nodes` before composing `nodes`/`edges`.
|
|
173
178
|
|
|
179
|
+
Approval flow graphs are separate metadata: exactly one `trigger`, at least one `approval` node, both `approvedEnd` and `rejectedEnd`, and dual outcome edges from each approval node. Employee task decisions are not available via API key / CLI. See `docs catalogs approval-nodes`.
|
|
180
|
+
|
|
174
181
|
```bash
|
|
175
182
|
flowbase docs catalogs workflow-nodes
|
|
176
183
|
flowbase workflows create obj_xxx --json '{"label":"Set owner","enabled":true,"trigger":{"event":"create","match":"all","conditions":[]},"nodes":[],"edges":[]}'
|
|
177
184
|
flowbase workflows update obj_xxx wfl_xxx --json-file workflow.json
|
|
178
185
|
flowbase workflows executions obj_xxx wfl_xxx --result failure
|
|
179
186
|
|
|
180
|
-
flowbase docs
|
|
187
|
+
flowbase docs catalogs approval-nodes
|
|
188
|
+
flowbase docs example approvals.create --output approval.json
|
|
189
|
+
flowbase approvals create obj_xxx --json-file approval.json
|
|
190
|
+
flowbase approvals set-enabled obj_xxx apr_xxx --base-revision 4 --enabled --yes
|
|
191
|
+
|
|
192
|
+
flowbase docs catalogs page-components # field + relatedList + approval keys
|
|
193
|
+
flowbase docs example pages.create --output page.json # create-page layout template
|
|
181
194
|
flowbase pages create --json-file page.json
|
|
182
195
|
flowbase pages set-default pag_xxx --base-revision 4 --yes
|
|
196
|
+
|
|
197
|
+
# Detail pages may embed relatedList and at most one approval component.
|
|
198
|
+
flowbase pages get pag_xxx > page.json # prefer get → edit → update
|
|
199
|
+
# or: flowbase docs example pages.update --output page.json
|
|
200
|
+
flowbase pages update pag_xxx --json-file page.json
|
|
183
201
|
```
|
|
184
202
|
|
|
185
203
|
### 6.5 Records and attachments
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# FlowBase CLI
|
|
2
2
|
|
|
3
|
-
`flowbase` is the official command line client for the FlowBase OpenAPI. It manages apps, objects, fields, views, custom pages, workflows, dashboards, business records, roles, and configuration versions across the three FlowBase environments.
|
|
3
|
+
`flowbase` is the official command line client for the FlowBase OpenAPI. It manages apps, objects, fields, views, custom pages, workflows, approval flows, dashboards, business records, roles, and configuration versions across the three FlowBase environments.
|
|
4
4
|
|
|
5
5
|
## 1. Install and connect
|
|
6
6
|
|
|
@@ -38,7 +38,7 @@ Most commands run on every Host; `roles` is production-only and `changes commit/
|
|
|
38
38
|
|
|
39
39
|
### 3.1 Metadata vs records
|
|
40
40
|
|
|
41
|
-
- **Metadata** (`metadata` scope): apps, objects, fields, views, pages, workflows, dashboards, roles, versions.
|
|
41
|
+
- **Metadata** (`metadata` scope): apps, objects, fields, views, pages, workflows, approvals, dashboards, roles, versions.
|
|
42
42
|
- **Business records** (`records` scope): rows inside an object.
|
|
43
43
|
|
|
44
44
|
Metadata changes are **drafts** against the current environment's configuration repository. They do not affect the running front-end until they are committed and released.
|
|
@@ -68,9 +68,11 @@ flowbase
|
|
|
68
68
|
│ add-dashboards | remove-dashboard | set-dashboard-group | reorder-members
|
|
69
69
|
├── groups create | reorder | update | delete App groups (under an app)
|
|
70
70
|
├── objects list | get | create | update | delete | restore
|
|
71
|
+
│ cli-edit-policy get | update
|
|
71
72
|
├── fields create | update | delete | restore Fields under an object
|
|
72
73
|
├── views list | create | update | reorder | set-enabled | delete
|
|
73
74
|
├── workflows list | get | create | update | reorder | set-enabled | delete | executions
|
|
75
|
+
├── approvals list | get | create | update | reorder | set-enabled | delete
|
|
74
76
|
├── pages list | options | get | create | update | set-default | delete | restore
|
|
75
77
|
├── dashboards list | get | create | update | copy | delete
|
|
76
78
|
├── changes commit | release | direct-release
|
|
@@ -105,6 +107,7 @@ flowbase docs catalogs field-types # field type names + allowed constrain
|
|
|
105
107
|
flowbase docs catalogs workflow-nodes # node types with in/out edge rules
|
|
106
108
|
flowbase docs catalogs permissions # function permission points + data scopes
|
|
107
109
|
flowbase docs catalogs dashboard-filters # filter types + date-range presets
|
|
110
|
+
flowbase docs catalogs page-components # page region component kinds (field / relatedList / approval)
|
|
108
111
|
flowbase docs catalogs id-prefixes # resource ID prefix meanings
|
|
109
112
|
flowbase docs catalogs icons # icon guidance + examples
|
|
110
113
|
```
|
|
@@ -129,9 +132,11 @@ Do not compose update bodies from scratch when an existing resource exists — `
|
|
|
129
132
|
flowbase objects create --label Customer --key customer --description "Customer records"
|
|
130
133
|
flowbase fields create obj_xxx --json '{"label":"Name","key":"name","description":"Customer name","type":"text.singleLine","permission":"required"}'
|
|
131
134
|
flowbase records create obj_xxx --json '{"values":{"name":"Acme Corp"}}'
|
|
135
|
+
flowbase objects cli-edit-policy get obj_xxx
|
|
136
|
+
flowbase objects cli-edit-policy update obj_xxx --json '{"baseRevision":4,"policies":{"fld_xxx":"user_approval"}}' --yes
|
|
132
137
|
```
|
|
133
138
|
|
|
134
|
-
Record `values` keys are **field keys** (`name`), not internal IDs (`fld_...`). IDs are `obj_`/`fld_`/`dsh_`/`wfl_`/`pag_`-prefixed; see `docs catalogs id-prefixes`.
|
|
139
|
+
Record `values` keys are **field keys** (`name`), not internal IDs (`fld_...`). IDs are `obj_`/`fld_`/`dsh_`/`wfl_`/`pag_`/`reg_`/`cmp_`-prefixed; see `docs catalogs id-prefixes`.
|
|
135
140
|
|
|
136
141
|
### 6.2 Edit and publish configuration on a test environment
|
|
137
142
|
|
|
@@ -158,19 +163,32 @@ flowbase changes direct-release --message "Fix customer field" --yes
|
|
|
158
163
|
|
|
159
164
|
`direct-release` always runs a server-side preflight, prints the current version, diff summary, checks and risk, and binds a one-time confirmation token. `--yes` accepts that reviewed result; it never skips validation.
|
|
160
165
|
|
|
161
|
-
### 6.4 Workflows and pages
|
|
166
|
+
### 6.4 Workflows, approvals, and pages
|
|
162
167
|
|
|
163
168
|
A workflow graph must satisfy structural rules: exactly one `trigger` node, at least one `end` node, a DAG, and valid per-type edges. See `docs catalogs workflow-nodes` before composing `nodes`/`edges`.
|
|
164
169
|
|
|
170
|
+
Approval flow graphs are separate metadata: exactly one `trigger`, at least one `approval` node, both `approvedEnd` and `rejectedEnd`, and dual outcome edges from each approval node. Employee task decisions are not available via API key / CLI. See `docs catalogs approval-nodes`.
|
|
171
|
+
|
|
165
172
|
```bash
|
|
166
173
|
flowbase docs catalogs workflow-nodes
|
|
167
174
|
flowbase workflows create obj_xxx --json '{"label":"Set owner","enabled":true,"trigger":{"event":"create","match":"all","conditions":[]},"nodes":[],"edges":[]}'
|
|
168
175
|
flowbase workflows update obj_xxx wfl_xxx --json-file workflow.json
|
|
169
176
|
flowbase workflows executions obj_xxx wfl_xxx --result failure
|
|
170
177
|
|
|
171
|
-
flowbase docs
|
|
178
|
+
flowbase docs catalogs approval-nodes
|
|
179
|
+
flowbase docs example approvals.create --output approval.json
|
|
180
|
+
flowbase approvals create obj_xxx --json-file approval.json
|
|
181
|
+
flowbase approvals set-enabled obj_xxx apr_xxx --base-revision 4 --enabled --yes
|
|
182
|
+
|
|
183
|
+
flowbase docs catalogs page-components # field + relatedList + approval keys
|
|
184
|
+
flowbase docs example pages.create --output page.json # create-page layout template
|
|
172
185
|
flowbase pages create --json-file page.json
|
|
173
186
|
flowbase pages set-default pag_xxx --base-revision 4 --yes
|
|
187
|
+
|
|
188
|
+
# Detail pages may embed relatedList and at most one approval component.
|
|
189
|
+
flowbase pages get pag_xxx > page.json # prefer get → edit → update
|
|
190
|
+
# or: flowbase docs example pages.update --output page.json
|
|
191
|
+
flowbase pages update pag_xxx --json-file page.json
|
|
174
192
|
```
|
|
175
193
|
|
|
176
194
|
### 6.5 Records and attachments
|
|
@@ -173,6 +173,29 @@ ENDPOINTS = (
|
|
|
173
173
|
request_body='{"label":"Customer","description":"Customer records","icon":"IconDatabase"}'),
|
|
174
174
|
Endpoint("objects.delete", "flowbase_objects_delete", "DELETE", "/objects/{object_id}", "metadata:write", "Logically delete an object.", "flowbase objects delete obj_xxx --yes", True),
|
|
175
175
|
Endpoint("objects.restore", "flowbase_objects_restore", "POST", "/objects/{object_id}/restore", "metadata:write", "Restore an object.", "flowbase objects restore obj_xxx --yes", True),
|
|
176
|
+
Endpoint(
|
|
177
|
+
"objects.cli-edit-policy.get",
|
|
178
|
+
"flowbase_objects_cli_edit_policy_get",
|
|
179
|
+
"GET",
|
|
180
|
+
"/objects/{object_id}/cli-edit-policy",
|
|
181
|
+
"metadata:read",
|
|
182
|
+
"Get object CLI field edit policies.",
|
|
183
|
+
"flowbase objects cli-edit-policy get obj_xxx",
|
|
184
|
+
),
|
|
185
|
+
Endpoint(
|
|
186
|
+
"objects.cli-edit-policy.update",
|
|
187
|
+
"flowbase_objects_cli_edit_policy_update",
|
|
188
|
+
"PUT",
|
|
189
|
+
"/objects/{object_id}/cli-edit-policy",
|
|
190
|
+
"metadata:write",
|
|
191
|
+
"Update object CLI field edit policies as a complete draft snapshot.",
|
|
192
|
+
"flowbase objects cli-edit-policy update obj_xxx --json-file cli-policy.json --yes",
|
|
193
|
+
requires_confirmation=True,
|
|
194
|
+
request_body=(
|
|
195
|
+
'{"baseRevision":4,"policies":{'
|
|
196
|
+
'"fld_confirm":"user_approval","fld_direct":"direct"}}'
|
|
197
|
+
),
|
|
198
|
+
),
|
|
176
199
|
Endpoint("fields.create", "flowbase_fields_create", "POST", "/objects/{object_id}/fields", "metadata:write", "Create a field draft.", "flowbase fields create obj_xxx --json-file field.json",
|
|
177
200
|
request_body=(
|
|
178
201
|
'{"label":"Stage","key":"stage","description":"Pipeline stage","type":"select.single","typeVersion":"v1",'
|
|
@@ -222,6 +245,48 @@ ENDPOINTS = (
|
|
|
222
245
|
Endpoint("workflows.delete", "flowbase_workflows_delete", "POST", "/objects/{object_id}/workflows/{workflow_id}/delete", "metadata:write", "Logically delete a workflow.", "flowbase workflows delete obj_xxx wfl_xxx --base-revision 4 --yes", True,
|
|
223
246
|
request_body='{"baseRevision":4}'),
|
|
224
247
|
Endpoint("workflows.executions", "flowbase_workflows_executions", "GET", "/objects/{object_id}/workflows/{workflow_id}/executions", "metadata:read", "List workflow execution records.", "flowbase workflows executions obj_xxx wfl_xxx --result failure"),
|
|
248
|
+
Endpoint("approvals.list", "flowbase_approvals_list", "GET", "/objects/{object_id}/approvals", "metadata:read", "List object approval flows.", "flowbase approvals list obj_xxx"),
|
|
249
|
+
Endpoint("approvals.get", "flowbase_approvals_get", "GET", "/objects/{object_id}/approvals/{approval_id}", "metadata:read", "Get an approval flow graph.", "flowbase approvals get obj_xxx apr_xxx"),
|
|
250
|
+
Endpoint("approvals.create", "flowbase_approvals_create", "POST", "/objects/{object_id}/approvals", "metadata:write", "Create an approval flow draft.", "flowbase approvals create obj_xxx --json-file approval.json",
|
|
251
|
+
request_body=(
|
|
252
|
+
'{"baseRevision":3,"label":"Contract approval","enabled":true,'
|
|
253
|
+
'"trigger":{"manual":true,"create":false,"update":false,"match":"all","conditions":[]},'
|
|
254
|
+
'"displayFieldIds":["fld_xxx"],"lockRecord":true,"allowWithdraw":true,'
|
|
255
|
+
'"nodes":['
|
|
256
|
+
'{"id":"apn_trigger","type":"trigger"},'
|
|
257
|
+
'{"id":"apn_review","type":"approval","label":"Manager review",'
|
|
258
|
+
'"approverRules":[{"type":"employees","employeeIds":["emp_xxx"]}],'
|
|
259
|
+
'"mode":"all","selfPolicy":"skip","unavailablePolicy":"autoReject"},'
|
|
260
|
+
'{"id":"apn_approved","type":"approvedEnd"},'
|
|
261
|
+
'{"id":"apn_rejected","type":"rejectedEnd"}],'
|
|
262
|
+
'"edges":['
|
|
263
|
+
'{"id":"ape_1","source":"apn_trigger","target":"apn_review"},'
|
|
264
|
+
'{"id":"ape_2","source":"apn_review","target":"apn_approved","outcome":"approved"},'
|
|
265
|
+
'{"id":"ape_3","source":"apn_review","target":"apn_rejected","outcome":"rejected"}]}'
|
|
266
|
+
)),
|
|
267
|
+
Endpoint("approvals.update", "flowbase_approvals_update", "PUT", "/objects/{object_id}/approvals/{approval_id}", "metadata:write", "Update an approval flow graph.", "flowbase approvals update obj_xxx apr_xxx --json-file approval.json",
|
|
268
|
+
request_body=(
|
|
269
|
+
'{"baseRevision":4,"label":"Contract approval","enabled":true,'
|
|
270
|
+
'"trigger":{"manual":true,"create":false,"update":false,"match":"all","conditions":[]},'
|
|
271
|
+
'"displayFieldIds":["fld_xxx"],"lockRecord":true,"allowWithdraw":true,'
|
|
272
|
+
'"nodes":['
|
|
273
|
+
'{"id":"apn_trigger","type":"trigger"},'
|
|
274
|
+
'{"id":"apn_review","type":"approval","label":"Manager review",'
|
|
275
|
+
'"approverRules":[{"type":"employees","employeeIds":["emp_xxx"]}],'
|
|
276
|
+
'"mode":"all","selfPolicy":"skip","unavailablePolicy":"autoReject"},'
|
|
277
|
+
'{"id":"apn_approved","type":"approvedEnd"},'
|
|
278
|
+
'{"id":"apn_rejected","type":"rejectedEnd"}],'
|
|
279
|
+
'"edges":['
|
|
280
|
+
'{"id":"ape_1","source":"apn_trigger","target":"apn_review"},'
|
|
281
|
+
'{"id":"ape_2","source":"apn_review","target":"apn_approved","outcome":"approved"},'
|
|
282
|
+
'{"id":"ape_3","source":"apn_review","target":"apn_rejected","outcome":"rejected"}]}'
|
|
283
|
+
)),
|
|
284
|
+
Endpoint("approvals.reorder", "flowbase_approvals_reorder", "PUT", "/objects/{object_id}/approvals/order", "metadata:write", "Reorder object approval flows.", "flowbase approvals reorder obj_xxx --json '{\"baseRevision\":4,\"approvalIds\":[\"apr_a\",\"apr_b\"]}'",
|
|
285
|
+
request_body='{"baseRevision":4,"approvalIds":["apr_a","apr_b"]}'),
|
|
286
|
+
Endpoint("approvals.set-enabled", "flowbase_approvals_set_enabled", "POST", "/objects/{object_id}/approvals/{approval_id}/enabled", "metadata:write", "Enable or disable an approval flow.", "flowbase approvals set-enabled obj_xxx apr_xxx --base-revision 4 --enabled --yes", True,
|
|
287
|
+
request_body='{"baseRevision":4,"enabled":true}'),
|
|
288
|
+
Endpoint("approvals.delete", "flowbase_approvals_delete", "POST", "/objects/{object_id}/approvals/{approval_id}/delete", "metadata:write", "Logically delete an approval flow.", "flowbase approvals delete obj_xxx apr_xxx --base-revision 4 --yes", True,
|
|
289
|
+
request_body='{"baseRevision":4}'),
|
|
225
290
|
Endpoint("pages.list", "flowbase_pages_list", "GET", "/pages", "metadata:read", "List custom Page metadata.", "flowbase pages list --object-id obj_xxx"),
|
|
226
291
|
Endpoint("pages.options", "flowbase_pages_options", "GET", "/pages/options", "metadata:read", "Get custom Page design options.", "flowbase pages options"),
|
|
227
292
|
Endpoint("pages.get", "flowbase_pages_get", "GET", "/pages/{page_id}", "metadata:read", "Get a custom Page.", "flowbase pages get pag_xxx"),
|
|
@@ -233,9 +298,17 @@ ENDPOINTS = (
|
|
|
233
298
|
)),
|
|
234
299
|
Endpoint("pages.update", "flowbase_pages_update", "PUT", "/pages/{page_id}", "metadata:write", "Update a custom Page draft.", "flowbase pages update pag_xxx --json-file page.json",
|
|
235
300
|
request_body=(
|
|
236
|
-
'{"baseRevision":4,"page":{"id":"pag_xxx","key":"
|
|
237
|
-
'"description":"","objectId":"
|
|
238
|
-
'"regions":[{"id":"reg_xxx","label":"
|
|
301
|
+
'{"baseRevision":4,"page":{"id":"pag_xxx","key":"customer_detail","label":"Customer detail",'
|
|
302
|
+
'"description":"","objectId":"obj_customer","type":"detail",'
|
|
303
|
+
'"regions":[{"id":"reg_xxx","label":"Related","hideLabel":false,"columnRatios":[1],'
|
|
304
|
+
'"columns":[[{"kind":"relatedList","label":"Tickets","hideLabel":false,'
|
|
305
|
+
'"objectId":"obj_ticket","relationFieldId":"fld_customer",'
|
|
306
|
+
'"filters":[{"fieldId":"fld_status","operator":"neq","value":"done"}],'
|
|
307
|
+
'"columnFieldIds":["fld_title","fld_status","fld_created_at"],'
|
|
308
|
+
'"sort":{"fieldId":"fld_created_at","direction":"desc"},"limit":5,"showViewAll":true,'
|
|
309
|
+
'"createAction":{"enabled":true,"label":"New ticket"},'
|
|
310
|
+
'"carryIns":[{"fieldId":"fld_customer","source":"currentRecord"}]},'
|
|
311
|
+
'{"kind":"approval","label":"Approval","hideLabel":false}]]}],"rules":[]}}'
|
|
239
312
|
)),
|
|
240
313
|
Endpoint("pages.set-default", "flowbase_pages_set_default", "POST", "/pages/{page_id}/set-default", "metadata:write", "Set a default custom Page.", "flowbase pages set-default pag_xxx --base-revision 4 --yes", True,
|
|
241
314
|
request_body='{"baseRevision":4}'),
|
|
@@ -384,9 +457,9 @@ ENDPOINTS = (
|
|
|
384
457
|
ENDPOINT_BY_KEY = {endpoint.key: endpoint for endpoint in ENDPOINTS}
|
|
385
458
|
|
|
386
459
|
# Closed-domain catalogs for offline authoring of complex request bodies.
|
|
387
|
-
# Keep in sync with server registries (field types, workflow node kinds,
|
|
388
|
-
# permission points, dashboard filter presets
|
|
389
|
-
# the server file because the full Tabler set is large.
|
|
460
|
+
# Keep in sync with server registries (field types, workflow/approval node kinds,
|
|
461
|
+
# permission points, dashboard filter presets, page component kinds).
|
|
462
|
+
# Icons intentionally reference the server file because the full Tabler set is large.
|
|
390
463
|
FIELD_TYPES = (
|
|
391
464
|
{"type": "text.singleLine", "constraints": ["maxLength"], "writable": True},
|
|
392
465
|
{"type": "text.multiLine", "constraints": ["maxLength"], "writable": True},
|
|
@@ -406,6 +479,13 @@ FIELD_TYPES = (
|
|
|
406
479
|
{"type": "url", "constraints": ["maxLength", "placeholder"], "writable": True},
|
|
407
480
|
{"type": "attachment", "constraints": ["maxFileCount", "maxFileSize", "maxTotalSize", "allowedExtensions"], "writable": True},
|
|
408
481
|
{"type": "text.rich", "constraints": ["maxLength", "placeholder"], "writable": True},
|
|
482
|
+
{"type": "autonumber", "constraints": ["prefix", "start", "minDigits"], "writable": False},
|
|
483
|
+
{"type": "number.currency", "constraints": ["currency", "minimum", "maximum", "scale", "currencyDisplay", "useThousands"], "writable": True},
|
|
484
|
+
{"type": "number.percent", "constraints": ["minimum", "maximum", "scale"], "writable": True},
|
|
485
|
+
{"type": "duration", "constraints": ["precision", "minimum", "maximum", "displayFormat"], "writable": True},
|
|
486
|
+
{"type": "rating", "constraints": ["maxRating", "color"], "writable": True},
|
|
487
|
+
{"type": "lookup.record.multiple", "constraints": ["targetObjectId", "displayFieldId", "maxSelections"], "writable": True},
|
|
488
|
+
{"type": "lookup.department", "constraints": [], "writable": True},
|
|
409
489
|
)
|
|
410
490
|
|
|
411
491
|
WORKFLOW_NODE_TYPES = (
|
|
@@ -419,6 +499,22 @@ WORKFLOW_NODE_TYPES = (
|
|
|
419
499
|
"note": "terminal node"},
|
|
420
500
|
)
|
|
421
501
|
|
|
502
|
+
APPROVAL_NODE_TYPES = (
|
|
503
|
+
{"type": "trigger", "cardinality": "exactly 1", "in": 0, "out": 1,
|
|
504
|
+
"note": "entry node; initiation modes live on the flow trigger, not the node"},
|
|
505
|
+
{"type": "approval", "cardinality": ">= 1", "in": 1, "out": 2,
|
|
506
|
+
"note": "requires approved + rejected outcome edges; approverRules / mode / "
|
|
507
|
+
"selfPolicy / unavailablePolicy"},
|
|
508
|
+
{"type": "branch", "cardinality": ">= 0", "in": 1, "out": "1 per branch (+ defaultBranchId)",
|
|
509
|
+
"note": "same condition protocol as workflows; each branchId needs an edge"},
|
|
510
|
+
{"type": "updateRecord", "cardinality": ">= 0", "in": 1, "out": 1,
|
|
511
|
+
"note": "assignments update the locked record as system actor"},
|
|
512
|
+
{"type": "approvedEnd", "cardinality": ">= 1", "in": "any", "out": 0,
|
|
513
|
+
"note": "terminal approved outcome"},
|
|
514
|
+
{"type": "rejectedEnd", "cardinality": ">= 1", "in": "any", "out": 0,
|
|
515
|
+
"note": "terminal rejected outcome"},
|
|
516
|
+
)
|
|
517
|
+
|
|
422
518
|
FUNCTION_PERMISSIONS = (
|
|
423
519
|
"workspace.access", "app.manage", "object.manage", "dashboard.manage",
|
|
424
520
|
"version.manage", "org.manage", "system.manage",
|
|
@@ -432,9 +528,76 @@ DASHBOARD_DATE_RANGE_PRESETS = (
|
|
|
432
528
|
"this_week", "this_month", "this_quarter", "this_year",
|
|
433
529
|
)
|
|
434
530
|
|
|
531
|
+
# Page region column components (field + relatedList + approval).
|
|
532
|
+
# Field components omit `kind` (legacy snapshot shape); relatedList/approval set kind.
|
|
533
|
+
PAGE_COMPONENTS = {
|
|
534
|
+
"note": (
|
|
535
|
+
"Region columns hold field, relatedList, and/or approval components. "
|
|
536
|
+
"relatedList and approval are allowed only on detail pages; each detail "
|
|
537
|
+
"page may include at most one approval component. Prefer pages.get → "
|
|
538
|
+
"edit → pages.update; use pages.options to resolve candidate objects/fields."
|
|
539
|
+
),
|
|
540
|
+
"kinds": (
|
|
541
|
+
{
|
|
542
|
+
"kind": None,
|
|
543
|
+
"name": "field",
|
|
544
|
+
"note": "Omit kind. Places fields of sourceObjectId on a grid.",
|
|
545
|
+
"requiredKeys": (
|
|
546
|
+
"label", "hideLabel", "sourceObjectId", "grid",
|
|
547
|
+
"alignment", "labelPosition", "labelWidth", "fields",
|
|
548
|
+
),
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
"kind": "relatedList",
|
|
552
|
+
"name": "related record list",
|
|
553
|
+
"pageTypes": ("detail",),
|
|
554
|
+
"requiredKeys": (
|
|
555
|
+
"kind", "label", "hideLabel", "objectId", "relationFieldId",
|
|
556
|
+
"columnFieldIds", "sort", "limit", "showViewAll", "createAction",
|
|
557
|
+
),
|
|
558
|
+
"optionalKeys": ("id", "filters", "carryIns"),
|
|
559
|
+
"limit": (5, 10, 20),
|
|
560
|
+
"sortDirections": ("asc", "desc"),
|
|
561
|
+
"carryInSources": (
|
|
562
|
+
{
|
|
563
|
+
"source": "currentRecord",
|
|
564
|
+
"note": "Value is the current record id; target field must be "
|
|
565
|
+
"lookup.record pointing at the page object. When create "
|
|
566
|
+
"is enabled, relationFieldId must appear exactly once "
|
|
567
|
+
"with this source.",
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
"source": "currentRecordField",
|
|
571
|
+
"requires": "sourceFieldId",
|
|
572
|
+
"note": "Copy an active field from the current record; source "
|
|
573
|
+
"and target field types must match.",
|
|
574
|
+
},
|
|
575
|
+
),
|
|
576
|
+
"filters": {
|
|
577
|
+
"shape": {"fieldId": "fld_...", "operator": "...", "value": "..."},
|
|
578
|
+
"note": "Same filter protocol as object views; AND-merged with the "
|
|
579
|
+
"implicit relationFieldId eq currentRecord condition.",
|
|
580
|
+
},
|
|
581
|
+
},
|
|
582
|
+
{
|
|
583
|
+
"kind": "approval",
|
|
584
|
+
"name": "record approval",
|
|
585
|
+
"pageTypes": ("detail",),
|
|
586
|
+
"maxPerPage": 1,
|
|
587
|
+
"requiredKeys": ("kind", "label", "hideLabel"),
|
|
588
|
+
"optionalKeys": ("id",),
|
|
589
|
+
"note": (
|
|
590
|
+
"Reference-free presentation slot for submit / status / history. "
|
|
591
|
+
"Runtime data comes from employee APIs, not OpenAPI/CLI."
|
|
592
|
+
),
|
|
593
|
+
},
|
|
594
|
+
),
|
|
595
|
+
}
|
|
596
|
+
|
|
435
597
|
RESOURCE_ID_PREFIXES = {
|
|
436
598
|
"app_": "app", "grp_": "app group", "obj_": "object", "fld_": "field",
|
|
437
|
-
"viw_": "view", "wfl_": "workflow", "pag_": "custom page",
|
|
599
|
+
"viw_": "view", "wfl_": "workflow", "apr_": "approval flow", "pag_": "custom page",
|
|
600
|
+
"reg_": "page region", "cmp_": "page component",
|
|
438
601
|
"dsh_": "dashboard", "dcm_": "dashboard component", "rol_": "role",
|
|
439
602
|
"rec_": "record", "att_": "attachment", "emp_": "employee", "dep_": "department",
|
|
440
603
|
}
|
|
@@ -442,11 +605,13 @@ RESOURCE_ID_PREFIXES = {
|
|
|
442
605
|
CATALOGS = {
|
|
443
606
|
"field-types": FIELD_TYPES,
|
|
444
607
|
"workflow-nodes": WORKFLOW_NODE_TYPES,
|
|
608
|
+
"approval-nodes": APPROVAL_NODE_TYPES,
|
|
445
609
|
"permissions": {"function": FUNCTION_PERMISSIONS, "dataScopes": DATA_SCOPES},
|
|
446
610
|
"dashboard-filters": {
|
|
447
611
|
"types": DASHBOARD_FILTER_TYPES,
|
|
448
612
|
"dateRangePresets": DASHBOARD_DATE_RANGE_PRESETS,
|
|
449
613
|
},
|
|
614
|
+
"page-components": PAGE_COMPONENTS,
|
|
450
615
|
"id-prefixes": RESOURCE_ID_PREFIXES,
|
|
451
616
|
"icons": {
|
|
452
617
|
"note": "6146 Tabler icon names are supported; any Icon* name from the "
|
|
@@ -523,6 +523,22 @@ def handle_objects(args: argparse.Namespace) -> int:
|
|
|
523
523
|
return run_request(args, "objects.list", query={"status": args.status})
|
|
524
524
|
if action == "get":
|
|
525
525
|
return run_request(args, "objects.get", path_params={"object_id": args.object_id}, query={"status": args.status})
|
|
526
|
+
if action == "cli-edit-policy":
|
|
527
|
+
path = {"object_id": args.object_id}
|
|
528
|
+
if args.objects_cli_edit_policy_action == "get":
|
|
529
|
+
return run_request(
|
|
530
|
+
args, "objects.cli-edit-policy.get", path_params=path
|
|
531
|
+
)
|
|
532
|
+
body = load_json_payload(args.json, args.json_file)
|
|
533
|
+
require_fields(body, "baseRevision", "policies")
|
|
534
|
+
if not isinstance(body["policies"], dict):
|
|
535
|
+
raise CliError("Request field 'policies' must be an object.")
|
|
536
|
+
return run_request(
|
|
537
|
+
args,
|
|
538
|
+
"objects.cli-edit-policy.update",
|
|
539
|
+
path_params=path,
|
|
540
|
+
body=body,
|
|
541
|
+
)
|
|
526
542
|
if action == "create":
|
|
527
543
|
body = _payload_from_args(
|
|
528
544
|
args,
|
|
@@ -638,6 +654,34 @@ def handle_workflows(args: argparse.Namespace) -> int:
|
|
|
638
654
|
)
|
|
639
655
|
|
|
640
656
|
|
|
657
|
+
def handle_approvals(args: argparse.Namespace) -> int:
|
|
658
|
+
action = args.approvals_action
|
|
659
|
+
path = {"object_id": args.object_id}
|
|
660
|
+
if hasattr(args, "approval_id"):
|
|
661
|
+
path["approval_id"] = args.approval_id
|
|
662
|
+
if action in ("list", "get"):
|
|
663
|
+
return run_request(args, f"approvals.{action}", path_params=path)
|
|
664
|
+
if action in ("create", "update"):
|
|
665
|
+
body = load_json_payload(args.json, args.json_file)
|
|
666
|
+
require_fields(body, "label", "trigger", "displayFieldIds", "nodes", "edges")
|
|
667
|
+
return run_request(args, f"approvals.{action}", path_params=path, body=body)
|
|
668
|
+
if action == "reorder":
|
|
669
|
+
body = load_json_payload(args.json, args.json_file)
|
|
670
|
+
require_fields(body, "baseRevision", "approvalIds")
|
|
671
|
+
if not isinstance(body["approvalIds"], list):
|
|
672
|
+
raise CliError("Request field 'approvalIds' must be an array.")
|
|
673
|
+
return run_request(args, "approvals.reorder", path_params=path, body=body)
|
|
674
|
+
if action == "set-enabled":
|
|
675
|
+
return run_request(
|
|
676
|
+
args, "approvals.set-enabled", path_params=path,
|
|
677
|
+
body={"baseRevision": args.base_revision, "enabled": args.enabled},
|
|
678
|
+
)
|
|
679
|
+
return run_request(
|
|
680
|
+
args, "approvals.delete", path_params=path,
|
|
681
|
+
body={"baseRevision": args.base_revision},
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
|
|
641
685
|
def handle_pages(args: argparse.Namespace) -> int:
|
|
642
686
|
action = args.pages_action
|
|
643
687
|
if action == "list":
|
|
@@ -906,7 +950,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
906
950
|
docs_actions.add_parser("operations")
|
|
907
951
|
docs_catalogs = docs_actions.add_parser(
|
|
908
952
|
"catalogs",
|
|
909
|
-
help="List closed-domain catalogs, or dump one (field-types, workflow-nodes, permissions, dashboard-filters, id-prefixes, icons).",
|
|
953
|
+
help="List closed-domain catalogs, or dump one (field-types, workflow-nodes, approval-nodes, permissions, dashboard-filters, page-components, id-prefixes, icons).",
|
|
910
954
|
)
|
|
911
955
|
docs_catalogs.add_argument("name", nargs="?", default=None)
|
|
912
956
|
docs_show = docs_actions.add_parser("show")
|
|
@@ -1054,6 +1098,21 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1054
1098
|
item = object_actions.add_parser(action)
|
|
1055
1099
|
item.add_argument("object_id")
|
|
1056
1100
|
add_yes(item)
|
|
1101
|
+
object_cli_policy = object_actions.add_parser(
|
|
1102
|
+
"cli-edit-policy", help="Read or update user CLI field edit policies."
|
|
1103
|
+
)
|
|
1104
|
+
object_cli_policy_actions = object_cli_policy.add_subparsers(
|
|
1105
|
+
dest="objects_cli_edit_policy_action", required=True
|
|
1106
|
+
)
|
|
1107
|
+
object_cli_policy_get = object_cli_policy_actions.add_parser("get")
|
|
1108
|
+
object_cli_policy_get.add_argument("object_id")
|
|
1109
|
+
object_cli_policy_update = object_cli_policy_actions.add_parser("update")
|
|
1110
|
+
object_cli_policy_update.add_argument("object_id")
|
|
1111
|
+
add_json_args(
|
|
1112
|
+
object_cli_policy_update,
|
|
1113
|
+
endpoint_key="objects.cli-edit-policy.update",
|
|
1114
|
+
)
|
|
1115
|
+
add_yes(object_cli_policy_update)
|
|
1057
1116
|
objects.set_defaults(handler=handle_objects)
|
|
1058
1117
|
|
|
1059
1118
|
fields = groups.add_parser("fields", help="Manage field metadata.")
|
|
@@ -1137,6 +1196,36 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1137
1196
|
workflow_executions.add_argument("--result", choices=("success", "failure"))
|
|
1138
1197
|
workflows.set_defaults(handler=handle_workflows)
|
|
1139
1198
|
|
|
1199
|
+
approvals = groups.add_parser("approvals", help="Manage object approval flow metadata.")
|
|
1200
|
+
approval_actions = approvals.add_subparsers(dest="approvals_action", required=True)
|
|
1201
|
+
approval_list = approval_actions.add_parser("list")
|
|
1202
|
+
approval_list.add_argument("object_id")
|
|
1203
|
+
approval_get = approval_actions.add_parser("get")
|
|
1204
|
+
approval_get.add_argument("object_id")
|
|
1205
|
+
approval_get.add_argument("approval_id")
|
|
1206
|
+
for action in ("create", "reorder"):
|
|
1207
|
+
item = approval_actions.add_parser(action)
|
|
1208
|
+
item.add_argument("object_id")
|
|
1209
|
+
add_json_args(item, endpoint_key=f"approvals.{action}")
|
|
1210
|
+
approval_update = approval_actions.add_parser("update")
|
|
1211
|
+
approval_update.add_argument("object_id")
|
|
1212
|
+
approval_update.add_argument("approval_id")
|
|
1213
|
+
add_json_args(approval_update, endpoint_key="approvals.update")
|
|
1214
|
+
approval_enabled = approval_actions.add_parser("set-enabled")
|
|
1215
|
+
approval_enabled.add_argument("object_id")
|
|
1216
|
+
approval_enabled.add_argument("approval_id")
|
|
1217
|
+
approval_enabled.add_argument("--base-revision", required=True, type=int)
|
|
1218
|
+
approval_enabled.add_argument(
|
|
1219
|
+
"--enabled", action=argparse.BooleanOptionalAction, required=True,
|
|
1220
|
+
)
|
|
1221
|
+
add_yes(approval_enabled)
|
|
1222
|
+
approval_delete = approval_actions.add_parser("delete")
|
|
1223
|
+
approval_delete.add_argument("object_id")
|
|
1224
|
+
approval_delete.add_argument("approval_id")
|
|
1225
|
+
approval_delete.add_argument("--base-revision", required=True, type=int)
|
|
1226
|
+
add_yes(approval_delete)
|
|
1227
|
+
approvals.set_defaults(handler=handle_approvals)
|
|
1228
|
+
|
|
1140
1229
|
pages = groups.add_parser("pages", help="Manage custom Page metadata.")
|
|
1141
1230
|
page_actions = pages.add_subparsers(dest="pages_action", required=True)
|
|
1142
1231
|
page_list = page_actions.add_parser("list")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flowbase-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.9
|
|
4
4
|
Summary: Command line client for the FlowBase OpenAPI
|
|
5
5
|
Author: DeepFlowAI
|
|
6
6
|
License: Apache-2.0
|
|
@@ -9,7 +9,7 @@ Description-Content-Type: text/markdown
|
|
|
9
9
|
|
|
10
10
|
# FlowBase CLI
|
|
11
11
|
|
|
12
|
-
`flowbase` is the official command line client for the FlowBase OpenAPI. It manages apps, objects, fields, views, custom pages, workflows, dashboards, business records, roles, and configuration versions across the three FlowBase environments.
|
|
12
|
+
`flowbase` is the official command line client for the FlowBase OpenAPI. It manages apps, objects, fields, views, custom pages, workflows, approval flows, dashboards, business records, roles, and configuration versions across the three FlowBase environments.
|
|
13
13
|
|
|
14
14
|
## 1. Install and connect
|
|
15
15
|
|
|
@@ -47,7 +47,7 @@ Most commands run on every Host; `roles` is production-only and `changes commit/
|
|
|
47
47
|
|
|
48
48
|
### 3.1 Metadata vs records
|
|
49
49
|
|
|
50
|
-
- **Metadata** (`metadata` scope): apps, objects, fields, views, pages, workflows, dashboards, roles, versions.
|
|
50
|
+
- **Metadata** (`metadata` scope): apps, objects, fields, views, pages, workflows, approvals, dashboards, roles, versions.
|
|
51
51
|
- **Business records** (`records` scope): rows inside an object.
|
|
52
52
|
|
|
53
53
|
Metadata changes are **drafts** against the current environment's configuration repository. They do not affect the running front-end until they are committed and released.
|
|
@@ -77,9 +77,11 @@ flowbase
|
|
|
77
77
|
│ add-dashboards | remove-dashboard | set-dashboard-group | reorder-members
|
|
78
78
|
├── groups create | reorder | update | delete App groups (under an app)
|
|
79
79
|
├── objects list | get | create | update | delete | restore
|
|
80
|
+
│ cli-edit-policy get | update
|
|
80
81
|
├── fields create | update | delete | restore Fields under an object
|
|
81
82
|
├── views list | create | update | reorder | set-enabled | delete
|
|
82
83
|
├── workflows list | get | create | update | reorder | set-enabled | delete | executions
|
|
84
|
+
├── approvals list | get | create | update | reorder | set-enabled | delete
|
|
83
85
|
├── pages list | options | get | create | update | set-default | delete | restore
|
|
84
86
|
├── dashboards list | get | create | update | copy | delete
|
|
85
87
|
├── changes commit | release | direct-release
|
|
@@ -114,6 +116,7 @@ flowbase docs catalogs field-types # field type names + allowed constrain
|
|
|
114
116
|
flowbase docs catalogs workflow-nodes # node types with in/out edge rules
|
|
115
117
|
flowbase docs catalogs permissions # function permission points + data scopes
|
|
116
118
|
flowbase docs catalogs dashboard-filters # filter types + date-range presets
|
|
119
|
+
flowbase docs catalogs page-components # page region component kinds (field / relatedList / approval)
|
|
117
120
|
flowbase docs catalogs id-prefixes # resource ID prefix meanings
|
|
118
121
|
flowbase docs catalogs icons # icon guidance + examples
|
|
119
122
|
```
|
|
@@ -138,9 +141,11 @@ Do not compose update bodies from scratch when an existing resource exists — `
|
|
|
138
141
|
flowbase objects create --label Customer --key customer --description "Customer records"
|
|
139
142
|
flowbase fields create obj_xxx --json '{"label":"Name","key":"name","description":"Customer name","type":"text.singleLine","permission":"required"}'
|
|
140
143
|
flowbase records create obj_xxx --json '{"values":{"name":"Acme Corp"}}'
|
|
144
|
+
flowbase objects cli-edit-policy get obj_xxx
|
|
145
|
+
flowbase objects cli-edit-policy update obj_xxx --json '{"baseRevision":4,"policies":{"fld_xxx":"user_approval"}}' --yes
|
|
141
146
|
```
|
|
142
147
|
|
|
143
|
-
Record `values` keys are **field keys** (`name`), not internal IDs (`fld_...`). IDs are `obj_`/`fld_`/`dsh_`/`wfl_`/`pag_`-prefixed; see `docs catalogs id-prefixes`.
|
|
148
|
+
Record `values` keys are **field keys** (`name`), not internal IDs (`fld_...`). IDs are `obj_`/`fld_`/`dsh_`/`wfl_`/`pag_`/`reg_`/`cmp_`-prefixed; see `docs catalogs id-prefixes`.
|
|
144
149
|
|
|
145
150
|
### 6.2 Edit and publish configuration on a test environment
|
|
146
151
|
|
|
@@ -167,19 +172,32 @@ flowbase changes direct-release --message "Fix customer field" --yes
|
|
|
167
172
|
|
|
168
173
|
`direct-release` always runs a server-side preflight, prints the current version, diff summary, checks and risk, and binds a one-time confirmation token. `--yes` accepts that reviewed result; it never skips validation.
|
|
169
174
|
|
|
170
|
-
### 6.4 Workflows and pages
|
|
175
|
+
### 6.4 Workflows, approvals, and pages
|
|
171
176
|
|
|
172
177
|
A workflow graph must satisfy structural rules: exactly one `trigger` node, at least one `end` node, a DAG, and valid per-type edges. See `docs catalogs workflow-nodes` before composing `nodes`/`edges`.
|
|
173
178
|
|
|
179
|
+
Approval flow graphs are separate metadata: exactly one `trigger`, at least one `approval` node, both `approvedEnd` and `rejectedEnd`, and dual outcome edges from each approval node. Employee task decisions are not available via API key / CLI. See `docs catalogs approval-nodes`.
|
|
180
|
+
|
|
174
181
|
```bash
|
|
175
182
|
flowbase docs catalogs workflow-nodes
|
|
176
183
|
flowbase workflows create obj_xxx --json '{"label":"Set owner","enabled":true,"trigger":{"event":"create","match":"all","conditions":[]},"nodes":[],"edges":[]}'
|
|
177
184
|
flowbase workflows update obj_xxx wfl_xxx --json-file workflow.json
|
|
178
185
|
flowbase workflows executions obj_xxx wfl_xxx --result failure
|
|
179
186
|
|
|
180
|
-
flowbase docs
|
|
187
|
+
flowbase docs catalogs approval-nodes
|
|
188
|
+
flowbase docs example approvals.create --output approval.json
|
|
189
|
+
flowbase approvals create obj_xxx --json-file approval.json
|
|
190
|
+
flowbase approvals set-enabled obj_xxx apr_xxx --base-revision 4 --enabled --yes
|
|
191
|
+
|
|
192
|
+
flowbase docs catalogs page-components # field + relatedList + approval keys
|
|
193
|
+
flowbase docs example pages.create --output page.json # create-page layout template
|
|
181
194
|
flowbase pages create --json-file page.json
|
|
182
195
|
flowbase pages set-default pag_xxx --base-revision 4 --yes
|
|
196
|
+
|
|
197
|
+
# Detail pages may embed relatedList and at most one approval component.
|
|
198
|
+
flowbase pages get pag_xxx > page.json # prefer get → edit → update
|
|
199
|
+
# or: flowbase docs example pages.update --output page.json
|
|
200
|
+
flowbase pages update pag_xxx --json-file page.json
|
|
183
201
|
```
|
|
184
202
|
|
|
185
203
|
### 6.5 Records and attachments
|
|
@@ -90,6 +90,8 @@ class FlowBaseCliTest(unittest.TestCase):
|
|
|
90
90
|
self.assertTrue(any(item["key"] == "pages.create" for item in payload))
|
|
91
91
|
self.assertTrue(any(item["key"] == "views.create" for item in payload))
|
|
92
92
|
self.assertTrue(any(item["key"] == "roles.create" for item in payload))
|
|
93
|
+
self.assertTrue(any(item["key"] == "objects.cli-edit-policy.get" for item in payload))
|
|
94
|
+
self.assertTrue(any(item["key"] == "objects.cli-edit-policy.update" for item in payload))
|
|
93
95
|
role_create = next(item for item in payload if item["key"] == "roles.create")
|
|
94
96
|
self.assertEqual(role_create["environments"], ["production"])
|
|
95
97
|
|
|
@@ -110,6 +112,22 @@ class FlowBaseCliTest(unittest.TestCase):
|
|
|
110
112
|
payload = json.loads(stdout.getvalue())
|
|
111
113
|
self.assertIn("regions", payload["requestBody"])
|
|
112
114
|
|
|
115
|
+
stdout = io.StringIO()
|
|
116
|
+
with patch("sys.stdout", stdout):
|
|
117
|
+
code = main(["docs", "show", "pages.update"])
|
|
118
|
+
self.assertEqual(code, 0)
|
|
119
|
+
payload = json.loads(stdout.getvalue())
|
|
120
|
+
body = json.loads(payload["requestBody"])
|
|
121
|
+
self.assertEqual(body["page"]["type"], "detail")
|
|
122
|
+
components = body["page"]["regions"][0]["columns"][0]
|
|
123
|
+
self.assertEqual(components[0]["kind"], "relatedList")
|
|
124
|
+
self.assertIn("relationFieldId", components[0])
|
|
125
|
+
self.assertIn("carryIns", components[0])
|
|
126
|
+
self.assertEqual(
|
|
127
|
+
components[1],
|
|
128
|
+
{"kind": "approval", "label": "Approval", "hideLabel": False},
|
|
129
|
+
)
|
|
130
|
+
|
|
113
131
|
def test_docs_catalogs_lists_and_dumps_domains(self):
|
|
114
132
|
stdout = io.StringIO()
|
|
115
133
|
with patch("sys.stdout", stdout):
|
|
@@ -118,7 +136,9 @@ class FlowBaseCliTest(unittest.TestCase):
|
|
|
118
136
|
names = json.loads(stdout.getvalue())
|
|
119
137
|
self.assertIn("field-types", names)
|
|
120
138
|
self.assertIn("workflow-nodes", names)
|
|
139
|
+
self.assertIn("approval-nodes", names)
|
|
121
140
|
self.assertIn("permissions", names)
|
|
141
|
+
self.assertIn("page-components", names)
|
|
122
142
|
|
|
123
143
|
stdout = io.StringIO()
|
|
124
144
|
with patch("sys.stdout", stdout):
|
|
@@ -126,8 +146,15 @@ class FlowBaseCliTest(unittest.TestCase):
|
|
|
126
146
|
self.assertEqual(code, 0)
|
|
127
147
|
field_types = json.loads(stdout.getvalue())
|
|
128
148
|
type_names = {item["type"] for item in field_types}
|
|
149
|
+
self.assertEqual(len(field_types), 25)
|
|
129
150
|
self.assertIn("select.single", type_names)
|
|
130
151
|
self.assertIn("lookup.employee", type_names)
|
|
152
|
+
self.assertTrue({
|
|
153
|
+
"autonumber", "number.currency", "number.percent", "duration",
|
|
154
|
+
"rating", "lookup.record.multiple", "lookup.department",
|
|
155
|
+
}.issubset(type_names))
|
|
156
|
+
autonumber = next(item for item in field_types if item["type"] == "autonumber")
|
|
157
|
+
self.assertFalse(autonumber["writable"])
|
|
131
158
|
|
|
132
159
|
stdout = io.StringIO()
|
|
133
160
|
with patch("sys.stdout", stdout):
|
|
@@ -136,6 +163,40 @@ class FlowBaseCliTest(unittest.TestCase):
|
|
|
136
163
|
nodes = json.loads(stdout.getvalue())
|
|
137
164
|
self.assertIn("trigger", {item["type"] for item in nodes})
|
|
138
165
|
|
|
166
|
+
stdout = io.StringIO()
|
|
167
|
+
with patch("sys.stdout", stdout):
|
|
168
|
+
code = main(["docs", "catalogs", "approval-nodes"])
|
|
169
|
+
self.assertEqual(code, 0)
|
|
170
|
+
approval_nodes = json.loads(stdout.getvalue())
|
|
171
|
+
self.assertTrue(
|
|
172
|
+
{"trigger", "approval", "approvedEnd", "rejectedEnd"}.issubset(
|
|
173
|
+
{item["type"] for item in approval_nodes}
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
stdout = io.StringIO()
|
|
178
|
+
with patch("sys.stdout", stdout):
|
|
179
|
+
code = main(["docs", "catalogs", "page-components"])
|
|
180
|
+
self.assertEqual(code, 0)
|
|
181
|
+
page_components = json.loads(stdout.getvalue())
|
|
182
|
+
kinds = {item.get("kind") for item in page_components["kinds"]}
|
|
183
|
+
self.assertIn(None, kinds)
|
|
184
|
+
self.assertIn("relatedList", kinds)
|
|
185
|
+
self.assertIn("approval", kinds)
|
|
186
|
+
approval = next(
|
|
187
|
+
item for item in page_components["kinds"] if item.get("kind") == "approval"
|
|
188
|
+
)
|
|
189
|
+
self.assertEqual(approval["maxPerPage"], 1)
|
|
190
|
+
self.assertEqual(set(approval["requiredKeys"]), {"kind", "label", "hideLabel"})
|
|
191
|
+
|
|
192
|
+
stdout = io.StringIO()
|
|
193
|
+
with patch("sys.stdout", stdout):
|
|
194
|
+
code = main(["docs", "catalogs", "id-prefixes"])
|
|
195
|
+
self.assertEqual(code, 0)
|
|
196
|
+
prefixes = json.loads(stdout.getvalue())
|
|
197
|
+
self.assertEqual(prefixes["reg_"], "page region")
|
|
198
|
+
self.assertEqual(prefixes["cmp_"], "page component")
|
|
199
|
+
|
|
139
200
|
def test_docs_list_marks_body_and_confirmation(self):
|
|
140
201
|
stdout = io.StringIO()
|
|
141
202
|
with patch("sys.stdout", stdout):
|
|
@@ -204,6 +265,92 @@ class FlowBaseCliTest(unittest.TestCase):
|
|
|
204
265
|
)
|
|
205
266
|
self.assertEqual(request.call_args.kwargs["body"], body)
|
|
206
267
|
|
|
268
|
+
def test_approval_create_sends_graph_snapshot(self):
|
|
269
|
+
body = {
|
|
270
|
+
"baseRevision": 3,
|
|
271
|
+
"label": "Contract approval",
|
|
272
|
+
"trigger": {
|
|
273
|
+
"manual": True,
|
|
274
|
+
"create": False,
|
|
275
|
+
"update": False,
|
|
276
|
+
"match": "all",
|
|
277
|
+
"conditions": [],
|
|
278
|
+
},
|
|
279
|
+
"displayFieldIds": ["fld_1"],
|
|
280
|
+
"lockRecord": True,
|
|
281
|
+
"allowWithdraw": True,
|
|
282
|
+
"nodes": [{"id": "apn_trigger", "type": "trigger"}],
|
|
283
|
+
"edges": [],
|
|
284
|
+
}
|
|
285
|
+
stdout = io.StringIO()
|
|
286
|
+
with (
|
|
287
|
+
patch(
|
|
288
|
+
"flowbase_cli.main.OpenApiClient.request",
|
|
289
|
+
return_value=b'{"approvalId":"apr_1","draftRevision":4}',
|
|
290
|
+
) as request,
|
|
291
|
+
patch("sys.stdout", stdout),
|
|
292
|
+
):
|
|
293
|
+
code = main([
|
|
294
|
+
"--api-key",
|
|
295
|
+
"fbk_test",
|
|
296
|
+
"approvals",
|
|
297
|
+
"create",
|
|
298
|
+
"obj_1",
|
|
299
|
+
"--json",
|
|
300
|
+
json.dumps(body),
|
|
301
|
+
])
|
|
302
|
+
self.assertEqual(code, 0)
|
|
303
|
+
self.assertEqual(request.call_args.args[0], "approvals.create")
|
|
304
|
+
self.assertEqual(
|
|
305
|
+
request.call_args.kwargs["path_params"],
|
|
306
|
+
{"object_id": "obj_1"},
|
|
307
|
+
)
|
|
308
|
+
self.assertEqual(request.call_args.kwargs["body"], body)
|
|
309
|
+
|
|
310
|
+
def test_approval_set_enabled_sends_revision_and_requires_yes(self):
|
|
311
|
+
stderr = io.StringIO()
|
|
312
|
+
with patch("sys.stderr", stderr), patch("sys.stdin.isatty", return_value=False):
|
|
313
|
+
code = main([
|
|
314
|
+
"--api-key",
|
|
315
|
+
"fbk_test",
|
|
316
|
+
"approvals",
|
|
317
|
+
"set-enabled",
|
|
318
|
+
"obj_1",
|
|
319
|
+
"apr_1",
|
|
320
|
+
"--base-revision",
|
|
321
|
+
"4",
|
|
322
|
+
"--no-enabled",
|
|
323
|
+
])
|
|
324
|
+
self.assertEqual(code, 1)
|
|
325
|
+
self.assertIn("without --yes", stderr.getvalue())
|
|
326
|
+
|
|
327
|
+
with patch(
|
|
328
|
+
"flowbase_cli.main.OpenApiClient.request",
|
|
329
|
+
return_value=b'{"ok":true,"draftRevision":5}',
|
|
330
|
+
) as request:
|
|
331
|
+
code = main([
|
|
332
|
+
"--api-key",
|
|
333
|
+
"fbk_test",
|
|
334
|
+
"approvals",
|
|
335
|
+
"set-enabled",
|
|
336
|
+
"obj_1",
|
|
337
|
+
"apr_1",
|
|
338
|
+
"--base-revision",
|
|
339
|
+
"4",
|
|
340
|
+
"--no-enabled",
|
|
341
|
+
"--yes",
|
|
342
|
+
])
|
|
343
|
+
self.assertEqual(code, 0)
|
|
344
|
+
self.assertEqual(request.call_args.args[0], "approvals.set-enabled")
|
|
345
|
+
self.assertEqual(
|
|
346
|
+
request.call_args.kwargs["path_params"],
|
|
347
|
+
{"object_id": "obj_1", "approval_id": "apr_1"},
|
|
348
|
+
)
|
|
349
|
+
self.assertEqual(
|
|
350
|
+
request.call_args.kwargs["body"],
|
|
351
|
+
{"baseRevision": 4, "enabled": False},
|
|
352
|
+
)
|
|
353
|
+
|
|
207
354
|
def test_view_set_enabled_sends_revision_and_requires_yes(self):
|
|
208
355
|
stderr = io.StringIO()
|
|
209
356
|
with patch("sys.stderr", stderr), patch("sys.stdin.isatty", return_value=False):
|
|
@@ -338,6 +485,83 @@ class FlowBaseCliTest(unittest.TestCase):
|
|
|
338
485
|
},
|
|
339
486
|
)
|
|
340
487
|
|
|
488
|
+
def test_object_cli_edit_policy_get_and_update(self):
|
|
489
|
+
with patch(
|
|
490
|
+
"flowbase_cli.main.OpenApiClient.request",
|
|
491
|
+
return_value=b'{"objectId":"obj_1","draftRevision":4,"policies":{}}',
|
|
492
|
+
) as request:
|
|
493
|
+
code = main([
|
|
494
|
+
"--api-key",
|
|
495
|
+
"fbk_test",
|
|
496
|
+
"objects",
|
|
497
|
+
"cli-edit-policy",
|
|
498
|
+
"get",
|
|
499
|
+
"obj_1",
|
|
500
|
+
])
|
|
501
|
+
self.assertEqual(code, 0)
|
|
502
|
+
self.assertEqual(request.call_args.args[0], "objects.cli-edit-policy.get")
|
|
503
|
+
self.assertEqual(
|
|
504
|
+
request.call_args.kwargs["path_params"], {"object_id": "obj_1"}
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
body = {
|
|
508
|
+
"baseRevision": 4,
|
|
509
|
+
"policies": {"fld_1": "user_approval", "fld_2": "direct"},
|
|
510
|
+
}
|
|
511
|
+
with patch(
|
|
512
|
+
"flowbase_cli.main.OpenApiClient.request",
|
|
513
|
+
return_value=b'{"ok":true,"draftRevision":5}',
|
|
514
|
+
) as request:
|
|
515
|
+
code = main([
|
|
516
|
+
"--api-key",
|
|
517
|
+
"fbk_test",
|
|
518
|
+
"objects",
|
|
519
|
+
"cli-edit-policy",
|
|
520
|
+
"update",
|
|
521
|
+
"obj_1",
|
|
522
|
+
"--json",
|
|
523
|
+
json.dumps(body),
|
|
524
|
+
"--yes",
|
|
525
|
+
])
|
|
526
|
+
self.assertEqual(code, 0)
|
|
527
|
+
self.assertEqual(request.call_args.args[0], "objects.cli-edit-policy.update")
|
|
528
|
+
self.assertEqual(request.call_args.kwargs["body"], body)
|
|
529
|
+
|
|
530
|
+
def test_object_cli_edit_policy_update_requires_complete_body_and_yes(self):
|
|
531
|
+
stderr = io.StringIO()
|
|
532
|
+
with patch("sys.stderr", stderr):
|
|
533
|
+
code = main([
|
|
534
|
+
"objects",
|
|
535
|
+
"cli-edit-policy",
|
|
536
|
+
"update",
|
|
537
|
+
"obj_1",
|
|
538
|
+
"--json",
|
|
539
|
+
'{"baseRevision":4}',
|
|
540
|
+
"--yes",
|
|
541
|
+
])
|
|
542
|
+
self.assertEqual(code, 1)
|
|
543
|
+
self.assertIn("policies", stderr.getvalue())
|
|
544
|
+
|
|
545
|
+
stderr = io.StringIO()
|
|
546
|
+
with (
|
|
547
|
+
patch("sys.stderr", stderr),
|
|
548
|
+
patch("sys.stdin.isatty", return_value=False),
|
|
549
|
+
patch("flowbase_cli.main.OpenApiClient.request") as request,
|
|
550
|
+
):
|
|
551
|
+
code = main([
|
|
552
|
+
"--api-key",
|
|
553
|
+
"fbk_test",
|
|
554
|
+
"objects",
|
|
555
|
+
"cli-edit-policy",
|
|
556
|
+
"update",
|
|
557
|
+
"obj_1",
|
|
558
|
+
"--json",
|
|
559
|
+
'{"baseRevision":4,"policies":{}}',
|
|
560
|
+
])
|
|
561
|
+
self.assertEqual(code, 1)
|
|
562
|
+
self.assertIn("without --yes", stderr.getvalue())
|
|
563
|
+
request.assert_not_called()
|
|
564
|
+
|
|
341
565
|
def test_group_create_and_app_set_group(self):
|
|
342
566
|
with patch(
|
|
343
567
|
"flowbase_cli.main.OpenApiClient.request",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|