@qingflow-tech/qingflow-cli 1.1.2 → 1.1.34
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 +2 -2
- package/docs/local-agent-install.md +10 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/skills/qingflow-cli/SKILL.md +61 -47
- package/skills/qingflow-cli/manifest.yaml +1 -1
- package/skills/qingflow-cli/reference/00-INDEX.md +35 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_APP_DELIVERY_WORKFLOW.md +21 -477
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_VIEWS_WORKFLOW.md +44 -290
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_SCHEMA_APPLY_FIELD_TYPES_AND_SCENARIOS.md +11 -102
- package/skills/qingflow-cli/reference/builder/10-build-single-app.md +57 -0
- package/skills/qingflow-cli/reference/builder/20-build-complete-system.md +84 -0
- package/skills/qingflow-cli/reference/builder/30-schema-fields.md +317 -0
- package/skills/qingflow-cli/reference/builder/40-layout.md +70 -0
- package/skills/qingflow-cli/reference/builder/50-views.md +540 -0
- package/skills/qingflow-cli/reference/builder/60-charts.md +328 -0
- package/skills/qingflow-cli/reference/builder/70-portal.md +372 -0
- package/skills/qingflow-cli/reference/builder/80-buttons-associated-resources.md +148 -0
- package/skills/qingflow-cli/reference/builder/90-workflow.md +35 -0
- package/skills/qingflow-cli/reference/builder/99-publish-verify.md +64 -0
- package/skills/qingflow-cli/reference/builder/README.md +62 -0
- package/skills/qingflow-cli/reference/builder/code-integrations/README.md +130 -0
- package/skills/qingflow-cli/reference/builder/code-integrations/code-block.md +66 -0
- package/skills/qingflow-cli/reference/builder/code-integrations/q-linker.md +77 -0
- package/skills/qingflow-cli/reference/builder/reference/app-delivery-sop.md +508 -0
- package/skills/qingflow-cli/reference/builder/reference/match-rules.md +229 -0
- package/skills/qingflow-cli/reference/builder/reference/workspace-icons.md +89 -0
- package/skills/qingflow-cli/reference/builder/workflow/01-overview.md +48 -0
- package/skills/qingflow-cli/reference/builder/workflow/02-update-mode.md +63 -0
- package/skills/qingflow-cli/reference/builder/workflow/03-flow-patterns.md +59 -0
- package/skills/qingflow-cli/reference/builder/workflow/04-stage1-business-modeling.md +131 -0
- package/skills/qingflow-cli/reference/builder/workflow/05-stage2-members-roles.md +29 -0
- package/skills/qingflow-cli/reference/builder/workflow/06-stage3-build-spec.md +211 -0
- package/skills/qingflow-cli/reference/builder/workflow/07-stage4-validate-spec.md +36 -0
- package/skills/qingflow-cli/reference/builder/workflow/08-stage5-apply-verify.md +66 -0
- package/skills/qingflow-cli/reference/builder/workflow/09-stage6-summary.md +88 -0
- package/skills/qingflow-cli/reference/builder/workflow/10-node-config-reference.md +93 -0
- package/skills/qingflow-cli/reference/builder/workflow/11-troubleshooting.md +18 -0
- package/skills/qingflow-cli/reference/builder/workflow/README.md +88 -0
- package/skills/qingflow-cli/reference/builder/workflow/workflow-schema.json +1754 -0
- package/skills/qingflow-cli/reference/charts_upsert_dashboard_starter.example.json +81 -0
- package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_ADMIN_CHEATSHEET.md +94 -0
- package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md +139 -0
- package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_EXPLORATION_REPORT.md +84 -0
- package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_FIELD_DATA_TYPES.md +129 -0
- package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_MEMBER_CHEATSHEET.md +195 -0
- package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_ONE_SHOT_CHEATSHEET.md +159 -0
- package/skills/qingflow-cli/reference/examples/README.md +17 -0
- package/skills/qingflow-cli/reference/examples/associated/associated_resources_semantic_full.example.json +50 -0
- package/skills/qingflow-cli/reference/examples/buttons/custom_buttons_advanced.example.json +56 -0
- package/skills/qingflow-cli/reference/examples/charts/charts_remove.example.json +1 -0
- package/skills/qingflow-cli/reference/examples/charts/charts_reorder.example.json +1 -0
- package/skills/qingflow-cli/reference/examples/charts/charts_upsert_bar.example.json +8 -0
- package/skills/qingflow-cli/reference/examples/charts/charts_upsert_dashboard_starter.example.json +118 -0
- package/skills/qingflow-cli/reference/examples/charts/charts_upsert_minimal.example.json +13 -0
- package/skills/qingflow-cli/reference/examples/layout/layout_sections_full.example.json +22 -0
- package/skills/qingflow-cli/reference/examples/portal/portal_sections_all_types.example.json +145 -0
- package/skills/qingflow-cli/reference/examples/portal/portal_sections_five_types.example.json +126 -0
- package/skills/qingflow-cli/reference/examples/portal/portal_sections_standard_workbench.example.json +178 -0
- package/skills/qingflow-cli/reference/examples/record/record_insert_all_field_types.example.json +35 -0
- package/skills/qingflow-cli/reference/examples/schema/_batch_schema_complex.json +18 -0
- package/skills/qingflow-cli/reference/examples/schema/_batch_schema_scalar.json +17 -0
- package/skills/qingflow-cli/reference/examples/schema/schema_add_fields_minimal.example.json +7 -0
- package/skills/qingflow-cli/reference/examples/schema/schema_apply_add_fields_all_types.json +78 -0
- package/skills/qingflow-cli/reference/examples/schema/schema_multi_app_form_relation_layout.example.json +166 -0
- package/skills/qingflow-cli/reference/examples/schema/schema_single_app_form_standard.example.json +95 -0
- package/skills/qingflow-cli/reference/examples/views/views_batch_full.example.json +159 -0
- package/skills/qingflow-cli/reference/examples/views/views_upsert_table_minimal.example.json +42 -0
- package/skills/qingflow-cli/reference/portal_sections_all_types.example.json +18 -4
- package/skills/qingflow-cli/reference/portal_sections_five_types.example.json +3 -3
- package/skills/qingflow-cli/reference/portal_sections_standard_workbench.example.json +57 -7
- package/skills/qingflow-cli/reference/record/QINGFLOW_CLI_RECORD_CREATE_WORKFLOW.md +22 -0
- package/skills/qingflow-cli/reference/record/QINGFLOW_CLI_RECORD_DELETE_WORKFLOW.md +31 -0
- package/skills/qingflow-cli/reference/record/QINGFLOW_CLI_RECORD_IMPORT_WORKFLOW.md +176 -0
- package/skills/qingflow-cli/reference/record/QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md +167 -0
- package/skills/qingflow-cli/reference/record/analysis/README.md +130 -0
- package/skills/qingflow-cli/reference/record/analysis/analysis-gotchas.md +91 -0
- package/skills/qingflow-cli/reference/record/analysis/analysis-patterns.md +112 -0
- package/skills/qingflow-cli/reference/record/analysis/business-context.md +74 -0
- package/skills/qingflow-cli/reference/record/analysis/confidence-reporting.md +69 -0
- package/skills/qingflow-cli/reference/record/analysis/data-access-playbook.md +106 -0
- package/skills/qingflow-cli/reference/record/analysis/pandas-recipes.md +172 -0
- package/skills/qingflow-cli/reference/record/analysis/report-format.md +76 -0
- package/skills/qingflow-cli/reference/record/insert/README.md +125 -0
- package/skills/qingflow-cli/reference/schema_apply_add_fields_all_types.json +1 -1
- package/skills/qingflow-cli/reference/task/QINGFLOW_CLI_TASK_CONTEXT_WORKFLOW.md +153 -0
- package/skills/qingflow-cli/reference/task/ops/README.md +134 -0
- package/skills/qingflow-cli/reference/task/ops/environments.md +43 -0
- package/skills/qingflow-cli/reference/task/ops/workflow-usage.md +29 -0
- package/skills/qingflow-cli/reference/views_upsert_table_minimal.example.json +42 -7
- package/skills/qingflow-cli/scripts/validate_system_build_summary.py +124 -0
- package/skills/qingflow-cli/scripts/workflow/diff_flow_spec.py +275 -0
- package/skills/qingflow-cli/scripts/workflow/validate_flow_spec.py +627 -0
- package/src/qingflow_mcp/builder_facade/models.py +413 -119
- package/src/qingflow_mcp/builder_facade/service.py +4238 -666
- package/src/qingflow_mcp/cli/commands/builder.py +232 -75
- package/src/qingflow_mcp/cli/main.py +34 -0
- package/src/qingflow_mcp/public_surface.py +10 -0
- package/src/qingflow_mcp/server_app_builder.py +62 -34
- package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +173 -0
- package/src/qingflow_mcp/solution/executor.py +2 -0
- package/src/qingflow_mcp/tools/ai_builder_tools.py +2649 -457
- package/src/qingflow_mcp/tools/record_tools.py +23 -11
|
@@ -203,6 +203,10 @@ class LayoutPreset(str, Enum):
|
|
|
203
203
|
single_section = "single_section"
|
|
204
204
|
|
|
205
205
|
|
|
206
|
+
class FlowPreset(str, Enum):
|
|
207
|
+
basic_approval = "basic_approval"
|
|
208
|
+
basic_fill_then_approve = "basic_fill_then_approve"
|
|
209
|
+
|
|
206
210
|
|
|
207
211
|
class ViewsPreset(str, Enum):
|
|
208
212
|
default_table = "default_table"
|
|
@@ -243,6 +247,116 @@ class ViewFilterOperator(str, Enum):
|
|
|
243
247
|
not_empty = "not_empty"
|
|
244
248
|
|
|
245
249
|
|
|
250
|
+
_PUBLIC_CONDITION_OPERATOR_ALIASES: dict[str, str] = {
|
|
251
|
+
"eq": "eq",
|
|
252
|
+
"equal": "eq",
|
|
253
|
+
"equals": "eq",
|
|
254
|
+
"=": "eq",
|
|
255
|
+
"==": "eq",
|
|
256
|
+
"neq": "neq",
|
|
257
|
+
"ne": "neq",
|
|
258
|
+
"not_eq": "neq",
|
|
259
|
+
"not_equal": "neq",
|
|
260
|
+
"not_equals": "neq",
|
|
261
|
+
"noteq": "neq",
|
|
262
|
+
"notequal": "neq",
|
|
263
|
+
"notequals": "neq",
|
|
264
|
+
"!=": "neq",
|
|
265
|
+
"in": "in",
|
|
266
|
+
"any": "in",
|
|
267
|
+
"any_of": "in",
|
|
268
|
+
"anyof": "in",
|
|
269
|
+
"one_of": "in",
|
|
270
|
+
"oneof": "in",
|
|
271
|
+
"between_any": "in",
|
|
272
|
+
"betweenany": "in",
|
|
273
|
+
"equal_any": "in",
|
|
274
|
+
"equalany": "in",
|
|
275
|
+
"any_match": "in",
|
|
276
|
+
"anymatch": "in",
|
|
277
|
+
"contains": "contains",
|
|
278
|
+
"contain": "contains",
|
|
279
|
+
"include": "contains",
|
|
280
|
+
"includes": "contains",
|
|
281
|
+
"like": "contains",
|
|
282
|
+
"fuzzy": "contains",
|
|
283
|
+
"fuzzy_match": "contains",
|
|
284
|
+
"fuzzymatch": "contains",
|
|
285
|
+
"gte": "gte",
|
|
286
|
+
"ge": "gte",
|
|
287
|
+
">=": "gte",
|
|
288
|
+
"greater_or_equal": "gte",
|
|
289
|
+
"greaterorequal": "gte",
|
|
290
|
+
"greater_than_or_equal": "gte",
|
|
291
|
+
"greaterthanorequal": "gte",
|
|
292
|
+
"lte": "lte",
|
|
293
|
+
"le": "lte",
|
|
294
|
+
"<=": "lte",
|
|
295
|
+
"less_or_equal": "lte",
|
|
296
|
+
"lessorequal": "lte",
|
|
297
|
+
"less_than_or_equal": "lte",
|
|
298
|
+
"lessthanorequal": "lte",
|
|
299
|
+
"is_empty": "is_empty",
|
|
300
|
+
"isempty": "is_empty",
|
|
301
|
+
"empty": "is_empty",
|
|
302
|
+
"blank": "is_empty",
|
|
303
|
+
"is_blank": "is_empty",
|
|
304
|
+
"isblank": "is_empty",
|
|
305
|
+
"null": "is_empty",
|
|
306
|
+
"is_null": "is_empty",
|
|
307
|
+
"isnull": "is_empty",
|
|
308
|
+
"not_empty": "not_empty",
|
|
309
|
+
"notempty": "not_empty",
|
|
310
|
+
"is_not_empty": "not_empty",
|
|
311
|
+
"isnotempty": "not_empty",
|
|
312
|
+
"not_blank": "not_empty",
|
|
313
|
+
"notblank": "not_empty",
|
|
314
|
+
"not_null": "not_empty",
|
|
315
|
+
"notnull": "not_empty",
|
|
316
|
+
"is_not_null": "not_empty",
|
|
317
|
+
"isnotnull": "not_empty",
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def normalize_public_condition_operator(raw_operator: Any, *, extra_aliases: dict[str, str] | None = None) -> Any:
|
|
322
|
+
if not isinstance(raw_operator, str):
|
|
323
|
+
return raw_operator
|
|
324
|
+
text = raw_operator.strip().lower()
|
|
325
|
+
if not text:
|
|
326
|
+
return text
|
|
327
|
+
lookup = dict(_PUBLIC_CONDITION_OPERATOR_ALIASES)
|
|
328
|
+
if extra_aliases:
|
|
329
|
+
lookup.update(extra_aliases)
|
|
330
|
+
candidates = [
|
|
331
|
+
text,
|
|
332
|
+
re.sub(r"[\s-]+", "_", text),
|
|
333
|
+
re.sub(r"[\s_-]+", "", text),
|
|
334
|
+
]
|
|
335
|
+
for candidate in candidates:
|
|
336
|
+
if candidate in lookup:
|
|
337
|
+
return lookup[candidate]
|
|
338
|
+
return text
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _normalize_public_condition_payload(value: Any) -> Any:
|
|
342
|
+
if not isinstance(value, dict):
|
|
343
|
+
return value
|
|
344
|
+
payload = dict(value)
|
|
345
|
+
if "value" in payload and "values" not in payload:
|
|
346
|
+
raw_value = payload.pop("value")
|
|
347
|
+
payload["values"] = list(raw_value) if isinstance(raw_value, list) else [raw_value]
|
|
348
|
+
elif "values" in payload and not isinstance(payload.get("values"), list):
|
|
349
|
+
payload["values"] = [payload.get("values")]
|
|
350
|
+
raw_operator = payload.get("operator", payload.get("op"))
|
|
351
|
+
if raw_operator is None:
|
|
352
|
+
payload["operator"] = "eq"
|
|
353
|
+
raw_operator = "eq"
|
|
354
|
+
if isinstance(raw_operator, str):
|
|
355
|
+
payload["operator"] = normalize_public_condition_operator(raw_operator)
|
|
356
|
+
payload.pop("op", None)
|
|
357
|
+
return payload
|
|
358
|
+
|
|
359
|
+
|
|
246
360
|
class FlowAssigneePatch(StrictModel):
|
|
247
361
|
role_ids: list[int] = Field(default_factory=list)
|
|
248
362
|
role_names: list[str] = Field(default_factory=list)
|
|
@@ -258,45 +372,13 @@ class FlowNodePermissionsPatch(StrictModel):
|
|
|
258
372
|
|
|
259
373
|
class FlowConditionRulePatch(StrictModel):
|
|
260
374
|
field_name: str = Field(validation_alias=AliasChoices("field_name", "fieldName", "field", "name"))
|
|
261
|
-
operator: FlowConditionOperator = Field(validation_alias=AliasChoices("operator", "op"))
|
|
375
|
+
operator: FlowConditionOperator = Field(default=FlowConditionOperator.eq, validation_alias=AliasChoices("operator", "op"))
|
|
262
376
|
values: list[Any] = Field(default_factory=list)
|
|
263
377
|
|
|
264
378
|
@model_validator(mode="before")
|
|
265
379
|
@classmethod
|
|
266
380
|
def normalize_aliases(cls, value: Any) -> Any:
|
|
267
|
-
|
|
268
|
-
return value
|
|
269
|
-
payload = dict(value)
|
|
270
|
-
if "value" in payload and "values" not in payload:
|
|
271
|
-
raw_value = payload.pop("value")
|
|
272
|
-
payload["values"] = list(raw_value) if isinstance(raw_value, list) else [raw_value]
|
|
273
|
-
raw_operator = payload.get("operator", payload.get("op"))
|
|
274
|
-
if isinstance(raw_operator, str):
|
|
275
|
-
normalized = raw_operator.strip().lower()
|
|
276
|
-
operator_aliases = {
|
|
277
|
-
"equals": FlowConditionOperator.eq.value,
|
|
278
|
-
"equal": FlowConditionOperator.eq.value,
|
|
279
|
-
"=": FlowConditionOperator.eq.value,
|
|
280
|
-
"not_equals": FlowConditionOperator.neq.value,
|
|
281
|
-
"not_equal": FlowConditionOperator.neq.value,
|
|
282
|
-
"!=": FlowConditionOperator.neq.value,
|
|
283
|
-
">=": FlowConditionOperator.gte.value,
|
|
284
|
-
"<=": FlowConditionOperator.lte.value,
|
|
285
|
-
"any_of": FlowConditionOperator.in_.value,
|
|
286
|
-
"one_of": FlowConditionOperator.in_.value,
|
|
287
|
-
"between_any": FlowConditionOperator.in_.value,
|
|
288
|
-
"empty": FlowConditionOperator.is_empty.value,
|
|
289
|
-
"is blank": FlowConditionOperator.is_empty.value,
|
|
290
|
-
"blank": FlowConditionOperator.is_empty.value,
|
|
291
|
-
"not_empty": FlowConditionOperator.not_empty.value,
|
|
292
|
-
"not blank": FlowConditionOperator.not_empty.value,
|
|
293
|
-
}
|
|
294
|
-
if normalized in operator_aliases:
|
|
295
|
-
payload["operator"] = operator_aliases[normalized]
|
|
296
|
-
elif "operator" not in payload:
|
|
297
|
-
payload["operator"] = normalized
|
|
298
|
-
payload.pop("op", None)
|
|
299
|
-
return payload
|
|
381
|
+
return _normalize_public_condition_payload(value)
|
|
300
382
|
|
|
301
383
|
@model_validator(mode="after")
|
|
302
384
|
def validate_shape(self) -> "FlowConditionRulePatch":
|
|
@@ -310,45 +392,13 @@ class FlowConditionRulePatch(StrictModel):
|
|
|
310
392
|
|
|
311
393
|
class ViewFilterRulePatch(StrictModel):
|
|
312
394
|
field_name: str = Field(validation_alias=AliasChoices("field_name", "fieldName", "field", "name"))
|
|
313
|
-
operator: ViewFilterOperator = Field(validation_alias=AliasChoices("operator", "op"))
|
|
395
|
+
operator: ViewFilterOperator = Field(default=ViewFilterOperator.eq, validation_alias=AliasChoices("operator", "op"))
|
|
314
396
|
values: list[Any] = Field(default_factory=list)
|
|
315
397
|
|
|
316
398
|
@model_validator(mode="before")
|
|
317
399
|
@classmethod
|
|
318
400
|
def normalize_aliases(cls, value: Any) -> Any:
|
|
319
|
-
|
|
320
|
-
return value
|
|
321
|
-
payload = dict(value)
|
|
322
|
-
if "value" in payload and "values" not in payload:
|
|
323
|
-
raw_value = payload.pop("value")
|
|
324
|
-
payload["values"] = list(raw_value) if isinstance(raw_value, list) else [raw_value]
|
|
325
|
-
raw_operator = payload.get("operator", payload.get("op"))
|
|
326
|
-
if isinstance(raw_operator, str):
|
|
327
|
-
normalized = raw_operator.strip().lower()
|
|
328
|
-
operator_aliases = {
|
|
329
|
-
"equals": ViewFilterOperator.eq.value,
|
|
330
|
-
"equal": ViewFilterOperator.eq.value,
|
|
331
|
-
"=": ViewFilterOperator.eq.value,
|
|
332
|
-
"not_equals": ViewFilterOperator.neq.value,
|
|
333
|
-
"not_equal": ViewFilterOperator.neq.value,
|
|
334
|
-
"!=": ViewFilterOperator.neq.value,
|
|
335
|
-
">=": ViewFilterOperator.gte.value,
|
|
336
|
-
"<=": ViewFilterOperator.lte.value,
|
|
337
|
-
"any_of": ViewFilterOperator.in_.value,
|
|
338
|
-
"one_of": ViewFilterOperator.in_.value,
|
|
339
|
-
"between_any": ViewFilterOperator.in_.value,
|
|
340
|
-
"empty": ViewFilterOperator.is_empty.value,
|
|
341
|
-
"is blank": ViewFilterOperator.is_empty.value,
|
|
342
|
-
"blank": ViewFilterOperator.is_empty.value,
|
|
343
|
-
"not_empty": ViewFilterOperator.not_empty.value,
|
|
344
|
-
"not blank": ViewFilterOperator.not_empty.value,
|
|
345
|
-
}
|
|
346
|
-
if normalized in operator_aliases:
|
|
347
|
-
payload["operator"] = operator_aliases[normalized]
|
|
348
|
-
elif "operator" not in payload:
|
|
349
|
-
payload["operator"] = normalized
|
|
350
|
-
payload.pop("op", None)
|
|
351
|
-
return payload
|
|
401
|
+
return _normalize_public_condition_payload(value)
|
|
352
402
|
|
|
353
403
|
@model_validator(mode="after")
|
|
354
404
|
def validate_shape(self) -> "ViewFilterRulePatch":
|
|
@@ -1101,6 +1151,91 @@ class ViewAssociatedResourcesPatch(StrictModel):
|
|
|
1101
1151
|
)
|
|
1102
1152
|
|
|
1103
1153
|
|
|
1154
|
+
class ViewActionButtonPatch(StrictModel):
|
|
1155
|
+
button_id: int | None = Field(default=None, validation_alias=AliasChoices("button_id", "buttonId", "id"))
|
|
1156
|
+
client_key: str | None = Field(default=None, validation_alias=AliasChoices("client_key", "clientKey"))
|
|
1157
|
+
text: str = Field(validation_alias=AliasChoices("text", "button_text", "buttonText", "name"))
|
|
1158
|
+
action: PublicButtonTriggerAction = Field(validation_alias=AliasChoices("action", "trigger_action", "triggerAction"))
|
|
1159
|
+
url: str | None = Field(default=None, validation_alias=AliasChoices("url", "link_url", "linkUrl", "trigger_link_url", "triggerLinkUrl"))
|
|
1160
|
+
target_app_key: str | None = Field(default=None, validation_alias=AliasChoices("target_app_key", "targetAppKey", "related_app_key", "relatedAppKey"))
|
|
1161
|
+
target_app_name: str | None = Field(default=None, validation_alias=AliasChoices("target_app_name", "targetAppName", "related_app_name", "relatedAppName"))
|
|
1162
|
+
field_mappings: list[dict[str, Any]] = Field(default_factory=list, validation_alias=AliasChoices("field_mappings", "fieldMappings", "mappings"))
|
|
1163
|
+
default_values: dict[str, Any] = Field(default_factory=dict, validation_alias=AliasChoices("default_values", "defaultValues", "defaults"))
|
|
1164
|
+
placement: PublicButtonPlacement = Field(default=PublicButtonPlacement.detail, validation_alias=AliasChoices("placement", "position"))
|
|
1165
|
+
primary: bool = Field(default=False, validation_alias=AliasChoices("primary", "being_main", "beingMain"))
|
|
1166
|
+
visible_when: list[list[ViewFilterRulePatch]] = Field(
|
|
1167
|
+
default_factory=list,
|
|
1168
|
+
validation_alias=AliasChoices("visible_when", "visibleWhen", "button_limit", "buttonLimit"),
|
|
1169
|
+
)
|
|
1170
|
+
button_formula: str | None = Field(default=None, validation_alias=AliasChoices("button_formula", "buttonFormula"))
|
|
1171
|
+
button_formula_type: int = Field(default=1, validation_alias=AliasChoices("button_formula_type", "buttonFormulaType"))
|
|
1172
|
+
print_tpls: list[Any] = Field(default_factory=list, validation_alias=AliasChoices("print_tpls", "printTpls"))
|
|
1173
|
+
style_preset: str | None = Field(default=None, validation_alias=AliasChoices("style_preset", "stylePreset"))
|
|
1174
|
+
background_color: str | None = Field(default=None, validation_alias=AliasChoices("background_color", "backgroundColor"))
|
|
1175
|
+
text_color: str | None = Field(default=None, validation_alias=AliasChoices("text_color", "textColor"))
|
|
1176
|
+
button_icon: str | None = Field(default=None, validation_alias=AliasChoices("button_icon", "buttonIcon"))
|
|
1177
|
+
external_qrobot_config: dict[str, Any] | None = Field(
|
|
1178
|
+
default=None,
|
|
1179
|
+
validation_alias=AliasChoices(
|
|
1180
|
+
"external_qrobot_config",
|
|
1181
|
+
"externalQrobotConfig",
|
|
1182
|
+
"externalQRobotConfig",
|
|
1183
|
+
"custom_button_external_qrobot_relation_vo",
|
|
1184
|
+
"customButtonExternalQRobotRelationVO",
|
|
1185
|
+
),
|
|
1186
|
+
)
|
|
1187
|
+
trigger_wings_config: dict[str, Any] | None = Field(default=None, validation_alias=AliasChoices("trigger_wings_config", "triggerWingsConfig"))
|
|
1188
|
+
|
|
1189
|
+
@model_validator(mode="before")
|
|
1190
|
+
@classmethod
|
|
1191
|
+
def normalize_aliases(cls, value: Any) -> Any:
|
|
1192
|
+
if not isinstance(value, dict):
|
|
1193
|
+
return value
|
|
1194
|
+
payload = dict(value)
|
|
1195
|
+
raw_action = payload.get("action", payload.get("trigger_action", payload.get("triggerAction")))
|
|
1196
|
+
if isinstance(raw_action, str):
|
|
1197
|
+
normalized = raw_action.strip()
|
|
1198
|
+
aliases = {
|
|
1199
|
+
"add_data": PublicButtonTriggerAction.add_data.value,
|
|
1200
|
+
"add-data": PublicButtonTriggerAction.add_data.value,
|
|
1201
|
+
"adddata": PublicButtonTriggerAction.add_data.value,
|
|
1202
|
+
"addData": PublicButtonTriggerAction.add_data.value,
|
|
1203
|
+
"link": PublicButtonTriggerAction.link.value,
|
|
1204
|
+
"url": PublicButtonTriggerAction.link.value,
|
|
1205
|
+
"qrobot": PublicButtonTriggerAction.qrobot.value,
|
|
1206
|
+
"qRobot": PublicButtonTriggerAction.qrobot.value,
|
|
1207
|
+
"wings": PublicButtonTriggerAction.wings.value,
|
|
1208
|
+
}
|
|
1209
|
+
payload["action"] = aliases.get(normalized, aliases.get(normalized.lower(), normalized))
|
|
1210
|
+
raw_placement = payload.get("placement", payload.get("position", payload.get("config_type", payload.get("configType"))))
|
|
1211
|
+
if isinstance(raw_placement, str):
|
|
1212
|
+
normalized = raw_placement.strip().lower()
|
|
1213
|
+
if normalized in {"top", "header"}:
|
|
1214
|
+
payload["placement"] = PublicButtonPlacement.header.value
|
|
1215
|
+
elif normalized in {"detail", "data_detail"}:
|
|
1216
|
+
payload["placement"] = PublicButtonPlacement.detail.value
|
|
1217
|
+
elif normalized in {"list", "row", "row_action", "inside"}:
|
|
1218
|
+
payload["placement"] = PublicButtonPlacement.list.value
|
|
1219
|
+
raw_limits = payload.get("visible_when", payload.get("visibleWhen", payload.get("button_limit", payload.get("buttonLimit"))))
|
|
1220
|
+
if isinstance(raw_limits, list) and raw_limits and all(isinstance(item, dict) for item in raw_limits):
|
|
1221
|
+
payload["visible_when"] = [raw_limits]
|
|
1222
|
+
return payload
|
|
1223
|
+
|
|
1224
|
+
@model_validator(mode="after")
|
|
1225
|
+
def validate_shape(self) -> "ViewActionButtonPatch":
|
|
1226
|
+
if not str(self.text or "").strip():
|
|
1227
|
+
raise ValueError("action_buttons[].text is required")
|
|
1228
|
+
if self.action == PublicButtonTriggerAction.link and not str(self.url or "").strip():
|
|
1229
|
+
raise ValueError("link action_buttons require url")
|
|
1230
|
+
if self.action == PublicButtonTriggerAction.add_data and not str(self.target_app_key or "").strip():
|
|
1231
|
+
raise ValueError("add_data action_buttons require target_app_key")
|
|
1232
|
+
if self.action == PublicButtonTriggerAction.qrobot and self.external_qrobot_config is None:
|
|
1233
|
+
raise ValueError("qRobot action_buttons require external_qrobot_config")
|
|
1234
|
+
if self.action == PublicButtonTriggerAction.wings and self.trigger_wings_config is None:
|
|
1235
|
+
raise ValueError("wings action_buttons require trigger_wings_config")
|
|
1236
|
+
return self
|
|
1237
|
+
|
|
1238
|
+
|
|
1104
1239
|
class ViewUpsertPatch(StrictModel):
|
|
1105
1240
|
name: str
|
|
1106
1241
|
view_key: str | None = Field(default=None, validation_alias=AliasChoices("view_key", "viewKey"))
|
|
@@ -1112,6 +1247,8 @@ class ViewUpsertPatch(StrictModel):
|
|
|
1112
1247
|
end_field: str | None = Field(default=None, validation_alias=AliasChoices("end_field", "endField"))
|
|
1113
1248
|
title_field: str | None = Field(default=None, validation_alias=AliasChoices("title_field", "titleField"))
|
|
1114
1249
|
buttons: list["ViewButtonBindingPatch"] | None = None
|
|
1250
|
+
action_buttons: list[ViewActionButtonPatch] | None = Field(default=None, validation_alias=AliasChoices("action_buttons", "actionButtons"))
|
|
1251
|
+
action_buttons_mode: str = Field(default="merge", validation_alias=AliasChoices("action_buttons_mode", "actionButtonsMode", "button_mode", "buttonMode"))
|
|
1115
1252
|
visibility: VisibilityPatch | None = None
|
|
1116
1253
|
query_conditions: ViewQueryConditionsPatch | None = Field(default=None, validation_alias=AliasChoices("query_conditions", "queryConditions", "query_condition", "queryCondition"))
|
|
1117
1254
|
associated_resources: ViewAssociatedResourcesPatch | None = Field(
|
|
@@ -1161,6 +1298,12 @@ class ViewUpsertPatch(StrictModel):
|
|
|
1161
1298
|
payload["associated_resources"] = payload.pop("associatedReports")
|
|
1162
1299
|
if "asosChartConfig" in payload and "associated_resources" not in payload:
|
|
1163
1300
|
payload["associated_resources"] = payload.pop("asosChartConfig")
|
|
1301
|
+
if "actionButtons" in payload and "action_buttons" not in payload:
|
|
1302
|
+
payload["action_buttons"] = payload.pop("actionButtons")
|
|
1303
|
+
if "actionButtonsMode" in payload and "action_buttons_mode" not in payload:
|
|
1304
|
+
payload["action_buttons_mode"] = payload.pop("actionButtonsMode")
|
|
1305
|
+
if "buttonMode" in payload and "action_buttons_mode" not in payload:
|
|
1306
|
+
payload["action_buttons_mode"] = payload.pop("buttonMode")
|
|
1164
1307
|
raw_type = payload.get("type")
|
|
1165
1308
|
if isinstance(raw_type, str):
|
|
1166
1309
|
normalized = raw_type.strip().lower()
|
|
@@ -1182,6 +1325,10 @@ class ViewUpsertPatch(StrictModel):
|
|
|
1182
1325
|
raise ValueError("board view requires group_by")
|
|
1183
1326
|
if self.type == PublicViewType.gantt and not (self.start_field and self.end_field):
|
|
1184
1327
|
raise ValueError("gantt view requires start_field and end_field")
|
|
1328
|
+
normalized_mode = str(self.action_buttons_mode or "merge").strip().lower()
|
|
1329
|
+
if normalized_mode not in {"merge", "replace"}:
|
|
1330
|
+
raise ValueError("action_buttons_mode must be merge or replace")
|
|
1331
|
+
self.action_buttons_mode = normalized_mode
|
|
1185
1332
|
return self
|
|
1186
1333
|
|
|
1187
1334
|
|
|
@@ -1207,6 +1354,76 @@ class ViewPartialPatch(StrictModel):
|
|
|
1207
1354
|
return self
|
|
1208
1355
|
|
|
1209
1356
|
|
|
1357
|
+
def public_button_trigger_action_value(value: Any) -> str:
|
|
1358
|
+
raw = value.value if isinstance(value, PublicButtonTriggerAction) else str(value or "").strip()
|
|
1359
|
+
normalized = raw.lower()
|
|
1360
|
+
if raw == PublicButtonTriggerAction.add_data.value or normalized in {"add_data", "add-data", "adddata"}:
|
|
1361
|
+
return "add_data"
|
|
1362
|
+
if normalized in {PublicButtonTriggerAction.link.value, "url"}:
|
|
1363
|
+
return "link"
|
|
1364
|
+
if raw == PublicButtonTriggerAction.qrobot.value or normalized == "qrobot":
|
|
1365
|
+
return "qRobot"
|
|
1366
|
+
if normalized == PublicButtonTriggerAction.wings.value:
|
|
1367
|
+
return "wings"
|
|
1368
|
+
return raw
|
|
1369
|
+
|
|
1370
|
+
|
|
1371
|
+
def _drop_empty_public_payload_values(payload: dict[str, Any]) -> dict[str, Any]:
|
|
1372
|
+
compact: dict[str, Any] = {}
|
|
1373
|
+
for key, item in payload.items():
|
|
1374
|
+
if item is None or item == "" or item == [] or item == {}:
|
|
1375
|
+
continue
|
|
1376
|
+
if key == "primary" and item is False:
|
|
1377
|
+
continue
|
|
1378
|
+
compact[key] = item
|
|
1379
|
+
return compact
|
|
1380
|
+
|
|
1381
|
+
|
|
1382
|
+
def public_view_action_button_payload(button: Any, *, compact: bool = False) -> dict[str, Any]:
|
|
1383
|
+
if isinstance(button, ViewActionButtonPatch):
|
|
1384
|
+
payload = button.model_dump(mode="json")
|
|
1385
|
+
elif isinstance(button, dict):
|
|
1386
|
+
payload = dict(button)
|
|
1387
|
+
else:
|
|
1388
|
+
return {}
|
|
1389
|
+
raw_action = payload.get("action", payload.get("trigger_action", payload.get("triggerAction")))
|
|
1390
|
+
if raw_action is not None:
|
|
1391
|
+
payload["action"] = public_button_trigger_action_value(raw_action)
|
|
1392
|
+
payload.pop("trigger_action", None)
|
|
1393
|
+
payload.pop("triggerAction", None)
|
|
1394
|
+
if compact:
|
|
1395
|
+
return _drop_empty_public_payload_values(payload)
|
|
1396
|
+
return payload
|
|
1397
|
+
|
|
1398
|
+
|
|
1399
|
+
def public_view_upsert_payload(view: ViewUpsertPatch) -> dict[str, Any]:
|
|
1400
|
+
payload = view.model_dump(mode="json")
|
|
1401
|
+
if view.action_buttons is not None:
|
|
1402
|
+
payload["action_buttons"] = [
|
|
1403
|
+
public_view_action_button_payload(button, compact=True)
|
|
1404
|
+
for button in view.action_buttons
|
|
1405
|
+
]
|
|
1406
|
+
return payload
|
|
1407
|
+
|
|
1408
|
+
|
|
1409
|
+
def public_view_partial_payload(patch: ViewPartialPatch) -> dict[str, Any]:
|
|
1410
|
+
payload = patch.model_dump(mode="json")
|
|
1411
|
+
patch_set = payload.get("set")
|
|
1412
|
+
if isinstance(patch_set, dict):
|
|
1413
|
+
raw_buttons = patch_set.get("action_buttons")
|
|
1414
|
+
if raw_buttons is None:
|
|
1415
|
+
raw_buttons = patch_set.get("actionButtons")
|
|
1416
|
+
if isinstance(raw_buttons, list):
|
|
1417
|
+
patch_set = dict(patch_set)
|
|
1418
|
+
patch_set.pop("actionButtons", None)
|
|
1419
|
+
patch_set["action_buttons"] = [
|
|
1420
|
+
public_view_action_button_payload(button, compact=True)
|
|
1421
|
+
for button in raw_buttons
|
|
1422
|
+
]
|
|
1423
|
+
payload["set"] = patch_set
|
|
1424
|
+
return payload
|
|
1425
|
+
|
|
1426
|
+
|
|
1210
1427
|
class CustomButtonJudgeValuePatch(StrictModel):
|
|
1211
1428
|
id: int | str | None = None
|
|
1212
1429
|
value: Any | None = None
|
|
@@ -1275,32 +1492,39 @@ class CustomButtonFieldMappingPatch(StrictModel):
|
|
|
1275
1492
|
|
|
1276
1493
|
|
|
1277
1494
|
class FieldMatchMappingPatch(StrictModel):
|
|
1278
|
-
target_field: Any = Field(
|
|
1279
|
-
validation_alias=AliasChoices("target_field", "targetField", "target", "field", "field_name", "fieldName")
|
|
1280
|
-
)
|
|
1495
|
+
target_field: Any = Field(validation_alias=AliasChoices("target_field", "targetField", "target", "field", "field_name", "fieldName", "name"))
|
|
1281
1496
|
source_field: Any | None = Field(default=None, validation_alias=AliasChoices("source_field", "sourceField", "source"))
|
|
1282
|
-
value: Any | None = Field(default=None, validation_alias=AliasChoices("value", "static_value", "staticValue"))
|
|
1497
|
+
value: Any | None = Field(default=None, validation_alias=AliasChoices("value", "values", "static_value", "staticValue"))
|
|
1283
1498
|
operator: str = Field(default="eq", validation_alias=AliasChoices("operator", "op", "judge_type", "judgeType"))
|
|
1284
1499
|
|
|
1285
1500
|
@model_validator(mode="before")
|
|
1286
1501
|
@classmethod
|
|
1287
|
-
def
|
|
1502
|
+
def normalize_aliases(cls, value: Any) -> Any:
|
|
1288
1503
|
if not isinstance(value, dict):
|
|
1289
1504
|
return value
|
|
1290
1505
|
payload = dict(value)
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
return payload
|
|
1296
|
-
if isinstance(values, list) and len(values) == 1:
|
|
1297
|
-
payload["value"] = values[0]
|
|
1506
|
+
if "values" in payload and "value" not in payload:
|
|
1507
|
+
raw_values = payload.pop("values")
|
|
1508
|
+
if isinstance(raw_values, list) and len(raw_values) == 1:
|
|
1509
|
+
payload["value"] = raw_values[0]
|
|
1298
1510
|
else:
|
|
1299
|
-
payload["value"] =
|
|
1511
|
+
payload["value"] = raw_values
|
|
1512
|
+
raw_operator = payload.get("operator", payload.get("op", payload.get("judge_type", payload.get("judgeType"))))
|
|
1513
|
+
if isinstance(raw_operator, str):
|
|
1514
|
+
payload["operator"] = normalize_public_condition_operator(
|
|
1515
|
+
raw_operator,
|
|
1516
|
+
extra_aliases={"include_any": "include_any", "includes_any": "include_any", "includeany": "include_any"},
|
|
1517
|
+
)
|
|
1518
|
+
for alias in ("op", "judge_type", "judgeType"):
|
|
1519
|
+
payload.pop(alias, None)
|
|
1300
1520
|
return payload
|
|
1301
1521
|
|
|
1302
1522
|
@model_validator(mode="after")
|
|
1303
1523
|
def validate_shape(self) -> "FieldMatchMappingPatch":
|
|
1524
|
+
if str(self.operator or "").strip().lower() in {"is_empty", "not_empty"}:
|
|
1525
|
+
self.source_field = None
|
|
1526
|
+
self.value = None
|
|
1527
|
+
return self
|
|
1304
1528
|
has_source = self.source_field is not None and str(self.source_field).strip() != ""
|
|
1305
1529
|
has_value = self.value is not None
|
|
1306
1530
|
if has_source == has_value:
|
|
@@ -1762,45 +1986,13 @@ class ViewButtonBindingPatch(StrictModel):
|
|
|
1762
1986
|
|
|
1763
1987
|
class ChartFilterRulePatch(StrictModel):
|
|
1764
1988
|
field_name: str = Field(validation_alias=AliasChoices("field_name", "fieldName", "field", "name"))
|
|
1765
|
-
operator: ViewFilterOperator = Field(validation_alias=AliasChoices("operator", "op"))
|
|
1989
|
+
operator: ViewFilterOperator = Field(default=ViewFilterOperator.eq, validation_alias=AliasChoices("operator", "op"))
|
|
1766
1990
|
values: list[Any] = Field(default_factory=list)
|
|
1767
1991
|
|
|
1768
1992
|
@model_validator(mode="before")
|
|
1769
1993
|
@classmethod
|
|
1770
1994
|
def normalize_aliases(cls, value: Any) -> Any:
|
|
1771
|
-
|
|
1772
|
-
return value
|
|
1773
|
-
payload = dict(value)
|
|
1774
|
-
if "value" in payload and "values" not in payload:
|
|
1775
|
-
raw_value = payload.pop("value")
|
|
1776
|
-
payload["values"] = list(raw_value) if isinstance(raw_value, list) else [raw_value]
|
|
1777
|
-
raw_operator = payload.get("operator", payload.get("op"))
|
|
1778
|
-
if isinstance(raw_operator, str):
|
|
1779
|
-
normalized = raw_operator.strip().lower()
|
|
1780
|
-
operator_aliases = {
|
|
1781
|
-
"equals": ViewFilterOperator.eq.value,
|
|
1782
|
-
"equal": ViewFilterOperator.eq.value,
|
|
1783
|
-
"=": ViewFilterOperator.eq.value,
|
|
1784
|
-
"not_equals": ViewFilterOperator.neq.value,
|
|
1785
|
-
"not_equal": ViewFilterOperator.neq.value,
|
|
1786
|
-
"!=": ViewFilterOperator.neq.value,
|
|
1787
|
-
">=": ViewFilterOperator.gte.value,
|
|
1788
|
-
"<=": ViewFilterOperator.lte.value,
|
|
1789
|
-
"any_of": ViewFilterOperator.in_.value,
|
|
1790
|
-
"one_of": ViewFilterOperator.in_.value,
|
|
1791
|
-
"between_any": ViewFilterOperator.in_.value,
|
|
1792
|
-
"empty": ViewFilterOperator.is_empty.value,
|
|
1793
|
-
"is blank": ViewFilterOperator.is_empty.value,
|
|
1794
|
-
"blank": ViewFilterOperator.is_empty.value,
|
|
1795
|
-
"not_empty": ViewFilterOperator.not_empty.value,
|
|
1796
|
-
"not blank": ViewFilterOperator.not_empty.value,
|
|
1797
|
-
}
|
|
1798
|
-
if normalized in operator_aliases:
|
|
1799
|
-
payload["operator"] = operator_aliases[normalized]
|
|
1800
|
-
elif "operator" not in payload:
|
|
1801
|
-
payload["operator"] = normalized
|
|
1802
|
-
payload.pop("op", None)
|
|
1803
|
-
return payload
|
|
1995
|
+
return _normalize_public_condition_payload(value)
|
|
1804
1996
|
|
|
1805
1997
|
@model_validator(mode="after")
|
|
1806
1998
|
def validate_shape(self) -> "ChartFilterRulePatch":
|
|
@@ -2089,6 +2281,22 @@ class ChartApplyRequest(StrictModel):
|
|
|
2089
2281
|
return self
|
|
2090
2282
|
|
|
2091
2283
|
|
|
2284
|
+
class PortalInlineChartPatch(ChartUpsertPatch):
|
|
2285
|
+
app_key: str = Field(validation_alias=AliasChoices("app_key", "appKey"))
|
|
2286
|
+
|
|
2287
|
+
@model_validator(mode="before")
|
|
2288
|
+
@classmethod
|
|
2289
|
+
def normalize_inline_aliases(cls, value: Any) -> Any:
|
|
2290
|
+
if not isinstance(value, dict):
|
|
2291
|
+
return value
|
|
2292
|
+
payload = dict(value)
|
|
2293
|
+
if "chart_name" in payload and "name" not in payload:
|
|
2294
|
+
payload["name"] = payload.pop("chart_name")
|
|
2295
|
+
if "chartName" in payload and "name" not in payload:
|
|
2296
|
+
payload["name"] = payload.pop("chartName")
|
|
2297
|
+
return payload
|
|
2298
|
+
|
|
2299
|
+
|
|
2092
2300
|
class PortalComponentPositionPatch(StrictModel):
|
|
2093
2301
|
pc_x: int = Field(default=0, validation_alias=AliasChoices("pc_x", "pcX", "x"))
|
|
2094
2302
|
pc_y: int = Field(default=0, validation_alias=AliasChoices("pc_y", "pcY", "y"))
|
|
@@ -2133,14 +2341,36 @@ class PortalComponentPositionPatch(StrictModel):
|
|
|
2133
2341
|
|
|
2134
2342
|
|
|
2135
2343
|
class PortalChartRefPatch(StrictModel):
|
|
2136
|
-
app_key: str
|
|
2344
|
+
app_key: str | None = None
|
|
2137
2345
|
chart_id: str | None = None
|
|
2138
2346
|
chart_name: str | None = None
|
|
2139
2347
|
|
|
2348
|
+
@model_validator(mode="before")
|
|
2349
|
+
@classmethod
|
|
2350
|
+
def normalize_aliases(cls, value: Any) -> Any:
|
|
2351
|
+
if not isinstance(value, dict):
|
|
2352
|
+
return value
|
|
2353
|
+
payload = dict(value)
|
|
2354
|
+
if "chartId" in payload and "chart_id" not in payload:
|
|
2355
|
+
payload["chart_id"] = payload.pop("chartId")
|
|
2356
|
+
if "chart_key" in payload and "chart_id" not in payload:
|
|
2357
|
+
payload["chart_id"] = payload.pop("chart_key")
|
|
2358
|
+
if "chartKey" in payload and "chart_id" not in payload:
|
|
2359
|
+
payload["chart_id"] = payload.pop("chartKey")
|
|
2360
|
+
if "chartName" in payload and "chart_name" not in payload:
|
|
2361
|
+
payload["chart_name"] = payload.pop("chartName")
|
|
2362
|
+
if "name" in payload and "chart_name" not in payload:
|
|
2363
|
+
payload["chart_name"] = payload.pop("name")
|
|
2364
|
+
if "appKey" in payload and "app_key" not in payload:
|
|
2365
|
+
payload["app_key"] = payload.pop("appKey")
|
|
2366
|
+
return payload
|
|
2367
|
+
|
|
2140
2368
|
@model_validator(mode="after")
|
|
2141
2369
|
def validate_target(self) -> "PortalChartRefPatch":
|
|
2142
2370
|
if not (self.chart_id or self.chart_name):
|
|
2143
2371
|
raise ValueError("chart_ref requires chart_id or chart_name")
|
|
2372
|
+
if self.chart_name and not self.chart_id and not self.app_key:
|
|
2373
|
+
raise ValueError("chart_ref.app_key is required when resolving by chart_name")
|
|
2144
2374
|
return self
|
|
2145
2375
|
|
|
2146
2376
|
|
|
@@ -2171,6 +2401,7 @@ class PortalSectionPatch(StrictModel):
|
|
|
2171
2401
|
dash_style_config: dict[str, Any] | None = Field(default=None, validation_alias=AliasChoices("dash_style_config", "dashStyleConfigBO"))
|
|
2172
2402
|
config: dict[str, Any] = Field(default_factory=dict)
|
|
2173
2403
|
chart_ref: PortalChartRefPatch | None = None
|
|
2404
|
+
chart: PortalInlineChartPatch | None = Field(default=None, validation_alias=AliasChoices("chart", "inline_chart", "inlineChart", "create_chart", "createChart"))
|
|
2174
2405
|
view_ref: PortalViewRefPatch | None = None
|
|
2175
2406
|
text: str | None = None
|
|
2176
2407
|
url: str | None = None
|
|
@@ -2189,6 +2420,27 @@ class PortalSectionPatch(StrictModel):
|
|
|
2189
2420
|
payload["role"] = raw_role.strip().lower()
|
|
2190
2421
|
if "chartRef" in payload and "chart_ref" not in payload:
|
|
2191
2422
|
payload["chart_ref"] = payload.pop("chartRef")
|
|
2423
|
+
if "inlineChart" in payload and "chart" not in payload:
|
|
2424
|
+
payload["chart"] = payload.pop("inlineChart")
|
|
2425
|
+
if "inline_chart" in payload and "chart" not in payload:
|
|
2426
|
+
payload["chart"] = payload.pop("inline_chart")
|
|
2427
|
+
if "createChart" in payload and "chart" not in payload:
|
|
2428
|
+
payload["chart"] = payload.pop("createChart")
|
|
2429
|
+
if "create_chart" in payload and "chart" not in payload:
|
|
2430
|
+
payload["chart"] = payload.pop("create_chart")
|
|
2431
|
+
raw_chart = payload.get("chart")
|
|
2432
|
+
if isinstance(raw_chart, dict):
|
|
2433
|
+
chart_payload = dict(raw_chart)
|
|
2434
|
+
has_ref_id = any(key in chart_payload for key in ("chart_id", "chartId", "chart_key", "chartKey"))
|
|
2435
|
+
has_chart_type = any(key in chart_payload for key in ("chart_type", "chartType"))
|
|
2436
|
+
has_create_config = has_chart_type or any(
|
|
2437
|
+
key in chart_payload
|
|
2438
|
+
for key in ("metric", "metrics", "group_by", "groupBy", "where", "filters", "dimensions")
|
|
2439
|
+
)
|
|
2440
|
+
has_ref_name = any(key in chart_payload for key in ("chart_name", "chartName", "name"))
|
|
2441
|
+
if has_ref_id or (has_ref_name and not has_create_config):
|
|
2442
|
+
payload["chart_ref"] = chart_payload
|
|
2443
|
+
payload.pop("chart", None)
|
|
2192
2444
|
if "viewRef" in payload and "view_ref" not in payload:
|
|
2193
2445
|
payload["view_ref"] = payload.pop("viewRef")
|
|
2194
2446
|
if "dashStyleConfigBO" in payload and "dash_style_config" not in payload:
|
|
@@ -2200,8 +2452,8 @@ class PortalSectionPatch(StrictModel):
|
|
|
2200
2452
|
supported = {"chart", "view", "grid", "filter", "text", "link"}
|
|
2201
2453
|
if self.source_type not in supported:
|
|
2202
2454
|
raise ValueError(f"unsupported portal source_type '{self.source_type}'")
|
|
2203
|
-
if self.source_type == "chart" and self.chart_ref is None:
|
|
2204
|
-
raise ValueError("chart section requires chart_ref")
|
|
2455
|
+
if self.source_type == "chart" and self.chart_ref is None and self.chart is None:
|
|
2456
|
+
raise ValueError("chart section requires chart_ref or chart")
|
|
2205
2457
|
if self.source_type == "view" and self.view_ref is None:
|
|
2206
2458
|
raise ValueError("view section requires view_ref")
|
|
2207
2459
|
if self.source_type == "text" and self.text is None:
|
|
@@ -2338,6 +2590,7 @@ AppReadSummaryResponse = AppGetResponse
|
|
|
2338
2590
|
AppFieldsReadResponse = AppGetFieldsResponse
|
|
2339
2591
|
AppLayoutReadResponse = AppGetLayoutResponse
|
|
2340
2592
|
AppViewsReadResponse = AppGetViewsResponse
|
|
2593
|
+
AppFlowReadResponse = AppGetFlowResponse
|
|
2341
2594
|
AppChartsReadResponse = AppGetChartsResponse
|
|
2342
2595
|
|
|
2343
2596
|
|
|
@@ -2404,7 +2657,6 @@ class SchemaPlanRequest(StrictModel):
|
|
|
2404
2657
|
icon: str | None = None
|
|
2405
2658
|
color: str | None = None
|
|
2406
2659
|
visibility: VisibilityPatch | None = None
|
|
2407
|
-
create_if_missing: bool = False
|
|
2408
2660
|
add_fields: list[FieldPatch] = Field(default_factory=list)
|
|
2409
2661
|
update_fields: list[FieldUpdatePatch] = Field(default_factory=list)
|
|
2410
2662
|
remove_fields: list[FieldRemovePatch] = Field(default_factory=list)
|
|
@@ -2427,6 +2679,40 @@ class LayoutPlanRequest(StrictModel):
|
|
|
2427
2679
|
return payload
|
|
2428
2680
|
|
|
2429
2681
|
|
|
2682
|
+
class FlowPlanRequest(StrictModel):
|
|
2683
|
+
app_key: str
|
|
2684
|
+
mode: str = "replace"
|
|
2685
|
+
nodes: list[FlowNodePatch] = Field(default_factory=list)
|
|
2686
|
+
transitions: list[FlowTransitionPatch] = Field(default_factory=list)
|
|
2687
|
+
preset: FlowPreset | None = None
|
|
2688
|
+
|
|
2689
|
+
@model_validator(mode="before")
|
|
2690
|
+
@classmethod
|
|
2691
|
+
def normalize_mode_alias(cls, value: Any) -> Any:
|
|
2692
|
+
if not isinstance(value, dict):
|
|
2693
|
+
return value
|
|
2694
|
+
payload = dict(value)
|
|
2695
|
+
if str(payload.get("mode") or "").strip().lower() == "overwrite":
|
|
2696
|
+
payload["mode"] = "replace"
|
|
2697
|
+
raw_preset = payload.get("preset")
|
|
2698
|
+
if raw_preset is None and isinstance(payload.get("base_preset"), str):
|
|
2699
|
+
raw_preset = payload["base_preset"]
|
|
2700
|
+
payload["preset"] = raw_preset
|
|
2701
|
+
if isinstance(raw_preset, str):
|
|
2702
|
+
normalized_preset = raw_preset.strip().lower()
|
|
2703
|
+
preset_aliases = {
|
|
2704
|
+
"default_approval": FlowPreset.basic_approval.value,
|
|
2705
|
+
"approval": FlowPreset.basic_approval.value,
|
|
2706
|
+
"basic approval": FlowPreset.basic_approval.value,
|
|
2707
|
+
"default_fill_then_approve": FlowPreset.basic_fill_then_approve.value,
|
|
2708
|
+
"default-fill-then-approve": FlowPreset.basic_fill_then_approve.value,
|
|
2709
|
+
"fill_then_approve": FlowPreset.basic_fill_then_approve.value,
|
|
2710
|
+
}
|
|
2711
|
+
if normalized_preset in preset_aliases:
|
|
2712
|
+
payload["preset"] = preset_aliases[normalized_preset]
|
|
2713
|
+
return payload
|
|
2714
|
+
|
|
2715
|
+
|
|
2430
2716
|
class ViewsPlanRequest(StrictModel):
|
|
2431
2717
|
app_key: str
|
|
2432
2718
|
upsert_views: list[ViewUpsertPatch] = Field(default_factory=list)
|
|
@@ -2454,6 +2740,14 @@ def _normalize_field_payload(value: Any) -> Any:
|
|
|
2454
2740
|
if not isinstance(value, dict):
|
|
2455
2741
|
return value
|
|
2456
2742
|
payload = dict(value)
|
|
2743
|
+
if "data_title" in payload and "as_data_title" not in payload and "asDataTitle" not in payload:
|
|
2744
|
+
payload["as_data_title"] = payload.pop("data_title")
|
|
2745
|
+
if "dataTitle" in payload and "as_data_title" not in payload and "asDataTitle" not in payload:
|
|
2746
|
+
payload["as_data_title"] = payload.pop("dataTitle")
|
|
2747
|
+
if "data_cover" in payload and "as_data_cover" not in payload and "asDataCover" not in payload:
|
|
2748
|
+
payload["as_data_cover"] = payload.pop("data_cover")
|
|
2749
|
+
if "dataCover" in payload and "as_data_cover" not in payload and "asDataCover" not in payload:
|
|
2750
|
+
payload["as_data_cover"] = payload.pop("dataCover")
|
|
2457
2751
|
if "fields" in payload and "subfields" not in payload:
|
|
2458
2752
|
payload["subfields"] = payload.pop("fields")
|
|
2459
2753
|
if "options" in payload:
|