@qingflow-tech/qingflow-app-builder-mcp 1.0.9 → 1.0.10
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/skills/qingflow-app-builder/SKILL.md +8 -0
- package/skills/qingflow-app-builder/references/create-app.md +39 -2
- package/skills/qingflow-app-builder/references/tool-selection.md +2 -2
- package/skills/qingflow-app-builder/references/update-views.md +2 -1
- package/src/qingflow_mcp/builder_facade/models.py +2 -0
- package/src/qingflow_mcp/builder_facade/service.py +223 -39
- package/src/qingflow_mcp/cli/commands/builder.py +40 -11
- package/src/qingflow_mcp/cli/main.py +204 -3
- package/src/qingflow_mcp/response_trim.py +15 -10
- package/src/qingflow_mcp/server_app_builder.py +27 -2
- package/src/qingflow_mcp/tools/ai_builder_tools.py +1189 -29
- package/src/qingflow_mcp/tools/record_tools.py +200 -6
|
@@ -2564,6 +2564,7 @@ class AiBuilderFacade:
|
|
|
2564
2564
|
details=_with_state_read_blocked_details({"app_key": app_key}, resource="custom_buttons", error=api_error),
|
|
2565
2565
|
suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
|
|
2566
2566
|
))
|
|
2567
|
+
app_name = self._read_app_name_for_builder_output(profile=profile, app_key=app_key)
|
|
2567
2568
|
|
|
2568
2569
|
existing_by_id = {
|
|
2569
2570
|
button_id: item
|
|
@@ -3013,6 +3014,7 @@ class AiBuilderFacade:
|
|
|
3013
3014
|
},
|
|
3014
3015
|
"verified": verified,
|
|
3015
3016
|
"app_key": app_key,
|
|
3017
|
+
"app_name": app_name,
|
|
3016
3018
|
"mode": "apply",
|
|
3017
3019
|
"created": created,
|
|
3018
3020
|
"updated": updated,
|
|
@@ -3663,6 +3665,7 @@ class AiBuilderFacade:
|
|
|
3663
3665
|
details=_with_state_read_blocked_details({"app_key": app_key}, resource="associated_resources", error=api_error),
|
|
3664
3666
|
suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
|
|
3665
3667
|
))
|
|
3668
|
+
app_name = self._read_app_name_for_builder_output(profile=profile, app_key=app_key)
|
|
3666
3669
|
|
|
3667
3670
|
existing_by_id = _associated_resource_index(existing_resources)
|
|
3668
3671
|
blocking_issues: list[dict[str, Any]] = []
|
|
@@ -3671,7 +3674,6 @@ class AiBuilderFacade:
|
|
|
3671
3674
|
client_key_to_patch: dict[str, AssociatedResourceUpsertPatch] = {}
|
|
3672
3675
|
client_key_to_id: dict[str, int] = {}
|
|
3673
3676
|
used_client_keys: set[str] = set()
|
|
3674
|
-
force_update_resource_ids: set[int] = set()
|
|
3675
3677
|
resolved_associated_item_refs: dict[str, list[int]] = {}
|
|
3676
3678
|
|
|
3677
3679
|
upsert_resources = list(request.upsert_resources)
|
|
@@ -3691,11 +3693,6 @@ class AiBuilderFacade:
|
|
|
3691
3693
|
)
|
|
3692
3694
|
)
|
|
3693
3695
|
upsert_resources.extend(expanded_resources)
|
|
3694
|
-
force_update_resource_ids.update(
|
|
3695
|
-
item_id
|
|
3696
|
-
for item_id in (_coerce_positive_int(patch.associated_item_id) for patch in expanded_resources)
|
|
3697
|
-
if item_id is not None
|
|
3698
|
-
)
|
|
3699
3696
|
normalized_args["upsert_resources"] = [patch.model_dump(mode="json") for patch in upsert_resources]
|
|
3700
3697
|
normalized_args["patch_results"] = patch_results
|
|
3701
3698
|
|
|
@@ -3733,14 +3730,9 @@ class AiBuilderFacade:
|
|
|
3733
3730
|
touched_ids.add(associated_item_id)
|
|
3734
3731
|
if client_key:
|
|
3735
3732
|
client_key_to_id[client_key] = associated_item_id
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
or _associated_resource_patch_has_match_config(patch)
|
|
3740
|
-
else "unchanged"
|
|
3741
|
-
if _associated_resource_matches_patch(existing_by_id[associated_item_id], patch)
|
|
3742
|
-
else "update"
|
|
3743
|
-
)
|
|
3733
|
+
has_match_config = _associated_resource_patch_has_match_config(patch)
|
|
3734
|
+
identity_matches = _associated_resource_matches_patch(existing_by_id[associated_item_id], patch)
|
|
3735
|
+
operation = "update" if has_match_config or not identity_matches else "unchanged"
|
|
3744
3736
|
upsert_ops.append({"operation": operation, "associated_item_id": associated_item_id, "patch": patch, "index": index})
|
|
3745
3737
|
continue
|
|
3746
3738
|
matches = [
|
|
@@ -4018,6 +4010,26 @@ class AiBuilderFacade:
|
|
|
4018
4010
|
|
|
4019
4011
|
for index, view_config in enumerate(request.view_configs):
|
|
4020
4012
|
resolved_view_config_ids = resolved_associated_item_refs.get(f"view_configs[{index}].associated_item_ids", [])
|
|
4013
|
+
view_name = str(view_config.view_key or "").strip()
|
|
4014
|
+
missing_ref_ids = [
|
|
4015
|
+
str(ref or "").strip()
|
|
4016
|
+
for ref in view_config.associated_item_refs
|
|
4017
|
+
if str(ref or "").strip() and str(ref or "").strip() not in client_key_to_id
|
|
4018
|
+
]
|
|
4019
|
+
if missing_ref_ids:
|
|
4020
|
+
failed.append(
|
|
4021
|
+
{
|
|
4022
|
+
"operation": "view_config",
|
|
4023
|
+
"index": index,
|
|
4024
|
+
"view_key": view_config.view_key,
|
|
4025
|
+
"view_name": view_name,
|
|
4026
|
+
"status": "failed",
|
|
4027
|
+
"error_code": "ASSOCIATED_RESOURCE_REF_UNRESOLVED",
|
|
4028
|
+
"missing_refs": missing_ref_ids,
|
|
4029
|
+
"message": "view_config references associated resources that were not successfully created or resolved; skipped view config write to avoid clearing existing associations",
|
|
4030
|
+
}
|
|
4031
|
+
)
|
|
4032
|
+
continue
|
|
4021
4033
|
selected_ids = [
|
|
4022
4034
|
item_id
|
|
4023
4035
|
for item_id in (
|
|
@@ -4034,19 +4046,21 @@ class AiBuilderFacade:
|
|
|
4034
4046
|
available_resources=resources_after,
|
|
4035
4047
|
)
|
|
4036
4048
|
if issues:
|
|
4037
|
-
failed.append({"operation": "view_config", "index": index, "view_key": view_config.view_key, "status": "failed", "issues": issues})
|
|
4049
|
+
failed.append({"operation": "view_config", "index": index, "view_key": view_config.view_key, "view_name": view_name, "status": "failed", "issues": issues})
|
|
4038
4050
|
continue
|
|
4039
4051
|
try:
|
|
4040
4052
|
write_executed = True
|
|
4041
4053
|
self._update_view_associated_resources_config(profile=profile, view_key=view_config.view_key, associated_resources_payload=view_payload)
|
|
4042
4054
|
try:
|
|
4043
4055
|
config = self.views.view_get_config(profile=profile, viewgraph_key=view_config.view_key).get("result") or {}
|
|
4056
|
+
view_name = _extract_view_name(config if isinstance(config, dict) else {}) or view_name
|
|
4044
4057
|
actual_config = _extract_view_associated_resources_config(config if isinstance(config, dict) else {}, available_resources=resources_after)
|
|
4045
4058
|
verified_config = expected_config is not None and _associated_resources_config_matches(expected_config, actual_config)
|
|
4046
4059
|
if not verified_config and expected_config is not None:
|
|
4047
4060
|
self._update_view_associated_resources_config(profile=profile, view_key=view_config.view_key, associated_resources_payload=view_payload)
|
|
4048
4061
|
refreshed_resources = self._load_associated_resources_for_builder(profile=profile, app_key=app_key)
|
|
4049
4062
|
refreshed_config = self.views.view_get_config(profile=profile, viewgraph_key=view_config.view_key).get("result") or {}
|
|
4063
|
+
view_name = _extract_view_name(refreshed_config if isinstance(refreshed_config, dict) else {}) or view_name
|
|
4050
4064
|
actual_config = _extract_view_associated_resources_config(
|
|
4051
4065
|
refreshed_config if isinstance(refreshed_config, dict) else {},
|
|
4052
4066
|
available_resources=refreshed_resources,
|
|
@@ -4055,10 +4069,10 @@ class AiBuilderFacade:
|
|
|
4055
4069
|
except (QingflowApiError, RuntimeError):
|
|
4056
4070
|
actual_config = {}
|
|
4057
4071
|
verified_config = False
|
|
4058
|
-
view_config_results.append({"index": index, "view_key": view_config.view_key, "status": "success" if verified_config else "partial_success", "associated_resources_verified": verified_config, "expected": expected_config, "actual": actual_config})
|
|
4072
|
+
view_config_results.append({"index": index, "view_key": view_config.view_key, "view_name": view_name, "status": "success" if verified_config else "partial_success", "associated_resources_verified": verified_config, "expected": expected_config, "actual": actual_config})
|
|
4059
4073
|
except (QingflowApiError, RuntimeError) as error:
|
|
4060
4074
|
api_error = _coerce_api_error(error)
|
|
4061
|
-
failed.append({"operation": "view_config", "index": index, "view_key": view_config.view_key, "status": "failed", "error_code": "VIEW_ASSOCIATED_RESOURCES_WRITE_FAILED", "message": api_error.message, "transport_error": _transport_error_payload(api_error)})
|
|
4075
|
+
failed.append({"operation": "view_config", "index": index, "view_key": view_config.view_key, "view_name": view_name, "status": "failed", "error_code": "VIEW_ASSOCIATED_RESOURCES_WRITE_FAILED", "message": api_error.message, "transport_error": _transport_error_payload(api_error)})
|
|
4062
4076
|
|
|
4063
4077
|
final_resources: list[dict[str, Any]] = []
|
|
4064
4078
|
readback_failed = False
|
|
@@ -4134,6 +4148,7 @@ class AiBuilderFacade:
|
|
|
4134
4148
|
"verification": {"associated_resources_verified": pool_verified, "associated_resource_view_configs_verified": view_configs_verified, "readback_loaded": not readback_failed},
|
|
4135
4149
|
"verified": verified,
|
|
4136
4150
|
"app_key": app_key,
|
|
4151
|
+
"app_name": app_name,
|
|
4137
4152
|
"mode": "apply",
|
|
4138
4153
|
"created": created,
|
|
4139
4154
|
"updated": updated,
|
|
@@ -4612,9 +4627,18 @@ class AiBuilderFacade:
|
|
|
4612
4627
|
"associated_resources": len(associated_resources),
|
|
4613
4628
|
"custom_buttons": len(custom_buttons),
|
|
4614
4629
|
}
|
|
4630
|
+
app_name = str(
|
|
4631
|
+
base_result.get("formTitle")
|
|
4632
|
+
or base_result.get("title")
|
|
4633
|
+
or base_result.get("appName")
|
|
4634
|
+
or base_result.get("name")
|
|
4635
|
+
or app_key
|
|
4636
|
+
).strip() or app_key
|
|
4615
4637
|
response = AppReadSummaryResponse(
|
|
4616
4638
|
app_key=app_key,
|
|
4617
|
-
|
|
4639
|
+
app_name=app_name,
|
|
4640
|
+
name=app_name,
|
|
4641
|
+
title=app_name,
|
|
4618
4642
|
app_icon=str(base_result.get("appIcon") or "").strip() or None,
|
|
4619
4643
|
visibility=_public_visibility_from_member_auth(base_result.get("auth")),
|
|
4620
4644
|
tag_ids=_coerce_int_list(base_result.get("tagIds")),
|
|
@@ -5441,7 +5465,7 @@ class AiBuilderFacade:
|
|
|
5441
5465
|
for index, patch, config in semantic_patches:
|
|
5442
5466
|
config_payload = config.model_dump(mode="json", exclude_none=True)
|
|
5443
5467
|
reason_base = f"upsert_buttons[{index}].trigger_add_data_config"
|
|
5444
|
-
if config.
|
|
5468
|
+
if "que_relation" in config.model_fields_set:
|
|
5445
5469
|
issues.append(
|
|
5446
5470
|
{
|
|
5447
5471
|
"error_code": "MIXED_CUSTOM_BUTTON_MAPPING_MODES",
|
|
@@ -5745,11 +5769,12 @@ class AiBuilderFacade:
|
|
|
5745
5769
|
)
|
|
5746
5770
|
return {"verified": False, "write_executed": False, "write_succeeded": False, "view_configs": results, "failed": failed, "warnings": warnings}
|
|
5747
5771
|
|
|
5748
|
-
|
|
5749
|
-
_extract_view_key(view)
|
|
5772
|
+
existing_views_by_key = {
|
|
5773
|
+
_extract_view_key(view): view
|
|
5750
5774
|
for view in (existing_views if isinstance(existing_views, list) else [])
|
|
5751
5775
|
if isinstance(view, dict) and _extract_view_key(view)
|
|
5752
5776
|
}
|
|
5777
|
+
view_keys = set(existing_views_by_key)
|
|
5753
5778
|
button_inventory: dict[int, dict[str, Any]] = {}
|
|
5754
5779
|
for item in [*existing_buttons, *readback_buttons]:
|
|
5755
5780
|
if not isinstance(item, dict):
|
|
@@ -5781,6 +5806,7 @@ class AiBuilderFacade:
|
|
|
5781
5806
|
try:
|
|
5782
5807
|
current_response = self.views.view_get_config(profile=profile, viewgraph_key=view_key)
|
|
5783
5808
|
current_config = current_response.get("result") if isinstance(current_response.get("result"), dict) else {}
|
|
5809
|
+
view_name = _extract_view_name(current_config) or _extract_view_name(existing_views_by_key.get(view_key) or {}) or view_key
|
|
5784
5810
|
except (QingflowApiError, RuntimeError) as error:
|
|
5785
5811
|
api_error = _coerce_api_error(error)
|
|
5786
5812
|
issue = {
|
|
@@ -5789,6 +5815,7 @@ class AiBuilderFacade:
|
|
|
5789
5815
|
"status": "failed",
|
|
5790
5816
|
"error_code": "VIEW_CONFIG_READ_FAILED",
|
|
5791
5817
|
"view_key": view_key,
|
|
5818
|
+
"view_name": _extract_view_name(existing_views_by_key.get(view_key) or {}) or view_key,
|
|
5792
5819
|
"message": api_error.message,
|
|
5793
5820
|
"transport_error": _transport_error_payload(api_error),
|
|
5794
5821
|
}
|
|
@@ -5879,6 +5906,7 @@ class AiBuilderFacade:
|
|
|
5879
5906
|
"status": "failed",
|
|
5880
5907
|
"error_code": "INSIDE_BUTTON_BACKEND_UNSUPPORTED",
|
|
5881
5908
|
"view_key": view_key,
|
|
5909
|
+
"view_name": view_name,
|
|
5882
5910
|
"message": "backend rejected inside/list-button placement; header/detail placements were retried without inside buttons",
|
|
5883
5911
|
"backend_message": api_error.message,
|
|
5884
5912
|
"transport_error": _transport_error_payload(api_error),
|
|
@@ -5891,9 +5919,10 @@ class AiBuilderFacade:
|
|
|
5891
5919
|
"index": config_index,
|
|
5892
5920
|
"operation": "view_config",
|
|
5893
5921
|
"status": "failed",
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5922
|
+
"error_code": "VIEW_BUTTON_CONFIG_WRITE_FAILED",
|
|
5923
|
+
"view_key": view_key,
|
|
5924
|
+
"view_name": view_name,
|
|
5925
|
+
"message": fallback_api_error.message,
|
|
5897
5926
|
"transport_error": _transport_error_payload(fallback_api_error),
|
|
5898
5927
|
"initial_error": _transport_error_payload(api_error),
|
|
5899
5928
|
}
|
|
@@ -5908,6 +5937,7 @@ class AiBuilderFacade:
|
|
|
5908
5937
|
"status": "failed",
|
|
5909
5938
|
"error_code": "INSIDE_BUTTON_BACKEND_UNSUPPORTED",
|
|
5910
5939
|
"view_key": view_key,
|
|
5940
|
+
"view_name": view_name,
|
|
5911
5941
|
"message": "backend rejected inside/list-button placement",
|
|
5912
5942
|
"backend_message": api_error.message,
|
|
5913
5943
|
"transport_error": _transport_error_payload(api_error),
|
|
@@ -5924,6 +5954,7 @@ class AiBuilderFacade:
|
|
|
5924
5954
|
"status": "failed",
|
|
5925
5955
|
"error_code": "VIEW_BUTTON_CONFIG_WRITE_FAILED",
|
|
5926
5956
|
"view_key": view_key,
|
|
5957
|
+
"view_name": view_name,
|
|
5927
5958
|
"message": api_error.message,
|
|
5928
5959
|
"transport_error": _transport_error_payload(api_error),
|
|
5929
5960
|
}
|
|
@@ -5953,6 +5984,7 @@ class AiBuilderFacade:
|
|
|
5953
5984
|
"operation": "view_config",
|
|
5954
5985
|
"status": "success" if verified else ("partial_success" if unsupported_list_issue is not None else "unverified"),
|
|
5955
5986
|
"view_key": view_key,
|
|
5987
|
+
"view_name": view_name,
|
|
5956
5988
|
"mode": "replace" if replace_existing else "merge",
|
|
5957
5989
|
"buttons_configured": len(new_dtos),
|
|
5958
5990
|
"view_buttons_verified": verified,
|
|
@@ -5975,6 +6007,7 @@ class AiBuilderFacade:
|
|
|
5975
6007
|
"operation": "view_config",
|
|
5976
6008
|
"status": "unverified",
|
|
5977
6009
|
"view_key": view_key,
|
|
6010
|
+
"view_name": view_name,
|
|
5978
6011
|
"mode": "replace" if replace_existing else "merge",
|
|
5979
6012
|
"buttons_configured": len(new_dtos),
|
|
5980
6013
|
"view_buttons_verified": None,
|
|
@@ -7055,6 +7088,7 @@ class AiBuilderFacade:
|
|
|
7055
7088
|
},
|
|
7056
7089
|
"app_key": target.app_key,
|
|
7057
7090
|
"app_icon": str(resolved.get("app_icon") or visual_result.get("app_icon") or "").strip() or None,
|
|
7091
|
+
"app_name": str(visual_result.get("app_name_after") or target.app_name),
|
|
7058
7092
|
"app_name_before": str(visual_result.get("app_name_before") or target.app_name),
|
|
7059
7093
|
"app_name_after": str(visual_result.get("app_name_after") or target.app_name),
|
|
7060
7094
|
"app_base_updated": bool(visual_result.get("updated")),
|
|
@@ -7295,7 +7329,8 @@ class AiBuilderFacade:
|
|
|
7295
7329
|
actual_app_name = str(base_info.get("formTitle") or effective_app_name).strip() or effective_app_name
|
|
7296
7330
|
actual_app_icon = str(base_info.get("appIcon") or visual_result.get("app_icon") or "").strip() or None
|
|
7297
7331
|
expected_app_icon = str(visual_result.get("app_icon") or "").strip() or None
|
|
7298
|
-
|
|
7332
|
+
app_icon_verified = True if expected_app_icon is None else actual_app_icon == expected_app_icon
|
|
7333
|
+
app_base_verified = actual_app_name == effective_app_name and app_icon_verified
|
|
7299
7334
|
verified = app_base_verified and relation_target_metadata_verified
|
|
7300
7335
|
response = {
|
|
7301
7336
|
"status": "success" if verified else "partial_success",
|
|
@@ -7319,6 +7354,7 @@ class AiBuilderFacade:
|
|
|
7319
7354
|
},
|
|
7320
7355
|
"app_key": target.app_key,
|
|
7321
7356
|
"app_icon": str(visual_result.get("app_icon") or "").strip() or None,
|
|
7357
|
+
"app_name": effective_app_name,
|
|
7322
7358
|
"app_name_before": str(visual_result.get("app_name_before") or target.app_name),
|
|
7323
7359
|
"app_name_after": effective_app_name,
|
|
7324
7360
|
"app_base_updated": bool(visual_result.get("updated")),
|
|
@@ -7516,6 +7552,7 @@ class AiBuilderFacade:
|
|
|
7516
7552
|
},
|
|
7517
7553
|
"app_key": target.app_key,
|
|
7518
7554
|
"app_icon": str(visual_result.get("app_icon") or resolved.get("app_icon") or "").strip() or None,
|
|
7555
|
+
"app_name": effective_app_name,
|
|
7519
7556
|
"app_name_before": str(visual_result.get("app_name_before") or target.app_name),
|
|
7520
7557
|
"app_name_after": effective_app_name,
|
|
7521
7558
|
"app_base_updated": bool(visual_result.get("updated")),
|
|
@@ -7525,6 +7562,13 @@ class AiBuilderFacade:
|
|
|
7525
7562
|
"updated": updated,
|
|
7526
7563
|
"removed": removed,
|
|
7527
7564
|
},
|
|
7565
|
+
"field_diff_details": _schema_field_diff_details(
|
|
7566
|
+
added=added,
|
|
7567
|
+
updated=updated,
|
|
7568
|
+
removed=removed,
|
|
7569
|
+
before_fields=original_fields,
|
|
7570
|
+
after_fields=current_fields,
|
|
7571
|
+
),
|
|
7528
7572
|
"verified": False,
|
|
7529
7573
|
"tag_ids_after": [],
|
|
7530
7574
|
"package_attached": None,
|
|
@@ -7544,6 +7588,13 @@ class AiBuilderFacade:
|
|
|
7544
7588
|
try:
|
|
7545
7589
|
verified = self.app_read(profile=profile, app_key=target.app_key, include_raw=False)
|
|
7546
7590
|
verified_field_names = {field["name"] for field in verified["schema"]["fields"]}
|
|
7591
|
+
response["field_diff_details"] = _schema_field_diff_details(
|
|
7592
|
+
added=added,
|
|
7593
|
+
updated=updated,
|
|
7594
|
+
removed=removed,
|
|
7595
|
+
before_fields=original_fields,
|
|
7596
|
+
after_fields=cast(list[dict[str, Any]], verified["schema"]["fields"]),
|
|
7597
|
+
)
|
|
7547
7598
|
verification_ok = all(name in verified_field_names for name in added + updated) and all(name not in verified_field_names for name in removed)
|
|
7548
7599
|
data_display_verification = _verify_data_display_readback(
|
|
7549
7600
|
form_settings=verified.get("form_settings"),
|
|
@@ -7575,7 +7626,8 @@ class AiBuilderFacade:
|
|
|
7575
7626
|
actual_app_name = str(base_info.get("formTitle") or effective_app_name).strip() or effective_app_name
|
|
7576
7627
|
actual_app_icon = str(base_info.get("appIcon") or visual_result.get("app_icon") or "").strip() or None
|
|
7577
7628
|
expected_app_icon = str(visual_result.get("app_icon") or "").strip() or None
|
|
7578
|
-
|
|
7629
|
+
app_icon_verified = True if expected_app_icon is None else actual_app_icon == expected_app_icon
|
|
7630
|
+
app_base_verified = actual_app_name == effective_app_name and app_icon_verified
|
|
7579
7631
|
except (QingflowApiError, RuntimeError) as error:
|
|
7580
7632
|
base_error = _coerce_api_error(error)
|
|
7581
7633
|
if verification_error is None:
|
|
@@ -7669,6 +7721,7 @@ class AiBuilderFacade:
|
|
|
7669
7721
|
details=_with_state_read_blocked_details({"app_key": app_key}, resource="schema", error=api_error),
|
|
7670
7722
|
suggested_next_call={"tool_name": "app_get_layout", "arguments": {"profile": profile, "app_key": app_key}},
|
|
7671
7723
|
))
|
|
7724
|
+
app_name = str(schema_result.get("formTitle") or schema_result.get("title") or schema_result.get("appName") or app_key).strip() or app_key
|
|
7672
7725
|
parsed = _parse_schema(schema_result)
|
|
7673
7726
|
current_fields = parsed["fields"]
|
|
7674
7727
|
requested_sections, missing_selectors = _resolve_layout_sections_to_names(requested_sections, current_fields)
|
|
@@ -7751,6 +7804,7 @@ class AiBuilderFacade:
|
|
|
7751
7804
|
"warnings": [],
|
|
7752
7805
|
"verification": {"layout_verified": True, "layout_summary_verified": True},
|
|
7753
7806
|
"app_key": app_key,
|
|
7807
|
+
"app_name": app_name,
|
|
7754
7808
|
"layout_diff": {
|
|
7755
7809
|
"mode": mode.value,
|
|
7756
7810
|
"replaced": mode == LayoutApplyMode.replace,
|
|
@@ -7839,6 +7893,7 @@ class AiBuilderFacade:
|
|
|
7839
7893
|
"warnings": [],
|
|
7840
7894
|
"verification": {"layout_verified": False, "layout_summary_verified": False, "layout_read_unavailable": True},
|
|
7841
7895
|
"app_key": app_key,
|
|
7896
|
+
"app_name": app_name,
|
|
7842
7897
|
"layout_diff": {
|
|
7843
7898
|
"mode": mode.value,
|
|
7844
7899
|
"replaced": mode == LayoutApplyMode.replace,
|
|
@@ -7895,6 +7950,7 @@ class AiBuilderFacade:
|
|
|
7895
7950
|
"warnings": warnings,
|
|
7896
7951
|
"verification": {"layout_verified": layout_verified, "layout_summary_verified": layout_summary_verified},
|
|
7897
7952
|
"app_key": app_key,
|
|
7953
|
+
"app_name": app_name,
|
|
7898
7954
|
"layout_diff": {
|
|
7899
7955
|
"mode": mode.value,
|
|
7900
7956
|
"replaced": mode == LayoutApplyMode.replace,
|
|
@@ -7957,6 +8013,7 @@ class AiBuilderFacade:
|
|
|
7957
8013
|
details=_with_state_read_blocked_details({"app_key": app_key}, resource="workflow", error=api_error),
|
|
7958
8014
|
suggested_next_call={"tool_name": "app_get_flow", "arguments": {"profile": profile, "app_key": app_key}},
|
|
7959
8015
|
))
|
|
8016
|
+
app_name = str(base.get("formTitle") or base.get("title") or base.get("appName") or app_key).strip() or app_key
|
|
7960
8017
|
entity = _entity_spec_from_app(base_info=base, schema=schema, views=None)
|
|
7961
8018
|
current_fields = _parse_schema(schema)["fields"]
|
|
7962
8019
|
normalized_nodes, resolution_issues = self._normalize_flow_nodes(profile=profile, current_fields=current_fields, nodes=nodes)
|
|
@@ -8124,6 +8181,7 @@ class AiBuilderFacade:
|
|
|
8124
8181
|
"workflow_read_unavailable": verified_nodes_unavailable,
|
|
8125
8182
|
},
|
|
8126
8183
|
"app_key": app_key,
|
|
8184
|
+
"app_name": app_name,
|
|
8127
8185
|
"flow_diff": {"mode": "replace", "node_count": desired_node_count},
|
|
8128
8186
|
"verified": workflow_verified,
|
|
8129
8187
|
}
|
|
@@ -8199,6 +8257,7 @@ class AiBuilderFacade:
|
|
|
8199
8257
|
details=_with_state_read_blocked_details({"app_key": app_key}, resource="views", error=api_error),
|
|
8200
8258
|
suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
|
|
8201
8259
|
))
|
|
8260
|
+
app_name = str(base.get("formTitle") or base.get("title") or base.get("appName") or "").strip() or None
|
|
8202
8261
|
existing_views = existing_views or []
|
|
8203
8262
|
existing_by_key: dict[str, dict[str, Any]] = {}
|
|
8204
8263
|
existing_by_name: dict[str, list[dict[str, Any]]] = {}
|
|
@@ -8306,9 +8365,15 @@ class AiBuilderFacade:
|
|
|
8306
8365
|
)
|
|
8307
8366
|
)
|
|
8308
8367
|
removed: list[str] = []
|
|
8368
|
+
removed_keys: set[str] = set()
|
|
8309
8369
|
view_results: list[dict[str, Any]] = []
|
|
8310
|
-
|
|
8311
|
-
|
|
8370
|
+
failed_views: list[dict[str, Any]] = []
|
|
8371
|
+
for selector in remove_views:
|
|
8372
|
+
selector_text = str(selector or "").strip()
|
|
8373
|
+
if not selector_text:
|
|
8374
|
+
continue
|
|
8375
|
+
key_match = existing_by_key.get(selector_text)
|
|
8376
|
+
matches = [key_match] if isinstance(key_match, dict) else existing_by_name.get(selector_text, [])
|
|
8312
8377
|
if len(matches) > 1:
|
|
8313
8378
|
return _failed(
|
|
8314
8379
|
"AMBIGUOUS_VIEW",
|
|
@@ -8316,7 +8381,7 @@ class AiBuilderFacade:
|
|
|
8316
8381
|
normalized_args=normalized_args,
|
|
8317
8382
|
details={
|
|
8318
8383
|
"app_key": app_key,
|
|
8319
|
-
"view_name":
|
|
8384
|
+
"view_name": selector_text,
|
|
8320
8385
|
"matches": [
|
|
8321
8386
|
{"name": _extract_view_name(view), "view_key": _extract_view_key(view), "type": _normalize_view_type_name(view.get("viewgraphType") or view.get("type"))}
|
|
8322
8387
|
for view in matches
|
|
@@ -8324,21 +8389,36 @@ class AiBuilderFacade:
|
|
|
8324
8389
|
},
|
|
8325
8390
|
suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
|
|
8326
8391
|
)
|
|
8392
|
+
if not matches:
|
|
8393
|
+
failed_view = {
|
|
8394
|
+
"name": selector_text,
|
|
8395
|
+
"view_key": selector_text,
|
|
8396
|
+
"type": None,
|
|
8397
|
+
"status": "failed",
|
|
8398
|
+
"error_code": "VIEW_NOT_FOUND",
|
|
8399
|
+
"message": "remove_views item did not match an existing view name or view_key",
|
|
8400
|
+
}
|
|
8401
|
+
failed_views.append(failed_view)
|
|
8402
|
+
view_results.append(deepcopy(failed_view))
|
|
8403
|
+
continue
|
|
8327
8404
|
if len(matches) == 1:
|
|
8328
8405
|
key = _extract_view_key(matches[0])
|
|
8406
|
+
removed_name = _extract_view_name(matches[0]) or selector_text
|
|
8329
8407
|
self.views.view_delete(profile=profile, viewgraph_key=key)
|
|
8330
|
-
removed.append(
|
|
8331
|
-
|
|
8332
|
-
|
|
8333
|
-
|
|
8408
|
+
removed.append(removed_name)
|
|
8409
|
+
if key:
|
|
8410
|
+
removed_keys.add(key)
|
|
8411
|
+
existing_by_key.pop(key, None)
|
|
8412
|
+
existing_by_name.pop(removed_name, None)
|
|
8413
|
+
view_results.append({"name": removed_name, "view_key": key, "type": None, "status": "removed"})
|
|
8334
8414
|
created: list[str] = []
|
|
8335
8415
|
updated: list[str] = []
|
|
8336
|
-
failed_views: list[dict[str, Any]] = []
|
|
8337
8416
|
existing_view_list = [
|
|
8338
8417
|
view
|
|
8339
8418
|
for view in (existing_views if isinstance(existing_views, list) else [])
|
|
8340
8419
|
if isinstance(view, dict)
|
|
8341
|
-
and _extract_view_name(view) not in
|
|
8420
|
+
and _extract_view_name(view) not in removed
|
|
8421
|
+
and _extract_view_key(view) not in removed_keys
|
|
8342
8422
|
]
|
|
8343
8423
|
for ordinal, patch in enumerate(upsert_views, start=1):
|
|
8344
8424
|
apply_columns = _resolve_view_visible_field_names(patch)
|
|
@@ -8551,6 +8631,11 @@ class AiBuilderFacade:
|
|
|
8551
8631
|
if not existing_key and query_condition_payload_for_apply is None:
|
|
8552
8632
|
query_condition_payload_for_apply = _empty_view_query_conditions_payload()
|
|
8553
8633
|
expected_query_conditions_for_verify = _normalize_view_query_conditions_for_compare(query_condition_payload_for_apply)
|
|
8634
|
+
if not existing_key and associated_resources_payload_for_apply is None:
|
|
8635
|
+
associated_resources_payload_for_apply, _, _ = _build_view_associated_resources_payload(
|
|
8636
|
+
associated_resources={"visible": True, "limit_type": "all", "associated_item_ids": []},
|
|
8637
|
+
available_resources=associated_resources,
|
|
8638
|
+
)
|
|
8554
8639
|
try:
|
|
8555
8640
|
view_auth_override = (
|
|
8556
8641
|
self._compile_visibility_to_member_auth(profile=profile, visibility=patch.visibility)
|
|
@@ -9160,9 +9245,11 @@ class AiBuilderFacade:
|
|
|
9160
9245
|
verification_by_view.append(
|
|
9161
9246
|
{
|
|
9162
9247
|
"name": name,
|
|
9248
|
+
"view_key": item.get("view_key"),
|
|
9163
9249
|
"type": item.get("type"),
|
|
9164
9250
|
"status": "removed",
|
|
9165
|
-
"present_in_readback": None if verified_views_unavailable else name
|
|
9251
|
+
"present_in_readback": None if verified_views_unavailable else name in verified_names,
|
|
9252
|
+
"removed_verified": None if verified_views_unavailable else name not in verified_names,
|
|
9166
9253
|
}
|
|
9167
9254
|
)
|
|
9168
9255
|
else:
|
|
@@ -9183,7 +9270,7 @@ class AiBuilderFacade:
|
|
|
9183
9270
|
view_filters_verified = verified and not filter_readback_pending and not filter_mismatches
|
|
9184
9271
|
view_query_conditions_verified = verified and not query_condition_readback_pending and not query_condition_mismatches
|
|
9185
9272
|
view_associated_resources_verified = verified and not associated_resource_readback_pending and not associated_resource_mismatches
|
|
9186
|
-
view_buttons_verified = verified and not button_readback_pending and not button_mismatches
|
|
9273
|
+
view_buttons_verified = verified and not button_readback_pending and not button_mismatches and not custom_button_readback_pending
|
|
9187
9274
|
noop = not created and not updated and not removed
|
|
9188
9275
|
if failed_views:
|
|
9189
9276
|
successful_changes = bool(created or updated or removed)
|
|
@@ -9252,6 +9339,7 @@ class AiBuilderFacade:
|
|
|
9252
9339
|
"custom_button_readback_pending_entries": deepcopy(custom_button_readback_pending_entries),
|
|
9253
9340
|
},
|
|
9254
9341
|
"app_key": app_key,
|
|
9342
|
+
"app_name": app_name,
|
|
9255
9343
|
"views_diff": {"created": created, "updated": updated, "removed": removed, "failed": failed_views},
|
|
9256
9344
|
"verified": verified and view_filters_verified and view_query_conditions_verified and view_associated_resources_verified and view_buttons_verified,
|
|
9257
9345
|
}
|
|
@@ -9324,6 +9412,7 @@ class AiBuilderFacade:
|
|
|
9324
9412
|
"by_view": verification_by_view,
|
|
9325
9413
|
},
|
|
9326
9414
|
"app_key": app_key,
|
|
9415
|
+
"app_name": app_name,
|
|
9327
9416
|
"views_diff": {"created": created, "updated": updated, "removed": removed, "failed": []},
|
|
9328
9417
|
"verified": all_verified,
|
|
9329
9418
|
}
|
|
@@ -9349,6 +9438,7 @@ class AiBuilderFacade:
|
|
|
9349
9438
|
suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
|
|
9350
9439
|
)
|
|
9351
9440
|
tag_ids_before = _coerce_int_list(base_before.get("tagIds"))
|
|
9441
|
+
app_name_before = str(base_before.get("formTitle") or base_before.get("title") or base_before.get("appName") or "").strip() or None
|
|
9352
9442
|
already_published = bool(base_before.get("appPublishStatus") in {1, 2})
|
|
9353
9443
|
package_already_attached = None if not expected_package_tag_id else expected_package_tag_id in tag_ids_before
|
|
9354
9444
|
try:
|
|
@@ -9379,6 +9469,7 @@ class AiBuilderFacade:
|
|
|
9379
9469
|
"warnings": [],
|
|
9380
9470
|
"verification": {"published": True, "package_attached": package_already_attached, "views_ok": True},
|
|
9381
9471
|
"app_key": app_key,
|
|
9472
|
+
"app_name": app_name_before,
|
|
9382
9473
|
"published": True,
|
|
9383
9474
|
"package_attached": package_already_attached,
|
|
9384
9475
|
"tag_ids_after": tag_ids_before,
|
|
@@ -9411,6 +9502,7 @@ class AiBuilderFacade:
|
|
|
9411
9502
|
suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
|
|
9412
9503
|
)
|
|
9413
9504
|
tag_ids_after = _coerce_int_list(base.get("tagIds"))
|
|
9505
|
+
app_name_after = str(base.get("formTitle") or base.get("title") or base.get("appName") or app_name_before or "").strip() or None
|
|
9414
9506
|
package_attached = None if not expected_package_tag_id else expected_package_tag_id in tag_ids_after
|
|
9415
9507
|
try:
|
|
9416
9508
|
views, views_unavailable = self._load_views_result(profile=profile, app_key=app_key, tolerate_404=True)
|
|
@@ -9451,6 +9543,7 @@ class AiBuilderFacade:
|
|
|
9451
9543
|
"warnings": warnings,
|
|
9452
9544
|
"verification": {"published": bool(base.get("appPublishStatus") in {1, 2}), "package_attached": package_attached, "views_ok": views_ok, "views_read_unavailable": views_unavailable},
|
|
9453
9545
|
"app_key": app_key,
|
|
9546
|
+
"app_name": app_name_after,
|
|
9454
9547
|
"published": bool(base.get("appPublishStatus") in {1, 2}),
|
|
9455
9548
|
"package_attached": package_attached,
|
|
9456
9549
|
"tag_ids_after": tag_ids_after,
|
|
@@ -9588,6 +9681,7 @@ class AiBuilderFacade:
|
|
|
9588
9681
|
if resolved_outcome is not None:
|
|
9589
9682
|
permission_outcomes.append(resolved_outcome)
|
|
9590
9683
|
app_key = str(app_result.get("app_key") or request.app_key)
|
|
9684
|
+
app_name = str(app_result.get("app_name") or "").strip() or None
|
|
9591
9685
|
permission_outcome = self._guard_app_permission(
|
|
9592
9686
|
profile=profile,
|
|
9593
9687
|
app_key=app_key,
|
|
@@ -9933,6 +10027,7 @@ class AiBuilderFacade:
|
|
|
9933
10027
|
"chart_list_source": readback_list_source or existing_chart_list_source,
|
|
9934
10028
|
},
|
|
9935
10029
|
"app_key": app_key,
|
|
10030
|
+
"app_name": app_name,
|
|
9936
10031
|
"chart_results": chart_results,
|
|
9937
10032
|
"verified": False if failed_items else verified,
|
|
9938
10033
|
})
|
|
@@ -9961,6 +10056,7 @@ class AiBuilderFacade:
|
|
|
9961
10056
|
"chart_list_source": existing_chart_list_source if noop else readback_list_source,
|
|
9962
10057
|
},
|
|
9963
10058
|
"app_key": app_key,
|
|
10059
|
+
"app_name": app_name,
|
|
9964
10060
|
"chart_results": chart_results,
|
|
9965
10061
|
"verified": result_verified,
|
|
9966
10062
|
})
|
|
@@ -10246,6 +10342,9 @@ class AiBuilderFacade:
|
|
|
10246
10342
|
"publish_failed": publish_failed,
|
|
10247
10343
|
},
|
|
10248
10344
|
"dash_key": dash_key,
|
|
10345
|
+
"dash_name": update_payload.get("dashName"),
|
|
10346
|
+
"package_id": target_package_tag_id,
|
|
10347
|
+
"created": creating,
|
|
10249
10348
|
"published": published,
|
|
10250
10349
|
"verified": verified,
|
|
10251
10350
|
"draft_result": draft_result,
|
|
@@ -10338,6 +10437,49 @@ class AiBuilderFacade:
|
|
|
10338
10437
|
verification["published"] = False
|
|
10339
10438
|
return response
|
|
10340
10439
|
publish_result = self._publish_current_edit_version(profile=profile, app_key=app_key, edit_version_no=edit_version_no)
|
|
10440
|
+
if publish_result.get("status") == "failed" and publish_result.get("error_code") == "APP_EDIT_LOCKED":
|
|
10441
|
+
details = response.get("details")
|
|
10442
|
+
if not isinstance(details, dict):
|
|
10443
|
+
details = {}
|
|
10444
|
+
response["details"] = details
|
|
10445
|
+
suggested = publish_result.get("suggested_next_call")
|
|
10446
|
+
release_args = suggested.get("arguments") if isinstance(suggested, dict) else {}
|
|
10447
|
+
if isinstance(release_args, dict):
|
|
10448
|
+
release_result = self.app_release_edit_lock_if_mine(
|
|
10449
|
+
profile=profile,
|
|
10450
|
+
app_key=app_key,
|
|
10451
|
+
lock_owner_email=str(release_args.get("lock_owner_email") or ""),
|
|
10452
|
+
lock_owner_name=str(release_args.get("lock_owner_name") or ""),
|
|
10453
|
+
)
|
|
10454
|
+
details["edit_lock_release_result"] = release_result
|
|
10455
|
+
if release_result.get("status") == "success":
|
|
10456
|
+
retry_result = self._publish_current_edit_version(profile=profile, app_key=app_key, edit_version_no=None)
|
|
10457
|
+
details["publish_retry_after_edit_lock_release"] = retry_result
|
|
10458
|
+
publish_result = retry_result
|
|
10459
|
+
response["retried_after_edit_lock_release"] = True
|
|
10460
|
+
response["edit_lock_released"] = True
|
|
10461
|
+
elif release_result.get("error_code") == "EDIT_LOCK_OWNER_UNKNOWN" and edit_version_no is not None:
|
|
10462
|
+
try:
|
|
10463
|
+
self.apps.app_edit_finished(profile=profile, app_key=app_key, payload={"editVersionNo": edit_version_no})
|
|
10464
|
+
release_result = {
|
|
10465
|
+
"status": "success",
|
|
10466
|
+
"error_code": None,
|
|
10467
|
+
"recoverable": False,
|
|
10468
|
+
"message": "released current apply edit context before publish retry",
|
|
10469
|
+
"details": {"app_key": app_key, "edit_version_no": edit_version_no, "release_strategy": "current_apply_edit_context"},
|
|
10470
|
+
"verification": {"released": True},
|
|
10471
|
+
"app_key": app_key,
|
|
10472
|
+
"verified": True,
|
|
10473
|
+
}
|
|
10474
|
+
details["edit_lock_release_result"] = release_result
|
|
10475
|
+
retry_result = self._publish_current_edit_version(profile=profile, app_key=app_key, edit_version_no=None)
|
|
10476
|
+
details["publish_retry_after_edit_lock_release"] = retry_result
|
|
10477
|
+
publish_result = retry_result
|
|
10478
|
+
response["retried_after_edit_lock_release"] = True
|
|
10479
|
+
response["edit_lock_released"] = True
|
|
10480
|
+
except (QingflowApiError, RuntimeError) as error:
|
|
10481
|
+
api_error = _coerce_api_error(error)
|
|
10482
|
+
details["current_apply_edit_context_release_error"] = _transport_error_payload(api_error)
|
|
10341
10483
|
response["publish_result"] = publish_result
|
|
10342
10484
|
response["published"] = bool(publish_result.get("published"))
|
|
10343
10485
|
verification["published"] = bool(publish_result.get("published"))
|
|
@@ -10362,6 +10504,14 @@ class AiBuilderFacade:
|
|
|
10362
10504
|
"schema_source": schema_source,
|
|
10363
10505
|
}
|
|
10364
10506
|
|
|
10507
|
+
def _read_app_name_for_builder_output(self, *, profile: str, app_key: str) -> str | None:
|
|
10508
|
+
try:
|
|
10509
|
+
base = self.apps.app_get_base(profile=profile, app_key=app_key, include_raw=True).get("result") or {}
|
|
10510
|
+
except (QingflowApiError, RuntimeError):
|
|
10511
|
+
return None
|
|
10512
|
+
app_name = str(base.get("formTitle") or base.get("title") or base.get("appName") or "").strip()
|
|
10513
|
+
return app_name or None
|
|
10514
|
+
|
|
10365
10515
|
def _sync_system_view_apply_config(
|
|
10366
10516
|
self,
|
|
10367
10517
|
*,
|
|
@@ -12217,6 +12367,8 @@ def _extract_edit_lock_owner(message: str) -> JSONObject:
|
|
|
12217
12367
|
patterns = [
|
|
12218
12368
|
r"应用已被\s*(?P<name>[^((]+?)\s*[((](?P<email>[^))]+)[))]\s*编辑",
|
|
12219
12369
|
r"edited by\s*(?P<name>[^<(]+?)\s*<(?P<email>[^>]+)>",
|
|
12370
|
+
r"active editor\s+(?P<email>[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,})",
|
|
12371
|
+
r"(?P<email>[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,})",
|
|
12220
12372
|
]
|
|
12221
12373
|
for pattern in patterns:
|
|
12222
12374
|
match = re.search(pattern, text)
|
|
@@ -14193,6 +14345,38 @@ def _parse_schema(schema: dict[str, Any]) -> dict[str, Any]:
|
|
|
14193
14345
|
return parsed
|
|
14194
14346
|
|
|
14195
14347
|
|
|
14348
|
+
def _schema_field_identity(field: dict[str, Any] | None, *, fallback_name: str | None = None) -> dict[str, Any]:
|
|
14349
|
+
field = field or {}
|
|
14350
|
+
name = str(field.get("name") or fallback_name or "").strip() or None
|
|
14351
|
+
field_id = str(field.get("field_id") or "").strip() or None
|
|
14352
|
+
que_id = _coerce_positive_int(field.get("que_id") or field.get("queId"))
|
|
14353
|
+
return {
|
|
14354
|
+
"name": name,
|
|
14355
|
+
"field_id": field_id,
|
|
14356
|
+
"que_id": que_id,
|
|
14357
|
+
}
|
|
14358
|
+
|
|
14359
|
+
|
|
14360
|
+
def _schema_field_diff_details(
|
|
14361
|
+
*,
|
|
14362
|
+
added: list[str],
|
|
14363
|
+
updated: list[str],
|
|
14364
|
+
removed: list[str],
|
|
14365
|
+
before_fields: list[dict[str, Any]],
|
|
14366
|
+
after_fields: list[dict[str, Any]],
|
|
14367
|
+
) -> dict[str, list[dict[str, Any]]]:
|
|
14368
|
+
before_by_name = {str(field.get("name") or ""): field for field in before_fields if str(field.get("name") or "").strip()}
|
|
14369
|
+
after_by_name = {str(field.get("name") or ""): field for field in after_fields if str(field.get("name") or "").strip()}
|
|
14370
|
+
return {
|
|
14371
|
+
"added": [_schema_field_identity(after_by_name.get(name), fallback_name=name) for name in added],
|
|
14372
|
+
"updated": [
|
|
14373
|
+
_schema_field_identity(after_by_name.get(name) or before_by_name.get(name), fallback_name=name)
|
|
14374
|
+
for name in updated
|
|
14375
|
+
],
|
|
14376
|
+
"removed": [_schema_field_identity(before_by_name.get(name), fallback_name=name) for name in removed],
|
|
14377
|
+
}
|
|
14378
|
+
|
|
14379
|
+
|
|
14196
14380
|
def _resolve_layout_sections_to_names(
|
|
14197
14381
|
requested_sections: list[dict[str, Any]],
|
|
14198
14382
|
fields: list[dict[str, Any]],
|
|
@@ -19360,7 +19544,7 @@ def _compare_view_button_summaries(
|
|
|
19360
19544
|
expected_without_pending = [item for index, item in enumerate(expected) if index not in pending_indexes]
|
|
19361
19545
|
if _sorted_view_button_compare_signatures(actual) == _sorted_view_button_compare_signatures(expected_without_pending):
|
|
19362
19546
|
return {
|
|
19363
|
-
"verified":
|
|
19547
|
+
"verified": False,
|
|
19364
19548
|
"custom_button_readback_pending": True,
|
|
19365
19549
|
"pending_custom_buttons": deepcopy(pending_custom_buttons),
|
|
19366
19550
|
}
|
|
@@ -19371,7 +19555,7 @@ def _compare_view_button_summaries(
|
|
|
19371
19555
|
and _sorted_view_button_compare_signatures(actual_other) == _sorted_view_button_compare_signatures(expected_other)
|
|
19372
19556
|
)
|
|
19373
19557
|
return {
|
|
19374
|
-
"verified":
|
|
19558
|
+
"verified": False,
|
|
19375
19559
|
"custom_button_readback_pending": custom_button_readback_pending,
|
|
19376
19560
|
"pending_custom_buttons": deepcopy(expected_custom) if custom_button_readback_pending else [],
|
|
19377
19561
|
}
|