@josephyan/qingflow-app-builder-mcp 1.1.25 → 1.1.27
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 +6 -6
- package/docs/local-agent-install.md +6 -57
- package/entry_point.py +1 -1
- package/npm/bin/qingflow-app-builder-mcp.mjs +33 -2
- package/npm/lib/runtime.mjs +101 -21
- package/npm/scripts/postinstall.mjs +10 -1
- package/package.json +2 -3
- package/pyproject.toml +1 -1
- package/skills/qingflow-app-builder/SKILL.md +51 -164
- package/skills/qingflow-app-builder/references/build-complete-system.md +234 -0
- package/skills/qingflow-app-builder/references/build-single-app.md +138 -0
- package/skills/qingflow-app-builder/references/create-app.md +24 -31
- package/skills/qingflow-app-builder/references/environments.md +1 -1
- package/skills/qingflow-app-builder/references/gotchas.md +13 -39
- package/skills/qingflow-app-builder/references/match-rules.md +18 -29
- package/skills/qingflow-app-builder/references/public-surface-sync.md +2 -2
- package/skills/qingflow-app-builder/references/solution-playbooks.md +0 -10
- package/skills/qingflow-app-builder/references/tool-selection.md +25 -42
- package/skills/qingflow-app-builder/references/update-flow.md +38 -22
- package/skills/qingflow-app-builder/references/update-schema.md +0 -14
- package/skills/qingflow-app-builder/references/update-views.md +14 -83
- package/skills/qingflow-app-builder-code-integrations/SKILL.md +3 -5
- package/skills/qingflow-app-builder-code-integrations/references/code-block.md +1 -1
- package/skills/qingflow-app-builder-code-integrations/references/q-linker.md +1 -1
- package/skills/qingflow-mcp-setup/SKILL.md +111 -0
- package/skills/qingflow-mcp-setup/agents/openai.yaml +4 -0
- package/skills/qingflow-mcp-setup/references/claude-desktop.md +34 -0
- package/skills/qingflow-mcp-setup/references/environments.md +61 -0
- package/skills/qingflow-mcp-setup/references/generic-stdio.md +31 -0
- package/skills/qingflow-mcp-setup/scripts/check_local_server.sh +38 -0
- package/skills/qingflow-workflow-builder/SKILL.md +96 -0
- package/skills/qingflow-workflow-builder/manifest.yaml +8 -0
- package/skills/qingflow-workflow-builder/references/01-overview.md +45 -0
- package/skills/qingflow-workflow-builder/references/02-update-mode.md +53 -0
- package/skills/qingflow-workflow-builder/references/03-flow-patterns.md +57 -0
- package/skills/qingflow-workflow-builder/references/04-stage1-business-modeling.md +131 -0
- package/skills/qingflow-workflow-builder/references/05-stage2-members-roles.md +29 -0
- package/skills/qingflow-workflow-builder/references/06-stage3-build-spec.md +165 -0
- package/skills/qingflow-workflow-builder/references/07-stage4-validate-spec.md +33 -0
- package/skills/qingflow-workflow-builder/references/08-stage5-apply-verify.md +51 -0
- package/skills/qingflow-workflow-builder/references/09-stage6-summary.md +88 -0
- package/skills/qingflow-workflow-builder/references/10-node-config-reference.md +93 -0
- package/skills/qingflow-workflow-builder/references/11-troubleshooting.md +15 -0
- package/skills/qingflow-workflow-builder/scripts/diff_flow_spec.py +275 -0
- package/skills/qingflow-workflow-builder/scripts/validate_flow_spec.py +605 -0
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/__main__.py +2 -6
- package/src/qingflow_mcp/builder_facade/models.py +49 -330
- package/src/qingflow_mcp/builder_facade/service.py +1621 -6149
- package/src/qingflow_mcp/cli/commands/builder.py +366 -322
- package/src/qingflow_mcp/cli/commands/chart.py +1 -1
- package/src/qingflow_mcp/cli/commands/common.py +3 -12
- package/src/qingflow_mcp/cli/commands/exports.py +2 -2
- package/src/qingflow_mcp/cli/commands/imports.py +3 -3
- package/src/qingflow_mcp/cli/commands/portal.py +2 -2
- package/src/qingflow_mcp/cli/commands/record.py +27 -101
- package/src/qingflow_mcp/cli/commands/task.py +47 -28
- package/src/qingflow_mcp/cli/commands/view.py +1 -1
- package/src/qingflow_mcp/cli/context.py +3 -0
- package/src/qingflow_mcp/cli/formatters.py +16 -784
- package/src/qingflow_mcp/cli/main.py +41 -120
- package/src/qingflow_mcp/errors.py +2 -43
- package/src/qingflow_mcp/public_surface.py +17 -26
- package/src/qingflow_mcp/response_trim.py +17 -81
- package/src/qingflow_mcp/server.py +12 -14
- package/src/qingflow_mcp/server_app_builder.py +39 -81
- package/src/qingflow_mcp/server_app_user.py +16 -22
- package/src/qingflow_mcp/session_store.py +7 -11
- package/src/qingflow_mcp/solution/compiler/__init__.py +1 -3
- package/src/qingflow_mcp/solution/executor.py +18 -245
- package/src/qingflow_mcp/tools/ai_builder_tools.py +1483 -3139
- package/src/qingflow_mcp/tools/app_tools.py +43 -184
- package/src/qingflow_mcp/tools/approval_tools.py +35 -197
- package/src/qingflow_mcp/tools/auth_tools.py +16 -92
- package/src/qingflow_mcp/tools/code_block_tools.py +40 -298
- package/src/qingflow_mcp/tools/custom_button_tools.py +10 -64
- package/src/qingflow_mcp/tools/directory_tools.py +72 -236
- package/src/qingflow_mcp/tools/export_tools.py +34 -244
- package/src/qingflow_mcp/tools/feedback_tools.py +0 -9
- package/src/qingflow_mcp/tools/file_tools.py +3 -9
- package/src/qingflow_mcp/tools/import_tools.py +49 -336
- package/src/qingflow_mcp/tools/navigation_tools.py +12 -91
- package/src/qingflow_mcp/tools/package_tools.py +6 -118
- package/src/qingflow_mcp/tools/portal_tools.py +3 -39
- package/src/qingflow_mcp/tools/qingbi_report_tools.py +7 -116
- package/src/qingflow_mcp/tools/record_tools.py +360 -1145
- package/src/qingflow_mcp/tools/resource_read_tools.py +39 -188
- package/src/qingflow_mcp/tools/role_tools.py +9 -80
- package/src/qingflow_mcp/tools/solution_tools.py +45 -59
- package/src/qingflow_mcp/tools/task_context_tools.py +158 -662
- package/src/qingflow_mcp/tools/task_tools.py +29 -113
- package/src/qingflow_mcp/tools/view_tools.py +3 -106
- package/src/qingflow_mcp/tools/workflow_tools.py +4 -48
- package/src/qingflow_mcp/tools/workspace_tools.py +3 -71
- package/src/qingflow_mcp/version.py +2 -0
- package/npm/bin/qingflow-skills.mjs +0 -5
- package/skills/qingflow-app-builder/references/complete-system-development-guide.md +0 -172
- package/skills/qingflow-app-builder/references/single-app-development-guide.md +0 -87
- package/skills/qingflow-app-builder/scripts/validate_system_build_summary.py +0 -124
- package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +0 -173
|
@@ -71,36 +71,20 @@ class PublicExternalVisibilityMode(str, Enum):
|
|
|
71
71
|
|
|
72
72
|
|
|
73
73
|
FIELD_TYPE_ALIASES: dict[str, PublicFieldType] = {
|
|
74
|
+
"textarea": PublicFieldType.long_text,
|
|
74
75
|
"multiline": PublicFieldType.long_text,
|
|
75
|
-
"multiline_text": PublicFieldType.long_text,
|
|
76
76
|
"multi_line": PublicFieldType.long_text,
|
|
77
|
-
"
|
|
78
|
-
"textarea": PublicFieldType.long_text,
|
|
79
|
-
"longtext": PublicFieldType.long_text,
|
|
80
|
-
"long-text": PublicFieldType.long_text,
|
|
77
|
+
"multiline_text": PublicFieldType.long_text,
|
|
81
78
|
"amount": PublicFieldType.amount,
|
|
82
79
|
"currency": PublicFieldType.amount,
|
|
83
80
|
"mobile": PublicFieldType.phone,
|
|
84
81
|
"user": PublicFieldType.member,
|
|
85
82
|
"users": PublicFieldType.member,
|
|
86
83
|
"select": PublicFieldType.single_select,
|
|
87
|
-
"single_choice": PublicFieldType.single_select,
|
|
88
|
-
"single-choice": PublicFieldType.single_select,
|
|
89
|
-
"single choice": PublicFieldType.single_select,
|
|
90
|
-
"choice": PublicFieldType.single_select,
|
|
91
|
-
"dropdown": PublicFieldType.single_select,
|
|
92
84
|
"radio": PublicFieldType.single_select,
|
|
93
85
|
"checkbox": PublicFieldType.multi_select,
|
|
94
86
|
"multi_select": PublicFieldType.multi_select,
|
|
95
87
|
"multi-select": PublicFieldType.multi_select,
|
|
96
|
-
"multi select": PublicFieldType.multi_select,
|
|
97
|
-
"multiselect": PublicFieldType.multi_select,
|
|
98
|
-
"multi_choice": PublicFieldType.multi_select,
|
|
99
|
-
"multi-choice": PublicFieldType.multi_select,
|
|
100
|
-
"multi choice": PublicFieldType.multi_select,
|
|
101
|
-
"multiple_choice": PublicFieldType.multi_select,
|
|
102
|
-
"multiple-choice": PublicFieldType.multi_select,
|
|
103
|
-
"multiple choice": PublicFieldType.multi_select,
|
|
104
88
|
"departments": PublicFieldType.department,
|
|
105
89
|
"qlinker": PublicFieldType.q_linker,
|
|
106
90
|
"q_linker": PublicFieldType.q_linker,
|
|
@@ -203,10 +187,6 @@ class LayoutPreset(str, Enum):
|
|
|
203
187
|
single_section = "single_section"
|
|
204
188
|
|
|
205
189
|
|
|
206
|
-
class FlowPreset(str, Enum):
|
|
207
|
-
basic_approval = "basic_approval"
|
|
208
|
-
basic_fill_then_approve = "basic_fill_then_approve"
|
|
209
|
-
|
|
210
190
|
|
|
211
191
|
class ViewsPreset(str, Enum):
|
|
212
192
|
default_table = "default_table"
|
|
@@ -1105,91 +1085,6 @@ class ViewAssociatedResourcesPatch(StrictModel):
|
|
|
1105
1085
|
)
|
|
1106
1086
|
|
|
1107
1087
|
|
|
1108
|
-
class ViewActionButtonPatch(StrictModel):
|
|
1109
|
-
button_id: int | None = Field(default=None, validation_alias=AliasChoices("button_id", "buttonId", "id"))
|
|
1110
|
-
client_key: str | None = Field(default=None, validation_alias=AliasChoices("client_key", "clientKey"))
|
|
1111
|
-
text: str = Field(validation_alias=AliasChoices("text", "button_text", "buttonText", "name"))
|
|
1112
|
-
action: PublicButtonTriggerAction = Field(validation_alias=AliasChoices("action", "trigger_action", "triggerAction"))
|
|
1113
|
-
url: str | None = Field(default=None, validation_alias=AliasChoices("url", "link_url", "linkUrl", "trigger_link_url", "triggerLinkUrl"))
|
|
1114
|
-
target_app_key: str | None = Field(default=None, validation_alias=AliasChoices("target_app_key", "targetAppKey", "related_app_key", "relatedAppKey"))
|
|
1115
|
-
target_app_name: str | None = Field(default=None, validation_alias=AliasChoices("target_app_name", "targetAppName", "related_app_name", "relatedAppName"))
|
|
1116
|
-
field_mappings: list[dict[str, Any]] = Field(default_factory=list, validation_alias=AliasChoices("field_mappings", "fieldMappings", "mappings"))
|
|
1117
|
-
default_values: dict[str, Any] = Field(default_factory=dict, validation_alias=AliasChoices("default_values", "defaultValues", "defaults"))
|
|
1118
|
-
placement: PublicButtonPlacement = Field(default=PublicButtonPlacement.detail, validation_alias=AliasChoices("placement", "position"))
|
|
1119
|
-
primary: bool = Field(default=False, validation_alias=AliasChoices("primary", "being_main", "beingMain"))
|
|
1120
|
-
visible_when: list[list[ViewFilterRulePatch]] = Field(
|
|
1121
|
-
default_factory=list,
|
|
1122
|
-
validation_alias=AliasChoices("visible_when", "visibleWhen", "button_limit", "buttonLimit"),
|
|
1123
|
-
)
|
|
1124
|
-
button_formula: str | None = Field(default=None, validation_alias=AliasChoices("button_formula", "buttonFormula"))
|
|
1125
|
-
button_formula_type: int = Field(default=1, validation_alias=AliasChoices("button_formula_type", "buttonFormulaType"))
|
|
1126
|
-
print_tpls: list[Any] = Field(default_factory=list, validation_alias=AliasChoices("print_tpls", "printTpls"))
|
|
1127
|
-
style_preset: str | None = Field(default=None, validation_alias=AliasChoices("style_preset", "stylePreset"))
|
|
1128
|
-
background_color: str | None = Field(default=None, validation_alias=AliasChoices("background_color", "backgroundColor"))
|
|
1129
|
-
text_color: str | None = Field(default=None, validation_alias=AliasChoices("text_color", "textColor"))
|
|
1130
|
-
button_icon: str | None = Field(default=None, validation_alias=AliasChoices("button_icon", "buttonIcon"))
|
|
1131
|
-
external_qrobot_config: dict[str, Any] | None = Field(
|
|
1132
|
-
default=None,
|
|
1133
|
-
validation_alias=AliasChoices(
|
|
1134
|
-
"external_qrobot_config",
|
|
1135
|
-
"externalQrobotConfig",
|
|
1136
|
-
"externalQRobotConfig",
|
|
1137
|
-
"custom_button_external_qrobot_relation_vo",
|
|
1138
|
-
"customButtonExternalQRobotRelationVO",
|
|
1139
|
-
),
|
|
1140
|
-
)
|
|
1141
|
-
trigger_wings_config: dict[str, Any] | None = Field(default=None, validation_alias=AliasChoices("trigger_wings_config", "triggerWingsConfig"))
|
|
1142
|
-
|
|
1143
|
-
@model_validator(mode="before")
|
|
1144
|
-
@classmethod
|
|
1145
|
-
def normalize_aliases(cls, value: Any) -> Any:
|
|
1146
|
-
if not isinstance(value, dict):
|
|
1147
|
-
return value
|
|
1148
|
-
payload = dict(value)
|
|
1149
|
-
raw_action = payload.get("action", payload.get("trigger_action", payload.get("triggerAction")))
|
|
1150
|
-
if isinstance(raw_action, str):
|
|
1151
|
-
normalized = raw_action.strip()
|
|
1152
|
-
aliases = {
|
|
1153
|
-
"add_data": PublicButtonTriggerAction.add_data.value,
|
|
1154
|
-
"add-data": PublicButtonTriggerAction.add_data.value,
|
|
1155
|
-
"adddata": PublicButtonTriggerAction.add_data.value,
|
|
1156
|
-
"addData": PublicButtonTriggerAction.add_data.value,
|
|
1157
|
-
"link": PublicButtonTriggerAction.link.value,
|
|
1158
|
-
"url": PublicButtonTriggerAction.link.value,
|
|
1159
|
-
"qrobot": PublicButtonTriggerAction.qrobot.value,
|
|
1160
|
-
"qRobot": PublicButtonTriggerAction.qrobot.value,
|
|
1161
|
-
"wings": PublicButtonTriggerAction.wings.value,
|
|
1162
|
-
}
|
|
1163
|
-
payload["action"] = aliases.get(normalized, aliases.get(normalized.lower(), normalized))
|
|
1164
|
-
raw_placement = payload.get("placement", payload.get("position", payload.get("config_type", payload.get("configType"))))
|
|
1165
|
-
if isinstance(raw_placement, str):
|
|
1166
|
-
normalized = raw_placement.strip().lower()
|
|
1167
|
-
if normalized in {"top", "header"}:
|
|
1168
|
-
payload["placement"] = PublicButtonPlacement.header.value
|
|
1169
|
-
elif normalized in {"detail", "data_detail"}:
|
|
1170
|
-
payload["placement"] = PublicButtonPlacement.detail.value
|
|
1171
|
-
elif normalized in {"list", "row", "row_action", "inside"}:
|
|
1172
|
-
payload["placement"] = PublicButtonPlacement.list.value
|
|
1173
|
-
raw_limits = payload.get("visible_when", payload.get("visibleWhen", payload.get("button_limit", payload.get("buttonLimit"))))
|
|
1174
|
-
if isinstance(raw_limits, list) and raw_limits and all(isinstance(item, dict) for item in raw_limits):
|
|
1175
|
-
payload["visible_when"] = [raw_limits]
|
|
1176
|
-
return payload
|
|
1177
|
-
|
|
1178
|
-
@model_validator(mode="after")
|
|
1179
|
-
def validate_shape(self) -> "ViewActionButtonPatch":
|
|
1180
|
-
if not str(self.text or "").strip():
|
|
1181
|
-
raise ValueError("action_buttons[].text is required")
|
|
1182
|
-
if self.action == PublicButtonTriggerAction.link and not str(self.url or "").strip():
|
|
1183
|
-
raise ValueError("link action_buttons require url")
|
|
1184
|
-
if self.action == PublicButtonTriggerAction.add_data and not str(self.target_app_key or "").strip():
|
|
1185
|
-
raise ValueError("add_data action_buttons require target_app_key")
|
|
1186
|
-
if self.action == PublicButtonTriggerAction.qrobot and self.external_qrobot_config is None:
|
|
1187
|
-
raise ValueError("qRobot action_buttons require external_qrobot_config")
|
|
1188
|
-
if self.action == PublicButtonTriggerAction.wings and self.trigger_wings_config is None:
|
|
1189
|
-
raise ValueError("wings action_buttons require trigger_wings_config")
|
|
1190
|
-
return self
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
1088
|
class ViewUpsertPatch(StrictModel):
|
|
1194
1089
|
name: str
|
|
1195
1090
|
view_key: str | None = Field(default=None, validation_alias=AliasChoices("view_key", "viewKey"))
|
|
@@ -1201,8 +1096,6 @@ class ViewUpsertPatch(StrictModel):
|
|
|
1201
1096
|
end_field: str | None = Field(default=None, validation_alias=AliasChoices("end_field", "endField"))
|
|
1202
1097
|
title_field: str | None = Field(default=None, validation_alias=AliasChoices("title_field", "titleField"))
|
|
1203
1098
|
buttons: list["ViewButtonBindingPatch"] | None = None
|
|
1204
|
-
action_buttons: list[ViewActionButtonPatch] | None = Field(default=None, validation_alias=AliasChoices("action_buttons", "actionButtons"))
|
|
1205
|
-
action_buttons_mode: str = Field(default="merge", validation_alias=AliasChoices("action_buttons_mode", "actionButtonsMode", "button_mode", "buttonMode"))
|
|
1206
1099
|
visibility: VisibilityPatch | None = None
|
|
1207
1100
|
query_conditions: ViewQueryConditionsPatch | None = Field(default=None, validation_alias=AliasChoices("query_conditions", "queryConditions", "query_condition", "queryCondition"))
|
|
1208
1101
|
associated_resources: ViewAssociatedResourcesPatch | None = Field(
|
|
@@ -1252,12 +1145,6 @@ class ViewUpsertPatch(StrictModel):
|
|
|
1252
1145
|
payload["associated_resources"] = payload.pop("associatedReports")
|
|
1253
1146
|
if "asosChartConfig" in payload and "associated_resources" not in payload:
|
|
1254
1147
|
payload["associated_resources"] = payload.pop("asosChartConfig")
|
|
1255
|
-
if "actionButtons" in payload and "action_buttons" not in payload:
|
|
1256
|
-
payload["action_buttons"] = payload.pop("actionButtons")
|
|
1257
|
-
if "actionButtonsMode" in payload and "action_buttons_mode" not in payload:
|
|
1258
|
-
payload["action_buttons_mode"] = payload.pop("actionButtonsMode")
|
|
1259
|
-
if "buttonMode" in payload and "action_buttons_mode" not in payload:
|
|
1260
|
-
payload["action_buttons_mode"] = payload.pop("buttonMode")
|
|
1261
1148
|
raw_type = payload.get("type")
|
|
1262
1149
|
if isinstance(raw_type, str):
|
|
1263
1150
|
normalized = raw_type.strip().lower()
|
|
@@ -1279,10 +1166,6 @@ class ViewUpsertPatch(StrictModel):
|
|
|
1279
1166
|
raise ValueError("board view requires group_by")
|
|
1280
1167
|
if self.type == PublicViewType.gantt and not (self.start_field and self.end_field):
|
|
1281
1168
|
raise ValueError("gantt view requires start_field and end_field")
|
|
1282
|
-
normalized_mode = str(self.action_buttons_mode or "merge").strip().lower()
|
|
1283
|
-
if normalized_mode not in {"merge", "replace"}:
|
|
1284
|
-
raise ValueError("action_buttons_mode must be merge or replace")
|
|
1285
|
-
self.action_buttons_mode = normalized_mode
|
|
1286
1169
|
return self
|
|
1287
1170
|
|
|
1288
1171
|
|
|
@@ -1308,76 +1191,6 @@ class ViewPartialPatch(StrictModel):
|
|
|
1308
1191
|
return self
|
|
1309
1192
|
|
|
1310
1193
|
|
|
1311
|
-
def public_button_trigger_action_value(value: Any) -> str:
|
|
1312
|
-
raw = value.value if isinstance(value, PublicButtonTriggerAction) else str(value or "").strip()
|
|
1313
|
-
normalized = raw.lower()
|
|
1314
|
-
if raw == PublicButtonTriggerAction.add_data.value or normalized in {"add_data", "add-data", "adddata"}:
|
|
1315
|
-
return "add_data"
|
|
1316
|
-
if normalized in {PublicButtonTriggerAction.link.value, "url"}:
|
|
1317
|
-
return "link"
|
|
1318
|
-
if raw == PublicButtonTriggerAction.qrobot.value or normalized == "qrobot":
|
|
1319
|
-
return "qRobot"
|
|
1320
|
-
if normalized == PublicButtonTriggerAction.wings.value:
|
|
1321
|
-
return "wings"
|
|
1322
|
-
return raw
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
def _drop_empty_public_payload_values(payload: dict[str, Any]) -> dict[str, Any]:
|
|
1326
|
-
compact: dict[str, Any] = {}
|
|
1327
|
-
for key, item in payload.items():
|
|
1328
|
-
if item is None or item == "" or item == [] or item == {}:
|
|
1329
|
-
continue
|
|
1330
|
-
if key == "primary" and item is False:
|
|
1331
|
-
continue
|
|
1332
|
-
compact[key] = item
|
|
1333
|
-
return compact
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
def public_view_action_button_payload(button: Any, *, compact: bool = False) -> dict[str, Any]:
|
|
1337
|
-
if isinstance(button, ViewActionButtonPatch):
|
|
1338
|
-
payload = button.model_dump(mode="json")
|
|
1339
|
-
elif isinstance(button, dict):
|
|
1340
|
-
payload = dict(button)
|
|
1341
|
-
else:
|
|
1342
|
-
return {}
|
|
1343
|
-
raw_action = payload.get("action", payload.get("trigger_action", payload.get("triggerAction")))
|
|
1344
|
-
if raw_action is not None:
|
|
1345
|
-
payload["action"] = public_button_trigger_action_value(raw_action)
|
|
1346
|
-
payload.pop("trigger_action", None)
|
|
1347
|
-
payload.pop("triggerAction", None)
|
|
1348
|
-
if compact:
|
|
1349
|
-
return _drop_empty_public_payload_values(payload)
|
|
1350
|
-
return payload
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
def public_view_upsert_payload(view: ViewUpsertPatch) -> dict[str, Any]:
|
|
1354
|
-
payload = view.model_dump(mode="json")
|
|
1355
|
-
if view.action_buttons is not None:
|
|
1356
|
-
payload["action_buttons"] = [
|
|
1357
|
-
public_view_action_button_payload(button, compact=True)
|
|
1358
|
-
for button in view.action_buttons
|
|
1359
|
-
]
|
|
1360
|
-
return payload
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
def public_view_partial_payload(patch: ViewPartialPatch) -> dict[str, Any]:
|
|
1364
|
-
payload = patch.model_dump(mode="json")
|
|
1365
|
-
patch_set = payload.get("set")
|
|
1366
|
-
if isinstance(patch_set, dict):
|
|
1367
|
-
raw_buttons = patch_set.get("action_buttons")
|
|
1368
|
-
if raw_buttons is None:
|
|
1369
|
-
raw_buttons = patch_set.get("actionButtons")
|
|
1370
|
-
if isinstance(raw_buttons, list):
|
|
1371
|
-
patch_set = dict(patch_set)
|
|
1372
|
-
patch_set.pop("actionButtons", None)
|
|
1373
|
-
patch_set["action_buttons"] = [
|
|
1374
|
-
public_view_action_button_payload(button, compact=True)
|
|
1375
|
-
for button in raw_buttons
|
|
1376
|
-
]
|
|
1377
|
-
payload["set"] = patch_set
|
|
1378
|
-
return payload
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
1194
|
class CustomButtonJudgeValuePatch(StrictModel):
|
|
1382
1195
|
id: int | str | None = None
|
|
1383
1196
|
value: Any | None = None
|
|
@@ -1446,30 +1259,11 @@ class CustomButtonFieldMappingPatch(StrictModel):
|
|
|
1446
1259
|
|
|
1447
1260
|
|
|
1448
1261
|
class FieldMatchMappingPatch(StrictModel):
|
|
1449
|
-
target_field: Any = Field(
|
|
1450
|
-
validation_alias=AliasChoices("target_field", "targetField", "target", "field", "field_name", "fieldName")
|
|
1451
|
-
)
|
|
1262
|
+
target_field: Any = Field(validation_alias=AliasChoices("target_field", "targetField", "target", "field"))
|
|
1452
1263
|
source_field: Any | None = Field(default=None, validation_alias=AliasChoices("source_field", "sourceField", "source"))
|
|
1453
1264
|
value: Any | None = Field(default=None, validation_alias=AliasChoices("value", "static_value", "staticValue"))
|
|
1454
1265
|
operator: str = Field(default="eq", validation_alias=AliasChoices("operator", "op", "judge_type", "judgeType"))
|
|
1455
1266
|
|
|
1456
|
-
@model_validator(mode="before")
|
|
1457
|
-
@classmethod
|
|
1458
|
-
def normalize_semantic_aliases(cls, value: Any) -> Any:
|
|
1459
|
-
if not isinstance(value, dict):
|
|
1460
|
-
return value
|
|
1461
|
-
payload = dict(value)
|
|
1462
|
-
has_static_value = any(key in payload for key in ("value", "static_value", "staticValue"))
|
|
1463
|
-
if "values" in payload:
|
|
1464
|
-
values = payload.pop("values")
|
|
1465
|
-
if has_static_value:
|
|
1466
|
-
return payload
|
|
1467
|
-
if isinstance(values, list) and len(values) == 1:
|
|
1468
|
-
payload["value"] = values[0]
|
|
1469
|
-
else:
|
|
1470
|
-
payload["value"] = values
|
|
1471
|
-
return payload
|
|
1472
|
-
|
|
1473
1267
|
@model_validator(mode="after")
|
|
1474
1268
|
def validate_shape(self) -> "FieldMatchMappingPatch":
|
|
1475
1269
|
has_source = self.source_field is not None and str(self.source_field).strip() != ""
|
|
@@ -1665,6 +1459,9 @@ class CustomButtonViewButtonBindingPatch(StrictModel):
|
|
|
1665
1459
|
raw_limits = payload.get("button_limit", payload.get("buttonLimit", payload.get("visible_when", payload.get("visibleWhen"))))
|
|
1666
1460
|
if isinstance(raw_limits, list) and raw_limits and all(isinstance(item, dict) for item in raw_limits):
|
|
1667
1461
|
payload["button_limit"] = [raw_limits]
|
|
1462
|
+
for alias in ("buttonLimit", "visible_when", "visibleWhen"):
|
|
1463
|
+
if alias in payload and alias != "button_limit":
|
|
1464
|
+
payload.pop(alias, None)
|
|
1668
1465
|
return payload
|
|
1669
1466
|
|
|
1670
1467
|
|
|
@@ -1816,7 +1613,6 @@ class AssociatedResourcesApplyRequest(StrictModel):
|
|
|
1816
1613
|
if not isinstance(value, dict):
|
|
1817
1614
|
return value
|
|
1818
1615
|
payload = dict(value)
|
|
1819
|
-
default_target_app_key = str(payload.get("app_key", payload.get("appKey", "")) or "").strip()
|
|
1820
1616
|
if "upsertResources" in payload and "upsert_resources" not in payload:
|
|
1821
1617
|
payload["upsert_resources"] = payload.pop("upsertResources")
|
|
1822
1618
|
if "patchResources" in payload and "patch_resources" not in payload:
|
|
@@ -1829,16 +1625,6 @@ class AssociatedResourcesApplyRequest(StrictModel):
|
|
|
1829
1625
|
payload["reorder_associated_item_ids"] = payload.pop("reorderAssociatedItemIds")
|
|
1830
1626
|
if "viewConfigs" in payload and "view_configs" not in payload:
|
|
1831
1627
|
payload["view_configs"] = payload.pop("viewConfigs")
|
|
1832
|
-
if default_target_app_key and isinstance(payload.get("upsert_resources"), list):
|
|
1833
|
-
normalized_resources = []
|
|
1834
|
-
for item in payload["upsert_resources"]:
|
|
1835
|
-
if isinstance(item, dict) and not any(
|
|
1836
|
-
str(item.get(key) or "").strip()
|
|
1837
|
-
for key in ("target_app_key", "targetAppKey", "app_key", "appKey")
|
|
1838
|
-
):
|
|
1839
|
-
item = {**item, "target_app_key": default_target_app_key}
|
|
1840
|
-
normalized_resources.append(item)
|
|
1841
|
-
payload["upsert_resources"] = normalized_resources
|
|
1842
1628
|
return payload
|
|
1843
1629
|
|
|
1844
1630
|
@model_validator(mode="after")
|
|
@@ -1862,7 +1648,7 @@ class ViewButtonBindingPatch(StrictModel):
|
|
|
1862
1648
|
being_main: bool = Field(default=False, validation_alias=AliasChoices("being_main", "beingMain"))
|
|
1863
1649
|
button_limit: list[list[ViewFilterRulePatch]] = Field(
|
|
1864
1650
|
default_factory=list,
|
|
1865
|
-
validation_alias=AliasChoices("button_limit", "buttonLimit"),
|
|
1651
|
+
validation_alias=AliasChoices("button_limit", "buttonLimit", "visible_when", "visibleWhen"),
|
|
1866
1652
|
)
|
|
1867
1653
|
button_formula: str | None = Field(default=None, validation_alias=AliasChoices("button_formula", "buttonFormula"))
|
|
1868
1654
|
button_formula_type: int = Field(default=1, validation_alias=AliasChoices("button_formula_type", "buttonFormulaType"))
|
|
@@ -1891,9 +1677,12 @@ class ViewButtonBindingPatch(StrictModel):
|
|
|
1891
1677
|
payload["config_type"] = "DETAIL"
|
|
1892
1678
|
elif normalized_config in {"inside", "list", "row", "row_action"}:
|
|
1893
1679
|
payload["config_type"] = "INSIDE"
|
|
1894
|
-
raw_limits = payload.get("button_limit", payload.get("buttonLimit"))
|
|
1680
|
+
raw_limits = payload.get("button_limit", payload.get("buttonLimit", payload.get("visible_when", payload.get("visibleWhen"))))
|
|
1895
1681
|
if isinstance(raw_limits, list) and raw_limits and all(isinstance(item, dict) for item in raw_limits):
|
|
1896
1682
|
payload["button_limit"] = [raw_limits]
|
|
1683
|
+
for alias in ("buttonLimit", "visible_when", "visibleWhen"):
|
|
1684
|
+
if alias in payload and alias != "button_limit":
|
|
1685
|
+
payload.pop(alias, None)
|
|
1897
1686
|
return payload
|
|
1898
1687
|
|
|
1899
1688
|
@model_validator(mode="after")
|
|
@@ -2260,22 +2049,6 @@ class ChartApplyRequest(StrictModel):
|
|
|
2260
2049
|
return self
|
|
2261
2050
|
|
|
2262
2051
|
|
|
2263
|
-
class PortalInlineChartPatch(ChartUpsertPatch):
|
|
2264
|
-
app_key: str = Field(validation_alias=AliasChoices("app_key", "appKey"))
|
|
2265
|
-
|
|
2266
|
-
@model_validator(mode="before")
|
|
2267
|
-
@classmethod
|
|
2268
|
-
def normalize_inline_aliases(cls, value: Any) -> Any:
|
|
2269
|
-
if not isinstance(value, dict):
|
|
2270
|
-
return value
|
|
2271
|
-
payload = dict(value)
|
|
2272
|
-
if "chart_name" in payload and "name" not in payload:
|
|
2273
|
-
payload["name"] = payload.pop("chart_name")
|
|
2274
|
-
if "chartName" in payload and "name" not in payload:
|
|
2275
|
-
payload["name"] = payload.pop("chartName")
|
|
2276
|
-
return payload
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
2052
|
class PortalComponentPositionPatch(StrictModel):
|
|
2280
2053
|
pc_x: int = Field(default=0, validation_alias=AliasChoices("pc_x", "pcX", "x"))
|
|
2281
2054
|
pc_y: int = Field(default=0, validation_alias=AliasChoices("pc_y", "pcY", "y"))
|
|
@@ -2322,14 +2095,33 @@ class PortalComponentPositionPatch(StrictModel):
|
|
|
2322
2095
|
class PortalChartRefPatch(StrictModel):
|
|
2323
2096
|
app_key: str | None = None
|
|
2324
2097
|
chart_id: str | None = None
|
|
2098
|
+
chart_key: str | None = None
|
|
2325
2099
|
chart_name: str | None = None
|
|
2326
2100
|
|
|
2101
|
+
@model_validator(mode="before")
|
|
2102
|
+
@classmethod
|
|
2103
|
+
def normalize_aliases(cls, value: Any) -> Any:
|
|
2104
|
+
if not isinstance(value, dict):
|
|
2105
|
+
return value
|
|
2106
|
+
payload = dict(value)
|
|
2107
|
+
if "chartKey" in payload and "chart_key" not in payload:
|
|
2108
|
+
payload["chart_key"] = payload.pop("chartKey")
|
|
2109
|
+
if "chartId" in payload and "chart_id" not in payload:
|
|
2110
|
+
payload["chart_id"] = payload.pop("chartId")
|
|
2111
|
+
if "biChartId" in payload and "chart_id" not in payload:
|
|
2112
|
+
payload["chart_id"] = payload.pop("biChartId")
|
|
2113
|
+
if "chartName" in payload and "chart_name" not in payload:
|
|
2114
|
+
payload["chart_name"] = payload.pop("chartName")
|
|
2115
|
+
if "appKey" in payload and "app_key" not in payload:
|
|
2116
|
+
payload["app_key"] = payload.pop("appKey")
|
|
2117
|
+
return payload
|
|
2118
|
+
|
|
2327
2119
|
@model_validator(mode="after")
|
|
2328
2120
|
def validate_target(self) -> "PortalChartRefPatch":
|
|
2329
|
-
if not (self.chart_id or self.chart_name):
|
|
2330
|
-
raise ValueError("chart_ref requires chart_id or chart_name")
|
|
2331
|
-
if self.chart_name and not self.
|
|
2332
|
-
raise ValueError("chart_ref
|
|
2121
|
+
if not (self.chart_id or self.chart_key or self.chart_name):
|
|
2122
|
+
raise ValueError("chart_ref requires chart_id, chart_key, or chart_name")
|
|
2123
|
+
if self.chart_name and not self.app_key and not (self.chart_id or self.chart_key):
|
|
2124
|
+
raise ValueError("chart_ref with chart_name requires app_key unless chart_id/chart_key is also provided")
|
|
2333
2125
|
return self
|
|
2334
2126
|
|
|
2335
2127
|
|
|
@@ -2355,12 +2147,12 @@ class PortalViewRefPatch(StrictModel):
|
|
|
2355
2147
|
class PortalSectionPatch(StrictModel):
|
|
2356
2148
|
title: str
|
|
2357
2149
|
source_type: str = Field(validation_alias=AliasChoices("source_type", "sourceType"))
|
|
2358
|
-
role: str | None =
|
|
2150
|
+
role: str | None = None
|
|
2359
2151
|
position: PortalComponentPositionPatch | None = None
|
|
2360
2152
|
dash_style_config: dict[str, Any] | None = Field(default=None, validation_alias=AliasChoices("dash_style_config", "dashStyleConfigBO"))
|
|
2361
2153
|
config: dict[str, Any] = Field(default_factory=dict)
|
|
2154
|
+
chart: dict[str, Any] | None = None
|
|
2362
2155
|
chart_ref: PortalChartRefPatch | None = None
|
|
2363
|
-
chart: PortalInlineChartPatch | None = Field(default=None, validation_alias=AliasChoices("chart", "inline_chart", "inlineChart", "create_chart", "createChart"))
|
|
2364
2156
|
view_ref: PortalViewRefPatch | None = None
|
|
2365
2157
|
text: str | None = None
|
|
2366
2158
|
url: str | None = None
|
|
@@ -2374,19 +2166,8 @@ class PortalSectionPatch(StrictModel):
|
|
|
2374
2166
|
raw_type = payload.get("source_type", payload.get("sourceType"))
|
|
2375
2167
|
if isinstance(raw_type, str):
|
|
2376
2168
|
payload["source_type"] = raw_type.strip().lower()
|
|
2377
|
-
raw_role = payload.get("role", payload.get("zone", payload.get("section_role", payload.get("sectionRole"))))
|
|
2378
|
-
if isinstance(raw_role, str):
|
|
2379
|
-
payload["role"] = raw_role.strip().lower()
|
|
2380
2169
|
if "chartRef" in payload and "chart_ref" not in payload:
|
|
2381
2170
|
payload["chart_ref"] = payload.pop("chartRef")
|
|
2382
|
-
if "inlineChart" in payload and "chart" not in payload:
|
|
2383
|
-
payload["chart"] = payload.pop("inlineChart")
|
|
2384
|
-
if "inline_chart" in payload and "chart" not in payload:
|
|
2385
|
-
payload["chart"] = payload.pop("inline_chart")
|
|
2386
|
-
if "createChart" in payload and "chart" not in payload:
|
|
2387
|
-
payload["chart"] = payload.pop("createChart")
|
|
2388
|
-
if "create_chart" in payload and "chart" not in payload:
|
|
2389
|
-
payload["chart"] = payload.pop("create_chart")
|
|
2390
2171
|
if "viewRef" in payload and "view_ref" not in payload:
|
|
2391
2172
|
payload["view_ref"] = payload.pop("viewRef")
|
|
2392
2173
|
if "dashStyleConfigBO" in payload and "dash_style_config" not in payload:
|
|
@@ -2398,8 +2179,18 @@ class PortalSectionPatch(StrictModel):
|
|
|
2398
2179
|
supported = {"chart", "view", "grid", "filter", "text", "link"}
|
|
2399
2180
|
if self.source_type not in supported:
|
|
2400
2181
|
raise ValueError(f"unsupported portal source_type '{self.source_type}'")
|
|
2401
|
-
if self.
|
|
2402
|
-
|
|
2182
|
+
if self.role is not None:
|
|
2183
|
+
normalized_role = str(self.role or "").strip().lower()
|
|
2184
|
+
if normalized_role not in {"metric"}:
|
|
2185
|
+
raise ValueError(f"unsupported portal section role '{self.role}'")
|
|
2186
|
+
self.role = normalized_role
|
|
2187
|
+
if self.source_type != "chart":
|
|
2188
|
+
raise ValueError("portal section role is only supported for chart sections")
|
|
2189
|
+
if self.source_type == "chart":
|
|
2190
|
+
if self.chart is not None and self.chart_ref is not None:
|
|
2191
|
+
raise ValueError("chart section accepts either chart or chart_ref, not both")
|
|
2192
|
+
if self.chart is None and self.chart_ref is None:
|
|
2193
|
+
raise ValueError("chart section requires chart or chart_ref")
|
|
2403
2194
|
if self.source_type == "view" and self.view_ref is None:
|
|
2404
2195
|
raise ValueError("view section requires view_ref")
|
|
2405
2196
|
if self.source_type == "text" and self.text is None:
|
|
@@ -2536,7 +2327,6 @@ AppReadSummaryResponse = AppGetResponse
|
|
|
2536
2327
|
AppFieldsReadResponse = AppGetFieldsResponse
|
|
2537
2328
|
AppLayoutReadResponse = AppGetLayoutResponse
|
|
2538
2329
|
AppViewsReadResponse = AppGetViewsResponse
|
|
2539
|
-
AppFlowReadResponse = AppGetFlowResponse
|
|
2540
2330
|
AppChartsReadResponse = AppGetChartsResponse
|
|
2541
2331
|
|
|
2542
2332
|
|
|
@@ -2603,6 +2393,7 @@ class SchemaPlanRequest(StrictModel):
|
|
|
2603
2393
|
icon: str | None = None
|
|
2604
2394
|
color: str | None = None
|
|
2605
2395
|
visibility: VisibilityPatch | None = None
|
|
2396
|
+
create_if_missing: bool = False
|
|
2606
2397
|
add_fields: list[FieldPatch] = Field(default_factory=list)
|
|
2607
2398
|
update_fields: list[FieldUpdatePatch] = Field(default_factory=list)
|
|
2608
2399
|
remove_fields: list[FieldRemovePatch] = Field(default_factory=list)
|
|
@@ -2625,40 +2416,6 @@ class LayoutPlanRequest(StrictModel):
|
|
|
2625
2416
|
return payload
|
|
2626
2417
|
|
|
2627
2418
|
|
|
2628
|
-
class FlowPlanRequest(StrictModel):
|
|
2629
|
-
app_key: str
|
|
2630
|
-
mode: str = "replace"
|
|
2631
|
-
nodes: list[FlowNodePatch] = Field(default_factory=list)
|
|
2632
|
-
transitions: list[FlowTransitionPatch] = Field(default_factory=list)
|
|
2633
|
-
preset: FlowPreset | None = None
|
|
2634
|
-
|
|
2635
|
-
@model_validator(mode="before")
|
|
2636
|
-
@classmethod
|
|
2637
|
-
def normalize_mode_alias(cls, value: Any) -> Any:
|
|
2638
|
-
if not isinstance(value, dict):
|
|
2639
|
-
return value
|
|
2640
|
-
payload = dict(value)
|
|
2641
|
-
if str(payload.get("mode") or "").strip().lower() == "overwrite":
|
|
2642
|
-
payload["mode"] = "replace"
|
|
2643
|
-
raw_preset = payload.get("preset")
|
|
2644
|
-
if raw_preset is None and isinstance(payload.get("base_preset"), str):
|
|
2645
|
-
raw_preset = payload["base_preset"]
|
|
2646
|
-
payload["preset"] = raw_preset
|
|
2647
|
-
if isinstance(raw_preset, str):
|
|
2648
|
-
normalized_preset = raw_preset.strip().lower()
|
|
2649
|
-
preset_aliases = {
|
|
2650
|
-
"default_approval": FlowPreset.basic_approval.value,
|
|
2651
|
-
"approval": FlowPreset.basic_approval.value,
|
|
2652
|
-
"basic approval": FlowPreset.basic_approval.value,
|
|
2653
|
-
"default_fill_then_approve": FlowPreset.basic_fill_then_approve.value,
|
|
2654
|
-
"default-fill-then-approve": FlowPreset.basic_fill_then_approve.value,
|
|
2655
|
-
"fill_then_approve": FlowPreset.basic_fill_then_approve.value,
|
|
2656
|
-
}
|
|
2657
|
-
if normalized_preset in preset_aliases:
|
|
2658
|
-
payload["preset"] = preset_aliases[normalized_preset]
|
|
2659
|
-
return payload
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
2419
|
class ViewsPlanRequest(StrictModel):
|
|
2663
2420
|
app_key: str
|
|
2664
2421
|
upsert_views: list[ViewUpsertPatch] = Field(default_factory=list)
|
|
@@ -2686,18 +2443,8 @@ def _normalize_field_payload(value: Any) -> Any:
|
|
|
2686
2443
|
if not isinstance(value, dict):
|
|
2687
2444
|
return value
|
|
2688
2445
|
payload = dict(value)
|
|
2689
|
-
if "data_title" in payload and "as_data_title" not in payload and "asDataTitle" not in payload:
|
|
2690
|
-
payload["as_data_title"] = payload.pop("data_title")
|
|
2691
|
-
if "dataTitle" in payload and "as_data_title" not in payload and "asDataTitle" not in payload:
|
|
2692
|
-
payload["as_data_title"] = payload.pop("dataTitle")
|
|
2693
|
-
if "data_cover" in payload and "as_data_cover" not in payload and "asDataCover" not in payload:
|
|
2694
|
-
payload["as_data_cover"] = payload.pop("data_cover")
|
|
2695
|
-
if "dataCover" in payload and "as_data_cover" not in payload and "asDataCover" not in payload:
|
|
2696
|
-
payload["as_data_cover"] = payload.pop("dataCover")
|
|
2697
2446
|
if "fields" in payload and "subfields" not in payload:
|
|
2698
2447
|
payload["subfields"] = payload.pop("fields")
|
|
2699
|
-
if "options" in payload:
|
|
2700
|
-
payload["options"] = _normalize_field_options(payload.get("options"))
|
|
2701
2448
|
raw_type = payload.get("type")
|
|
2702
2449
|
if isinstance(raw_type, int):
|
|
2703
2450
|
normalized_from_id = FIELD_TYPE_ID_ALIASES.get(raw_type)
|
|
@@ -2730,34 +2477,6 @@ def _normalize_field_payload(value: Any) -> Any:
|
|
|
2730
2477
|
return payload
|
|
2731
2478
|
|
|
2732
2479
|
|
|
2733
|
-
def _normalize_field_options(value: Any) -> Any:
|
|
2734
|
-
if not isinstance(value, list):
|
|
2735
|
-
return value
|
|
2736
|
-
normalized: list[str] = []
|
|
2737
|
-
for item in value:
|
|
2738
|
-
if isinstance(item, str):
|
|
2739
|
-
normalized.append(item)
|
|
2740
|
-
continue
|
|
2741
|
-
if isinstance(item, (int, float, bool)):
|
|
2742
|
-
normalized.append(str(item))
|
|
2743
|
-
continue
|
|
2744
|
-
if isinstance(item, dict):
|
|
2745
|
-
label = (
|
|
2746
|
-
item.get("label")
|
|
2747
|
-
or item.get("name")
|
|
2748
|
-
or item.get("title")
|
|
2749
|
-
or item.get("value")
|
|
2750
|
-
or item.get("text")
|
|
2751
|
-
or item.get("optValue")
|
|
2752
|
-
or item.get("optName")
|
|
2753
|
-
)
|
|
2754
|
-
if label is not None:
|
|
2755
|
-
normalized.append(str(label))
|
|
2756
|
-
continue
|
|
2757
|
-
normalized.append(str(item))
|
|
2758
|
-
return normalized
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
2480
|
def _slugify_title(title: str) -> str:
|
|
2762
2481
|
normalized = "".join(ch.lower() if ch.isalnum() else "_" for ch in str(title or ""))
|
|
2763
2482
|
collapsed = "_".join(part for part in normalized.split("_") if part)
|