@qingflow-tech/qingflow-app-user-mcp 1.0.7 → 1.0.8
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/package.json +1 -1
- package/pyproject.toml +1 -1
- package/skills/qingflow-app-user/references/public-surface-sync.md +3 -0
- package/src/qingflow_mcp/builder_facade/models.py +171 -9
- package/src/qingflow_mcp/builder_facade/service.py +2030 -83
- package/src/qingflow_mcp/cli/commands/builder.py +8 -0
- package/src/qingflow_mcp/cli/commands/record.py +55 -1
- package/src/qingflow_mcp/public_surface.py +2 -2
- package/src/qingflow_mcp/response_trim.py +14 -0
- package/src/qingflow_mcp/server.py +1 -0
- package/src/qingflow_mcp/server_app_builder.py +12 -2
- package/src/qingflow_mcp/server_app_user.py +1 -0
- package/src/qingflow_mcp/tools/ai_builder_tools.py +189 -7
package/README.md
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
Install:
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npm install @qingflow-tech/qingflow-app-user-mcp@1.0.
|
|
6
|
+
npm install @qingflow-tech/qingflow-app-user-mcp@1.0.8
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
Run:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npx -y -p @qingflow-tech/qingflow-app-user-mcp@1.0.
|
|
12
|
+
npx -y -p @qingflow-tech/qingflow-app-user-mcp@1.0.8 qingflow-app-user-mcp
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Environment:
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -39,11 +39,14 @@ It is not a user-facing product spec. It exists to prevent skill drift.
|
|
|
39
39
|
- `app_layout_apply`
|
|
40
40
|
- `app_flow_apply`
|
|
41
41
|
- `app_views_apply`
|
|
42
|
+
- `app_custom_buttons_apply`
|
|
43
|
+
- `app_associated_resources_apply`
|
|
42
44
|
- `app_charts_apply`
|
|
43
45
|
- `portal_apply`
|
|
44
46
|
- `app_publish_verify`
|
|
45
47
|
- `portal_apply` edit mode may omit `sections` for base-info-only updates
|
|
46
48
|
- `app_charts_apply.visibility` is a public capability and should be treated as a base-only visibility update
|
|
49
|
+
- Existing object parameter replacement should use `patch_views`, `patch_buttons`, `patch_resources`, and `patch_charts`; `upsert_*` is for creation or full target config
|
|
47
50
|
- `app_get.editability` uses:
|
|
48
51
|
- `can_edit_app_base`
|
|
49
52
|
- `can_edit_form`
|
|
@@ -9,6 +9,22 @@ from .button_style_catalog import resolve_button_style
|
|
|
9
9
|
from ..solution.spec_models import StrictModel
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
def _normalize_builder_view_key_value(value: Any) -> Any:
|
|
13
|
+
if not isinstance(value, str):
|
|
14
|
+
return value
|
|
15
|
+
raw = value.strip()
|
|
16
|
+
if raw.startswith("custom:"):
|
|
17
|
+
return raw.split(":", 1)[1].strip()
|
|
18
|
+
return raw
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _normalize_builder_view_key_payload(payload: dict[str, Any]) -> dict[str, Any]:
|
|
22
|
+
for key in ("view_key", "viewKey", "viewgraphKey", "viewGraphKey"):
|
|
23
|
+
if key in payload:
|
|
24
|
+
payload[key] = _normalize_builder_view_key_value(payload[key])
|
|
25
|
+
return payload
|
|
26
|
+
|
|
27
|
+
|
|
12
28
|
class PublicFieldType(str, Enum):
|
|
13
29
|
text = "text"
|
|
14
30
|
long_text = "long_text"
|
|
@@ -115,7 +131,7 @@ class PublicViewButtonType(str, Enum):
|
|
|
115
131
|
class PublicViewButtonConfigType(str, Enum):
|
|
116
132
|
top = "TOP"
|
|
117
133
|
detail = "DETAIL"
|
|
118
|
-
list = "
|
|
134
|
+
list = "INSIDE"
|
|
119
135
|
|
|
120
136
|
|
|
121
137
|
class PublicButtonPlacement(str, Enum):
|
|
@@ -1068,6 +1084,11 @@ class ViewUpsertPatch(StrictModel):
|
|
|
1068
1084
|
"asosChartConfig",
|
|
1069
1085
|
),
|
|
1070
1086
|
)
|
|
1087
|
+
partial_update: bool = Field(default=False, exclude=True, validation_alias=AliasChoices("_partial_update", "partial_update", "partialUpdate"))
|
|
1088
|
+
preserve_filters: bool = Field(default=False, exclude=True, validation_alias=AliasChoices("_preserve_filters", "preserve_filters", "preserveFilters"))
|
|
1089
|
+
preserve_buttons: bool = Field(default=False, exclude=True, validation_alias=AliasChoices("_preserve_buttons", "preserve_buttons", "preserveButtons"))
|
|
1090
|
+
preserve_query_conditions: bool = Field(default=False, exclude=True, validation_alias=AliasChoices("_preserve_query_conditions", "preserve_query_conditions", "preserveQueryConditions"))
|
|
1091
|
+
preserve_associated_resources: bool = Field(default=False, exclude=True, validation_alias=AliasChoices("_preserve_associated_resources", "preserve_associated_resources", "preserveAssociatedResources"))
|
|
1071
1092
|
|
|
1072
1093
|
@model_validator(mode="before")
|
|
1073
1094
|
@classmethod
|
|
@@ -1075,6 +1096,7 @@ class ViewUpsertPatch(StrictModel):
|
|
|
1075
1096
|
if not isinstance(value, dict):
|
|
1076
1097
|
return value
|
|
1077
1098
|
payload = dict(value)
|
|
1099
|
+
payload = _normalize_builder_view_key_payload(payload)
|
|
1078
1100
|
if "fields" in payload and "columns" not in payload:
|
|
1079
1101
|
payload["columns"] = payload.pop("fields")
|
|
1080
1102
|
if "column_names" in payload and "columns" not in payload:
|
|
@@ -1123,6 +1145,28 @@ class ViewUpsertPatch(StrictModel):
|
|
|
1123
1145
|
return self
|
|
1124
1146
|
|
|
1125
1147
|
|
|
1148
|
+
class ViewPartialPatch(StrictModel):
|
|
1149
|
+
view_key: str | None = Field(default=None, validation_alias=AliasChoices("view_key", "viewKey", "viewgraphKey", "viewGraphKey"))
|
|
1150
|
+
name: str | None = Field(default=None, validation_alias=AliasChoices("name", "view_name", "viewName"))
|
|
1151
|
+
set: dict[str, Any] = Field(default_factory=dict, validation_alias=AliasChoices("set", "update", "values"))
|
|
1152
|
+
unset: list[str] = Field(default_factory=list, validation_alias=AliasChoices("unset", "clear", "remove"))
|
|
1153
|
+
|
|
1154
|
+
@model_validator(mode="before")
|
|
1155
|
+
@classmethod
|
|
1156
|
+
def normalize_aliases(cls, value: Any) -> Any:
|
|
1157
|
+
if not isinstance(value, dict):
|
|
1158
|
+
return value
|
|
1159
|
+
return _normalize_builder_view_key_payload(dict(value))
|
|
1160
|
+
|
|
1161
|
+
@model_validator(mode="after")
|
|
1162
|
+
def validate_shape(self) -> "ViewPartialPatch":
|
|
1163
|
+
if not str(self.view_key or "").strip() and not str(self.name or "").strip():
|
|
1164
|
+
raise ValueError("patch_views[] requires view_key or unique name")
|
|
1165
|
+
if not self.set and not self.unset:
|
|
1166
|
+
raise ValueError("patch_views[] requires set or unset")
|
|
1167
|
+
return self
|
|
1168
|
+
|
|
1169
|
+
|
|
1126
1170
|
class CustomButtonJudgeValuePatch(StrictModel):
|
|
1127
1171
|
id: int | str | None = None
|
|
1128
1172
|
value: Any | None = None
|
|
@@ -1190,6 +1234,21 @@ class CustomButtonFieldMappingPatch(StrictModel):
|
|
|
1190
1234
|
target_field: Any = Field(validation_alias=AliasChoices("target_field", "targetField", "target"))
|
|
1191
1235
|
|
|
1192
1236
|
|
|
1237
|
+
class FieldMatchMappingPatch(StrictModel):
|
|
1238
|
+
target_field: Any = Field(validation_alias=AliasChoices("target_field", "targetField", "target", "field"))
|
|
1239
|
+
source_field: Any | None = Field(default=None, validation_alias=AliasChoices("source_field", "sourceField", "source"))
|
|
1240
|
+
value: Any | None = Field(default=None, validation_alias=AliasChoices("value", "static_value", "staticValue"))
|
|
1241
|
+
operator: str = Field(default="eq", validation_alias=AliasChoices("operator", "op", "judge_type", "judgeType"))
|
|
1242
|
+
|
|
1243
|
+
@model_validator(mode="after")
|
|
1244
|
+
def validate_shape(self) -> "FieldMatchMappingPatch":
|
|
1245
|
+
has_source = self.source_field is not None and str(self.source_field).strip() != ""
|
|
1246
|
+
has_value = self.value is not None
|
|
1247
|
+
if has_source == has_value:
|
|
1248
|
+
raise ValueError("match_mappings[] requires exactly one of source_field or value")
|
|
1249
|
+
return self
|
|
1250
|
+
|
|
1251
|
+
|
|
1193
1252
|
class CustomButtonAddDataConfigPatch(StrictModel):
|
|
1194
1253
|
related_app_key: str | None = Field(
|
|
1195
1254
|
default=None,
|
|
@@ -1319,6 +1378,22 @@ class CustomButtonUpsertPatch(CustomButtonPatch):
|
|
|
1319
1378
|
client_key: str | None = Field(default=None, validation_alias=AliasChoices("client_key", "clientKey"))
|
|
1320
1379
|
|
|
1321
1380
|
|
|
1381
|
+
class CustomButtonPartialPatch(StrictModel):
|
|
1382
|
+
button_id: int | None = Field(default=None, validation_alias=AliasChoices("button_id", "buttonId", "id"))
|
|
1383
|
+
button_text: str | None = Field(default=None, validation_alias=AliasChoices("button_text", "buttonText", "name"))
|
|
1384
|
+
client_key: str | None = Field(default=None, validation_alias=AliasChoices("client_key", "clientKey"))
|
|
1385
|
+
set: dict[str, Any] = Field(default_factory=dict, validation_alias=AliasChoices("set", "update", "values"))
|
|
1386
|
+
unset: list[str] = Field(default_factory=list, validation_alias=AliasChoices("unset", "clear", "remove"))
|
|
1387
|
+
|
|
1388
|
+
@model_validator(mode="after")
|
|
1389
|
+
def validate_shape(self) -> "CustomButtonPartialPatch":
|
|
1390
|
+
if self.button_id is None and not str(self.button_text or "").strip():
|
|
1391
|
+
raise ValueError("patch_buttons[] requires button_id or unique button_text")
|
|
1392
|
+
if not self.set and not self.unset:
|
|
1393
|
+
raise ValueError("patch_buttons[] requires set or unset")
|
|
1394
|
+
return self
|
|
1395
|
+
|
|
1396
|
+
|
|
1322
1397
|
class CustomButtonRemovePatch(StrictModel):
|
|
1323
1398
|
button_id: int | None = Field(default=None, validation_alias=AliasChoices("button_id", "buttonId", "id"))
|
|
1324
1399
|
button_text: str | None = Field(default=None, validation_alias=AliasChoices("button_text", "buttonText", "name"))
|
|
@@ -1368,6 +1443,13 @@ class CustomButtonViewConfigPatch(StrictModel):
|
|
|
1368
1443
|
mode: str = Field(default="merge", validation_alias=AliasChoices("mode", "apply_mode", "applyMode"))
|
|
1369
1444
|
buttons: list[CustomButtonViewButtonBindingPatch] = Field(default_factory=list)
|
|
1370
1445
|
|
|
1446
|
+
@model_validator(mode="before")
|
|
1447
|
+
@classmethod
|
|
1448
|
+
def normalize_aliases(cls, value: Any) -> Any:
|
|
1449
|
+
if not isinstance(value, dict):
|
|
1450
|
+
return value
|
|
1451
|
+
return _normalize_builder_view_key_payload(dict(value))
|
|
1452
|
+
|
|
1371
1453
|
@model_validator(mode="after")
|
|
1372
1454
|
def validate_mode(self) -> "CustomButtonViewConfigPatch":
|
|
1373
1455
|
normalized = str(self.mode or "").strip().lower()
|
|
@@ -1382,6 +1464,7 @@ class CustomButtonViewConfigPatch(StrictModel):
|
|
|
1382
1464
|
class CustomButtonsApplyRequest(StrictModel):
|
|
1383
1465
|
app_key: str
|
|
1384
1466
|
upsert_buttons: list[CustomButtonUpsertPatch] = Field(default_factory=list)
|
|
1467
|
+
patch_buttons: list[CustomButtonPartialPatch] = Field(default_factory=list)
|
|
1385
1468
|
remove_buttons: list[CustomButtonRemovePatch] = Field(default_factory=list)
|
|
1386
1469
|
view_configs: list[CustomButtonViewConfigPatch] = Field(default_factory=list)
|
|
1387
1470
|
|
|
@@ -1395,6 +1478,8 @@ class CustomButtonsApplyRequest(StrictModel):
|
|
|
1395
1478
|
payload["upsert_buttons"] = payload.pop("buttons")
|
|
1396
1479
|
if "upsertButtons" in payload and "upsert_buttons" not in payload:
|
|
1397
1480
|
payload["upsert_buttons"] = payload.pop("upsertButtons")
|
|
1481
|
+
if "patchButtons" in payload and "patch_buttons" not in payload:
|
|
1482
|
+
payload["patch_buttons"] = payload.pop("patchButtons")
|
|
1398
1483
|
if "removeButtons" in payload and "remove_buttons" not in payload:
|
|
1399
1484
|
payload["remove_buttons"] = payload.pop("removeButtons")
|
|
1400
1485
|
if "viewConfigs" in payload and "view_configs" not in payload:
|
|
@@ -1403,8 +1488,8 @@ class CustomButtonsApplyRequest(StrictModel):
|
|
|
1403
1488
|
|
|
1404
1489
|
@model_validator(mode="after")
|
|
1405
1490
|
def validate_shape(self) -> "CustomButtonsApplyRequest":
|
|
1406
|
-
if not self.upsert_buttons and not self.remove_buttons and not self.view_configs:
|
|
1407
|
-
raise ValueError("custom button apply requires at least one upsert, remove, or view config operation")
|
|
1491
|
+
if not self.upsert_buttons and not self.patch_buttons and not self.remove_buttons and not self.view_configs:
|
|
1492
|
+
raise ValueError("custom button apply requires at least one upsert, patch, remove, or view config operation")
|
|
1408
1493
|
return self
|
|
1409
1494
|
|
|
1410
1495
|
|
|
@@ -1421,6 +1506,10 @@ class AssociatedResourceUpsertPatch(StrictModel):
|
|
|
1421
1506
|
report_source: str | None = Field(default=None, validation_alias=AliasChoices("report_source", "reportSource"))
|
|
1422
1507
|
source_type: str | None = Field(default=None, validation_alias=AliasChoices("source_type", "sourceType"))
|
|
1423
1508
|
match_rules: list[CustomButtonMatchRulePatch] = Field(default_factory=list, validation_alias=AliasChoices("match_rules", "matchRules"))
|
|
1509
|
+
match_mappings: list[FieldMatchMappingPatch] = Field(
|
|
1510
|
+
default_factory=list,
|
|
1511
|
+
validation_alias=AliasChoices("match_mappings", "matchMappings"),
|
|
1512
|
+
)
|
|
1424
1513
|
|
|
1425
1514
|
@model_validator(mode="before")
|
|
1426
1515
|
@classmethod
|
|
@@ -1428,6 +1517,7 @@ class AssociatedResourceUpsertPatch(StrictModel):
|
|
|
1428
1517
|
if not isinstance(value, dict):
|
|
1429
1518
|
return value
|
|
1430
1519
|
payload = dict(value)
|
|
1520
|
+
payload = _normalize_builder_view_key_payload(payload)
|
|
1431
1521
|
if "chart_id" in payload and "chart_key" not in payload and "chartKey" not in payload:
|
|
1432
1522
|
payload["chart_key"] = str(payload.pop("chart_id"))
|
|
1433
1523
|
raw_graph_type = str(payload.get("graph_type", payload.get("graphType", "")) or "").strip().lower()
|
|
@@ -1449,6 +1539,21 @@ class AssociatedResourceUpsertPatch(StrictModel):
|
|
|
1449
1539
|
return payload
|
|
1450
1540
|
|
|
1451
1541
|
|
|
1542
|
+
class AssociatedResourcePartialPatch(StrictModel):
|
|
1543
|
+
associated_item_id: int = Field(validation_alias=AliasChoices("associated_item_id", "associatedItemId", "asosChartId", "id"))
|
|
1544
|
+
client_key: str | None = Field(default=None, validation_alias=AliasChoices("client_key", "clientKey"))
|
|
1545
|
+
set: dict[str, Any] = Field(default_factory=dict, validation_alias=AliasChoices("set", "update", "values"))
|
|
1546
|
+
unset: list[str] = Field(default_factory=list, validation_alias=AliasChoices("unset", "clear", "remove"))
|
|
1547
|
+
|
|
1548
|
+
@model_validator(mode="after")
|
|
1549
|
+
def validate_shape(self) -> "AssociatedResourcePartialPatch":
|
|
1550
|
+
if self.associated_item_id <= 0:
|
|
1551
|
+
raise ValueError("patch_resources[].associated_item_id must be a positive integer")
|
|
1552
|
+
if not self.set and not self.unset:
|
|
1553
|
+
raise ValueError("patch_resources[] requires set or unset")
|
|
1554
|
+
return self
|
|
1555
|
+
|
|
1556
|
+
|
|
1452
1557
|
class AssociatedResourceViewConfigPatch(StrictModel):
|
|
1453
1558
|
view_key: str = Field(validation_alias=AliasChoices("view_key", "viewKey", "viewgraphKey", "viewGraphKey"))
|
|
1454
1559
|
visible: bool = Field(default=True, validation_alias=AliasChoices("visible", "enabled", "asosChartVisible"))
|
|
@@ -1459,10 +1564,18 @@ class AssociatedResourceViewConfigPatch(StrictModel):
|
|
|
1459
1564
|
)
|
|
1460
1565
|
associated_item_refs: list[str] = Field(default_factory=list, validation_alias=AliasChoices("associated_item_refs", "associatedItemRefs", "refs"))
|
|
1461
1566
|
|
|
1567
|
+
@model_validator(mode="before")
|
|
1568
|
+
@classmethod
|
|
1569
|
+
def normalize_aliases(cls, value: Any) -> Any:
|
|
1570
|
+
if not isinstance(value, dict):
|
|
1571
|
+
return value
|
|
1572
|
+
return _normalize_builder_view_key_payload(dict(value))
|
|
1573
|
+
|
|
1462
1574
|
|
|
1463
1575
|
class AssociatedResourcesApplyRequest(StrictModel):
|
|
1464
1576
|
app_key: str
|
|
1465
1577
|
upsert_resources: list[AssociatedResourceUpsertPatch] = Field(default_factory=list)
|
|
1578
|
+
patch_resources: list[AssociatedResourcePartialPatch] = Field(default_factory=list)
|
|
1466
1579
|
remove_associated_item_ids: list[int] = Field(default_factory=list)
|
|
1467
1580
|
reorder_associated_item_ids: list[int] = Field(default_factory=list)
|
|
1468
1581
|
view_configs: list[AssociatedResourceViewConfigPatch] = Field(default_factory=list)
|
|
@@ -1475,6 +1588,8 @@ class AssociatedResourcesApplyRequest(StrictModel):
|
|
|
1475
1588
|
payload = dict(value)
|
|
1476
1589
|
if "upsertResources" in payload and "upsert_resources" not in payload:
|
|
1477
1590
|
payload["upsert_resources"] = payload.pop("upsertResources")
|
|
1591
|
+
if "patchResources" in payload and "patch_resources" not in payload:
|
|
1592
|
+
payload["patch_resources"] = payload.pop("patchResources")
|
|
1478
1593
|
if "resources" in payload and "upsert_resources" not in payload:
|
|
1479
1594
|
payload["upsert_resources"] = payload.pop("resources")
|
|
1480
1595
|
if "removeAssociatedItemIds" in payload and "remove_associated_item_ids" not in payload:
|
|
@@ -1487,8 +1602,8 @@ class AssociatedResourcesApplyRequest(StrictModel):
|
|
|
1487
1602
|
|
|
1488
1603
|
@model_validator(mode="after")
|
|
1489
1604
|
def validate_shape(self) -> "AssociatedResourcesApplyRequest":
|
|
1490
|
-
if not self.upsert_resources and not self.remove_associated_item_ids and not self.reorder_associated_item_ids and not self.view_configs:
|
|
1491
|
-
raise ValueError("associated resources apply requires at least one
|
|
1605
|
+
if not self.upsert_resources and not self.patch_resources and not self.remove_associated_item_ids and not self.reorder_associated_item_ids and not self.view_configs:
|
|
1606
|
+
raise ValueError("associated resources apply requires at least one upsert, patch, remove, reorder, or view config operation")
|
|
1492
1607
|
return self
|
|
1493
1608
|
|
|
1494
1609
|
|
|
@@ -1533,8 +1648,8 @@ class ViewButtonBindingPatch(StrictModel):
|
|
|
1533
1648
|
payload["config_type"] = "TOP"
|
|
1534
1649
|
elif normalized_config == "detail":
|
|
1535
1650
|
payload["config_type"] = "DETAIL"
|
|
1536
|
-
elif normalized_config in {"list", "row", "row_action"}:
|
|
1537
|
-
payload["config_type"] = "
|
|
1651
|
+
elif normalized_config in {"inside", "list", "row", "row_action"}:
|
|
1652
|
+
payload["config_type"] = "INSIDE"
|
|
1538
1653
|
raw_limits = payload.get("button_limit", payload.get("buttonLimit"))
|
|
1539
1654
|
if isinstance(raw_limits, list) and raw_limits and all(isinstance(item, dict) for item in raw_limits):
|
|
1540
1655
|
payload["button_limit"] = [raw_limits]
|
|
@@ -1676,9 +1791,39 @@ class ChartUpsertPatch(StrictModel):
|
|
|
1676
1791
|
return payload
|
|
1677
1792
|
|
|
1678
1793
|
|
|
1794
|
+
class ChartPartialPatch(StrictModel):
|
|
1795
|
+
chart_id: str | None = None
|
|
1796
|
+
name: str | None = None
|
|
1797
|
+
set: dict[str, Any] = Field(default_factory=dict, validation_alias=AliasChoices("set", "update", "values"))
|
|
1798
|
+
unset: list[str] = Field(default_factory=list, validation_alias=AliasChoices("unset", "clear", "remove"))
|
|
1799
|
+
|
|
1800
|
+
@model_validator(mode="before")
|
|
1801
|
+
@classmethod
|
|
1802
|
+
def normalize_aliases(cls, value: Any) -> Any:
|
|
1803
|
+
if not isinstance(value, dict):
|
|
1804
|
+
return value
|
|
1805
|
+
payload = dict(value)
|
|
1806
|
+
if "id" in payload and "chart_id" not in payload:
|
|
1807
|
+
payload["chart_id"] = str(payload.pop("id"))
|
|
1808
|
+
if "chart_name" in payload and "name" not in payload:
|
|
1809
|
+
payload["name"] = payload.pop("chart_name")
|
|
1810
|
+
if "chartName" in payload and "name" not in payload:
|
|
1811
|
+
payload["name"] = payload.pop("chartName")
|
|
1812
|
+
return payload
|
|
1813
|
+
|
|
1814
|
+
@model_validator(mode="after")
|
|
1815
|
+
def validate_shape(self) -> "ChartPartialPatch":
|
|
1816
|
+
if not str(self.chart_id or "").strip() and not str(self.name or "").strip():
|
|
1817
|
+
raise ValueError("patch_charts[] requires chart_id or unique name")
|
|
1818
|
+
if not self.set and not self.unset:
|
|
1819
|
+
raise ValueError("patch_charts[] requires set or unset")
|
|
1820
|
+
return self
|
|
1821
|
+
|
|
1822
|
+
|
|
1679
1823
|
class ChartApplyRequest(StrictModel):
|
|
1680
1824
|
app_key: str
|
|
1681
1825
|
upsert_charts: list[ChartUpsertPatch] = Field(default_factory=list)
|
|
1826
|
+
patch_charts: list[ChartPartialPatch] = Field(default_factory=list)
|
|
1682
1827
|
remove_chart_ids: list[str] = Field(default_factory=list)
|
|
1683
1828
|
reorder_chart_ids: list[str] = Field(default_factory=list)
|
|
1684
1829
|
|
|
@@ -1688,6 +1833,14 @@ class ChartApplyRequest(StrictModel):
|
|
|
1688
1833
|
if not isinstance(value, dict):
|
|
1689
1834
|
return value
|
|
1690
1835
|
payload = dict(value)
|
|
1836
|
+
if "upsertCharts" in payload and "upsert_charts" not in payload:
|
|
1837
|
+
payload["upsert_charts"] = payload.pop("upsertCharts")
|
|
1838
|
+
if "patchCharts" in payload and "patch_charts" not in payload:
|
|
1839
|
+
payload["patch_charts"] = payload.pop("patchCharts")
|
|
1840
|
+
if "removeChartIds" in payload and "remove_chart_ids" not in payload:
|
|
1841
|
+
payload["remove_chart_ids"] = payload.pop("removeChartIds")
|
|
1842
|
+
if "reorderChartIds" in payload and "reorder_chart_ids" not in payload:
|
|
1843
|
+
payload["reorder_chart_ids"] = payload.pop("reorderChartIds")
|
|
1691
1844
|
for key in ("remove_chart_ids", "reorder_chart_ids"):
|
|
1692
1845
|
raw = payload.get(key)
|
|
1693
1846
|
if isinstance(raw, list):
|
|
@@ -1696,8 +1849,8 @@ class ChartApplyRequest(StrictModel):
|
|
|
1696
1849
|
|
|
1697
1850
|
@model_validator(mode="after")
|
|
1698
1851
|
def validate_shape(self) -> "ChartApplyRequest":
|
|
1699
|
-
if not self.upsert_charts and not self.remove_chart_ids and not self.reorder_chart_ids:
|
|
1700
|
-
raise ValueError("chart apply requires at least one upsert, remove, or reorder operation")
|
|
1852
|
+
if not self.upsert_charts and not self.patch_charts and not self.remove_chart_ids and not self.reorder_chart_ids:
|
|
1853
|
+
raise ValueError("chart apply requires at least one upsert, patch, remove, or reorder operation")
|
|
1701
1854
|
return self
|
|
1702
1855
|
|
|
1703
1856
|
|
|
@@ -1757,6 +1910,13 @@ class PortalViewRefPatch(StrictModel):
|
|
|
1757
1910
|
view_key: str | None = None
|
|
1758
1911
|
view_name: str | None = None
|
|
1759
1912
|
|
|
1913
|
+
@model_validator(mode="before")
|
|
1914
|
+
@classmethod
|
|
1915
|
+
def normalize_aliases(cls, value: Any) -> Any:
|
|
1916
|
+
if not isinstance(value, dict):
|
|
1917
|
+
return value
|
|
1918
|
+
return _normalize_builder_view_key_payload(dict(value))
|
|
1919
|
+
|
|
1760
1920
|
@model_validator(mode="after")
|
|
1761
1921
|
def validate_target(self) -> "PortalViewRefPatch":
|
|
1762
1922
|
if not (self.view_key or self.view_name):
|
|
@@ -1944,6 +2104,7 @@ class ViewGetResponse(StrictModel):
|
|
|
1944
2104
|
config: dict[str, Any] = Field(default_factory=dict)
|
|
1945
2105
|
questions: list[dict[str, Any]] = Field(default_factory=list)
|
|
1946
2106
|
associations: list[dict[str, Any]] = Field(default_factory=list)
|
|
2107
|
+
buttons_config: dict[str, Any] = Field(default_factory=dict)
|
|
1947
2108
|
associated_resources_config: dict[str, Any] = Field(default_factory=dict)
|
|
1948
2109
|
|
|
1949
2110
|
|
|
@@ -2021,6 +2182,7 @@ class FlowPlanRequest(StrictModel):
|
|
|
2021
2182
|
class ViewsPlanRequest(StrictModel):
|
|
2022
2183
|
app_key: str
|
|
2023
2184
|
upsert_views: list[ViewUpsertPatch] = Field(default_factory=list)
|
|
2185
|
+
patch_views: list[ViewPartialPatch] = Field(default_factory=list)
|
|
2024
2186
|
remove_views: list[str] = Field(default_factory=list)
|
|
2025
2187
|
preset: ViewsPreset | None = None
|
|
2026
2188
|
|