@qingflow-tech/qingflow-app-builder-mcp 1.0.37 → 1.0.39
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
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
Install:
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npm install @qingflow-tech/qingflow-app-builder-mcp@1.0.
|
|
6
|
+
npm install @qingflow-tech/qingflow-app-builder-mcp@1.0.39
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
Run:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npx -y -p @qingflow-tech/qingflow-app-builder-mcp@1.0.
|
|
12
|
+
npx -y -p @qingflow-tech/qingflow-app-builder-mcp@1.0.39 qingflow-app-builder-mcp
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Environment:
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -2215,6 +2215,7 @@ class ChartGetResponse(StrictModel):
|
|
|
2215
2215
|
chart_id: str
|
|
2216
2216
|
base: dict[str, Any] = Field(default_factory=dict)
|
|
2217
2217
|
visibility: dict[str, Any] = Field(default_factory=dict)
|
|
2218
|
+
filters: list[list[dict[str, Any]]] = Field(default_factory=list)
|
|
2218
2219
|
config: dict[str, Any] = Field(default_factory=dict)
|
|
2219
2220
|
|
|
2220
2221
|
|
|
@@ -7184,6 +7184,7 @@ class AiBuilderFacade:
|
|
|
7184
7184
|
chart_id=chart_id,
|
|
7185
7185
|
base=deepcopy(base) if isinstance(base, dict) else {},
|
|
7186
7186
|
visibility=_public_visibility_from_chart_visible_auth(base.get("visibleAuth")),
|
|
7187
|
+
filters=_public_chart_filter_groups_from_qingbi_config(config) if isinstance(config, dict) else [],
|
|
7187
7188
|
config=deepcopy(config) if isinstance(config, dict) else {},
|
|
7188
7189
|
)
|
|
7189
7190
|
return {
|
|
@@ -11378,11 +11379,14 @@ class AiBuilderFacade:
|
|
|
11378
11379
|
|
|
11379
11380
|
if failed_items:
|
|
11380
11381
|
successful_changes = bool(created_ids or updated_ids or removed_ids or reordered)
|
|
11382
|
+
primary_failure = failed_items[0]
|
|
11383
|
+
primary_error_code = str(primary_failure.get("error_code") or "").strip()
|
|
11384
|
+
primary_message = str(primary_failure.get("message") or "").strip()
|
|
11381
11385
|
return finalize({
|
|
11382
11386
|
"status": "partial_success" if successful_changes else "failed",
|
|
11383
|
-
"error_code": "CHART_APPLY_PARTIAL" if successful_changes else "CHART_APPLY_FAILED",
|
|
11387
|
+
"error_code": "CHART_APPLY_PARTIAL" if successful_changes else primary_error_code or "CHART_APPLY_FAILED",
|
|
11384
11388
|
"recoverable": True,
|
|
11385
|
-
"message": "applied some chart operations; at least one chart operation failed" if successful_changes else "one or more chart operations failed",
|
|
11389
|
+
"message": "applied some chart operations; at least one chart operation failed" if successful_changes else primary_message or "one or more chart operations failed",
|
|
11386
11390
|
"normalized_args": normalized_args,
|
|
11387
11391
|
"missing_fields": [],
|
|
11388
11392
|
"allowed_values": {"chart.chart_type": [member.value for member in PublicChartType], "chart.filter.operator": [member.value for member in ViewFilterOperator]},
|
|
@@ -11609,12 +11613,12 @@ class AiBuilderFacade:
|
|
|
11609
11613
|
base_payload=base_payload,
|
|
11610
11614
|
)
|
|
11611
11615
|
if sections_requested:
|
|
11612
|
-
component_payload = self._build_portal_components_from_sections(
|
|
11616
|
+
component_payload, layout_metadata = self._build_portal_components_from_sections(
|
|
11613
11617
|
profile=profile,
|
|
11614
11618
|
sections=request.sections,
|
|
11615
11619
|
layout_preset=request.layout_preset,
|
|
11616
11620
|
)
|
|
11617
|
-
layout_diagnostics = _portal_layout_diagnostics(request.sections, component_payload)
|
|
11621
|
+
layout_diagnostics = _portal_layout_diagnostics(request.sections, component_payload, layout_metadata=layout_metadata)
|
|
11618
11622
|
update_payload["components"] = component_payload
|
|
11619
11623
|
self.portals.portal_update(profile=profile, dash_key=dash_key, payload=update_payload)
|
|
11620
11624
|
write_executed = True
|
|
@@ -12560,8 +12564,9 @@ class AiBuilderFacade:
|
|
|
12560
12564
|
profile: str,
|
|
12561
12565
|
sections: list[PortalSectionPatch],
|
|
12562
12566
|
layout_preset: str | None = None,
|
|
12563
|
-
) -> list[dict[str, Any]]:
|
|
12567
|
+
) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
|
|
12564
12568
|
resolved_components: list[dict[str, Any]] = []
|
|
12569
|
+
layout_metadata: list[dict[str, Any]] = []
|
|
12565
12570
|
pc_x = 0
|
|
12566
12571
|
pc_y = 0
|
|
12567
12572
|
pc_row_height = 0
|
|
@@ -12593,6 +12598,11 @@ class AiBuilderFacade:
|
|
|
12593
12598
|
profile=profile,
|
|
12594
12599
|
ref=section.chart_ref,
|
|
12595
12600
|
)
|
|
12601
|
+
chart_type = str(
|
|
12602
|
+
resolved_chart.get("chart_type")
|
|
12603
|
+
or _public_chart_type_from_backend(section.config.get("chartType") or section.config.get("chart_type"))
|
|
12604
|
+
or ""
|
|
12605
|
+
).strip()
|
|
12596
12606
|
chart_config = {
|
|
12597
12607
|
"biChartId": resolved_chart["chart_id"],
|
|
12598
12608
|
"chartComponentTitle": section.title,
|
|
@@ -12600,6 +12610,7 @@ class AiBuilderFacade:
|
|
|
12600
12610
|
**deepcopy(section.config),
|
|
12601
12611
|
}
|
|
12602
12612
|
component = {"type": 9, "position": position_payload, "chartConfig": _compact_dict(chart_config)}
|
|
12613
|
+
layout_metadata.append({"source_type": section.source_type, "chart_type": chart_type})
|
|
12603
12614
|
elif section.source_type == "view":
|
|
12604
12615
|
resolved_view = _resolve_view_reference(
|
|
12605
12616
|
facade=self,
|
|
@@ -12622,26 +12633,31 @@ class AiBuilderFacade:
|
|
|
12622
12633
|
**deepcopy(section.config),
|
|
12623
12634
|
}
|
|
12624
12635
|
component = {"type": 10, "position": position_payload, "viewgraphConfig": _compact_dict(view_config)}
|
|
12636
|
+
layout_metadata.append({"source_type": section.source_type})
|
|
12625
12637
|
elif section.source_type == "grid":
|
|
12626
12638
|
component = {
|
|
12627
12639
|
"type": 2,
|
|
12628
12640
|
"position": position_payload,
|
|
12629
12641
|
"gridConfig": _compact_dict({"gridTitle": section.title, "beingShowTitle": True, **deepcopy(section.config)}),
|
|
12630
12642
|
}
|
|
12643
|
+
layout_metadata.append({"source_type": section.source_type})
|
|
12631
12644
|
elif section.source_type == "filter":
|
|
12632
12645
|
component = {"type": 6, "position": position_payload, "filterConfig": deepcopy(section.config)}
|
|
12646
|
+
layout_metadata.append({"source_type": section.source_type})
|
|
12633
12647
|
elif section.source_type == "text":
|
|
12634
12648
|
component = {"type": 5, "position": position_payload, "textConfig": {"text": section.text or "", **deepcopy(section.config)}}
|
|
12649
|
+
layout_metadata.append({"source_type": section.source_type})
|
|
12635
12650
|
else:
|
|
12636
12651
|
component = {
|
|
12637
12652
|
"type": 4,
|
|
12638
12653
|
"position": position_payload,
|
|
12639
12654
|
"linkConfig": {"url": section.url or "", "beingLoginAuth": False, **deepcopy(section.config)},
|
|
12640
12655
|
}
|
|
12656
|
+
layout_metadata.append({"source_type": section.source_type})
|
|
12641
12657
|
if dash_style is not None:
|
|
12642
12658
|
component["dashStyleConfigBO"] = dash_style
|
|
12643
12659
|
resolved_components.append(component)
|
|
12644
|
-
return resolved_components
|
|
12660
|
+
return resolved_components, layout_metadata
|
|
12645
12661
|
|
|
12646
12662
|
def _resolve_current_user_identity(self, *, profile: str) -> JSONObject:
|
|
12647
12663
|
session_profile = self.apps.sessions.get_profile(profile)
|
|
@@ -15209,6 +15225,103 @@ def _qingbi_chart_filter_value_to_text(*, value: Any, form_field: dict[str, Any]
|
|
|
15209
15225
|
reject(value)
|
|
15210
15226
|
|
|
15211
15227
|
|
|
15228
|
+
def _public_chart_filter_groups_from_qingbi_config(config: dict[str, Any]) -> list[list[dict[str, Any]]]:
|
|
15229
|
+
groups: list[list[dict[str, Any]]] = []
|
|
15230
|
+
raw_groups = config.get("beforeAggregationFilterMatrix")
|
|
15231
|
+
if not isinstance(raw_groups, list):
|
|
15232
|
+
return groups
|
|
15233
|
+
for raw_group in raw_groups:
|
|
15234
|
+
if not isinstance(raw_group, list):
|
|
15235
|
+
continue
|
|
15236
|
+
group: list[dict[str, Any]] = []
|
|
15237
|
+
for raw_rule in raw_group:
|
|
15238
|
+
if not isinstance(raw_rule, dict):
|
|
15239
|
+
continue
|
|
15240
|
+
operator = _public_chart_filter_operator_from_judge_type(raw_rule.get("judgeType"))
|
|
15241
|
+
field_id = raw_rule.get("fieldId") or raw_rule.get("field_id")
|
|
15242
|
+
field_name = (
|
|
15243
|
+
raw_rule.get("fieldName")
|
|
15244
|
+
or raw_rule.get("field_name")
|
|
15245
|
+
or raw_rule.get("queTitle")
|
|
15246
|
+
or raw_rule.get("title")
|
|
15247
|
+
or field_id
|
|
15248
|
+
)
|
|
15249
|
+
public_rule: dict[str, Any] = {
|
|
15250
|
+
"field_name": _stringify_condition_value(field_name).strip(),
|
|
15251
|
+
"operator": operator,
|
|
15252
|
+
}
|
|
15253
|
+
if field_id is not None:
|
|
15254
|
+
public_rule["field_id"] = _stringify_condition_value(field_id).strip()
|
|
15255
|
+
values = _public_chart_filter_values_from_rule(raw_rule, operator=operator)
|
|
15256
|
+
if values:
|
|
15257
|
+
public_rule["values"] = values
|
|
15258
|
+
group.append(public_rule)
|
|
15259
|
+
if group:
|
|
15260
|
+
groups.append(group)
|
|
15261
|
+
return groups
|
|
15262
|
+
|
|
15263
|
+
|
|
15264
|
+
def _public_chart_filter_operator_from_judge_type(judge_type: Any) -> str:
|
|
15265
|
+
normalized = _stringify_condition_value(judge_type).strip()
|
|
15266
|
+
mapping = {
|
|
15267
|
+
"equal": "eq",
|
|
15268
|
+
"equals": "eq",
|
|
15269
|
+
"=": "eq",
|
|
15270
|
+
"unequal": "neq",
|
|
15271
|
+
"notEqual": "neq",
|
|
15272
|
+
"!=": "neq",
|
|
15273
|
+
"anyMatch": "in",
|
|
15274
|
+
"oneOf": "in",
|
|
15275
|
+
"include": "contains",
|
|
15276
|
+
"contains": "contains",
|
|
15277
|
+
"greaterOrEqual": "gte",
|
|
15278
|
+
"gte": "gte",
|
|
15279
|
+
"lessOrEqual": "lte",
|
|
15280
|
+
"lte": "lte",
|
|
15281
|
+
"isNull": "is_empty",
|
|
15282
|
+
"empty": "is_empty",
|
|
15283
|
+
"notNull": "not_empty",
|
|
15284
|
+
"not_empty": "not_empty",
|
|
15285
|
+
}
|
|
15286
|
+
if normalized in mapping:
|
|
15287
|
+
return mapping[normalized]
|
|
15288
|
+
numeric = _coerce_nonnegative_int(judge_type)
|
|
15289
|
+
if numeric is not None:
|
|
15290
|
+
return _public_view_filter_operator_from_judge_type(numeric)
|
|
15291
|
+
return f"judge_type:{judge_type}"
|
|
15292
|
+
|
|
15293
|
+
|
|
15294
|
+
def _public_chart_filter_values_from_rule(rule: dict[str, Any], *, operator: str) -> list[str]:
|
|
15295
|
+
if operator in {"is_empty", "not_empty"}:
|
|
15296
|
+
return []
|
|
15297
|
+
raw_value = rule.get("judgeValue")
|
|
15298
|
+
if raw_value is None and "judgeValues" in rule:
|
|
15299
|
+
raw_value = rule.get("judgeValues")
|
|
15300
|
+
values: list[str] = []
|
|
15301
|
+
if isinstance(raw_value, list):
|
|
15302
|
+
values = [_stringify_condition_value(value).strip() for value in raw_value]
|
|
15303
|
+
elif isinstance(raw_value, str) and "<&&>" in raw_value:
|
|
15304
|
+
values = [value.strip() for value in raw_value.split("<&&>")]
|
|
15305
|
+
elif raw_value is not None:
|
|
15306
|
+
values = [_stringify_condition_value(raw_value).strip()]
|
|
15307
|
+
values = [value for value in values if value]
|
|
15308
|
+
if values:
|
|
15309
|
+
return values
|
|
15310
|
+
for detail_key in ("judgeValueDetailList", "judgeValueDetails"):
|
|
15311
|
+
details = rule.get(detail_key)
|
|
15312
|
+
if not isinstance(details, list):
|
|
15313
|
+
continue
|
|
15314
|
+
for detail in details:
|
|
15315
|
+
if not isinstance(detail, dict):
|
|
15316
|
+
continue
|
|
15317
|
+
for value_key in ("value", "label", "name", "title", "dataValue", "id"):
|
|
15318
|
+
value = _stringify_condition_value(detail.get(value_key)).strip()
|
|
15319
|
+
if value:
|
|
15320
|
+
values.append(value)
|
|
15321
|
+
break
|
|
15322
|
+
return values
|
|
15323
|
+
|
|
15324
|
+
|
|
15212
15325
|
def _build_public_chart_config_payload(
|
|
15213
15326
|
*,
|
|
15214
15327
|
patch: ChartUpsertPatch,
|
|
@@ -15418,6 +15531,17 @@ def _extract_chart_identifier(chart: Any) -> str:
|
|
|
15418
15531
|
).strip()
|
|
15419
15532
|
|
|
15420
15533
|
|
|
15534
|
+
def _chart_type_from_item(chart: Any) -> str:
|
|
15535
|
+
if not isinstance(chart, dict):
|
|
15536
|
+
return ""
|
|
15537
|
+
return _public_chart_type_from_backend(
|
|
15538
|
+
chart.get("chartType")
|
|
15539
|
+
or chart.get("chart_type")
|
|
15540
|
+
or chart.get("chartGraphType")
|
|
15541
|
+
or chart.get("type")
|
|
15542
|
+
)
|
|
15543
|
+
|
|
15544
|
+
|
|
15421
15545
|
def _normalize_backend_chart_type(value: Any) -> str:
|
|
15422
15546
|
raw = str(value or "").strip()
|
|
15423
15547
|
if not raw:
|
|
@@ -15534,7 +15658,7 @@ def _portal_component_position_public(
|
|
|
15534
15658
|
rows = 2
|
|
15535
15659
|
elif source_name == "view":
|
|
15536
15660
|
cols = 24
|
|
15537
|
-
rows =
|
|
15661
|
+
rows = 11
|
|
15538
15662
|
elif source_name == "link":
|
|
15539
15663
|
cols = 12
|
|
15540
15664
|
rows = 2
|
|
@@ -15543,7 +15667,7 @@ def _portal_component_position_public(
|
|
|
15543
15667
|
cols = 12
|
|
15544
15668
|
else:
|
|
15545
15669
|
cols = 8
|
|
15546
|
-
rows =
|
|
15670
|
+
rows = 7
|
|
15547
15671
|
if cols == 24:
|
|
15548
15672
|
if pc_x != 0:
|
|
15549
15673
|
pc_y += pc_row_height
|
|
@@ -15578,7 +15702,12 @@ def _empty_portal_layout_diagnostics() -> dict[str, Any]:
|
|
|
15578
15702
|
}
|
|
15579
15703
|
|
|
15580
15704
|
|
|
15581
|
-
def _portal_layout_diagnostics(
|
|
15705
|
+
def _portal_layout_diagnostics(
|
|
15706
|
+
sections: list[PortalSectionPatch],
|
|
15707
|
+
components: list[dict[str, Any]],
|
|
15708
|
+
*,
|
|
15709
|
+
layout_metadata: list[dict[str, Any]] | None = None,
|
|
15710
|
+
) -> dict[str, Any]:
|
|
15582
15711
|
diagnostics = _empty_portal_layout_diagnostics()
|
|
15583
15712
|
diagnostics["section_count"] = len(sections)
|
|
15584
15713
|
explicit_count = sum(1 for section in sections if section.position is not None)
|
|
@@ -15593,16 +15722,26 @@ def _portal_layout_diagnostics(sections: list[PortalSectionPatch], components: l
|
|
|
15593
15722
|
if pc:
|
|
15594
15723
|
pc_positions.append(pc)
|
|
15595
15724
|
section = sections[index] if index < len(sections) else None
|
|
15725
|
+
metadata = layout_metadata[index] if isinstance(layout_metadata, list) and index < len(layout_metadata) and isinstance(layout_metadata[index], dict) else {}
|
|
15596
15726
|
source_type = str(getattr(section, "source_type", "") or "").lower() if section is not None else ""
|
|
15597
15727
|
title = str(getattr(section, "title", "") or "").strip() if section is not None else None
|
|
15598
15728
|
cols = int(pc.get("cols") or 0)
|
|
15599
15729
|
rows = int(pc.get("rows") or 0)
|
|
15600
|
-
|
|
15730
|
+
chart_type = str(metadata.get("chart_type") or "").strip().lower()
|
|
15731
|
+
is_metric_chart = chart_type in {"target", "indicator"}
|
|
15732
|
+
min_chart_cols = 6 if is_metric_chart else 8
|
|
15733
|
+
min_chart_rows = 5 if is_metric_chart else 7
|
|
15734
|
+
if source_type == "chart" and (cols < min_chart_cols or rows < min_chart_rows):
|
|
15601
15735
|
warnings.append(_warning(
|
|
15602
15736
|
"PORTAL_CHART_CARD_TOO_SMALL",
|
|
15603
|
-
|
|
15737
|
+
(
|
|
15738
|
+
"metric chart portal card is too small; use at least pc.cols >= 6 and pc.rows >= 5"
|
|
15739
|
+
if is_metric_chart
|
|
15740
|
+
else "chart portal card is too small; use at least pc.cols >= 8 and pc.rows >= 7 for non-metric charts"
|
|
15741
|
+
),
|
|
15604
15742
|
section_index=index,
|
|
15605
15743
|
title=title,
|
|
15744
|
+
chart_type=chart_type or None,
|
|
15606
15745
|
pc=deepcopy(pc),
|
|
15607
15746
|
))
|
|
15608
15747
|
if section is not None and section.position is not None and not bool(getattr(section.position, "mobile_provided", False)):
|
|
@@ -15645,7 +15784,7 @@ def _resolve_chart_reference(*, charts: QingbiReportTools, profile: str, ref: An
|
|
|
15645
15784
|
item_id = _extract_chart_identifier(item)
|
|
15646
15785
|
item_name = str(item.get("chartName") or "").strip()
|
|
15647
15786
|
if item_id == chart_id:
|
|
15648
|
-
return {"chart_id": item_id, "chart_name": item_name, "app_key": app_key}
|
|
15787
|
+
return {"chart_id": item_id, "chart_name": item_name, "app_key": app_key, "chart_type": _chart_type_from_item(item)}
|
|
15649
15788
|
raise ValueError(f"chart ref chart_id '{chart_id}' could not be resolved under app '{app_key}'")
|
|
15650
15789
|
matches = _find_charts_by_name(items, chart_name=chart_name)
|
|
15651
15790
|
if len(matches) > 1:
|
|
@@ -15656,6 +15795,7 @@ def _resolve_chart_reference(*, charts: QingbiReportTools, profile: str, ref: An
|
|
|
15656
15795
|
"chart_id": _extract_chart_identifier(item),
|
|
15657
15796
|
"chart_name": str(item.get("chartName") or "").strip(),
|
|
15658
15797
|
"app_key": app_key,
|
|
15798
|
+
"chart_type": _chart_type_from_item(item),
|
|
15659
15799
|
}
|
|
15660
15800
|
raise ValueError(f"chart ref could not be resolved under app '{app_key}'")
|
|
15661
15801
|
|
|
@@ -24716,7 +24856,7 @@ def _public_view_filter_groups_from_match_rules(
|
|
|
24716
24856
|
|
|
24717
24857
|
|
|
24718
24858
|
def _public_view_filter_operator_from_judge_type(judge_type: Any) -> str:
|
|
24719
|
-
normalized =
|
|
24859
|
+
normalized = _coerce_nonnegative_int(judge_type)
|
|
24720
24860
|
if normalized == JUDGE_EQUAL:
|
|
24721
24861
|
return "eq"
|
|
24722
24862
|
if normalized == JUDGE_UNEQUAL:
|