@qingflow-tech/qingflow-cli 1.1.50-beta.5 → 1.1.51
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 +4 -4
- package/npm/lib/runtime.mjs +13 -29
- package/package.json +1 -1
- package/pyproject.toml +2 -8
- package/skills/qingflow-cli/SKILL.md +322 -93
- package/skills/qingflow-cli/manifest.yaml +1 -1
- package/skills/qingflow-cli/reference/00-INDEX.md +4 -4
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_ADMIN_CHEATSHEET.md +1 -1
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_BUILDER_VIEWS_WORKFLOW.md +9 -3
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md +137 -3
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_EXPLORATION_REPORT.md +4 -6
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_FIELD_DATA_TYPES.md +14 -14
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_MEMBER_CHEATSHEET.md +19 -20
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_ONE_SHOT_CHEATSHEET.md +14 -18
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_CREATE_WORKFLOW.md +12 -7
- package/skills/qingflow-cli/reference/QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md +1 -2
- package/skills/qingflow-cli/reference/builder/20-build-complete-system.md +2 -3
- package/skills/qingflow-cli/reference/builder/30-schema-fields.md +5 -9
- package/skills/qingflow-cli/reference/builder/50-views.md +191 -192
- package/skills/qingflow-cli/reference/builder/80-buttons-associated-resources.md +0 -2
- package/skills/qingflow-cli/reference/builder/README.md +1 -1
- package/skills/qingflow-cli/reference/builder/reference/app-delivery-sop.md +6 -6
- package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_ADMIN_CHEATSHEET.md +1 -1
- package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md +95 -76
- package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_EXPLORATION_REPORT.md +4 -6
- package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_FIELD_DATA_TYPES.md +13 -13
- package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_MEMBER_CHEATSHEET.md +19 -20
- package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_ONE_SHOT_CHEATSHEET.md +14 -18
- package/skills/qingflow-cli/reference/examples/README.md +1 -1
- package/skills/qingflow-cli/reference/examples/views/views_batch_full.example.json +159 -0
- package/skills/qingflow-cli/reference/examples/views/views_upsert_table_minimal.example.json +36 -30
- package/skills/qingflow-cli/reference/record/QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md +1 -2
- package/skills/qingflow-cli/reference/record/analysis/README.md +113 -21
- package/skills/qingflow-cli/reference/record/analysis/analysis-gotchas.md +10 -10
- package/skills/qingflow-cli/reference/record/analysis/analysis-patterns.md +9 -9
- package/skills/qingflow-cli/reference/record/analysis/business-context.md +1 -1
- package/skills/qingflow-cli/reference/record/analysis/confidence-reporting.md +10 -10
- package/skills/qingflow-cli/reference/record/analysis/data-access-playbook.md +80 -52
- package/skills/qingflow-cli/reference/record/analysis/pandas-recipes.md +2 -2
- package/skills/qingflow-cli/reference/views_upsert_table_minimal.example.json +42 -0
- package/skills/qingflow-common/SKILL.md +4 -7
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/app_form_compat.py +0 -3
- package/src/qingflow_mcp/app_form_validator.py +17 -69
- package/src/qingflow_mcp/builder_facade/app_form.py +100 -497
- package/src/qingflow_mcp/builder_facade/models.py +17 -227
- package/src/qingflow_mcp/builder_facade/schemas/app_form/manifest.json +0 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/complete.json +30 -64
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/address.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/amount.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/attachment.json +10 -6
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/code_block.json +10 -6
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/data_relation.json +18 -11
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/date.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/datetime.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/department.json +10 -6
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/email.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/link.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/long_text.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/member.json +10 -6
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/multi_select.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/number.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/phone.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/q_linker.json +10 -6
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/relation.json +15 -46
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/single_choice.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/single_select.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/subtable.json +27 -61
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/text.json +8 -4
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/formula.json +0 -43
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/summary.json +12 -7
- package/src/qingflow_mcp/builder_facade/service.py +539 -1671
- package/src/qingflow_mcp/builder_facade/workflow_spec.py +1 -1
- package/src/qingflow_mcp/cli/commands/__init__.py +1 -2
- package/src/qingflow_mcp/cli/commands/builder.py +55 -76
- package/src/qingflow_mcp/cli/commands/record.py +1 -1
- package/src/qingflow_mcp/cli/context.py +0 -3
- package/src/qingflow_mcp/cli/formatters.py +1 -97
- package/src/qingflow_mcp/config.py +1 -0
- package/src/qingflow_mcp/public_surface.py +0 -8
- package/src/qingflow_mcp/response_trim.py +2 -11
- package/src/qingflow_mcp/server.py +0 -2
- package/src/qingflow_mcp/server_app_builder.py +10 -34
- package/src/qingflow_mcp/server_app_user.py +0 -4
- package/src/qingflow_mcp/solution/compiler/view_compiler.py +1 -2
- package/src/qingflow_mcp/solution/executor.py +8 -96
- package/src/qingflow_mcp/solution/spec_models.py +0 -1
- package/src/qingflow_mcp/tools/ai_builder_tools.py +398 -218
- package/src/qingflow_mcp/tools/base.py +42 -10
- package/src/qingflow_mcp/tools/record_tools.py +46 -11
- package/src/qingflow_mcp/tools/solution_tools.py +0 -2
- package/src/qingflow_mcp/tools/view_tools.py +6 -0
- package/skills/qingflow-cli/reference/data/README.md +0 -32
- package/skills/qingflow-cli/reference/examples/views/views_apply_v1alpha1.example.json +0 -14
- package/skills/qingflow-cli/reference/record/QINGFLOW_CLI_RECORD_SPECIALIZED_WORKFLOW.md +0 -50
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/rich_text.json +0 -146
- package/src/qingflow_mcp/builder_facade/schemas/views/manifest.json +0 -42
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/complete.json +0 -1245
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/details/button-config.json +0 -53
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/details/data-permission.json +0 -60
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/details/display.json +0 -73
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/details/operation-permission.json +0 -48
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/details/query-condition.json +0 -47
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/summary.json +0 -319
- package/src/qingflow_mcp/builder_facade/views_extract.py +0 -592
- package/src/qingflow_mcp/builder_facade/views_schema.py +0 -400
- package/src/qingflow_mcp/cli/commands/data.py +0 -134
- package/src/qingflow_mcp/tools/csv_export.py +0 -168
- package/src/qingflow_mcp/tools/data_tools.py +0 -284
- package/src/qingflow_mcp/tools/view_writer.py +0 -44
|
@@ -115,15 +115,6 @@ 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
|
-
|
|
127
118
|
def _merge_duration_breakdown(payload: JSONObject, **items: int | None) -> None:
|
|
128
119
|
breakdown = payload.get("duration_breakdown_ms")
|
|
129
120
|
if not isinstance(breakdown, dict):
|
|
@@ -160,6 +151,7 @@ BUILDER_APPLY_TOOL_NAMES = {
|
|
|
160
151
|
"app_publish_verify",
|
|
161
152
|
}
|
|
162
153
|
MULTI_APP_SCHEMA_APPLY_PARALLEL_LIMIT = 10
|
|
154
|
+
VIEW_APPLY_PARALLEL_LIMIT = 20
|
|
163
155
|
PORTAL_INLINE_CHART_MAX_WORKERS = 10
|
|
164
156
|
|
|
165
157
|
|
|
@@ -442,30 +434,6 @@ class AiBuilderTools(ToolBase):
|
|
|
442
434
|
) -> JSONObject:
|
|
443
435
|
return self.app_form_apply(profile=profile, spec=spec or {})
|
|
444
436
|
|
|
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
|
-
|
|
469
437
|
@mcp.tool(description=self._high_risk_tool_description(operation="delete", target="app configuration"))
|
|
470
438
|
def app_delete(
|
|
471
439
|
profile: str = DEFAULT_PROFILE,
|
|
@@ -795,17 +763,19 @@ class AiBuilderTools(ToolBase):
|
|
|
795
763
|
profile: str = DEFAULT_PROFILE,
|
|
796
764
|
app_key: str = "",
|
|
797
765
|
publish: bool = True,
|
|
798
|
-
|
|
766
|
+
upsert_views: list[JSONObject] | None = None,
|
|
799
767
|
patch_views: list[JSONObject] | None = None,
|
|
800
768
|
remove_views: list[str] | None = None,
|
|
769
|
+
apps: list[JSONObject] | None = None,
|
|
801
770
|
) -> JSONObject:
|
|
802
771
|
return self.app_views_apply(
|
|
803
772
|
profile=profile,
|
|
804
773
|
app_key=app_key,
|
|
805
774
|
publish=publish,
|
|
806
|
-
|
|
775
|
+
upsert_views=upsert_views or [],
|
|
807
776
|
patch_views=patch_views or [],
|
|
808
777
|
remove_views=remove_views or [],
|
|
778
|
+
apps=apps,
|
|
809
779
|
)
|
|
810
780
|
|
|
811
781
|
@mcp.tool()
|
|
@@ -2009,22 +1979,20 @@ class AiBuilderTools(ToolBase):
|
|
|
2009
1979
|
suggested_next_call={"tool_name": "app_get_charts", "arguments": {"profile": profile, "app_key": app_key}},
|
|
2010
1980
|
)
|
|
2011
1981
|
|
|
2012
|
-
def app_get_buttons(self, *, profile: str, app_key: str = "", app_keys: list[str] | None = None
|
|
1982
|
+
def app_get_buttons(self, *, profile: str, app_key: str = "", app_keys: list[str] | None = None) -> JSONObject:
|
|
2013
1983
|
if app_keys is not None:
|
|
2014
1984
|
return self._read_app_batch(
|
|
2015
1985
|
tool_name="app_get_buttons",
|
|
2016
1986
|
profile=profile,
|
|
2017
1987
|
app_keys=app_keys,
|
|
2018
|
-
read_one=lambda item_app_key: self.app_get_buttons(profile=profile, app_key=item_app_key, app_keys=None
|
|
1988
|
+
read_one=lambda item_app_key: self.app_get_buttons(profile=profile, app_key=item_app_key, app_keys=None),
|
|
2019
1989
|
)
|
|
2020
|
-
normalized_args
|
|
2021
|
-
if str(button_text or "").strip():
|
|
2022
|
-
normalized_args["button_text"] = str(button_text or "").strip()
|
|
1990
|
+
normalized_args = {"app_key": app_key}
|
|
2023
1991
|
return _safe_tool_call(
|
|
2024
|
-
lambda: self._facade.app_get_buttons(profile=profile, app_key=app_key
|
|
1992
|
+
lambda: self._facade.app_get_buttons(profile=profile, app_key=app_key),
|
|
2025
1993
|
error_code="APP_GET_BUTTONS_FAILED",
|
|
2026
1994
|
normalized_args=normalized_args,
|
|
2027
|
-
suggested_next_call={"tool_name": "app_get_buttons", "arguments": {"profile": profile,
|
|
1995
|
+
suggested_next_call={"tool_name": "app_get_buttons", "arguments": {"profile": profile, "app_key": app_key}},
|
|
2028
1996
|
)
|
|
2029
1997
|
|
|
2030
1998
|
def app_get_associated_resources(self, *, profile: str, app_key: str = "", app_keys: list[str] | None = None) -> JSONObject:
|
|
@@ -2283,34 +2251,17 @@ class AiBuilderTools(ToolBase):
|
|
|
2283
2251
|
*,
|
|
2284
2252
|
profile: str,
|
|
2285
2253
|
app_key: str,
|
|
2286
|
-
upsert_view: JSONObject | None = None,
|
|
2287
2254
|
upsert_views: list[JSONObject] | None = None,
|
|
2288
2255
|
patch_views: list[JSONObject] | None = None,
|
|
2289
2256
|
remove_views: list[str] | None = None,
|
|
2290
2257
|
preset: str | None = None,
|
|
2291
2258
|
) -> JSONObject:
|
|
2292
|
-
"""
|
|
2293
|
-
if upsert_view is not None and upsert_views:
|
|
2294
|
-
return _config_failure(
|
|
2295
|
-
tool_name="app_views_plan",
|
|
2296
|
-
message="pass either upsert_view or upsert_views, not both.",
|
|
2297
|
-
fix_hint="Use upsert_view for one new or full view declaration.",
|
|
2298
|
-
details={"provided": ["upsert_view", "upsert_views"]},
|
|
2299
|
-
)
|
|
2300
|
-
normalized_upsert_views = [upsert_view] if upsert_view is not None else list(upsert_views or [])
|
|
2301
|
-
if len(normalized_upsert_views) > 1:
|
|
2302
|
-
return _config_failure(
|
|
2303
|
-
tool_name="app_views_plan",
|
|
2304
|
-
message="app_views_plan supports planning one new or fully updated view per call.",
|
|
2305
|
-
fix_hint="Call app_views_plan once for each view, using upsert_view.",
|
|
2306
|
-
details={"max_upsert_views": 1, "received_upsert_views": len(normalized_upsert_views)},
|
|
2307
|
-
error_code="VIEW_BATCH_CREATE_UNSUPPORTED",
|
|
2308
|
-
)
|
|
2259
|
+
"""执行应用相关逻辑。"""
|
|
2309
2260
|
reserved_failure = _reserved_system_view_name_failure(
|
|
2310
2261
|
tool_name="app_views_plan",
|
|
2311
2262
|
profile=profile,
|
|
2312
2263
|
app_key=app_key,
|
|
2313
|
-
upsert_views=
|
|
2264
|
+
upsert_views=upsert_views,
|
|
2314
2265
|
remove_views=remove_views,
|
|
2315
2266
|
)
|
|
2316
2267
|
if reserved_failure is not None:
|
|
@@ -2319,7 +2270,7 @@ class AiBuilderTools(ToolBase):
|
|
|
2319
2270
|
request = ViewsPlanRequest.model_validate(
|
|
2320
2271
|
{
|
|
2321
2272
|
"app_key": app_key,
|
|
2322
|
-
"upsert_views":
|
|
2273
|
+
"upsert_views": upsert_views or [],
|
|
2323
2274
|
"patch_views": patch_views or [],
|
|
2324
2275
|
"remove_views": remove_views or [],
|
|
2325
2276
|
"preset": preset,
|
|
@@ -2336,7 +2287,7 @@ class AiBuilderTools(ToolBase):
|
|
|
2336
2287
|
"profile": profile,
|
|
2337
2288
|
"app_key": app_key,
|
|
2338
2289
|
"preset": "default_table",
|
|
2339
|
-
"
|
|
2290
|
+
"upsert_views": [],
|
|
2340
2291
|
"patch_views": [],
|
|
2341
2292
|
"remove_views": [],
|
|
2342
2293
|
},
|
|
@@ -2347,7 +2298,7 @@ class AiBuilderTools(ToolBase):
|
|
|
2347
2298
|
error_code="VIEWS_PLAN_FAILED",
|
|
2348
2299
|
normalized_args={
|
|
2349
2300
|
"app_key": request.app_key,
|
|
2350
|
-
"
|
|
2301
|
+
"upsert_views": [public_view_upsert_payload(view) for view in request.upsert_views],
|
|
2351
2302
|
"patch_views": [public_view_partial_payload(patch) for patch in request.patch_views],
|
|
2352
2303
|
"remove_views": list(request.remove_views),
|
|
2353
2304
|
"preset": request.preset.value if request.preset is not None else None,
|
|
@@ -2357,7 +2308,7 @@ class AiBuilderTools(ToolBase):
|
|
|
2357
2308
|
"arguments": {
|
|
2358
2309
|
"profile": profile,
|
|
2359
2310
|
"app_key": request.app_key,
|
|
2360
|
-
"
|
|
2311
|
+
"upsert_views": [public_view_upsert_payload(view) for view in request.upsert_views],
|
|
2361
2312
|
"patch_views": [public_view_partial_payload(patch) for patch in request.patch_views],
|
|
2362
2313
|
"remove_views": list(request.remove_views),
|
|
2363
2314
|
"preset": request.preset.value if request.preset is not None else None,
|
|
@@ -3980,47 +3931,41 @@ class AiBuilderTools(ToolBase):
|
|
|
3980
3931
|
profile: str,
|
|
3981
3932
|
app_key: str,
|
|
3982
3933
|
publish: bool = True,
|
|
3983
|
-
|
|
3934
|
+
upsert_views: list[JSONObject],
|
|
3984
3935
|
patch_views: list[JSONObject] | None = None,
|
|
3985
|
-
remove_views: list[str]
|
|
3986
|
-
|
|
3987
|
-
|
|
3936
|
+
remove_views: list[str],
|
|
3937
|
+
apps: list[JSONObject] | None = None,
|
|
3938
|
+
views: list[JSONObject] | None = None,
|
|
3988
3939
|
) -> JSONObject:
|
|
3989
|
-
"""
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
return _attach_builder_apply_envelope(
|
|
3996
|
-
"app_views_apply",
|
|
3997
|
-
_config_failure(
|
|
3998
|
-
tool_name="app_views_apply",
|
|
3999
|
-
message="pass either upsert_view or upsert_views, not both.",
|
|
4000
|
-
fix_hint="Use upsert_view for one new or full view declaration.",
|
|
4001
|
-
details={"provided": ["upsert_view", "upsert_views"]},
|
|
4002
|
-
),
|
|
3940
|
+
"""执行应用相关逻辑。"""
|
|
3941
|
+
if views is not None:
|
|
3942
|
+
return self._app_views_apply_view_batch(
|
|
3943
|
+
profile=profile,
|
|
3944
|
+
publish=publish,
|
|
3945
|
+
views=views,
|
|
4003
3946
|
)
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
3947
|
+
if apps is not None:
|
|
3948
|
+
return self._apply_app_batch(
|
|
3949
|
+
tool_name="app_views_apply",
|
|
3950
|
+
profile=profile,
|
|
3951
|
+
apps=apps,
|
|
3952
|
+
apply_one=lambda _index, item, item_app_key: self.app_views_apply(
|
|
3953
|
+
profile=profile,
|
|
3954
|
+
app_key=item_app_key,
|
|
3955
|
+
publish=_payload_bool(item, "publish", default=publish),
|
|
3956
|
+
upsert_views=_payload_list(item, "upsert_views", "upsertViews", "views", default=upsert_views or []),
|
|
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,
|
|
4014
3960
|
),
|
|
4015
3961
|
)
|
|
4016
3962
|
result = self._app_views_apply_once(
|
|
4017
3963
|
profile=profile,
|
|
4018
3964
|
app_key=app_key,
|
|
4019
3965
|
publish=publish,
|
|
4020
|
-
upsert_views=
|
|
3966
|
+
upsert_views=upsert_views,
|
|
4021
3967
|
patch_views=patch_views or [],
|
|
4022
|
-
remove_views=remove_views
|
|
4023
|
-
schema_version=schema_version,
|
|
3968
|
+
remove_views=remove_views,
|
|
4024
3969
|
)
|
|
4025
3970
|
result = self._retry_after_self_lock_release(
|
|
4026
3971
|
profile=profile,
|
|
@@ -4029,14 +3974,255 @@ class AiBuilderTools(ToolBase):
|
|
|
4029
3974
|
profile=profile,
|
|
4030
3975
|
app_key=app_key,
|
|
4031
3976
|
publish=publish,
|
|
4032
|
-
remove_views=remove_views
|
|
4033
|
-
upsert_views=
|
|
3977
|
+
remove_views=remove_views,
|
|
3978
|
+
upsert_views=upsert_views,
|
|
4034
3979
|
patch_views=patch_views or [],
|
|
4035
|
-
schema_version=schema_version,
|
|
4036
3980
|
),
|
|
4037
3981
|
)
|
|
4038
3982
|
return self._attach_apply_envelope(profile, "app_views_apply", result)
|
|
4039
3983
|
|
|
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
|
+
|
|
4040
4226
|
def _app_views_apply_once(
|
|
4041
4227
|
self,
|
|
4042
4228
|
*,
|
|
@@ -4046,7 +4232,6 @@ class AiBuilderTools(ToolBase):
|
|
|
4046
4232
|
upsert_views: list[JSONObject],
|
|
4047
4233
|
patch_views: list[JSONObject],
|
|
4048
4234
|
remove_views: list[str],
|
|
4049
|
-
schema_version: str | None = None,
|
|
4050
4235
|
) -> JSONObject:
|
|
4051
4236
|
"""执行内部辅助逻辑。"""
|
|
4052
4237
|
reserved_failure = _reserved_system_view_name_failure(
|
|
@@ -4074,7 +4259,7 @@ class AiBuilderTools(ToolBase):
|
|
|
4074
4259
|
"profile": profile,
|
|
4075
4260
|
"app_key": app_key,
|
|
4076
4261
|
"publish": publish,
|
|
4077
|
-
"
|
|
4262
|
+
"upsert_views": upsert_views or [{"name": "业务台账视图", "type": "table", "columns": ["字段A"]}],
|
|
4078
4263
|
"patch_views": patch_views or [],
|
|
4079
4264
|
"remove_views": remove_views or [],
|
|
4080
4265
|
},
|
|
@@ -4083,10 +4268,9 @@ class AiBuilderTools(ToolBase):
|
|
|
4083
4268
|
normalized_args = {
|
|
4084
4269
|
"app_key": app_key,
|
|
4085
4270
|
"publish": publish,
|
|
4086
|
-
"
|
|
4271
|
+
"upsert_views": [public_view_upsert_payload(view) for view in parsed_views],
|
|
4087
4272
|
"patch_views": [public_view_partial_payload(patch) for patch in parsed_patch_views],
|
|
4088
4273
|
"remove_views": list(remove_views or []),
|
|
4089
|
-
"schema_version": schema_version,
|
|
4090
4274
|
}
|
|
4091
4275
|
return _safe_tool_call(
|
|
4092
4276
|
lambda: self._facade.app_views_apply(
|
|
@@ -4096,44 +4280,12 @@ class AiBuilderTools(ToolBase):
|
|
|
4096
4280
|
upsert_views=parsed_views,
|
|
4097
4281
|
patch_views=parsed_patch_views,
|
|
4098
4282
|
remove_views=list(remove_views or []),
|
|
4099
|
-
schema_version=schema_version,
|
|
4100
4283
|
),
|
|
4101
4284
|
error_code="VIEWS_APPLY_FAILED",
|
|
4102
4285
|
normalized_args=normalized_args,
|
|
4103
4286
|
suggested_next_call={"tool_name": "app_views_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
4104
4287
|
)
|
|
4105
4288
|
|
|
4106
|
-
def app_views_schema(
|
|
4107
|
-
self,
|
|
4108
|
-
*,
|
|
4109
|
-
schema_version: str | None = None,
|
|
4110
|
-
schema_kind: str | None = None,
|
|
4111
|
-
) -> JSONObject:
|
|
4112
|
-
"""Return a versioned views schema (summary/complete or a focused detail kind)."""
|
|
4113
|
-
return self._facade.app_views_schema(schema_version=schema_version, schema_kind=schema_kind)
|
|
4114
|
-
|
|
4115
|
-
def app_views_get(
|
|
4116
|
-
self,
|
|
4117
|
-
*,
|
|
4118
|
-
profile: str,
|
|
4119
|
-
app_key: str,
|
|
4120
|
-
schema_version: str | None = None,
|
|
4121
|
-
schema_kind: str | None = None,
|
|
4122
|
-
) -> JSONObject:
|
|
4123
|
-
"""Read canonical views declarations (summary projection or complete reverse-compilation)."""
|
|
4124
|
-
return self._facade.app_views_get(
|
|
4125
|
-
profile=profile, app_key=app_key, schema_version=schema_version, schema_kind=schema_kind or "summary"
|
|
4126
|
-
)
|
|
4127
|
-
|
|
4128
|
-
def app_views_validate(
|
|
4129
|
-
self,
|
|
4130
|
-
*,
|
|
4131
|
-
declaration: JSONObject,
|
|
4132
|
-
schema_version: str | None = None,
|
|
4133
|
-
) -> JSONObject:
|
|
4134
|
-
"""Offline-validate a views declaration without touching the backend."""
|
|
4135
|
-
return self._facade.app_views_validate(declaration=declaration or {}, schema_version=schema_version)
|
|
4136
|
-
|
|
4137
4289
|
def chart_apply(
|
|
4138
4290
|
self,
|
|
4139
4291
|
*,
|
|
@@ -5516,10 +5668,12 @@ def _reserved_system_view_name_failure(
|
|
|
5516
5668
|
"arguments": {
|
|
5517
5669
|
"profile": profile,
|
|
5518
5670
|
"app_key": app_key,
|
|
5519
|
-
"
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5671
|
+
"upsert_views": [
|
|
5672
|
+
{
|
|
5673
|
+
**item,
|
|
5674
|
+
"name": "业务台账视图",
|
|
5675
|
+
}
|
|
5676
|
+
],
|
|
5523
5677
|
"patch_views": patch_views or [],
|
|
5524
5678
|
"remove_views": remove_views or [],
|
|
5525
5679
|
},
|
|
@@ -5529,7 +5683,7 @@ def _reserved_system_view_name_failure(
|
|
|
5529
5683
|
return _config_failure(
|
|
5530
5684
|
tool_name=tool_name,
|
|
5531
5685
|
error_code="RESERVED_SYSTEM_VIEW_NAME",
|
|
5532
|
-
message=f"
|
|
5686
|
+
message=f"upsert_views[{index}].name uses built-in system view name '{name}' without view_key",
|
|
5533
5687
|
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.",
|
|
5534
5688
|
details={
|
|
5535
5689
|
"reserved_view_names": sorted(_RESERVED_SYSTEM_VIEW_NAMES),
|
|
@@ -7993,20 +8147,20 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
7993
8147
|
"app_views_plan": {
|
|
7994
8148
|
"allowed_keys": [
|
|
7995
8149
|
"app_key",
|
|
7996
|
-
"
|
|
8150
|
+
"upsert_views",
|
|
7997
8151
|
"patch_views",
|
|
7998
8152
|
"remove_views",
|
|
7999
8153
|
"preset",
|
|
8000
|
-
"
|
|
8001
|
-
"
|
|
8002
|
-
"
|
|
8003
|
-
"
|
|
8004
|
-
"
|
|
8005
|
-
"
|
|
8006
|
-
"
|
|
8007
|
-
"
|
|
8008
|
-
"
|
|
8009
|
-
"
|
|
8154
|
+
"upsert_views[].view_key",
|
|
8155
|
+
"upsert_views[].name",
|
|
8156
|
+
"upsert_views[].type",
|
|
8157
|
+
"upsert_views[].columns",
|
|
8158
|
+
"upsert_views[].filters",
|
|
8159
|
+
"upsert_views[].buttons",
|
|
8160
|
+
"upsert_views[].action_buttons",
|
|
8161
|
+
"upsert_views[].action_buttons_mode",
|
|
8162
|
+
"upsert_views[].visibility",
|
|
8163
|
+
"upsert_views[].query_conditions",
|
|
8010
8164
|
"patch_views[].view_key",
|
|
8011
8165
|
"patch_views[].name",
|
|
8012
8166
|
"patch_views[].set",
|
|
@@ -8027,7 +8181,6 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
8027
8181
|
"queryConditions": "query_conditions",
|
|
8028
8182
|
"query_condition": "query_conditions",
|
|
8029
8183
|
"queryCondition": "query_conditions",
|
|
8030
|
-
"upsertView": "upsert_view",
|
|
8031
8184
|
"patchViews": "patch_views",
|
|
8032
8185
|
"startField": "start_field",
|
|
8033
8186
|
"endField": "end_field",
|
|
@@ -8062,19 +8215,18 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
8062
8215
|
},
|
|
8063
8216
|
"execution_notes": [
|
|
8064
8217
|
"creating a new view follows backend createViewgraphConfig and requires both ViewManagementAuth and DataManageAuth; updating/deleting existing views only requires ViewManagementAuth",
|
|
8065
|
-
"
|
|
8066
|
-
"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",
|
|
8218
|
+
"upsert_views[].visibility may set per-view visibility; omit it to preserve an existing view's auth or default a new view to workspace/not",
|
|
8067
8219
|
"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",
|
|
8068
|
-
"
|
|
8220
|
+
"upsert_views[].query_conditions.rows is a layout matrix of query-panel supported field names; it is compiled to backend queryCondition queIds",
|
|
8069
8221
|
"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",
|
|
8070
8222
|
"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",
|
|
8071
8223
|
"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",
|
|
8072
8224
|
"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",
|
|
8073
|
-
"legacy
|
|
8225
|
+
"legacy upsert_views[].buttons is a compatibility path for raw full replacement of existing view button config; do not use it as the normal agent path",
|
|
8074
8226
|
"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",
|
|
8075
8227
|
"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",
|
|
8076
8228
|
"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",
|
|
8077
|
-
"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/
|
|
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/upsert_views explicitly changes associated_resources",
|
|
8078
8230
|
"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",
|
|
8079
8231
|
"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",
|
|
8080
8232
|
*_VISIBILITY_EXECUTION_NOTES,
|
|
@@ -8082,7 +8234,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
8082
8234
|
"minimal_example": {
|
|
8083
8235
|
"profile": "default",
|
|
8084
8236
|
"app_key": "APP_KEY",
|
|
8085
|
-
"
|
|
8237
|
+
"upsert_views": [{"name": "项目台账视图", "type": "table", "columns": ["项目名称"], "visibility": deepcopy(_VISIBILITY_WORKSPACE_EXAMPLE)}],
|
|
8086
8238
|
"remove_views": [],
|
|
8087
8239
|
},
|
|
8088
8240
|
"query_conditions_example": {
|
|
@@ -8104,37 +8256,41 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
8104
8256
|
"action_buttons_example": {
|
|
8105
8257
|
"profile": "default",
|
|
8106
8258
|
"app_key": "APP_KEY",
|
|
8107
|
-
"
|
|
8108
|
-
|
|
8109
|
-
|
|
8110
|
-
|
|
8111
|
-
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
|
|
8115
|
-
|
|
8116
|
-
|
|
8117
|
-
|
|
8118
|
-
|
|
8119
|
-
|
|
8120
|
-
|
|
8259
|
+
"upsert_views": [
|
|
8260
|
+
{
|
|
8261
|
+
"name": "生产工单执行视图",
|
|
8262
|
+
"type": "table",
|
|
8263
|
+
"columns": ["工单编号", "产品", "状态", "负责人"],
|
|
8264
|
+
"action_buttons": [
|
|
8265
|
+
{
|
|
8266
|
+
"text": "开始生产",
|
|
8267
|
+
"action": "link",
|
|
8268
|
+
"url": "https://example.com/start",
|
|
8269
|
+
"placement": "list",
|
|
8270
|
+
"visible_when": [{"field_name": "状态", "operator": "eq", "value": "待排产"}],
|
|
8271
|
+
}
|
|
8272
|
+
],
|
|
8273
|
+
}
|
|
8274
|
+
],
|
|
8121
8275
|
"remove_views": [],
|
|
8122
8276
|
},
|
|
8123
8277
|
"full_upsert_query_conditions_example": {
|
|
8124
8278
|
"profile": "default",
|
|
8125
8279
|
"app_key": "APP_KEY",
|
|
8126
|
-
"
|
|
8127
|
-
|
|
8128
|
-
|
|
8129
|
-
|
|
8130
|
-
|
|
8131
|
-
|
|
8132
|
-
"
|
|
8133
|
-
|
|
8134
|
-
|
|
8135
|
-
|
|
8136
|
-
|
|
8137
|
-
|
|
8280
|
+
"upsert_views": [
|
|
8281
|
+
{
|
|
8282
|
+
"name": "客户查询视图",
|
|
8283
|
+
"type": "table",
|
|
8284
|
+
"columns": ["客户名称", "负责人", "客户状态", "创建时间"],
|
|
8285
|
+
"filters": [{"field_name": "客户状态", "operator": "eq", "value": "有效"}],
|
|
8286
|
+
"query_conditions": {
|
|
8287
|
+
"enabled": True,
|
|
8288
|
+
"exact": False,
|
|
8289
|
+
"hide_before_query": False,
|
|
8290
|
+
"rows": [["客户名称", "负责人"], ["创建时间"]],
|
|
8291
|
+
},
|
|
8292
|
+
}
|
|
8293
|
+
],
|
|
8138
8294
|
"remove_views": [],
|
|
8139
8295
|
},
|
|
8140
8296
|
"query_conditions_field_rules": {
|
|
@@ -8148,35 +8304,48 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
8148
8304
|
"gantt_example": {
|
|
8149
8305
|
"profile": "default",
|
|
8150
8306
|
"app_key": "APP_KEY",
|
|
8151
|
-
"
|
|
8152
|
-
|
|
8153
|
-
|
|
8154
|
-
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
|
|
8307
|
+
"upsert_views": [
|
|
8308
|
+
{
|
|
8309
|
+
"name": "项目甘特图",
|
|
8310
|
+
"type": "gantt",
|
|
8311
|
+
"columns": ["项目名称", "开始日期", "结束日期"],
|
|
8312
|
+
"start_field": "开始日期",
|
|
8313
|
+
"end_field": "结束日期",
|
|
8314
|
+
"title_field": "项目名称",
|
|
8315
|
+
"filters": [{"field_name": "状态", "operator": "eq", "value": "进行中"}],
|
|
8316
|
+
}
|
|
8317
|
+
],
|
|
8160
8318
|
"remove_views": [],
|
|
8161
8319
|
},
|
|
8162
8320
|
},
|
|
8163
8321
|
"app_views_apply": {
|
|
8164
8322
|
"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",
|
|
8165
8334
|
"app_key",
|
|
8166
8335
|
"publish",
|
|
8167
|
-
"
|
|
8336
|
+
"upsert_views",
|
|
8168
8337
|
"patch_views",
|
|
8169
8338
|
"remove_views",
|
|
8170
|
-
"
|
|
8171
|
-
"
|
|
8172
|
-
"
|
|
8173
|
-
"
|
|
8174
|
-
"
|
|
8175
|
-
"
|
|
8176
|
-
"
|
|
8177
|
-
"
|
|
8178
|
-
"
|
|
8179
|
-
"
|
|
8339
|
+
"upsert_views[].view_key",
|
|
8340
|
+
"upsert_views[].name",
|
|
8341
|
+
"upsert_views[].type",
|
|
8342
|
+
"upsert_views[].columns",
|
|
8343
|
+
"upsert_views[].filters",
|
|
8344
|
+
"upsert_views[].buttons",
|
|
8345
|
+
"upsert_views[].action_buttons",
|
|
8346
|
+
"upsert_views[].action_buttons_mode",
|
|
8347
|
+
"upsert_views[].visibility",
|
|
8348
|
+
"upsert_views[].query_conditions",
|
|
8180
8349
|
"patch_views[].view_key",
|
|
8181
8350
|
"patch_views[].name",
|
|
8182
8351
|
"patch_views[].set",
|
|
@@ -8197,7 +8366,10 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
8197
8366
|
"queryConditions": "query_conditions",
|
|
8198
8367
|
"query_condition": "query_conditions",
|
|
8199
8368
|
"queryCondition": "query_conditions",
|
|
8200
|
-
"
|
|
8369
|
+
"views[].op": "views[].operation",
|
|
8370
|
+
"views[].fields": "views[].columns",
|
|
8371
|
+
"views[].viewKey": "views[].view_key",
|
|
8372
|
+
"views[].actionButtons": "views[].action_buttons",
|
|
8201
8373
|
"patchViews": "patch_views",
|
|
8202
8374
|
"startField": "start_field",
|
|
8203
8375
|
"endField": "end_field",
|
|
@@ -8220,6 +8392,7 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
8220
8392
|
"action_buttons[].defaultValues": "action_buttons[].default_values",
|
|
8221
8393
|
},
|
|
8222
8394
|
"allowed_values": {
|
|
8395
|
+
"views[].operation": ["upsert", "patch", "remove"],
|
|
8223
8396
|
"view.type": [member.value for member in PublicViewType],
|
|
8224
8397
|
"view.filter.operator": [member.value for member in ViewFilterOperator],
|
|
8225
8398
|
"view.buttons.button_type": ["SYSTEM", "CUSTOM"],
|
|
@@ -8230,7 +8403,9 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
8230
8403
|
**deepcopy(_VISIBILITY_ALLOWED_VALUES),
|
|
8231
8404
|
},
|
|
8232
8405
|
"execution_notes": [
|
|
8233
|
-
"
|
|
8406
|
+
"preferred batch input is views[]; each item has app_key and operation=upsert/patch/remove, and the same batch may mix same-app and cross-app views",
|
|
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",
|
|
8234
8409
|
"creating a new view follows backend createViewgraphConfig and requires both ViewManagementAuth and DataManageAuth; updating/deleting existing views only requires ViewManagementAuth",
|
|
8235
8410
|
"when duplicate view names exist, supply view_key to target the exact view",
|
|
8236
8411
|
"read back app_get after any failed or partial view apply",
|
|
@@ -8239,25 +8414,24 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
8239
8414
|
"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",
|
|
8240
8415
|
"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",
|
|
8241
8416
|
"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",
|
|
8242
|
-
"
|
|
8243
|
-
"
|
|
8417
|
+
"legacy upsert_views[].buttons is a compatibility path for raw full replacement of existing view button config; do not use it as the normal agent path",
|
|
8418
|
+
"upsert_views[].visibility may set per-view visibility; omit it to preserve an existing view's auth or default a new view to workspace/not",
|
|
8244
8419
|
"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",
|
|
8245
|
-
"
|
|
8420
|
+
"upsert_views[].query_conditions.rows is a layout matrix of query-panel supported field names; it is compiled to backend queryCondition queIds",
|
|
8246
8421
|
"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",
|
|
8247
8422
|
"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",
|
|
8248
8423
|
"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",
|
|
8249
8424
|
"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",
|
|
8250
8425
|
"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",
|
|
8251
|
-
"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/
|
|
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/upsert_views explicitly changes associated_resources",
|
|
8252
8427
|
"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",
|
|
8253
8428
|
"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",
|
|
8254
8429
|
*_VISIBILITY_EXECUTION_NOTES,
|
|
8255
8430
|
],
|
|
8256
8431
|
"minimal_example": {
|
|
8257
8432
|
"profile": "default",
|
|
8258
|
-
"app_key": "APP_KEY",
|
|
8259
8433
|
"publish": True,
|
|
8260
|
-
"
|
|
8434
|
+
"views": [{"operation": "upsert", "app_key": "APP_KEY", "name": "项目台账视图", "type": "table", "columns": ["项目名称"], "visibility": deepcopy(_VISIBILITY_WORKSPACE_EXAMPLE)}],
|
|
8261
8435
|
},
|
|
8262
8436
|
"query_conditions_example": {
|
|
8263
8437
|
"profile": "default",
|
|
@@ -8280,7 +8454,8 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
8280
8454
|
"profile": "default",
|
|
8281
8455
|
"app_key": "APP_KEY",
|
|
8282
8456
|
"publish": True,
|
|
8283
|
-
"
|
|
8457
|
+
"upsert_views": [
|
|
8458
|
+
{
|
|
8284
8459
|
"name": "生产工单执行视图",
|
|
8285
8460
|
"type": "table",
|
|
8286
8461
|
"columns": ["工单编号", "产品", "状态", "负责人"],
|
|
@@ -8293,7 +8468,8 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
8293
8468
|
"visible_when": [{"field_name": "状态", "operator": "eq", "value": "待排产"}],
|
|
8294
8469
|
}
|
|
8295
8470
|
],
|
|
8296
|
-
}
|
|
8471
|
+
}
|
|
8472
|
+
],
|
|
8297
8473
|
"remove_views": [],
|
|
8298
8474
|
},
|
|
8299
8475
|
"action_buttons_patch_example": {
|
|
@@ -8323,7 +8499,8 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
8323
8499
|
"profile": "default",
|
|
8324
8500
|
"app_key": "APP_KEY",
|
|
8325
8501
|
"publish": True,
|
|
8326
|
-
"
|
|
8502
|
+
"upsert_views": [
|
|
8503
|
+
{
|
|
8327
8504
|
"name": "客户查询视图",
|
|
8328
8505
|
"type": "table",
|
|
8329
8506
|
"columns": ["客户名称", "负责人", "客户状态", "创建时间"],
|
|
@@ -8334,7 +8511,8 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
8334
8511
|
"hide_before_query": False,
|
|
8335
8512
|
"rows": [["客户名称", "负责人"], ["创建时间"]],
|
|
8336
8513
|
},
|
|
8337
|
-
}
|
|
8514
|
+
}
|
|
8515
|
+
],
|
|
8338
8516
|
"remove_views": [],
|
|
8339
8517
|
},
|
|
8340
8518
|
"query_conditions_field_rules": {
|
|
@@ -8349,7 +8527,8 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
8349
8527
|
"profile": "default",
|
|
8350
8528
|
"app_key": "APP_KEY",
|
|
8351
8529
|
"publish": True,
|
|
8352
|
-
"
|
|
8530
|
+
"upsert_views": [
|
|
8531
|
+
{
|
|
8353
8532
|
"name": "项目甘特图",
|
|
8354
8533
|
"type": "gantt",
|
|
8355
8534
|
"columns": ["项目名称", "开始日期", "结束日期"],
|
|
@@ -8357,7 +8536,8 @@ _BUILDER_TOOL_CONTRACTS: dict[str, JSONObject] = {
|
|
|
8357
8536
|
"end_field": "结束日期",
|
|
8358
8537
|
"title_field": "项目名称",
|
|
8359
8538
|
"filters": [{"field_name": "状态", "operator": "eq", "value": "进行中"}],
|
|
8360
|
-
}
|
|
8539
|
+
}
|
|
8540
|
+
],
|
|
8361
8541
|
"remove_views": [],
|
|
8362
8542
|
},
|
|
8363
8543
|
},
|