@qingflow-tech/qingflow-app-user-mcp 1.0.7 → 1.0.9
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 +205 -11
- package/src/qingflow_mcp/builder_facade/service.py +2303 -159
- 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 +13 -2
- package/src/qingflow_mcp/server_app_user.py +1 -0
- package/src/qingflow_mcp/tools/ai_builder_tools.py +199 -10
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.9
|
|
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.9 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):
|
|
@@ -126,10 +142,34 @@ class PublicButtonPlacement(str, Enum):
|
|
|
126
142
|
|
|
127
143
|
class PublicChartType(str, Enum):
|
|
128
144
|
target = "target"
|
|
145
|
+
indicator = "indicator"
|
|
146
|
+
summary = "summary"
|
|
129
147
|
pie = "pie"
|
|
130
148
|
bar = "bar"
|
|
149
|
+
columnar = "columnar"
|
|
131
150
|
line = "line"
|
|
132
151
|
table = "table"
|
|
152
|
+
detail = "detail"
|
|
153
|
+
area = "area"
|
|
154
|
+
stacked_area = "stacked_area"
|
|
155
|
+
pct_stack_area = "pct_stack_area"
|
|
156
|
+
funnel = "funnel"
|
|
157
|
+
waterfall = "waterfall"
|
|
158
|
+
gauge = "gauge"
|
|
159
|
+
heatmap = "heatmap"
|
|
160
|
+
histogram = "histogram"
|
|
161
|
+
treemap = "treemap"
|
|
162
|
+
radar = "radar"
|
|
163
|
+
stacked_bar = "stacked_bar"
|
|
164
|
+
pct_stack_bar = "pct_stack_bar"
|
|
165
|
+
stacked_column = "stacked_column"
|
|
166
|
+
pct_stack_col = "pct_stack_col"
|
|
167
|
+
scatter = "scatter"
|
|
168
|
+
ring = "ring"
|
|
169
|
+
rose = "rose"
|
|
170
|
+
dualaxes = "dualaxes"
|
|
171
|
+
map = "map"
|
|
172
|
+
timeline = "timeline"
|
|
133
173
|
|
|
134
174
|
|
|
135
175
|
class LayoutApplyMode(str, Enum):
|
|
@@ -1068,6 +1108,11 @@ class ViewUpsertPatch(StrictModel):
|
|
|
1068
1108
|
"asosChartConfig",
|
|
1069
1109
|
),
|
|
1070
1110
|
)
|
|
1111
|
+
partial_update: bool = Field(default=False, exclude=True, validation_alias=AliasChoices("_partial_update", "partial_update", "partialUpdate"))
|
|
1112
|
+
preserve_filters: bool = Field(default=False, exclude=True, validation_alias=AliasChoices("_preserve_filters", "preserve_filters", "preserveFilters"))
|
|
1113
|
+
preserve_buttons: bool = Field(default=False, exclude=True, validation_alias=AliasChoices("_preserve_buttons", "preserve_buttons", "preserveButtons"))
|
|
1114
|
+
preserve_query_conditions: bool = Field(default=False, exclude=True, validation_alias=AliasChoices("_preserve_query_conditions", "preserve_query_conditions", "preserveQueryConditions"))
|
|
1115
|
+
preserve_associated_resources: bool = Field(default=False, exclude=True, validation_alias=AliasChoices("_preserve_associated_resources", "preserve_associated_resources", "preserveAssociatedResources"))
|
|
1071
1116
|
|
|
1072
1117
|
@model_validator(mode="before")
|
|
1073
1118
|
@classmethod
|
|
@@ -1075,6 +1120,7 @@ class ViewUpsertPatch(StrictModel):
|
|
|
1075
1120
|
if not isinstance(value, dict):
|
|
1076
1121
|
return value
|
|
1077
1122
|
payload = dict(value)
|
|
1123
|
+
payload = _normalize_builder_view_key_payload(payload)
|
|
1078
1124
|
if "fields" in payload and "columns" not in payload:
|
|
1079
1125
|
payload["columns"] = payload.pop("fields")
|
|
1080
1126
|
if "column_names" in payload and "columns" not in payload:
|
|
@@ -1123,6 +1169,28 @@ class ViewUpsertPatch(StrictModel):
|
|
|
1123
1169
|
return self
|
|
1124
1170
|
|
|
1125
1171
|
|
|
1172
|
+
class ViewPartialPatch(StrictModel):
|
|
1173
|
+
view_key: str | None = Field(default=None, validation_alias=AliasChoices("view_key", "viewKey", "viewgraphKey", "viewGraphKey"))
|
|
1174
|
+
name: str | None = Field(default=None, validation_alias=AliasChoices("name", "view_name", "viewName"))
|
|
1175
|
+
set: dict[str, Any] = Field(default_factory=dict, validation_alias=AliasChoices("set", "update", "values"))
|
|
1176
|
+
unset: list[str] = Field(default_factory=list, validation_alias=AliasChoices("unset", "clear", "remove"))
|
|
1177
|
+
|
|
1178
|
+
@model_validator(mode="before")
|
|
1179
|
+
@classmethod
|
|
1180
|
+
def normalize_aliases(cls, value: Any) -> Any:
|
|
1181
|
+
if not isinstance(value, dict):
|
|
1182
|
+
return value
|
|
1183
|
+
return _normalize_builder_view_key_payload(dict(value))
|
|
1184
|
+
|
|
1185
|
+
@model_validator(mode="after")
|
|
1186
|
+
def validate_shape(self) -> "ViewPartialPatch":
|
|
1187
|
+
if not str(self.view_key or "").strip() and not str(self.name or "").strip():
|
|
1188
|
+
raise ValueError("patch_views[] requires view_key or unique name")
|
|
1189
|
+
if not self.set and not self.unset:
|
|
1190
|
+
raise ValueError("patch_views[] requires set or unset")
|
|
1191
|
+
return self
|
|
1192
|
+
|
|
1193
|
+
|
|
1126
1194
|
class CustomButtonJudgeValuePatch(StrictModel):
|
|
1127
1195
|
id: int | str | None = None
|
|
1128
1196
|
value: Any | None = None
|
|
@@ -1190,6 +1258,21 @@ class CustomButtonFieldMappingPatch(StrictModel):
|
|
|
1190
1258
|
target_field: Any = Field(validation_alias=AliasChoices("target_field", "targetField", "target"))
|
|
1191
1259
|
|
|
1192
1260
|
|
|
1261
|
+
class FieldMatchMappingPatch(StrictModel):
|
|
1262
|
+
target_field: Any = Field(validation_alias=AliasChoices("target_field", "targetField", "target", "field"))
|
|
1263
|
+
source_field: Any | None = Field(default=None, validation_alias=AliasChoices("source_field", "sourceField", "source"))
|
|
1264
|
+
value: Any | None = Field(default=None, validation_alias=AliasChoices("value", "static_value", "staticValue"))
|
|
1265
|
+
operator: str = Field(default="eq", validation_alias=AliasChoices("operator", "op", "judge_type", "judgeType"))
|
|
1266
|
+
|
|
1267
|
+
@model_validator(mode="after")
|
|
1268
|
+
def validate_shape(self) -> "FieldMatchMappingPatch":
|
|
1269
|
+
has_source = self.source_field is not None and str(self.source_field).strip() != ""
|
|
1270
|
+
has_value = self.value is not None
|
|
1271
|
+
if has_source == has_value:
|
|
1272
|
+
raise ValueError("match_mappings[] requires exactly one of source_field or value")
|
|
1273
|
+
return self
|
|
1274
|
+
|
|
1275
|
+
|
|
1193
1276
|
class CustomButtonAddDataConfigPatch(StrictModel):
|
|
1194
1277
|
related_app_key: str | None = Field(
|
|
1195
1278
|
default=None,
|
|
@@ -1319,6 +1402,22 @@ class CustomButtonUpsertPatch(CustomButtonPatch):
|
|
|
1319
1402
|
client_key: str | None = Field(default=None, validation_alias=AliasChoices("client_key", "clientKey"))
|
|
1320
1403
|
|
|
1321
1404
|
|
|
1405
|
+
class CustomButtonPartialPatch(StrictModel):
|
|
1406
|
+
button_id: int | None = Field(default=None, validation_alias=AliasChoices("button_id", "buttonId", "id"))
|
|
1407
|
+
button_text: str | None = Field(default=None, validation_alias=AliasChoices("button_text", "buttonText", "name"))
|
|
1408
|
+
client_key: str | None = Field(default=None, validation_alias=AliasChoices("client_key", "clientKey"))
|
|
1409
|
+
set: dict[str, Any] = Field(default_factory=dict, validation_alias=AliasChoices("set", "update", "values"))
|
|
1410
|
+
unset: list[str] = Field(default_factory=list, validation_alias=AliasChoices("unset", "clear", "remove"))
|
|
1411
|
+
|
|
1412
|
+
@model_validator(mode="after")
|
|
1413
|
+
def validate_shape(self) -> "CustomButtonPartialPatch":
|
|
1414
|
+
if self.button_id is None and not str(self.button_text or "").strip():
|
|
1415
|
+
raise ValueError("patch_buttons[] requires button_id or unique button_text")
|
|
1416
|
+
if not self.set and not self.unset:
|
|
1417
|
+
raise ValueError("patch_buttons[] requires set or unset")
|
|
1418
|
+
return self
|
|
1419
|
+
|
|
1420
|
+
|
|
1322
1421
|
class CustomButtonRemovePatch(StrictModel):
|
|
1323
1422
|
button_id: int | None = Field(default=None, validation_alias=AliasChoices("button_id", "buttonId", "id"))
|
|
1324
1423
|
button_text: str | None = Field(default=None, validation_alias=AliasChoices("button_text", "buttonText", "name"))
|
|
@@ -1368,6 +1467,13 @@ class CustomButtonViewConfigPatch(StrictModel):
|
|
|
1368
1467
|
mode: str = Field(default="merge", validation_alias=AliasChoices("mode", "apply_mode", "applyMode"))
|
|
1369
1468
|
buttons: list[CustomButtonViewButtonBindingPatch] = Field(default_factory=list)
|
|
1370
1469
|
|
|
1470
|
+
@model_validator(mode="before")
|
|
1471
|
+
@classmethod
|
|
1472
|
+
def normalize_aliases(cls, value: Any) -> Any:
|
|
1473
|
+
if not isinstance(value, dict):
|
|
1474
|
+
return value
|
|
1475
|
+
return _normalize_builder_view_key_payload(dict(value))
|
|
1476
|
+
|
|
1371
1477
|
@model_validator(mode="after")
|
|
1372
1478
|
def validate_mode(self) -> "CustomButtonViewConfigPatch":
|
|
1373
1479
|
normalized = str(self.mode or "").strip().lower()
|
|
@@ -1382,6 +1488,7 @@ class CustomButtonViewConfigPatch(StrictModel):
|
|
|
1382
1488
|
class CustomButtonsApplyRequest(StrictModel):
|
|
1383
1489
|
app_key: str
|
|
1384
1490
|
upsert_buttons: list[CustomButtonUpsertPatch] = Field(default_factory=list)
|
|
1491
|
+
patch_buttons: list[CustomButtonPartialPatch] = Field(default_factory=list)
|
|
1385
1492
|
remove_buttons: list[CustomButtonRemovePatch] = Field(default_factory=list)
|
|
1386
1493
|
view_configs: list[CustomButtonViewConfigPatch] = Field(default_factory=list)
|
|
1387
1494
|
|
|
@@ -1395,6 +1502,8 @@ class CustomButtonsApplyRequest(StrictModel):
|
|
|
1395
1502
|
payload["upsert_buttons"] = payload.pop("buttons")
|
|
1396
1503
|
if "upsertButtons" in payload and "upsert_buttons" not in payload:
|
|
1397
1504
|
payload["upsert_buttons"] = payload.pop("upsertButtons")
|
|
1505
|
+
if "patchButtons" in payload and "patch_buttons" not in payload:
|
|
1506
|
+
payload["patch_buttons"] = payload.pop("patchButtons")
|
|
1398
1507
|
if "removeButtons" in payload and "remove_buttons" not in payload:
|
|
1399
1508
|
payload["remove_buttons"] = payload.pop("removeButtons")
|
|
1400
1509
|
if "viewConfigs" in payload and "view_configs" not in payload:
|
|
@@ -1403,8 +1512,8 @@ class CustomButtonsApplyRequest(StrictModel):
|
|
|
1403
1512
|
|
|
1404
1513
|
@model_validator(mode="after")
|
|
1405
1514
|
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")
|
|
1515
|
+
if not self.upsert_buttons and not self.patch_buttons and not self.remove_buttons and not self.view_configs:
|
|
1516
|
+
raise ValueError("custom button apply requires at least one upsert, patch, remove, or view config operation")
|
|
1408
1517
|
return self
|
|
1409
1518
|
|
|
1410
1519
|
|
|
@@ -1421,6 +1530,10 @@ class AssociatedResourceUpsertPatch(StrictModel):
|
|
|
1421
1530
|
report_source: str | None = Field(default=None, validation_alias=AliasChoices("report_source", "reportSource"))
|
|
1422
1531
|
source_type: str | None = Field(default=None, validation_alias=AliasChoices("source_type", "sourceType"))
|
|
1423
1532
|
match_rules: list[CustomButtonMatchRulePatch] = Field(default_factory=list, validation_alias=AliasChoices("match_rules", "matchRules"))
|
|
1533
|
+
match_mappings: list[FieldMatchMappingPatch] = Field(
|
|
1534
|
+
default_factory=list,
|
|
1535
|
+
validation_alias=AliasChoices("match_mappings", "matchMappings"),
|
|
1536
|
+
)
|
|
1424
1537
|
|
|
1425
1538
|
@model_validator(mode="before")
|
|
1426
1539
|
@classmethod
|
|
@@ -1428,6 +1541,7 @@ class AssociatedResourceUpsertPatch(StrictModel):
|
|
|
1428
1541
|
if not isinstance(value, dict):
|
|
1429
1542
|
return value
|
|
1430
1543
|
payload = dict(value)
|
|
1544
|
+
payload = _normalize_builder_view_key_payload(payload)
|
|
1431
1545
|
if "chart_id" in payload and "chart_key" not in payload and "chartKey" not in payload:
|
|
1432
1546
|
payload["chart_key"] = str(payload.pop("chart_id"))
|
|
1433
1547
|
raw_graph_type = str(payload.get("graph_type", payload.get("graphType", "")) or "").strip().lower()
|
|
@@ -1449,6 +1563,21 @@ class AssociatedResourceUpsertPatch(StrictModel):
|
|
|
1449
1563
|
return payload
|
|
1450
1564
|
|
|
1451
1565
|
|
|
1566
|
+
class AssociatedResourcePartialPatch(StrictModel):
|
|
1567
|
+
associated_item_id: int = Field(validation_alias=AliasChoices("associated_item_id", "associatedItemId", "asosChartId", "id"))
|
|
1568
|
+
client_key: str | None = Field(default=None, validation_alias=AliasChoices("client_key", "clientKey"))
|
|
1569
|
+
set: dict[str, Any] = Field(default_factory=dict, validation_alias=AliasChoices("set", "update", "values"))
|
|
1570
|
+
unset: list[str] = Field(default_factory=list, validation_alias=AliasChoices("unset", "clear", "remove"))
|
|
1571
|
+
|
|
1572
|
+
@model_validator(mode="after")
|
|
1573
|
+
def validate_shape(self) -> "AssociatedResourcePartialPatch":
|
|
1574
|
+
if self.associated_item_id <= 0:
|
|
1575
|
+
raise ValueError("patch_resources[].associated_item_id must be a positive integer")
|
|
1576
|
+
if not self.set and not self.unset:
|
|
1577
|
+
raise ValueError("patch_resources[] requires set or unset")
|
|
1578
|
+
return self
|
|
1579
|
+
|
|
1580
|
+
|
|
1452
1581
|
class AssociatedResourceViewConfigPatch(StrictModel):
|
|
1453
1582
|
view_key: str = Field(validation_alias=AliasChoices("view_key", "viewKey", "viewgraphKey", "viewGraphKey"))
|
|
1454
1583
|
visible: bool = Field(default=True, validation_alias=AliasChoices("visible", "enabled", "asosChartVisible"))
|
|
@@ -1459,12 +1588,20 @@ class AssociatedResourceViewConfigPatch(StrictModel):
|
|
|
1459
1588
|
)
|
|
1460
1589
|
associated_item_refs: list[str] = Field(default_factory=list, validation_alias=AliasChoices("associated_item_refs", "associatedItemRefs", "refs"))
|
|
1461
1590
|
|
|
1591
|
+
@model_validator(mode="before")
|
|
1592
|
+
@classmethod
|
|
1593
|
+
def normalize_aliases(cls, value: Any) -> Any:
|
|
1594
|
+
if not isinstance(value, dict):
|
|
1595
|
+
return value
|
|
1596
|
+
return _normalize_builder_view_key_payload(dict(value))
|
|
1597
|
+
|
|
1462
1598
|
|
|
1463
1599
|
class AssociatedResourcesApplyRequest(StrictModel):
|
|
1464
1600
|
app_key: str
|
|
1465
1601
|
upsert_resources: list[AssociatedResourceUpsertPatch] = Field(default_factory=list)
|
|
1466
|
-
|
|
1467
|
-
|
|
1602
|
+
patch_resources: list[AssociatedResourcePartialPatch] = Field(default_factory=list)
|
|
1603
|
+
remove_associated_item_ids: list[Any] = Field(default_factory=list)
|
|
1604
|
+
reorder_associated_item_ids: list[Any] = Field(default_factory=list)
|
|
1468
1605
|
view_configs: list[AssociatedResourceViewConfigPatch] = Field(default_factory=list)
|
|
1469
1606
|
|
|
1470
1607
|
@model_validator(mode="before")
|
|
@@ -1475,6 +1612,8 @@ class AssociatedResourcesApplyRequest(StrictModel):
|
|
|
1475
1612
|
payload = dict(value)
|
|
1476
1613
|
if "upsertResources" in payload and "upsert_resources" not in payload:
|
|
1477
1614
|
payload["upsert_resources"] = payload.pop("upsertResources")
|
|
1615
|
+
if "patchResources" in payload and "patch_resources" not in payload:
|
|
1616
|
+
payload["patch_resources"] = payload.pop("patchResources")
|
|
1478
1617
|
if "resources" in payload and "upsert_resources" not in payload:
|
|
1479
1618
|
payload["upsert_resources"] = payload.pop("resources")
|
|
1480
1619
|
if "removeAssociatedItemIds" in payload and "remove_associated_item_ids" not in payload:
|
|
@@ -1487,8 +1626,8 @@ class AssociatedResourcesApplyRequest(StrictModel):
|
|
|
1487
1626
|
|
|
1488
1627
|
@model_validator(mode="after")
|
|
1489
1628
|
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
|
|
1629
|
+
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:
|
|
1630
|
+
raise ValueError("associated resources apply requires at least one upsert, patch, remove, reorder, or view config operation")
|
|
1492
1631
|
return self
|
|
1493
1632
|
|
|
1494
1633
|
|
|
@@ -1533,8 +1672,8 @@ class ViewButtonBindingPatch(StrictModel):
|
|
|
1533
1672
|
payload["config_type"] = "TOP"
|
|
1534
1673
|
elif normalized_config == "detail":
|
|
1535
1674
|
payload["config_type"] = "DETAIL"
|
|
1536
|
-
elif normalized_config in {"list", "row", "row_action"}:
|
|
1537
|
-
payload["config_type"] = "
|
|
1675
|
+
elif normalized_config in {"inside", "list", "row", "row_action"}:
|
|
1676
|
+
payload["config_type"] = "INSIDE"
|
|
1538
1677
|
raw_limits = payload.get("button_limit", payload.get("buttonLimit"))
|
|
1539
1678
|
if isinstance(raw_limits, list) and raw_limits and all(isinstance(item, dict) for item in raw_limits):
|
|
1540
1679
|
payload["button_limit"] = [raw_limits]
|
|
@@ -1660,10 +1799,18 @@ class ChartUpsertPatch(StrictModel):
|
|
|
1660
1799
|
normalized = raw_type.strip().lower()
|
|
1661
1800
|
aliases = {
|
|
1662
1801
|
"targetchart": PublicChartType.target.value,
|
|
1802
|
+
"indicatorchart": PublicChartType.indicator.value,
|
|
1803
|
+
"summarychart": PublicChartType.summary.value,
|
|
1663
1804
|
"piechart": PublicChartType.pie.value,
|
|
1664
1805
|
"barchart": PublicChartType.bar.value,
|
|
1806
|
+
"columnchart": PublicChartType.columnar.value,
|
|
1807
|
+
"columnarchart": PublicChartType.columnar.value,
|
|
1665
1808
|
"linechart": PublicChartType.line.value,
|
|
1666
1809
|
"tablechart": PublicChartType.table.value,
|
|
1810
|
+
"detailchart": PublicChartType.detail.value,
|
|
1811
|
+
"percent_stacked_column": PublicChartType.pct_stack_col.value,
|
|
1812
|
+
"percent_stacked_bar": PublicChartType.pct_stack_bar.value,
|
|
1813
|
+
"percent_stacked_area": PublicChartType.pct_stack_area.value,
|
|
1667
1814
|
}
|
|
1668
1815
|
if normalized in aliases:
|
|
1669
1816
|
payload["chart_type"] = aliases[normalized]
|
|
@@ -1676,9 +1823,39 @@ class ChartUpsertPatch(StrictModel):
|
|
|
1676
1823
|
return payload
|
|
1677
1824
|
|
|
1678
1825
|
|
|
1826
|
+
class ChartPartialPatch(StrictModel):
|
|
1827
|
+
chart_id: str | None = None
|
|
1828
|
+
name: str | None = None
|
|
1829
|
+
set: dict[str, Any] = Field(default_factory=dict, validation_alias=AliasChoices("set", "update", "values"))
|
|
1830
|
+
unset: list[str] = Field(default_factory=list, validation_alias=AliasChoices("unset", "clear", "remove"))
|
|
1831
|
+
|
|
1832
|
+
@model_validator(mode="before")
|
|
1833
|
+
@classmethod
|
|
1834
|
+
def normalize_aliases(cls, value: Any) -> Any:
|
|
1835
|
+
if not isinstance(value, dict):
|
|
1836
|
+
return value
|
|
1837
|
+
payload = dict(value)
|
|
1838
|
+
if "id" in payload and "chart_id" not in payload:
|
|
1839
|
+
payload["chart_id"] = str(payload.pop("id"))
|
|
1840
|
+
if "chart_name" in payload and "name" not in payload:
|
|
1841
|
+
payload["name"] = payload.pop("chart_name")
|
|
1842
|
+
if "chartName" in payload and "name" not in payload:
|
|
1843
|
+
payload["name"] = payload.pop("chartName")
|
|
1844
|
+
return payload
|
|
1845
|
+
|
|
1846
|
+
@model_validator(mode="after")
|
|
1847
|
+
def validate_shape(self) -> "ChartPartialPatch":
|
|
1848
|
+
if not str(self.chart_id or "").strip() and not str(self.name or "").strip():
|
|
1849
|
+
raise ValueError("patch_charts[] requires chart_id or unique name")
|
|
1850
|
+
if not self.set and not self.unset:
|
|
1851
|
+
raise ValueError("patch_charts[] requires set or unset")
|
|
1852
|
+
return self
|
|
1853
|
+
|
|
1854
|
+
|
|
1679
1855
|
class ChartApplyRequest(StrictModel):
|
|
1680
1856
|
app_key: str
|
|
1681
1857
|
upsert_charts: list[ChartUpsertPatch] = Field(default_factory=list)
|
|
1858
|
+
patch_charts: list[ChartPartialPatch] = Field(default_factory=list)
|
|
1682
1859
|
remove_chart_ids: list[str] = Field(default_factory=list)
|
|
1683
1860
|
reorder_chart_ids: list[str] = Field(default_factory=list)
|
|
1684
1861
|
|
|
@@ -1688,6 +1865,14 @@ class ChartApplyRequest(StrictModel):
|
|
|
1688
1865
|
if not isinstance(value, dict):
|
|
1689
1866
|
return value
|
|
1690
1867
|
payload = dict(value)
|
|
1868
|
+
if "upsertCharts" in payload and "upsert_charts" not in payload:
|
|
1869
|
+
payload["upsert_charts"] = payload.pop("upsertCharts")
|
|
1870
|
+
if "patchCharts" in payload and "patch_charts" not in payload:
|
|
1871
|
+
payload["patch_charts"] = payload.pop("patchCharts")
|
|
1872
|
+
if "removeChartIds" in payload and "remove_chart_ids" not in payload:
|
|
1873
|
+
payload["remove_chart_ids"] = payload.pop("removeChartIds")
|
|
1874
|
+
if "reorderChartIds" in payload and "reorder_chart_ids" not in payload:
|
|
1875
|
+
payload["reorder_chart_ids"] = payload.pop("reorderChartIds")
|
|
1691
1876
|
for key in ("remove_chart_ids", "reorder_chart_ids"):
|
|
1692
1877
|
raw = payload.get(key)
|
|
1693
1878
|
if isinstance(raw, list):
|
|
@@ -1696,8 +1881,8 @@ class ChartApplyRequest(StrictModel):
|
|
|
1696
1881
|
|
|
1697
1882
|
@model_validator(mode="after")
|
|
1698
1883
|
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")
|
|
1884
|
+
if not self.upsert_charts and not self.patch_charts and not self.remove_chart_ids and not self.reorder_chart_ids:
|
|
1885
|
+
raise ValueError("chart apply requires at least one upsert, patch, remove, or reorder operation")
|
|
1701
1886
|
return self
|
|
1702
1887
|
|
|
1703
1888
|
|
|
@@ -1757,6 +1942,13 @@ class PortalViewRefPatch(StrictModel):
|
|
|
1757
1942
|
view_key: str | None = None
|
|
1758
1943
|
view_name: str | None = None
|
|
1759
1944
|
|
|
1945
|
+
@model_validator(mode="before")
|
|
1946
|
+
@classmethod
|
|
1947
|
+
def normalize_aliases(cls, value: Any) -> Any:
|
|
1948
|
+
if not isinstance(value, dict):
|
|
1949
|
+
return value
|
|
1950
|
+
return _normalize_builder_view_key_payload(dict(value))
|
|
1951
|
+
|
|
1760
1952
|
@model_validator(mode="after")
|
|
1761
1953
|
def validate_target(self) -> "PortalViewRefPatch":
|
|
1762
1954
|
if not (self.view_key or self.view_name):
|
|
@@ -1944,6 +2136,7 @@ class ViewGetResponse(StrictModel):
|
|
|
1944
2136
|
config: dict[str, Any] = Field(default_factory=dict)
|
|
1945
2137
|
questions: list[dict[str, Any]] = Field(default_factory=list)
|
|
1946
2138
|
associations: list[dict[str, Any]] = Field(default_factory=list)
|
|
2139
|
+
buttons_config: dict[str, Any] = Field(default_factory=dict)
|
|
1947
2140
|
associated_resources_config: dict[str, Any] = Field(default_factory=dict)
|
|
1948
2141
|
|
|
1949
2142
|
|
|
@@ -2021,6 +2214,7 @@ class FlowPlanRequest(StrictModel):
|
|
|
2021
2214
|
class ViewsPlanRequest(StrictModel):
|
|
2022
2215
|
app_key: str
|
|
2023
2216
|
upsert_views: list[ViewUpsertPatch] = Field(default_factory=list)
|
|
2217
|
+
patch_views: list[ViewPartialPatch] = Field(default_factory=list)
|
|
2024
2218
|
remove_views: list[str] = Field(default_factory=list)
|
|
2025
2219
|
preset: ViewsPreset | None = None
|
|
2026
2220
|
|