@qingflow-tech/qingflow-app-user-mcp 1.0.38 → 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-user-mcp@1.0.
|
|
6
|
+
npm install @qingflow-tech/qingflow-app-user-mcp@1.0.39
|
|
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.39 qingflow-app-user-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 {
|
|
@@ -11612,12 +11613,12 @@ class AiBuilderFacade:
|
|
|
11612
11613
|
base_payload=base_payload,
|
|
11613
11614
|
)
|
|
11614
11615
|
if sections_requested:
|
|
11615
|
-
component_payload = self._build_portal_components_from_sections(
|
|
11616
|
+
component_payload, layout_metadata = self._build_portal_components_from_sections(
|
|
11616
11617
|
profile=profile,
|
|
11617
11618
|
sections=request.sections,
|
|
11618
11619
|
layout_preset=request.layout_preset,
|
|
11619
11620
|
)
|
|
11620
|
-
layout_diagnostics = _portal_layout_diagnostics(request.sections, component_payload)
|
|
11621
|
+
layout_diagnostics = _portal_layout_diagnostics(request.sections, component_payload, layout_metadata=layout_metadata)
|
|
11621
11622
|
update_payload["components"] = component_payload
|
|
11622
11623
|
self.portals.portal_update(profile=profile, dash_key=dash_key, payload=update_payload)
|
|
11623
11624
|
write_executed = True
|
|
@@ -12563,8 +12564,9 @@ class AiBuilderFacade:
|
|
|
12563
12564
|
profile: str,
|
|
12564
12565
|
sections: list[PortalSectionPatch],
|
|
12565
12566
|
layout_preset: str | None = None,
|
|
12566
|
-
) -> list[dict[str, Any]]:
|
|
12567
|
+
) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
|
|
12567
12568
|
resolved_components: list[dict[str, Any]] = []
|
|
12569
|
+
layout_metadata: list[dict[str, Any]] = []
|
|
12568
12570
|
pc_x = 0
|
|
12569
12571
|
pc_y = 0
|
|
12570
12572
|
pc_row_height = 0
|
|
@@ -12596,6 +12598,11 @@ class AiBuilderFacade:
|
|
|
12596
12598
|
profile=profile,
|
|
12597
12599
|
ref=section.chart_ref,
|
|
12598
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()
|
|
12599
12606
|
chart_config = {
|
|
12600
12607
|
"biChartId": resolved_chart["chart_id"],
|
|
12601
12608
|
"chartComponentTitle": section.title,
|
|
@@ -12603,6 +12610,7 @@ class AiBuilderFacade:
|
|
|
12603
12610
|
**deepcopy(section.config),
|
|
12604
12611
|
}
|
|
12605
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})
|
|
12606
12614
|
elif section.source_type == "view":
|
|
12607
12615
|
resolved_view = _resolve_view_reference(
|
|
12608
12616
|
facade=self,
|
|
@@ -12625,26 +12633,31 @@ class AiBuilderFacade:
|
|
|
12625
12633
|
**deepcopy(section.config),
|
|
12626
12634
|
}
|
|
12627
12635
|
component = {"type": 10, "position": position_payload, "viewgraphConfig": _compact_dict(view_config)}
|
|
12636
|
+
layout_metadata.append({"source_type": section.source_type})
|
|
12628
12637
|
elif section.source_type == "grid":
|
|
12629
12638
|
component = {
|
|
12630
12639
|
"type": 2,
|
|
12631
12640
|
"position": position_payload,
|
|
12632
12641
|
"gridConfig": _compact_dict({"gridTitle": section.title, "beingShowTitle": True, **deepcopy(section.config)}),
|
|
12633
12642
|
}
|
|
12643
|
+
layout_metadata.append({"source_type": section.source_type})
|
|
12634
12644
|
elif section.source_type == "filter":
|
|
12635
12645
|
component = {"type": 6, "position": position_payload, "filterConfig": deepcopy(section.config)}
|
|
12646
|
+
layout_metadata.append({"source_type": section.source_type})
|
|
12636
12647
|
elif section.source_type == "text":
|
|
12637
12648
|
component = {"type": 5, "position": position_payload, "textConfig": {"text": section.text or "", **deepcopy(section.config)}}
|
|
12649
|
+
layout_metadata.append({"source_type": section.source_type})
|
|
12638
12650
|
else:
|
|
12639
12651
|
component = {
|
|
12640
12652
|
"type": 4,
|
|
12641
12653
|
"position": position_payload,
|
|
12642
12654
|
"linkConfig": {"url": section.url or "", "beingLoginAuth": False, **deepcopy(section.config)},
|
|
12643
12655
|
}
|
|
12656
|
+
layout_metadata.append({"source_type": section.source_type})
|
|
12644
12657
|
if dash_style is not None:
|
|
12645
12658
|
component["dashStyleConfigBO"] = dash_style
|
|
12646
12659
|
resolved_components.append(component)
|
|
12647
|
-
return resolved_components
|
|
12660
|
+
return resolved_components, layout_metadata
|
|
12648
12661
|
|
|
12649
12662
|
def _resolve_current_user_identity(self, *, profile: str) -> JSONObject:
|
|
12650
12663
|
session_profile = self.apps.sessions.get_profile(profile)
|
|
@@ -15212,6 +15225,103 @@ def _qingbi_chart_filter_value_to_text(*, value: Any, form_field: dict[str, Any]
|
|
|
15212
15225
|
reject(value)
|
|
15213
15226
|
|
|
15214
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
|
+
|
|
15215
15325
|
def _build_public_chart_config_payload(
|
|
15216
15326
|
*,
|
|
15217
15327
|
patch: ChartUpsertPatch,
|
|
@@ -15421,6 +15531,17 @@ def _extract_chart_identifier(chart: Any) -> str:
|
|
|
15421
15531
|
).strip()
|
|
15422
15532
|
|
|
15423
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
|
+
|
|
15424
15545
|
def _normalize_backend_chart_type(value: Any) -> str:
|
|
15425
15546
|
raw = str(value or "").strip()
|
|
15426
15547
|
if not raw:
|
|
@@ -15537,7 +15658,7 @@ def _portal_component_position_public(
|
|
|
15537
15658
|
rows = 2
|
|
15538
15659
|
elif source_name == "view":
|
|
15539
15660
|
cols = 24
|
|
15540
|
-
rows =
|
|
15661
|
+
rows = 11
|
|
15541
15662
|
elif source_name == "link":
|
|
15542
15663
|
cols = 12
|
|
15543
15664
|
rows = 2
|
|
@@ -15546,7 +15667,7 @@ def _portal_component_position_public(
|
|
|
15546
15667
|
cols = 12
|
|
15547
15668
|
else:
|
|
15548
15669
|
cols = 8
|
|
15549
|
-
rows =
|
|
15670
|
+
rows = 7
|
|
15550
15671
|
if cols == 24:
|
|
15551
15672
|
if pc_x != 0:
|
|
15552
15673
|
pc_y += pc_row_height
|
|
@@ -15581,7 +15702,12 @@ def _empty_portal_layout_diagnostics() -> dict[str, Any]:
|
|
|
15581
15702
|
}
|
|
15582
15703
|
|
|
15583
15704
|
|
|
15584
|
-
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]:
|
|
15585
15711
|
diagnostics = _empty_portal_layout_diagnostics()
|
|
15586
15712
|
diagnostics["section_count"] = len(sections)
|
|
15587
15713
|
explicit_count = sum(1 for section in sections if section.position is not None)
|
|
@@ -15596,16 +15722,26 @@ def _portal_layout_diagnostics(sections: list[PortalSectionPatch], components: l
|
|
|
15596
15722
|
if pc:
|
|
15597
15723
|
pc_positions.append(pc)
|
|
15598
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 {}
|
|
15599
15726
|
source_type = str(getattr(section, "source_type", "") or "").lower() if section is not None else ""
|
|
15600
15727
|
title = str(getattr(section, "title", "") or "").strip() if section is not None else None
|
|
15601
15728
|
cols = int(pc.get("cols") or 0)
|
|
15602
15729
|
rows = int(pc.get("rows") or 0)
|
|
15603
|
-
|
|
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):
|
|
15604
15735
|
warnings.append(_warning(
|
|
15605
15736
|
"PORTAL_CHART_CARD_TOO_SMALL",
|
|
15606
|
-
|
|
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
|
+
),
|
|
15607
15742
|
section_index=index,
|
|
15608
15743
|
title=title,
|
|
15744
|
+
chart_type=chart_type or None,
|
|
15609
15745
|
pc=deepcopy(pc),
|
|
15610
15746
|
))
|
|
15611
15747
|
if section is not None and section.position is not None and not bool(getattr(section.position, "mobile_provided", False)):
|
|
@@ -15648,7 +15784,7 @@ def _resolve_chart_reference(*, charts: QingbiReportTools, profile: str, ref: An
|
|
|
15648
15784
|
item_id = _extract_chart_identifier(item)
|
|
15649
15785
|
item_name = str(item.get("chartName") or "").strip()
|
|
15650
15786
|
if item_id == chart_id:
|
|
15651
|
-
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)}
|
|
15652
15788
|
raise ValueError(f"chart ref chart_id '{chart_id}' could not be resolved under app '{app_key}'")
|
|
15653
15789
|
matches = _find_charts_by_name(items, chart_name=chart_name)
|
|
15654
15790
|
if len(matches) > 1:
|
|
@@ -15659,6 +15795,7 @@ def _resolve_chart_reference(*, charts: QingbiReportTools, profile: str, ref: An
|
|
|
15659
15795
|
"chart_id": _extract_chart_identifier(item),
|
|
15660
15796
|
"chart_name": str(item.get("chartName") or "").strip(),
|
|
15661
15797
|
"app_key": app_key,
|
|
15798
|
+
"chart_type": _chart_type_from_item(item),
|
|
15662
15799
|
}
|
|
15663
15800
|
raise ValueError(f"chart ref could not be resolved under app '{app_key}'")
|
|
15664
15801
|
|
|
@@ -24719,7 +24856,7 @@ def _public_view_filter_groups_from_match_rules(
|
|
|
24719
24856
|
|
|
24720
24857
|
|
|
24721
24858
|
def _public_view_filter_operator_from_judge_type(judge_type: Any) -> str:
|
|
24722
|
-
normalized =
|
|
24859
|
+
normalized = _coerce_nonnegative_int(judge_type)
|
|
24723
24860
|
if normalized == JUDGE_EQUAL:
|
|
24724
24861
|
return "eq"
|
|
24725
24862
|
if normalized == JUDGE_UNEQUAL:
|