@josephyan/qingflow-cli 1.1.7 → 1.1.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/src/qingflow_mcp/builder_facade/service.py +164 -10
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.9
|
|
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.9 qingflow
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Environment:
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -5999,6 +5999,20 @@ class AiBuilderFacade:
|
|
|
5999
5999
|
result["message"] = "read app chart config"
|
|
6000
6000
|
return result
|
|
6001
6001
|
|
|
6002
|
+
def _chart_filter_field_names_by_id(
|
|
6003
|
+
self,
|
|
6004
|
+
*,
|
|
6005
|
+
profile: str,
|
|
6006
|
+
app_key: str,
|
|
6007
|
+
) -> tuple[dict[str, str], dict[str, Any] | None]:
|
|
6008
|
+
try:
|
|
6009
|
+
state = self._load_base_schema_state(profile=profile, app_key=app_key)
|
|
6010
|
+
except (QingflowApiError, RuntimeError) as error:
|
|
6011
|
+
api_error = _coerce_api_error(error)
|
|
6012
|
+
return {}, _transport_error_payload(api_error)
|
|
6013
|
+
fields = cast(list[dict[str, Any]], state["parsed"]["fields"])
|
|
6014
|
+
return _chart_field_names_by_id_from_public_fields(app_key=app_key, fields=fields), None
|
|
6015
|
+
|
|
6002
6016
|
def app_get_buttons(self, *, profile: str, app_key: str) -> JSONObject:
|
|
6003
6017
|
self.apps._require_app_key(app_key)
|
|
6004
6018
|
warnings: list[dict[str, Any]] = []
|
|
@@ -6524,6 +6538,8 @@ class AiBuilderFacade:
|
|
|
6524
6538
|
)
|
|
6525
6539
|
charts = _summarize_charts(items)
|
|
6526
6540
|
chart_visibility_read_errors: list[dict[str, Any]] = []
|
|
6541
|
+
chart_config_read_errors: list[dict[str, Any]] = []
|
|
6542
|
+
field_name_by_id, field_name_read_error = self._chart_filter_field_names_by_id(profile=profile, app_key=resolved_app_key)
|
|
6527
6543
|
for chart in charts:
|
|
6528
6544
|
chart_id = str(chart.get("chart_id") or "").strip()
|
|
6529
6545
|
if not chart_id:
|
|
@@ -6546,6 +6562,24 @@ class AiBuilderFacade:
|
|
|
6546
6562
|
base_info.get("visibleAuth") if isinstance(base_info, dict) else None
|
|
6547
6563
|
)
|
|
6548
6564
|
)
|
|
6565
|
+
try:
|
|
6566
|
+
config_response = self.charts.qingbi_report_get_config(profile=profile, chart_id=chart_id)
|
|
6567
|
+
config = config_response.get("result") or {}
|
|
6568
|
+
except (QingflowApiError, RuntimeError) as error:
|
|
6569
|
+
api_error = _coerce_api_error(error)
|
|
6570
|
+
chart_config_read_errors.append(
|
|
6571
|
+
{
|
|
6572
|
+
"chart_id": chart_id,
|
|
6573
|
+
"request_id": api_error.request_id,
|
|
6574
|
+
"http_status": api_error.http_status,
|
|
6575
|
+
"backend_code": api_error.backend_code,
|
|
6576
|
+
}
|
|
6577
|
+
)
|
|
6578
|
+
continue
|
|
6579
|
+
if isinstance(config, dict):
|
|
6580
|
+
chart["group_by"] = _public_chart_group_by_from_qingbi_config(config)
|
|
6581
|
+
chart["metrics"] = _public_chart_metrics_from_qingbi_config(config)
|
|
6582
|
+
chart["filters"] = _public_chart_filter_groups_from_qingbi_config(config, field_name_by_id=field_name_by_id)
|
|
6549
6583
|
response = AppChartsReadResponse(
|
|
6550
6584
|
app_key=resolved_app_key,
|
|
6551
6585
|
charts=charts,
|
|
@@ -6559,7 +6593,11 @@ class AiBuilderFacade:
|
|
|
6559
6593
|
"normalized_args": {"app_key": resolved_app_key},
|
|
6560
6594
|
"missing_fields": [],
|
|
6561
6595
|
"allowed_values": {},
|
|
6562
|
-
"details": {
|
|
6596
|
+
"details": {
|
|
6597
|
+
**({"chart_visibility_read_errors": chart_visibility_read_errors} if chart_visibility_read_errors else {}),
|
|
6598
|
+
**({"chart_config_read_errors": chart_config_read_errors} if chart_config_read_errors else {}),
|
|
6599
|
+
**({"chart_filter_field_name_read_error": field_name_read_error} if field_name_read_error else {}),
|
|
6600
|
+
},
|
|
6563
6601
|
"request_id": None,
|
|
6564
6602
|
"suggested_next_call": None,
|
|
6565
6603
|
"noop": False,
|
|
@@ -6570,14 +6608,26 @@ class AiBuilderFacade:
|
|
|
6570
6608
|
if chart_visibility_read_errors
|
|
6571
6609
|
else []
|
|
6572
6610
|
)
|
|
6611
|
+
+ (
|
|
6612
|
+
[_warning("CHART_CONFIG_READ_PARTIAL", "some chart configs could not be read back; metrics/group_by/filters are incomplete for those charts")]
|
|
6613
|
+
if chart_config_read_errors
|
|
6614
|
+
else []
|
|
6615
|
+
)
|
|
6616
|
+
+ (
|
|
6617
|
+
[_warning("CHART_FILTER_FIELD_NAMES_UNRESOLVED", "chart configs were read, but form fields could not be loaded to resolve filter field names")]
|
|
6618
|
+
if field_name_read_error
|
|
6619
|
+
else []
|
|
6620
|
+
)
|
|
6573
6621
|
),
|
|
6574
6622
|
"verification": {
|
|
6575
6623
|
"app_exists": True,
|
|
6576
6624
|
"chart_order_verified": list_source == "sorted",
|
|
6577
6625
|
"chart_list_source": list_source,
|
|
6578
6626
|
"chart_visibility_readback_complete": not chart_visibility_read_errors,
|
|
6627
|
+
"chart_config_readback_complete": not chart_config_read_errors,
|
|
6628
|
+
"chart_filter_field_names_resolved": not field_name_read_error,
|
|
6579
6629
|
},
|
|
6580
|
-
"verified":
|
|
6630
|
+
"verified": not chart_config_read_errors and not field_name_read_error,
|
|
6581
6631
|
**response.model_dump(mode="json"),
|
|
6582
6632
|
}
|
|
6583
6633
|
|
|
@@ -7775,11 +7825,24 @@ class AiBuilderFacade:
|
|
|
7775
7825
|
suggested_next_call={"tool_name": "chart_get", "arguments": {"profile": profile, "chart_id": chart_id}},
|
|
7776
7826
|
)
|
|
7777
7827
|
|
|
7828
|
+
field_name_by_id: dict[str, str] = {}
|
|
7829
|
+
data_source = config.get("dataSource") if isinstance(config.get("dataSource"), dict) else {}
|
|
7830
|
+
data_source_app_key = str(data_source.get("dataSourceId") or config.get("dataSourceId") or "").strip()
|
|
7831
|
+
if data_source_app_key:
|
|
7832
|
+
field_name_by_id, field_name_error = self._chart_filter_field_names_by_id(profile=profile, app_key=data_source_app_key)
|
|
7833
|
+
if field_name_error:
|
|
7834
|
+
warnings.append(
|
|
7835
|
+
_warning(
|
|
7836
|
+
"CHART_FILTER_FIELD_NAMES_UNRESOLVED",
|
|
7837
|
+
"chart config was read, but form fields could not be loaded to resolve filter field names",
|
|
7838
|
+
**field_name_error,
|
|
7839
|
+
)
|
|
7840
|
+
)
|
|
7778
7841
|
response = ChartGetResponse(
|
|
7779
7842
|
chart_id=chart_id,
|
|
7780
7843
|
base=deepcopy(base) if isinstance(base, dict) else {},
|
|
7781
7844
|
visibility=_public_visibility_from_chart_visible_auth(base.get("visibleAuth")),
|
|
7782
|
-
filters=_public_chart_filter_groups_from_qingbi_config(config) if isinstance(config, dict) else [],
|
|
7845
|
+
filters=_public_chart_filter_groups_from_qingbi_config(config, field_name_by_id=field_name_by_id) if isinstance(config, dict) else [],
|
|
7783
7846
|
group_by=_public_chart_group_by_from_qingbi_config(config) if isinstance(config, dict) else [],
|
|
7784
7847
|
metrics=_public_chart_metrics_from_qingbi_config(config) if isinstance(config, dict) else [],
|
|
7785
7848
|
config=deepcopy(config) if isinstance(config, dict) else {},
|
|
@@ -16288,11 +16351,38 @@ def _qingbi_chart_filter_value_to_text(*, value: Any, form_field: dict[str, Any]
|
|
|
16288
16351
|
reject(value)
|
|
16289
16352
|
|
|
16290
16353
|
|
|
16291
|
-
def
|
|
16354
|
+
def _chart_field_names_by_id_from_public_fields(*, app_key: str, fields: list[dict[str, Any]]) -> dict[str, str]:
|
|
16355
|
+
field_name_by_id: dict[str, str] = {}
|
|
16356
|
+
for field in fields:
|
|
16357
|
+
if not isinstance(field, dict):
|
|
16358
|
+
continue
|
|
16359
|
+
name = str(field.get("name") or "").strip()
|
|
16360
|
+
if not name:
|
|
16361
|
+
continue
|
|
16362
|
+
que_id = field.get("que_id")
|
|
16363
|
+
field_id = str(field.get("field_id") or "").strip()
|
|
16364
|
+
for raw_key in (
|
|
16365
|
+
que_id,
|
|
16366
|
+
field_id,
|
|
16367
|
+
f"{app_key}:{que_id}" if que_id is not None else None,
|
|
16368
|
+
f"{app_key}:{field_id}" if field_id else None,
|
|
16369
|
+
):
|
|
16370
|
+
key = str(raw_key or "").strip()
|
|
16371
|
+
if key:
|
|
16372
|
+
field_name_by_id.setdefault(key, name)
|
|
16373
|
+
return field_name_by_id
|
|
16374
|
+
|
|
16375
|
+
|
|
16376
|
+
def _public_chart_filter_groups_from_qingbi_config(
|
|
16377
|
+
config: dict[str, Any],
|
|
16378
|
+
*,
|
|
16379
|
+
field_name_by_id: dict[str, str] | None = None,
|
|
16380
|
+
) -> list[list[dict[str, Any]]]:
|
|
16292
16381
|
groups: list[list[dict[str, Any]]] = []
|
|
16293
16382
|
raw_groups = config.get("beforeAggregationFilterMatrix")
|
|
16294
16383
|
if not isinstance(raw_groups, list):
|
|
16295
16384
|
return groups
|
|
16385
|
+
resolved_field_name_by_id = field_name_by_id or {}
|
|
16296
16386
|
for raw_group in raw_groups:
|
|
16297
16387
|
if not isinstance(raw_group, list):
|
|
16298
16388
|
continue
|
|
@@ -16302,19 +16392,26 @@ def _public_chart_filter_groups_from_qingbi_config(config: dict[str, Any]) -> li
|
|
|
16302
16392
|
continue
|
|
16303
16393
|
operator = _public_chart_filter_operator_from_judge_type(raw_rule.get("judgeType"))
|
|
16304
16394
|
field_id = raw_rule.get("fieldId") or raw_rule.get("field_id")
|
|
16305
|
-
|
|
16395
|
+
field_id_text = _stringify_condition_value(field_id).strip() if field_id is not None else ""
|
|
16396
|
+
raw_field_name = (
|
|
16306
16397
|
raw_rule.get("fieldName")
|
|
16307
16398
|
or raw_rule.get("field_name")
|
|
16308
16399
|
or raw_rule.get("queTitle")
|
|
16309
16400
|
or raw_rule.get("title")
|
|
16310
|
-
|
|
16401
|
+
)
|
|
16402
|
+
raw_field_name_text = _stringify_condition_value(raw_field_name).strip()
|
|
16403
|
+
field_name = (
|
|
16404
|
+
resolved_field_name_by_id.get(raw_field_name_text)
|
|
16405
|
+
or resolved_field_name_by_id.get(field_id_text)
|
|
16406
|
+
or raw_field_name_text
|
|
16407
|
+
or field_id_text
|
|
16311
16408
|
)
|
|
16312
16409
|
public_rule: dict[str, Any] = {
|
|
16313
|
-
"field_name":
|
|
16410
|
+
"field_name": field_name,
|
|
16314
16411
|
"operator": operator,
|
|
16315
16412
|
}
|
|
16316
16413
|
if field_id is not None:
|
|
16317
|
-
public_rule["field_id"] =
|
|
16414
|
+
public_rule["field_id"] = field_id_text
|
|
16318
16415
|
values = _public_chart_filter_values_from_rule(raw_rule, operator=operator)
|
|
16319
16416
|
if values:
|
|
16320
16417
|
public_rule["values"] = values
|
|
@@ -23330,6 +23427,24 @@ def _extract_view_question_entries(questions: Any) -> list[dict[str, Any]]:
|
|
|
23330
23427
|
"visible": visible,
|
|
23331
23428
|
"display_order": display_order if display_order is not None else fallback_order,
|
|
23332
23429
|
}
|
|
23430
|
+
option_details: list[dict[str, Any]] = []
|
|
23431
|
+
option_values: list[str] = []
|
|
23432
|
+
raw_options = item.get("options")
|
|
23433
|
+
if isinstance(raw_options, list):
|
|
23434
|
+
for raw_option in raw_options:
|
|
23435
|
+
if not isinstance(raw_option, dict):
|
|
23436
|
+
continue
|
|
23437
|
+
option_id = raw_option.get("optId")
|
|
23438
|
+
option_value = str(raw_option.get("optValue") or "").strip()
|
|
23439
|
+
if not option_value and option_id is None:
|
|
23440
|
+
continue
|
|
23441
|
+
if option_value:
|
|
23442
|
+
option_values.append(option_value)
|
|
23443
|
+
option_details.append({"id": option_id, "value": option_value or str(option_id)})
|
|
23444
|
+
if option_values:
|
|
23445
|
+
entry["options"] = option_values
|
|
23446
|
+
if option_details:
|
|
23447
|
+
entry["option_details"] = option_details
|
|
23333
23448
|
width = _coerce_positive_int(item.get("width"))
|
|
23334
23449
|
if width is not None:
|
|
23335
23450
|
entry["width"] = width
|
|
@@ -23348,7 +23463,14 @@ def _view_field_lookup_from_question_entries(entries: list[dict[str, Any]]) -> d
|
|
|
23348
23463
|
que_id = _coerce_positive_int(entry.get("field_id") or entry.get("que_id") or entry.get("queId"))
|
|
23349
23464
|
if not name or que_id is None:
|
|
23350
23465
|
continue
|
|
23351
|
-
|
|
23466
|
+
field_entry: dict[str, Any] = {"name": name, "que_id": que_id}
|
|
23467
|
+
options = entry.get("options")
|
|
23468
|
+
if isinstance(options, list) and options:
|
|
23469
|
+
field_entry["options"] = [str(value) for value in options if str(value or "").strip()]
|
|
23470
|
+
option_details = entry.get("option_details")
|
|
23471
|
+
if isinstance(option_details, list) and option_details:
|
|
23472
|
+
field_entry["option_details"] = [deepcopy(value) for value in option_details if isinstance(value, dict)]
|
|
23473
|
+
fields_by_name.setdefault(name, field_entry)
|
|
23352
23474
|
return fields_by_name
|
|
23353
23475
|
|
|
23354
23476
|
|
|
@@ -26464,6 +26586,38 @@ def _view_filter_rule_values_for_signature(rule: dict[str, Any]) -> list[str]:
|
|
|
26464
26586
|
return fallback_values
|
|
26465
26587
|
|
|
26466
26588
|
|
|
26589
|
+
def _view_filter_option_value_by_id(field: dict[str, Any]) -> dict[str, str]:
|
|
26590
|
+
value_by_id: dict[str, str] = {}
|
|
26591
|
+
for detail in field.get("option_details") or []:
|
|
26592
|
+
if not isinstance(detail, dict):
|
|
26593
|
+
continue
|
|
26594
|
+
option_id = detail.get("id")
|
|
26595
|
+
option_value = str(detail.get("value") or "").strip()
|
|
26596
|
+
if option_id is None or not option_value:
|
|
26597
|
+
continue
|
|
26598
|
+
value_by_id[str(option_id)] = option_value
|
|
26599
|
+
return value_by_id
|
|
26600
|
+
|
|
26601
|
+
|
|
26602
|
+
def _public_view_filter_rule_values(rule: dict[str, Any], *, field: dict[str, Any]) -> list[str]:
|
|
26603
|
+
value_by_id = _view_filter_option_value_by_id(field)
|
|
26604
|
+
detail_value_by_id: dict[str, str] = {}
|
|
26605
|
+
ordered_detail_values: list[str] = []
|
|
26606
|
+
for detail in rule.get("judgeValueDetails") or []:
|
|
26607
|
+
if not isinstance(detail, dict):
|
|
26608
|
+
continue
|
|
26609
|
+
detail_value = str(detail.get("value") or "").strip()
|
|
26610
|
+
detail_id = detail.get("id")
|
|
26611
|
+
if detail_value:
|
|
26612
|
+
ordered_detail_values.append(detail_value)
|
|
26613
|
+
if detail_id is not None:
|
|
26614
|
+
detail_value_by_id[str(detail_id)] = detail_value
|
|
26615
|
+
values = [str(value) for value in (rule.get("judgeValues") or []) if str(value or "").strip()]
|
|
26616
|
+
if values:
|
|
26617
|
+
return [value_by_id.get(value) or detail_value_by_id.get(value) or value for value in values]
|
|
26618
|
+
return ordered_detail_values
|
|
26619
|
+
|
|
26620
|
+
|
|
26467
26621
|
def _view_filter_groups_signature(groups: Any) -> list[list[dict[str, Any]]]:
|
|
26468
26622
|
signature: list[list[dict[str, Any]]] = []
|
|
26469
26623
|
for group in _normalize_view_filter_groups_for_compare(groups):
|
|
@@ -26545,7 +26699,7 @@ def _public_view_filter_groups_from_match_rules(
|
|
|
26545
26699
|
for rule in group:
|
|
26546
26700
|
que_id = _coerce_positive_int(rule.get("queId")) or 0
|
|
26547
26701
|
field = fields_by_que_id.get(que_id) or {}
|
|
26548
|
-
values =
|
|
26702
|
+
values = _public_view_filter_rule_values(rule, field=field)
|
|
26549
26703
|
public_rule: dict[str, Any] = {
|
|
26550
26704
|
"field_name": str(field.get("name") or rule.get("queTitle") or que_id),
|
|
26551
26705
|
"operator": _public_view_filter_operator_from_judge_type(rule.get("judgeType")),
|