flowbase-cli 0.1.8__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.8 → flowbase_cli-0.1.9}/PKG-INFO +18 -7
- {flowbase_cli-0.1.8 → flowbase_cli-0.1.9}/README.md +17 -6
- {flowbase_cli-0.1.8 → flowbase_cli-0.1.9}/pyproject.toml +1 -1
- {flowbase_cli-0.1.8 → flowbase_cli-0.1.9}/src/flowbase_cli/__init__.py +1 -1
- {flowbase_cli-0.1.8 → flowbase_cli-0.1.9}/src/flowbase_cli/endpoints.py +111 -8
- {flowbase_cli-0.1.8 → flowbase_cli-0.1.9}/src/flowbase_cli/main.py +90 -1
- {flowbase_cli-0.1.8 → flowbase_cli-0.1.9}/src/flowbase_cli.egg-info/PKG-INFO +18 -7
- {flowbase_cli-0.1.8 → flowbase_cli-0.1.9}/tests/test_cli.py +198 -4
- {flowbase_cli-0.1.8 → flowbase_cli-0.1.9}/setup.cfg +0 -0
- {flowbase_cli-0.1.8 → flowbase_cli-0.1.9}/src/flowbase_cli.egg-info/SOURCES.txt +0 -0
- {flowbase_cli-0.1.8 → flowbase_cli-0.1.9}/src/flowbase_cli.egg-info/dependency_links.txt +0 -0
- {flowbase_cli-0.1.8 → flowbase_cli-0.1.9}/src/flowbase_cli.egg-info/entry_points.txt +0 -0
- {flowbase_cli-0.1.8 → 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,7 +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
|
|
117
|
-
flowbase docs catalogs page-components # page region component kinds (field / relatedList)
|
|
119
|
+
flowbase docs catalogs page-components # page region component kinds (field / relatedList / approval)
|
|
118
120
|
flowbase docs catalogs id-prefixes # resource ID prefix meanings
|
|
119
121
|
flowbase docs catalogs icons # icon guidance + examples
|
|
120
122
|
```
|
|
@@ -139,6 +141,8 @@ Do not compose update bodies from scratch when an existing resource exists — `
|
|
|
139
141
|
flowbase objects create --label Customer --key customer --description "Customer records"
|
|
140
142
|
flowbase fields create obj_xxx --json '{"label":"Name","key":"name","description":"Customer name","type":"text.singleLine","permission":"required"}'
|
|
141
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
|
|
142
146
|
```
|
|
143
147
|
|
|
144
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`.
|
|
@@ -168,22 +172,29 @@ flowbase changes direct-release --message "Fix customer field" --yes
|
|
|
168
172
|
|
|
169
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.
|
|
170
174
|
|
|
171
|
-
### 6.4 Workflows and pages
|
|
175
|
+
### 6.4 Workflows, approvals, and pages
|
|
172
176
|
|
|
173
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`.
|
|
174
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
|
+
|
|
175
181
|
```bash
|
|
176
182
|
flowbase docs catalogs workflow-nodes
|
|
177
183
|
flowbase workflows create obj_xxx --json '{"label":"Set owner","enabled":true,"trigger":{"event":"create","match":"all","conditions":[]},"nodes":[],"edges":[]}'
|
|
178
184
|
flowbase workflows update obj_xxx wfl_xxx --json-file workflow.json
|
|
179
185
|
flowbase workflows executions obj_xxx wfl_xxx --result failure
|
|
180
186
|
|
|
181
|
-
flowbase docs catalogs
|
|
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
|
|
182
193
|
flowbase docs example pages.create --output page.json # create-page layout template
|
|
183
194
|
flowbase pages create --json-file page.json
|
|
184
195
|
flowbase pages set-default pag_xxx --base-revision 4 --yes
|
|
185
196
|
|
|
186
|
-
# Detail pages may embed relatedList
|
|
197
|
+
# Detail pages may embed relatedList and at most one approval component.
|
|
187
198
|
flowbase pages get pag_xxx > page.json # prefer get → edit → update
|
|
188
199
|
# or: flowbase docs example pages.update --output page.json
|
|
189
200
|
flowbase pages update pag_xxx --json-file page.json
|
|
@@ -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,7 +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
|
|
108
|
-
flowbase docs catalogs page-components # page region component kinds (field / relatedList)
|
|
110
|
+
flowbase docs catalogs page-components # page region component kinds (field / relatedList / approval)
|
|
109
111
|
flowbase docs catalogs id-prefixes # resource ID prefix meanings
|
|
110
112
|
flowbase docs catalogs icons # icon guidance + examples
|
|
111
113
|
```
|
|
@@ -130,6 +132,8 @@ Do not compose update bodies from scratch when an existing resource exists — `
|
|
|
130
132
|
flowbase objects create --label Customer --key customer --description "Customer records"
|
|
131
133
|
flowbase fields create obj_xxx --json '{"label":"Name","key":"name","description":"Customer name","type":"text.singleLine","permission":"required"}'
|
|
132
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
|
|
133
137
|
```
|
|
134
138
|
|
|
135
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`.
|
|
@@ -159,22 +163,29 @@ flowbase changes direct-release --message "Fix customer field" --yes
|
|
|
159
163
|
|
|
160
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.
|
|
161
165
|
|
|
162
|
-
### 6.4 Workflows and pages
|
|
166
|
+
### 6.4 Workflows, approvals, and pages
|
|
163
167
|
|
|
164
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`.
|
|
165
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
|
+
|
|
166
172
|
```bash
|
|
167
173
|
flowbase docs catalogs workflow-nodes
|
|
168
174
|
flowbase workflows create obj_xxx --json '{"label":"Set owner","enabled":true,"trigger":{"event":"create","match":"all","conditions":[]},"nodes":[],"edges":[]}'
|
|
169
175
|
flowbase workflows update obj_xxx wfl_xxx --json-file workflow.json
|
|
170
176
|
flowbase workflows executions obj_xxx wfl_xxx --result failure
|
|
171
177
|
|
|
172
|
-
flowbase docs catalogs
|
|
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
|
|
173
184
|
flowbase docs example pages.create --output page.json # create-page layout template
|
|
174
185
|
flowbase pages create --json-file page.json
|
|
175
186
|
flowbase pages set-default pag_xxx --base-revision 4 --yes
|
|
176
187
|
|
|
177
|
-
# Detail pages may embed relatedList
|
|
188
|
+
# Detail pages may embed relatedList and at most one approval component.
|
|
178
189
|
flowbase pages get pag_xxx > page.json # prefer get → edit → update
|
|
179
190
|
# or: flowbase docs example pages.update --output page.json
|
|
180
191
|
flowbase pages update pag_xxx --json-file page.json
|
|
@@ -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"),
|
|
@@ -242,7 +307,8 @@ ENDPOINTS = (
|
|
|
242
307
|
'"columnFieldIds":["fld_title","fld_status","fld_created_at"],'
|
|
243
308
|
'"sort":{"fieldId":"fld_created_at","direction":"desc"},"limit":5,"showViewAll":true,'
|
|
244
309
|
'"createAction":{"enabled":true,"label":"New ticket"},'
|
|
245
|
-
'"carryIns":[{"fieldId":"fld_customer","source":"currentRecord"}]}
|
|
310
|
+
'"carryIns":[{"fieldId":"fld_customer","source":"currentRecord"}]},'
|
|
311
|
+
'{"kind":"approval","label":"Approval","hideLabel":false}]]}],"rules":[]}}'
|
|
246
312
|
)),
|
|
247
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,
|
|
248
314
|
request_body='{"baseRevision":4}'),
|
|
@@ -391,7 +457,7 @@ ENDPOINTS = (
|
|
|
391
457
|
ENDPOINT_BY_KEY = {endpoint.key: endpoint for endpoint in ENDPOINTS}
|
|
392
458
|
|
|
393
459
|
# Closed-domain catalogs for offline authoring of complex request bodies.
|
|
394
|
-
# Keep in sync with server registries (field types, workflow node kinds,
|
|
460
|
+
# Keep in sync with server registries (field types, workflow/approval node kinds,
|
|
395
461
|
# permission points, dashboard filter presets, page component kinds).
|
|
396
462
|
# Icons intentionally reference the server file because the full Tabler set is large.
|
|
397
463
|
FIELD_TYPES = (
|
|
@@ -413,6 +479,13 @@ FIELD_TYPES = (
|
|
|
413
479
|
{"type": "url", "constraints": ["maxLength", "placeholder"], "writable": True},
|
|
414
480
|
{"type": "attachment", "constraints": ["maxFileCount", "maxFileSize", "maxTotalSize", "allowedExtensions"], "writable": True},
|
|
415
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},
|
|
416
489
|
)
|
|
417
490
|
|
|
418
491
|
WORKFLOW_NODE_TYPES = (
|
|
@@ -426,6 +499,22 @@ WORKFLOW_NODE_TYPES = (
|
|
|
426
499
|
"note": "terminal node"},
|
|
427
500
|
)
|
|
428
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
|
+
|
|
429
518
|
FUNCTION_PERMISSIONS = (
|
|
430
519
|
"workspace.access", "app.manage", "object.manage", "dashboard.manage",
|
|
431
520
|
"version.manage", "org.manage", "system.manage",
|
|
@@ -439,13 +528,14 @@ DASHBOARD_DATE_RANGE_PRESETS = (
|
|
|
439
528
|
"this_week", "this_month", "this_quarter", "this_year",
|
|
440
529
|
)
|
|
441
530
|
|
|
442
|
-
# Page region column components (
|
|
443
|
-
# Field components omit `kind` (legacy snapshot shape);
|
|
531
|
+
# Page region column components (field + relatedList + approval).
|
|
532
|
+
# Field components omit `kind` (legacy snapshot shape); relatedList/approval set kind.
|
|
444
533
|
PAGE_COMPONENTS = {
|
|
445
534
|
"note": (
|
|
446
|
-
"Region columns hold field
|
|
447
|
-
"relatedList
|
|
448
|
-
"
|
|
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."
|
|
449
539
|
),
|
|
450
540
|
"kinds": (
|
|
451
541
|
{
|
|
@@ -489,12 +579,24 @@ PAGE_COMPONENTS = {
|
|
|
489
579
|
"implicit relationFieldId eq currentRecord condition.",
|
|
490
580
|
},
|
|
491
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
|
+
},
|
|
492
594
|
),
|
|
493
595
|
}
|
|
494
596
|
|
|
495
597
|
RESOURCE_ID_PREFIXES = {
|
|
496
598
|
"app_": "app", "grp_": "app group", "obj_": "object", "fld_": "field",
|
|
497
|
-
"viw_": "view", "wfl_": "workflow", "pag_": "custom page",
|
|
599
|
+
"viw_": "view", "wfl_": "workflow", "apr_": "approval flow", "pag_": "custom page",
|
|
498
600
|
"reg_": "page region", "cmp_": "page component",
|
|
499
601
|
"dsh_": "dashboard", "dcm_": "dashboard component", "rol_": "role",
|
|
500
602
|
"rec_": "record", "att_": "attachment", "emp_": "employee", "dep_": "department",
|
|
@@ -503,6 +605,7 @@ RESOURCE_ID_PREFIXES = {
|
|
|
503
605
|
CATALOGS = {
|
|
504
606
|
"field-types": FIELD_TYPES,
|
|
505
607
|
"workflow-nodes": WORKFLOW_NODE_TYPES,
|
|
608
|
+
"approval-nodes": APPROVAL_NODE_TYPES,
|
|
506
609
|
"permissions": {"function": FUNCTION_PERMISSIONS, "dataScopes": DATA_SCOPES},
|
|
507
610
|
"dashboard-filters": {
|
|
508
611
|
"types": DASHBOARD_FILTER_TYPES,
|
|
@@ -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, page-components, 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,7 +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
|
|
117
|
-
flowbase docs catalogs page-components # page region component kinds (field / relatedList)
|
|
119
|
+
flowbase docs catalogs page-components # page region component kinds (field / relatedList / approval)
|
|
118
120
|
flowbase docs catalogs id-prefixes # resource ID prefix meanings
|
|
119
121
|
flowbase docs catalogs icons # icon guidance + examples
|
|
120
122
|
```
|
|
@@ -139,6 +141,8 @@ Do not compose update bodies from scratch when an existing resource exists — `
|
|
|
139
141
|
flowbase objects create --label Customer --key customer --description "Customer records"
|
|
140
142
|
flowbase fields create obj_xxx --json '{"label":"Name","key":"name","description":"Customer name","type":"text.singleLine","permission":"required"}'
|
|
141
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
|
|
142
146
|
```
|
|
143
147
|
|
|
144
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`.
|
|
@@ -168,22 +172,29 @@ flowbase changes direct-release --message "Fix customer field" --yes
|
|
|
168
172
|
|
|
169
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.
|
|
170
174
|
|
|
171
|
-
### 6.4 Workflows and pages
|
|
175
|
+
### 6.4 Workflows, approvals, and pages
|
|
172
176
|
|
|
173
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`.
|
|
174
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
|
+
|
|
175
181
|
```bash
|
|
176
182
|
flowbase docs catalogs workflow-nodes
|
|
177
183
|
flowbase workflows create obj_xxx --json '{"label":"Set owner","enabled":true,"trigger":{"event":"create","match":"all","conditions":[]},"nodes":[],"edges":[]}'
|
|
178
184
|
flowbase workflows update obj_xxx wfl_xxx --json-file workflow.json
|
|
179
185
|
flowbase workflows executions obj_xxx wfl_xxx --result failure
|
|
180
186
|
|
|
181
|
-
flowbase docs catalogs
|
|
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
|
|
182
193
|
flowbase docs example pages.create --output page.json # create-page layout template
|
|
183
194
|
flowbase pages create --json-file page.json
|
|
184
195
|
flowbase pages set-default pag_xxx --base-revision 4 --yes
|
|
185
196
|
|
|
186
|
-
# Detail pages may embed relatedList
|
|
197
|
+
# Detail pages may embed relatedList and at most one approval component.
|
|
187
198
|
flowbase pages get pag_xxx > page.json # prefer get → edit → update
|
|
188
199
|
# or: flowbase docs example pages.update --output page.json
|
|
189
200
|
flowbase pages update pag_xxx --json-file page.json
|
|
@@ -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
|
|
|
@@ -117,10 +119,14 @@ class FlowBaseCliTest(unittest.TestCase):
|
|
|
117
119
|
payload = json.loads(stdout.getvalue())
|
|
118
120
|
body = json.loads(payload["requestBody"])
|
|
119
121
|
self.assertEqual(body["page"]["type"], "detail")
|
|
120
|
-
|
|
121
|
-
self.assertEqual(
|
|
122
|
-
self.assertIn("relationFieldId",
|
|
123
|
-
self.assertIn("carryIns",
|
|
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
|
+
)
|
|
124
130
|
|
|
125
131
|
def test_docs_catalogs_lists_and_dumps_domains(self):
|
|
126
132
|
stdout = io.StringIO()
|
|
@@ -130,6 +136,7 @@ class FlowBaseCliTest(unittest.TestCase):
|
|
|
130
136
|
names = json.loads(stdout.getvalue())
|
|
131
137
|
self.assertIn("field-types", names)
|
|
132
138
|
self.assertIn("workflow-nodes", names)
|
|
139
|
+
self.assertIn("approval-nodes", names)
|
|
133
140
|
self.assertIn("permissions", names)
|
|
134
141
|
self.assertIn("page-components", names)
|
|
135
142
|
|
|
@@ -139,8 +146,15 @@ class FlowBaseCliTest(unittest.TestCase):
|
|
|
139
146
|
self.assertEqual(code, 0)
|
|
140
147
|
field_types = json.loads(stdout.getvalue())
|
|
141
148
|
type_names = {item["type"] for item in field_types}
|
|
149
|
+
self.assertEqual(len(field_types), 25)
|
|
142
150
|
self.assertIn("select.single", type_names)
|
|
143
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"])
|
|
144
158
|
|
|
145
159
|
stdout = io.StringIO()
|
|
146
160
|
with patch("sys.stdout", stdout):
|
|
@@ -149,6 +163,17 @@ class FlowBaseCliTest(unittest.TestCase):
|
|
|
149
163
|
nodes = json.loads(stdout.getvalue())
|
|
150
164
|
self.assertIn("trigger", {item["type"] for item in nodes})
|
|
151
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
|
+
|
|
152
177
|
stdout = io.StringIO()
|
|
153
178
|
with patch("sys.stdout", stdout):
|
|
154
179
|
code = main(["docs", "catalogs", "page-components"])
|
|
@@ -157,6 +182,12 @@ class FlowBaseCliTest(unittest.TestCase):
|
|
|
157
182
|
kinds = {item.get("kind") for item in page_components["kinds"]}
|
|
158
183
|
self.assertIn(None, kinds)
|
|
159
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"})
|
|
160
191
|
|
|
161
192
|
stdout = io.StringIO()
|
|
162
193
|
with patch("sys.stdout", stdout):
|
|
@@ -234,6 +265,92 @@ class FlowBaseCliTest(unittest.TestCase):
|
|
|
234
265
|
)
|
|
235
266
|
self.assertEqual(request.call_args.kwargs["body"], body)
|
|
236
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
|
+
|
|
237
354
|
def test_view_set_enabled_sends_revision_and_requires_yes(self):
|
|
238
355
|
stderr = io.StringIO()
|
|
239
356
|
with patch("sys.stderr", stderr), patch("sys.stdin.isatty", return_value=False):
|
|
@@ -368,6 +485,83 @@ class FlowBaseCliTest(unittest.TestCase):
|
|
|
368
485
|
},
|
|
369
486
|
)
|
|
370
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
|
+
|
|
371
565
|
def test_group_create_and_app_set_group(self):
|
|
372
566
|
with patch(
|
|
373
567
|
"flowbase_cli.main.OpenApiClient.request",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|