@qingflow-tech/qingflow-cli 1.1.50-beta.2 → 1.1.50-beta.4
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 +3 -3
- package/package.json +1 -1
- package/pyproject.toml +7 -2
- package/skills/qingflow-cli/SKILL.md +74 -0
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_VIEWS_WORKFLOW.md +3 -9
- package/skills/qingflow-cli/reference/builder/20-build-complete-system.md +1 -0
- package/skills/qingflow-cli/reference/builder/50-views.md +180 -191
- package/skills/qingflow-cli/reference/builder/README.md +1 -1
- package/skills/qingflow-cli/reference/builder/reference/app-delivery-sop.md +3 -3
- package/skills/qingflow-cli/reference/examples/README.md +1 -1
- package/skills/qingflow-cli/reference/examples/views/views_apply_v1alpha1.example.json +14 -0
- package/skills/qingflow-cli/reference/examples/views/views_upsert_table_minimal.example.json +30 -36
- package/src/qingflow_mcp/builder_facade/models.py +227 -17
- package/src/qingflow_mcp/builder_facade/schemas/views/manifest.json +42 -0
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/complete.json +1245 -0
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/details/button-config.json +53 -0
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/details/data-permission.json +60 -0
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/details/display.json +73 -0
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/details/operation-permission.json +48 -0
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/details/query-condition.json +47 -0
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/summary.json +319 -0
- package/src/qingflow_mcp/builder_facade/service.py +1460 -410
- package/src/qingflow_mcp/builder_facade/views_extract.py +592 -0
- package/src/qingflow_mcp/builder_facade/views_schema.py +400 -0
- package/src/qingflow_mcp/cli/commands/builder.py +72 -53
- package/src/qingflow_mcp/public_surface.py +3 -0
- package/src/qingflow_mcp/response_trim.py +11 -2
- package/src/qingflow_mcp/server_app_builder.py +32 -8
- package/src/qingflow_mcp/solution/compiler/view_compiler.py +2 -1
- package/src/qingflow_mcp/solution/executor.py +96 -8
- package/src/qingflow_mcp/solution/spec_models.py +1 -0
- package/src/qingflow_mcp/tools/ai_builder_tools.py +211 -393
- package/src/qingflow_mcp/tools/solution_tools.py +2 -0
- package/src/qingflow_mcp/tools/view_tools.py +0 -6
- package/src/qingflow_mcp/tools/view_writer.py +44 -0
- package/skills/qingflow-cli/reference/examples/views/views_batch_full.example.json +0 -159
- package/skills/qingflow-cli/reference/views_upsert_table_minimal.example.json +0 -42
|
@@ -115,6 +115,15 @@ def _elapsed_ms(started_at: float) -> int:
|
|
|
115
115
|
return max(0, int((time.perf_counter() - started_at) * 1000))
|
|
116
116
|
|
|
117
117
|
|
|
118
|
+
def _transport_error_payload(error: QingflowApiError) -> JSONObject:
|
|
119
|
+
return {
|
|
120
|
+
"http_status": error.http_status,
|
|
121
|
+
"backend_code": error.backend_code,
|
|
122
|
+
"category": error.category,
|
|
123
|
+
"request_id": error.request_id,
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
|
|
118
127
|
def _merge_duration_breakdown(payload: JSONObject, **items: int | None) -> None:
|
|
119
128
|
breakdown = payload.get("duration_breakdown_ms")
|
|
120
129
|
if not isinstance(breakdown, dict):
|
|
@@ -151,7 +160,6 @@ BUILDER_APPLY_TOOL_NAMES = {
|
|
|
151
160
|
"app_publish_verify",
|
|
152
161
|
}
|
|
153
162
|
MULTI_APP_SCHEMA_APPLY_PARALLEL_LIMIT = 10
|
|
154
|
-
VIEW_APPLY_PARALLEL_LIMIT = 20
|
|
155
163
|
PORTAL_INLINE_CHART_MAX_WORKERS = 10
|
|
156
164
|
|
|
157
165
|
|
|
@@ -434,6 +442,30 @@ class AiBuilderTools(ToolBase):
|
|
|
434
442
|
) -> JSONObject:
|
|
435
443
|
return self.app_form_apply(profile=profile, spec=spec or {})
|
|
436
444
|
|
|
445
|
+
@mcp.tool()
|
|
446
|
+
def app_views_schema(
|
|
447
|
+
schema_version: str | None = None,
|
|
448
|
+
schema_kind: str | None = None,
|
|
449
|
+
) -> JSONObject:
|
|
450
|
+
"""Get a complete views schema or a focused detail schema such as display or button-config."""
|
|
451
|
+
return self.app_views_schema(schema_version=schema_version, schema_kind=schema_kind)
|
|
452
|
+
|
|
453
|
+
@mcp.tool()
|
|
454
|
+
def app_views_get(
|
|
455
|
+
profile: str = DEFAULT_PROFILE,
|
|
456
|
+
app_key: str = "",
|
|
457
|
+
schema_version: str | None = None,
|
|
458
|
+
schema_kind: str | None = None,
|
|
459
|
+
) -> JSONObject:
|
|
460
|
+
return self.app_views_get(profile=profile, app_key=app_key, schema_version=schema_version, schema_kind=schema_kind)
|
|
461
|
+
|
|
462
|
+
@mcp.tool()
|
|
463
|
+
def app_views_validate(
|
|
464
|
+
declaration: JSONObject | None = None,
|
|
465
|
+
schema_version: str | None = None,
|
|
466
|
+
) -> JSONObject:
|
|
467
|
+
return self.app_views_validate(declaration=declaration or {}, schema_version=schema_version)
|
|
468
|
+
|
|
437
469
|
@mcp.tool(description=self._high_risk_tool_description(operation="delete", target="app configuration"))
|
|
438
470
|
def app_delete(
|
|
439
471
|
profile: str = DEFAULT_PROFILE,
|
|
@@ -763,19 +795,17 @@ class AiBuilderTools(ToolBase):
|
|
|
763
795
|
profile: str = DEFAULT_PROFILE,
|
|
764
796
|
app_key: str = "",
|
|
765
797
|
publish: bool = True,
|
|
766
|
-
|
|
798
|
+
upsert_view: JSONObject | None = None,
|
|
767
799
|
patch_views: list[JSONObject] | None = None,
|
|
768
800
|
remove_views: list[str] | None = None,
|
|
769
|
-
apps: list[JSONObject] | None = None,
|
|
770
801
|
) -> JSONObject:
|
|
771
802
|
return self.app_views_apply(
|
|
772
803
|
profile=profile,
|
|
773
804
|
app_key=app_key,
|
|
774
805
|
publish=publish,
|
|
775
|
-
|
|
806
|
+
upsert_view=upsert_view,
|
|
776
807
|
patch_views=patch_views or [],
|
|
777
808
|
remove_views=remove_views or [],
|
|
778
|
-
apps=apps,
|
|
779
809
|
)
|
|
780
810
|
|
|
781
811
|
@mcp.tool()
|
|
@@ -2251,17 +2281,34 @@ class AiBuilderTools(ToolBase):
|
|
|
2251
2281
|
*,
|
|
2252
2282
|
profile: str,
|
|
2253
2283
|
app_key: str,
|
|
2284
|
+
upsert_view: JSONObject | None = None,
|
|
2254
2285
|
upsert_views: list[JSONObject] | None = None,
|
|
2255
2286
|
patch_views: list[JSONObject] | None = None,
|
|
2256
2287
|
remove_views: list[str] | None = None,
|
|
2257
2288
|
preset: str | None = None,
|
|
2258
2289
|
) -> JSONObject:
|
|
2259
|
-
"""
|
|
2290
|
+
"""Plan one new or fully updated view, plus optional existing-view changes."""
|
|
2291
|
+
if upsert_view is not None and upsert_views:
|
|
2292
|
+
return _config_failure(
|
|
2293
|
+
tool_name="app_views_plan",
|
|
2294
|
+
message="pass either upsert_view or upsert_views, not both.",
|
|
2295
|
+
fix_hint="Use upsert_view for one new or full view declaration.",
|
|
2296
|
+
details={"provided": ["upsert_view", "upsert_views"]},
|
|
2297
|
+
)
|
|
2298
|
+
normalized_upsert_views = [upsert_view] if upsert_view is not None else list(upsert_views or [])
|
|
2299
|
+
if len(normalized_upsert_views) > 1:
|
|
2300
|
+
return _config_failure(
|
|
2301
|
+
tool_name="app_views_plan",
|
|
2302
|
+
message="app_views_plan supports planning one new or fully updated view per call.",
|
|
2303
|
+
fix_hint="Call app_views_plan once for each view, using upsert_view.",
|
|
2304
|
+
details={"max_upsert_views": 1, "received_upsert_views": len(normalized_upsert_views)},
|
|
2305
|
+
error_code="VIEW_BATCH_CREATE_UNSUPPORTED",
|
|
2306
|
+
)
|
|
2260
2307
|
reserved_failure = _reserved_system_view_name_failure(
|
|
2261
2308
|
tool_name="app_views_plan",
|
|
2262
2309
|
profile=profile,
|
|
2263
2310
|
app_key=app_key,
|
|
2264
|
-
upsert_views=
|
|
2311
|
+
upsert_views=normalized_upsert_views,
|
|
2265
2312
|
remove_views=remove_views,
|
|
2266
2313
|
)
|
|
2267
2314
|
if reserved_failure is not None:
|
|
@@ -2270,7 +2317,7 @@ class AiBuilderTools(ToolBase):
|
|
|
2270
2317
|
request = ViewsPlanRequest.model_validate(
|
|
2271
2318
|
{
|
|
2272
2319
|
"app_key": app_key,
|
|
2273
|
-
"upsert_views":
|
|
2320
|
+
"upsert_views": normalized_upsert_views,
|
|
2274
2321
|
"patch_views": patch_views or [],
|
|
2275
2322
|
"remove_views": remove_views or [],
|
|
2276
2323
|
"preset": preset,
|
|
@@ -2287,7 +2334,7 @@ class AiBuilderTools(ToolBase):
|
|
|
2287
2334
|
"profile": profile,
|
|
2288
2335
|
"app_key": app_key,
|
|
2289
2336
|
"preset": "default_table",
|
|
2290
|
-
"
|
|
2337
|
+
"upsert_view": None,
|
|
2291
2338
|
"patch_views": [],
|
|
2292
2339
|
"remove_views": [],
|
|
2293
2340
|
},
|
|
@@ -2298,7 +2345,7 @@ class AiBuilderTools(ToolBase):
|
|
|
2298
2345
|
error_code="VIEWS_PLAN_FAILED",
|
|
2299
2346
|
normalized_args={
|
|
2300
2347
|
"app_key": request.app_key,
|
|
2301
|
-
"
|
|
2348
|
+
"upsert_view": public_view_upsert_payload(request.upsert_views[0]) if request.upsert_views else None,
|
|
2302
2349
|
"patch_views": [public_view_partial_payload(patch) for patch in request.patch_views],
|
|
2303
2350
|
"remove_views": list(request.remove_views),
|
|
2304
2351
|
"preset": request.preset.value if request.preset is not None else None,
|
|
@@ -2308,7 +2355,7 @@ class AiBuilderTools(ToolBase):
|
|
|
2308
2355
|
"arguments": {
|
|
2309
2356
|
"profile": profile,
|
|
2310
2357
|
"app_key": request.app_key,
|
|
2311
|
-
"
|
|
2358
|
+
"upsert_view": public_view_upsert_payload(request.upsert_views[0]) if request.upsert_views else None,
|
|
2312
2359
|
"patch_views": [public_view_partial_payload(patch) for patch in request.patch_views],
|
|
2313
2360
|
"remove_views": list(request.remove_views),
|
|
2314
2361
|
"preset": request.preset.value if request.preset is not None else None,
|
|
@@ -3931,41 +3978,47 @@ class AiBuilderTools(ToolBase):
|
|
|
3931
3978
|
profile: str,
|
|
3932
3979
|
app_key: str,
|
|
3933
3980
|
publish: bool = True,
|
|
3934
|
-
|
|
3981
|
+
upsert_view: JSONObject | None = None,
|
|
3935
3982
|
patch_views: list[JSONObject] | None = None,
|
|
3936
|
-
remove_views: list[str],
|
|
3937
|
-
|
|
3938
|
-
|
|
3983
|
+
remove_views: list[str] | None = None,
|
|
3984
|
+
schema_version: str | None = None,
|
|
3985
|
+
upsert_views: list[JSONObject] | None = None,
|
|
3939
3986
|
) -> JSONObject:
|
|
3940
|
-
"""
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3987
|
+
"""Apply one new or full view declaration for one application.
|
|
3988
|
+
|
|
3989
|
+
``upsert_views`` remains an internal compatibility alias while callers
|
|
3990
|
+
migrate to ``upsert_view``. It accepts at most one item.
|
|
3991
|
+
"""
|
|
3992
|
+
if upsert_view is not None and upsert_views:
|
|
3993
|
+
return _attach_builder_apply_envelope(
|
|
3994
|
+
"app_views_apply",
|
|
3995
|
+
_config_failure(
|
|
3996
|
+
tool_name="app_views_apply",
|
|
3997
|
+
message="pass either upsert_view or upsert_views, not both.",
|
|
3998
|
+
fix_hint="Use upsert_view for one new or full view declaration.",
|
|
3999
|
+
details={"provided": ["upsert_view", "upsert_views"]},
|
|
4000
|
+
),
|
|
3946
4001
|
)
|
|
3947
|
-
if
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
patch_views=_payload_list(item, "patch_views", "patchViews", default=patch_views or []),
|
|
3958
|
-
remove_views=_payload_list(item, "remove_views", "removeViews", default=remove_views or []),
|
|
3959
|
-
apps=None,
|
|
4002
|
+
normalized_upsert_views = [upsert_view] if upsert_view is not None else list(upsert_views or [])
|
|
4003
|
+
if len(normalized_upsert_views) > 1:
|
|
4004
|
+
return _attach_builder_apply_envelope(
|
|
4005
|
+
"app_views_apply",
|
|
4006
|
+
_config_failure(
|
|
4007
|
+
tool_name="app_views_apply",
|
|
4008
|
+
message="app_views_apply supports creating or fully updating one view per call.",
|
|
4009
|
+
fix_hint="Call app_views_apply once for each view, using upsert_view.",
|
|
4010
|
+
details={"max_upsert_views": 1, "received_upsert_views": len(normalized_upsert_views)},
|
|
4011
|
+
error_code="VIEW_BATCH_CREATE_UNSUPPORTED",
|
|
3960
4012
|
),
|
|
3961
4013
|
)
|
|
3962
4014
|
result = self._app_views_apply_once(
|
|
3963
4015
|
profile=profile,
|
|
3964
4016
|
app_key=app_key,
|
|
3965
4017
|
publish=publish,
|
|
3966
|
-
upsert_views=
|
|
4018
|
+
upsert_views=normalized_upsert_views,
|
|
3967
4019
|
patch_views=patch_views or [],
|
|
3968
|
-
remove_views=remove_views,
|
|
4020
|
+
remove_views=remove_views or [],
|
|
4021
|
+
schema_version=schema_version,
|
|
3969
4022
|
)
|
|
3970
4023
|
result = self._retry_after_self_lock_release(
|
|
3971
4024
|
profile=profile,
|
|
@@ -3974,255 +4027,14 @@ class AiBuilderTools(ToolBase):
|
|
|
3974
4027
|
profile=profile,
|
|
3975
4028
|
app_key=app_key,
|
|
3976
4029
|
publish=publish,
|
|
3977
|
-
remove_views=remove_views,
|
|
3978
|
-
upsert_views=
|
|
4030
|
+
remove_views=remove_views or [],
|
|
4031
|
+
upsert_views=normalized_upsert_views,
|
|
3979
4032
|
patch_views=patch_views or [],
|
|
4033
|
+
schema_version=schema_version,
|
|
3980
4034
|
),
|
|
3981
4035
|
)
|
|
3982
4036
|
return self._attach_apply_envelope(profile, "app_views_apply", result)
|
|
3983
4037
|
|
|
3984
|
-
def _app_views_apply_view_batch(
|
|
3985
|
-
self,
|
|
3986
|
-
*,
|
|
3987
|
-
profile: str,
|
|
3988
|
-
publish: bool,
|
|
3989
|
-
views: list[JSONObject],
|
|
3990
|
-
) -> JSONObject:
|
|
3991
|
-
started_at = time.perf_counter()
|
|
3992
|
-
if not isinstance(views, list) or not views:
|
|
3993
|
-
return self._attach_apply_envelope(profile,
|
|
3994
|
-
"app_views_apply",
|
|
3995
|
-
_config_failure(
|
|
3996
|
-
tool_name="app_views_apply",
|
|
3997
|
-
message="app_views_apply views[] batch mode requires a non-empty views array.",
|
|
3998
|
-
fix_hint="Pass views as a JSON array; each item must include app_key and operation.",
|
|
3999
|
-
details={"expected_shape": {"views": [{"operation": "upsert", "app_key": "APP_KEY", "name": "业务视图", "type": "table", "columns": ["字段A"]}]}},
|
|
4000
|
-
),
|
|
4001
|
-
)
|
|
4002
|
-
|
|
4003
|
-
def _infer_operation(item: JSONObject) -> str:
|
|
4004
|
-
raw = str(item.get("operation") or item.get("op") or "").strip().lower()
|
|
4005
|
-
aliases = {
|
|
4006
|
-
"create": "upsert",
|
|
4007
|
-
"update": "upsert",
|
|
4008
|
-
"replace": "upsert",
|
|
4009
|
-
"delete": "remove",
|
|
4010
|
-
"remove": "remove",
|
|
4011
|
-
"patch": "patch",
|
|
4012
|
-
"upsert": "upsert",
|
|
4013
|
-
}
|
|
4014
|
-
if raw:
|
|
4015
|
-
return aliases.get(raw, raw)
|
|
4016
|
-
if "set" in item or "unset" in item or "update" in item or "values" in item:
|
|
4017
|
-
return "patch"
|
|
4018
|
-
if "view_key" in item or "viewKey" in item:
|
|
4019
|
-
return "patch" if ("name" in item or "type" in item or "columns" in item or "fields" in item) else "remove"
|
|
4020
|
-
return "upsert"
|
|
4021
|
-
|
|
4022
|
-
def _view_job_payload(item: JSONObject) -> JSONObject:
|
|
4023
|
-
return {
|
|
4024
|
-
key: deepcopy(value)
|
|
4025
|
-
for key, value in item.items()
|
|
4026
|
-
if key
|
|
4027
|
-
not in {
|
|
4028
|
-
"operation",
|
|
4029
|
-
"op",
|
|
4030
|
-
"app_key",
|
|
4031
|
-
"appKey",
|
|
4032
|
-
"publish",
|
|
4033
|
-
}
|
|
4034
|
-
}
|
|
4035
|
-
|
|
4036
|
-
def _view_selector(payload: JSONObject) -> str:
|
|
4037
|
-
return str(payload.get("view_key") or payload.get("viewKey") or payload.get("viewgraphKey") or payload.get("viewGraphKey") or payload.get("name") or "").strip()
|
|
4038
|
-
|
|
4039
|
-
normalized_jobs: list[JSONObject] = []
|
|
4040
|
-
job_results: list[JSONObject | None] = [None for _ in views]
|
|
4041
|
-
errors: list[JSONObject] = []
|
|
4042
|
-
seen_patch_remove: dict[tuple[str, str], int] = {}
|
|
4043
|
-
seen_upsert_names: dict[tuple[str, str], int] = {}
|
|
4044
|
-
|
|
4045
|
-
def _add_validation_error(index: int, error_code: str, message: str, *, app_key: str = "", operation: str = "", reason_path: str = "") -> None:
|
|
4046
|
-
error = {
|
|
4047
|
-
"index": index,
|
|
4048
|
-
"app_key": app_key,
|
|
4049
|
-
"operation": operation,
|
|
4050
|
-
"status": "failed",
|
|
4051
|
-
"error_code": error_code,
|
|
4052
|
-
"message": message,
|
|
4053
|
-
**({"reason_path": reason_path} if reason_path else {}),
|
|
4054
|
-
}
|
|
4055
|
-
errors.append(error)
|
|
4056
|
-
job_results[index] = error
|
|
4057
|
-
|
|
4058
|
-
for index, item in enumerate(views):
|
|
4059
|
-
if not isinstance(item, dict):
|
|
4060
|
-
_add_validation_error(index, "VIEWS_FILE_ITEM_INVALID", "views[] item must be an object", reason_path=f"views[{index}]")
|
|
4061
|
-
continue
|
|
4062
|
-
app_key = str(item.get("app_key") or item.get("appKey") or "").strip()
|
|
4063
|
-
operation = _infer_operation(item)
|
|
4064
|
-
payload = _view_job_payload(item)
|
|
4065
|
-
if not app_key:
|
|
4066
|
-
_add_validation_error(index, "VIEWS_FILE_APP_KEY_REQUIRED", "views[] item requires app_key", operation=operation, reason_path=f"views[{index}].app_key")
|
|
4067
|
-
continue
|
|
4068
|
-
if operation not in {"upsert", "patch", "remove"}:
|
|
4069
|
-
_add_validation_error(index, "VIEWS_FILE_OPERATION_INVALID", "views[].operation must be upsert, patch, or remove", app_key=app_key, operation=operation, reason_path=f"views[{index}].operation")
|
|
4070
|
-
continue
|
|
4071
|
-
selector = _view_selector(payload)
|
|
4072
|
-
if operation in {"patch", "remove"} and not selector:
|
|
4073
|
-
_add_validation_error(index, "VIEWS_FILE_VIEW_SELECTOR_REQUIRED", f"{operation} view job requires view_key or name", app_key=app_key, operation=operation, reason_path=f"views[{index}].view_key")
|
|
4074
|
-
continue
|
|
4075
|
-
if operation in {"patch", "remove"} and selector:
|
|
4076
|
-
conflict_key = (app_key, selector)
|
|
4077
|
-
first_index = seen_patch_remove.get(conflict_key)
|
|
4078
|
-
if first_index is not None:
|
|
4079
|
-
_add_validation_error(index, "DUPLICATE_VIEW_JOB_CONFLICT", f"views[{index}] conflicts with views[{first_index}] for the same app/view selector", app_key=app_key, operation=operation, reason_path=f"views[{index}]")
|
|
4080
|
-
continue
|
|
4081
|
-
seen_patch_remove[conflict_key] = index
|
|
4082
|
-
if operation == "upsert":
|
|
4083
|
-
name = str(payload.get("name") or "").strip()
|
|
4084
|
-
if name:
|
|
4085
|
-
conflict_key = (app_key, name)
|
|
4086
|
-
first_index = seen_upsert_names.get(conflict_key)
|
|
4087
|
-
if first_index is not None:
|
|
4088
|
-
_add_validation_error(index, "DUPLICATE_VIEW_NAME_IN_BATCH", f"views[{index}] duplicates upsert view name from views[{first_index}] in the same app", app_key=app_key, operation=operation, reason_path=f"views[{index}].name")
|
|
4089
|
-
continue
|
|
4090
|
-
seen_upsert_names[conflict_key] = index
|
|
4091
|
-
normalized_jobs.append(
|
|
4092
|
-
{
|
|
4093
|
-
"index": index,
|
|
4094
|
-
"app_key": app_key,
|
|
4095
|
-
"operation": operation,
|
|
4096
|
-
"publish": _payload_bool(item, "publish", default=publish),
|
|
4097
|
-
"payload": payload,
|
|
4098
|
-
}
|
|
4099
|
-
)
|
|
4100
|
-
|
|
4101
|
-
def _extract_view_identity(result: JSONObject, fallback_payload: JSONObject, fallback_operation: str) -> tuple[str | None, str | None, str]:
|
|
4102
|
-
diff = result.get("views_diff") if isinstance(result.get("views_diff"), dict) else {}
|
|
4103
|
-
for key, operation_name in (("created", "created"), ("updated", "updated"), ("removed", "removed"), ("failed", "failed")):
|
|
4104
|
-
entries = diff.get(key)
|
|
4105
|
-
if isinstance(entries, list) and entries:
|
|
4106
|
-
entry = entries[0]
|
|
4107
|
-
if isinstance(entry, dict):
|
|
4108
|
-
return (
|
|
4109
|
-
str(entry.get("name") or entry.get("view_name") or entry.get("viewName") or fallback_payload.get("name") or "").strip() or None,
|
|
4110
|
-
str(entry.get("view_key") or entry.get("viewKey") or fallback_payload.get("view_key") or fallback_payload.get("viewKey") or "").strip() or None,
|
|
4111
|
-
operation_name,
|
|
4112
|
-
)
|
|
4113
|
-
if entry not in (None, ""):
|
|
4114
|
-
return (str(entry), None, operation_name)
|
|
4115
|
-
fallback_name = str(fallback_payload.get("name") or "").strip() or None
|
|
4116
|
-
fallback_view_key = str(fallback_payload.get("view_key") or fallback_payload.get("viewKey") or "").strip() or None
|
|
4117
|
-
return fallback_name, fallback_view_key, "removed" if fallback_operation == "remove" else "updated"
|
|
4118
|
-
|
|
4119
|
-
def _run_view_job(job: JSONObject) -> JSONObject:
|
|
4120
|
-
payload = deepcopy(job["payload"])
|
|
4121
|
-
operation = str(job["operation"])
|
|
4122
|
-
app_key = str(job["app_key"])
|
|
4123
|
-
job_publish = bool(job.get("publish"))
|
|
4124
|
-
if operation == "upsert":
|
|
4125
|
-
call_kwargs = {"upsert_views": [payload], "patch_views": [], "remove_views": []}
|
|
4126
|
-
elif operation == "patch":
|
|
4127
|
-
call_kwargs = {"upsert_views": [], "patch_views": [payload], "remove_views": []}
|
|
4128
|
-
else:
|
|
4129
|
-
call_kwargs = {"upsert_views": [], "patch_views": [], "remove_views": [_view_selector(payload)]}
|
|
4130
|
-
result = self._app_views_apply_once(
|
|
4131
|
-
profile=profile,
|
|
4132
|
-
app_key=app_key,
|
|
4133
|
-
publish=job_publish,
|
|
4134
|
-
**call_kwargs,
|
|
4135
|
-
)
|
|
4136
|
-
result = self._retry_after_self_lock_release(
|
|
4137
|
-
profile=profile,
|
|
4138
|
-
result=result,
|
|
4139
|
-
retry_call=lambda: self._app_views_apply_once(
|
|
4140
|
-
profile=profile,
|
|
4141
|
-
app_key=app_key,
|
|
4142
|
-
publish=job_publish,
|
|
4143
|
-
**call_kwargs,
|
|
4144
|
-
),
|
|
4145
|
-
)
|
|
4146
|
-
status = str(result.get("status") or "success") if isinstance(result, dict) else "failed"
|
|
4147
|
-
name, view_key, actual_operation = _extract_view_identity(result if isinstance(result, dict) else {}, payload, operation)
|
|
4148
|
-
item: JSONObject = {
|
|
4149
|
-
"index": job["index"],
|
|
4150
|
-
"app_key": app_key,
|
|
4151
|
-
"operation": actual_operation if status != "failed" else operation,
|
|
4152
|
-
"requested_operation": operation,
|
|
4153
|
-
"status": status,
|
|
4154
|
-
**({"name": name} if name else {}),
|
|
4155
|
-
**({"view_key": view_key} if view_key else {}),
|
|
4156
|
-
"result": result if isinstance(result, dict) else {"status": "failed", "message": str(result)},
|
|
4157
|
-
}
|
|
4158
|
-
if status == "failed":
|
|
4159
|
-
item["error_code"] = item["result"].get("error_code")
|
|
4160
|
-
item["message"] = item["result"].get("message")
|
|
4161
|
-
return item
|
|
4162
|
-
|
|
4163
|
-
view_write_started_at = time.perf_counter()
|
|
4164
|
-
view_workers = min(VIEW_APPLY_PARALLEL_LIMIT, len(normalized_jobs)) if normalized_jobs else 0
|
|
4165
|
-
if normalized_jobs:
|
|
4166
|
-
with ThreadPoolExecutor(max_workers=view_workers) as executor:
|
|
4167
|
-
future_map = {executor.submit(_run_view_job, job): job for job in normalized_jobs}
|
|
4168
|
-
for future in as_completed(future_map):
|
|
4169
|
-
job = future_map[future]
|
|
4170
|
-
try:
|
|
4171
|
-
item_result = future.result()
|
|
4172
|
-
except (QingflowApiError, RuntimeError) as error:
|
|
4173
|
-
api_error = _coerce_api_error(error)
|
|
4174
|
-
item_result = {
|
|
4175
|
-
"index": job["index"],
|
|
4176
|
-
"app_key": job["app_key"],
|
|
4177
|
-
"operation": job["operation"],
|
|
4178
|
-
"requested_operation": job["operation"],
|
|
4179
|
-
"status": "failed",
|
|
4180
|
-
"error_code": "VIEW_BATCH_JOB_FAILED",
|
|
4181
|
-
"message": _public_error_message("VIEW_BATCH_JOB_FAILED", api_error),
|
|
4182
|
-
"details": {"transport_error": _transport_error_payload(api_error)},
|
|
4183
|
-
"request_id": api_error.request_id,
|
|
4184
|
-
"backend_code": api_error.backend_code,
|
|
4185
|
-
"http_status": None if api_error.http_status == 404 else api_error.http_status,
|
|
4186
|
-
}
|
|
4187
|
-
job_results[int(job["index"])] = item_result
|
|
4188
|
-
view_write_parallel_wall_ms = _elapsed_ms(view_write_started_at)
|
|
4189
|
-
|
|
4190
|
-
final_views = [item if isinstance(item, dict) else {"index": index, "status": "failed", "error_code": "VIEW_BATCH_JOB_MISSING", "message": "view batch job did not produce a result"} for index, item in enumerate(job_results)]
|
|
4191
|
-
errors.extend(item for item in final_views if str(item.get("status") or "") == "failed" and item not in errors)
|
|
4192
|
-
partial = sum(1 for item in final_views if str(item.get("status") or "") == "partial_success")
|
|
4193
|
-
succeeded = sum(1 for item in final_views if str(item.get("status") or "") in {"success", "partial_success"})
|
|
4194
|
-
failed = len(final_views) - succeeded
|
|
4195
|
-
status = "failed" if succeeded == 0 else "partial_success" if failed > 0 or partial > 0 else "success"
|
|
4196
|
-
write_executed = any(
|
|
4197
|
-
isinstance(item.get("result"), dict) and bool(item["result"].get("write_executed"))
|
|
4198
|
-
for item in final_views
|
|
4199
|
-
)
|
|
4200
|
-
payload: JSONObject = {
|
|
4201
|
-
"status": status,
|
|
4202
|
-
"error_code": None if status == "success" else "APP_VIEWS_APPLY_BATCH_PARTIAL" if succeeded else "APP_VIEWS_APPLY_BATCH_FAILED",
|
|
4203
|
-
"recoverable": failed > 0 or partial > 0,
|
|
4204
|
-
"message": f"applied app_views_apply to {succeeded}/{len(final_views)} views" if status != "success" else f"applied app_views_apply to {succeeded} views",
|
|
4205
|
-
"normalized_args": {"views": views},
|
|
4206
|
-
"views": final_views,
|
|
4207
|
-
"errors": errors,
|
|
4208
|
-
"parallelism": {"view_workers": view_workers, "same_app_parallel": True},
|
|
4209
|
-
"verification": {
|
|
4210
|
-
"batch_verified": failed == 0 and partial == 0,
|
|
4211
|
-
"succeeded": succeeded,
|
|
4212
|
-
"partial": partial,
|
|
4213
|
-
"failed": failed,
|
|
4214
|
-
},
|
|
4215
|
-
"write_executed": write_executed,
|
|
4216
|
-
"write_succeeded": write_executed and failed == 0 and partial == 0,
|
|
4217
|
-
"safe_to_retry": not write_executed,
|
|
4218
|
-
}
|
|
4219
|
-
_merge_duration_breakdown(
|
|
4220
|
-
payload,
|
|
4221
|
-
view_write_parallel_wall_ms=view_write_parallel_wall_ms,
|
|
4222
|
-
total_ms=_elapsed_ms(started_at),
|
|
4223
|
-
)
|
|
4224
|
-
return self._attach_apply_envelope(profile, "app_views_apply", payload)
|
|
4225
|
-
|
|
4226
4038
|
def _app_views_apply_once(
|
|
4227
4039
|
self,
|
|
4228
4040
|
*,
|
|
@@ -4232,6 +4044,7 @@ class AiBuilderTools(ToolBase):
|
|
|
4232
4044
|
upsert_views: list[JSONObject],
|
|
4233
4045
|
patch_views: list[JSONObject],
|
|
4234
4046
|
remove_views: list[str],
|
|
4047
|
+
schema_version: str | None = None,
|
|
4235
4048
|
) -> JSONObject:
|
|
4236
4049
|
"""执行内部辅助逻辑。"""
|
|
4237
4050
|
reserved_failure = _reserved_system_view_name_failure(
|
|
@@ -4259,7 +4072,7 @@ class AiBuilderTools(ToolBase):
|
|
|
4259
4072
|
"profile": profile,
|
|
4260
4073
|
"app_key": app_key,
|
|
4261
4074
|
"publish": publish,
|
|
4262
|
-
"
|
|
4075
|
+
"upsert_view": (upsert_views[0] if upsert_views else {"name": "业务台账视图", "type": "table", "columns": ["字段A"]}),
|
|
4263
4076
|
"patch_views": patch_views or [],
|
|
4264
4077
|
"remove_views": remove_views or [],
|
|
4265
4078
|
},
|
|
@@ -4268,9 +4081,10 @@ class AiBuilderTools(ToolBase):
|
|
|
4268
4081
|
normalized_args = {
|
|
4269
4082
|
"app_key": app_key,
|
|
4270
4083
|
"publish": publish,
|
|
4271
|
-
"
|
|
4084
|
+
"upsert_view": public_view_upsert_payload(parsed_views[0]) if parsed_views else None,
|
|
4272
4085
|
"patch_views": [public_view_partial_payload(patch) for patch in parsed_patch_views],
|
|
4273
4086
|
"remove_views": list(remove_views or []),
|
|
4087
|
+
"schema_version": schema_version,
|
|
4274
4088
|
}
|
|
4275
4089
|
return _safe_tool_call(
|
|
4276
4090
|
lambda: self._facade.app_views_apply(
|
|
@@ -4280,12 +4094,44 @@ class AiBuilderTools(ToolBase):
|
|
|
4280
4094
|
upsert_views=parsed_views,
|
|
4281
4095
|
patch_views=parsed_patch_views,
|
|
4282
4096
|
remove_views=list(remove_views or []),
|
|
4097
|
+
schema_version=schema_version,
|
|
4283
4098
|
),
|
|
4284
4099
|
error_code="VIEWS_APPLY_FAILED",
|
|
4285
4100
|
normalized_args=normalized_args,
|
|
4286
4101
|
suggested_next_call={"tool_name": "app_views_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
4287
4102
|
)
|
|
4288
4103
|
|
|
4104
|
+
def app_views_schema(
|
|
4105
|
+
self,
|
|
4106
|
+
*,
|
|
4107
|
+
schema_version: str | None = None,
|
|
4108
|
+
schema_kind: str | None = None,
|
|
4109
|
+
) -> JSONObject:
|
|
4110
|
+
"""Return a versioned views schema (summary/complete or a focused detail kind)."""
|
|
4111
|
+
return self._facade.app_views_schema(schema_version=schema_version, schema_kind=schema_kind)
|
|
4112
|
+
|
|
4113
|
+
def app_views_get(
|
|
4114
|
+
self,
|
|
4115
|
+
*,
|
|
4116
|
+
profile: str,
|
|
4117
|
+
app_key: str,
|
|
4118
|
+
schema_version: str | None = None,
|
|
4119
|
+
schema_kind: str | None = None,
|
|
4120
|
+
) -> JSONObject:
|
|
4121
|
+
"""Read canonical views declarations (summary projection or complete reverse-compilation)."""
|
|
4122
|
+
return self._facade.app_views_get(
|
|
4123
|
+
profile=profile, app_key=app_key, schema_version=schema_version, schema_kind=schema_kind or "summary"
|
|
4124
|
+
)
|
|
4125
|
+
|
|
4126
|
+
def app_views_validate(
|
|
4127
|
+
self,
|
|
4128
|
+
*,
|
|
4129
|
+
declaration: JSONObject,
|
|
4130
|
+
schema_version: str | None = None,
|
|
4131
|
+
) -> JSONObject:
|
|
4132
|
+
"""Offline-validate a views declaration without touching the backend."""
|
|
4133
|
+
return self._facade.app_views_validate(declaration=declaration or {}, schema_version=schema_version)
|
|
4134
|
+
|
|
4289
4135
|
def chart_apply(
|
|
4290
4136
|
self,
|
|
4291
4137
|
*,
|
|
@@ -5668,12 +5514,10 @@ def _reserved_system_view_name_failure(
|
|
|
5668
5514
|
"arguments": {
|
|
5669
5515
|
"profile": profile,
|
|
5670
5516
|
"app_key": app_key,
|
|
5671
|
-
"
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
}
|
|
5676
|
-
],
|
|
5517
|
+
"upsert_view": {
|
|
5518
|
+
**item,
|
|
5519
|
+
"name": "业务台账视图",
|
|
5520
|
+
},
|
|
5677
5521
|
"patch_views": patch_views or [],
|
|
5678
5522
|
"remove_views": remove_views or [],
|
|
5679
5523
|
},
|
|
@@ -5683,7 +5527,7 @@ def _reserved_system_view_name_failure(
|
|
|
5683
5527
|
return _config_failure(
|
|
5684
5528
|
tool_name=tool_name,
|
|
5685
5529
|
error_code="RESERVED_SYSTEM_VIEW_NAME",
|
|
5686
|
-
message=f"
|
|
5530
|
+
message=f"upsert_view.name uses built-in system view name '{name}' without view_key",
|
|
5687
5531
|
fix_hint="Do not create business views named 全部数据, 我的数据, 我发起的, 待办, 已办, or 抄送. Use a business-specific view name for new views; to modify a built-in view, pass its existing raw view_key or use patch_views.",
|
|
5688
5532
|
details={
|
|
5689
5533
|
"reserved_view_names": sorted(_RESERVED_SYSTEM_VIEW_NAMES),
|
|
@@ -8147,20 +7991,20 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
8147
7991
|
"app_views_plan": {
|
|
8148
7992
|
"allowed_keys": [
|
|
8149
7993
|
"app_key",
|
|
8150
|
-
"
|
|
7994
|
+
"upsert_view",
|
|
8151
7995
|
"patch_views",
|
|
8152
7996
|
"remove_views",
|
|
8153
7997
|
"preset",
|
|
8154
|
-
"
|
|
8155
|
-
"
|
|
8156
|
-
"
|
|
8157
|
-
"
|
|
8158
|
-
"
|
|
8159
|
-
"
|
|
8160
|
-
"
|
|
8161
|
-
"
|
|
8162
|
-
"
|
|
8163
|
-
"
|
|
7998
|
+
"upsert_view.view_key",
|
|
7999
|
+
"upsert_view.name",
|
|
8000
|
+
"upsert_view.type",
|
|
8001
|
+
"upsert_view.columns",
|
|
8002
|
+
"upsert_view.filters",
|
|
8003
|
+
"upsert_view.buttons",
|
|
8004
|
+
"upsert_view.action_buttons",
|
|
8005
|
+
"upsert_view.action_buttons_mode",
|
|
8006
|
+
"upsert_view.visibility",
|
|
8007
|
+
"upsert_view.query_conditions",
|
|
8164
8008
|
"patch_views[].view_key",
|
|
8165
8009
|
"patch_views[].name",
|
|
8166
8010
|
"patch_views[].set",
|
|
@@ -8181,6 +8025,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
8181
8025
|
"queryConditions": "query_conditions",
|
|
8182
8026
|
"query_condition": "query_conditions",
|
|
8183
8027
|
"queryCondition": "query_conditions",
|
|
8028
|
+
"upsertView": "upsert_view",
|
|
8184
8029
|
"patchViews": "patch_views",
|
|
8185
8030
|
"startField": "start_field",
|
|
8186
8031
|
"endField": "end_field",
|
|
@@ -8215,18 +8060,19 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
8215
8060
|
},
|
|
8216
8061
|
"execution_notes": [
|
|
8217
8062
|
"creating a new view follows backend createViewgraphConfig and requires both ViewManagementAuth and DataManageAuth; updating/deleting existing views only requires ViewManagementAuth",
|
|
8218
|
-
"
|
|
8063
|
+
"upsert_view accepts one new or full view declaration; plan additional views in separate calls",
|
|
8064
|
+
"upsert_view.visibility may set per-view visibility; omit it to preserve an existing view's auth or default a new view to workspace/not",
|
|
8219
8065
|
"filters are saved fixed filters that apply when the view opens; query_conditions configure the frontend query panel and only apply after a user enters query values",
|
|
8220
|
-
"
|
|
8066
|
+
"upsert_view.query_conditions.rows is a layout matrix of query-panel supported field names, field IDs, or queIds; queIds are preferred and all forms are compiled to backend queryCondition queIds",
|
|
8221
8067
|
"do not put relation/attachment/subtable/code_block/q_linker/address fields in query_conditions; use filters for fixed filters or app_associated_resources_apply.match_mappings for current-record relation/report matching",
|
|
8222
8068
|
"use patch_views for partial parameter replacement on existing views; the tool reads current config, merges patch_views[].set/unset, then submits the backend full-save payload internally",
|
|
8223
8069
|
"business buttons should be declared with action_buttons on the target view; app_views_apply writes the view first, then compiles action_buttons through the custom button writer and binds them to the resulting view_key",
|
|
8224
8070
|
"action_buttons_mode defaults to merge; replace rewrites this view's custom button bindings, and replace plus action_buttons=[] clears this view's custom button bindings without deleting button bodies",
|
|
8225
|
-
"legacy
|
|
8071
|
+
"legacy upsert_view.buttons is a compatibility path for raw full replacement of existing view button config; do not use it as the normal agent path",
|
|
8226
8072
|
"remove_views accepts a raw view_key or an exact unique view name; after DELETE the tool verifies deletion by single view_key readback, not by a full app view list",
|
|
8227
8073
|
"deleted views return verification.by_view[].delete_executed, readback_status, and safe_to_retry_delete=false; if readback is pending, do not blindly repeat the delete",
|
|
8228
8074
|
"do not create business views named 全部数据, 我的数据, 我发起的, 待办, 已办, or 抄送; these are built-in system/default views. Use business-specific names for new views, and pass the existing raw view_key or patch_views when changing a built-in view",
|
|
8229
|
-
"new views created by app_views_apply default associated report/view display to visible with limit_type=all; existing views preserve their current associated display unless patch_views/
|
|
8075
|
+
"new views created by app_views_apply default associated report/view display to visible with limit_type=all; existing views preserve their current associated display unless patch_views/upsert_view explicitly changes associated_resources",
|
|
8230
8076
|
"associated report/view resource pool and per-view selected resources are configured through app_associated_resources_apply; app_views_apply only keeps legacy associated_resources input compatible",
|
|
8231
8077
|
"for multi-value operators such as in, pass values as a list; value may also be used as an alias when it already contains a list",
|
|
8232
8078
|
*_VISIBILITY_EXECUTION_NOTES,
|
|
@@ -8234,7 +8080,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
8234
8080
|
"minimal_example": {
|
|
8235
8081
|
"profile": "default",
|
|
8236
8082
|
"app_key": "APP_KEY",
|
|
8237
|
-
"
|
|
8083
|
+
"upsert_view": {"name": "项目台账视图", "type": "table", "columns": ["项目名称"], "visibility": deepcopy(_VISIBILITY_WORKSPACE_EXAMPLE)},
|
|
8238
8084
|
"remove_views": [],
|
|
8239
8085
|
},
|
|
8240
8086
|
"query_conditions_example": {
|
|
@@ -8256,41 +8102,37 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
8256
8102
|
"action_buttons_example": {
|
|
8257
8103
|
"profile": "default",
|
|
8258
8104
|
"app_key": "APP_KEY",
|
|
8259
|
-
"
|
|
8260
|
-
|
|
8261
|
-
|
|
8262
|
-
|
|
8263
|
-
|
|
8264
|
-
|
|
8265
|
-
|
|
8266
|
-
|
|
8267
|
-
|
|
8268
|
-
|
|
8269
|
-
|
|
8270
|
-
|
|
8271
|
-
|
|
8272
|
-
|
|
8273
|
-
}
|
|
8274
|
-
],
|
|
8105
|
+
"upsert_view": {
|
|
8106
|
+
"name": "生产工单执行视图",
|
|
8107
|
+
"type": "table",
|
|
8108
|
+
"columns": ["工单编号", "产品", "状态", "负责人"],
|
|
8109
|
+
"action_buttons": [
|
|
8110
|
+
{
|
|
8111
|
+
"text": "开始生产",
|
|
8112
|
+
"action": "link",
|
|
8113
|
+
"url": "https://example.com/start",
|
|
8114
|
+
"placement": "list",
|
|
8115
|
+
"visible_when": [{"field_name": "状态", "operator": "eq", "value": "待排产"}],
|
|
8116
|
+
}
|
|
8117
|
+
],
|
|
8118
|
+
},
|
|
8275
8119
|
"remove_views": [],
|
|
8276
8120
|
},
|
|
8277
8121
|
"full_upsert_query_conditions_example": {
|
|
8278
8122
|
"profile": "default",
|
|
8279
8123
|
"app_key": "APP_KEY",
|
|
8280
|
-
"
|
|
8281
|
-
|
|
8282
|
-
|
|
8283
|
-
|
|
8284
|
-
|
|
8285
|
-
|
|
8286
|
-
"
|
|
8287
|
-
|
|
8288
|
-
|
|
8289
|
-
|
|
8290
|
-
|
|
8291
|
-
|
|
8292
|
-
}
|
|
8293
|
-
],
|
|
8124
|
+
"upsert_view": {
|
|
8125
|
+
"name": "客户查询视图",
|
|
8126
|
+
"type": "table",
|
|
8127
|
+
"columns": ["客户名称", "负责人", "客户状态", "创建时间"],
|
|
8128
|
+
"filters": [{"field_name": "客户状态", "operator": "eq", "value": "有效"}],
|
|
8129
|
+
"query_conditions": {
|
|
8130
|
+
"enabled": True,
|
|
8131
|
+
"exact": False,
|
|
8132
|
+
"hide_before_query": False,
|
|
8133
|
+
"rows": [["客户名称", "负责人"], ["创建时间"]],
|
|
8134
|
+
},
|
|
8135
|
+
},
|
|
8294
8136
|
"remove_views": [],
|
|
8295
8137
|
},
|
|
8296
8138
|
"query_conditions_field_rules": {
|
|
@@ -8304,48 +8146,35 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
8304
8146
|
"gantt_example": {
|
|
8305
8147
|
"profile": "default",
|
|
8306
8148
|
"app_key": "APP_KEY",
|
|
8307
|
-
"
|
|
8308
|
-
|
|
8309
|
-
|
|
8310
|
-
|
|
8311
|
-
|
|
8312
|
-
|
|
8313
|
-
|
|
8314
|
-
|
|
8315
|
-
|
|
8316
|
-
}
|
|
8317
|
-
],
|
|
8149
|
+
"upsert_view": {
|
|
8150
|
+
"name": "项目甘特图",
|
|
8151
|
+
"type": "gantt",
|
|
8152
|
+
"columns": ["项目名称", "开始日期", "结束日期"],
|
|
8153
|
+
"start_field": "开始日期",
|
|
8154
|
+
"end_field": "结束日期",
|
|
8155
|
+
"title_field": "项目名称",
|
|
8156
|
+
"filters": [{"field_name": "状态", "operator": "eq", "value": "进行中"}],
|
|
8157
|
+
},
|
|
8318
8158
|
"remove_views": [],
|
|
8319
8159
|
},
|
|
8320
8160
|
},
|
|
8321
8161
|
"app_views_apply": {
|
|
8322
8162
|
"allowed_keys": [
|
|
8323
|
-
"views",
|
|
8324
|
-
"views[].operation",
|
|
8325
|
-
"views[].app_key",
|
|
8326
|
-
"views[].view_key",
|
|
8327
|
-
"views[].name",
|
|
8328
|
-
"views[].type",
|
|
8329
|
-
"views[].columns",
|
|
8330
|
-
"views[].filters",
|
|
8331
|
-
"views[].action_buttons",
|
|
8332
|
-
"views[].set",
|
|
8333
|
-
"views[].unset",
|
|
8334
8163
|
"app_key",
|
|
8335
8164
|
"publish",
|
|
8336
|
-
"
|
|
8165
|
+
"upsert_view",
|
|
8337
8166
|
"patch_views",
|
|
8338
8167
|
"remove_views",
|
|
8339
|
-
"
|
|
8340
|
-
"
|
|
8341
|
-
"
|
|
8342
|
-
"
|
|
8343
|
-
"
|
|
8344
|
-
"
|
|
8345
|
-
"
|
|
8346
|
-
"
|
|
8347
|
-
"
|
|
8348
|
-
"
|
|
8168
|
+
"upsert_view.view_key",
|
|
8169
|
+
"upsert_view.name",
|
|
8170
|
+
"upsert_view.type",
|
|
8171
|
+
"upsert_view.columns",
|
|
8172
|
+
"upsert_view.filters",
|
|
8173
|
+
"upsert_view.buttons",
|
|
8174
|
+
"upsert_view.action_buttons",
|
|
8175
|
+
"upsert_view.action_buttons_mode",
|
|
8176
|
+
"upsert_view.visibility",
|
|
8177
|
+
"upsert_view.query_conditions",
|
|
8349
8178
|
"patch_views[].view_key",
|
|
8350
8179
|
"patch_views[].name",
|
|
8351
8180
|
"patch_views[].set",
|
|
@@ -8366,10 +8195,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
8366
8195
|
"queryConditions": "query_conditions",
|
|
8367
8196
|
"query_condition": "query_conditions",
|
|
8368
8197
|
"queryCondition": "query_conditions",
|
|
8369
|
-
"
|
|
8370
|
-
"views[].fields": "views[].columns",
|
|
8371
|
-
"views[].viewKey": "views[].view_key",
|
|
8372
|
-
"views[].actionButtons": "views[].action_buttons",
|
|
8198
|
+
"upsertView": "upsert_view",
|
|
8373
8199
|
"patchViews": "patch_views",
|
|
8374
8200
|
"startField": "start_field",
|
|
8375
8201
|
"endField": "end_field",
|
|
@@ -8392,7 +8218,6 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
8392
8218
|
"action_buttons[].defaultValues": "action_buttons[].default_values",
|
|
8393
8219
|
},
|
|
8394
8220
|
"allowed_values": {
|
|
8395
|
-
"views[].operation": ["upsert", "patch", "remove"],
|
|
8396
8221
|
"view.type": [member.value for member in PublicViewType],
|
|
8397
8222
|
"view.filter.operator": [member.value for member in ViewFilterOperator],
|
|
8398
8223
|
"view.buttons.button_type": ["SYSTEM", "CUSTOM"],
|
|
@@ -8403,9 +8228,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
8403
8228
|
**deepcopy(_VISIBILITY_ALLOWED_VALUES),
|
|
8404
8229
|
},
|
|
8405
8230
|
"execution_notes": [
|
|
8406
|
-
"
|
|
8407
|
-
f"views[] jobs run concurrently with an internal limit of {VIEW_APPLY_PARALLEL_LIMIT}; results are returned in input order",
|
|
8408
|
-
"apply may return partial_success when some views land and others fail",
|
|
8231
|
+
"upsert_view accepts one new or full view declaration for the app_key in this call; submit additional views in separate calls",
|
|
8409
8232
|
"creating a new view follows backend createViewgraphConfig and requires both ViewManagementAuth and DataManageAuth; updating/deleting existing views only requires ViewManagementAuth",
|
|
8410
8233
|
"when duplicate view names exist, supply view_key to target the exact view",
|
|
8411
8234
|
"read back app_get after any failed or partial view apply",
|
|
@@ -8414,24 +8237,25 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
8414
8237
|
"business buttons should be declared with action_buttons on the target view; app_views_apply writes the view first, then compiles action_buttons through the custom button writer and binds them to the resulting view_key",
|
|
8415
8238
|
"patch_views[].set.action_buttons can add buttons to an existing view without rewriting columns/filters; action_buttons_mode defaults to merge, replace rewrites this view's custom button bindings, and replace plus action_buttons=[] clears bindings without deleting button bodies",
|
|
8416
8239
|
"publish=false with action_buttons is blocked as VIEW_ACTION_BUTTONS_REQUIRE_PUBLISH because the underlying custom button writer may publish after successful button writes",
|
|
8417
|
-
"
|
|
8418
|
-
"
|
|
8240
|
+
"upsert_view.buttons is a compatibility path for raw full replacement of existing view button config; do not use it as the normal agent path",
|
|
8241
|
+
"upsert_view.visibility may set per-view visibility; omit it to preserve an existing view's auth or default a new view to workspace/not",
|
|
8419
8242
|
"filters are saved fixed filters that apply when the view opens; query_conditions configure the frontend query panel and only apply after a user enters query values",
|
|
8420
|
-
"
|
|
8243
|
+
"upsert_view.query_conditions.rows is a layout matrix of query-panel supported field names, field IDs, or queIds; queIds are preferred and all forms are compiled to backend queryCondition queIds",
|
|
8421
8244
|
"do not put relation/attachment/subtable/code_block/q_linker/address fields in query_conditions; use filters for fixed filters or app_associated_resources_apply.match_mappings for current-record relation/report matching",
|
|
8422
8245
|
"use patch_views for partial parameter replacement on existing views; the public update mode is patch even though the backend save is still a full view payload",
|
|
8423
8246
|
"remove_views accepts a raw view_key or an exact unique view name; after DELETE the tool verifies deletion by single view_key readback, not by a full app view list",
|
|
8424
8247
|
"deleted views return verification.by_view[].delete_executed, readback_status, and safe_to_retry_delete=false; if readback is pending, do not blindly repeat the delete",
|
|
8425
8248
|
"do not create business views named 全部数据, 我的数据, 我发起的, 待办, 已办, or 抄送; these are built-in system/default views. Use business-specific names for new views, and pass the existing raw view_key or patch_views when changing a built-in view",
|
|
8426
|
-
"new views created by app_views_apply default associated report/view display to visible with limit_type=all; existing views preserve their current associated display unless patch_views/
|
|
8249
|
+
"new views created by app_views_apply default associated report/view display to visible with limit_type=all; existing views preserve their current associated display unless patch_views/upsert_view explicitly changes associated_resources",
|
|
8427
8250
|
"associated report/view resource pool and per-view selected resources are configured through app_associated_resources_apply; app_views_apply keeps legacy associated_resources input compatible but it is no longer the recommended public contract",
|
|
8428
8251
|
"for multi-value operators such as in, pass values as a list; value may also be used as an alias when it already contains a list",
|
|
8429
8252
|
*_VISIBILITY_EXECUTION_NOTES,
|
|
8430
8253
|
],
|
|
8431
8254
|
"minimal_example": {
|
|
8432
8255
|
"profile": "default",
|
|
8256
|
+
"app_key": "APP_KEY",
|
|
8433
8257
|
"publish": True,
|
|
8434
|
-
"
|
|
8258
|
+
"upsert_view": {"name": "项目台账视图", "type": "table", "columns": ["项目名称"], "visibility": deepcopy(_VISIBILITY_WORKSPACE_EXAMPLE)},
|
|
8435
8259
|
},
|
|
8436
8260
|
"query_conditions_example": {
|
|
8437
8261
|
"profile": "default",
|
|
@@ -8454,8 +8278,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
8454
8278
|
"profile": "default",
|
|
8455
8279
|
"app_key": "APP_KEY",
|
|
8456
8280
|
"publish": True,
|
|
8457
|
-
"
|
|
8458
|
-
{
|
|
8281
|
+
"upsert_view": {
|
|
8459
8282
|
"name": "生产工单执行视图",
|
|
8460
8283
|
"type": "table",
|
|
8461
8284
|
"columns": ["工单编号", "产品", "状态", "负责人"],
|
|
@@ -8468,8 +8291,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
8468
8291
|
"visible_when": [{"field_name": "状态", "operator": "eq", "value": "待排产"}],
|
|
8469
8292
|
}
|
|
8470
8293
|
],
|
|
8471
|
-
}
|
|
8472
|
-
],
|
|
8294
|
+
},
|
|
8473
8295
|
"remove_views": [],
|
|
8474
8296
|
},
|
|
8475
8297
|
"action_buttons_patch_example": {
|
|
@@ -8499,8 +8321,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
8499
8321
|
"profile": "default",
|
|
8500
8322
|
"app_key": "APP_KEY",
|
|
8501
8323
|
"publish": True,
|
|
8502
|
-
"
|
|
8503
|
-
{
|
|
8324
|
+
"upsert_view": {
|
|
8504
8325
|
"name": "客户查询视图",
|
|
8505
8326
|
"type": "table",
|
|
8506
8327
|
"columns": ["客户名称", "负责人", "客户状态", "创建时间"],
|
|
@@ -8511,8 +8332,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
8511
8332
|
"hide_before_query": False,
|
|
8512
8333
|
"rows": [["客户名称", "负责人"], ["创建时间"]],
|
|
8513
8334
|
},
|
|
8514
|
-
}
|
|
8515
|
-
],
|
|
8335
|
+
},
|
|
8516
8336
|
"remove_views": [],
|
|
8517
8337
|
},
|
|
8518
8338
|
"query_conditions_field_rules": {
|
|
@@ -8527,8 +8347,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
8527
8347
|
"profile": "default",
|
|
8528
8348
|
"app_key": "APP_KEY",
|
|
8529
8349
|
"publish": True,
|
|
8530
|
-
"
|
|
8531
|
-
{
|
|
8350
|
+
"upsert_view": {
|
|
8532
8351
|
"name": "项目甘特图",
|
|
8533
8352
|
"type": "gantt",
|
|
8534
8353
|
"columns": ["项目名称", "开始日期", "结束日期"],
|
|
@@ -8536,8 +8355,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
8536
8355
|
"end_field": "结束日期",
|
|
8537
8356
|
"title_field": "项目名称",
|
|
8538
8357
|
"filters": [{"field_name": "状态", "operator": "eq", "value": "进行中"}],
|
|
8539
|
-
}
|
|
8540
|
-
],
|
|
8358
|
+
},
|
|
8541
8359
|
"remove_views": [],
|
|
8542
8360
|
},
|
|
8543
8361
|
},
|