@qingflow-tech/qingflow-cli 1.1.50-beta.3 → 1.1.50-beta.5
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 +1 -1
- package/skills/qingflow-cli/SKILL.md +37 -0
- package/skills/qingflow-cli/reference/builder/50-views.md +19 -4
- package/skills/qingflow-cli/reference/builder/80-buttons-associated-resources.md +2 -0
- package/src/qingflow_mcp/app_form_validator.py +53 -14
- package/src/qingflow_mcp/builder_facade/app_form.py +152 -47
- package/src/qingflow_mcp/builder_facade/models.py +19 -0
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/complete.json +3 -3
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/address.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/amount.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/attachment.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/code_block.json +3 -3
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/data_relation.json +3 -3
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/date.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/datetime.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/department.json +3 -3
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/email.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/link.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/long_text.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/member.json +3 -3
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/multi_select.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/number.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/phone.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/q_linker.json +3 -3
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/relation.json +3 -3
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/rich_text.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/single_choice.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/single_select.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/subtable.json +3 -3
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/text.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/formula.json +35 -0
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/summary.json +2 -2
- package/src/qingflow_mcp/builder_facade/schemas/views/manifest.json +2 -3
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/complete.json +68 -16
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/details/button-config.json +6 -6
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/details/data-permission.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/details/display.json +13 -11
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/details/operation-permission.json +3 -3
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/details/query-condition.json +1 -1
- package/src/qingflow_mcp/builder_facade/schemas/views/v1alpha1/summary.json +51 -6
- package/src/qingflow_mcp/builder_facade/service.py +198 -83
- package/src/qingflow_mcp/builder_facade/views_extract.py +21 -1
- package/src/qingflow_mcp/cli/commands/builder.py +4 -2
- package/src/qingflow_mcp/server_app_builder.py +2 -2
- package/src/qingflow_mcp/solution/compiler/view_compiler.py +2 -1
- package/src/qingflow_mcp/solution/executor.py +3 -1
- package/src/qingflow_mcp/solution/spec_models.py +1 -0
- package/src/qingflow_mcp/tools/ai_builder_tools.py +7 -5
|
@@ -163,9 +163,14 @@ FIELD_TYPE_TO_QUESTION_TYPE: dict[str, int] = {
|
|
|
163
163
|
FieldType.relation.value: 25,
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
# code_block / q_linker 输出目标字段类型。后端写链无类型白名单(FormSvcImpl 原样
|
|
167
|
+
# 落库),运行时 DataSvcImpl.getAnswerValueByString:attachment 按拆分 URL 写入、
|
|
168
|
+
# subtable 整表组装(均有专门分支),rich_text/data_relation 走 Question.canImport
|
|
169
|
+
# 兜底按字符串写入(不报错)——四者为存量合法配置,与 validator 侧白名单同口径。
|
|
166
170
|
INTEGRATION_OUTPUT_TARGET_FIELD_TYPES: tuple[str, ...] = (
|
|
167
171
|
FieldType.text.value,
|
|
168
172
|
FieldType.long_text.value,
|
|
173
|
+
"rich_text",
|
|
169
174
|
FieldType.number.value,
|
|
170
175
|
FieldType.amount.value,
|
|
171
176
|
FieldType.date.value,
|
|
@@ -173,6 +178,9 @@ INTEGRATION_OUTPUT_TARGET_FIELD_TYPES: tuple[str, ...] = (
|
|
|
173
178
|
FieldType.single_select.value,
|
|
174
179
|
FieldType.multi_select.value,
|
|
175
180
|
FieldType.boolean.value,
|
|
181
|
+
FieldType.attachment.value,
|
|
182
|
+
"data_relation",
|
|
183
|
+
FieldType.subtable.value,
|
|
176
184
|
)
|
|
177
185
|
|
|
178
186
|
MATCH_TYPE_ACCURACY = 1
|
|
@@ -3925,7 +3933,7 @@ class AiBuilderFacade:
|
|
|
3925
3933
|
"reason_path": f"patch_buttons[{index}].button_id",
|
|
3926
3934
|
"button_id": selector_id,
|
|
3927
3935
|
"message": "button_id does not exist in the current app draft",
|
|
3928
|
-
"next_action": "call
|
|
3936
|
+
"next_action": "call app_get_buttons with button_text to reuse an existing custom button; create only if absent via app_custom_buttons_apply",
|
|
3929
3937
|
}
|
|
3930
3938
|
issues.append(issue)
|
|
3931
3939
|
results.append({"index": index, "status": "failed", **issue})
|
|
@@ -4229,7 +4237,7 @@ class AiBuilderFacade:
|
|
|
4229
4237
|
"reason_path": f"upsert_buttons[{index}].button_id",
|
|
4230
4238
|
"button_id": button_id,
|
|
4231
4239
|
"message": "button_id does not exist in the current app draft",
|
|
4232
|
-
"next_action": "call
|
|
4240
|
+
"next_action": "call app_get_buttons with button_text to reuse an existing custom button; create only if absent via app_custom_buttons_apply",
|
|
4233
4241
|
}
|
|
4234
4242
|
)
|
|
4235
4243
|
continue
|
|
@@ -4300,7 +4308,7 @@ class AiBuilderFacade:
|
|
|
4300
4308
|
"reason_path": f"remove_buttons[{index}].button_id",
|
|
4301
4309
|
"button_id": selector_id,
|
|
4302
4310
|
"message": "button_id does not exist in the current app draft",
|
|
4303
|
-
"next_action": "call
|
|
4311
|
+
"next_action": "call app_get_buttons with button_text to reuse an existing custom button; create only if absent via app_custom_buttons_apply",
|
|
4304
4312
|
}
|
|
4305
4313
|
)
|
|
4306
4314
|
continue
|
|
@@ -7173,10 +7181,14 @@ class AiBuilderFacade:
|
|
|
7173
7181
|
fields = cast(list[dict[str, Any]], state["parsed"]["fields"])
|
|
7174
7182
|
return _chart_field_names_by_id_from_public_fields(app_key=app_key, fields=fields), None
|
|
7175
7183
|
|
|
7176
|
-
def app_get_buttons(self, *, profile: str, app_key: str) -> JSONObject:
|
|
7184
|
+
def app_get_buttons(self, *, profile: str, app_key: str, button_text: str | None = None) -> JSONObject:
|
|
7177
7185
|
self.apps._require_app_key(app_key)
|
|
7178
7186
|
warnings: list[dict[str, Any]] = []
|
|
7179
7187
|
verification: dict[str, Any] = {"buttons_loaded": True, "view_configs_loaded": True}
|
|
7188
|
+
normalized_text = str(button_text or "").strip() or None
|
|
7189
|
+
normalized_args: dict[str, Any] = {"app_key": app_key}
|
|
7190
|
+
if normalized_text is not None:
|
|
7191
|
+
normalized_args["button_text"] = normalized_text
|
|
7180
7192
|
try:
|
|
7181
7193
|
raw = self.buttons.custom_button_list(profile=profile, app_key=app_key, being_draft=True)
|
|
7182
7194
|
except (QingflowApiError, RuntimeError) as error:
|
|
@@ -7184,11 +7196,18 @@ class AiBuilderFacade:
|
|
|
7184
7196
|
return _failed_from_api_error(
|
|
7185
7197
|
"BUTTONS_READ_FAILED",
|
|
7186
7198
|
api_error,
|
|
7187
|
-
normalized_args=
|
|
7199
|
+
normalized_args=normalized_args,
|
|
7188
7200
|
details={"app_key": app_key},
|
|
7189
7201
|
suggested_next_call={"tool_name": "app_get", "arguments": {"profile": profile, "app_key": app_key}},
|
|
7190
7202
|
)
|
|
7191
7203
|
buttons = raw.get("items") if isinstance(raw.get("items"), list) else []
|
|
7204
|
+
total_count = len(buttons)
|
|
7205
|
+
if normalized_text is not None:
|
|
7206
|
+
buttons = [
|
|
7207
|
+
item
|
|
7208
|
+
for item in buttons
|
|
7209
|
+
if isinstance(item, dict) and str(item.get("buttonText") or item.get("button_text") or "").strip() == normalized_text
|
|
7210
|
+
]
|
|
7192
7211
|
view_configs: list[dict[str, Any]] = []
|
|
7193
7212
|
view_config_read_errors: list[dict[str, Any]] = []
|
|
7194
7213
|
try:
|
|
@@ -7208,7 +7227,7 @@ class AiBuilderFacade:
|
|
|
7208
7227
|
if view_config_read_errors:
|
|
7209
7228
|
verification["view_configs_loaded"] = False
|
|
7210
7229
|
warnings.append(_warning("BUTTON_VIEW_CONFIGS_PARTIAL", "some view button binding configs could not be read back"))
|
|
7211
|
-
|
|
7230
|
+
response: JSONObject = {
|
|
7212
7231
|
"status": "success",
|
|
7213
7232
|
"error_code": None,
|
|
7214
7233
|
"recoverable": False,
|
|
@@ -7223,6 +7242,11 @@ class AiBuilderFacade:
|
|
|
7223
7242
|
"verification": verification,
|
|
7224
7243
|
"details": {"view_config_read_errors": view_config_read_errors} if view_config_read_errors else {},
|
|
7225
7244
|
}
|
|
7245
|
+
if normalized_text is not None:
|
|
7246
|
+
response["button_text_filter"] = normalized_text
|
|
7247
|
+
response["total_count"] = total_count
|
|
7248
|
+
verification["button_text_exact_match"] = True
|
|
7249
|
+
return response
|
|
7226
7250
|
|
|
7227
7251
|
def app_get_associated_resources(self, *, profile: str, app_key: str) -> JSONObject:
|
|
7228
7252
|
self.apps._require_app_key(app_key)
|
|
@@ -9507,6 +9531,7 @@ class AiBuilderFacade:
|
|
|
9507
9531
|
"view_name": patch.get("name"),
|
|
9508
9532
|
}
|
|
9509
9533
|
for issue in filter_issues
|
|
9534
|
+
if issue.get("error_code")
|
|
9510
9535
|
)
|
|
9511
9536
|
if translated_filters:
|
|
9512
9537
|
patch["filters"] = [dict(rule) for rule in (patch.get("filters") or [])]
|
|
@@ -12406,6 +12431,7 @@ class AiBuilderFacade:
|
|
|
12406
12431
|
and _extract_view_name(view) not in removed
|
|
12407
12432
|
and _extract_view_key(view) not in removed_keys
|
|
12408
12433
|
]
|
|
12434
|
+
stale_warnings: list[dict[str, Any]] = []
|
|
12409
12435
|
for ordinal, patch in ((1, upsert_views[0]),) if operation == "upsert" and upsert_views else ():
|
|
12410
12436
|
apply_columns = _resolve_view_visible_que_ids(patch)
|
|
12411
12437
|
if patch.type in {PublicViewType.table, PublicViewType.card} and patch.columns and not apply_columns:
|
|
@@ -12415,65 +12441,37 @@ class AiBuilderFacade:
|
|
|
12415
12441
|
normalized_args=normalized_args,
|
|
12416
12442
|
details={"app_key": app_key, "view_name": patch.name},
|
|
12417
12443
|
)
|
|
12444
|
+
# 死引用(字段已从表单删除,后端保存视图时原样透传)降级为警告透传,不再整视图拒绝
|
|
12418
12445
|
missing_columns = [
|
|
12419
12446
|
que for que in apply_columns
|
|
12420
12447
|
if not _is_known_system_view_que_id(que) and que not in fields_by_que_id
|
|
12421
12448
|
]
|
|
12422
12449
|
if missing_columns:
|
|
12423
|
-
|
|
12424
|
-
"
|
|
12425
|
-
"view columns reference unknown fields",
|
|
12426
|
-
normalized_args=normalized_args,
|
|
12427
|
-
details={
|
|
12428
|
-
"app_key": app_key,
|
|
12429
|
-
"view_name": patch.name,
|
|
12430
|
-
"missing_fields": missing_columns,
|
|
12431
|
-
},
|
|
12432
|
-
missing_fields=missing_columns,
|
|
12433
|
-
suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": app_key}},
|
|
12450
|
+
stale_warnings.append(
|
|
12451
|
+
_stale_view_reference_warning(scope="columns", view_name=patch.name, field_ids=missing_columns)
|
|
12434
12452
|
)
|
|
12435
12453
|
if patch.group_que_id and patch.group_que_id not in fields_by_que_id:
|
|
12436
|
-
|
|
12437
|
-
"
|
|
12438
|
-
f"group_by references unknown field '{patch.group_que_id}'",
|
|
12439
|
-
normalized_args=normalized_args,
|
|
12440
|
-
details={
|
|
12441
|
-
"app_key": app_key,
|
|
12442
|
-
"view_name": patch.name,
|
|
12443
|
-
"missing_fields": [patch.group_que_id],
|
|
12444
|
-
},
|
|
12445
|
-
missing_fields=[patch.group_que_id],
|
|
12446
|
-
suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": app_key}},
|
|
12454
|
+
stale_warnings.append(
|
|
12455
|
+
_stale_view_reference_warning(scope="group_que_id", view_name=patch.name, field_ids=[patch.group_que_id])
|
|
12447
12456
|
)
|
|
12448
|
-
for
|
|
12449
|
-
|
|
12450
|
-
|
|
12451
|
-
|
|
12452
|
-
|
|
12453
|
-
|
|
12454
|
-
|
|
12455
|
-
|
|
12456
|
-
|
|
12457
|
-
"missing_fields": [gantt_que_id],
|
|
12458
|
-
},
|
|
12459
|
-
missing_fields=[gantt_que_id],
|
|
12460
|
-
suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": app_key}},
|
|
12457
|
+
for ref_scope, ref_que_id in (
|
|
12458
|
+
("gantt.start_que_id", patch.start_que_id),
|
|
12459
|
+
("gantt.end_que_id", patch.end_que_id),
|
|
12460
|
+
("title_que_id", patch.title_que_id),
|
|
12461
|
+
):
|
|
12462
|
+
# 系统字段(提交人/提交时间等)可作标题/时间轴;业务字段死引用仅警告透传
|
|
12463
|
+
if ref_que_id and not _is_known_system_view_que_id(ref_que_id) and ref_que_id not in fields_by_que_id:
|
|
12464
|
+
stale_warnings.append(
|
|
12465
|
+
_stale_view_reference_warning(scope=ref_scope, view_name=patch.name, field_ids=[ref_que_id])
|
|
12461
12466
|
)
|
|
12462
12467
|
for group_name, config_que_ids in _iter_view_config_group_field_names(patch):
|
|
12463
|
-
missing_config_fields = [
|
|
12468
|
+
missing_config_fields = [
|
|
12469
|
+
que for que in config_que_ids
|
|
12470
|
+
if que not in fields_by_que_id and not _is_known_system_view_que_id(que)
|
|
12471
|
+
]
|
|
12464
12472
|
if missing_config_fields:
|
|
12465
|
-
|
|
12466
|
-
|
|
12467
|
-
f"view {group_name} configuration references unknown fields",
|
|
12468
|
-
normalized_args=normalized_args,
|
|
12469
|
-
details={
|
|
12470
|
-
"app_key": app_key,
|
|
12471
|
-
"view_name": patch.name,
|
|
12472
|
-
"missing_fields": missing_config_fields,
|
|
12473
|
-
"reason_path": f"{group_name}",
|
|
12474
|
-
},
|
|
12475
|
-
missing_fields=missing_config_fields,
|
|
12476
|
-
suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": app_key}},
|
|
12473
|
+
stale_warnings.append(
|
|
12474
|
+
_stale_view_reference_warning(scope=group_name, view_name=patch.name, field_ids=missing_config_fields)
|
|
12477
12475
|
)
|
|
12478
12476
|
hierarchy_root_filter_groups: list[list[dict[str, Any]]] | None = None
|
|
12479
12477
|
if patch.hierarchy is not None and patch.hierarchy.root_filter:
|
|
@@ -12483,8 +12481,19 @@ class AiBuilderFacade:
|
|
|
12483
12481
|
fields_by_que_id=fields_by_que_id,
|
|
12484
12482
|
filters=root_filter_group,
|
|
12485
12483
|
)
|
|
12486
|
-
|
|
12487
|
-
|
|
12484
|
+
for issue in hierarchy_filter_issues:
|
|
12485
|
+
if issue.get("warning_code") == "STALE_OPTION_REFERENCE":
|
|
12486
|
+
stale_warnings.append(
|
|
12487
|
+
_stale_view_reference_warning(
|
|
12488
|
+
scope="hierarchy.root_filter",
|
|
12489
|
+
view_name=patch.name,
|
|
12490
|
+
values=issue.get("details", {}).get("unresolved_values") or [],
|
|
12491
|
+
option=True,
|
|
12492
|
+
)
|
|
12493
|
+
)
|
|
12494
|
+
hierarchy_filter_errors = [issue for issue in hierarchy_filter_issues if issue.get("error_code")]
|
|
12495
|
+
if hierarchy_filter_errors:
|
|
12496
|
+
first_issue = hierarchy_filter_errors[0]
|
|
12488
12497
|
return _failed(
|
|
12489
12498
|
str(first_issue.get("error_code") or "UNKNOWN_VIEW_FIELD"),
|
|
12490
12499
|
"view hierarchy root_filter references invalid fields or values",
|
|
@@ -12512,20 +12521,32 @@ class AiBuilderFacade:
|
|
|
12512
12521
|
else:
|
|
12513
12522
|
translated_filters, filter_issues = _build_view_filter_groups(fields_by_que_id=fields_by_que_id, filters=patch.filters)
|
|
12514
12523
|
if filter_issues:
|
|
12515
|
-
|
|
12516
|
-
|
|
12517
|
-
|
|
12518
|
-
|
|
12519
|
-
|
|
12520
|
-
|
|
12521
|
-
|
|
12522
|
-
|
|
12523
|
-
|
|
12524
|
-
|
|
12525
|
-
|
|
12526
|
-
|
|
12527
|
-
|
|
12528
|
-
|
|
12524
|
+
for issue in filter_issues:
|
|
12525
|
+
if issue.get("warning_code") == "STALE_OPTION_REFERENCE":
|
|
12526
|
+
stale_warnings.append(
|
|
12527
|
+
_stale_view_reference_warning(
|
|
12528
|
+
scope="filters",
|
|
12529
|
+
view_name=patch.name,
|
|
12530
|
+
values=issue.get("details", {}).get("unresolved_values") or [],
|
|
12531
|
+
option=True,
|
|
12532
|
+
)
|
|
12533
|
+
)
|
|
12534
|
+
filter_errors = [issue for issue in filter_issues if issue.get("error_code")]
|
|
12535
|
+
if filter_errors:
|
|
12536
|
+
first_issue = filter_errors[0]
|
|
12537
|
+
return _failed(
|
|
12538
|
+
str(first_issue.get("error_code") or "UNKNOWN_VIEW_FIELD"),
|
|
12539
|
+
"view filters reference invalid fields or values",
|
|
12540
|
+
normalized_args=normalized_args,
|
|
12541
|
+
details={
|
|
12542
|
+
"app_key": app_key,
|
|
12543
|
+
"view_name": patch.name,
|
|
12544
|
+
**first_issue,
|
|
12545
|
+
},
|
|
12546
|
+
missing_fields=list(first_issue.get("missing_fields") or []),
|
|
12547
|
+
allowed_values=first_issue.get("allowed_values") or {"view_types": [member.value for member in PublicViewType], "view.filter.operator": [member.value for member in ViewFilterOperator]},
|
|
12548
|
+
suggested_next_call={"tool_name": "app_get_fields", "arguments": {"profile": profile, "app_key": app_key}},
|
|
12549
|
+
)
|
|
12529
12550
|
if patch.preserve_query_conditions:
|
|
12530
12551
|
query_condition_payload = None
|
|
12531
12552
|
expected_query_conditions = None
|
|
@@ -12606,7 +12627,10 @@ class AiBuilderFacade:
|
|
|
12606
12627
|
},
|
|
12607
12628
|
missing_fields=list(first_issue.get("missing_fields") or []),
|
|
12608
12629
|
allowed_values=first_issue.get("allowed_values") or {"view_types": [member.value for member in PublicViewType], "view.filter.operator": [member.value for member in ViewFilterOperator]},
|
|
12609
|
-
suggested_next_call={
|
|
12630
|
+
suggested_next_call={
|
|
12631
|
+
"tool_name": "app_get_buttons",
|
|
12632
|
+
"arguments": {"profile": profile, "app_key": app_key},
|
|
12633
|
+
},
|
|
12610
12634
|
)
|
|
12611
12635
|
expected_button_summary = _normalize_expected_view_buttons_for_compare(
|
|
12612
12636
|
explicit_button_dtos or [],
|
|
@@ -13509,7 +13533,8 @@ class AiBuilderFacade:
|
|
|
13509
13533
|
"http_status": first_failure.get("http_status"),
|
|
13510
13534
|
"noop": noop,
|
|
13511
13535
|
"warnings": (
|
|
13512
|
-
(
|
|
13536
|
+
list(stale_warnings)
|
|
13537
|
+
+ (
|
|
13513
13538
|
[_warning("VIEW_FILTERS_UNVERIFIED", "view definitions may exist, but saved filter behavior is not fully verified")]
|
|
13514
13539
|
if (filter_readback_pending or filter_mismatches)
|
|
13515
13540
|
else []
|
|
@@ -13564,6 +13589,7 @@ class AiBuilderFacade:
|
|
|
13564
13589
|
}
|
|
13565
13590
|
return finish_with_publish(response)
|
|
13566
13591
|
warnings: list[dict[str, Any]] = []
|
|
13592
|
+
warnings.extend(stale_warnings)
|
|
13567
13593
|
if filter_readback_pending or filter_mismatches:
|
|
13568
13594
|
warnings.append(_warning("VIEW_FILTERS_UNVERIFIED", "view definitions were applied, but saved filter behavior is not fully verified"))
|
|
13569
13595
|
if query_condition_readback_pending or query_condition_mismatches:
|
|
@@ -17438,7 +17464,7 @@ def _resolve_custom_button_view_button_ref(
|
|
|
17438
17464
|
"reason_path": reason_path,
|
|
17439
17465
|
"button_ref": button_ref,
|
|
17440
17466
|
"message": "button_ref id does not exist in the current app draft or was removed in this apply",
|
|
17441
|
-
"next_action": "call
|
|
17467
|
+
"next_action": "call app_get_buttons with button_text to find an existing button_id, or use a client_key from upsert_buttons",
|
|
17442
17468
|
}
|
|
17443
17469
|
ref_text = str(button_ref or "").strip()
|
|
17444
17470
|
if ref_text in client_key_map:
|
|
@@ -20942,6 +20968,8 @@ def _normalize_portal_view_type(view: dict[str, Any]) -> str:
|
|
|
20942
20968
|
"ganttView": "ganttView",
|
|
20943
20969
|
"hierarchy": "hierarchyView",
|
|
20944
20970
|
"hierarchyView": "hierarchyView",
|
|
20971
|
+
"calendar": "calendarView",
|
|
20972
|
+
"calendarView": "calendarView",
|
|
20945
20973
|
}
|
|
20946
20974
|
return mapping.get(raw, "tableView")
|
|
20947
20975
|
|
|
@@ -23215,6 +23243,23 @@ def _apply_field_mutation(field: dict[str, Any], mutation: Any) -> None:
|
|
|
23215
23243
|
field.pop("_question_rebuild_required", None)
|
|
23216
23244
|
|
|
23217
23245
|
|
|
23246
|
+
class AmbiguousFieldSelector(ValueError):
|
|
23247
|
+
"""name-only selector 命中多个同名字段。
|
|
23248
|
+
|
|
23249
|
+
方案 A(duplicate-field-name):后端以 queId 为字段身份、允许同父级同名;
|
|
23250
|
+
name 命中多个字段时禁止静默取第一个,必须显式失败并给出候选 queId。
|
|
23251
|
+
compiler 包装层(app_form)把它转写为 AMBIGUOUS_FIELD_SELECTOR 错误码。
|
|
23252
|
+
"""
|
|
23253
|
+
|
|
23254
|
+
def __init__(self, location: str, candidates: list[int]) -> None:
|
|
23255
|
+
self.location = location
|
|
23256
|
+
self.candidates = candidates
|
|
23257
|
+
super().__init__(
|
|
23258
|
+
f"{location} matches multiple fields with the same name; "
|
|
23259
|
+
f"use queId to disambiguate (candidate queIds: {sorted(candidates)})"
|
|
23260
|
+
)
|
|
23261
|
+
|
|
23262
|
+
|
|
23218
23263
|
def _resolve_field_selector_with_uniqueness(
|
|
23219
23264
|
*,
|
|
23220
23265
|
fields: list[dict[str, Any]],
|
|
@@ -23230,7 +23275,12 @@ def _resolve_field_selector_with_uniqueness(
|
|
|
23230
23275
|
name = str(selector.name or "").strip()
|
|
23231
23276
|
matched = [field for field in fields if str(field.get("name") or "").strip() == name]
|
|
23232
23277
|
if len(matched) > 1:
|
|
23233
|
-
|
|
23278
|
+
candidate_ids = [
|
|
23279
|
+
ref
|
|
23280
|
+
for candidate in matched
|
|
23281
|
+
if (ref := _coerce_any_int(candidate.get("que_id"))) is not None
|
|
23282
|
+
]
|
|
23283
|
+
raise AmbiguousFieldSelector(location, candidate_ids)
|
|
23234
23284
|
else:
|
|
23235
23285
|
matched = []
|
|
23236
23286
|
if len(matched) != 1:
|
|
@@ -26778,7 +26828,7 @@ def _merge_view_summary_with_config(
|
|
|
26778
26828
|
and str(entry.get("name") or "").strip()
|
|
26779
26829
|
and str(entry.get("name") or "").strip() not in _KNOWN_SYSTEM_VIEW_COLUMNS
|
|
26780
26830
|
]
|
|
26781
|
-
summary["columns"] =
|
|
26831
|
+
summary["columns"] = canonical_display_columns
|
|
26782
26832
|
summary["display_columns"] = canonical_display_columns
|
|
26783
26833
|
summary["display_column_ids"] = canonical_display_column_ids
|
|
26784
26834
|
summary["column_details"] = canonical_display_entries
|
|
@@ -27556,6 +27606,7 @@ def _serialize_view_button_binding(
|
|
|
27556
27606
|
"reason_path": "buttons[].button_id",
|
|
27557
27607
|
"missing_fields": [],
|
|
27558
27608
|
"details": {"button_id": binding.button_id},
|
|
27609
|
+
"next_action": "call app_get_buttons with the intended button_text to reuse an existing custom button; create only if absent via app_custom_buttons_apply",
|
|
27559
27610
|
}
|
|
27560
27611
|
]
|
|
27561
27612
|
translated_limits, limit_issues = _build_view_button_limit_groups(
|
|
@@ -27616,6 +27667,11 @@ def _build_view_button_limit_groups(
|
|
|
27616
27667
|
issue["reason_path"] = "buttons[].button_limit[].values"
|
|
27617
27668
|
issues.append(issue)
|
|
27618
27669
|
continue
|
|
27670
|
+
stale_warning = translated_rule.pop("_stale_warning", None)
|
|
27671
|
+
if stale_warning is not None:
|
|
27672
|
+
stale_warning = deepcopy(stale_warning)
|
|
27673
|
+
stale_warning["reason_path"] = "buttons[].button_limit[].values"
|
|
27674
|
+
issues.append(stale_warning)
|
|
27619
27675
|
translated_rules.append(translated_rule)
|
|
27620
27676
|
if translated_rules:
|
|
27621
27677
|
translated_groups.append(translated_rules)
|
|
@@ -29741,6 +29797,8 @@ def _normalize_view_type_name(value: Any) -> str:
|
|
|
29741
29797
|
return "board"
|
|
29742
29798
|
if "hierarchy" in normalized:
|
|
29743
29799
|
return "hierarchy"
|
|
29800
|
+
if "calendar" in normalized:
|
|
29801
|
+
return "calendar"
|
|
29744
29802
|
if "card" in normalized:
|
|
29745
29803
|
return "card"
|
|
29746
29804
|
return "table"
|
|
@@ -29847,6 +29905,10 @@ def _build_view_update_payload(
|
|
|
29847
29905
|
payload["viewgraphType"] = "hierarchyView"
|
|
29848
29906
|
payload["beingShowTitleQue"] = True
|
|
29849
29907
|
payload.pop("groupQueId", None)
|
|
29908
|
+
elif normalized_type == "calendar":
|
|
29909
|
+
payload["viewgraphType"] = "calendarView"
|
|
29910
|
+
payload["beingShowTitleQue"] = True
|
|
29911
|
+
payload.pop("groupQueId", None)
|
|
29850
29912
|
return _hydrate_view_backend_payload(
|
|
29851
29913
|
payload=payload,
|
|
29852
29914
|
view_type=normalized_type,
|
|
@@ -29949,6 +30011,7 @@ def _build_minimal_view_payload(
|
|
|
29949
30011
|
"board": "boardView",
|
|
29950
30012
|
"gantt": "ganttView",
|
|
29951
30013
|
"hierarchy": "hierarchyView",
|
|
30014
|
+
"calendar": "calendarView",
|
|
29952
30015
|
}[patch.type.value],
|
|
29953
30016
|
"ordinal": ordinal,
|
|
29954
30017
|
"viewgraphQueIds": visible_que_id_values,
|
|
@@ -30067,6 +30130,11 @@ def _hydrate_view_backend_payload(
|
|
|
30067
30130
|
data["beingShowTitleQue"] = True
|
|
30068
30131
|
data["groupQueId"] = None
|
|
30069
30132
|
data["viewgraphGanttConfigVO"] = None
|
|
30133
|
+
elif view_type == "calendar":
|
|
30134
|
+
data["viewgraphType"] = "calendarView"
|
|
30135
|
+
data["beingShowTitleQue"] = True
|
|
30136
|
+
data["groupQueId"] = None
|
|
30137
|
+
data["viewgraphGanttConfigVO"] = None
|
|
30070
30138
|
if view_filters is not None:
|
|
30071
30139
|
data["viewgraphLimit"] = deepcopy(view_filters)
|
|
30072
30140
|
data["viewgraphLimitType"] = 1
|
|
@@ -30164,6 +30232,13 @@ def _apply_view_config_groups(
|
|
|
30164
30232
|
"rootNodeFilter": deepcopy(hierarchy_filter_groups) if hierarchy_filter_groups is not None else [],
|
|
30165
30233
|
}
|
|
30166
30234
|
data["titleQue"] = patch.hierarchy.title_que_id
|
|
30235
|
+
if patch.calendar is not None and view_type == "calendar":
|
|
30236
|
+
data["viewgraphCalendarConfigVO"] = {
|
|
30237
|
+
"titleQueId": patch.calendar.title_que_id,
|
|
30238
|
+
"dateQueId": patch.calendar.date_que_id,
|
|
30239
|
+
"colorQueId": patch.calendar.color_que_id,
|
|
30240
|
+
}
|
|
30241
|
+
data["titleQue"] = patch.calendar.title_que_id
|
|
30167
30242
|
display = patch.display
|
|
30168
30243
|
if display is not None:
|
|
30169
30244
|
if display.row_height is not None:
|
|
@@ -30202,6 +30277,33 @@ def _apply_view_config_groups(
|
|
|
30202
30277
|
data["beingPrintStatus"] = share.print_enabled
|
|
30203
30278
|
|
|
30204
30279
|
|
|
30280
|
+
def _stale_view_reference_warning(
|
|
30281
|
+
*,
|
|
30282
|
+
scope: str,
|
|
30283
|
+
view_name: str,
|
|
30284
|
+
field_ids: list[Any] | None = None,
|
|
30285
|
+
values: list[Any] | None = None,
|
|
30286
|
+
option: bool = False,
|
|
30287
|
+
) -> dict[str, Any]:
|
|
30288
|
+
"""存量死引用(已删字段/已删选项)透传警告:后端保存视图不校验引用存在性并原样透传,
|
|
30289
|
+
CLI 同口径透传写回,仅以警告留痕,不再整视图拒绝(apply(get(S))≡S 重放兼容)。"""
|
|
30290
|
+
warning = _warning(
|
|
30291
|
+
"STALE_OPTION_REFERENCE" if option else "STALE_FIELD_REFERENCE",
|
|
30292
|
+
(
|
|
30293
|
+
f"view {scope} references option values not present in the current form; original values passed through (backend tolerates stale references)"
|
|
30294
|
+
if option
|
|
30295
|
+
else f"view {scope} references fields not present in the current form; ids passed through (backend tolerates stale references)"
|
|
30296
|
+
),
|
|
30297
|
+
scope=scope,
|
|
30298
|
+
view_name=view_name,
|
|
30299
|
+
)
|
|
30300
|
+
if field_ids:
|
|
30301
|
+
warning["field_ids"] = list(field_ids)
|
|
30302
|
+
if values:
|
|
30303
|
+
warning["values"] = list(values)
|
|
30304
|
+
return warning
|
|
30305
|
+
|
|
30306
|
+
|
|
30205
30307
|
def _iter_view_config_group_field_names(patch: ViewUpsertPatch) -> list[tuple[str, list[int]]]:
|
|
30206
30308
|
"""枚举 complete 档新字段组引用的 queId(用于 UNKNOWN_VIEW_FIELD 校验)。"""
|
|
30207
30309
|
groups: list[tuple[str, list[int]]] = []
|
|
@@ -30231,6 +30333,11 @@ def _iter_view_config_group_field_names(patch: ViewUpsertPatch) -> list[tuple[st
|
|
|
30231
30333
|
groups.append(("gantt", gantt_ids))
|
|
30232
30334
|
if patch.hierarchy is not None:
|
|
30233
30335
|
groups.append(("hierarchy", [patch.hierarchy.title_que_id, patch.hierarchy.children_que_id]))
|
|
30336
|
+
if patch.calendar is not None:
|
|
30337
|
+
calendar_ids = [patch.calendar.title_que_id, patch.calendar.date_que_id]
|
|
30338
|
+
if patch.calendar.color_que_id is not None:
|
|
30339
|
+
calendar_ids.append(patch.calendar.color_que_id)
|
|
30340
|
+
groups.append(("calendar", calendar_ids))
|
|
30234
30341
|
return groups
|
|
30235
30342
|
|
|
30236
30343
|
|
|
@@ -30241,6 +30348,8 @@ def _resolve_view_visible_que_ids(patch: ViewUpsertPatch) -> list[int]:
|
|
|
30241
30348
|
extra_ids: list[int | None] = []
|
|
30242
30349
|
if patch.hierarchy is not None:
|
|
30243
30350
|
extra_ids.extend([patch.hierarchy.title_que_id, patch.hierarchy.children_que_id])
|
|
30351
|
+
if patch.calendar is not None:
|
|
30352
|
+
extra_ids.extend([patch.calendar.title_que_id, patch.calendar.date_que_id, patch.calendar.color_que_id])
|
|
30244
30353
|
for value in [*patch.columns, patch.title_que_id, patch.start_que_id, patch.end_que_id, patch.group_que_id, *extra_ids]:
|
|
30245
30354
|
que_id = _coerce_any_int(value)
|
|
30246
30355
|
if que_id is not None and (que_id > 0 or _is_known_system_view_que_id(que_id)) and que_id not in ordered:
|
|
@@ -30328,6 +30437,9 @@ def _build_view_filter_groups(
|
|
|
30328
30437
|
if issue:
|
|
30329
30438
|
issues.append(issue)
|
|
30330
30439
|
continue
|
|
30440
|
+
stale_warning = translated_rule.pop("_stale_warning", None)
|
|
30441
|
+
if stale_warning is not None:
|
|
30442
|
+
issues.append(stale_warning)
|
|
30331
30443
|
translated_rules.append(translated_rule)
|
|
30332
30444
|
return ([translated_rules] if translated_rules else []), issues
|
|
30333
30445
|
|
|
@@ -30338,9 +30450,9 @@ def _translate_view_filter_rule(*, field: dict[str, Any], rule: dict[str, Any])
|
|
|
30338
30450
|
field_type = str(field.get("type") or FieldType.text.value)
|
|
30339
30451
|
que_id = _coerce_positive_int(field.get("que_id")) or 0
|
|
30340
30452
|
que_type = _coerce_positive_int(field.get("que_type")) or FIELD_TYPE_TO_QUESTION_TYPE.get(field_type, 2)
|
|
30341
|
-
judge_values, judge_value_details,
|
|
30342
|
-
if
|
|
30343
|
-
return {},
|
|
30453
|
+
judge_values, judge_value_details, value_issue = _resolve_view_filter_values(field=field, values=values)
|
|
30454
|
+
if value_issue and value_issue.get("error_code"):
|
|
30455
|
+
return {}, value_issue
|
|
30344
30456
|
payload: dict[str, Any] = {
|
|
30345
30457
|
"queId": que_id,
|
|
30346
30458
|
"queTitle": str(field.get("name") or ""),
|
|
@@ -30372,6 +30484,9 @@ def _translate_view_filter_rule(*, field: dict[str, Any], rule: dict[str, Any])
|
|
|
30372
30484
|
elif operator == "not_empty":
|
|
30373
30485
|
payload["judgeType"] = JUDGE_UNEQUAL
|
|
30374
30486
|
payload["judgeValues"] = []
|
|
30487
|
+
if value_issue is not None:
|
|
30488
|
+
# 死选项透传:规则照常写入,warning 随 payload 携带由上层收集(_build_view_filter_groups 弹出)
|
|
30489
|
+
payload["_stale_warning"] = value_issue
|
|
30375
30490
|
return payload, None
|
|
30376
30491
|
|
|
30377
30492
|
|
|
@@ -30403,15 +30518,15 @@ def _resolve_view_filter_values(
|
|
|
30403
30518
|
continue
|
|
30404
30519
|
resolved_details.append({"id": resolved.get("id"), "value": str(resolved.get("value") or resolved.get("id"))})
|
|
30405
30520
|
if unresolved_values and not resolved_details:
|
|
30406
|
-
# 全部值都解析不出(选项已删)
|
|
30521
|
+
# 全部值都解析不出(选项已删):原值透传(后端对存量死选项引用原样保存),
|
|
30522
|
+
# 以 STALE_OPTION_REFERENCE 警告留痕,不再整视图失败
|
|
30407
30523
|
return (
|
|
30408
|
-
[],
|
|
30409
|
-
|
|
30524
|
+
[_stringify_condition_value(value) for value in values],
|
|
30525
|
+
_build_view_filter_value_details(values),
|
|
30410
30526
|
{
|
|
30411
|
-
"
|
|
30527
|
+
"warning_code": "STALE_OPTION_REFERENCE",
|
|
30412
30528
|
"reason_path": "filters[].values",
|
|
30413
30529
|
"missing_fields": [],
|
|
30414
|
-
"allowed_values": {"filter_values": sorted(option_by_value.keys())},
|
|
30415
30530
|
"details": {
|
|
30416
30531
|
"field_name": str(field.get("name") or ""),
|
|
30417
30532
|
"unresolved_values": unresolved_values,
|
|
@@ -25,6 +25,7 @@ _VIEW_TYPE_BY_BACKEND = {
|
|
|
25
25
|
"boardView": "board",
|
|
26
26
|
"ganttView": "gantt",
|
|
27
27
|
"hierarchyView": "hierarchy",
|
|
28
|
+
"calendarView": "calendar",
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
# 后端 dataScope → 公开 data_permission.scope(与 _DATA_PERMISSION_SCOPE_MAP 对称)
|
|
@@ -68,7 +69,6 @@ _SYSTEM_COLUMN_NAMES = {
|
|
|
68
69
|
# complete 契约不覆盖、读面存在的配置键(进入 SKIP 账本,evaluation.md §4.3)
|
|
69
70
|
_UNCOVERED_READBACK_KEYS = (
|
|
70
71
|
"attachVisibleOnlyConfig",
|
|
71
|
-
"viewgraphCalendarConfigVO",
|
|
72
72
|
"beingRemarkStatus",
|
|
73
73
|
"querySearchStatus",
|
|
74
74
|
"beingDefaultPrintTplStatus",
|
|
@@ -417,6 +417,26 @@ def extract_complete_view_declaration(
|
|
|
417
417
|
declaration["hierarchy"] = hierarchy
|
|
418
418
|
_optionally(declaration, "title_que_id", title_que)
|
|
419
419
|
|
|
420
|
+
if view_type == "calendar":
|
|
421
|
+
calendar_raw = view_info.get("viewgraphCalendarConfigVO") if isinstance(view_info.get("viewgraphCalendarConfigVO"), dict) else {}
|
|
422
|
+
title_que = _coerce_positive_int(calendar_raw.get("titleQueId"))
|
|
423
|
+
date_que = _coerce_any_int(calendar_raw.get("dateQueId"))
|
|
424
|
+
color_que = _coerce_positive_int(calendar_raw.get("colorQueId"))
|
|
425
|
+
if title_que is None or title_que not in fields_by_que_id:
|
|
426
|
+
gaps.append(_gap("calendar.title_que_id", "unresolved_calendar_config", "calendar title queId does not resolve; view is not extractable", que_id=title_que))
|
|
427
|
+
return {}, gaps
|
|
428
|
+
if date_que is None or (date_que not in {2, 3} and date_que not in fields_by_que_id):
|
|
429
|
+
gaps.append(_gap("calendar.date_que_id", "unresolved_calendar_config", "calendar date queId does not resolve; view is not extractable", que_id=date_que))
|
|
430
|
+
return {}, gaps
|
|
431
|
+
calendar: dict[str, Any] = {"title_que_id": title_que, "date_que_id": date_que}
|
|
432
|
+
if color_que is not None:
|
|
433
|
+
if color_que not in fields_by_que_id:
|
|
434
|
+
gaps.append(_gap("calendar.color_que_id", "unresolved_que_id", "calendar color queId does not resolve", que_id=color_que))
|
|
435
|
+
return {}, gaps
|
|
436
|
+
calendar["color_que_id"] = color_que
|
|
437
|
+
declaration["calendar"] = calendar
|
|
438
|
+
declaration["title_que_id"] = title_que
|
|
439
|
+
|
|
420
440
|
if view_type in {"card", "board"}:
|
|
421
441
|
_optionally(declaration, "title_que_id", _coerce_positive_int(view_info.get("titleQue")))
|
|
422
442
|
|
|
@@ -225,6 +225,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
225
225
|
button_get.add_argument("--app-key", default="")
|
|
226
226
|
button_get.add_argument("--app-keys", default="", help="逗号分隔的多应用 app_key;用于批量读取按钮配置")
|
|
227
227
|
button_get.add_argument("--app-keys-file", help="JSON 字符串数组;用于批量读取按钮配置")
|
|
228
|
+
button_get.add_argument("--button-text", default="", help="按按钮文案精确查重;命中复用其 button_id,未命中再创建")
|
|
228
229
|
button_get.set_defaults(handler=_handle_button_get, format_hint="builder_summary")
|
|
229
230
|
|
|
230
231
|
button_apply = button_subparsers.add_parser("apply", help="声明式创建、更新或删除自定义按钮")
|
|
@@ -898,12 +899,13 @@ def _handle_associated_resource_apply(args: argparse.Namespace, context: CliCont
|
|
|
898
899
|
|
|
899
900
|
|
|
900
901
|
def _handle_button_get(args: argparse.Namespace, context: CliContext) -> dict:
|
|
902
|
+
button_text = str(getattr(args, "button_text", "") or "").strip() or None
|
|
901
903
|
app_keys = _app_keys_from_args(args)
|
|
902
904
|
if app_keys is not None:
|
|
903
|
-
return context.builder.app_get_buttons(profile=args.profile, app_key="", app_keys=app_keys)
|
|
905
|
+
return context.builder.app_get_buttons(profile=args.profile, app_key="", app_keys=app_keys, button_text=button_text)
|
|
904
906
|
if not (args.app_key or "").strip():
|
|
905
907
|
raise_config_error("builder button get requires --app-key unless --app-keys or --app-keys-file is provided")
|
|
906
|
-
return context.builder.app_get_buttons(profile=args.profile, app_key=args.app_key)
|
|
908
|
+
return context.builder.app_get_buttons(profile=args.profile, app_key=args.app_key, button_text=button_text)
|
|
907
909
|
|
|
908
910
|
|
|
909
911
|
def _handle_associated_resource_get(args: argparse.Namespace, context: CliContext) -> dict:
|
|
@@ -471,8 +471,8 @@ def build_builder_server() -> FastMCP:
|
|
|
471
471
|
return ai_builder.app_get_charts(profile=profile, app_key=app_key, app_keys=app_keys)
|
|
472
472
|
|
|
473
473
|
@server.tool()
|
|
474
|
-
def app_get_buttons(profile: str = DEFAULT_PROFILE, app_key: str = "", app_keys: list[str] | None = None) -> dict:
|
|
475
|
-
return ai_builder.app_get_buttons(profile=profile, app_key=app_key, app_keys=app_keys)
|
|
474
|
+
def app_get_buttons(profile: str = DEFAULT_PROFILE, app_key: str = "", app_keys: list[str] | None = None, button_text: str | None = None) -> dict:
|
|
475
|
+
return ai_builder.app_get_buttons(profile=profile, app_key=app_key, app_keys=app_keys, button_text=button_text)
|
|
476
476
|
|
|
477
477
|
@server.tool()
|
|
478
478
|
def app_get_associated_resources(profile: str = DEFAULT_PROFILE, app_key: str = "", app_keys: list[str] | None = None) -> dict:
|
|
@@ -12,6 +12,7 @@ VIEW_TYPE_MAP = {
|
|
|
12
12
|
ViewType.board: "boardView",
|
|
13
13
|
ViewType.gantt: "ganttView",
|
|
14
14
|
ViewType.hierarchy: "hierarchyView",
|
|
15
|
+
ViewType.calendar: "calendarView",
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
|
|
@@ -32,7 +33,7 @@ def compile_views(entity: EntitySpec) -> list[dict[str, Any]]:
|
|
|
32
33
|
"viewgraphQueIds": list(view.field_ids),
|
|
33
34
|
"viewgraphQuestions": [],
|
|
34
35
|
"ordinal": ordinal,
|
|
35
|
-
"beingShowTitleQue": view.type in (ViewType.card, ViewType.board, ViewType.gantt, ViewType.hierarchy),
|
|
36
|
+
"beingShowTitleQue": view.type in (ViewType.card, ViewType.board, ViewType.gantt, ViewType.hierarchy, ViewType.calendar),
|
|
36
37
|
"beingShowCover": False,
|
|
37
38
|
"viewgraphLimitType": 1,
|
|
38
39
|
"viewgraphLimit": view.filters,
|