@josephyan/qingflow-cli 0.2.0-beta.1014 → 0.2.0-beta.1015
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@0.2.0-beta.
|
|
6
|
+
npm install @josephyan/qingflow-cli@0.2.0-beta.1015
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
Run:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npx -y -p @josephyan/qingflow-cli@0.2.0-beta.
|
|
12
|
+
npx -y -p @josephyan/qingflow-cli@0.2.0-beta.1015 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": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.1015",
|
|
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
|
@@ -54,8 +54,8 @@ class QingflowApiError(Exception):
|
|
|
54
54
|
)
|
|
55
55
|
|
|
56
56
|
@classmethod
|
|
57
|
-
def config_error(cls, message: str) -> "QingflowApiError":
|
|
58
|
-
return cls(category="config", message=message)
|
|
57
|
+
def config_error(cls, message: str, *, details: JSONObject | None = None) -> "QingflowApiError":
|
|
58
|
+
return cls(category="config", message=message, details=details)
|
|
59
59
|
|
|
60
60
|
@classmethod
|
|
61
61
|
def not_supported(cls, message: str) -> "QingflowApiError":
|
|
@@ -19,6 +19,7 @@ from .base import ToolBase, tool_cn_name
|
|
|
19
19
|
from .record_tools import (
|
|
20
20
|
AccessibleViewRoute,
|
|
21
21
|
DEFAULT_LIST_PAGE_SIZE,
|
|
22
|
+
FormField,
|
|
22
23
|
LAYOUT_ONLY_QUE_TYPES,
|
|
23
24
|
RecordTools,
|
|
24
25
|
_normalize_public_column_selectors,
|
|
@@ -736,6 +737,11 @@ class ExportTools(ToolBase):
|
|
|
736
737
|
filter_payload: JSONObject = {}
|
|
737
738
|
if resolved_view.kind == "system" and resolved_view.list_type is not None:
|
|
738
739
|
filter_payload["type"] = resolved_view.list_type
|
|
740
|
+
elif resolved_view.kind == "custom":
|
|
741
|
+
# Custom-view native export later flows through shared data-export code that
|
|
742
|
+
# expects a list semantics integer. Frontend export treats custom views as
|
|
743
|
+
# creator-all style export, so mirror LIST_CREATOR_ALL here.
|
|
744
|
+
filter_payload["type"] = DEFAULT_RECORD_LIST_TYPE
|
|
739
745
|
if selected_record_ids:
|
|
740
746
|
filter_payload["applyIds"] = selected_record_ids
|
|
741
747
|
return {
|
|
@@ -763,18 +769,14 @@ class ExportTools(ToolBase):
|
|
|
763
769
|
)
|
|
764
770
|
index = browse_scope["index"]
|
|
765
771
|
visible_question_ids = cast(set[int], browse_scope.get("visible_question_ids") or set())
|
|
766
|
-
ordered_visible_fields =
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
resolved_view=resolved_view,
|
|
775
|
-
)
|
|
776
|
-
if field.que_id in visible_question_ids and field.que_type not in LAYOUT_ONLY_QUE_TYPES
|
|
777
|
-
]
|
|
772
|
+
ordered_visible_fields, warnings = self._resolve_exportable_fields(
|
|
773
|
+
profile=profile,
|
|
774
|
+
context=context,
|
|
775
|
+
app_key=app_key,
|
|
776
|
+
resolved_view=resolved_view,
|
|
777
|
+
index=index,
|
|
778
|
+
visible_question_ids=visible_question_ids,
|
|
779
|
+
)
|
|
778
780
|
if not ordered_visible_fields:
|
|
779
781
|
ordered_visible_fields = [
|
|
780
782
|
field
|
|
@@ -805,7 +807,6 @@ class ExportTools(ToolBase):
|
|
|
805
807
|
else:
|
|
806
808
|
selected_fields = ordered_visible_fields
|
|
807
809
|
question_export_config_list: list[JSONObject] = []
|
|
808
|
-
warnings: list[JSONObject] = []
|
|
809
810
|
for field in selected_fields:
|
|
810
811
|
if field.que_type is None:
|
|
811
812
|
warnings.append(
|
|
@@ -830,6 +831,79 @@ class ExportTools(ToolBase):
|
|
|
830
831
|
)
|
|
831
832
|
return {"questionExportConfigList": question_export_config_list}, warnings
|
|
832
833
|
|
|
834
|
+
def _resolve_exportable_fields(
|
|
835
|
+
self,
|
|
836
|
+
*,
|
|
837
|
+
profile: str,
|
|
838
|
+
context,
|
|
839
|
+
app_key: str,
|
|
840
|
+
resolved_view: AccessibleViewRoute,
|
|
841
|
+
index,
|
|
842
|
+
visible_question_ids: set[int],
|
|
843
|
+
) -> tuple[list[FormField], list[JSONObject]]: # type: ignore[no-untyped-def]
|
|
844
|
+
warnings: list[JSONObject] = []
|
|
845
|
+
if resolved_view.kind == "custom" and resolved_view.view_selection is not None:
|
|
846
|
+
custom_fields = self._resolve_custom_view_exportable_fields(
|
|
847
|
+
profile=profile,
|
|
848
|
+
context=context,
|
|
849
|
+
app_key=app_key,
|
|
850
|
+
view_key=resolved_view.view_selection.view_key,
|
|
851
|
+
index=index,
|
|
852
|
+
visible_question_ids=visible_question_ids,
|
|
853
|
+
)
|
|
854
|
+
if custom_fields is not None:
|
|
855
|
+
return custom_fields, warnings
|
|
856
|
+
warnings.append(
|
|
857
|
+
{
|
|
858
|
+
"code": "EXPORT_VIEW_CONFIG_PARTIAL",
|
|
859
|
+
"message": "custom view export fields fell back to schema order because viewConfig could not provide exportable field entries",
|
|
860
|
+
}
|
|
861
|
+
)
|
|
862
|
+
ordered_visible_fields = [
|
|
863
|
+
field
|
|
864
|
+
for field in self._record_tools._schema_fields_for_mode(
|
|
865
|
+
profile,
|
|
866
|
+
context,
|
|
867
|
+
app_key,
|
|
868
|
+
index,
|
|
869
|
+
schema_mode="browse",
|
|
870
|
+
resolved_view=resolved_view,
|
|
871
|
+
)
|
|
872
|
+
if field.que_id in visible_question_ids and field.que_type not in LAYOUT_ONLY_QUE_TYPES
|
|
873
|
+
]
|
|
874
|
+
return ordered_visible_fields, warnings
|
|
875
|
+
|
|
876
|
+
def _resolve_custom_view_exportable_fields(
|
|
877
|
+
self,
|
|
878
|
+
*,
|
|
879
|
+
profile: str,
|
|
880
|
+
context,
|
|
881
|
+
app_key: str,
|
|
882
|
+
view_key: str,
|
|
883
|
+
index,
|
|
884
|
+
visible_question_ids: set[int],
|
|
885
|
+
) -> list[FormField] | None: # type: ignore[no-untyped-def]
|
|
886
|
+
view_config = self._record_tools._get_view_config(profile, context, view_key)
|
|
887
|
+
if not isinstance(view_config, dict):
|
|
888
|
+
return None
|
|
889
|
+
entries = _extract_export_view_question_entries(view_config.get("viewgraphQuestions"))
|
|
890
|
+
if not entries:
|
|
891
|
+
return []
|
|
892
|
+
ordered_fields: list[FormField] = []
|
|
893
|
+
seen: set[int] = set()
|
|
894
|
+
for entry in entries:
|
|
895
|
+
if not bool(entry.get("downloadable", True)):
|
|
896
|
+
continue
|
|
897
|
+
field_id = _coerce_int(entry.get("field_id"))
|
|
898
|
+
if field_id is None or field_id in seen:
|
|
899
|
+
continue
|
|
900
|
+
field = cast(FormField | None, cast(Any, index).by_id.get(str(field_id)))
|
|
901
|
+
if field is None or field.que_type in LAYOUT_ONLY_QUE_TYPES:
|
|
902
|
+
continue
|
|
903
|
+
ordered_fields.append(field)
|
|
904
|
+
seen.add(field_id)
|
|
905
|
+
return ordered_fields
|
|
906
|
+
|
|
833
907
|
def _estimate_export_result_amount(
|
|
834
908
|
self,
|
|
835
909
|
context,
|
|
@@ -1241,6 +1315,58 @@ def _coerce_int(value: Any) -> int | None:
|
|
|
1241
1315
|
return None
|
|
1242
1316
|
|
|
1243
1317
|
|
|
1318
|
+
def _coerce_positive_int(value: Any) -> int | None:
|
|
1319
|
+
parsed = _coerce_int(value)
|
|
1320
|
+
if parsed is None or parsed <= 0:
|
|
1321
|
+
return None
|
|
1322
|
+
return parsed
|
|
1323
|
+
|
|
1324
|
+
|
|
1325
|
+
def _extract_export_view_question_entries(questions: Any) -> list[JSONObject]:
|
|
1326
|
+
if not isinstance(questions, list):
|
|
1327
|
+
return []
|
|
1328
|
+
entries: list[JSONObject] = []
|
|
1329
|
+
fallback_order = 0
|
|
1330
|
+
|
|
1331
|
+
def walk(nodes: Any) -> None:
|
|
1332
|
+
nonlocal fallback_order
|
|
1333
|
+
if not isinstance(nodes, list):
|
|
1334
|
+
return
|
|
1335
|
+
for item in nodes:
|
|
1336
|
+
if not isinstance(item, dict):
|
|
1337
|
+
continue
|
|
1338
|
+
children: list[Any] = []
|
|
1339
|
+
for child_key in ("innerQues", "subQues", "innerQuestions", "subQuestions"):
|
|
1340
|
+
child_value = item.get(child_key)
|
|
1341
|
+
if isinstance(child_value, list) and child_value:
|
|
1342
|
+
children.extend(child_value)
|
|
1343
|
+
if children:
|
|
1344
|
+
walk(children)
|
|
1345
|
+
continue
|
|
1346
|
+
field_id = _coerce_int(item.get("queId"))
|
|
1347
|
+
if field_id is None:
|
|
1348
|
+
continue
|
|
1349
|
+
fallback_order += 1
|
|
1350
|
+
downloadable_raw = item.get("beingDownload")
|
|
1351
|
+
entries.append(
|
|
1352
|
+
{
|
|
1353
|
+
"field_id": field_id,
|
|
1354
|
+
"name": str(item.get("queTitle") or "").strip(),
|
|
1355
|
+
"display_order": _coerce_positive_int(item.get("displayOrdinal")) or fallback_order,
|
|
1356
|
+
"downloadable": bool(downloadable_raw) if downloadable_raw is not None else True,
|
|
1357
|
+
}
|
|
1358
|
+
)
|
|
1359
|
+
|
|
1360
|
+
walk(questions)
|
|
1361
|
+
return sorted(
|
|
1362
|
+
entries,
|
|
1363
|
+
key=lambda entry: (
|
|
1364
|
+
_coerce_positive_int(entry.get("display_order")) if _coerce_positive_int(entry.get("display_order")) is not None else 10**9,
|
|
1365
|
+
str(entry.get("name") or ""),
|
|
1366
|
+
),
|
|
1367
|
+
)
|
|
1368
|
+
|
|
1369
|
+
|
|
1244
1370
|
def _normalize_export_columns(columns: list[JSONObject | int]) -> list[int]:
|
|
1245
1371
|
normalized: list[int] = []
|
|
1246
1372
|
for field_id in _normalize_public_column_selectors(columns):
|
|
@@ -6890,6 +6890,8 @@ class RecordTools(ToolBase):
|
|
|
6890
6890
|
isinstance(payload.get("viewgraphLimit"), list)
|
|
6891
6891
|
or isinstance(payload.get("viewConfig"), dict)
|
|
6892
6892
|
or isinstance(payload.get("viewgraphConfig"), dict)
|
|
6893
|
+
or isinstance(payload.get("viewgraphQuestions"), list)
|
|
6894
|
+
or isinstance(payload.get("viewgraphQueIds"), list)
|
|
6893
6895
|
):
|
|
6894
6896
|
config = payload
|
|
6895
6897
|
else:
|