@josephyan/qingflow-app-builder-mcp 0.2.0-beta.7 → 0.2.0-beta.71
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 +5 -3
- package/docs/local-agent-install.md +21 -5
- package/npm/bin/qingflow-app-builder-mcp.mjs +1 -1
- package/npm/lib/runtime.mjs +168 -12
- package/package.json +1 -1
- package/pyproject.toml +4 -1
- package/skills/qingflow-app-builder/SKILL.md +155 -22
- package/skills/qingflow-app-builder/references/create-app.md +51 -21
- package/skills/qingflow-app-builder/references/environments.md +1 -1
- package/skills/qingflow-app-builder/references/flow-actors-and-permissions.md +123 -0
- package/skills/qingflow-app-builder/references/gotchas.md +28 -1
- package/skills/qingflow-app-builder/references/solution-playbooks.md +14 -12
- package/skills/qingflow-app-builder/references/tool-selection.md +47 -19
- package/skills/qingflow-app-builder/references/update-flow.md +112 -25
- package/skills/qingflow-app-builder/references/update-layout.md +11 -24
- package/skills/qingflow-app-builder/references/update-schema.md +1 -23
- package/skills/qingflow-app-builder/references/update-views.md +87 -21
- package/skills/qingflow-app-builder-code-integrations/SKILL.md +137 -0
- package/skills/qingflow-app-builder-code-integrations/agents/openai.yaml +4 -0
- package/skills/qingflow-app-builder-code-integrations/references/code-block.md +66 -0
- package/skills/qingflow-app-builder-code-integrations/references/q-linker.md +77 -0
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/backend_client.py +210 -0
- package/src/qingflow_mcp/builder_facade/models.py +1252 -3
- package/src/qingflow_mcp/builder_facade/service.py +11367 -2389
- package/src/qingflow_mcp/cli/__init__.py +1 -0
- package/src/qingflow_mcp/cli/commands/__init__.py +15 -0
- package/src/qingflow_mcp/cli/commands/app.py +40 -0
- package/src/qingflow_mcp/cli/commands/auth.py +78 -0
- package/src/qingflow_mcp/cli/commands/builder.py +515 -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/record.py +304 -0
- package/src/qingflow_mcp/cli/commands/task.py +89 -0
- package/src/qingflow_mcp/cli/commands/workspace.py +33 -0
- package/src/qingflow_mcp/cli/context.py +48 -0
- package/src/qingflow_mcp/cli/formatters.py +355 -0
- package/src/qingflow_mcp/cli/json_io.py +50 -0
- package/src/qingflow_mcp/cli/main.py +149 -0
- package/src/qingflow_mcp/config.py +39 -0
- package/src/qingflow_mcp/import_store.py +121 -0
- package/src/qingflow_mcp/list_type_labels.py +24 -0
- package/src/qingflow_mcp/response_trim.py +668 -0
- package/src/qingflow_mcp/server.py +160 -18
- package/src/qingflow_mcp/server_app_builder.py +275 -68
- package/src/qingflow_mcp/server_app_user.py +219 -191
- package/src/qingflow_mcp/session_store.py +41 -1
- package/src/qingflow_mcp/solution/compiler/form_compiler.py +43 -4
- package/src/qingflow_mcp/solution/compiler/icon_utils.py +119 -45
- package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +41 -2
- package/src/qingflow_mcp/solution/executor.py +107 -11
- package/src/qingflow_mcp/solution/spec_models.py +2 -0
- package/src/qingflow_mcp/tools/ai_builder_tools.py +2032 -127
- package/src/qingflow_mcp/tools/app_tools.py +419 -12
- package/src/qingflow_mcp/tools/approval_tools.py +571 -72
- package/src/qingflow_mcp/tools/auth_tools.py +398 -2
- package/src/qingflow_mcp/tools/code_block_tools.py +756 -0
- package/src/qingflow_mcp/tools/custom_button_tools.py +179 -0
- package/src/qingflow_mcp/tools/directory_tools.py +203 -31
- package/src/qingflow_mcp/tools/feedback_tools.py +230 -0
- package/src/qingflow_mcp/tools/file_tools.py +1 -0
- package/src/qingflow_mcp/tools/import_tools.py +2150 -0
- package/src/qingflow_mcp/tools/package_tools.py +18 -4
- package/src/qingflow_mcp/tools/portal_tools.py +31 -0
- package/src/qingflow_mcp/tools/qingbi_report_tools.py +109 -7
- package/src/qingflow_mcp/tools/record_tools.py +9894 -1104
- package/src/qingflow_mcp/tools/solution_tools.py +115 -3
- package/src/qingflow_mcp/tools/task_context_tools.py +2040 -0
- package/src/qingflow_mcp/tools/task_tools.py +376 -225
- package/src/qingflow_mcp/tools/workspace_tools.py +163 -19
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from copy import deepcopy
|
|
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
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CustomButtonTools(ToolBase):
|
|
12
|
+
def custom_button_list(
|
|
13
|
+
self,
|
|
14
|
+
*,
|
|
15
|
+
profile: str,
|
|
16
|
+
app_key: str,
|
|
17
|
+
being_draft: bool = True,
|
|
18
|
+
include_raw: bool = False,
|
|
19
|
+
) -> JSONObject:
|
|
20
|
+
self._require_app_key(app_key)
|
|
21
|
+
|
|
22
|
+
def runner(session_profile, context):
|
|
23
|
+
result = self.backend.request(
|
|
24
|
+
"GET",
|
|
25
|
+
context,
|
|
26
|
+
f"/app/{app_key}/customButton",
|
|
27
|
+
params={"beingDraft": being_draft},
|
|
28
|
+
)
|
|
29
|
+
items = []
|
|
30
|
+
raw_items = result.get("result") if isinstance(result, dict) and isinstance(result.get("result"), list) else []
|
|
31
|
+
for item in raw_items:
|
|
32
|
+
if not isinstance(item, dict):
|
|
33
|
+
continue
|
|
34
|
+
items.append(self._compact_button_base_info(item))
|
|
35
|
+
response = {
|
|
36
|
+
"profile": profile,
|
|
37
|
+
"ws_id": session_profile.selected_ws_id,
|
|
38
|
+
"app_key": app_key,
|
|
39
|
+
"being_draft": being_draft,
|
|
40
|
+
"items": result if include_raw else items,
|
|
41
|
+
"count": len(items),
|
|
42
|
+
"compact": not include_raw,
|
|
43
|
+
}
|
|
44
|
+
if include_raw:
|
|
45
|
+
response["summary"] = items
|
|
46
|
+
return response
|
|
47
|
+
|
|
48
|
+
return self._run(profile, runner)
|
|
49
|
+
|
|
50
|
+
def custom_button_get(
|
|
51
|
+
self,
|
|
52
|
+
*,
|
|
53
|
+
profile: str,
|
|
54
|
+
app_key: str,
|
|
55
|
+
button_id: int,
|
|
56
|
+
being_draft: bool = True,
|
|
57
|
+
include_raw: bool = False,
|
|
58
|
+
) -> JSONObject:
|
|
59
|
+
self._require_app_key(app_key)
|
|
60
|
+
self._require_button_id(button_id)
|
|
61
|
+
|
|
62
|
+
def runner(session_profile, context):
|
|
63
|
+
params = {"beingDraft": being_draft}
|
|
64
|
+
result = self.backend.request("GET", context, f"/app/{app_key}/customButton/{button_id}", params=params)
|
|
65
|
+
response = {
|
|
66
|
+
"profile": profile,
|
|
67
|
+
"ws_id": session_profile.selected_ws_id,
|
|
68
|
+
"app_key": app_key,
|
|
69
|
+
"button_id": button_id,
|
|
70
|
+
"being_draft": being_draft,
|
|
71
|
+
"result": result if include_raw else self._compact_button_detail(result if isinstance(result, dict) else {}),
|
|
72
|
+
"compact": not include_raw,
|
|
73
|
+
}
|
|
74
|
+
if include_raw:
|
|
75
|
+
response["summary"] = self._compact_button_detail(result if isinstance(result, dict) else {})
|
|
76
|
+
return response
|
|
77
|
+
|
|
78
|
+
return self._run(profile, runner)
|
|
79
|
+
|
|
80
|
+
def custom_button_create(self, *, profile: str, app_key: str, payload: JSONObject) -> JSONObject:
|
|
81
|
+
self._require_app_key(app_key)
|
|
82
|
+
body = self._require_dict(payload)
|
|
83
|
+
|
|
84
|
+
def runner(session_profile, context):
|
|
85
|
+
result = self.backend.request("POST", context, f"/app/{app_key}/customButton", json_body=deepcopy(body))
|
|
86
|
+
return {"profile": profile, "ws_id": session_profile.selected_ws_id, "app_key": app_key, "result": result}
|
|
87
|
+
|
|
88
|
+
return self._run(profile, runner)
|
|
89
|
+
|
|
90
|
+
def custom_button_update(self, *, profile: str, app_key: str, button_id: int, payload: JSONObject) -> JSONObject:
|
|
91
|
+
self._require_app_key(app_key)
|
|
92
|
+
self._require_button_id(button_id)
|
|
93
|
+
body = self._require_dict(payload)
|
|
94
|
+
|
|
95
|
+
def runner(session_profile, context):
|
|
96
|
+
result = self.backend.request(
|
|
97
|
+
"POST",
|
|
98
|
+
context,
|
|
99
|
+
f"/app/{app_key}/customButton/{button_id}",
|
|
100
|
+
json_body=deepcopy(body),
|
|
101
|
+
)
|
|
102
|
+
return self._attach_human_review_notice(
|
|
103
|
+
{
|
|
104
|
+
"profile": profile,
|
|
105
|
+
"ws_id": session_profile.selected_ws_id,
|
|
106
|
+
"app_key": app_key,
|
|
107
|
+
"button_id": button_id,
|
|
108
|
+
"result": result,
|
|
109
|
+
},
|
|
110
|
+
operation="update",
|
|
111
|
+
target="custom button configuration",
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
return self._run(profile, runner)
|
|
115
|
+
|
|
116
|
+
def custom_button_delete(self, *, profile: str, app_key: str, button_id: int) -> JSONObject:
|
|
117
|
+
self._require_app_key(app_key)
|
|
118
|
+
self._require_button_id(button_id)
|
|
119
|
+
|
|
120
|
+
def runner(session_profile, context):
|
|
121
|
+
result = self.backend.request("DELETE", context, f"/app/{app_key}/customButton/{button_id}")
|
|
122
|
+
return self._attach_human_review_notice(
|
|
123
|
+
{
|
|
124
|
+
"profile": profile,
|
|
125
|
+
"ws_id": session_profile.selected_ws_id,
|
|
126
|
+
"app_key": app_key,
|
|
127
|
+
"button_id": button_id,
|
|
128
|
+
"result": result,
|
|
129
|
+
},
|
|
130
|
+
operation="delete",
|
|
131
|
+
target="custom button configuration",
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
return self._run(profile, runner)
|
|
135
|
+
|
|
136
|
+
def _require_app_key(self, app_key: str) -> None:
|
|
137
|
+
if not str(app_key or "").strip():
|
|
138
|
+
raise_tool_error(QingflowApiError.config_error("app_key is required"))
|
|
139
|
+
|
|
140
|
+
def _require_button_id(self, button_id: int) -> None:
|
|
141
|
+
if not isinstance(button_id, int) or isinstance(button_id, bool) or button_id <= 0:
|
|
142
|
+
raise_tool_error(QingflowApiError.config_error("button_id must be a positive integer"))
|
|
143
|
+
|
|
144
|
+
def _compact_button_base_info(self, item: dict[str, object]) -> JSONObject:
|
|
145
|
+
creator = item.get("creatorUserInfo") if isinstance(item.get("creatorUserInfo"), dict) else {}
|
|
146
|
+
return {
|
|
147
|
+
"button_id": item.get("buttonId"),
|
|
148
|
+
"button_text": item.get("buttonText"),
|
|
149
|
+
"button_icon": item.get("buttonIcon"),
|
|
150
|
+
"icon_color": item.get("iconColor"),
|
|
151
|
+
"background_color": item.get("backgroundColor"),
|
|
152
|
+
"text_color": item.get("textColor"),
|
|
153
|
+
"creator_user_info": {
|
|
154
|
+
"uid": creator.get("uid"),
|
|
155
|
+
"name": creator.get("name"),
|
|
156
|
+
"email": creator.get("email"),
|
|
157
|
+
}
|
|
158
|
+
if creator
|
|
159
|
+
else None,
|
|
160
|
+
"used_in_chart_count": item.get("userInChartCount"),
|
|
161
|
+
"being_effective_external_qrobot": item.get("beingEffectiveExternalQRobot"),
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
def _compact_button_detail(self, item: dict[str, object]) -> JSONObject:
|
|
165
|
+
return {
|
|
166
|
+
"button_id": item.get("buttonId"),
|
|
167
|
+
"button_text": item.get("buttonText"),
|
|
168
|
+
"button_icon": item.get("buttonIcon"),
|
|
169
|
+
"icon_color": item.get("iconColor"),
|
|
170
|
+
"background_color": item.get("backgroundColor"),
|
|
171
|
+
"text_color": item.get("textColor"),
|
|
172
|
+
"trigger_action": item.get("triggerAction"),
|
|
173
|
+
"trigger_link_url": item.get("triggerLinkUrl"),
|
|
174
|
+
"trigger_add_data_config": deepcopy(item.get("triggerAddDataConfig")) if isinstance(item.get("triggerAddDataConfig"), dict) else None,
|
|
175
|
+
"external_qrobot_config": deepcopy(item.get("customButtonExternalQRobotRelationVO"))
|
|
176
|
+
if isinstance(item.get("customButtonExternalQRobotRelationVO"), dict)
|
|
177
|
+
else None,
|
|
178
|
+
"trigger_wings_config": deepcopy(item.get("triggerWingsConfig")) if isinstance(item.get("triggerWingsConfig"), dict) else None,
|
|
179
|
+
}
|
|
@@ -35,39 +35,39 @@ class DirectoryTools(ToolBase):
|
|
|
35
35
|
def directory_list_internal_users(
|
|
36
36
|
profile: str = DEFAULT_PROFILE,
|
|
37
37
|
keyword: str | None = None,
|
|
38
|
-
|
|
38
|
+
department_id: int | None = None,
|
|
39
39
|
role_id: int | None = None,
|
|
40
40
|
page_num: int = 1,
|
|
41
41
|
page_size: int = 20,
|
|
42
|
-
|
|
42
|
+
include_disabled: bool = False,
|
|
43
43
|
) -> dict[str, Any]:
|
|
44
44
|
return self.directory_list_internal_users(
|
|
45
45
|
profile=profile,
|
|
46
46
|
keyword=keyword,
|
|
47
|
-
dept_id=
|
|
47
|
+
dept_id=department_id,
|
|
48
48
|
role_id=role_id,
|
|
49
49
|
page_num=page_num,
|
|
50
50
|
page_size=page_size,
|
|
51
|
-
contain_disable=
|
|
51
|
+
contain_disable=include_disabled,
|
|
52
52
|
)
|
|
53
53
|
|
|
54
54
|
@mcp.tool()
|
|
55
55
|
def directory_list_all_internal_users(
|
|
56
56
|
profile: str = DEFAULT_PROFILE,
|
|
57
57
|
keyword: str | None = None,
|
|
58
|
-
|
|
58
|
+
department_id: int | None = None,
|
|
59
59
|
role_id: int | None = None,
|
|
60
60
|
page_size: int = 200,
|
|
61
|
-
|
|
61
|
+
include_disabled: bool = False,
|
|
62
62
|
max_pages: int = 100,
|
|
63
63
|
) -> dict[str, Any]:
|
|
64
64
|
return self.directory_list_all_internal_users(
|
|
65
65
|
profile=profile,
|
|
66
66
|
keyword=keyword,
|
|
67
|
-
dept_id=
|
|
67
|
+
dept_id=department_id,
|
|
68
68
|
role_id=role_id,
|
|
69
69
|
page_size=page_size,
|
|
70
|
-
contain_disable=
|
|
70
|
+
contain_disable=include_disabled,
|
|
71
71
|
max_pages=max_pages,
|
|
72
72
|
)
|
|
73
73
|
|
|
@@ -88,13 +88,13 @@ class DirectoryTools(ToolBase):
|
|
|
88
88
|
@mcp.tool()
|
|
89
89
|
def directory_list_all_departments(
|
|
90
90
|
profile: str = DEFAULT_PROFILE,
|
|
91
|
-
|
|
91
|
+
parent_department_id: int | None = None,
|
|
92
92
|
max_depth: int = 20,
|
|
93
93
|
max_items: int = 2000,
|
|
94
94
|
) -> dict[str, Any]:
|
|
95
95
|
return self.directory_list_all_departments(
|
|
96
96
|
profile=profile,
|
|
97
|
-
parent_dept_id=
|
|
97
|
+
parent_dept_id=parent_department_id,
|
|
98
98
|
max_depth=max_depth,
|
|
99
99
|
max_items=max_items,
|
|
100
100
|
)
|
|
@@ -102,9 +102,9 @@ class DirectoryTools(ToolBase):
|
|
|
102
102
|
@mcp.tool()
|
|
103
103
|
def directory_list_sub_departments(
|
|
104
104
|
profile: str = DEFAULT_PROFILE,
|
|
105
|
-
|
|
105
|
+
parent_department_id: int | None = None,
|
|
106
106
|
) -> dict[str, Any]:
|
|
107
|
-
return self.directory_list_sub_departments(profile=profile, parent_dept_id=
|
|
107
|
+
return self.directory_list_sub_departments(profile=profile, parent_dept_id=parent_department_id)
|
|
108
108
|
|
|
109
109
|
@mcp.tool()
|
|
110
110
|
def directory_list_external_members(
|
|
@@ -150,9 +150,27 @@ class DirectoryTools(ToolBase):
|
|
|
150
150
|
"pageSize": page_size,
|
|
151
151
|
},
|
|
152
152
|
)
|
|
153
|
-
return {
|
|
153
|
+
return {
|
|
154
|
+
"profile": profile,
|
|
155
|
+
"ws_id": session_profile.selected_ws_id,
|
|
156
|
+
"request_route": self._request_route_payload(context),
|
|
157
|
+
"result": result,
|
|
158
|
+
}
|
|
154
159
|
|
|
155
|
-
|
|
160
|
+
raw = self._run(profile, runner)
|
|
161
|
+
items = [item for item in _directory_items(raw.get("result")) if isinstance(item, dict)]
|
|
162
|
+
return self._public_directory_response(
|
|
163
|
+
raw,
|
|
164
|
+
items=items,
|
|
165
|
+
pagination={
|
|
166
|
+
"page": page_num,
|
|
167
|
+
"page_size": page_size,
|
|
168
|
+
"returned_items": len(items),
|
|
169
|
+
"reported_total": _coerce_int(_payload_value(raw.get("result"), "total")),
|
|
170
|
+
"page_amount": _coerce_int(_payload_value(raw.get("result"), "pageAmount")),
|
|
171
|
+
},
|
|
172
|
+
selection={"query": query, "scopes": normalized_scopes},
|
|
173
|
+
)
|
|
156
174
|
|
|
157
175
|
def directory_list_internal_users(
|
|
158
176
|
self,
|
|
@@ -178,9 +196,27 @@ class DirectoryTools(ToolBase):
|
|
|
178
196
|
if role_id is not None:
|
|
179
197
|
params["roleId"] = role_id
|
|
180
198
|
result = self.backend.request("GET", context, "/contact", params=params)
|
|
181
|
-
return {
|
|
199
|
+
return {
|
|
200
|
+
"profile": profile,
|
|
201
|
+
"ws_id": session_profile.selected_ws_id,
|
|
202
|
+
"request_route": self._request_route_payload(context),
|
|
203
|
+
"result": result,
|
|
204
|
+
}
|
|
182
205
|
|
|
183
|
-
|
|
206
|
+
raw = self._run(profile, runner)
|
|
207
|
+
items = [item for item in _directory_items(raw.get("result")) if isinstance(item, dict)]
|
|
208
|
+
return self._public_directory_response(
|
|
209
|
+
raw,
|
|
210
|
+
items=items,
|
|
211
|
+
pagination={
|
|
212
|
+
"page": page_num,
|
|
213
|
+
"page_size": page_size,
|
|
214
|
+
"returned_items": len(items),
|
|
215
|
+
"reported_total": _coerce_int(_payload_value(raw.get("result"), "total")),
|
|
216
|
+
"page_amount": _coerce_int(_payload_value(raw.get("result"), "pageAmount")),
|
|
217
|
+
},
|
|
218
|
+
selection={"keyword": keyword, "department_id": dept_id, "role_id": role_id, "include_disabled": contain_disable},
|
|
219
|
+
)
|
|
184
220
|
|
|
185
221
|
def directory_list_all_internal_users(
|
|
186
222
|
self,
|
|
@@ -237,6 +273,7 @@ class DirectoryTools(ToolBase):
|
|
|
237
273
|
return {
|
|
238
274
|
"profile": profile,
|
|
239
275
|
"ws_id": session_profile.selected_ws_id,
|
|
276
|
+
"request_route": self._request_route_payload(context),
|
|
240
277
|
"items": items,
|
|
241
278
|
"pagination": {
|
|
242
279
|
"page_size": page_size,
|
|
@@ -250,7 +287,13 @@ class DirectoryTools(ToolBase):
|
|
|
250
287
|
},
|
|
251
288
|
}
|
|
252
289
|
|
|
253
|
-
|
|
290
|
+
raw = self._run(profile, runner)
|
|
291
|
+
return self._public_directory_response(
|
|
292
|
+
raw,
|
|
293
|
+
items=[item for item in raw.get("items", []) if isinstance(item, dict)],
|
|
294
|
+
pagination=raw.get("pagination", {}),
|
|
295
|
+
selection={"keyword": keyword, "department_id": dept_id, "role_id": role_id, "include_disabled": contain_disable},
|
|
296
|
+
)
|
|
254
297
|
|
|
255
298
|
def directory_list_internal_departments(
|
|
256
299
|
self,
|
|
@@ -260,19 +303,79 @@ class DirectoryTools(ToolBase):
|
|
|
260
303
|
page_num: int,
|
|
261
304
|
page_size: int,
|
|
262
305
|
) -> dict[str, Any]:
|
|
263
|
-
if
|
|
264
|
-
raise_tool_error(QingflowApiError.config_error("
|
|
306
|
+
if page_num <= 0:
|
|
307
|
+
raise_tool_error(QingflowApiError.config_error("page_num must be positive"))
|
|
308
|
+
if page_size <= 0:
|
|
309
|
+
raise_tool_error(QingflowApiError.config_error("page_size must be positive"))
|
|
310
|
+
normalized_keyword = keyword.strip()
|
|
311
|
+
|
|
312
|
+
if not normalized_keyword:
|
|
313
|
+
def runner(session_profile, context):
|
|
314
|
+
fetch_limit = max((page_num + 1) * page_size + 1, page_size + 1)
|
|
315
|
+
items, truncated, deepest_depth = self._walk_department_tree(
|
|
316
|
+
context,
|
|
317
|
+
parent_dept_id=None,
|
|
318
|
+
max_depth=20,
|
|
319
|
+
max_items=fetch_limit,
|
|
320
|
+
)
|
|
321
|
+
start = (page_num - 1) * page_size
|
|
322
|
+
page_items = items[start : start + page_size]
|
|
323
|
+
reported_total = None if truncated else len(items)
|
|
324
|
+
page_amount = None if truncated else ((len(items) + page_size - 1) // page_size if items else 0)
|
|
325
|
+
if truncated and page_items:
|
|
326
|
+
page_amount = max(page_num + 1, (start + len(page_items) + page_size - 1) // page_size)
|
|
327
|
+
return {
|
|
328
|
+
"profile": profile,
|
|
329
|
+
"ws_id": session_profile.selected_ws_id,
|
|
330
|
+
"request_route": self._request_route_payload(context),
|
|
331
|
+
"items": page_items,
|
|
332
|
+
"pagination": {
|
|
333
|
+
"page": page_num,
|
|
334
|
+
"page_size": page_size,
|
|
335
|
+
"returned_items": len(page_items),
|
|
336
|
+
"reported_total": reported_total,
|
|
337
|
+
"page_amount": page_amount,
|
|
338
|
+
"depth_scanned": deepest_depth + 1 if page_items else 0,
|
|
339
|
+
},
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
raw = self._run(profile, runner)
|
|
343
|
+
items = [item for item in raw.get("items", []) if isinstance(item, dict)]
|
|
344
|
+
return self._public_directory_response(
|
|
345
|
+
raw,
|
|
346
|
+
items=items,
|
|
347
|
+
pagination=raw.get("pagination", {}),
|
|
348
|
+
selection={"keyword": None},
|
|
349
|
+
)
|
|
265
350
|
|
|
266
351
|
def runner(session_profile, context):
|
|
267
352
|
result = self.backend.request(
|
|
268
353
|
"GET",
|
|
269
354
|
context,
|
|
270
355
|
"/contact/deptByPage",
|
|
271
|
-
params={"keyword":
|
|
356
|
+
params={"keyword": normalized_keyword, "pageNum": page_num, "pageSize": page_size},
|
|
272
357
|
)
|
|
273
|
-
return {
|
|
358
|
+
return {
|
|
359
|
+
"profile": profile,
|
|
360
|
+
"ws_id": session_profile.selected_ws_id,
|
|
361
|
+
"request_route": self._request_route_payload(context),
|
|
362
|
+
"page": result,
|
|
363
|
+
}
|
|
274
364
|
|
|
275
|
-
|
|
365
|
+
raw = self._run(profile, runner)
|
|
366
|
+
items = [item for item in _directory_items(raw.get("page")) if isinstance(item, dict)]
|
|
367
|
+
return self._public_directory_response(
|
|
368
|
+
raw,
|
|
369
|
+
items=items,
|
|
370
|
+
pagination={
|
|
371
|
+
"page": page_num,
|
|
372
|
+
"page_size": page_size,
|
|
373
|
+
"returned_items": len(items),
|
|
374
|
+
"reported_total": _coerce_int(_payload_value(raw.get("page"), "total")),
|
|
375
|
+
"page_amount": _coerce_int(_payload_value(raw.get("page"), "pageAmount")),
|
|
376
|
+
},
|
|
377
|
+
selection={"keyword": normalized_keyword},
|
|
378
|
+
)
|
|
276
379
|
|
|
277
380
|
def directory_list_all_departments(
|
|
278
381
|
self,
|
|
@@ -304,18 +407,25 @@ class DirectoryTools(ToolBase):
|
|
|
304
407
|
return {
|
|
305
408
|
"profile": profile,
|
|
306
409
|
"ws_id": session_profile.selected_ws_id,
|
|
410
|
+
"request_route": self._request_route_payload(context),
|
|
307
411
|
"items": items,
|
|
308
|
-
"
|
|
309
|
-
"
|
|
412
|
+
"pagination": {
|
|
413
|
+
"root_parent_department_id": parent_dept_id,
|
|
310
414
|
"returned_items": len(items),
|
|
311
|
-
"
|
|
312
|
-
"truncated": truncated,
|
|
415
|
+
"fetched_pages": deepest_depth + 1 if items else 0,
|
|
313
416
|
"is_complete": not truncated,
|
|
417
|
+
"has_more": truncated,
|
|
314
418
|
"max_items": max_items,
|
|
315
419
|
},
|
|
316
420
|
}
|
|
317
421
|
|
|
318
|
-
|
|
422
|
+
raw = self._run(profile, runner)
|
|
423
|
+
return self._public_directory_response(
|
|
424
|
+
raw,
|
|
425
|
+
items=[item for item in raw.get("items", []) if isinstance(item, dict)],
|
|
426
|
+
pagination=raw.get("pagination", {}),
|
|
427
|
+
selection={"parent_department_id": parent_dept_id, "max_depth": max_depth, "max_items": max_items},
|
|
428
|
+
)
|
|
319
429
|
|
|
320
430
|
def directory_list_sub_departments(self, *, profile: str, parent_dept_id: int | None) -> dict[str, Any]:
|
|
321
431
|
def runner(session_profile, context):
|
|
@@ -323,9 +433,21 @@ class DirectoryTools(ToolBase):
|
|
|
323
433
|
if parent_dept_id is not None:
|
|
324
434
|
params["parentDeptId"] = parent_dept_id
|
|
325
435
|
result = self.backend.request("GET", context, "/contact/subDeptList", params=params)
|
|
326
|
-
return {
|
|
436
|
+
return {
|
|
437
|
+
"profile": profile,
|
|
438
|
+
"ws_id": session_profile.selected_ws_id,
|
|
439
|
+
"request_route": self._request_route_payload(context),
|
|
440
|
+
"items": result,
|
|
441
|
+
}
|
|
327
442
|
|
|
328
|
-
|
|
443
|
+
raw = self._run(profile, runner)
|
|
444
|
+
items = [item for item in raw.get("items", []) if isinstance(item, dict)]
|
|
445
|
+
return self._public_directory_response(
|
|
446
|
+
raw,
|
|
447
|
+
items=items,
|
|
448
|
+
pagination={"returned_items": len(items)},
|
|
449
|
+
selection={"parent_department_id": parent_dept_id},
|
|
450
|
+
)
|
|
329
451
|
|
|
330
452
|
def directory_list_external_members(
|
|
331
453
|
self,
|
|
@@ -349,9 +471,59 @@ class DirectoryTools(ToolBase):
|
|
|
349
471
|
if keyword:
|
|
350
472
|
params["keyword"] = keyword
|
|
351
473
|
result = self.backend.request("GET", context, "/external/member/pageList", params=params)
|
|
352
|
-
return {
|
|
474
|
+
return {
|
|
475
|
+
"profile": profile,
|
|
476
|
+
"ws_id": session_profile.selected_ws_id,
|
|
477
|
+
"request_route": self._request_route_payload(context),
|
|
478
|
+
"page": result,
|
|
479
|
+
"simple": simple,
|
|
480
|
+
}
|
|
353
481
|
|
|
354
|
-
|
|
482
|
+
raw = self._run(profile, runner)
|
|
483
|
+
items = [item for item in _directory_items(raw.get("page")) if isinstance(item, dict)]
|
|
484
|
+
return self._public_directory_response(
|
|
485
|
+
raw,
|
|
486
|
+
items=items,
|
|
487
|
+
pagination={
|
|
488
|
+
"page": page_num,
|
|
489
|
+
"page_size": page_size,
|
|
490
|
+
"returned_items": len(items),
|
|
491
|
+
"reported_total": _coerce_int(_payload_value(raw.get("page"), "total")),
|
|
492
|
+
"page_amount": _coerce_int(_payload_value(raw.get("page"), "pageAmount")),
|
|
493
|
+
},
|
|
494
|
+
selection={"keyword": keyword, "simple": simple},
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
def _request_route_payload(self, context) -> dict[str, Any]: # type: ignore[no-untyped-def]
|
|
498
|
+
describe_route = getattr(self.backend, "describe_route", None)
|
|
499
|
+
if callable(describe_route):
|
|
500
|
+
payload = describe_route(context)
|
|
501
|
+
if isinstance(payload, dict):
|
|
502
|
+
return payload
|
|
503
|
+
return {
|
|
504
|
+
"base_url": getattr(context, "base_url", None),
|
|
505
|
+
"qf_version": getattr(context, "qf_version", None),
|
|
506
|
+
"qf_version_source": getattr(context, "qf_version_source", None) or ("context" if getattr(context, "qf_version", None) else "unknown"),
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
def _public_directory_response(
|
|
510
|
+
self,
|
|
511
|
+
raw: dict[str, Any],
|
|
512
|
+
*,
|
|
513
|
+
items: list[dict[str, Any]],
|
|
514
|
+
pagination: dict[str, Any],
|
|
515
|
+
selection: dict[str, Any],
|
|
516
|
+
) -> dict[str, Any]:
|
|
517
|
+
response = dict(raw)
|
|
518
|
+
response["ok"] = bool(raw.get("ok", True))
|
|
519
|
+
response["warnings"] = []
|
|
520
|
+
response["output_profile"] = "normal"
|
|
521
|
+
response["data"] = {
|
|
522
|
+
"items": items,
|
|
523
|
+
"pagination": pagination,
|
|
524
|
+
"selection": selection,
|
|
525
|
+
}
|
|
526
|
+
return response
|
|
355
527
|
|
|
356
528
|
def _walk_department_tree(
|
|
357
529
|
self,
|