fenix-mcp 1.5.0__tar.gz → 1.7.0__tar.gz
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.
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/PKG-INFO +1 -1
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/__init__.py +1 -1
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/application/tools/knowledge.py +159 -43
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/domain/knowledge.py +11 -0
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/infrastructure/fenix_api/client.py +9 -0
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp.egg-info/PKG-INFO +1 -1
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/README.md +0 -0
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/application/presenters.py +0 -0
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/application/tool_base.py +0 -0
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/application/tool_registry.py +0 -0
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/application/tools/__init__.py +0 -0
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/application/tools/health.py +0 -0
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/application/tools/initialize.py +0 -0
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/application/tools/intelligence.py +0 -0
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/application/tools/productivity.py +0 -0
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/application/tools/user_config.py +0 -0
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/domain/initialization.py +0 -0
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/domain/intelligence.py +0 -0
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/domain/productivity.py +0 -0
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/domain/user_config.py +0 -0
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/infrastructure/config.py +0 -0
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/infrastructure/context.py +0 -0
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/infrastructure/http_client.py +0 -0
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/infrastructure/logging.py +0 -0
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/interface/mcp_server.py +0 -0
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/interface/transports.py +0 -0
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp/main.py +0 -0
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp.egg-info/SOURCES.txt +0 -0
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp.egg-info/dependency_links.txt +0 -0
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp.egg-info/entry_points.txt +0 -0
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp.egg-info/requires.txt +0 -0
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/fenix_mcp.egg-info/top_level.txt +0 -0
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/pyproject.toml +0 -0
- {fenix_mcp-1.5.0 → fenix_mcp-1.7.0}/setup.cfg +0 -0
|
@@ -39,18 +39,20 @@ class KnowledgeAction(str, Enum):
|
|
|
39
39
|
# Work items
|
|
40
40
|
WORK_CREATE = (
|
|
41
41
|
"work_create",
|
|
42
|
-
"Creates a work item with title, status and optional links.",
|
|
42
|
+
"Creates a work item with title, status and optional links. Use parent_key (e.g., DVPT-0001) to set parent.",
|
|
43
43
|
)
|
|
44
44
|
WORK_LIST = (
|
|
45
45
|
"work_list",
|
|
46
46
|
"Lists work items with status, priority and context filters.",
|
|
47
47
|
)
|
|
48
|
-
WORK_GET = (
|
|
48
|
+
WORK_GET = (
|
|
49
|
+
"work_get",
|
|
50
|
+
"Gets full details of a work item by ID or key (e.g., DVPT-0001).",
|
|
51
|
+
)
|
|
49
52
|
WORK_UPDATE = (
|
|
50
53
|
"work_update",
|
|
51
|
-
"Updates
|
|
54
|
+
"Updates allowed fields of an existing work item (title, description, priority, story_points, tags, due_date).",
|
|
52
55
|
)
|
|
53
|
-
WORK_DELETE = ("work_delete", "Permanently removes a work item.")
|
|
54
56
|
WORK_BACKLOG = ("work_backlog", "Lists backlog items for a team.")
|
|
55
57
|
WORK_SEARCH = (
|
|
56
58
|
"work_search",
|
|
@@ -60,7 +62,10 @@ class KnowledgeAction(str, Enum):
|
|
|
60
62
|
WORK_BY_BOARD = ("work_by_board", "Lists work items associated with a board.")
|
|
61
63
|
WORK_BY_SPRINT = ("work_by_sprint", "Lists work items associated with a sprint.")
|
|
62
64
|
WORK_BY_EPIC = ("work_by_epic", "Lists work items associated with an epic.")
|
|
63
|
-
WORK_CHILDREN = (
|
|
65
|
+
WORK_CHILDREN = (
|
|
66
|
+
"work_children",
|
|
67
|
+
"Lists child work items of a parent item by ID or key (e.g., DVPT-0001).",
|
|
68
|
+
)
|
|
64
69
|
WORK_STATUS_UPDATE = (
|
|
65
70
|
"work_status_update",
|
|
66
71
|
"Updates only the status of a work item.",
|
|
@@ -69,6 +74,14 @@ class KnowledgeAction(str, Enum):
|
|
|
69
74
|
"work_assign_sprint",
|
|
70
75
|
"Assigns work items to a sprint.",
|
|
71
76
|
)
|
|
77
|
+
WORK_ASSIGN_TO_ME = (
|
|
78
|
+
"work_assign_to_me",
|
|
79
|
+
"Assigns a work item to the current user.",
|
|
80
|
+
)
|
|
81
|
+
WORK_BULK_CREATE = (
|
|
82
|
+
"work_bulk_create",
|
|
83
|
+
"Creates multiple work items atomically with hierarchy. Use temp_id as temporary identifier and parent_temp_id to reference parent. Example: [{temp_id:'epic-1', title:'My Epic', item_type:'epic', work_category:'backend'}, {temp_id:'task-1', parent_temp_id:'epic-1', title:'My Task', item_type:'task', work_category:'backend'}]",
|
|
84
|
+
)
|
|
72
85
|
|
|
73
86
|
# Boards
|
|
74
87
|
BOARD_LIST = ("board_list", "Lists available boards with optional filters.")
|
|
@@ -203,6 +216,10 @@ class KnowledgeRequest(ToolRequest):
|
|
|
203
216
|
)
|
|
204
217
|
|
|
205
218
|
# Work item fields
|
|
219
|
+
work_key: Optional[str] = Field(
|
|
220
|
+
default=None,
|
|
221
|
+
description="Work item key (e.g., DVPT-0001). Use this instead of id for work_get and work_children.",
|
|
222
|
+
)
|
|
206
223
|
work_title: Optional[TitleStr] = Field(default=None, description="Work item title.")
|
|
207
224
|
work_description: Optional[MarkdownStr] = Field(
|
|
208
225
|
default=None, description="Work item description (Markdown)."
|
|
@@ -213,7 +230,7 @@ class KnowledgeRequest(ToolRequest):
|
|
|
213
230
|
)
|
|
214
231
|
work_status: Optional[str] = Field(
|
|
215
232
|
default=None,
|
|
216
|
-
description="Work item status (
|
|
233
|
+
description="Work item status ID (UUID of TeamStatus).",
|
|
217
234
|
)
|
|
218
235
|
work_priority: Optional[str] = Field(
|
|
219
236
|
default=None,
|
|
@@ -230,7 +247,12 @@ class KnowledgeRequest(ToolRequest):
|
|
|
230
247
|
default=None, description="Assignee ID (UUID)."
|
|
231
248
|
)
|
|
232
249
|
parent_id: Optional[UUIDStr] = Field(
|
|
233
|
-
default=None,
|
|
250
|
+
default=None,
|
|
251
|
+
description="Parent item ID (UUID). Prefer using parent_key instead.",
|
|
252
|
+
)
|
|
253
|
+
parent_key: Optional[str] = Field(
|
|
254
|
+
default=None,
|
|
255
|
+
description="Parent item key (e.g., DVPT-0001). Use this to set the parent of a work item.",
|
|
234
256
|
)
|
|
235
257
|
work_due_date: Optional[DateTimeStr] = Field(
|
|
236
258
|
default=None, description="Work item due date (ISO 8601)."
|
|
@@ -242,6 +264,17 @@ class KnowledgeRequest(ToolRequest):
|
|
|
242
264
|
default=None,
|
|
243
265
|
description="List of work item IDs for batch operations (UUIDs).",
|
|
244
266
|
)
|
|
267
|
+
work_items: Optional[List[Dict[str, Any]]] = Field(
|
|
268
|
+
default=None,
|
|
269
|
+
description=(
|
|
270
|
+
"List of work items for bulk create (max 50). Each item requires: "
|
|
271
|
+
"temp_id (your temporary ID like 'epic-1', 'task-a'), title, item_type, work_category. "
|
|
272
|
+
"Use parent_temp_id to set parent (e.g., parent_temp_id:'epic-1' makes it child of epic-1). "
|
|
273
|
+
"Optional: description, priority (low/medium/high/critical), story_points, tags, due_date. "
|
|
274
|
+
'Example: [{"temp_id":"e1", "title":"Epic", "item_type":"epic", "work_category":"backend"}, '
|
|
275
|
+
'{"temp_id":"t1", "parent_temp_id":"e1", "title":"Task", "item_type":"task", "work_category":"backend"}]'
|
|
276
|
+
),
|
|
277
|
+
)
|
|
245
278
|
|
|
246
279
|
# Mode fields
|
|
247
280
|
mode_id: Optional[UUIDStr] = Field(
|
|
@@ -372,19 +405,25 @@ class KnowledgeTool(Tool):
|
|
|
372
405
|
return text(
|
|
373
406
|
"❌ Provide work_category to create the item. Values: backend, frontend, mobile, fullstack, devops, infra, platform, sre, database, security, data, analytics, ai_ml, qa, automation, design, research, product, project, agile, support, operations, documentation, training, architecture, planning, development."
|
|
374
407
|
)
|
|
408
|
+
|
|
409
|
+
# Resolve parent_key to parent_id if provided
|
|
410
|
+
parent_id = payload.parent_id
|
|
411
|
+
if payload.parent_key and not parent_id:
|
|
412
|
+
parent_work = await self._service.work_get_by_key(payload.parent_key)
|
|
413
|
+
parent_id = parent_work.get("id")
|
|
414
|
+
|
|
375
415
|
work = await self._service.work_create(
|
|
376
416
|
{
|
|
377
417
|
"title": payload.work_title,
|
|
378
418
|
"description": payload.work_description,
|
|
379
419
|
"item_type": payload.work_type,
|
|
380
|
-
"
|
|
420
|
+
"status_id": payload.work_status,
|
|
381
421
|
"priority": payload.work_priority,
|
|
382
422
|
"work_category": payload.work_category,
|
|
383
423
|
"story_points": payload.story_points,
|
|
384
424
|
"assignee_id": payload.assignee_id,
|
|
385
425
|
"sprint_id": payload.sprint_id,
|
|
386
|
-
"
|
|
387
|
-
"parent_id": payload.parent_id,
|
|
426
|
+
"parent_id": parent_id,
|
|
388
427
|
"due_date": payload.work_due_date,
|
|
389
428
|
"tags": payload.work_tags,
|
|
390
429
|
}
|
|
@@ -399,7 +438,6 @@ class KnowledgeTool(Tool):
|
|
|
399
438
|
type=payload.work_type,
|
|
400
439
|
assignee=payload.assignee_id,
|
|
401
440
|
sprint=payload.sprint_id,
|
|
402
|
-
board=payload.board_id,
|
|
403
441
|
)
|
|
404
442
|
if not items:
|
|
405
443
|
return text("🎯 No work items found.")
|
|
@@ -407,41 +445,51 @@ class KnowledgeTool(Tool):
|
|
|
407
445
|
return text(f"🎯 **Work items ({len(items)}):**\n\n{body}")
|
|
408
446
|
|
|
409
447
|
if action is KnowledgeAction.WORK_GET:
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
448
|
+
# Support both id and work_key
|
|
449
|
+
if payload.work_key:
|
|
450
|
+
work = await self._service.work_get_by_key(payload.work_key)
|
|
451
|
+
elif payload.id:
|
|
452
|
+
work = await self._service.work_get(payload.id)
|
|
453
|
+
else:
|
|
454
|
+
return text("❌ Provide id or work_key to get the work item.")
|
|
413
455
|
return text(
|
|
414
456
|
_format_work(work, header="🎯 Work item details", show_description=True)
|
|
415
457
|
)
|
|
416
458
|
|
|
417
459
|
if action is KnowledgeAction.WORK_UPDATE:
|
|
418
|
-
|
|
419
|
-
|
|
460
|
+
# Resolve work_key to id if needed
|
|
461
|
+
work_id = payload.id
|
|
462
|
+
if payload.work_key and not work_id:
|
|
463
|
+
work_item = await self._service.work_get_by_key(payload.work_key)
|
|
464
|
+
work_id = work_item.get("id")
|
|
465
|
+
if not work_id:
|
|
466
|
+
return text("❌ Provide id or work_key to update the work item.")
|
|
467
|
+
|
|
468
|
+
# Only allow safe fields to be updated via MCP
|
|
469
|
+
# Excluded: item_type, status_id, assignee_id, sprint_id, parent_id, work_category
|
|
420
470
|
work = await self._service.work_update(
|
|
421
|
-
|
|
471
|
+
work_id,
|
|
422
472
|
{
|
|
423
473
|
"title": payload.work_title,
|
|
424
474
|
"description": payload.work_description,
|
|
425
|
-
"item_type": payload.work_type,
|
|
426
|
-
"status": payload.work_status,
|
|
427
475
|
"priority": payload.work_priority,
|
|
428
|
-
"work_category": payload.work_category,
|
|
429
476
|
"story_points": payload.story_points,
|
|
430
|
-
"assignee_id": payload.assignee_id,
|
|
431
|
-
"sprint_id": payload.sprint_id,
|
|
432
|
-
"board_id": payload.board_id,
|
|
433
|
-
"parent_id": payload.parent_id,
|
|
434
477
|
"due_date": payload.work_due_date,
|
|
435
478
|
"tags": payload.work_tags,
|
|
436
479
|
},
|
|
437
480
|
)
|
|
438
481
|
return text(_format_work(work, header="✅ Work item updated"))
|
|
439
482
|
|
|
440
|
-
if action is KnowledgeAction.
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
483
|
+
if action is KnowledgeAction.WORK_ASSIGN_TO_ME:
|
|
484
|
+
# Resolve work_key to id if needed
|
|
485
|
+
work_id = payload.id
|
|
486
|
+
if payload.work_key and not work_id:
|
|
487
|
+
work_item = await self._service.work_get_by_key(payload.work_key)
|
|
488
|
+
work_id = work_item.get("id")
|
|
489
|
+
if not work_id:
|
|
490
|
+
return text("❌ Provide id or work_key to assign the work item.")
|
|
491
|
+
work = await self._service.work_assign_to_me(work_id)
|
|
492
|
+
return text(_format_work(work, header="✅ Work item assigned to you"))
|
|
445
493
|
|
|
446
494
|
if action is KnowledgeAction.WORK_BACKLOG:
|
|
447
495
|
items = await self._service.work_backlog()
|
|
@@ -503,22 +551,32 @@ class KnowledgeTool(Tool):
|
|
|
503
551
|
return text(f"📦 **Epic work items ({len(items)}):**\n\n{body}")
|
|
504
552
|
|
|
505
553
|
if action is KnowledgeAction.WORK_CHILDREN:
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
554
|
+
# Support both id and work_key
|
|
555
|
+
work_id = payload.id
|
|
556
|
+
if payload.work_key and not work_id:
|
|
557
|
+
work_item = await self._service.work_get_by_key(payload.work_key)
|
|
558
|
+
work_id = work_item.get("id")
|
|
559
|
+
if not work_id:
|
|
560
|
+
return text("❌ Provide id or work_key to list children.")
|
|
561
|
+
items = await self._service.work_children(work_id)
|
|
509
562
|
if not items:
|
|
510
563
|
return text("👶 No child items found.")
|
|
511
564
|
body = "\n\n".join(_format_work(item) for item in items)
|
|
512
565
|
return text(f"👶 **Child work items ({len(items)}):**\n\n{body}")
|
|
513
566
|
|
|
514
567
|
if action is KnowledgeAction.WORK_STATUS_UPDATE:
|
|
515
|
-
|
|
516
|
-
|
|
568
|
+
# Resolve work_key to id if needed
|
|
569
|
+
work_id = payload.id
|
|
570
|
+
if payload.work_key and not work_id:
|
|
571
|
+
work_item = await self._service.work_get_by_key(payload.work_key)
|
|
572
|
+
work_id = work_item.get("id")
|
|
573
|
+
if not work_id:
|
|
574
|
+
return text("❌ Provide id or work_key to update status.")
|
|
517
575
|
if not payload.work_status:
|
|
518
|
-
return text("❌ Provide work_status to update.")
|
|
576
|
+
return text("❌ Provide work_status (status_id) to update.")
|
|
519
577
|
work = await self._service.work_update_status(
|
|
520
|
-
|
|
521
|
-
{"
|
|
578
|
+
work_id,
|
|
579
|
+
{"status_id": payload.work_status},
|
|
522
580
|
)
|
|
523
581
|
return text(_format_work(work, header="✅ Status updated"))
|
|
524
582
|
|
|
@@ -536,6 +594,48 @@ class KnowledgeTool(Tool):
|
|
|
536
594
|
count = len(payload.work_item_ids)
|
|
537
595
|
return text(f"✅ {count} work item(s) assigned to sprint.")
|
|
538
596
|
|
|
597
|
+
if action is KnowledgeAction.WORK_BULK_CREATE:
|
|
598
|
+
if not payload.work_items:
|
|
599
|
+
return text(
|
|
600
|
+
"❌ Provide work_items array. Each item requires: temp_id, title, item_type, work_category. "
|
|
601
|
+
"Optional: parent_temp_id, description, priority, story_points, tags, due_date."
|
|
602
|
+
)
|
|
603
|
+
if len(payload.work_items) > 50:
|
|
604
|
+
return text("❌ Maximum 50 items per bulk create.")
|
|
605
|
+
|
|
606
|
+
# Validate required fields
|
|
607
|
+
for i, item in enumerate(payload.work_items):
|
|
608
|
+
if not item.get("temp_id"):
|
|
609
|
+
return text(f"❌ Item {i}: missing temp_id.")
|
|
610
|
+
if not item.get("title"):
|
|
611
|
+
return text(f"❌ Item {i}: missing title.")
|
|
612
|
+
if not item.get("item_type"):
|
|
613
|
+
return text(f"❌ Item {i}: missing item_type.")
|
|
614
|
+
if not item.get("work_category"):
|
|
615
|
+
return text(f"❌ Item {i}: missing work_category.")
|
|
616
|
+
|
|
617
|
+
items = await self._service.work_bulk_create({"items": payload.work_items})
|
|
618
|
+
|
|
619
|
+
# Format response with summary
|
|
620
|
+
type_counts: Dict[str, int] = {}
|
|
621
|
+
for item in items:
|
|
622
|
+
item_type = item.get("item_type", "unknown")
|
|
623
|
+
type_counts[item_type] = type_counts.get(item_type, 0) + 1
|
|
624
|
+
|
|
625
|
+
lines = [f"✅ **{len(items)} work items created**", ""]
|
|
626
|
+
for item in items:
|
|
627
|
+
key = item.get("key", "")
|
|
628
|
+
title = item.get("title", "Untitled")
|
|
629
|
+
item_type = item.get("item_type", "unknown")
|
|
630
|
+
lines.append(f"- [{key}] {title} ({item_type})")
|
|
631
|
+
|
|
632
|
+
lines.append("")
|
|
633
|
+
lines.append("**Summary by type:**")
|
|
634
|
+
for item_type, count in sorted(type_counts.items()):
|
|
635
|
+
lines.append(f"- {item_type}: {count}")
|
|
636
|
+
|
|
637
|
+
return text("\n".join(lines))
|
|
638
|
+
|
|
539
639
|
return text(
|
|
540
640
|
"❌ Unsupported work item action.\n\nChoose one of the values:\n"
|
|
541
641
|
+ "\n".join(
|
|
@@ -1084,14 +1184,21 @@ def _format_work(
|
|
|
1084
1184
|
lines.append(header)
|
|
1085
1185
|
lines.append("")
|
|
1086
1186
|
|
|
1187
|
+
# Extract key (e.g., DVPT-0001)
|
|
1188
|
+
key = item.get("key", "")
|
|
1189
|
+
|
|
1087
1190
|
# Extract title
|
|
1088
1191
|
title = item.get("title") or item.get("name") or "Untitled"
|
|
1089
1192
|
|
|
1090
1193
|
# Extract type
|
|
1091
1194
|
item_type = item.get("item_type") or item.get("type") or "unknown"
|
|
1092
1195
|
|
|
1093
|
-
# Extract status
|
|
1094
|
-
|
|
1196
|
+
# Extract status - prefer status object with name, fallback to status_id
|
|
1197
|
+
status_obj = item.get("status")
|
|
1198
|
+
if isinstance(status_obj, dict):
|
|
1199
|
+
status = status_obj.get("name", "unknown")
|
|
1200
|
+
else:
|
|
1201
|
+
status = item.get("status_id") or status_obj or "unknown"
|
|
1095
1202
|
|
|
1096
1203
|
# Extract priority
|
|
1097
1204
|
priority = item.get("priority") or item.get("priority_level") or "undefined"
|
|
@@ -1102,15 +1209,19 @@ def _format_work(
|
|
|
1102
1209
|
# Extract assignee - check both assignee_id and assignee object
|
|
1103
1210
|
assignee = item.get("assignee_id")
|
|
1104
1211
|
if not assignee and item.get("assignee"):
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
)
|
|
1212
|
+
assignee_obj = item.get("assignee", {})
|
|
1213
|
+
assignee = assignee_obj.get("name") or assignee_obj.get("id")
|
|
1108
1214
|
if not assignee:
|
|
1109
1215
|
assignee = "N/A"
|
|
1110
1216
|
|
|
1217
|
+
# Format title line with key if available
|
|
1218
|
+
if key:
|
|
1219
|
+
lines.append(f"🎯 **[{key}] {title}**")
|
|
1220
|
+
else:
|
|
1221
|
+
lines.append(f"🎯 **{title}**")
|
|
1222
|
+
|
|
1111
1223
|
lines.extend(
|
|
1112
1224
|
[
|
|
1113
|
-
f"🎯 **{title}**",
|
|
1114
1225
|
f"ID: {item_id}",
|
|
1115
1226
|
f"Type: {item_type}",
|
|
1116
1227
|
f"Status: {status}",
|
|
@@ -1118,6 +1229,11 @@ def _format_work(
|
|
|
1118
1229
|
f"Assignee: {assignee}",
|
|
1119
1230
|
]
|
|
1120
1231
|
)
|
|
1232
|
+
|
|
1233
|
+
# Add key as separate line if present (for easy reference)
|
|
1234
|
+
if key:
|
|
1235
|
+
lines.append(f"Key: {key}")
|
|
1236
|
+
|
|
1121
1237
|
if item.get("due_date") or item.get("dueDate"):
|
|
1122
1238
|
lines.append(
|
|
1123
1239
|
f"Due date: {_format_date(item.get('due_date') or item.get('dueDate'))}"
|
|
@@ -72,6 +72,12 @@ class KnowledgeService:
|
|
|
72
72
|
async def work_get(self, work_id: str) -> Dict[str, Any]:
|
|
73
73
|
return await self._call_dict(self.api.get_work_item, work_id)
|
|
74
74
|
|
|
75
|
+
async def work_get_by_key(self, key: str) -> Dict[str, Any]:
|
|
76
|
+
return await self._call_dict(self.api.get_work_item_by_key, key)
|
|
77
|
+
|
|
78
|
+
async def work_assign_to_me(self, work_id: str) -> Dict[str, Any]:
|
|
79
|
+
return await self._call_dict(self.api.assign_work_item_to_me, work_id)
|
|
80
|
+
|
|
75
81
|
async def work_update(
|
|
76
82
|
self, work_id: str, payload: Dict[str, Any]
|
|
77
83
|
) -> Dict[str, Any]:
|
|
@@ -175,6 +181,11 @@ class KnowledgeService:
|
|
|
175
181
|
async def work_bulk_update(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
176
182
|
return await self._call(self.api.bulk_update_work_items, _strip_none(payload))
|
|
177
183
|
|
|
184
|
+
async def work_bulk_create(self, payload: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
185
|
+
return await self._call_list(
|
|
186
|
+
self.api.bulk_create_work_items, _strip_none(payload)
|
|
187
|
+
)
|
|
188
|
+
|
|
178
189
|
# ------------------------------------------------------------------
|
|
179
190
|
# Work boards
|
|
180
191
|
# ------------------------------------------------------------------
|
|
@@ -492,6 +492,12 @@ class FenixApiClient:
|
|
|
492
492
|
def get_work_item(self, item_id: str) -> Any:
|
|
493
493
|
return self._request("GET", f"/api/work-items/{item_id}")
|
|
494
494
|
|
|
495
|
+
def get_work_item_by_key(self, key: str) -> Any:
|
|
496
|
+
return self._request("GET", f"/api/work-items/by-key/{key}")
|
|
497
|
+
|
|
498
|
+
def assign_work_item_to_me(self, item_id: str) -> Any:
|
|
499
|
+
return self._request("POST", f"/api/work-items/{item_id}/assign-to-me")
|
|
500
|
+
|
|
495
501
|
def update_work_item(self, item_id: str, payload: Mapping[str, Any]) -> Any:
|
|
496
502
|
return self._request("PATCH", f"/api/work-items/{item_id}", json=payload)
|
|
497
503
|
|
|
@@ -567,6 +573,9 @@ class FenixApiClient:
|
|
|
567
573
|
def bulk_update_work_items(self, payload: Mapping[str, Any]) -> Any:
|
|
568
574
|
return self._request("PATCH", "/api/work-items/bulk-update", json=payload)
|
|
569
575
|
|
|
576
|
+
def bulk_create_work_items(self, payload: Mapping[str, Any]) -> Any:
|
|
577
|
+
return self._request("POST", "/api/work-items/bulk-create", json=payload)
|
|
578
|
+
|
|
570
579
|
# ------------------------------------------------------------------
|
|
571
580
|
# Knowledge: boards
|
|
572
581
|
# ------------------------------------------------------------------
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|