@josephyan/qingflow-cli 1.1.6 → 1.1.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/src/qingflow_mcp/builder_facade/service.py +135 -4
package/README.md
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
Install:
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npm install @josephyan/qingflow-cli@1.1.
|
|
6
|
+
npm install @josephyan/qingflow-cli@1.1.8
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
Run:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npx -y -p @josephyan/qingflow-cli@1.1.
|
|
12
|
+
npx -y -p @josephyan/qingflow-cli@1.1.8 qingflow
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Environment:
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -6524,6 +6524,7 @@ class AiBuilderFacade:
|
|
|
6524
6524
|
)
|
|
6525
6525
|
charts = _summarize_charts(items)
|
|
6526
6526
|
chart_visibility_read_errors: list[dict[str, Any]] = []
|
|
6527
|
+
chart_config_read_errors: list[dict[str, Any]] = []
|
|
6527
6528
|
for chart in charts:
|
|
6528
6529
|
chart_id = str(chart.get("chart_id") or "").strip()
|
|
6529
6530
|
if not chart_id:
|
|
@@ -6546,6 +6547,24 @@ class AiBuilderFacade:
|
|
|
6546
6547
|
base_info.get("visibleAuth") if isinstance(base_info, dict) else None
|
|
6547
6548
|
)
|
|
6548
6549
|
)
|
|
6550
|
+
try:
|
|
6551
|
+
config_response = self.charts.qingbi_report_get_config(profile=profile, chart_id=chart_id)
|
|
6552
|
+
config = config_response.get("result") or {}
|
|
6553
|
+
except (QingflowApiError, RuntimeError) as error:
|
|
6554
|
+
api_error = _coerce_api_error(error)
|
|
6555
|
+
chart_config_read_errors.append(
|
|
6556
|
+
{
|
|
6557
|
+
"chart_id": chart_id,
|
|
6558
|
+
"request_id": api_error.request_id,
|
|
6559
|
+
"http_status": api_error.http_status,
|
|
6560
|
+
"backend_code": api_error.backend_code,
|
|
6561
|
+
}
|
|
6562
|
+
)
|
|
6563
|
+
continue
|
|
6564
|
+
if isinstance(config, dict):
|
|
6565
|
+
chart["group_by"] = _public_chart_group_by_from_qingbi_config(config)
|
|
6566
|
+
chart["metrics"] = _public_chart_metrics_from_qingbi_config(config)
|
|
6567
|
+
chart["filters"] = _public_chart_filter_groups_from_qingbi_config(config)
|
|
6549
6568
|
response = AppChartsReadResponse(
|
|
6550
6569
|
app_key=resolved_app_key,
|
|
6551
6570
|
charts=charts,
|
|
@@ -6559,7 +6578,10 @@ class AiBuilderFacade:
|
|
|
6559
6578
|
"normalized_args": {"app_key": resolved_app_key},
|
|
6560
6579
|
"missing_fields": [],
|
|
6561
6580
|
"allowed_values": {},
|
|
6562
|
-
"details": {
|
|
6581
|
+
"details": {
|
|
6582
|
+
**({"chart_visibility_read_errors": chart_visibility_read_errors} if chart_visibility_read_errors else {}),
|
|
6583
|
+
**({"chart_config_read_errors": chart_config_read_errors} if chart_config_read_errors else {}),
|
|
6584
|
+
},
|
|
6563
6585
|
"request_id": None,
|
|
6564
6586
|
"suggested_next_call": None,
|
|
6565
6587
|
"noop": False,
|
|
@@ -6570,14 +6592,20 @@ class AiBuilderFacade:
|
|
|
6570
6592
|
if chart_visibility_read_errors
|
|
6571
6593
|
else []
|
|
6572
6594
|
)
|
|
6595
|
+
+ (
|
|
6596
|
+
[_warning("CHART_CONFIG_READ_PARTIAL", "some chart configs could not be read back; metrics/group_by/filters are incomplete for those charts")]
|
|
6597
|
+
if chart_config_read_errors
|
|
6598
|
+
else []
|
|
6599
|
+
)
|
|
6573
6600
|
),
|
|
6574
6601
|
"verification": {
|
|
6575
6602
|
"app_exists": True,
|
|
6576
6603
|
"chart_order_verified": list_source == "sorted",
|
|
6577
6604
|
"chart_list_source": list_source,
|
|
6578
6605
|
"chart_visibility_readback_complete": not chart_visibility_read_errors,
|
|
6606
|
+
"chart_config_readback_complete": not chart_config_read_errors,
|
|
6579
6607
|
},
|
|
6580
|
-
"verified":
|
|
6608
|
+
"verified": not chart_config_read_errors,
|
|
6581
6609
|
**response.model_dump(mode="json"),
|
|
6582
6610
|
}
|
|
6583
6611
|
|
|
@@ -23264,6 +23292,15 @@ def _merge_view_summary_with_config(
|
|
|
23264
23292
|
question_entries_by_id=query_question_entries_by_id,
|
|
23265
23293
|
)
|
|
23266
23294
|
config_enriched = True
|
|
23295
|
+
if "viewgraphLimit" in config:
|
|
23296
|
+
field_lookup = _view_field_lookup_from_question_entries(
|
|
23297
|
+
[*question_entries, *canonical_question_entries]
|
|
23298
|
+
)
|
|
23299
|
+
summary["filters"] = _public_view_filter_groups_from_match_rules(
|
|
23300
|
+
config.get("viewgraphLimit"),
|
|
23301
|
+
current_fields_by_name=field_lookup,
|
|
23302
|
+
)
|
|
23303
|
+
config_enriched = True
|
|
23267
23304
|
if any(key in config for key in ("asosChartVisible", "asosChartConfig", "asosChartIdList", "limitType")):
|
|
23268
23305
|
summary["associated_resources_config"] = _extract_view_associated_resources_config(config)
|
|
23269
23306
|
config_enriched = True
|
|
@@ -23321,6 +23358,24 @@ def _extract_view_question_entries(questions: Any) -> list[dict[str, Any]]:
|
|
|
23321
23358
|
"visible": visible,
|
|
23322
23359
|
"display_order": display_order if display_order is not None else fallback_order,
|
|
23323
23360
|
}
|
|
23361
|
+
option_details: list[dict[str, Any]] = []
|
|
23362
|
+
option_values: list[str] = []
|
|
23363
|
+
raw_options = item.get("options")
|
|
23364
|
+
if isinstance(raw_options, list):
|
|
23365
|
+
for raw_option in raw_options:
|
|
23366
|
+
if not isinstance(raw_option, dict):
|
|
23367
|
+
continue
|
|
23368
|
+
option_id = raw_option.get("optId")
|
|
23369
|
+
option_value = str(raw_option.get("optValue") or "").strip()
|
|
23370
|
+
if not option_value and option_id is None:
|
|
23371
|
+
continue
|
|
23372
|
+
if option_value:
|
|
23373
|
+
option_values.append(option_value)
|
|
23374
|
+
option_details.append({"id": option_id, "value": option_value or str(option_id)})
|
|
23375
|
+
if option_values:
|
|
23376
|
+
entry["options"] = option_values
|
|
23377
|
+
if option_details:
|
|
23378
|
+
entry["option_details"] = option_details
|
|
23324
23379
|
width = _coerce_positive_int(item.get("width"))
|
|
23325
23380
|
if width is not None:
|
|
23326
23381
|
entry["width"] = width
|
|
@@ -23330,6 +23385,43 @@ def _extract_view_question_entries(questions: Any) -> list[dict[str, Any]]:
|
|
|
23330
23385
|
return entries
|
|
23331
23386
|
|
|
23332
23387
|
|
|
23388
|
+
def _view_field_lookup_from_question_entries(entries: list[dict[str, Any]]) -> dict[str, dict[str, Any]]:
|
|
23389
|
+
fields_by_name: dict[str, dict[str, Any]] = {}
|
|
23390
|
+
for entry in entries:
|
|
23391
|
+
if not isinstance(entry, dict):
|
|
23392
|
+
continue
|
|
23393
|
+
name = str(entry.get("name") or entry.get("title") or "").strip()
|
|
23394
|
+
que_id = _coerce_positive_int(entry.get("field_id") or entry.get("que_id") or entry.get("queId"))
|
|
23395
|
+
if not name or que_id is None:
|
|
23396
|
+
continue
|
|
23397
|
+
field_entry: dict[str, Any] = {"name": name, "que_id": que_id}
|
|
23398
|
+
options = entry.get("options")
|
|
23399
|
+
if isinstance(options, list) and options:
|
|
23400
|
+
field_entry["options"] = [str(value) for value in options if str(value or "").strip()]
|
|
23401
|
+
option_details = entry.get("option_details")
|
|
23402
|
+
if isinstance(option_details, list) and option_details:
|
|
23403
|
+
field_entry["option_details"] = [deepcopy(value) for value in option_details if isinstance(value, dict)]
|
|
23404
|
+
fields_by_name.setdefault(name, field_entry)
|
|
23405
|
+
return fields_by_name
|
|
23406
|
+
|
|
23407
|
+
|
|
23408
|
+
def _view_field_lookup_from_summary(view: dict[str, Any]) -> dict[str, dict[str, Any]]:
|
|
23409
|
+
entries = view.get("column_details")
|
|
23410
|
+
if isinstance(entries, list):
|
|
23411
|
+
return _view_field_lookup_from_question_entries([entry for entry in entries if isinstance(entry, dict)])
|
|
23412
|
+
fields_by_name: dict[str, dict[str, Any]] = {}
|
|
23413
|
+
names = view.get("columns")
|
|
23414
|
+
ids = view.get("display_column_ids") or view.get("configured_column_ids") or []
|
|
23415
|
+
if isinstance(names, list):
|
|
23416
|
+
for index, raw_name in enumerate(names):
|
|
23417
|
+
name = str(raw_name or "").strip()
|
|
23418
|
+
if not name:
|
|
23419
|
+
continue
|
|
23420
|
+
que_id = _coerce_positive_int(ids[index] if isinstance(ids, list) and index < len(ids) else None)
|
|
23421
|
+
fields_by_name.setdefault(name, {"name": name, "que_id": que_id})
|
|
23422
|
+
return fields_by_name
|
|
23423
|
+
|
|
23424
|
+
|
|
23333
23425
|
def _filter_public_view_display_entries(
|
|
23334
23426
|
entries: list[dict[str, Any]],
|
|
23335
23427
|
*,
|
|
@@ -24146,6 +24238,7 @@ def _custom_button_view_configs_from_view_summaries(views: list[dict[str, Any]])
|
|
|
24146
24238
|
raw_buttons = view.get("buttons")
|
|
24147
24239
|
if not isinstance(raw_buttons, list):
|
|
24148
24240
|
continue
|
|
24241
|
+
field_lookup = _view_field_lookup_from_summary(view)
|
|
24149
24242
|
buttons: list[dict[str, Any]] = []
|
|
24150
24243
|
for entry in raw_buttons:
|
|
24151
24244
|
if not isinstance(entry, dict):
|
|
@@ -24153,6 +24246,12 @@ def _custom_button_view_configs_from_view_summaries(views: list[dict[str, Any]])
|
|
|
24153
24246
|
if _normalize_view_button_type(entry.get("button_type")) != PublicViewButtonType.custom.value:
|
|
24154
24247
|
continue
|
|
24155
24248
|
placement = _public_view_button_placement(entry.get("config_type")) or entry.get("placement")
|
|
24249
|
+
raw_button_limit = deepcopy(entry.get("button_limit") or [])
|
|
24250
|
+
public_button_limit = (
|
|
24251
|
+
_public_view_filter_groups_from_match_rules(raw_button_limit, current_fields_by_name=field_lookup)
|
|
24252
|
+
if raw_button_limit
|
|
24253
|
+
else []
|
|
24254
|
+
)
|
|
24156
24255
|
button = _compact_dict(
|
|
24157
24256
|
{
|
|
24158
24257
|
"button_ref": _coerce_positive_int(entry.get("button_id")),
|
|
@@ -24160,7 +24259,7 @@ def _custom_button_view_configs_from_view_summaries(views: list[dict[str, Any]])
|
|
|
24160
24259
|
"button_text": entry.get("button_text"),
|
|
24161
24260
|
"placement": placement,
|
|
24162
24261
|
"primary": bool(entry.get("being_main", False)),
|
|
24163
|
-
"button_limit":
|
|
24262
|
+
"button_limit": public_button_limit,
|
|
24164
24263
|
"button_formula": entry.get("button_formula"),
|
|
24165
24264
|
"button_formula_type": _coerce_positive_int(entry.get("button_formula_type")),
|
|
24166
24265
|
"print_tpls": deepcopy(entry.get("print_tpls") or []),
|
|
@@ -26418,6 +26517,38 @@ def _view_filter_rule_values_for_signature(rule: dict[str, Any]) -> list[str]:
|
|
|
26418
26517
|
return fallback_values
|
|
26419
26518
|
|
|
26420
26519
|
|
|
26520
|
+
def _view_filter_option_value_by_id(field: dict[str, Any]) -> dict[str, str]:
|
|
26521
|
+
value_by_id: dict[str, str] = {}
|
|
26522
|
+
for detail in field.get("option_details") or []:
|
|
26523
|
+
if not isinstance(detail, dict):
|
|
26524
|
+
continue
|
|
26525
|
+
option_id = detail.get("id")
|
|
26526
|
+
option_value = str(detail.get("value") or "").strip()
|
|
26527
|
+
if option_id is None or not option_value:
|
|
26528
|
+
continue
|
|
26529
|
+
value_by_id[str(option_id)] = option_value
|
|
26530
|
+
return value_by_id
|
|
26531
|
+
|
|
26532
|
+
|
|
26533
|
+
def _public_view_filter_rule_values(rule: dict[str, Any], *, field: dict[str, Any]) -> list[str]:
|
|
26534
|
+
value_by_id = _view_filter_option_value_by_id(field)
|
|
26535
|
+
detail_value_by_id: dict[str, str] = {}
|
|
26536
|
+
ordered_detail_values: list[str] = []
|
|
26537
|
+
for detail in rule.get("judgeValueDetails") or []:
|
|
26538
|
+
if not isinstance(detail, dict):
|
|
26539
|
+
continue
|
|
26540
|
+
detail_value = str(detail.get("value") or "").strip()
|
|
26541
|
+
detail_id = detail.get("id")
|
|
26542
|
+
if detail_value:
|
|
26543
|
+
ordered_detail_values.append(detail_value)
|
|
26544
|
+
if detail_id is not None:
|
|
26545
|
+
detail_value_by_id[str(detail_id)] = detail_value
|
|
26546
|
+
values = [str(value) for value in (rule.get("judgeValues") or []) if str(value or "").strip()]
|
|
26547
|
+
if values:
|
|
26548
|
+
return [value_by_id.get(value) or detail_value_by_id.get(value) or value for value in values]
|
|
26549
|
+
return ordered_detail_values
|
|
26550
|
+
|
|
26551
|
+
|
|
26421
26552
|
def _view_filter_groups_signature(groups: Any) -> list[list[dict[str, Any]]]:
|
|
26422
26553
|
signature: list[list[dict[str, Any]]] = []
|
|
26423
26554
|
for group in _normalize_view_filter_groups_for_compare(groups):
|
|
@@ -26499,7 +26630,7 @@ def _public_view_filter_groups_from_match_rules(
|
|
|
26499
26630
|
for rule in group:
|
|
26500
26631
|
que_id = _coerce_positive_int(rule.get("queId")) or 0
|
|
26501
26632
|
field = fields_by_que_id.get(que_id) or {}
|
|
26502
|
-
values =
|
|
26633
|
+
values = _public_view_filter_rule_values(rule, field=field)
|
|
26503
26634
|
public_rule: dict[str, Any] = {
|
|
26504
26635
|
"field_name": str(field.get("name") or rule.get("queTitle") or que_id),
|
|
26505
26636
|
"operator": _public_view_filter_operator_from_judge_type(rule.get("judgeType")),
|