@josephyan/qingflow-cli 1.1.7 → 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 CHANGED
@@ -3,13 +3,13 @@
3
3
  Install:
4
4
 
5
5
  ```bash
6
- npm install @josephyan/qingflow-cli@1.1.7
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.7 qingflow
12
+ npx -y -p @josephyan/qingflow-cli@1.1.8 qingflow
13
13
  ```
14
14
 
15
15
  Environment:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@josephyan/qingflow-cli",
3
- "version": "1.1.7",
3
+ "version": "1.1.8",
4
4
  "description": "Human-friendly Qingflow command line interface for auth, record operations, import, tasks, and stable builder flows.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "qingflow-mcp"
7
- version = "1.1.7"
7
+ version = "1.1.8"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -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": {"chart_visibility_read_errors": chart_visibility_read_errors} if chart_visibility_read_errors else {},
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": True,
6608
+ "verified": not chart_config_read_errors,
6581
6609
  **response.model_dump(mode="json"),
6582
6610
  }
6583
6611
 
@@ -23330,6 +23358,24 @@ def _extract_view_question_entries(questions: Any) -> list[dict[str, Any]]:
23330
23358
  "visible": visible,
23331
23359
  "display_order": display_order if display_order is not None else fallback_order,
23332
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
23333
23379
  width = _coerce_positive_int(item.get("width"))
23334
23380
  if width is not None:
23335
23381
  entry["width"] = width
@@ -23348,7 +23394,14 @@ def _view_field_lookup_from_question_entries(entries: list[dict[str, Any]]) -> d
23348
23394
  que_id = _coerce_positive_int(entry.get("field_id") or entry.get("que_id") or entry.get("queId"))
23349
23395
  if not name or que_id is None:
23350
23396
  continue
23351
- fields_by_name.setdefault(name, {"name": name, "que_id": que_id})
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)
23352
23405
  return fields_by_name
23353
23406
 
23354
23407
 
@@ -26464,6 +26517,38 @@ def _view_filter_rule_values_for_signature(rule: dict[str, Any]) -> list[str]:
26464
26517
  return fallback_values
26465
26518
 
26466
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
+
26467
26552
  def _view_filter_groups_signature(groups: Any) -> list[list[dict[str, Any]]]:
26468
26553
  signature: list[list[dict[str, Any]]] = []
26469
26554
  for group in _normalize_view_filter_groups_for_compare(groups):
@@ -26545,7 +26630,7 @@ def _public_view_filter_groups_from_match_rules(
26545
26630
  for rule in group:
26546
26631
  que_id = _coerce_positive_int(rule.get("queId")) or 0
26547
26632
  field = fields_by_que_id.get(que_id) or {}
26548
- values = _view_filter_rule_values_for_signature(rule)
26633
+ values = _public_view_filter_rule_values(rule, field=field)
26549
26634
  public_rule: dict[str, Any] = {
26550
26635
  "field_name": str(field.get("name") or rule.get("queTitle") or que_id),
26551
26636
  "operator": _public_view_filter_operator_from_judge_type(rule.get("judgeType")),