@josephyan/qingflow-cli 0.2.0-beta.1000
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 +31 -0
- package/docs/local-agent-install.md +309 -0
- package/entry_point.py +13 -0
- package/npm/bin/qingflow.mjs +5 -0
- package/npm/lib/runtime.mjs +346 -0
- package/npm/scripts/postinstall.mjs +16 -0
- package/package.json +34 -0
- package/pyproject.toml +67 -0
- package/qingflow +15 -0
- package/src/qingflow_mcp/__init__.py +37 -0
- package/src/qingflow_mcp/__main__.py +5 -0
- package/src/qingflow_mcp/backend_client.py +649 -0
- package/src/qingflow_mcp/builder_facade/__init__.py +3 -0
- package/src/qingflow_mcp/builder_facade/models.py +1846 -0
- package/src/qingflow_mcp/builder_facade/service.py +16502 -0
- package/src/qingflow_mcp/cli/__init__.py +1 -0
- package/src/qingflow_mcp/cli/commands/__init__.py +18 -0
- package/src/qingflow_mcp/cli/commands/app.py +40 -0
- package/src/qingflow_mcp/cli/commands/auth.py +112 -0
- package/src/qingflow_mcp/cli/commands/builder.py +539 -0
- package/src/qingflow_mcp/cli/commands/chart.py +18 -0
- package/src/qingflow_mcp/cli/commands/common.py +62 -0
- package/src/qingflow_mcp/cli/commands/imports.py +96 -0
- package/src/qingflow_mcp/cli/commands/portal.py +25 -0
- package/src/qingflow_mcp/cli/commands/record.py +331 -0
- package/src/qingflow_mcp/cli/commands/repo.py +80 -0
- package/src/qingflow_mcp/cli/commands/task.py +141 -0
- package/src/qingflow_mcp/cli/commands/view.py +18 -0
- package/src/qingflow_mcp/cli/commands/workspace.py +110 -0
- package/src/qingflow_mcp/cli/context.py +60 -0
- package/src/qingflow_mcp/cli/formatters.py +573 -0
- package/src/qingflow_mcp/cli/json_io.py +50 -0
- package/src/qingflow_mcp/cli/main.py +186 -0
- package/src/qingflow_mcp/cli/qingflow_login.py +116 -0
- package/src/qingflow_mcp/cli/terminal_ui.py +173 -0
- package/src/qingflow_mcp/config.py +407 -0
- package/src/qingflow_mcp/errors.py +66 -0
- package/src/qingflow_mcp/id_utils.py +49 -0
- package/src/qingflow_mcp/import_store.py +121 -0
- package/src/qingflow_mcp/json_types.py +18 -0
- package/src/qingflow_mcp/list_type_labels.py +76 -0
- package/src/qingflow_mcp/public_surface.py +243 -0
- package/src/qingflow_mcp/repository_store.py +71 -0
- package/src/qingflow_mcp/response_trim.py +841 -0
- package/src/qingflow_mcp/server.py +216 -0
- package/src/qingflow_mcp/server_app_builder.py +543 -0
- package/src/qingflow_mcp/server_app_user.py +386 -0
- package/src/qingflow_mcp/session_store.py +369 -0
- package/src/qingflow_mcp/solution/__init__.py +6 -0
- package/src/qingflow_mcp/solution/build_assembly_store.py +181 -0
- package/src/qingflow_mcp/solution/compiler/__init__.py +282 -0
- package/src/qingflow_mcp/solution/compiler/chart_compiler.py +96 -0
- package/src/qingflow_mcp/solution/compiler/form_compiler.py +495 -0
- package/src/qingflow_mcp/solution/compiler/icon_utils.py +187 -0
- package/src/qingflow_mcp/solution/compiler/navigation_compiler.py +57 -0
- package/src/qingflow_mcp/solution/compiler/package_compiler.py +19 -0
- package/src/qingflow_mcp/solution/compiler/portal_compiler.py +60 -0
- package/src/qingflow_mcp/solution/compiler/view_compiler.py +51 -0
- package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +173 -0
- package/src/qingflow_mcp/solution/design_session.py +222 -0
- package/src/qingflow_mcp/solution/design_store.py +100 -0
- package/src/qingflow_mcp/solution/executor.py +2398 -0
- package/src/qingflow_mcp/solution/normalizer.py +23 -0
- package/src/qingflow_mcp/solution/requirements_builder.py +536 -0
- package/src/qingflow_mcp/solution/run_store.py +244 -0
- package/src/qingflow_mcp/solution/spec_models.py +855 -0
- package/src/qingflow_mcp/tools/__init__.py +1 -0
- package/src/qingflow_mcp/tools/ai_builder_tools.py +3449 -0
- package/src/qingflow_mcp/tools/app_tools.py +926 -0
- package/src/qingflow_mcp/tools/approval_tools.py +1062 -0
- package/src/qingflow_mcp/tools/auth_tools.py +1133 -0
- package/src/qingflow_mcp/tools/base.py +281 -0
- package/src/qingflow_mcp/tools/code_block_tools.py +777 -0
- package/src/qingflow_mcp/tools/custom_button_tools.py +202 -0
- package/src/qingflow_mcp/tools/directory_tools.py +675 -0
- package/src/qingflow_mcp/tools/feedback_tools.py +238 -0
- package/src/qingflow_mcp/tools/file_tools.py +409 -0
- package/src/qingflow_mcp/tools/import_tools.py +2223 -0
- package/src/qingflow_mcp/tools/navigation_tools.py +210 -0
- package/src/qingflow_mcp/tools/package_tools.py +326 -0
- package/src/qingflow_mcp/tools/portal_tools.py +158 -0
- package/src/qingflow_mcp/tools/qingbi_report_tools.py +374 -0
- package/src/qingflow_mcp/tools/record_tools.py +14291 -0
- package/src/qingflow_mcp/tools/repository_dev_tools.py +552 -0
- package/src/qingflow_mcp/tools/resource_read_tools.py +503 -0
- package/src/qingflow_mcp/tools/role_tools.py +112 -0
- package/src/qingflow_mcp/tools/solution_tools.py +4054 -0
- package/src/qingflow_mcp/tools/task_context_tools.py +2986 -0
- package/src/qingflow_mcp/tools/task_tools.py +889 -0
- package/src/qingflow_mcp/tools/view_tools.py +335 -0
- package/src/qingflow_mcp/tools/workflow_tools.py +376 -0
- package/src/qingflow_mcp/tools/workspace_tools.py +266 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from mcp.server.fastmcp import FastMCP
|
|
4
|
+
|
|
5
|
+
from ..config import DEFAULT_PROFILE
|
|
6
|
+
from ..errors import QingflowApiError, raise_tool_error
|
|
7
|
+
from ..json_types import JSONObject
|
|
8
|
+
from .base import ToolBase, tool_cn_name
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PortalTools(ToolBase):
|
|
12
|
+
"""门户工具(中文名:门户页管理)。
|
|
13
|
+
|
|
14
|
+
类型:门户配置工具。
|
|
15
|
+
主要职责:
|
|
16
|
+
1. 查询门户列表与详情;
|
|
17
|
+
2. 维护门户基础信息与配置;
|
|
18
|
+
3. 执行门户发布与删除操作。
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def register(self, mcp: FastMCP) -> None:
|
|
22
|
+
"""注册当前工具到 MCP 服务。"""
|
|
23
|
+
@mcp.tool()
|
|
24
|
+
def portal_list(profile: str = DEFAULT_PROFILE) -> JSONObject:
|
|
25
|
+
return self.portal_list(profile=profile)
|
|
26
|
+
|
|
27
|
+
@mcp.tool()
|
|
28
|
+
def portal_get(profile: str = DEFAULT_PROFILE, dash_key: str = "", being_draft: bool = False) -> JSONObject:
|
|
29
|
+
return self.portal_get(profile=profile, dash_key=dash_key, being_draft=being_draft)
|
|
30
|
+
|
|
31
|
+
@mcp.tool()
|
|
32
|
+
def portal_get_base_info(profile: str = DEFAULT_PROFILE, dash_key: str = "") -> JSONObject:
|
|
33
|
+
return self.portal_get_base_info(profile=profile, dash_key=dash_key)
|
|
34
|
+
|
|
35
|
+
@mcp.tool()
|
|
36
|
+
def portal_create(profile: str = DEFAULT_PROFILE, payload: JSONObject | None = None) -> JSONObject:
|
|
37
|
+
return self.portal_create(profile=profile, payload=payload or {})
|
|
38
|
+
|
|
39
|
+
@mcp.tool(description=self._high_risk_tool_description(operation="update", target="portal base settings"))
|
|
40
|
+
def portal_update_base_info(profile: str = DEFAULT_PROFILE, dash_key: str = "", payload: JSONObject | None = None) -> JSONObject:
|
|
41
|
+
return self.portal_update_base_info(profile=profile, dash_key=dash_key, payload=payload or {})
|
|
42
|
+
|
|
43
|
+
@mcp.tool(description=self._high_risk_tool_description(operation="update", target="portal configuration"))
|
|
44
|
+
def portal_update(profile: str = DEFAULT_PROFILE, dash_key: str = "", payload: JSONObject | None = None) -> JSONObject:
|
|
45
|
+
return self.portal_update(profile=profile, dash_key=dash_key, payload=payload or {})
|
|
46
|
+
|
|
47
|
+
@mcp.tool(description=self._high_risk_tool_description(operation="delete", target="portal configuration"))
|
|
48
|
+
def portal_delete(profile: str = DEFAULT_PROFILE, dash_key: str = "") -> JSONObject:
|
|
49
|
+
return self.portal_delete(profile=profile, dash_key=dash_key)
|
|
50
|
+
|
|
51
|
+
@mcp.tool()
|
|
52
|
+
def portal_publish(profile: str = DEFAULT_PROFILE, dash_key: str = "") -> JSONObject:
|
|
53
|
+
return self.portal_publish(profile=profile, dash_key=dash_key)
|
|
54
|
+
|
|
55
|
+
@tool_cn_name("门户列表")
|
|
56
|
+
def portal_list(self, *, profile: str) -> JSONObject:
|
|
57
|
+
"""执行门户相关逻辑。"""
|
|
58
|
+
def runner(session_profile, context):
|
|
59
|
+
result = self.backend.request("GET", context, "/dash")
|
|
60
|
+
return {"profile": profile, "ws_id": session_profile.selected_ws_id, "items": result}
|
|
61
|
+
|
|
62
|
+
return self._run(profile, runner)
|
|
63
|
+
|
|
64
|
+
@tool_cn_name("门户详情")
|
|
65
|
+
def portal_get(self, *, profile: str, dash_key: str, being_draft: bool = False) -> JSONObject:
|
|
66
|
+
"""执行门户相关逻辑。"""
|
|
67
|
+
self._require_dash_key(dash_key)
|
|
68
|
+
|
|
69
|
+
def runner(session_profile, context):
|
|
70
|
+
result = self.backend.request("GET", context, f"/dash/{dash_key}", params={"beingDraft": being_draft})
|
|
71
|
+
return {"profile": profile, "ws_id": session_profile.selected_ws_id, "dash_key": dash_key, "result": result}
|
|
72
|
+
|
|
73
|
+
return self._run(profile, runner)
|
|
74
|
+
|
|
75
|
+
@tool_cn_name("创建门户")
|
|
76
|
+
def portal_create(self, *, profile: str, payload: JSONObject) -> JSONObject:
|
|
77
|
+
"""执行门户相关逻辑。"""
|
|
78
|
+
body = self._require_dict(payload)
|
|
79
|
+
|
|
80
|
+
def runner(session_profile, context):
|
|
81
|
+
result = self.backend.request("POST", context, "/dash", json_body=body)
|
|
82
|
+
return {"profile": profile, "ws_id": session_profile.selected_ws_id, "result": result}
|
|
83
|
+
|
|
84
|
+
return self._run(profile, runner)
|
|
85
|
+
|
|
86
|
+
@tool_cn_name("门户基础信息")
|
|
87
|
+
def portal_get_base_info(self, *, profile: str, dash_key: str) -> JSONObject:
|
|
88
|
+
"""执行门户相关逻辑。"""
|
|
89
|
+
self._require_dash_key(dash_key)
|
|
90
|
+
|
|
91
|
+
def runner(session_profile, context):
|
|
92
|
+
result = self.backend.request("GET", context, f"/dash/{dash_key}/baseInfo")
|
|
93
|
+
return {"profile": profile, "ws_id": session_profile.selected_ws_id, "dash_key": dash_key, "result": result}
|
|
94
|
+
|
|
95
|
+
return self._run(profile, runner)
|
|
96
|
+
|
|
97
|
+
@tool_cn_name("更新门户基础信息")
|
|
98
|
+
def portal_update_base_info(self, *, profile: str, dash_key: str, payload: JSONObject) -> JSONObject:
|
|
99
|
+
"""执行门户相关逻辑。"""
|
|
100
|
+
self._require_dash_key(dash_key)
|
|
101
|
+
body = self._require_dict(payload)
|
|
102
|
+
|
|
103
|
+
def runner(session_profile, context):
|
|
104
|
+
result = self.backend.request("POST", context, f"/dash/{dash_key}/baseInfo", json_body=body)
|
|
105
|
+
return self._attach_human_review_notice(
|
|
106
|
+
{"profile": profile, "ws_id": session_profile.selected_ws_id, "dash_key": dash_key, "result": result},
|
|
107
|
+
operation="update",
|
|
108
|
+
target="portal base settings",
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
return self._run(profile, runner)
|
|
112
|
+
|
|
113
|
+
@tool_cn_name("更新门户配置")
|
|
114
|
+
def portal_update(self, *, profile: str, dash_key: str, payload: JSONObject) -> JSONObject:
|
|
115
|
+
"""执行门户相关逻辑。"""
|
|
116
|
+
self._require_dash_key(dash_key)
|
|
117
|
+
body = self._require_dict(payload)
|
|
118
|
+
|
|
119
|
+
def runner(session_profile, context):
|
|
120
|
+
result = self.backend.request("POST", context, f"/dash/{dash_key}", json_body=body)
|
|
121
|
+
return self._attach_human_review_notice(
|
|
122
|
+
{"profile": profile, "ws_id": session_profile.selected_ws_id, "dash_key": dash_key, "result": result},
|
|
123
|
+
operation="update",
|
|
124
|
+
target="portal configuration",
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
return self._run(profile, runner)
|
|
128
|
+
|
|
129
|
+
@tool_cn_name("删除门户")
|
|
130
|
+
def portal_delete(self, *, profile: str, dash_key: str) -> JSONObject:
|
|
131
|
+
"""执行门户相关逻辑。"""
|
|
132
|
+
self._require_dash_key(dash_key)
|
|
133
|
+
|
|
134
|
+
def runner(session_profile, context):
|
|
135
|
+
result = self.backend.request("DELETE", context, f"/dash/{dash_key}")
|
|
136
|
+
return self._attach_human_review_notice(
|
|
137
|
+
{"profile": profile, "ws_id": session_profile.selected_ws_id, "dash_key": dash_key, "result": result},
|
|
138
|
+
operation="delete",
|
|
139
|
+
target="portal configuration",
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
return self._run(profile, runner)
|
|
143
|
+
|
|
144
|
+
@tool_cn_name("发布门户")
|
|
145
|
+
def portal_publish(self, *, profile: str, dash_key: str) -> JSONObject:
|
|
146
|
+
"""执行门户相关逻辑。"""
|
|
147
|
+
self._require_dash_key(dash_key)
|
|
148
|
+
|
|
149
|
+
def runner(session_profile, context):
|
|
150
|
+
result = self.backend.request("POST", context, f"/dash/{dash_key}/publish")
|
|
151
|
+
return {"profile": profile, "ws_id": session_profile.selected_ws_id, "dash_key": dash_key, "result": result}
|
|
152
|
+
|
|
153
|
+
return self._run(profile, runner)
|
|
154
|
+
|
|
155
|
+
def _require_dash_key(self, dash_key: str) -> None:
|
|
156
|
+
"""执行内部辅助逻辑。"""
|
|
157
|
+
if not dash_key:
|
|
158
|
+
raise_tool_error(QingflowApiError.config_error("dash_key is required"))
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from uuid import uuid4
|
|
5
|
+
|
|
6
|
+
from mcp.server.fastmcp import FastMCP
|
|
7
|
+
|
|
8
|
+
from ..backend_client import BackendRequestContext
|
|
9
|
+
from ..config import DEFAULT_PROFILE, normalize_base_url
|
|
10
|
+
from ..errors import QingflowApiError, raise_tool_error
|
|
11
|
+
from ..json_types import JSONObject, JSONValue
|
|
12
|
+
from .base import ToolBase, tool_cn_name
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _qingbi_base_url(base_url: str) -> str:
|
|
16
|
+
normalized = normalize_base_url(base_url)
|
|
17
|
+
if not normalized:
|
|
18
|
+
raise QingflowApiError.config_error("base_url is required")
|
|
19
|
+
return normalized[:-4] if normalized.endswith("/api") else normalized
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _should_retry_qflow_base(error: QingflowApiError) -> bool:
|
|
23
|
+
return int(getattr(error, "backend_code", 0) or 0) == 81007
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _should_retry_asos_data(error: QingflowApiError) -> bool:
|
|
27
|
+
backend_code = int(getattr(error, "backend_code", 0) or 0)
|
|
28
|
+
http_status = getattr(error, "http_status", None)
|
|
29
|
+
return backend_code in {44011, 81007} or http_status == 404
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _coerce_tool_error(error: RuntimeError | QingflowApiError) -> QingflowApiError | None:
|
|
33
|
+
if isinstance(error, QingflowApiError):
|
|
34
|
+
return error
|
|
35
|
+
if not isinstance(error, RuntimeError):
|
|
36
|
+
return None
|
|
37
|
+
try:
|
|
38
|
+
payload = json.loads(str(error))
|
|
39
|
+
except Exception:
|
|
40
|
+
return None
|
|
41
|
+
if not isinstance(payload, dict):
|
|
42
|
+
return None
|
|
43
|
+
return QingflowApiError(
|
|
44
|
+
category=str(payload.get("category") or "runtime"),
|
|
45
|
+
message=str(payload.get("message") or str(error)),
|
|
46
|
+
backend_code=payload.get("backend_code"),
|
|
47
|
+
request_id=payload.get("request_id"),
|
|
48
|
+
http_status=payload.get("http_status"),
|
|
49
|
+
details=payload.get("details") if isinstance(payload.get("details"), dict) else None,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class QingbiReportTools(ToolBase):
|
|
54
|
+
"""轻报表工具(中文名:图表与报表读取)。
|
|
55
|
+
|
|
56
|
+
类型:报表分析工具。
|
|
57
|
+
主要职责:
|
|
58
|
+
1. 查询报表列表与基础信息;
|
|
59
|
+
2. 获取报表配置与图表数据;
|
|
60
|
+
3. 为门户与任务上下文提供报表侧读取能力。
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def register(self, mcp: FastMCP) -> None:
|
|
64
|
+
"""注册当前工具到 MCP 服务。"""
|
|
65
|
+
@mcp.tool()
|
|
66
|
+
def qingbi_report_list(
|
|
67
|
+
profile: str = DEFAULT_PROFILE,
|
|
68
|
+
app_key: str = "",
|
|
69
|
+
) -> JSONObject:
|
|
70
|
+
return self.qingbi_report_list(profile=profile, app_key=app_key)
|
|
71
|
+
|
|
72
|
+
@mcp.tool()
|
|
73
|
+
def qingbi_report_get_base(
|
|
74
|
+
profile: str = DEFAULT_PROFILE,
|
|
75
|
+
chart_id: str = "",
|
|
76
|
+
) -> JSONObject:
|
|
77
|
+
return self.qingbi_report_get_base(profile=profile, chart_id=chart_id)
|
|
78
|
+
|
|
79
|
+
@mcp.tool()
|
|
80
|
+
def qingbi_report_get_config(
|
|
81
|
+
profile: str = DEFAULT_PROFILE,
|
|
82
|
+
chart_id: str = "",
|
|
83
|
+
) -> JSONObject:
|
|
84
|
+
return self.qingbi_report_get_config(profile=profile, chart_id=chart_id)
|
|
85
|
+
|
|
86
|
+
@mcp.tool()
|
|
87
|
+
def qingbi_report_get_data(
|
|
88
|
+
profile: str = DEFAULT_PROFILE,
|
|
89
|
+
chart_id: str = "",
|
|
90
|
+
payload: JSONObject | None = None,
|
|
91
|
+
page_num: int | None = None,
|
|
92
|
+
page_size: int | None = None,
|
|
93
|
+
page_num_y: int | None = None,
|
|
94
|
+
page_size_y: int | None = None,
|
|
95
|
+
) -> JSONObject:
|
|
96
|
+
return self.qingbi_report_get_data(
|
|
97
|
+
profile=profile,
|
|
98
|
+
chart_id=chart_id,
|
|
99
|
+
payload=payload or {},
|
|
100
|
+
page_num=page_num,
|
|
101
|
+
page_size=page_size,
|
|
102
|
+
page_num_y=page_num_y,
|
|
103
|
+
page_size_y=page_size_y,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
@mcp.tool(description=self._high_risk_tool_description(operation="delete", target="report chart"))
|
|
107
|
+
def qingbi_report_delete(
|
|
108
|
+
profile: str = DEFAULT_PROFILE,
|
|
109
|
+
chart_id: str = "",
|
|
110
|
+
) -> JSONObject:
|
|
111
|
+
return self.qingbi_report_delete(profile=profile, chart_id=chart_id)
|
|
112
|
+
|
|
113
|
+
@mcp.tool()
|
|
114
|
+
def qingbi_report_reorder(
|
|
115
|
+
profile: str = DEFAULT_PROFILE,
|
|
116
|
+
app_key: str = "",
|
|
117
|
+
chart_ids: list[str] | None = None,
|
|
118
|
+
) -> JSONObject:
|
|
119
|
+
return self.qingbi_report_reorder(profile=profile, app_key=app_key, chart_ids=chart_ids or [])
|
|
120
|
+
|
|
121
|
+
@mcp.tool()
|
|
122
|
+
def qingbi_report_list_fields(
|
|
123
|
+
profile: str = DEFAULT_PROFILE,
|
|
124
|
+
app_key: str = "",
|
|
125
|
+
) -> JSONObject:
|
|
126
|
+
return self.qingbi_report_list_fields(profile=profile, app_key=app_key)
|
|
127
|
+
|
|
128
|
+
@mcp.tool()
|
|
129
|
+
def qingbi_report_list_field_options(
|
|
130
|
+
profile: str = DEFAULT_PROFILE,
|
|
131
|
+
field_id: str = "",
|
|
132
|
+
chart_id: str = "",
|
|
133
|
+
) -> JSONObject:
|
|
134
|
+
return self.qingbi_report_list_field_options(profile=profile, field_id=field_id, chart_id=chart_id)
|
|
135
|
+
|
|
136
|
+
@tool_cn_name("报表列表")
|
|
137
|
+
def qingbi_report_list(self, *, profile: str, app_key: str) -> JSONObject:
|
|
138
|
+
"""执行工具方法逻辑。"""
|
|
139
|
+
self._require_app_key(app_key)
|
|
140
|
+
return self._request(profile, "GET", f"/qingbi/charts/data/bichart/{app_key}", app_key=app_key, items_key="items")
|
|
141
|
+
|
|
142
|
+
@tool_cn_name("报表排序列表")
|
|
143
|
+
def qingbi_report_list_sorted(
|
|
144
|
+
self,
|
|
145
|
+
*,
|
|
146
|
+
profile: str,
|
|
147
|
+
app_key: str,
|
|
148
|
+
page_num: int = 1,
|
|
149
|
+
page_size: int = 500,
|
|
150
|
+
search_key: str | None = None,
|
|
151
|
+
) -> JSONObject:
|
|
152
|
+
"""执行工具方法逻辑。"""
|
|
153
|
+
self._require_app_key(app_key)
|
|
154
|
+
body: JSONObject = {"appKey": app_key, "pageNum": page_num, "pageSize": page_size}
|
|
155
|
+
if search_key:
|
|
156
|
+
body["searchKey"] = search_key
|
|
157
|
+
return self._request(
|
|
158
|
+
profile,
|
|
159
|
+
"POST",
|
|
160
|
+
"/qingbi/charts/chart/list",
|
|
161
|
+
app_key=app_key,
|
|
162
|
+
page_num=page_num,
|
|
163
|
+
page_size=page_size,
|
|
164
|
+
search_key=search_key,
|
|
165
|
+
items_key="items",
|
|
166
|
+
result_transform=_extract_sorted_chart_items,
|
|
167
|
+
json_body=body,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
@tool_cn_name("创建报表")
|
|
171
|
+
def qingbi_report_create(self, *, profile: str, payload: JSONObject) -> JSONObject:
|
|
172
|
+
"""执行工具方法逻辑。"""
|
|
173
|
+
body = self._require_dict(payload)
|
|
174
|
+
return self._request(profile, "POST", "/qingbi/charts", json_body=body)
|
|
175
|
+
|
|
176
|
+
@tool_cn_name("报表基础信息")
|
|
177
|
+
def qingbi_report_get_base(self, *, profile: str, chart_id: str) -> JSONObject:
|
|
178
|
+
"""执行工具方法逻辑。"""
|
|
179
|
+
self._require_chart_id(chart_id)
|
|
180
|
+
try:
|
|
181
|
+
return self._request(profile, "GET", f"/qingbi/charts/baseinfo/{chart_id}", chart_id=chart_id)
|
|
182
|
+
except (QingflowApiError, RuntimeError) as raw_error:
|
|
183
|
+
error = _coerce_tool_error(raw_error)
|
|
184
|
+
if error is None or not _should_retry_qflow_base(error):
|
|
185
|
+
raise
|
|
186
|
+
return self._request(profile, "GET", f"/qingbi/charts/qflow/baseinfo/{chart_id}", chart_id=chart_id)
|
|
187
|
+
|
|
188
|
+
@tool_cn_name("更新报表基础信息")
|
|
189
|
+
def qingbi_report_update_base(self, *, profile: str, chart_id: str, payload: JSONObject) -> JSONObject:
|
|
190
|
+
"""执行工具方法逻辑。"""
|
|
191
|
+
self._require_chart_id(chart_id)
|
|
192
|
+
body = self._require_dict(payload)
|
|
193
|
+
return self._request(profile, "PUT", f"/qingbi/charts/baseinfo/{chart_id}", chart_id=chart_id, json_body=body, risk_operation="update", risk_target="report base settings")
|
|
194
|
+
|
|
195
|
+
@tool_cn_name("报表配置")
|
|
196
|
+
def qingbi_report_get_config(self, *, profile: str, chart_id: str) -> JSONObject:
|
|
197
|
+
"""执行工具方法逻辑。"""
|
|
198
|
+
self._require_chart_id(chart_id)
|
|
199
|
+
return self._request(profile, "GET", f"/qingbi/charts/{chart_id}/configs", chart_id=chart_id)
|
|
200
|
+
|
|
201
|
+
@tool_cn_name("更新报表配置")
|
|
202
|
+
def qingbi_report_update_config(self, *, profile: str, chart_id: str, payload: JSONObject) -> JSONObject:
|
|
203
|
+
"""执行工具方法逻辑。"""
|
|
204
|
+
self._require_chart_id(chart_id)
|
|
205
|
+
body = self._require_dict(payload)
|
|
206
|
+
return self._request(profile, "PUT", f"/qingbi/charts/{chart_id}/configs", chart_id=chart_id, json_body=body, risk_operation="update", risk_target="report chart config")
|
|
207
|
+
|
|
208
|
+
@tool_cn_name("报表数据")
|
|
209
|
+
def qingbi_report_get_data(
|
|
210
|
+
self,
|
|
211
|
+
*,
|
|
212
|
+
profile: str,
|
|
213
|
+
chart_id: str,
|
|
214
|
+
payload: JSONObject,
|
|
215
|
+
page_num: int | None = None,
|
|
216
|
+
page_size: int | None = None,
|
|
217
|
+
page_num_y: int | None = None,
|
|
218
|
+
page_size_y: int | None = None,
|
|
219
|
+
) -> JSONObject:
|
|
220
|
+
"""执行工具方法逻辑。"""
|
|
221
|
+
self._require_chart_id(chart_id)
|
|
222
|
+
params = {
|
|
223
|
+
"qfUUID": uuid4().hex,
|
|
224
|
+
# Match Qingflow's real BI/qflow read path defaults so a caller can
|
|
225
|
+
# fetch chart data without having to synthesize pagination first.
|
|
226
|
+
"pageNum": page_num if page_num is not None else 1,
|
|
227
|
+
"pageSize": page_size if page_size is not None else 20,
|
|
228
|
+
}
|
|
229
|
+
if page_num_y is not None:
|
|
230
|
+
params["pageNumY"] = page_num_y
|
|
231
|
+
if page_size_y is not None:
|
|
232
|
+
params["pageSizeY"] = page_size_y
|
|
233
|
+
try:
|
|
234
|
+
if payload:
|
|
235
|
+
return self._request(
|
|
236
|
+
profile,
|
|
237
|
+
"POST",
|
|
238
|
+
f"/qingbi/charts/data/qflow/{chart_id}/detail",
|
|
239
|
+
chart_id=chart_id,
|
|
240
|
+
params=params,
|
|
241
|
+
json_body=payload,
|
|
242
|
+
)
|
|
243
|
+
return self._request(
|
|
244
|
+
profile,
|
|
245
|
+
"GET",
|
|
246
|
+
f"/qingbi/charts/data/qflow/{chart_id}",
|
|
247
|
+
chart_id=chart_id,
|
|
248
|
+
params=params,
|
|
249
|
+
)
|
|
250
|
+
except (QingflowApiError, RuntimeError) as raw_error:
|
|
251
|
+
error = _coerce_tool_error(raw_error)
|
|
252
|
+
if error is None or not _should_retry_asos_data(error):
|
|
253
|
+
raise
|
|
254
|
+
return self._request(
|
|
255
|
+
profile,
|
|
256
|
+
"POST",
|
|
257
|
+
f"/qingbi/charts/data/qflow/{chart_id}/asos",
|
|
258
|
+
chart_id=chart_id,
|
|
259
|
+
params=params,
|
|
260
|
+
json_body=payload or {},
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
@tool_cn_name("删除报表")
|
|
264
|
+
def qingbi_report_delete(self, *, profile: str, chart_id: str) -> JSONObject:
|
|
265
|
+
"""执行工具方法逻辑。"""
|
|
266
|
+
self._require_chart_id(chart_id)
|
|
267
|
+
return self._request(profile, "DELETE", f"/qingbi/charts/{chart_id}", chart_id=chart_id, risk_operation="delete", risk_target="report chart")
|
|
268
|
+
|
|
269
|
+
@tool_cn_name("报表排序")
|
|
270
|
+
def qingbi_report_reorder(self, *, profile: str, app_key: str, chart_ids: list[str]) -> JSONObject:
|
|
271
|
+
"""执行工具方法逻辑。"""
|
|
272
|
+
self._require_app_key(app_key)
|
|
273
|
+
if not isinstance(chart_ids, list) or not chart_ids:
|
|
274
|
+
raise_tool_error(QingflowApiError.config_error("chart_ids must be a non-empty array"))
|
|
275
|
+
if any(not chart_id for chart_id in chart_ids):
|
|
276
|
+
raise_tool_error(QingflowApiError.config_error("chart_ids cannot contain empty values"))
|
|
277
|
+
return self._request(
|
|
278
|
+
profile,
|
|
279
|
+
"POST",
|
|
280
|
+
"/qingbi/charts/sort/chart",
|
|
281
|
+
app_key=app_key,
|
|
282
|
+
chart_ids=chart_ids,
|
|
283
|
+
json_body={"appKey": app_key, "sortChartIdList": chart_ids},
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
@tool_cn_name("报表字段列表")
|
|
287
|
+
def qingbi_report_list_fields(self, *, profile: str, app_key: str) -> JSONObject:
|
|
288
|
+
"""执行工具方法逻辑。"""
|
|
289
|
+
self._require_app_key(app_key)
|
|
290
|
+
return self._request(
|
|
291
|
+
profile,
|
|
292
|
+
"GET",
|
|
293
|
+
"/qingbi/datasets/datasource/fields",
|
|
294
|
+
app_key=app_key,
|
|
295
|
+
params={"dataSourceId": app_key, "dataSourceType": "qingflow"},
|
|
296
|
+
items_key="items",
|
|
297
|
+
result_transform=_extract_dataset_fields,
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
@tool_cn_name("报表字段选项")
|
|
301
|
+
def qingbi_report_list_field_options(self, *, profile: str, field_id: str, chart_id: str) -> JSONObject:
|
|
302
|
+
"""执行工具方法逻辑。"""
|
|
303
|
+
if not field_id:
|
|
304
|
+
raise_tool_error(QingflowApiError.config_error("field_id is required"))
|
|
305
|
+
path = f"/qingbi/charts/datasource/{chart_id}/question/option" if chart_id else "/qingbi/charts/datasource/question/option"
|
|
306
|
+
params = {"fieldId": field_id}
|
|
307
|
+
if chart_id:
|
|
308
|
+
self._require_chart_id(chart_id)
|
|
309
|
+
return self._request(profile, "GET", path, chart_id=chart_id or None, field_id=field_id, params=params, items_key="items")
|
|
310
|
+
|
|
311
|
+
def _request(
|
|
312
|
+
self,
|
|
313
|
+
profile: str,
|
|
314
|
+
method: str,
|
|
315
|
+
path: str,
|
|
316
|
+
*,
|
|
317
|
+
json_body: JSONValue = None,
|
|
318
|
+
params: JSONObject | None = None,
|
|
319
|
+
items_key: str | None = None,
|
|
320
|
+
result_transform: object | None = None,
|
|
321
|
+
risk_operation: str | None = None,
|
|
322
|
+
risk_target: str | None = None,
|
|
323
|
+
**extra: JSONValue,
|
|
324
|
+
) -> JSONObject:
|
|
325
|
+
"""执行内部辅助逻辑。"""
|
|
326
|
+
def runner(session_profile, context):
|
|
327
|
+
qingbi_context = BackendRequestContext(
|
|
328
|
+
base_url=_qingbi_base_url(context.base_url),
|
|
329
|
+
token=context.token,
|
|
330
|
+
ws_id=context.ws_id,
|
|
331
|
+
qf_request_id=context.qf_request_id,
|
|
332
|
+
qf_version=context.qf_version,
|
|
333
|
+
qf_version_source=context.qf_version_source,
|
|
334
|
+
)
|
|
335
|
+
result = self.backend.request(method, qingbi_context, path, json_body=json_body, params=params)
|
|
336
|
+
if callable(result_transform):
|
|
337
|
+
result = result_transform(result)
|
|
338
|
+
payload: JSONObject = {"profile": profile, "ws_id": session_profile.selected_ws_id}
|
|
339
|
+
if items_key:
|
|
340
|
+
payload[items_key] = result
|
|
341
|
+
else:
|
|
342
|
+
payload["result"] = result
|
|
343
|
+
payload.update(extra)
|
|
344
|
+
if risk_operation and risk_target:
|
|
345
|
+
return self._attach_human_review_notice(payload, operation=risk_operation, target=risk_target)
|
|
346
|
+
return payload
|
|
347
|
+
|
|
348
|
+
return self._run(profile, runner)
|
|
349
|
+
|
|
350
|
+
def _require_app_key(self, app_key: str) -> None:
|
|
351
|
+
"""执行内部辅助逻辑。"""
|
|
352
|
+
if not app_key:
|
|
353
|
+
raise_tool_error(QingflowApiError.config_error("app_key is required"))
|
|
354
|
+
|
|
355
|
+
def _require_chart_id(self, chart_id: str) -> None:
|
|
356
|
+
"""执行内部辅助逻辑。"""
|
|
357
|
+
if not chart_id:
|
|
358
|
+
raise_tool_error(QingflowApiError.config_error("chart_id is required"))
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def _extract_dataset_fields(result: JSONValue) -> list[JSONObject]:
|
|
362
|
+
if isinstance(result, dict) and isinstance(result.get("fields"), list):
|
|
363
|
+
return result["fields"]
|
|
364
|
+
if isinstance(result, list):
|
|
365
|
+
return result
|
|
366
|
+
return []
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _extract_sorted_chart_items(result: JSONValue) -> list[JSONObject]:
|
|
370
|
+
if isinstance(result, dict) and isinstance(result.get("list"), list):
|
|
371
|
+
return result["list"]
|
|
372
|
+
if isinstance(result, list):
|
|
373
|
+
return result
|
|
374
|
+
return []
|