kaiten-cli 0.3.3__py3-none-any.whl
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.
- kaiten_cli/__init__.py +5 -0
- kaiten_cli/__main__.py +9 -0
- kaiten_cli/agent_gateway.py +577 -0
- kaiten_cli/app.py +1636 -0
- kaiten_cli/completion.py +570 -0
- kaiten_cli/discovery.py +39 -0
- kaiten_cli/errors.py +79 -0
- kaiten_cli/i18n.py +92 -0
- kaiten_cli/locales/ru.json +2967 -0
- kaiten_cli/localized_click.py +160 -0
- kaiten_cli/models.py +202 -0
- kaiten_cli/profiles.py +316 -0
- kaiten_cli/registry/__init__.py +260 -0
- kaiten_cli/registry/addons.py +392 -0
- kaiten_cli/registry/audit_and_analytics.py +451 -0
- kaiten_cli/registry/automation_schema.py +44 -0
- kaiten_cli/registry/automation_schemas.json +7417 -0
- kaiten_cli/registry/automations.py +342 -0
- kaiten_cli/registry/base.py +62 -0
- kaiten_cli/registry/blockers.py +320 -0
- kaiten_cli/registry/boards.py +520 -0
- kaiten_cli/registry/card_relations.py +376 -0
- kaiten_cli/registry/card_types.py +363 -0
- kaiten_cli/registry/cards.py +1329 -0
- kaiten_cli/registry/charts.py +539 -0
- kaiten_cli/registry/checklists.py +600 -0
- kaiten_cli/registry/columns.py +558 -0
- kaiten_cli/registry/comments.py +224 -0
- kaiten_cli/registry/custom_directories.py +980 -0
- kaiten_cli/registry/custom_properties.py +1460 -0
- kaiten_cli/registry/dashboards.py +564 -0
- kaiten_cli/registry/documents.py +761 -0
- kaiten_cli/registry/external_links.py +128 -0
- kaiten_cli/registry/files.py +738 -0
- kaiten_cli/registry/github_addon.py +613 -0
- kaiten_cli/registry/iterations.py +397 -0
- kaiten_cli/registry/lanes.py +205 -0
- kaiten_cli/registry/live_contracts.py +361 -0
- kaiten_cli/registry/members.py +336 -0
- kaiten_cli/registry/module_docs.py +108 -0
- kaiten_cli/registry/projects.py +369 -0
- kaiten_cli/registry/query.py +129 -0
- kaiten_cli/registry/restrictions.py +249 -0
- kaiten_cli/registry/roles_and_groups.py +1102 -0
- kaiten_cli/registry/scim.py +263 -0
- kaiten_cli/registry/service_desk.py +1270 -0
- kaiten_cli/registry/snapshot.py +187 -0
- kaiten_cli/registry/spaces.py +272 -0
- kaiten_cli/registry/subscribers.py +168 -0
- kaiten_cli/registry/tags.py +193 -0
- kaiten_cli/registry/time_logs.py +353 -0
- kaiten_cli/registry/tree.py +352 -0
- kaiten_cli/registry/utilities.py +287 -0
- kaiten_cli/registry/webhooks.py +265 -0
- kaiten_cli/runtime/__init__.py +1 -0
- kaiten_cli/runtime/automation_validation.py +115 -0
- kaiten_cli/runtime/behaviors.py +787 -0
- kaiten_cli/runtime/cache.py +733 -0
- kaiten_cli/runtime/client.py +387 -0
- kaiten_cli/runtime/endpoints.py +59 -0
- kaiten_cli/runtime/executor.py +269 -0
- kaiten_cli/runtime/fs_security.py +168 -0
- kaiten_cli/runtime/input.py +310 -0
- kaiten_cli/runtime/output.py +59 -0
- kaiten_cli/runtime/public_validation.py +35 -0
- kaiten_cli/runtime/snapshot_build.py +527 -0
- kaiten_cli/runtime/snapshot_common.py +363 -0
- kaiten_cli/runtime/snapshot_query.py +306 -0
- kaiten_cli/runtime/snapshot_store.py +1192 -0
- kaiten_cli/runtime/snapshots.py +62 -0
- kaiten_cli/runtime/sqlite_errors.py +23 -0
- kaiten_cli/runtime/support/__init__.py +1 -0
- kaiten_cli/runtime/support/addons.py +953 -0
- kaiten_cli/runtime/support/audit.py +70 -0
- kaiten_cli/runtime/support/batch.py +234 -0
- kaiten_cli/runtime/support/card_move_url.py +213 -0
- kaiten_cli/runtime/support/cards.py +174 -0
- kaiten_cli/runtime/support/checklists.py +21 -0
- kaiten_cli/runtime/support/company_users.py +117 -0
- kaiten_cli/runtime/support/dashboards.py +35 -0
- kaiten_cli/runtime/support/documents.py +175 -0
- kaiten_cli/runtime/support/files.py +561 -0
- kaiten_cli/runtime/support/markdown_export.py +525 -0
- kaiten_cli/runtime/support/pagination.py +167 -0
- kaiten_cli/runtime/support/projects.py +66 -0
- kaiten_cli/runtime/support/relations.py +86 -0
- kaiten_cli/runtime/support/restrictions.py +62 -0
- kaiten_cli/runtime/support/spaces.py +20 -0
- kaiten_cli/runtime/support/time_logs.py +61 -0
- kaiten_cli/runtime/support/tree.py +140 -0
- kaiten_cli/runtime/support/tree_sharing.py +447 -0
- kaiten_cli/runtime/trace.py +500 -0
- kaiten_cli/runtime/transforms.py +128 -0
- kaiten_cli/schema_docs.py +22 -0
- kaiten_cli/update_check.py +519 -0
- kaiten_cli-0.3.3.dist-info/METADATA +1110 -0
- kaiten_cli-0.3.3.dist-info/RECORD +99 -0
- kaiten_cli-0.3.3.dist-info/WHEEL +4 -0
- kaiten_cli-0.3.3.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,787 @@
|
|
|
1
|
+
"""Declarative runtime behavior for tools with non-trivial execution."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from kaiten_cli.errors import BatchExecutionError, ValidationError
|
|
9
|
+
from kaiten_cli.i18n import tr
|
|
10
|
+
from kaiten_cli.runtime.support.audit import (
|
|
11
|
+
DEFAULT_HISTORY_WORKERS,
|
|
12
|
+
fetch_all_space_activity,
|
|
13
|
+
fetch_card_location_histories,
|
|
14
|
+
)
|
|
15
|
+
from kaiten_cli.runtime.support.batch import MAX_BATCH_WORKERS
|
|
16
|
+
from kaiten_cli.runtime.support.card_move_url import (
|
|
17
|
+
parse_card_url,
|
|
18
|
+
parse_target_url,
|
|
19
|
+
resolve_move_target,
|
|
20
|
+
validate_url_domain,
|
|
21
|
+
)
|
|
22
|
+
from kaiten_cli.runtime.support.cards import fetch_all_cards, fetch_cards_batch_get
|
|
23
|
+
from kaiten_cli.runtime.support.checklists import extract_card_checklists, extract_checklist_items
|
|
24
|
+
from kaiten_cli.runtime.support.documents import prepare_document_body
|
|
25
|
+
from kaiten_cli.runtime.support.pagination import (
|
|
26
|
+
API_MAX_PAGE_SIZE,
|
|
27
|
+
DEFAULT_COLLECTION_MAX_PAGES,
|
|
28
|
+
)
|
|
29
|
+
from kaiten_cli.runtime.support.projects import fetch_project_cards
|
|
30
|
+
from kaiten_cli.runtime.support.relations import fetch_card_children_batch, fetch_comments_batch
|
|
31
|
+
from kaiten_cli.runtime.support.restrictions import select_restriction
|
|
32
|
+
from kaiten_cli.runtime.support.spaces import fetch_space_topology
|
|
33
|
+
from kaiten_cli.runtime.support.time_logs import fetch_time_logs_batch
|
|
34
|
+
from kaiten_cli.runtime.support.tree import build_tree, fetch_all_entities, list_children
|
|
35
|
+
from kaiten_cli.runtime.transforms import compact_response, select_fields, strip_base64
|
|
36
|
+
|
|
37
|
+
Query = dict[str, Any] | None
|
|
38
|
+
Body = dict[str, Any] | None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def archive_card_request(
|
|
42
|
+
tool, payload: dict[str, Any], path: str, query: Query, body: Body
|
|
43
|
+
) -> tuple[str, Query, Body]:
|
|
44
|
+
return path, query, {"condition": 2}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def planned_relation_add_request(
|
|
48
|
+
tool, payload: dict[str, Any], path: str, query: Query, body: Body
|
|
49
|
+
) -> tuple[str, Query, Body]:
|
|
50
|
+
shaped = dict(body or {})
|
|
51
|
+
shaped.setdefault("type", "end-start")
|
|
52
|
+
return path, query, shaped
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def project_title_to_name_request(
|
|
56
|
+
tool, payload: dict[str, Any], path: str, query: Query, body: Body
|
|
57
|
+
) -> tuple[str, Query, Body]:
|
|
58
|
+
shaped = dict(body or {})
|
|
59
|
+
if "title" in shaped:
|
|
60
|
+
shaped["name"] = shaped.pop("title")
|
|
61
|
+
return path, query, shaped
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def card_child_add_request(
|
|
65
|
+
tool, payload: dict[str, Any], path: str, query: Query, body: Body
|
|
66
|
+
) -> tuple[str, Query, Body]:
|
|
67
|
+
shaped = dict(body or {})
|
|
68
|
+
return path, query, {"card_id": shaped["child_card_id"]}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def card_parent_add_request(
|
|
72
|
+
tool, payload: dict[str, Any], path: str, query: Query, body: Body
|
|
73
|
+
) -> tuple[str, Query, Body]:
|
|
74
|
+
shaped = dict(body or {})
|
|
75
|
+
return path, query, {"card_id": shaped["parent_card_id"]}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def default_role_time_log_request(
|
|
79
|
+
tool, payload: dict[str, Any], path: str, query: Query, body: Body
|
|
80
|
+
) -> tuple[str, Query, Body]:
|
|
81
|
+
shaped = dict(body or {})
|
|
82
|
+
shaped.setdefault("role_id", -1)
|
|
83
|
+
return path, query, shaped
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def prepare_document_request(
|
|
87
|
+
tool, payload: dict[str, Any], path: str, query: Query, body: Body
|
|
88
|
+
) -> tuple[str, Query, Body]:
|
|
89
|
+
return path, query, prepare_document_body(tool.canonical_name, dict(body or {}))
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def document_list_search_request(
|
|
93
|
+
tool, payload: dict[str, Any], path: str, query: Query, body: Body
|
|
94
|
+
) -> tuple[str, Query, Body]:
|
|
95
|
+
shaped_query = dict(query or {})
|
|
96
|
+
if "search_fields" in payload:
|
|
97
|
+
shaped_query["fields"] = payload["search_fields"]
|
|
98
|
+
return path, shaped_query or None, body
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def prevent_redirect_request(
|
|
102
|
+
tool, payload: dict[str, Any], path: str, query: Query, body: Body
|
|
103
|
+
) -> tuple[str, Query, Body]:
|
|
104
|
+
shaped_query = dict(query or {})
|
|
105
|
+
shaped_query["prevent_redirect"] = True
|
|
106
|
+
return path, shaped_query, body
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def checklist_item_compat_request(
|
|
110
|
+
tool, payload: dict[str, Any], path: str, query: Query, body: Body
|
|
111
|
+
) -> tuple[str, Query, Body]:
|
|
112
|
+
card_id = payload.get("card_id")
|
|
113
|
+
if card_id is None:
|
|
114
|
+
return checklist_relocation_request(tool, payload, path, query, body)
|
|
115
|
+
checklist_id = payload["checklist_id"]
|
|
116
|
+
compatibility_path = f"/cards/{card_id}/checklists/{checklist_id}/items"
|
|
117
|
+
if "item_id" in payload:
|
|
118
|
+
compatibility_path += f"/{payload['item_id']}"
|
|
119
|
+
return checklist_relocation_request(tool, payload, compatibility_path, query, body)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def checklist_relocation_request(tool, payload, path, query, body):
|
|
123
|
+
shaped = dict(body or {})
|
|
124
|
+
for source, target in (
|
|
125
|
+
("target_card_id", "card_id"),
|
|
126
|
+
("target_checklist_id", "checklist_id"),
|
|
127
|
+
("target_space_uid", "space_uid"),
|
|
128
|
+
):
|
|
129
|
+
if source in payload:
|
|
130
|
+
shaped[target] = payload[source]
|
|
131
|
+
return path, query, shaped or None
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def column_subscriber_default_type_request(
|
|
135
|
+
tool, payload: dict[str, Any], path: str, query: Query, body: Body
|
|
136
|
+
) -> tuple[str, Query, Body]:
|
|
137
|
+
shaped = dict(body or {})
|
|
138
|
+
shaped.setdefault("type", 1)
|
|
139
|
+
return path, query, shaped
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def automation_copy_request(
|
|
143
|
+
tool, payload: dict[str, Any], path: str, query: Query, body: Body
|
|
144
|
+
) -> tuple[str, Query, Body]:
|
|
145
|
+
shaped = dict(body or {})
|
|
146
|
+
if "target_space_id" in shaped:
|
|
147
|
+
shaped["targetSpaceId"] = shaped.pop("target_space_id")
|
|
148
|
+
return path, query, shaped
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def restriction_copy_request(tool, payload, path, query, body):
|
|
152
|
+
return path, query, {"source_restriction_id": payload["restriction_id"]}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
async def execute_restriction_get(client, tool, payload, path, query, body, timeout, reporter):
|
|
156
|
+
if reporter:
|
|
157
|
+
reporter(tr("execution: synthetic restriction read from the space restriction list"))
|
|
158
|
+
data = await client.get(path, timeout=timeout)
|
|
159
|
+
return select_restriction(data, payload["restriction_id"])
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def archive_service_request(
|
|
163
|
+
tool, payload: dict[str, Any], path: str, query: Query, body: Body
|
|
164
|
+
) -> tuple[str, Query, Body]:
|
|
165
|
+
return path, query, {"archived": True}
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def board_delete_force_request(
|
|
169
|
+
tool, payload: dict[str, Any], path: str, query: Query, body: Body
|
|
170
|
+
) -> tuple[str, Query, Body]:
|
|
171
|
+
if "force" not in payload:
|
|
172
|
+
return path, query, body
|
|
173
|
+
shaped_query = dict(query or {})
|
|
174
|
+
shaped_query["force"] = payload["force"]
|
|
175
|
+
return path, shaped_query, {"force": payload["force"]}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def board_get_scoped_request(
|
|
179
|
+
tool, payload: dict[str, Any], path: str, query: Query, body: Body
|
|
180
|
+
) -> tuple[str, Query, Body]:
|
|
181
|
+
space_id = payload.get("space_id")
|
|
182
|
+
if space_id is None:
|
|
183
|
+
return path, query, body
|
|
184
|
+
return f"/spaces/{space_id}/boards/{payload['board_id']}", query, body
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def board_place_existing_request(
|
|
188
|
+
tool, payload: dict[str, Any], path: str, query: Query, body: Body
|
|
189
|
+
) -> tuple[str, Query, Body]:
|
|
190
|
+
shaped = dict(body or {})
|
|
191
|
+
shaped.setdefault("top", 0)
|
|
192
|
+
shaped.setdefault("left", 0)
|
|
193
|
+
return path, query, shaped
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def saved_filter_title_request(
|
|
197
|
+
tool, payload: dict[str, Any], path: str, query: Query, body: Body
|
|
198
|
+
) -> tuple[str, Query, Body]:
|
|
199
|
+
shaped = dict(body or {})
|
|
200
|
+
if "name" in shaped:
|
|
201
|
+
shaped["title"] = shaped.pop("name")
|
|
202
|
+
return path, query, shaped
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def service_desk_stats_query_request(
|
|
206
|
+
tool, payload: dict[str, Any], path: str, query: Query, body: Body
|
|
207
|
+
) -> tuple[str, Query, Body]:
|
|
208
|
+
shaped_query = dict(query or {})
|
|
209
|
+
if "date_from" in shaped_query:
|
|
210
|
+
shaped_query["date-from"] = shaped_query.pop("date_from")
|
|
211
|
+
if "date_to" in shaped_query:
|
|
212
|
+
shaped_query["date-to"] = shaped_query.pop("date_to")
|
|
213
|
+
return path, shaped_query, body
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def company_members_section_request(
|
|
217
|
+
tool, payload: dict[str, Any], path: str, query: Query, body: Body
|
|
218
|
+
) -> tuple[str, Query, Body]:
|
|
219
|
+
shaped_query = dict(query or {})
|
|
220
|
+
shaped_query.setdefault("for_members_section", True)
|
|
221
|
+
shaped_query.setdefault("offset", 0)
|
|
222
|
+
return path, shaped_query, body
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def comment_format_request(
|
|
226
|
+
tool, payload: dict[str, Any], path: str, query: Query, body: Body
|
|
227
|
+
) -> tuple[str, Query, Body]:
|
|
228
|
+
shaped = dict(body or {})
|
|
229
|
+
format_value = shaped.pop("format", None)
|
|
230
|
+
if format_value == "html":
|
|
231
|
+
shaped["type"] = 2
|
|
232
|
+
elif format_value == "markdown":
|
|
233
|
+
shaped["type"] = 1
|
|
234
|
+
return path, query, shaped
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def payload_body_request(
|
|
238
|
+
tool, payload: dict[str, Any], path: str, query: Query, body: Body
|
|
239
|
+
) -> tuple[str, Query, Body]:
|
|
240
|
+
shaped = dict(body or {})
|
|
241
|
+
payload_body = shaped.pop("payload", None)
|
|
242
|
+
if payload_body is not None:
|
|
243
|
+
if not isinstance(payload_body, dict):
|
|
244
|
+
raise ValidationError(tr("Field payload must be a JSON object."))
|
|
245
|
+
shaped.update(payload_body)
|
|
246
|
+
return path, query, shaped or None
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def encode_object_query_request(
|
|
250
|
+
tool, payload: dict[str, Any], path: str, query: Query, body: Body
|
|
251
|
+
) -> tuple[str, Query, Body]:
|
|
252
|
+
shaped_query = dict(query or {})
|
|
253
|
+
for key, value in tuple(shaped_query.items()):
|
|
254
|
+
if isinstance(value, dict):
|
|
255
|
+
shaped_query[key] = json.dumps(value, ensure_ascii=False)
|
|
256
|
+
return path, shaped_query or None, body
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def reject_custom_property_include_values(tool, payload: dict[str, Any]) -> None:
|
|
260
|
+
if payload.get("include_values") is True:
|
|
261
|
+
raise ValidationError(
|
|
262
|
+
tr(
|
|
263
|
+
"Field include_values is no longer supported by Kaiten Public API. List Select options with `kaiten custom-properties select-values list` and Catalog entries with `kaiten custom-properties catalog-values list`."
|
|
264
|
+
)
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def scim_query_request(
|
|
269
|
+
tool, payload: dict[str, Any], path: str, query: Query, body: Body
|
|
270
|
+
) -> tuple[str, Query, Body]:
|
|
271
|
+
shaped_query = dict(query or {})
|
|
272
|
+
if "start_index" in shaped_query:
|
|
273
|
+
shaped_query["startIndex"] = shaped_query.pop("start_index")
|
|
274
|
+
return path, shaped_query or None, body
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def validate_cards_list_all_selection(tool, payload: dict[str, Any]) -> None:
|
|
278
|
+
selection = payload.get("selection")
|
|
279
|
+
if selection is None:
|
|
280
|
+
return
|
|
281
|
+
if "archived" in payload or "condition" in payload:
|
|
282
|
+
raise ValidationError(tr("Field selection cannot be combined with archived or condition."))
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def validate_card_id_batch(tool, payload: dict[str, Any]) -> None:
|
|
286
|
+
card_ids = payload.get("card_ids")
|
|
287
|
+
if not isinstance(card_ids, list) or not card_ids:
|
|
288
|
+
raise ValidationError(tr("Field card_ids must be a non-empty array."))
|
|
289
|
+
workers = payload.get("workers", DEFAULT_HISTORY_WORKERS)
|
|
290
|
+
if workers < 1 or workers > MAX_BATCH_WORKERS:
|
|
291
|
+
raise ValidationError(
|
|
292
|
+
tr("Field workers must be between 1 and {value_0}.", value_0=MAX_BATCH_WORKERS)
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
validate_history_batch_get = validate_card_id_batch
|
|
297
|
+
validate_cards_batch_get = validate_card_id_batch
|
|
298
|
+
validate_time_logs_batch_list = validate_card_id_batch
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
async def execute_blockers_get(
|
|
302
|
+
client,
|
|
303
|
+
tool,
|
|
304
|
+
payload: dict[str, Any],
|
|
305
|
+
path: str,
|
|
306
|
+
query: Query,
|
|
307
|
+
body: Body,
|
|
308
|
+
timeout: float,
|
|
309
|
+
reporter,
|
|
310
|
+
) -> Any:
|
|
311
|
+
if reporter:
|
|
312
|
+
reporter(tr("execution: custom read by filtering the blockers list"))
|
|
313
|
+
blockers = await client.get(path, params=query, timeout=timeout)
|
|
314
|
+
if not isinstance(blockers, list):
|
|
315
|
+
return blockers
|
|
316
|
+
blocker_id = payload["blocker_id"]
|
|
317
|
+
return next(
|
|
318
|
+
(item for item in blockers if isinstance(item, dict) and item.get("id") == blocker_id),
|
|
319
|
+
None,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
async def execute_tree_children_list(
|
|
324
|
+
client,
|
|
325
|
+
tool,
|
|
326
|
+
payload: dict[str, Any],
|
|
327
|
+
path: str,
|
|
328
|
+
query: Query,
|
|
329
|
+
body: Body,
|
|
330
|
+
timeout: float,
|
|
331
|
+
reporter,
|
|
332
|
+
) -> Any:
|
|
333
|
+
if reporter:
|
|
334
|
+
reporter(tr("execution: aggregated read from spaces, documents, and document groups"))
|
|
335
|
+
entities = await fetch_all_entities(client, timeout=timeout, reporter=reporter)
|
|
336
|
+
return list_children(entities, payload.get("parent_entity_uid"))
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
async def execute_tree_get(
|
|
340
|
+
client,
|
|
341
|
+
tool,
|
|
342
|
+
payload: dict[str, Any],
|
|
343
|
+
path: str,
|
|
344
|
+
query: Query,
|
|
345
|
+
body: Body,
|
|
346
|
+
timeout: float,
|
|
347
|
+
reporter,
|
|
348
|
+
) -> Any:
|
|
349
|
+
if reporter:
|
|
350
|
+
reporter(tr("execution: aggregated tree build from spaces, documents, and document groups"))
|
|
351
|
+
entities = await fetch_all_entities(client, timeout=timeout, reporter=reporter)
|
|
352
|
+
return build_tree(entities, payload.get("root_uid"), payload.get("depth", 0))
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
async def execute_project_cards_list(
|
|
356
|
+
client,
|
|
357
|
+
tool,
|
|
358
|
+
payload: dict[str, Any],
|
|
359
|
+
path: str,
|
|
360
|
+
query: Query,
|
|
361
|
+
body: Body,
|
|
362
|
+
timeout: float,
|
|
363
|
+
reporter,
|
|
364
|
+
) -> Any:
|
|
365
|
+
if reporter:
|
|
366
|
+
reporter(tr("execution: synthetic read with direct endpoint and embedded project fallback"))
|
|
367
|
+
return await fetch_project_cards(
|
|
368
|
+
client, str(payload["project_id"]), timeout=timeout, reporter=reporter
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
async def execute_cards_list_all(
|
|
373
|
+
client,
|
|
374
|
+
tool,
|
|
375
|
+
payload: dict[str, Any],
|
|
376
|
+
path: str,
|
|
377
|
+
query: Query,
|
|
378
|
+
body: Body,
|
|
379
|
+
timeout: float,
|
|
380
|
+
reporter,
|
|
381
|
+
) -> Any:
|
|
382
|
+
if reporter:
|
|
383
|
+
reporter(tr("execution: aggregated bounded pagination over /cards"))
|
|
384
|
+
return await fetch_all_cards(client, payload, timeout=timeout)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def _shape_move_by_url_card(card: Any, payload: dict[str, Any]) -> Any:
|
|
388
|
+
shaped = compact_response(card, bool(payload.get("compact", False)))
|
|
389
|
+
shaped = select_fields(shaped, payload.get("fields"))
|
|
390
|
+
shaped, _ = strip_base64(shaped)
|
|
391
|
+
return shaped
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def _verify_moved_card(card: Any, target: dict[str, Any]) -> None:
|
|
395
|
+
if not isinstance(card, dict):
|
|
396
|
+
raise ValidationError(tr("Move verification failed: final card response is not an object."))
|
|
397
|
+
|
|
398
|
+
expected = {
|
|
399
|
+
"board_id": target["board_id"],
|
|
400
|
+
"column_id": target["column_id"],
|
|
401
|
+
}
|
|
402
|
+
if target.get("lane_id") is not None:
|
|
403
|
+
expected["lane_id"] = target["lane_id"]
|
|
404
|
+
|
|
405
|
+
mismatches = [
|
|
406
|
+
tr(
|
|
407
|
+
"{key}: expected {expected}, got {actual}",
|
|
408
|
+
key=key,
|
|
409
|
+
expected=value,
|
|
410
|
+
actual=card.get(key),
|
|
411
|
+
)
|
|
412
|
+
for key, value in expected.items()
|
|
413
|
+
if card.get(key) != value
|
|
414
|
+
]
|
|
415
|
+
if mismatches:
|
|
416
|
+
raise ValidationError(
|
|
417
|
+
tr("Move verification failed: {details}", details="; ".join(mismatches))
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
async def execute_cards_move_by_url(
|
|
422
|
+
client,
|
|
423
|
+
tool,
|
|
424
|
+
payload: dict[str, Any],
|
|
425
|
+
path: str,
|
|
426
|
+
query: Query,
|
|
427
|
+
body: Body,
|
|
428
|
+
timeout: float,
|
|
429
|
+
reporter,
|
|
430
|
+
) -> Any:
|
|
431
|
+
if reporter:
|
|
432
|
+
reporter(tr("execution: aggregated card move by Kaiten UI URLs"))
|
|
433
|
+
|
|
434
|
+
card_url = parse_card_url(payload["card_url"])
|
|
435
|
+
target_url = parse_target_url(payload["target_url"])
|
|
436
|
+
validate_url_domain(card_url.domain, client.domain, label=tr("Card URL"))
|
|
437
|
+
validate_url_domain(target_url.domain, client.domain, label=tr("Target URL"))
|
|
438
|
+
|
|
439
|
+
target = await resolve_move_target(
|
|
440
|
+
client,
|
|
441
|
+
target_url,
|
|
442
|
+
lane_id=payload.get("lane_id"),
|
|
443
|
+
timeout=timeout,
|
|
444
|
+
)
|
|
445
|
+
patch_body: dict[str, Any] = {
|
|
446
|
+
"board_id": target["board_id"],
|
|
447
|
+
"column_id": target["column_id"],
|
|
448
|
+
}
|
|
449
|
+
if target.get("lane_id") is not None:
|
|
450
|
+
patch_body["lane_id"] = target["lane_id"]
|
|
451
|
+
if "sort_order" in payload:
|
|
452
|
+
patch_body["sort_order"] = payload["sort_order"]
|
|
453
|
+
|
|
454
|
+
if payload.get("dry_run", False):
|
|
455
|
+
return {
|
|
456
|
+
"card_ref": card_url.card_ref,
|
|
457
|
+
"card_space_id": card_url.space_id,
|
|
458
|
+
"target": target,
|
|
459
|
+
"dry_run": True,
|
|
460
|
+
"would_patch": patch_body,
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
moved_card = await client.patch(f"/cards/{card_url.card_ref}", json=patch_body, timeout=timeout)
|
|
464
|
+
verified = False
|
|
465
|
+
card = moved_card
|
|
466
|
+
if payload.get("verify", True):
|
|
467
|
+
card = await client.get(f"/cards/{card_url.card_ref}", timeout=timeout)
|
|
468
|
+
_verify_moved_card(card, target)
|
|
469
|
+
verified = True
|
|
470
|
+
|
|
471
|
+
return {
|
|
472
|
+
"card_ref": card_url.card_ref,
|
|
473
|
+
"target": target,
|
|
474
|
+
"card": _shape_move_by_url_card(card, payload),
|
|
475
|
+
"verified": verified,
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
async def execute_card_location_history_batch_get(
|
|
480
|
+
client,
|
|
481
|
+
tool,
|
|
482
|
+
payload: dict[str, Any],
|
|
483
|
+
path: str,
|
|
484
|
+
query: Query,
|
|
485
|
+
body: Body,
|
|
486
|
+
timeout: float,
|
|
487
|
+
reporter,
|
|
488
|
+
) -> Any:
|
|
489
|
+
workers = payload.get("workers", DEFAULT_HISTORY_WORKERS)
|
|
490
|
+
if reporter:
|
|
491
|
+
reporter(
|
|
492
|
+
tr(
|
|
493
|
+
"execution: aggregated batch history fetch over /cards/{{card_id}}/location-history with workers={value_0}",
|
|
494
|
+
value_0=workers,
|
|
495
|
+
)
|
|
496
|
+
)
|
|
497
|
+
result = await fetch_card_location_histories(
|
|
498
|
+
domain=client.domain,
|
|
499
|
+
token=client.token,
|
|
500
|
+
card_ids=list(payload["card_ids"]),
|
|
501
|
+
workers=workers,
|
|
502
|
+
fields=payload.get("fields"),
|
|
503
|
+
timeout=timeout,
|
|
504
|
+
reporter=reporter,
|
|
505
|
+
execution_context=client.execution_context,
|
|
506
|
+
cache_policy=client.cache_policy,
|
|
507
|
+
)
|
|
508
|
+
if reporter:
|
|
509
|
+
meta = result["meta"]
|
|
510
|
+
reporter(
|
|
511
|
+
tr(
|
|
512
|
+
"batch-history: requested={value_0} unique={value_1} succeeded={value_2} failed={value_3}",
|
|
513
|
+
value_0=meta["requested_count"],
|
|
514
|
+
value_1=meta["unique_count"],
|
|
515
|
+
value_2=meta["succeeded"],
|
|
516
|
+
value_3=meta["failed"],
|
|
517
|
+
)
|
|
518
|
+
)
|
|
519
|
+
if result["meta"]["succeeded"] == 0:
|
|
520
|
+
raise BatchExecutionError(
|
|
521
|
+
tr("Failed to fetch location history for all requested cards."), result
|
|
522
|
+
)
|
|
523
|
+
return result
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
async def execute_card_children_batch_list(
|
|
527
|
+
client,
|
|
528
|
+
tool,
|
|
529
|
+
payload: dict[str, Any],
|
|
530
|
+
path: str,
|
|
531
|
+
query: Query,
|
|
532
|
+
body: Body,
|
|
533
|
+
timeout: float,
|
|
534
|
+
reporter,
|
|
535
|
+
) -> Any:
|
|
536
|
+
workers = payload.get("workers", DEFAULT_HISTORY_WORKERS)
|
|
537
|
+
if reporter:
|
|
538
|
+
reporter(
|
|
539
|
+
tr(
|
|
540
|
+
"execution: aggregated batch relation fetch over /cards/{{card_id}}/children with workers={value_0}",
|
|
541
|
+
value_0=workers,
|
|
542
|
+
)
|
|
543
|
+
)
|
|
544
|
+
result = await fetch_card_children_batch(
|
|
545
|
+
domain=client.domain,
|
|
546
|
+
token=client.token,
|
|
547
|
+
card_ids=list(payload["card_ids"]),
|
|
548
|
+
workers=workers,
|
|
549
|
+
page_size=payload.get("page_size", API_MAX_PAGE_SIZE),
|
|
550
|
+
max_pages=payload.get("max_pages", DEFAULT_COLLECTION_MAX_PAGES),
|
|
551
|
+
compact=bool(payload.get("compact", False)),
|
|
552
|
+
fields=payload.get("fields"),
|
|
553
|
+
timeout=timeout,
|
|
554
|
+
reporter=reporter,
|
|
555
|
+
execution_context=client.execution_context,
|
|
556
|
+
cache_policy=client.cache_policy,
|
|
557
|
+
)
|
|
558
|
+
if reporter:
|
|
559
|
+
meta = result["meta"]
|
|
560
|
+
reporter(
|
|
561
|
+
tr(
|
|
562
|
+
"batch-children: requested={value_0} unique={value_1} succeeded={value_2} failed={value_3}",
|
|
563
|
+
value_0=meta["requested_count"],
|
|
564
|
+
value_1=meta["unique_count"],
|
|
565
|
+
value_2=meta["succeeded"],
|
|
566
|
+
value_3=meta["failed"],
|
|
567
|
+
)
|
|
568
|
+
)
|
|
569
|
+
if result["meta"]["succeeded"] == 0:
|
|
570
|
+
raise BatchExecutionError(
|
|
571
|
+
tr("Failed to fetch child cards for all requested cards."), result
|
|
572
|
+
)
|
|
573
|
+
return result
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
async def execute_cards_batch_get(
|
|
577
|
+
client,
|
|
578
|
+
tool,
|
|
579
|
+
payload: dict[str, Any],
|
|
580
|
+
path: str,
|
|
581
|
+
query: Query,
|
|
582
|
+
body: Body,
|
|
583
|
+
timeout: float,
|
|
584
|
+
reporter,
|
|
585
|
+
) -> Any:
|
|
586
|
+
workers = payload.get("workers", DEFAULT_HISTORY_WORKERS)
|
|
587
|
+
if reporter:
|
|
588
|
+
reporter(
|
|
589
|
+
tr(
|
|
590
|
+
"execution: aggregated batch card read over /cards/{{card_id}} with workers={value_0}",
|
|
591
|
+
value_0=workers,
|
|
592
|
+
)
|
|
593
|
+
)
|
|
594
|
+
result = await fetch_cards_batch_get(
|
|
595
|
+
domain=client.domain,
|
|
596
|
+
token=client.token,
|
|
597
|
+
card_ids=list(payload["card_ids"]),
|
|
598
|
+
workers=workers,
|
|
599
|
+
compact=bool(payload.get("compact", False)),
|
|
600
|
+
fields=payload.get("fields"),
|
|
601
|
+
timeout=timeout,
|
|
602
|
+
reporter=reporter,
|
|
603
|
+
execution_context=client.execution_context,
|
|
604
|
+
cache_policy=client.cache_policy,
|
|
605
|
+
)
|
|
606
|
+
if reporter:
|
|
607
|
+
meta = result["meta"]
|
|
608
|
+
reporter(
|
|
609
|
+
tr(
|
|
610
|
+
"batch-cards: requested={value_0} unique={value_1} succeeded={value_2} failed={value_3}",
|
|
611
|
+
value_0=meta["requested_count"],
|
|
612
|
+
value_1=meta["unique_count"],
|
|
613
|
+
value_2=meta["succeeded"],
|
|
614
|
+
value_3=meta["failed"],
|
|
615
|
+
)
|
|
616
|
+
)
|
|
617
|
+
if result["meta"]["succeeded"] == 0:
|
|
618
|
+
raise BatchExecutionError(tr("Failed to fetch cards for all requested card IDs."), result)
|
|
619
|
+
return result
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
async def execute_checklists_list(
|
|
623
|
+
client,
|
|
624
|
+
tool,
|
|
625
|
+
payload: dict[str, Any],
|
|
626
|
+
path: str,
|
|
627
|
+
query: Query,
|
|
628
|
+
body: Body,
|
|
629
|
+
timeout: float,
|
|
630
|
+
reporter,
|
|
631
|
+
) -> Any:
|
|
632
|
+
if reporter:
|
|
633
|
+
reporter(tr("execution: synthetic checklist read from /cards/{card_id} payload"))
|
|
634
|
+
card = await client.get(path, timeout=timeout)
|
|
635
|
+
return extract_card_checklists(card if isinstance(card, dict) else {})
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
async def execute_checklist_items_list(
|
|
639
|
+
client,
|
|
640
|
+
tool,
|
|
641
|
+
payload: dict[str, Any],
|
|
642
|
+
path: str,
|
|
643
|
+
query: Query,
|
|
644
|
+
body: Body,
|
|
645
|
+
timeout: float,
|
|
646
|
+
reporter,
|
|
647
|
+
) -> Any:
|
|
648
|
+
if reporter:
|
|
649
|
+
reporter(
|
|
650
|
+
tr(
|
|
651
|
+
"execution: synthetic checklist item read from /cards/{card_id}/checklists/{checklist_id} payload"
|
|
652
|
+
)
|
|
653
|
+
)
|
|
654
|
+
checklist = await client.get(path, timeout=timeout)
|
|
655
|
+
return extract_checklist_items(checklist)
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
async def execute_comments_batch_list(
|
|
659
|
+
client,
|
|
660
|
+
tool,
|
|
661
|
+
payload: dict[str, Any],
|
|
662
|
+
path: str,
|
|
663
|
+
query: Query,
|
|
664
|
+
body: Body,
|
|
665
|
+
timeout: float,
|
|
666
|
+
reporter,
|
|
667
|
+
) -> Any:
|
|
668
|
+
workers = payload.get("workers", DEFAULT_HISTORY_WORKERS)
|
|
669
|
+
if reporter:
|
|
670
|
+
reporter(
|
|
671
|
+
tr(
|
|
672
|
+
"execution: aggregated batch comment fetch over /cards/{{card_id}}/comments with workers={value_0}",
|
|
673
|
+
value_0=workers,
|
|
674
|
+
)
|
|
675
|
+
)
|
|
676
|
+
result = await fetch_comments_batch(
|
|
677
|
+
domain=client.domain,
|
|
678
|
+
token=client.token,
|
|
679
|
+
card_ids=list(payload["card_ids"]),
|
|
680
|
+
workers=workers,
|
|
681
|
+
page_size=payload.get("page_size", API_MAX_PAGE_SIZE),
|
|
682
|
+
max_pages=payload.get("max_pages", DEFAULT_COLLECTION_MAX_PAGES),
|
|
683
|
+
compact=bool(payload.get("compact", False)),
|
|
684
|
+
fields=payload.get("fields"),
|
|
685
|
+
timeout=timeout,
|
|
686
|
+
reporter=reporter,
|
|
687
|
+
execution_context=client.execution_context,
|
|
688
|
+
cache_policy=client.cache_policy,
|
|
689
|
+
)
|
|
690
|
+
if reporter:
|
|
691
|
+
meta = result["meta"]
|
|
692
|
+
reporter(
|
|
693
|
+
tr(
|
|
694
|
+
"batch-comments: requested={value_0} unique={value_1} succeeded={value_2} failed={value_3}",
|
|
695
|
+
value_0=meta["requested_count"],
|
|
696
|
+
value_1=meta["unique_count"],
|
|
697
|
+
value_2=meta["succeeded"],
|
|
698
|
+
value_3=meta["failed"],
|
|
699
|
+
)
|
|
700
|
+
)
|
|
701
|
+
if result["meta"]["succeeded"] == 0:
|
|
702
|
+
raise BatchExecutionError(tr("Failed to fetch comments for all requested cards."), result)
|
|
703
|
+
return result
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
async def execute_time_logs_batch_list(
|
|
707
|
+
client,
|
|
708
|
+
tool,
|
|
709
|
+
payload: dict[str, Any],
|
|
710
|
+
path: str,
|
|
711
|
+
query: Query,
|
|
712
|
+
body: Body,
|
|
713
|
+
timeout: float,
|
|
714
|
+
reporter,
|
|
715
|
+
) -> Any:
|
|
716
|
+
workers = payload.get("workers", DEFAULT_HISTORY_WORKERS)
|
|
717
|
+
if reporter:
|
|
718
|
+
reporter(
|
|
719
|
+
tr(
|
|
720
|
+
"execution: aggregated batch time-log fetch over /cards/{{card_id}}/time-logs with workers={value_0}",
|
|
721
|
+
value_0=workers,
|
|
722
|
+
)
|
|
723
|
+
)
|
|
724
|
+
result = await fetch_time_logs_batch(
|
|
725
|
+
domain=client.domain,
|
|
726
|
+
token=client.token,
|
|
727
|
+
card_ids=list(payload["card_ids"]),
|
|
728
|
+
workers=workers,
|
|
729
|
+
page_size=payload.get("page_size", API_MAX_PAGE_SIZE),
|
|
730
|
+
max_pages=payload.get("max_pages", DEFAULT_COLLECTION_MAX_PAGES),
|
|
731
|
+
for_date=payload.get("for_date"),
|
|
732
|
+
personal=payload.get("personal"),
|
|
733
|
+
compact=bool(payload.get("compact", False)),
|
|
734
|
+
fields=payload.get("fields"),
|
|
735
|
+
timeout=timeout,
|
|
736
|
+
reporter=reporter,
|
|
737
|
+
execution_context=client.execution_context,
|
|
738
|
+
cache_policy=client.cache_policy,
|
|
739
|
+
)
|
|
740
|
+
if reporter:
|
|
741
|
+
meta = result["meta"]
|
|
742
|
+
reporter(
|
|
743
|
+
tr(
|
|
744
|
+
"batch-time-logs: requested={value_0} unique={value_1} succeeded={value_2} failed={value_3}",
|
|
745
|
+
value_0=meta["requested_count"],
|
|
746
|
+
value_1=meta["unique_count"],
|
|
747
|
+
value_2=meta["succeeded"],
|
|
748
|
+
value_3=meta["failed"],
|
|
749
|
+
)
|
|
750
|
+
)
|
|
751
|
+
if result["meta"]["succeeded"] == 0:
|
|
752
|
+
raise BatchExecutionError(tr("Failed to fetch time logs for all requested cards."), result)
|
|
753
|
+
return result
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
async def execute_space_activity_all(
|
|
757
|
+
client,
|
|
758
|
+
tool,
|
|
759
|
+
payload: dict[str, Any],
|
|
760
|
+
path: str,
|
|
761
|
+
query: Query,
|
|
762
|
+
body: Body,
|
|
763
|
+
timeout: float,
|
|
764
|
+
reporter,
|
|
765
|
+
) -> Any:
|
|
766
|
+
if reporter:
|
|
767
|
+
reporter(tr("execution: aggregated bounded pagination over /spaces/{space_id}/activity"))
|
|
768
|
+
return await fetch_all_space_activity(client, payload, timeout=timeout)
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
async def execute_space_topology_get(
|
|
772
|
+
client,
|
|
773
|
+
tool,
|
|
774
|
+
payload: dict[str, Any],
|
|
775
|
+
path: str,
|
|
776
|
+
query: Query,
|
|
777
|
+
body: Body,
|
|
778
|
+
timeout: float,
|
|
779
|
+
reporter,
|
|
780
|
+
) -> Any:
|
|
781
|
+
if reporter:
|
|
782
|
+
reporter(
|
|
783
|
+
tr(
|
|
784
|
+
"execution: aggregated topology read over /spaces/{space_id}/boards and /boards/{board_id}"
|
|
785
|
+
)
|
|
786
|
+
)
|
|
787
|
+
return await fetch_space_topology(client, payload["space_id"], timeout=timeout)
|