@josephyan/qingflow-cli 0.2.0-beta.63 → 0.2.0-beta.64
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 +406 -19
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.64
|
|
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.64 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.64",
|
|
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
|
@@ -1406,6 +1406,15 @@ class AiBuilderFacade:
|
|
|
1406
1406
|
def finalize(response: JSONObject) -> JSONObject:
|
|
1407
1407
|
return _apply_permission_outcomes(response, *permission_outcomes)
|
|
1408
1408
|
|
|
1409
|
+
edit_version_no, edit_context_error = self._ensure_app_edit_context(
|
|
1410
|
+
profile=profile,
|
|
1411
|
+
app_key=app_key,
|
|
1412
|
+
normalized_args=normalized_args,
|
|
1413
|
+
failure_code="CUSTOM_BUTTON_CREATE_FAILED",
|
|
1414
|
+
)
|
|
1415
|
+
if edit_context_error is not None:
|
|
1416
|
+
return finalize(edit_context_error)
|
|
1417
|
+
|
|
1409
1418
|
create_result = self.buttons.custom_button_create(
|
|
1410
1419
|
profile=profile,
|
|
1411
1420
|
app_key=app_key,
|
|
@@ -1419,7 +1428,7 @@ class AiBuilderFacade:
|
|
|
1419
1428
|
"CUSTOM_BUTTON_CREATE_FAILED",
|
|
1420
1429
|
"custom button create succeeded but no button_id was returned",
|
|
1421
1430
|
normalized_args=normalized_args,
|
|
1422
|
-
details={"app_key": app_key, "result": deepcopy(raw_result)},
|
|
1431
|
+
details={"app_key": app_key, "result": deepcopy(raw_result), "edit_version_no": edit_version_no},
|
|
1423
1432
|
suggested_next_call={"tool_name": "app_custom_button_list", "arguments": {"profile": profile, "app_key": app_key}},
|
|
1424
1433
|
)
|
|
1425
1434
|
)
|
|
@@ -1444,6 +1453,7 @@ class AiBuilderFacade:
|
|
|
1444
1453
|
"details": {
|
|
1445
1454
|
"app_key": app_key,
|
|
1446
1455
|
"button_id": button_id,
|
|
1456
|
+
"edit_version_no": edit_version_no,
|
|
1447
1457
|
"transport_error": _transport_error_payload(api_error),
|
|
1448
1458
|
},
|
|
1449
1459
|
"request_id": api_error.request_id,
|
|
@@ -1487,6 +1497,7 @@ class AiBuilderFacade:
|
|
|
1487
1497
|
"verified": True,
|
|
1488
1498
|
"app_key": app_key,
|
|
1489
1499
|
"button_id": button_id,
|
|
1500
|
+
"edit_version_no": edit_version_no,
|
|
1490
1501
|
"button": button,
|
|
1491
1502
|
}
|
|
1492
1503
|
)
|
|
@@ -1514,6 +1525,15 @@ class AiBuilderFacade:
|
|
|
1514
1525
|
def finalize(response: JSONObject) -> JSONObject:
|
|
1515
1526
|
return _apply_permission_outcomes(response, *permission_outcomes)
|
|
1516
1527
|
|
|
1528
|
+
edit_version_no, edit_context_error = self._ensure_app_edit_context(
|
|
1529
|
+
profile=profile,
|
|
1530
|
+
app_key=app_key,
|
|
1531
|
+
normalized_args=normalized_args,
|
|
1532
|
+
failure_code="CUSTOM_BUTTON_UPDATE_FAILED",
|
|
1533
|
+
)
|
|
1534
|
+
if edit_context_error is not None:
|
|
1535
|
+
return finalize(edit_context_error)
|
|
1536
|
+
|
|
1517
1537
|
self.buttons.custom_button_update(
|
|
1518
1538
|
profile=profile,
|
|
1519
1539
|
app_key=app_key,
|
|
@@ -1541,6 +1561,7 @@ class AiBuilderFacade:
|
|
|
1541
1561
|
"details": {
|
|
1542
1562
|
"app_key": app_key,
|
|
1543
1563
|
"button_id": button_id,
|
|
1564
|
+
"edit_version_no": edit_version_no,
|
|
1544
1565
|
"transport_error": _transport_error_payload(api_error),
|
|
1545
1566
|
},
|
|
1546
1567
|
"request_id": api_error.request_id,
|
|
@@ -1584,6 +1605,7 @@ class AiBuilderFacade:
|
|
|
1584
1605
|
"verified": True,
|
|
1585
1606
|
"app_key": app_key,
|
|
1586
1607
|
"button_id": button_id,
|
|
1608
|
+
"edit_version_no": edit_version_no,
|
|
1587
1609
|
"button": button,
|
|
1588
1610
|
}
|
|
1589
1611
|
)
|
|
@@ -2567,7 +2589,17 @@ class AiBuilderFacade:
|
|
|
2567
2589
|
upsert_views = _build_views_preset(request.preset, list(field_names))
|
|
2568
2590
|
blocking_issues: list[dict[str, Any]] = []
|
|
2569
2591
|
for patch in upsert_views:
|
|
2570
|
-
|
|
2592
|
+
raw_columns = [str(name or "").strip() for name in (patch.get("columns") or []) if str(name or "").strip()]
|
|
2593
|
+
columns = _filter_known_system_view_columns(raw_columns)
|
|
2594
|
+
if patch.get("type") in {"table", "card"} and raw_columns and not columns:
|
|
2595
|
+
blocking_issues.append(
|
|
2596
|
+
{
|
|
2597
|
+
"error_code": "VALIDATION_ERROR",
|
|
2598
|
+
"view_name": patch.get("name"),
|
|
2599
|
+
"message": "view columns must include at least one real app field; system columns cannot be applied directly",
|
|
2600
|
+
"ignored_system_columns": [name for name in raw_columns if name in _KNOWN_SYSTEM_VIEW_COLUMNS],
|
|
2601
|
+
}
|
|
2602
|
+
)
|
|
2571
2603
|
missing_columns = [name for name in columns if name not in field_names]
|
|
2572
2604
|
if missing_columns:
|
|
2573
2605
|
blocking_issues.append({"error_code": "UNKNOWN_VIEW_FIELD", "view_name": patch.get("name"), "missing_fields": missing_columns})
|
|
@@ -3644,6 +3676,7 @@ class AiBuilderFacade:
|
|
|
3644
3676
|
for patch in upsert_views
|
|
3645
3677
|
)
|
|
3646
3678
|
valid_custom_button_ids: set[int] = set()
|
|
3679
|
+
custom_button_details_by_id: dict[int, dict[str, Any]] = {}
|
|
3647
3680
|
if requires_custom_button_validation:
|
|
3648
3681
|
try:
|
|
3649
3682
|
button_listing = self.buttons.custom_button_list(
|
|
@@ -3668,6 +3701,26 @@ class AiBuilderFacade:
|
|
|
3668
3701
|
for item in (button_listing.get("items") or [])
|
|
3669
3702
|
if isinstance(item, dict) and (button_id := _coerce_positive_int(item.get("button_id"))) is not None
|
|
3670
3703
|
}
|
|
3704
|
+
referenced_custom_button_ids = {
|
|
3705
|
+
binding.button_id
|
|
3706
|
+
for patch in upsert_views
|
|
3707
|
+
for binding in (patch.buttons or [])
|
|
3708
|
+
if binding.button_type == PublicViewButtonType.custom and binding.button_id in valid_custom_button_ids
|
|
3709
|
+
}
|
|
3710
|
+
for button_id in sorted(referenced_custom_button_ids):
|
|
3711
|
+
try:
|
|
3712
|
+
detail = self.buttons.custom_button_get(
|
|
3713
|
+
profile=profile,
|
|
3714
|
+
app_key=app_key,
|
|
3715
|
+
button_id=button_id,
|
|
3716
|
+
being_draft=True,
|
|
3717
|
+
include_raw=False,
|
|
3718
|
+
)
|
|
3719
|
+
except (QingflowApiError, RuntimeError):
|
|
3720
|
+
continue
|
|
3721
|
+
detail_result = detail.get("result")
|
|
3722
|
+
if isinstance(detail_result, dict):
|
|
3723
|
+
custom_button_details_by_id[button_id] = _normalize_custom_button_detail(detail_result)
|
|
3671
3724
|
removed: list[str] = []
|
|
3672
3725
|
view_results: list[dict[str, Any]] = []
|
|
3673
3726
|
for name in remove_views:
|
|
@@ -3704,7 +3757,24 @@ class AiBuilderFacade:
|
|
|
3704
3757
|
and _extract_view_name(view) not in remove_views
|
|
3705
3758
|
]
|
|
3706
3759
|
for ordinal, patch in enumerate(upsert_views, start=1):
|
|
3707
|
-
|
|
3760
|
+
apply_columns = _resolve_view_visible_field_names(patch)
|
|
3761
|
+
ignored_system_columns = [
|
|
3762
|
+
name
|
|
3763
|
+
for name in [str(value or "").strip() for value in patch.columns]
|
|
3764
|
+
if name in _KNOWN_SYSTEM_VIEW_COLUMNS
|
|
3765
|
+
]
|
|
3766
|
+
if patch.type in {PublicViewType.table, PublicViewType.card} and patch.columns and not apply_columns:
|
|
3767
|
+
return _failed(
|
|
3768
|
+
"VALIDATION_ERROR",
|
|
3769
|
+
"view columns must include at least one real app field; system columns cannot be applied directly",
|
|
3770
|
+
normalized_args=normalized_args,
|
|
3771
|
+
details={
|
|
3772
|
+
"app_key": app_key,
|
|
3773
|
+
"view_name": patch.name,
|
|
3774
|
+
"ignored_system_columns": ignored_system_columns,
|
|
3775
|
+
},
|
|
3776
|
+
)
|
|
3777
|
+
missing_columns = [name for name in apply_columns if name not in field_names]
|
|
3708
3778
|
if missing_columns:
|
|
3709
3779
|
return _failed(
|
|
3710
3780
|
"UNKNOWN_VIEW_FIELD",
|
|
@@ -3714,6 +3784,7 @@ class AiBuilderFacade:
|
|
|
3714
3784
|
"app_key": app_key,
|
|
3715
3785
|
"view_name": patch.name,
|
|
3716
3786
|
"missing_fields": missing_columns,
|
|
3787
|
+
"ignored_system_columns": ignored_system_columns,
|
|
3717
3788
|
},
|
|
3718
3789
|
missing_fields=missing_columns,
|
|
3719
3790
|
suggested_next_call={"tool_name": "app_read_fields", "arguments": {"profile": profile, "app_key": app_key}},
|
|
@@ -3784,7 +3855,10 @@ class AiBuilderFacade:
|
|
|
3784
3855
|
allowed_values=first_issue.get("allowed_values") or {"view_types": [member.value for member in PublicViewType], "view.filter.operator": [member.value for member in ViewFilterOperator]},
|
|
3785
3856
|
suggested_next_call={"tool_name": "app_custom_button_list", "arguments": {"profile": profile, "app_key": app_key}},
|
|
3786
3857
|
)
|
|
3787
|
-
expected_button_summary =
|
|
3858
|
+
expected_button_summary = _normalize_expected_view_buttons_for_compare(
|
|
3859
|
+
explicit_button_dtos or [],
|
|
3860
|
+
custom_button_details_by_id=custom_button_details_by_id,
|
|
3861
|
+
)
|
|
3788
3862
|
matched_existing_view: dict[str, Any] | None = None
|
|
3789
3863
|
existing_key: str | None = None
|
|
3790
3864
|
if patch.view_key:
|
|
@@ -3837,12 +3911,14 @@ class AiBuilderFacade:
|
|
|
3837
3911
|
system_view_sync: dict[str, Any] | None = None
|
|
3838
3912
|
if system_view_list_type is not None and patch.type.value == "table":
|
|
3839
3913
|
operation_phase = "default_view_apply_config_sync"
|
|
3840
|
-
system_view_sync = self.
|
|
3914
|
+
system_view_sync = self._sync_system_view_and_restore_buttons(
|
|
3841
3915
|
profile=profile,
|
|
3842
3916
|
app_key=app_key,
|
|
3917
|
+
viewgraph_key=existing_key,
|
|
3918
|
+
payload=payload,
|
|
3843
3919
|
list_type=system_view_list_type,
|
|
3844
3920
|
schema=schema,
|
|
3845
|
-
visible_field_names=
|
|
3921
|
+
visible_field_names=apply_columns,
|
|
3846
3922
|
)
|
|
3847
3923
|
if not bool(system_view_sync.get("verified")):
|
|
3848
3924
|
failure_entry = {
|
|
@@ -3864,6 +3940,7 @@ class AiBuilderFacade:
|
|
|
3864
3940
|
"list_type": system_view_list_type,
|
|
3865
3941
|
"expected_visible_order": system_view_sync.get("expected_visible_order"),
|
|
3866
3942
|
"actual_visible_order": system_view_sync.get("actual_visible_order"),
|
|
3943
|
+
"apply_columns": apply_columns,
|
|
3867
3944
|
},
|
|
3868
3945
|
}
|
|
3869
3946
|
failed_views.append(failure_entry)
|
|
@@ -3879,6 +3956,7 @@ class AiBuilderFacade:
|
|
|
3879
3956
|
"expected_filters": deepcopy(translated_filters),
|
|
3880
3957
|
"expected_buttons": deepcopy(expected_button_summary),
|
|
3881
3958
|
"system_view_sync": system_view_sync,
|
|
3959
|
+
"apply_columns": deepcopy(apply_columns),
|
|
3882
3960
|
}
|
|
3883
3961
|
)
|
|
3884
3962
|
else:
|
|
@@ -3956,6 +4034,49 @@ class AiBuilderFacade:
|
|
|
3956
4034
|
explicit_button_dtos=fallback_button_dtos,
|
|
3957
4035
|
)
|
|
3958
4036
|
self.views.view_update(profile=profile, viewgraph_key=target_key, payload=fallback_payload)
|
|
4037
|
+
system_view_sync: dict[str, Any] | None = None
|
|
4038
|
+
fallback_system_view_list_type = (
|
|
4039
|
+
_resolve_system_view_list_type(view_key=target_key, view_name=patch.name)
|
|
4040
|
+
if patch.type.value == "table" and target_key
|
|
4041
|
+
else None
|
|
4042
|
+
)
|
|
4043
|
+
if fallback_system_view_list_type is not None:
|
|
4044
|
+
operation_phase = "default_view_apply_config_sync"
|
|
4045
|
+
system_view_sync = self._sync_system_view_and_restore_buttons(
|
|
4046
|
+
profile=profile,
|
|
4047
|
+
app_key=app_key,
|
|
4048
|
+
viewgraph_key=target_key,
|
|
4049
|
+
payload=fallback_payload,
|
|
4050
|
+
list_type=fallback_system_view_list_type,
|
|
4051
|
+
schema=schema,
|
|
4052
|
+
visible_field_names=apply_columns,
|
|
4053
|
+
)
|
|
4054
|
+
if not bool(system_view_sync.get("verified")):
|
|
4055
|
+
failure_entry = {
|
|
4056
|
+
"name": patch.name,
|
|
4057
|
+
"view_key": target_key,
|
|
4058
|
+
"type": patch.type.value,
|
|
4059
|
+
"status": "failed",
|
|
4060
|
+
"error_code": "SYSTEM_VIEW_ORDER_SYNC_FAILED",
|
|
4061
|
+
"message": "default view column order did not verify through app apply/baseInfo readback",
|
|
4062
|
+
"request_id": None,
|
|
4063
|
+
"backend_code": None,
|
|
4064
|
+
"http_status": None,
|
|
4065
|
+
"operation": "sync_default_view",
|
|
4066
|
+
"details": {
|
|
4067
|
+
"app_key": app_key,
|
|
4068
|
+
"view_name": patch.name,
|
|
4069
|
+
"view_key": target_key,
|
|
4070
|
+
"view_type": patch.type.value,
|
|
4071
|
+
"list_type": fallback_system_view_list_type,
|
|
4072
|
+
"expected_visible_order": system_view_sync.get("expected_visible_order"),
|
|
4073
|
+
"actual_visible_order": system_view_sync.get("actual_visible_order"),
|
|
4074
|
+
"apply_columns": apply_columns,
|
|
4075
|
+
},
|
|
4076
|
+
}
|
|
4077
|
+
failed_views.append(failure_entry)
|
|
4078
|
+
view_results.append(failure_entry)
|
|
4079
|
+
continue
|
|
3959
4080
|
if existing_key:
|
|
3960
4081
|
updated.append(patch.name)
|
|
3961
4082
|
view_results.append(
|
|
@@ -3967,6 +4088,8 @@ class AiBuilderFacade:
|
|
|
3967
4088
|
"fallback_applied": True,
|
|
3968
4089
|
"expected_filters": deepcopy(translated_filters),
|
|
3969
4090
|
"expected_buttons": deepcopy(expected_button_summary),
|
|
4091
|
+
"system_view_sync": system_view_sync,
|
|
4092
|
+
"apply_columns": deepcopy(apply_columns),
|
|
3970
4093
|
}
|
|
3971
4094
|
)
|
|
3972
4095
|
else:
|
|
@@ -3980,6 +4103,8 @@ class AiBuilderFacade:
|
|
|
3980
4103
|
"fallback_applied": True,
|
|
3981
4104
|
"expected_filters": deepcopy(translated_filters),
|
|
3982
4105
|
"expected_buttons": deepcopy(expected_button_summary),
|
|
4106
|
+
"system_view_sync": system_view_sync,
|
|
4107
|
+
"apply_columns": deepcopy(apply_columns),
|
|
3983
4108
|
}
|
|
3984
4109
|
)
|
|
3985
4110
|
continue
|
|
@@ -4079,6 +4204,8 @@ class AiBuilderFacade:
|
|
|
4079
4204
|
filter_mismatches: list[dict[str, Any]] = []
|
|
4080
4205
|
button_readback_pending = False
|
|
4081
4206
|
button_mismatches: list[dict[str, Any]] = []
|
|
4207
|
+
custom_button_readback_pending = False
|
|
4208
|
+
custom_button_readback_pending_entries: list[dict[str, Any]] = []
|
|
4082
4209
|
for item in view_results:
|
|
4083
4210
|
status = str(item.get("status") or "")
|
|
4084
4211
|
name = str(item.get("name") or "")
|
|
@@ -4181,12 +4308,28 @@ class AiBuilderFacade:
|
|
|
4181
4308
|
config_response = self.views.view_get_config(profile=profile, viewgraph_key=verification_key)
|
|
4182
4309
|
config_result = (config_response.get("result") or {}) if isinstance(config_response.get("result"), dict) else {}
|
|
4183
4310
|
actual_buttons = _normalize_view_buttons_for_compare(config_result)
|
|
4184
|
-
|
|
4311
|
+
button_comparison = _compare_view_button_summaries(
|
|
4312
|
+
expected=expected_buttons,
|
|
4313
|
+
actual=actual_buttons,
|
|
4314
|
+
)
|
|
4315
|
+
buttons_verified = bool(button_comparison.get("verified"))
|
|
4185
4316
|
verification_entry["buttons_verified"] = buttons_verified
|
|
4186
4317
|
verification_entry["view_key"] = verification_key
|
|
4187
4318
|
verification_entry["expected_buttons"] = deepcopy(expected_buttons)
|
|
4188
4319
|
verification_entry["actual_buttons"] = actual_buttons
|
|
4189
|
-
if
|
|
4320
|
+
if button_comparison.get("custom_button_readback_pending"):
|
|
4321
|
+
verification_entry["custom_button_readback_pending"] = True
|
|
4322
|
+
verification_entry["pending_custom_buttons"] = deepcopy(button_comparison.get("pending_custom_buttons") or [])
|
|
4323
|
+
custom_button_readback_pending = True
|
|
4324
|
+
custom_button_readback_pending_entries.append(
|
|
4325
|
+
{
|
|
4326
|
+
"name": name,
|
|
4327
|
+
"type": item.get("type"),
|
|
4328
|
+
"view_key": verification_key,
|
|
4329
|
+
"pending_custom_buttons": deepcopy(button_comparison.get("pending_custom_buttons") or []),
|
|
4330
|
+
}
|
|
4331
|
+
)
|
|
4332
|
+
elif not buttons_verified:
|
|
4190
4333
|
button_mismatches.append(
|
|
4191
4334
|
{
|
|
4192
4335
|
"name": name,
|
|
@@ -4249,6 +4392,11 @@ class AiBuilderFacade:
|
|
|
4249
4392
|
"per_view_results": view_results,
|
|
4250
4393
|
"filter_mismatches": filter_mismatches,
|
|
4251
4394
|
"button_mismatches": button_mismatches,
|
|
4395
|
+
**(
|
|
4396
|
+
{"custom_button_readback_pending": deepcopy(custom_button_readback_pending_entries)}
|
|
4397
|
+
if custom_button_readback_pending_entries
|
|
4398
|
+
else {}
|
|
4399
|
+
),
|
|
4252
4400
|
},
|
|
4253
4401
|
"request_id": first_failure.get("request_id"),
|
|
4254
4402
|
"suggested_next_call": {"tool_name": "app_read_views_summary", "arguments": {"profile": profile, "app_key": app_key}},
|
|
@@ -4256,10 +4404,21 @@ class AiBuilderFacade:
|
|
|
4256
4404
|
"http_status": first_failure.get("http_status"),
|
|
4257
4405
|
"noop": noop,
|
|
4258
4406
|
"warnings": (
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4407
|
+
(
|
|
4408
|
+
[_warning("VIEW_FILTERS_UNVERIFIED", "view definitions may exist, but saved filter behavior is not fully verified")]
|
|
4409
|
+
if (filter_readback_pending or filter_mismatches)
|
|
4410
|
+
else []
|
|
4411
|
+
)
|
|
4412
|
+
+ (
|
|
4413
|
+
[_warning("VIEW_BUTTONS_UNVERIFIED", "view definitions may exist, but saved button behavior is not fully verified")]
|
|
4414
|
+
if (button_readback_pending or button_mismatches)
|
|
4415
|
+
else []
|
|
4416
|
+
)
|
|
4417
|
+
+ (
|
|
4418
|
+
[_warning("VIEW_CUSTOM_BUTTON_READBACK_PENDING", "system buttons verified, but draft custom button bindings are not fully visible through view readback yet")]
|
|
4419
|
+
if custom_button_readback_pending
|
|
4420
|
+
else []
|
|
4421
|
+
)
|
|
4263
4422
|
),
|
|
4264
4423
|
"verification": {
|
|
4265
4424
|
"views_verified": verified,
|
|
@@ -4267,6 +4426,8 @@ class AiBuilderFacade:
|
|
|
4267
4426
|
"view_buttons_verified": view_buttons_verified,
|
|
4268
4427
|
"views_read_unavailable": verified_views_unavailable,
|
|
4269
4428
|
"by_view": verification_by_view,
|
|
4429
|
+
"custom_button_readback_pending": custom_button_readback_pending,
|
|
4430
|
+
"custom_button_readback_pending_entries": deepcopy(custom_button_readback_pending_entries),
|
|
4270
4431
|
},
|
|
4271
4432
|
"app_key": app_key,
|
|
4272
4433
|
"views_diff": {"created": created, "updated": updated, "removed": removed, "failed": failed_views},
|
|
@@ -4278,6 +4439,13 @@ class AiBuilderFacade:
|
|
|
4278
4439
|
warnings.append(_warning("VIEW_FILTERS_UNVERIFIED", "view definitions were applied, but saved filter behavior is not fully verified"))
|
|
4279
4440
|
if button_readback_pending or button_mismatches:
|
|
4280
4441
|
warnings.append(_warning("VIEW_BUTTONS_UNVERIFIED", "view definitions were applied, but saved button behavior is not fully verified"))
|
|
4442
|
+
if custom_button_readback_pending:
|
|
4443
|
+
warnings.append(
|
|
4444
|
+
_warning(
|
|
4445
|
+
"VIEW_CUSTOM_BUTTON_READBACK_PENDING",
|
|
4446
|
+
"system buttons verified, but draft custom button bindings are not fully visible through view readback yet",
|
|
4447
|
+
)
|
|
4448
|
+
)
|
|
4281
4449
|
response = {
|
|
4282
4450
|
"status": "success" if verified and view_filters_verified and view_buttons_verified else "partial_success",
|
|
4283
4451
|
"error_code": None if verified and view_filters_verified and view_buttons_verified else ("VIEW_BUTTON_READBACK_MISMATCH" if button_mismatches else "VIEW_FILTER_READBACK_MISMATCH" if filter_mismatches else "VIEWS_READBACK_PENDING"),
|
|
@@ -4297,6 +4465,11 @@ class AiBuilderFacade:
|
|
|
4297
4465
|
"details": {
|
|
4298
4466
|
**({"filter_mismatches": filter_mismatches} if filter_mismatches else {}),
|
|
4299
4467
|
**({"button_mismatches": button_mismatches} if button_mismatches else {}),
|
|
4468
|
+
**(
|
|
4469
|
+
{"custom_button_readback_pending": deepcopy(custom_button_readback_pending_entries)}
|
|
4470
|
+
if custom_button_readback_pending_entries
|
|
4471
|
+
else {}
|
|
4472
|
+
),
|
|
4300
4473
|
},
|
|
4301
4474
|
"request_id": None,
|
|
4302
4475
|
"suggested_next_call": None if verified and view_filters_verified and view_buttons_verified else {"tool_name": "app_read_views_summary", "arguments": {"profile": profile, "app_key": app_key}},
|
|
@@ -4309,6 +4482,8 @@ class AiBuilderFacade:
|
|
|
4309
4482
|
"views_read_unavailable": verified_views_unavailable,
|
|
4310
4483
|
"filter_readback_pending": filter_readback_pending,
|
|
4311
4484
|
"button_readback_pending": button_readback_pending,
|
|
4485
|
+
"custom_button_readback_pending": custom_button_readback_pending,
|
|
4486
|
+
"custom_button_readback_pending_entries": deepcopy(custom_button_readback_pending_entries),
|
|
4312
4487
|
"by_view": verification_by_view,
|
|
4313
4488
|
},
|
|
4314
4489
|
"app_key": app_key,
|
|
@@ -5055,6 +5230,28 @@ class AiBuilderFacade:
|
|
|
5055
5230
|
version_result = {}
|
|
5056
5231
|
return _coerce_positive_int(version_result.get("editVersionNo") or version_result.get("versionNo")) or int(current_schema.get("editVersionNo") or 1)
|
|
5057
5232
|
|
|
5233
|
+
def _ensure_app_edit_context(
|
|
5234
|
+
self,
|
|
5235
|
+
*,
|
|
5236
|
+
profile: str,
|
|
5237
|
+
app_key: str,
|
|
5238
|
+
normalized_args: dict[str, Any],
|
|
5239
|
+
failure_code: str,
|
|
5240
|
+
) -> tuple[int | None, JSONObject | None]:
|
|
5241
|
+
try:
|
|
5242
|
+
version_result = self.apps.app_get_edit_version_no(profile=profile, app_key=app_key).get("result") or {}
|
|
5243
|
+
except (QingflowApiError, RuntimeError) as error:
|
|
5244
|
+
api_error = _coerce_api_error(error)
|
|
5245
|
+
return None, _failed_from_api_error(
|
|
5246
|
+
failure_code,
|
|
5247
|
+
api_error,
|
|
5248
|
+
normalized_args=normalized_args,
|
|
5249
|
+
details={"app_key": app_key, "phase": "prepare_edit_context"},
|
|
5250
|
+
suggested_next_call={"tool_name": "app_read_summary", "arguments": {"profile": profile, "app_key": app_key}},
|
|
5251
|
+
)
|
|
5252
|
+
edit_version_no = _coerce_positive_int(version_result.get("editVersionNo") or version_result.get("versionNo")) or 1
|
|
5253
|
+
return edit_version_no, None
|
|
5254
|
+
|
|
5058
5255
|
def _append_publish_result(self, *, profile: str, app_key: str, publish: bool, response: JSONObject) -> JSONObject:
|
|
5059
5256
|
verification = response.get("verification")
|
|
5060
5257
|
if not isinstance(verification, dict):
|
|
@@ -5134,6 +5331,29 @@ class AiBuilderFacade:
|
|
|
5134
5331
|
"verified": verified,
|
|
5135
5332
|
}
|
|
5136
5333
|
|
|
5334
|
+
def _sync_system_view_and_restore_buttons(
|
|
5335
|
+
self,
|
|
5336
|
+
*,
|
|
5337
|
+
profile: str,
|
|
5338
|
+
app_key: str,
|
|
5339
|
+
viewgraph_key: str,
|
|
5340
|
+
payload: dict[str, Any],
|
|
5341
|
+
list_type: int,
|
|
5342
|
+
schema: dict[str, Any],
|
|
5343
|
+
visible_field_names: list[str],
|
|
5344
|
+
) -> dict[str, Any]:
|
|
5345
|
+
sync_result = self._sync_system_view_apply_config(
|
|
5346
|
+
profile=profile,
|
|
5347
|
+
app_key=app_key,
|
|
5348
|
+
list_type=list_type,
|
|
5349
|
+
schema=schema,
|
|
5350
|
+
visible_field_names=visible_field_names,
|
|
5351
|
+
)
|
|
5352
|
+
if bool(sync_result.get("verified")) and "buttonConfigDTOList" in payload:
|
|
5353
|
+
self.views.view_update(profile=profile, viewgraph_key=viewgraph_key, payload=payload)
|
|
5354
|
+
sync_result = {**sync_result, "button_config_restored": True}
|
|
5355
|
+
return sync_result
|
|
5356
|
+
|
|
5137
5357
|
def _load_views_result(self, *, profile: str, app_key: str, tolerate_404: bool) -> tuple[Any, bool]:
|
|
5138
5358
|
try:
|
|
5139
5359
|
views = self.views.view_list_flat(profile=profile, app_key=app_key)
|
|
@@ -5512,6 +5732,8 @@ def _serialize_custom_button_payload(payload: CustomButtonPatch) -> dict[str, An
|
|
|
5512
5732
|
trigger_add_data_config = data.get("trigger_add_data_config")
|
|
5513
5733
|
if isinstance(trigger_add_data_config, dict):
|
|
5514
5734
|
serialized["triggerAddDataConfig"] = _serialize_custom_button_add_data_config(trigger_add_data_config)
|
|
5735
|
+
else:
|
|
5736
|
+
serialized["triggerAddDataConfig"] = _serialize_custom_button_add_data_config({})
|
|
5515
5737
|
external_qrobot_config = data.get("external_qrobot_config")
|
|
5516
5738
|
if isinstance(external_qrobot_config, dict):
|
|
5517
5739
|
serialized["customButtonExternalQRobotRelationVO"] = _serialize_custom_button_external_qrobot_config(external_qrobot_config)
|
|
@@ -8072,6 +8294,36 @@ def _merge_view_summary_with_config(base: dict[str, Any], *, config: dict[str, A
|
|
|
8072
8294
|
for entry in display_entries
|
|
8073
8295
|
if str(entry.get("name") or "").strip()
|
|
8074
8296
|
]
|
|
8297
|
+
apply_entries = [
|
|
8298
|
+
entry
|
|
8299
|
+
for entry in display_entries
|
|
8300
|
+
if _coerce_nonnegative_int(entry.get("field_id")) is not None
|
|
8301
|
+
and str(entry.get("name") or "").strip()
|
|
8302
|
+
and str(entry.get("name") or "").strip() not in _KNOWN_SYSTEM_VIEW_COLUMNS
|
|
8303
|
+
]
|
|
8304
|
+
apply_column_ids = [
|
|
8305
|
+
field_id
|
|
8306
|
+
for field_id in (_coerce_nonnegative_int(entry.get("field_id")) for entry in apply_entries)
|
|
8307
|
+
if field_id is not None
|
|
8308
|
+
]
|
|
8309
|
+
apply_columns = [
|
|
8310
|
+
str(entry.get("name") or "").strip()
|
|
8311
|
+
for entry in apply_entries
|
|
8312
|
+
if str(entry.get("name") or "").strip()
|
|
8313
|
+
]
|
|
8314
|
+
if not apply_columns and configured_column_ids:
|
|
8315
|
+
apply_columns = [
|
|
8316
|
+
str((question_entries_by_id.get(field_id) or {}).get("name") or "").strip()
|
|
8317
|
+
for field_id in configured_column_ids
|
|
8318
|
+
if str((question_entries_by_id.get(field_id) or {}).get("name") or "").strip()
|
|
8319
|
+
and str((question_entries_by_id.get(field_id) or {}).get("name") or "").strip() not in _KNOWN_SYSTEM_VIEW_COLUMNS
|
|
8320
|
+
]
|
|
8321
|
+
apply_column_ids = [
|
|
8322
|
+
field_id
|
|
8323
|
+
for field_id in configured_column_ids
|
|
8324
|
+
if str((question_entries_by_id.get(field_id) or {}).get("name") or "").strip()
|
|
8325
|
+
and str((question_entries_by_id.get(field_id) or {}).get("name") or "").strip() not in _KNOWN_SYSTEM_VIEW_COLUMNS
|
|
8326
|
+
]
|
|
8075
8327
|
if not display_columns and configured_columns:
|
|
8076
8328
|
display_columns = configured_columns
|
|
8077
8329
|
display_column_ids = list(configured_column_ids)
|
|
@@ -8087,6 +8339,10 @@ def _merge_view_summary_with_config(base: dict[str, Any], *, config: dict[str, A
|
|
|
8087
8339
|
summary["configured_columns"] = configured_columns
|
|
8088
8340
|
summary["configured_column_ids"] = configured_column_ids
|
|
8089
8341
|
config_enriched = True
|
|
8342
|
+
if apply_columns:
|
|
8343
|
+
summary["apply_columns"] = apply_columns
|
|
8344
|
+
summary["apply_column_ids"] = apply_column_ids
|
|
8345
|
+
config_enriched = True
|
|
8090
8346
|
if question_entries:
|
|
8091
8347
|
summary["column_details"] = display_entries or _sort_view_question_entries(question_entries)
|
|
8092
8348
|
config_enriched = True
|
|
@@ -8370,7 +8626,7 @@ def _normalize_view_buttons_for_compare(value: Any) -> list[dict[str, Any]]:
|
|
|
8370
8626
|
{
|
|
8371
8627
|
"button_type": normalized.get("button_type"),
|
|
8372
8628
|
"config_type": normalized.get("config_type"),
|
|
8373
|
-
"button_id": normalized.get("button_id"),
|
|
8629
|
+
"button_id": normalized.get("button_id") if normalized.get("button_type") == "CUSTOM" else None,
|
|
8374
8630
|
"button_text": normalized.get("button_text"),
|
|
8375
8631
|
"button_icon": normalized.get("button_icon"),
|
|
8376
8632
|
"background_color": normalized.get("background_color"),
|
|
@@ -8387,12 +8643,127 @@ def _normalize_view_buttons_for_compare(value: Any) -> list[dict[str, Any]]:
|
|
|
8387
8643
|
return normalized_entries
|
|
8388
8644
|
|
|
8389
8645
|
|
|
8646
|
+
_SYSTEM_VIEW_BUTTON_ID_BY_ACTION: dict[tuple[str, str], int] = {
|
|
8647
|
+
("TOP", "set"): 1,
|
|
8648
|
+
("TOP", "switchView"): 2,
|
|
8649
|
+
("TOP", "setRowHeight"): 3,
|
|
8650
|
+
("TOP", "search"): 6,
|
|
8651
|
+
("DETAIL", "share"): 7,
|
|
8652
|
+
("DETAIL", "edit"): 8,
|
|
8653
|
+
}
|
|
8654
|
+
|
|
8655
|
+
_SYSTEM_VIEW_BUTTON_ID_BY_TEXT: dict[tuple[str, str], int] = {
|
|
8656
|
+
("TOP", "字段管理"): 1,
|
|
8657
|
+
("TOP", "视图类型"): 2,
|
|
8658
|
+
("TOP", "行高"): 3,
|
|
8659
|
+
("TOP", "搜索"): 6,
|
|
8660
|
+
("DETAIL", "分享"): 7,
|
|
8661
|
+
("DETAIL", "修改"): 8,
|
|
8662
|
+
("DETAIL", "修改记录"): 8,
|
|
8663
|
+
}
|
|
8664
|
+
|
|
8665
|
+
|
|
8666
|
+
def _resolve_system_view_button_logical_id(entry: dict[str, Any]) -> int | None:
|
|
8667
|
+
config_type = _normalize_view_button_config_type(entry.get("configType") or entry.get("config_type")) or ""
|
|
8668
|
+
trigger_action = str(entry.get("triggerAction") or entry.get("trigger_action") or "").strip()
|
|
8669
|
+
if config_type and trigger_action:
|
|
8670
|
+
mapped = _SYSTEM_VIEW_BUTTON_ID_BY_ACTION.get((config_type, trigger_action))
|
|
8671
|
+
if mapped is not None:
|
|
8672
|
+
return mapped
|
|
8673
|
+
button_text = str(entry.get("buttonText") or entry.get("button_text") or "").strip()
|
|
8674
|
+
default_button_text = str(entry.get("defaultButtonText") or entry.get("default_button_text") or "").strip()
|
|
8675
|
+
for candidate in (button_text, default_button_text):
|
|
8676
|
+
if config_type and candidate:
|
|
8677
|
+
mapped = _SYSTEM_VIEW_BUTTON_ID_BY_TEXT.get((config_type, candidate))
|
|
8678
|
+
if mapped is not None:
|
|
8679
|
+
return mapped
|
|
8680
|
+
button_id = _coerce_positive_int(entry.get("buttonId") or entry.get("button_id") or entry.get("id"))
|
|
8681
|
+
if button_id is not None and button_id < 1000:
|
|
8682
|
+
return button_id
|
|
8683
|
+
return None
|
|
8684
|
+
|
|
8685
|
+
|
|
8686
|
+
def _normalize_expected_view_buttons_for_compare(
|
|
8687
|
+
value: Any,
|
|
8688
|
+
*,
|
|
8689
|
+
custom_button_details_by_id: dict[int, dict[str, Any]] | None = None,
|
|
8690
|
+
) -> list[dict[str, Any]]:
|
|
8691
|
+
normalized_entries = _normalize_view_buttons_for_compare(value)
|
|
8692
|
+
if not custom_button_details_by_id:
|
|
8693
|
+
return normalized_entries
|
|
8694
|
+
enriched_entries: list[dict[str, Any]] = []
|
|
8695
|
+
for item in normalized_entries:
|
|
8696
|
+
enriched = deepcopy(item)
|
|
8697
|
+
if enriched.get("button_type") == "CUSTOM":
|
|
8698
|
+
button_id = _coerce_positive_int(enriched.get("button_id"))
|
|
8699
|
+
detail = custom_button_details_by_id.get(button_id or -1)
|
|
8700
|
+
if isinstance(detail, dict):
|
|
8701
|
+
for key in (
|
|
8702
|
+
"button_text",
|
|
8703
|
+
"button_icon",
|
|
8704
|
+
"background_color",
|
|
8705
|
+
"text_color",
|
|
8706
|
+
"trigger_action",
|
|
8707
|
+
"trigger_link_url",
|
|
8708
|
+
):
|
|
8709
|
+
value = detail.get(key)
|
|
8710
|
+
if value not in {None, ""}:
|
|
8711
|
+
enriched[key] = deepcopy(value)
|
|
8712
|
+
enriched_entries.append(enriched)
|
|
8713
|
+
return enriched_entries
|
|
8714
|
+
|
|
8715
|
+
|
|
8716
|
+
def _partition_view_button_summaries(
|
|
8717
|
+
items: list[dict[str, Any]],
|
|
8718
|
+
) -> tuple[list[dict[str, Any]], list[dict[str, Any]], list[dict[str, Any]]]:
|
|
8719
|
+
system_buttons: list[dict[str, Any]] = []
|
|
8720
|
+
custom_buttons: list[dict[str, Any]] = []
|
|
8721
|
+
other_buttons: list[dict[str, Any]] = []
|
|
8722
|
+
for item in items:
|
|
8723
|
+
button_type = str(item.get("button_type") or "").strip().upper()
|
|
8724
|
+
if button_type == "SYSTEM":
|
|
8725
|
+
system_buttons.append(item)
|
|
8726
|
+
elif button_type == "CUSTOM":
|
|
8727
|
+
custom_buttons.append(item)
|
|
8728
|
+
else:
|
|
8729
|
+
other_buttons.append(item)
|
|
8730
|
+
return system_buttons, custom_buttons, other_buttons
|
|
8731
|
+
|
|
8732
|
+
|
|
8733
|
+
def _compare_view_button_summaries(
|
|
8734
|
+
*,
|
|
8735
|
+
expected: list[dict[str, Any]],
|
|
8736
|
+
actual: list[dict[str, Any]],
|
|
8737
|
+
) -> dict[str, Any]:
|
|
8738
|
+
if actual == expected:
|
|
8739
|
+
return {
|
|
8740
|
+
"verified": True,
|
|
8741
|
+
"custom_button_readback_pending": False,
|
|
8742
|
+
"pending_custom_buttons": [],
|
|
8743
|
+
}
|
|
8744
|
+
expected_system, expected_custom, expected_other = _partition_view_button_summaries(expected)
|
|
8745
|
+
actual_system, actual_custom, actual_other = _partition_view_button_summaries(actual)
|
|
8746
|
+
custom_button_readback_pending = (
|
|
8747
|
+
bool(expected_custom)
|
|
8748
|
+
and not actual_custom
|
|
8749
|
+
and actual_system == expected_system
|
|
8750
|
+
and actual_other == expected_other
|
|
8751
|
+
)
|
|
8752
|
+
return {
|
|
8753
|
+
"verified": custom_button_readback_pending,
|
|
8754
|
+
"custom_button_readback_pending": custom_button_readback_pending,
|
|
8755
|
+
"pending_custom_buttons": deepcopy(expected_custom) if custom_button_readback_pending else [],
|
|
8756
|
+
}
|
|
8757
|
+
|
|
8758
|
+
|
|
8390
8759
|
def _serialize_existing_view_button_entry(entry: dict[str, Any]) -> dict[str, Any]:
|
|
8391
8760
|
dto: dict[str, Any] = {}
|
|
8761
|
+
button_type = _normalize_view_button_type(entry.get("buttonType") or entry.get("button_type"))
|
|
8392
8762
|
button_id = _coerce_positive_int(entry.get("buttonId") or entry.get("button_id") or entry.get("id"))
|
|
8763
|
+
if button_type == "SYSTEM":
|
|
8764
|
+
button_id = _resolve_system_view_button_logical_id(entry)
|
|
8393
8765
|
if button_id is not None:
|
|
8394
8766
|
dto["buttonId"] = button_id
|
|
8395
|
-
button_type = _normalize_view_button_type(entry.get("buttonType") or entry.get("button_type"))
|
|
8396
8767
|
if button_type is not None:
|
|
8397
8768
|
dto["buttonType"] = button_type
|
|
8398
8769
|
config_type = _normalize_view_button_config_type(entry.get("configType") or entry.get("config_type"))
|
|
@@ -8405,6 +8776,7 @@ def _serialize_existing_view_button_entry(entry: dict[str, Any]) -> dict[str, An
|
|
|
8405
8776
|
dto["printTpls"] = _serialize_print_tpl_ids(entry.get("printTpls"))
|
|
8406
8777
|
for source_key, target_key in (
|
|
8407
8778
|
("buttonText", "buttonText"),
|
|
8779
|
+
("defaultButtonText", "defaultButtonText"),
|
|
8408
8780
|
("buttonIcon", "buttonIcon"),
|
|
8409
8781
|
("backgroundColor", "backgroundColor"),
|
|
8410
8782
|
("textColor", "textColor"),
|
|
@@ -8423,10 +8795,9 @@ def _serialize_existing_view_button_entry(entry: dict[str, Any]) -> dict[str, An
|
|
|
8423
8795
|
def _extract_existing_view_button_dtos(config: dict[str, Any]) -> list[dict[str, Any]]:
|
|
8424
8796
|
if not isinstance(config, dict):
|
|
8425
8797
|
return []
|
|
8426
|
-
|
|
8427
|
-
if
|
|
8428
|
-
return [deepcopy(item) for item in
|
|
8429
|
-
entries, _ = _extract_view_button_entries(config)
|
|
8798
|
+
entries, source = _extract_view_button_entries(config)
|
|
8799
|
+
if source == "buttonConfigDTOList":
|
|
8800
|
+
return [deepcopy(item) for item in entries if isinstance(item, dict)]
|
|
8430
8801
|
return [_serialize_existing_view_button_entry(entry) for entry in entries if isinstance(entry, dict)]
|
|
8431
8802
|
|
|
8432
8803
|
|
|
@@ -9171,6 +9542,22 @@ def _build_view_update_payload(
|
|
|
9171
9542
|
)
|
|
9172
9543
|
|
|
9173
9544
|
|
|
9545
|
+
_KNOWN_SYSTEM_VIEW_COLUMNS = {
|
|
9546
|
+
"编号",
|
|
9547
|
+
"当前流程状态",
|
|
9548
|
+
"申请人",
|
|
9549
|
+
"申请时间",
|
|
9550
|
+
"更新时间",
|
|
9551
|
+
"流程标题",
|
|
9552
|
+
"当前处理人",
|
|
9553
|
+
"当前处理节点",
|
|
9554
|
+
}
|
|
9555
|
+
|
|
9556
|
+
|
|
9557
|
+
def _filter_known_system_view_columns(columns: list[str]) -> list[str]:
|
|
9558
|
+
return [name for name in columns if str(name or "").strip() and str(name).strip() not in _KNOWN_SYSTEM_VIEW_COLUMNS]
|
|
9559
|
+
|
|
9560
|
+
|
|
9174
9561
|
def _build_minimal_view_payload(
|
|
9175
9562
|
*,
|
|
9176
9563
|
app_key: str,
|
|
@@ -9299,7 +9686,7 @@ def _resolve_view_visible_field_names(patch: ViewUpsertPatch) -> list[str]:
|
|
|
9299
9686
|
ordered: list[str] = []
|
|
9300
9687
|
for value in [*patch.columns, patch.title_field, patch.start_field, patch.end_field, patch.group_by]:
|
|
9301
9688
|
name = str(value or "").strip()
|
|
9302
|
-
if name and name not in ordered:
|
|
9689
|
+
if name and name not in _KNOWN_SYSTEM_VIEW_COLUMNS and name not in ordered:
|
|
9303
9690
|
ordered.append(name)
|
|
9304
9691
|
return ordered
|
|
9305
9692
|
|