@josephyan/qingflow-cli 1.0.10 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/npm/bin/qingflow.mjs +32 -1
- package/npm/lib/runtime.mjs +43 -2
- package/package.json +1 -1
- package/pyproject.toml +2 -1
- package/skills/qingflow-cli/SKILL.md +440 -0
- package/skills/qingflow-cli/manifest.yaml +10 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_ADMIN_CHEATSHEET.md +94 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_APP_DELIVERY_WORKFLOW.md +485 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_CHARTS_WORKFLOW.md +237 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_MATCH_RULES.md +137 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_PORTAL_WORKFLOW.md +263 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_VIEWS_WORKFLOW.md +304 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_WORKSPACE_ICONS.md +41 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md +139 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_EXPLORATION_REPORT.md +84 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_FIELD_DATA_TYPES.md +129 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_MEMBER_CHEATSHEET.md +195 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_ONE_SHOT_CHEATSHEET.md +159 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_CREATE_WORKFLOW.md +20 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_IMPORT_WORKFLOW.md +176 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md +163 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_SCHEMA_APPLY_FIELD_TYPES_AND_SCENARIOS.md +107 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_TASK_CONTEXT_WORKFLOW.md +151 -0
- package/skills/qingflow-cli/reference/_batch_schema_complex.json +18 -0
- package/skills/qingflow-cli/reference/_batch_schema_scalar.json +17 -0
- package/skills/qingflow-cli/reference/charts_remove.example.json +1 -0
- package/skills/qingflow-cli/reference/charts_reorder.example.json +1 -0
- package/skills/qingflow-cli/reference/charts_upsert_bar.example.json +8 -0
- package/skills/qingflow-cli/reference/charts_upsert_dashboard_starter.example.json +37 -0
- package/skills/qingflow-cli/reference/charts_upsert_minimal.example.json +13 -0
- package/skills/qingflow-cli/reference/portal_sections_all_types.example.json +131 -0
- package/skills/qingflow-cli/reference/portal_sections_five_types.example.json +126 -0
- package/skills/qingflow-cli/reference/portal_sections_standard_workbench.example.json +128 -0
- package/skills/qingflow-cli/reference/schema_add_fields_minimal.example.json +7 -0
- package/skills/qingflow-cli/reference/schema_apply_add_fields_all_types.json +78 -0
- package/skills/qingflow-cli/reference/views_upsert_table_minimal.example.json +7 -0
- package/skills/qingflow-cli/scripts/builder-package-from-app-list.py +140 -0
- package/skills/qingflow-cli/scripts/find-app-by-keyword.py +132 -0
- package/skills/qingflow-cli/scripts/validate_qingflow_output_files.py +87 -0
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/builder_facade/models.py +532 -48
- package/src/qingflow_mcp/builder_facade/service.py +9194 -2384
- package/src/qingflow_mcp/builder_facade/workflow_spec.py +111 -0
- package/src/qingflow_mcp/cli/commands/app.py +3 -16
- package/src/qingflow_mcp/cli/commands/builder.py +354 -56
- package/src/qingflow_mcp/cli/commands/record.py +89 -4
- package/src/qingflow_mcp/cli/formatters.py +53 -15
- package/src/qingflow_mcp/cli/main.py +204 -3
- package/src/qingflow_mcp/public_surface.py +11 -8
- package/src/qingflow_mcp/response_trim.py +185 -46
- package/src/qingflow_mcp/server.py +18 -15
- package/src/qingflow_mcp/server_app_builder.py +108 -30
- package/src/qingflow_mcp/server_app_user.py +20 -21
- package/src/qingflow_mcp/solution/compiler/__init__.py +1 -3
- package/src/qingflow_mcp/solution/compiler/icon_utils.py +294 -0
- package/src/qingflow_mcp/solution/executor.py +3 -133
- package/src/qingflow_mcp/tools/ai_builder_tools.py +2617 -440
- package/src/qingflow_mcp/tools/app_tools.py +53 -8
- package/src/qingflow_mcp/tools/package_tools.py +16 -2
- package/src/qingflow_mcp/tools/record_tools.py +3408 -599
- package/src/qingflow_mcp/tools/resource_read_tools.py +3 -0
- package/src/qingflow_mcp/tools/solution_tools.py +30 -2
- package/src/qingflow_mcp/tools/workflow_tools.py +3 -31
- package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +0 -173
|
@@ -5,6 +5,215 @@ import re
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
DEFAULT_ICON_COLOR = "qing-orange"
|
|
8
|
+
WORKSPACE_ICON_COLORS: tuple[str, ...] = (
|
|
9
|
+
"qing-orange",
|
|
10
|
+
"yellow",
|
|
11
|
+
"green",
|
|
12
|
+
"emerald",
|
|
13
|
+
"blue",
|
|
14
|
+
"azure",
|
|
15
|
+
"indigo",
|
|
16
|
+
"qing-purple",
|
|
17
|
+
"purple",
|
|
18
|
+
"pink",
|
|
19
|
+
"red",
|
|
20
|
+
"orange",
|
|
21
|
+
)
|
|
22
|
+
WORKSPACE_ICON_NAMES: tuple[str, ...] = (
|
|
23
|
+
"user",
|
|
24
|
+
"user-group",
|
|
25
|
+
"user-remove",
|
|
26
|
+
"user-add",
|
|
27
|
+
"user-circle",
|
|
28
|
+
"base-camera",
|
|
29
|
+
"view-grid",
|
|
30
|
+
"inbox",
|
|
31
|
+
"inbox-in",
|
|
32
|
+
"share",
|
|
33
|
+
"sitemap",
|
|
34
|
+
"airplane",
|
|
35
|
+
"template",
|
|
36
|
+
"music-note",
|
|
37
|
+
"movie-play",
|
|
38
|
+
"clock",
|
|
39
|
+
"document",
|
|
40
|
+
"document-search",
|
|
41
|
+
"clipboard-check",
|
|
42
|
+
"document-download",
|
|
43
|
+
"document-text",
|
|
44
|
+
"clipboard-copy",
|
|
45
|
+
"presentation-chart-bar",
|
|
46
|
+
"chart-square-bar",
|
|
47
|
+
"database",
|
|
48
|
+
"server",
|
|
49
|
+
"calendar",
|
|
50
|
+
"mail",
|
|
51
|
+
"annotation",
|
|
52
|
+
"chat",
|
|
53
|
+
"bell",
|
|
54
|
+
"key",
|
|
55
|
+
"shopping-bag",
|
|
56
|
+
"download",
|
|
57
|
+
"eye",
|
|
58
|
+
"eye-off",
|
|
59
|
+
"emoji-happy",
|
|
60
|
+
"emoji-sad",
|
|
61
|
+
"sun",
|
|
62
|
+
"moon",
|
|
63
|
+
"cloud",
|
|
64
|
+
"lightning-bolt",
|
|
65
|
+
"fire",
|
|
66
|
+
"star",
|
|
67
|
+
"sparkles",
|
|
68
|
+
"heart",
|
|
69
|
+
"cake",
|
|
70
|
+
"gift",
|
|
71
|
+
"light-bulb",
|
|
72
|
+
"exclamation",
|
|
73
|
+
"cog",
|
|
74
|
+
"thumb-up",
|
|
75
|
+
"thumb-down",
|
|
76
|
+
"cloud-download",
|
|
77
|
+
"cloud-upload",
|
|
78
|
+
"printer",
|
|
79
|
+
"phone-incoming",
|
|
80
|
+
"phone-missed-call",
|
|
81
|
+
"terminal",
|
|
82
|
+
"search-circle",
|
|
83
|
+
"x-circle",
|
|
84
|
+
"check-circle",
|
|
85
|
+
"exclamation-circle",
|
|
86
|
+
"question-mark-circle",
|
|
87
|
+
"information-circle",
|
|
88
|
+
"academic-cap",
|
|
89
|
+
"briefcase",
|
|
90
|
+
"home",
|
|
91
|
+
"phone",
|
|
92
|
+
"photograph",
|
|
93
|
+
"puzzle",
|
|
94
|
+
"color-swatch",
|
|
95
|
+
"lock-open",
|
|
96
|
+
"lock-closed",
|
|
97
|
+
"shield-check",
|
|
98
|
+
"shield-exclamation",
|
|
99
|
+
"currency-dollar",
|
|
100
|
+
"currency-yen",
|
|
101
|
+
"globe",
|
|
102
|
+
"at-symbol",
|
|
103
|
+
"slack",
|
|
104
|
+
"microphone",
|
|
105
|
+
"speakerphone",
|
|
106
|
+
"trash",
|
|
107
|
+
"book-open",
|
|
108
|
+
"truck",
|
|
109
|
+
"filter",
|
|
110
|
+
"essetional-filter-search",
|
|
111
|
+
"essetional-filter-tick",
|
|
112
|
+
"table",
|
|
113
|
+
"calculator",
|
|
114
|
+
"location-radar",
|
|
115
|
+
"essetional-weight",
|
|
116
|
+
"school-award",
|
|
117
|
+
"comp-cloud-connection",
|
|
118
|
+
"comp-cloud-remove",
|
|
119
|
+
"comp-cpu-charge",
|
|
120
|
+
"comp-cpu-setting",
|
|
121
|
+
"comp-cpu",
|
|
122
|
+
"comp-devices",
|
|
123
|
+
"comp-driver-2",
|
|
124
|
+
"comp-driver-refresh",
|
|
125
|
+
"location-global",
|
|
126
|
+
"location-location",
|
|
127
|
+
"location-map",
|
|
128
|
+
"location-gps",
|
|
129
|
+
"essetional-ranking",
|
|
130
|
+
"chart-bar",
|
|
131
|
+
"business-graph",
|
|
132
|
+
"business-status-up",
|
|
133
|
+
"business-trend-down",
|
|
134
|
+
"business-trend-up",
|
|
135
|
+
"business-presention-chart",
|
|
136
|
+
"business-favorite-chart",
|
|
137
|
+
"business-health",
|
|
138
|
+
"receipt-refund",
|
|
139
|
+
"receipt-tax",
|
|
140
|
+
"money-receipt-2-1",
|
|
141
|
+
"money-transaction-minus",
|
|
142
|
+
"action-hourglass-full",
|
|
143
|
+
"action-work",
|
|
144
|
+
"bug-f",
|
|
145
|
+
"essetional-pet",
|
|
146
|
+
"files-folder",
|
|
147
|
+
"badge-check",
|
|
148
|
+
"money-wallet-1",
|
|
149
|
+
"money-ticket",
|
|
150
|
+
"money-money",
|
|
151
|
+
"money-tag",
|
|
152
|
+
"money-wallet-2",
|
|
153
|
+
"business-personalcard",
|
|
154
|
+
"car-airplane",
|
|
155
|
+
"car-bus",
|
|
156
|
+
"car-car",
|
|
157
|
+
"car-driving",
|
|
158
|
+
"car-gas-station",
|
|
159
|
+
"car-smart-car",
|
|
160
|
+
"car-ship",
|
|
161
|
+
"location-map-1",
|
|
162
|
+
"location-route-square",
|
|
163
|
+
"cone",
|
|
164
|
+
"design-brush-4",
|
|
165
|
+
"paint-roll",
|
|
166
|
+
"wrench-f",
|
|
167
|
+
"essetional-reserve",
|
|
168
|
+
"essetional-broom",
|
|
169
|
+
"design-brush-2",
|
|
170
|
+
"essetional-judge",
|
|
171
|
+
"design-bucket",
|
|
172
|
+
"palette",
|
|
173
|
+
"comp-electricity",
|
|
174
|
+
"vial",
|
|
175
|
+
"beaker",
|
|
176
|
+
"leaf-f",
|
|
177
|
+
"cursor-click",
|
|
178
|
+
"solid-search-alt-2",
|
|
179
|
+
"md-library",
|
|
180
|
+
"building-3",
|
|
181
|
+
"office-building",
|
|
182
|
+
"building-hospital",
|
|
183
|
+
"school",
|
|
184
|
+
"store",
|
|
185
|
+
"video-camera-vintage-f",
|
|
186
|
+
"comp-monitor",
|
|
187
|
+
"delivery-truck",
|
|
188
|
+
"delivery-box-1",
|
|
189
|
+
"delivery-box-add",
|
|
190
|
+
"delivery-box-remove",
|
|
191
|
+
"settings-setting-3",
|
|
192
|
+
"document-duplicate",
|
|
193
|
+
"essetional-flag-2",
|
|
194
|
+
"flag",
|
|
195
|
+
"icon-currency-dollar",
|
|
196
|
+
"clipboard-list",
|
|
197
|
+
"save-as",
|
|
198
|
+
"wifi",
|
|
199
|
+
"status-online",
|
|
200
|
+
"scissors",
|
|
201
|
+
"globe-alt",
|
|
202
|
+
"ban",
|
|
203
|
+
"finger-print",
|
|
204
|
+
"qrcode",
|
|
205
|
+
"paper-clip",
|
|
206
|
+
"translate",
|
|
207
|
+
"cube-transparent",
|
|
208
|
+
"variable",
|
|
209
|
+
"switch-vertical",
|
|
210
|
+
"sports-baseball",
|
|
211
|
+
"sports-basketball",
|
|
212
|
+
"sports-soccer",
|
|
213
|
+
"sports-football",
|
|
214
|
+
"sports-volleyball",
|
|
215
|
+
)
|
|
216
|
+
GENERIC_WORKSPACE_ICON_NAMES: tuple[str, ...] = ("template",)
|
|
8
217
|
DEFAULT_ICON_STYLE_POOL: tuple[tuple[str, str], ...] = (
|
|
9
218
|
("briefcase", "qing-orange"),
|
|
10
219
|
("calendar", "emerald"),
|
|
@@ -115,6 +324,91 @@ def parse_workspace_icon(value: str | None) -> tuple[str | None, str | None, str
|
|
|
115
324
|
return normalize_workspace_icon_name(stripped), None, None
|
|
116
325
|
|
|
117
326
|
|
|
327
|
+
def workspace_icon_config(value: str | None) -> dict[str, str | None]:
|
|
328
|
+
icon_name, icon_color, icon_text = parse_workspace_icon(value)
|
|
329
|
+
raw = str(value).strip() if value not in (None, "") else None
|
|
330
|
+
return {
|
|
331
|
+
"icon_name": icon_name,
|
|
332
|
+
"icon_color": icon_color,
|
|
333
|
+
"icon_text": icon_text,
|
|
334
|
+
"raw": raw,
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def workspace_icon_catalog_payload() -> dict[str, object]:
|
|
339
|
+
return {
|
|
340
|
+
"icon_names": list(WORKSPACE_ICON_NAMES),
|
|
341
|
+
"icon_colors": list(WORKSPACE_ICON_COLORS),
|
|
342
|
+
"generic_icon_names": list(GENERIC_WORKSPACE_ICON_NAMES),
|
|
343
|
+
"notes": [
|
|
344
|
+
"Use explicit icon + color for app/package/portal creation.",
|
|
345
|
+
"Do not use template for newly created workspace resources.",
|
|
346
|
+
"The CLI validates candidates only; it does not infer an icon from business names.",
|
|
347
|
+
],
|
|
348
|
+
"common_examples": {
|
|
349
|
+
"employee": ["business-personalcard", "user-group", "user"],
|
|
350
|
+
"task": ["clipboard-check", "action-work"],
|
|
351
|
+
"worklog": ["clock", "action-hourglass-full"],
|
|
352
|
+
"order": ["delivery-box-1", "shopping-bag"],
|
|
353
|
+
"payment": ["money-receipt-2-1", "money-wallet-1"],
|
|
354
|
+
"opportunity": ["business-graph", "business-trend-up"],
|
|
355
|
+
"dashboard": ["view-grid", "chart-square-bar", "presentation-chart-bar"],
|
|
356
|
+
},
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def validate_workspace_icon_choice(
|
|
361
|
+
*,
|
|
362
|
+
icon: str | None,
|
|
363
|
+
color: str | None,
|
|
364
|
+
require_explicit: bool,
|
|
365
|
+
disallow_generic: bool,
|
|
366
|
+
) -> tuple[bool, str | None, str | None, dict[str, object]]:
|
|
367
|
+
normalized_icon = _normalize_workspace_icon_candidate(icon)
|
|
368
|
+
normalized_color = str(color or "").strip() or None
|
|
369
|
+
details: dict[str, object] = {
|
|
370
|
+
"icon": icon,
|
|
371
|
+
"normalized_icon": normalized_icon,
|
|
372
|
+
"color": color,
|
|
373
|
+
"icon_catalog_command": "qingflow --json builder icon catalog",
|
|
374
|
+
}
|
|
375
|
+
if require_explicit and not normalized_icon:
|
|
376
|
+
return False, "WORKSPACE_ICON_REQUIRED", "icon is required when creating a workspace resource", details
|
|
377
|
+
if require_explicit and not normalized_color:
|
|
378
|
+
return False, "WORKSPACE_ICON_COLOR_REQUIRED", "color is required when creating a workspace resource", details
|
|
379
|
+
if normalized_icon and normalized_icon not in WORKSPACE_ICON_NAMES:
|
|
380
|
+
details["allowed_icon_names"] = list(WORKSPACE_ICON_NAMES)
|
|
381
|
+
return False, "WORKSPACE_ICON_NOT_FOUND", "icon is not in the workspace icon catalog", details
|
|
382
|
+
if normalized_color and normalized_color not in WORKSPACE_ICON_COLORS:
|
|
383
|
+
details["allowed_icon_colors"] = list(WORKSPACE_ICON_COLORS)
|
|
384
|
+
return False, "WORKSPACE_ICON_COLOR_NOT_FOUND", "color is not in the workspace icon color catalog", details
|
|
385
|
+
if disallow_generic and normalized_icon in GENERIC_WORKSPACE_ICON_NAMES:
|
|
386
|
+
details["generic_icon_names"] = list(GENERIC_WORKSPACE_ICON_NAMES)
|
|
387
|
+
return False, "GENERIC_WORKSPACE_ICON_NOT_ALLOWED", "template is a generic icon and is not allowed for new workspace resources", details
|
|
388
|
+
return True, None, None, details
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def _normalize_workspace_icon_candidate(icon: str | None) -> str | None:
|
|
392
|
+
if not icon:
|
|
393
|
+
return None
|
|
394
|
+
raw = str(icon).strip()
|
|
395
|
+
if not raw:
|
|
396
|
+
return None
|
|
397
|
+
if _looks_like_icon_json(raw):
|
|
398
|
+
try:
|
|
399
|
+
payload = json.loads(raw)
|
|
400
|
+
except Exception:
|
|
401
|
+
return None
|
|
402
|
+
return _normalize_workspace_icon_candidate(payload.get("iconName"))
|
|
403
|
+
normalized = raw.lower()
|
|
404
|
+
if normalized in WORKSPACE_ICON_NAMES:
|
|
405
|
+
return normalized
|
|
406
|
+
legacy = LEGACY_EX_ICON_MAP.get(normalized)
|
|
407
|
+
if legacy in WORKSPACE_ICON_NAMES:
|
|
408
|
+
return legacy
|
|
409
|
+
return normalized
|
|
410
|
+
|
|
411
|
+
|
|
118
412
|
def encode_workspace_icon_with_defaults(
|
|
119
413
|
*,
|
|
120
414
|
icon: str | None,
|
|
@@ -14,7 +14,6 @@ from ..tools.qingbi_report_tools import QingbiReportTools
|
|
|
14
14
|
from ..tools.record_tools import RecordTools
|
|
15
15
|
from ..tools.role_tools import RoleTools
|
|
16
16
|
from ..tools.view_tools import ViewTools
|
|
17
|
-
from ..tools.workflow_tools import WorkflowTools
|
|
18
17
|
from ..tools.workspace_tools import WorkspaceTools
|
|
19
18
|
from .compiler import CompiledEntity, CompiledRole, CompiledSolution
|
|
20
19
|
from .compiler.form_compiler import QUESTION_TYPE_MAP
|
|
@@ -36,7 +35,6 @@ class SolutionExecutor:
|
|
|
36
35
|
role_tools: RoleTools,
|
|
37
36
|
app_tools: AppTools,
|
|
38
37
|
record_tools: RecordTools,
|
|
39
|
-
workflow_tools: WorkflowTools,
|
|
40
38
|
view_tools: ViewTools,
|
|
41
39
|
chart_tools: QingbiReportTools,
|
|
42
40
|
portal_tools: PortalTools,
|
|
@@ -47,7 +45,6 @@ class SolutionExecutor:
|
|
|
47
45
|
self.role_tools = role_tools
|
|
48
46
|
self.app_tools = app_tools
|
|
49
47
|
self.record_tools = record_tools
|
|
50
|
-
self.workflow_tools = workflow_tools
|
|
51
48
|
self.view_tools = view_tools
|
|
52
49
|
self.chart_tools = chart_tools
|
|
53
50
|
self.portal_tools = portal_tools
|
|
@@ -419,123 +416,10 @@ class SolutionExecutor:
|
|
|
419
416
|
def _build_workflow(self, profile: str, entity: CompiledEntity, store: RunArtifactStore) -> None:
|
|
420
417
|
if entity.workflow_plan is None:
|
|
421
418
|
return
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
entity.entity_id,
|
|
426
|
-
store,
|
|
427
|
-
app_key=app_key,
|
|
428
|
-
force_new=True,
|
|
429
|
-
)
|
|
430
|
-
node_artifacts = store.get_artifact("apps", entity.entity_id, {}).get("workflow_nodes", {})
|
|
431
|
-
existing_nodes = self.workflow_tools.workflow_list_nodes(profile=profile, app_key=app_key).get("result") or {}
|
|
432
|
-
current_nodes = _coerce_workflow_nodes(existing_nodes)
|
|
433
|
-
existing_nodes_by_name = {
|
|
434
|
-
node.get("auditNodeName"): int(node_id)
|
|
435
|
-
for node_id, node in current_nodes.items()
|
|
436
|
-
if isinstance(node, dict) and node.get("auditNodeName")
|
|
437
|
-
}
|
|
438
|
-
applicant_node_id = next(
|
|
439
|
-
(
|
|
440
|
-
int(node_id)
|
|
441
|
-
for node_id, node in current_nodes.items()
|
|
442
|
-
if isinstance(node, dict) and node.get("type") == 0 and node.get("dealType") == 3
|
|
443
|
-
),
|
|
444
|
-
None,
|
|
419
|
+
raise RuntimeError(
|
|
420
|
+
"Legacy auditNode workflow execution was removed. "
|
|
421
|
+
"Pass {app_key, spec} to solution_build_flow or use qingflow builder flow apply."
|
|
445
422
|
)
|
|
446
|
-
if applicant_node_id is not None:
|
|
447
|
-
node_artifacts.setdefault("__applicant__", applicant_node_id)
|
|
448
|
-
|
|
449
|
-
desired_global_settings = deepcopy(entity.workflow_plan["global_settings"])
|
|
450
|
-
explicit_global_settings = _has_explicit_workflow_global_settings(desired_global_settings)
|
|
451
|
-
current_global_settings: dict[str, Any] = {}
|
|
452
|
-
if explicit_global_settings:
|
|
453
|
-
current_global_settings = self.workflow_tools.workflow_get_global_settings(profile=profile, app_key=app_key).get("result") or {}
|
|
454
|
-
else:
|
|
455
|
-
try:
|
|
456
|
-
current_global_settings = self.workflow_tools.workflow_get_global_settings(profile=profile, app_key=app_key).get("result") or {}
|
|
457
|
-
except (QingflowApiError, RuntimeError) as error:
|
|
458
|
-
api_error = QingflowApiError(**_coerce_nested_error_payload(error))
|
|
459
|
-
if api_error.http_status != 404:
|
|
460
|
-
raise
|
|
461
|
-
current_global_settings = {}
|
|
462
|
-
if explicit_global_settings:
|
|
463
|
-
global_settings = deepcopy(current_global_settings if isinstance(current_global_settings, dict) else {})
|
|
464
|
-
global_settings.update(desired_global_settings)
|
|
465
|
-
global_settings["editVersionNo"] = workflow_edit_version_no or global_settings.get("editVersionNo") or 1
|
|
466
|
-
self.workflow_tools.workflow_update_global_settings(profile=profile, app_key=app_key, payload=global_settings)
|
|
467
|
-
for action in entity.workflow_plan["actions"]:
|
|
468
|
-
if action["action"] == "create_sub_branch" and node_artifacts.get(action["node_id"]) is not None:
|
|
469
|
-
continue
|
|
470
|
-
if action["action"] == "add_node":
|
|
471
|
-
if action.get("node_type") == "branch":
|
|
472
|
-
existing_branch_id = node_artifacts.get(action["node_id"])
|
|
473
|
-
if existing_branch_id is not None and not _workflow_node_is_branch(current_nodes, existing_branch_id):
|
|
474
|
-
existing_branch_id = None
|
|
475
|
-
if existing_branch_id is not None:
|
|
476
|
-
for branch_index, lane_id in enumerate(_find_branch_lane_ids(current_nodes, existing_branch_id), start=1):
|
|
477
|
-
node_artifacts[_branch_lane_ref(action["node_id"], branch_index)] = lane_id
|
|
478
|
-
apps_artifact = store.get_artifact("apps", entity.entity_id, {})
|
|
479
|
-
apps_artifact["workflow_nodes"] = node_artifacts
|
|
480
|
-
store.set_artifact("apps", entity.entity_id, apps_artifact)
|
|
481
|
-
continue
|
|
482
|
-
existing_node_id = node_artifacts.get(action["node_id"]) or existing_nodes_by_name.get(action.get("node_name"))
|
|
483
|
-
if existing_node_id is not None:
|
|
484
|
-
node_artifacts[action["node_id"]] = existing_node_id
|
|
485
|
-
apps_artifact = store.get_artifact("apps", entity.entity_id, {})
|
|
486
|
-
apps_artifact["workflow_nodes"] = node_artifacts
|
|
487
|
-
store.set_artifact("apps", entity.entity_id, apps_artifact)
|
|
488
|
-
continue
|
|
489
|
-
before_node_ids = set(current_nodes)
|
|
490
|
-
payload = self._resolve_workflow_payload(action["payload"], node_artifacts)
|
|
491
|
-
if workflow_edit_version_no is not None:
|
|
492
|
-
payload["editVersionNo"] = int(workflow_edit_version_no)
|
|
493
|
-
if action["action"] == "create_sub_branch":
|
|
494
|
-
result = self.workflow_tools.workflow_create_sub_branch(profile=profile, app_key=app_key, payload=payload)
|
|
495
|
-
elif action["action"] == "update_node":
|
|
496
|
-
target_node_id = node_artifacts.get(action["node_id"])
|
|
497
|
-
if target_node_id is None:
|
|
498
|
-
raise RuntimeError(f"workflow lane '{action['node_id']}' could not be resolved before update")
|
|
499
|
-
result = self.workflow_tools.workflow_update_node(
|
|
500
|
-
profile=profile,
|
|
501
|
-
app_key=app_key,
|
|
502
|
-
audit_node_id=target_node_id,
|
|
503
|
-
payload=payload,
|
|
504
|
-
)
|
|
505
|
-
else:
|
|
506
|
-
result = self.workflow_tools.workflow_add_node(profile=profile, app_key=app_key, payload=payload)
|
|
507
|
-
expected_type = 1 if action.get("node_type") == "branch" else None
|
|
508
|
-
audit_node_id = _extract_workflow_node_id(result.get("result"), expected_type=expected_type)
|
|
509
|
-
if action.get("node_type") == "branch" or action["action"] == "create_sub_branch":
|
|
510
|
-
current_nodes = _coerce_workflow_nodes(
|
|
511
|
-
self.workflow_tools.workflow_list_nodes(profile=profile, app_key=app_key).get("result") or {}
|
|
512
|
-
)
|
|
513
|
-
if audit_node_id is not None:
|
|
514
|
-
node_artifacts[action["node_id"]] = audit_node_id
|
|
515
|
-
if action.get("node_type") == "branch":
|
|
516
|
-
branch_node_id = node_artifacts.get(action["node_id"]) or _find_created_branch_node_id(
|
|
517
|
-
current_nodes,
|
|
518
|
-
before_node_ids=before_node_ids,
|
|
519
|
-
prev_id=payload.get("prevId"),
|
|
520
|
-
)
|
|
521
|
-
if branch_node_id is not None:
|
|
522
|
-
node_artifacts[action["node_id"]] = branch_node_id
|
|
523
|
-
for branch_index, lane_id in enumerate(_find_branch_lane_ids(current_nodes, branch_node_id), start=1):
|
|
524
|
-
node_artifacts[_branch_lane_ref(action["node_id"], branch_index)] = lane_id
|
|
525
|
-
if action["action"] == "create_sub_branch" and node_artifacts.get(action["node_id"]) is None:
|
|
526
|
-
created_lane_id = audit_node_id or _find_created_sub_branch_lane_id(
|
|
527
|
-
current_nodes,
|
|
528
|
-
before_node_ids=before_node_ids,
|
|
529
|
-
branch_node_id=payload.get("auditNodeId"),
|
|
530
|
-
)
|
|
531
|
-
if created_lane_id is not None:
|
|
532
|
-
node_artifacts[action["node_id"]] = created_lane_id
|
|
533
|
-
apps_artifact = store.get_artifact("apps", entity.entity_id, {})
|
|
534
|
-
apps_artifact["workflow_nodes"] = node_artifacts
|
|
535
|
-
store.set_artifact("apps", entity.entity_id, apps_artifact)
|
|
536
|
-
apps_artifact = store.get_artifact("apps", entity.entity_id, {})
|
|
537
|
-
apps_artifact["workflow_nodes"] = node_artifacts
|
|
538
|
-
store.set_artifact("apps", entity.entity_id, apps_artifact)
|
|
539
423
|
|
|
540
424
|
def _build_views(self, profile: str, entity: CompiledEntity, store: RunArtifactStore) -> None:
|
|
541
425
|
app_key = self._get_app_key(store, entity.entity_id)
|
|
@@ -2097,20 +1981,6 @@ def _find_created_sub_branch_lane_id(
|
|
|
2097
1981
|
return candidates[0] if candidates else None
|
|
2098
1982
|
|
|
2099
1983
|
|
|
2100
|
-
def _has_explicit_workflow_global_settings(global_settings: dict[str, Any] | None) -> bool:
|
|
2101
|
-
if not isinstance(global_settings, dict):
|
|
2102
|
-
return False
|
|
2103
|
-
for key, value in global_settings.items():
|
|
2104
|
-
if key == "editVersionNo":
|
|
2105
|
-
continue
|
|
2106
|
-
if value is None:
|
|
2107
|
-
continue
|
|
2108
|
-
if isinstance(value, (list, dict)) and not value:
|
|
2109
|
-
continue
|
|
2110
|
-
return True
|
|
2111
|
-
return False
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
1984
|
def _is_navigation_plugin_unavailable(error: QingflowApiError) -> bool:
|
|
2115
1985
|
try:
|
|
2116
1986
|
backend_code = int(error.backend_code)
|