-
Notifications
You must be signed in to change notification settings - Fork 1
feat(workitems): extend manage_work_item with date, time tracking, and tier-specific widgets #135
Description
Summary
Extend manage_work_item with additional widget parameters and linked items management that GitLab API supports but our tool currently doesn't expose.
Current State
manage_work_item supports: title, description, state, assigneeIds, labelIds, milestoneId
browse_work_items already fetches linkedItems { nodes { linkType, workItem } } in GraphQL queries — but there's no way to create or remove links.
Target State — New Parameters
Free Tier (CE)
| Parameter | Widget Input | Type | Description |
|---|---|---|---|
startDate |
startAndDueDateWidget |
Date (YYYY-MM-DD) |
Start date |
dueDate |
startAndDueDateWidget |
Date (YYYY-MM-DD) |
Due date |
parentId |
hierarchyWidget |
ID |
Set/remove parent work item (use null to unlink) |
childrenIds |
hierarchyWidget |
ID[] |
Add children work items |
timeEstimate |
timeTrackingWidget |
String ("1h 30m") |
Time estimate in human-readable format |
timeSpent |
timeTrackingWidget.timelog |
String ("2h") |
Log time spent (adds timelog entry) |
timeSpentAt |
timeTrackingWidget.timelog |
DateTime (ISO 8601) |
When time was spent (optional, defaults to now) |
timeSpentSummary |
timeTrackingWidget.timelog |
String |
Summary of work done (optional) |
Premium Tier (EE)
| Parameter | Widget Input | Type | Description |
|---|---|---|---|
isFixed |
startAndDueDateWidget |
Boolean |
Fixed dates (not inherited from children) |
weight |
weightWidget |
Integer |
Story points / weight |
iterationId |
iterationWidget |
ID |
Assign to iteration/sprint |
progressCurrentValue |
progressWidget |
Integer |
Current progress value (OKR) |
Ultimate Tier (EE)
| Parameter | Widget Input | Type | Description |
|---|---|---|---|
healthStatus |
healthStatusWidget |
Enum (onTrack, needsAttention, atRisk) |
Health status |
color |
colorWidget |
String (hex) |
Custom color (epics) |
Target State — Linked Items (New Actions)
Problem
GitLab Work Items support relationship links (blocks/blocked_by/relates_to) via separate mutations workItemAddLinkedItems and workItemRemoveLinkedItems. Our tool has read support (browse_work_items returns linkedItems) but no write support.
New Actions in manage_work_item
| Action | Description |
|---|---|
add_link |
Add a relationship link between two work items |
remove_link |
Remove a relationship link between two work items |
Link Types
| linkType | GraphQL Enum | Description |
|---|---|---|
BLOCKS |
BLOCKS |
This item blocks the target |
IS_BLOCKED_BY |
IS_BLOCKED_BY |
This item is blocked by the target |
RELATES_TO |
RELATED |
General relationship |
Parameters for add_link
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Source work item ID (numeric) |
targetId |
string | Yes | Target work item ID to link to (numeric) |
linkType |
enum | Yes | Relationship type: BLOCKS, IS_BLOCKED_BY, RELATES_TO |
Parameters for remove_link
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Source work item ID (numeric) |
targetId |
string | Yes | Target work item ID to unlink (numeric) |
linkType |
enum | Yes | Relationship type to remove: BLOCKS, IS_BLOCKED_BY, RELATES_TO |
GraphQL Mutations
# Add link
mutation workItemAddLinkedItems($input: WorkItemAddLinkedItemsInput!) {
workItemAddLinkedItems(input: $input) {
workItem { id iid title }
errors
message
}
}
# Input:
# { id: "gid://gitlab/WorkItem/123", workItemsIds: ["gid://gitlab/WorkItem/456"], linkType: BLOCKS }
# Remove link
mutation workItemRemoveLinkedItems($input: WorkItemRemoveLinkedItemsInput!) {
workItemRemoveLinkedItems(input: $input) {
workItem { id iid title }
errors
message
}
}
# Input:
# { id: "gid://gitlab/WorkItem/123", workItemsIds: ["gid://gitlab/WorkItem/456"], linkType: BLOCKS }Tier Requirement
Linked items are Free tier — available on all GitLab instances (CE and EE).
Hierarchy Widget Details
The hierarchyWidget also supports reordering:
adjacentWorkItemId— ID of sibling to position relative torelativePosition—BEFOREorAFTER
This enables setting parent (linking Issue->Epic), adding children, and reordering within a parent.
Documentation Updates Required
1. docs/TOOLS.md (auto-generated)
After implementation, regenerate with yarn list-tools --export --toc > docs/TOOLS.md. The following changes will appear:
manage_work_item section — new actions and parameters:
- Add
add_linkandremove_linkto actions table - Add all new widget parameters to
createandupdateaction tables - Add tier badges for Premium/Ultimate parameters
browse_work_items section — verify linkedItems appear in response docs (already fetched by GraphQL).
2. docs/tools/index.md
Update the work items tool summary table:
| `manage_work_item` | Command | Create, update, delete, link/unlink work items |3. Schema .describe() annotations (CRITICAL for auto-docs)
Every new parameter MUST have proper .describe() in Zod schema for doc generation:
startDate: z.string().optional().describe("Start date in YYYY-MM-DD format"),
linkType: z.enum(["BLOCKS", "IS_BLOCKED_BY", "RELATES_TO"]).describe("Relationship type between work items"),4. docs/advanced/ (if exists) or README
Add a "Work Item Relationships" section explaining:
- How to use
add_link/remove_linkactions - linkType semantics (BLOCKS means "this item blocks target")
- How linked items appear in
browse_work_itemsresponse
Integration Tests
Data Lifecycle Pattern
Follow the project's data lifecycle approach — create test infrastructure once, share across tests:
1. SETUP (beforeAll):
- Create test Epic (group level)
- Create test Issue (project level)
- Create 2 additional test Issues (for linking tests)
- Create test Task (project level)
- Create test Iteration (if Premium)
- Store IDs for dependent tests
2. TEST CASES (sequential, --runInBand):
// === Dates ===
- Create issue with startDate + dueDate -> verify in response
- Update issue: change dueDate -> verify updated
- Update issue: clear startDate (null) -> verify removed
- Create epic with isFixed=true (Premium) -> verify
// === Hierarchy ===
- Update task: set parentId to issue -> verify parent in response
- Update issue: set parentId to epic -> verify hierarchy
- Update issue: add childrenIds [task] -> verify children
- Update task: set parentId to null -> verify unlinked
- Reorder: adjacentWorkItemId + relativePosition
// === Linked Items (NEW) ===
- add_link: issue1 BLOCKS issue2 -> verify in browse_work_items response
- add_link: issue1 RELATES_TO issue3 -> verify both links exist
- browse issue2 -> verify IS_BLOCKED_BY appears (reverse link)
- remove_link: issue1 BLOCKS issue2 -> verify removed
- add_link: issue2 IS_BLOCKED_BY issue3 -> verify
- remove_link: issue2 IS_BLOCKED_BY issue3 -> verify removed
- Verify all links cleaned up (no orphan links)
// === Time Tracking ===
- Update issue: set timeEstimate "4h" -> verify humanTimeEstimate
- Update issue: add timeSpent "1h 30m" with summary -> verify totalTimeSpent
- Update issue: add timeSpent "2h" with spentAt -> verify accumulated
- Update issue: reset timeEstimate "0h" -> verify cleared
// === Weight (Premium) ===
- Update issue: set weight=5 -> verify
- Update issue: clear weight (null) -> verify removed
// === Iteration (Premium) ===
- Update issue: set iterationId -> verify
- Update issue: clear iterationId -> verify removed
// === Health Status (Ultimate) ===
- Update issue: set healthStatus="needsAttention" -> verify
- Update issue: set healthStatus="onTrack" -> verify changed
// === Color (Epics, Ultimate) ===
- Update epic: set color="#FF5733" -> verify
- Update epic: change color="#00AA00" -> verify
// === Progress (OKR, Premium) ===
- Create Objective + Key Result
- Update Key Result: set progressCurrentValue=50 -> verify
- Update Key Result: set progressCurrentValue=100 -> verify
3. TEARDOWN (afterAll):
- Remove all links first (remove_link)
- Delete test work items in reverse order (children first)
Edge Cases (Unit Tests)
// Validation errors
- Invalid date format "2024-13-45" -> expect validation error
- parentId pointing to wrong level (issue as parent of epic) -> expect hierarchy error
- timeEstimate invalid format ("abc") -> expect parsing error
- weight negative number -> expect validation error
- Widget parameters on Free tier when Premium required -> expect tier error (after #136)
// Linked items edge cases
- add_link with same source and target ID -> expect error
- add_link duplicate (same link already exists) -> expect graceful handling (idempotent or error)
- remove_link that doesn't exist -> expect graceful handling
- add_link with invalid linkType -> expect Zod validation error
- add_link with non-existent target ID -> expect API error with clear message
Schema Validation
- All new widget fields in GraphQL response must be typed
- Verify response includes widget data after create/update
- Validate against real API responses (dynamic assertions, no hardcoded IDs)
- Linked items response: verify
linkTypeandworkItemstructure
Implementation Notes
Widget Parameters (update/create)
- Parameters should be added to both
createandupdateaction schemas - GraphQL mutations already return these widgets in browse queries (types + queries exist in
src/graphql/workItems.ts) - Widget interface types already defined:
WorkItemStartAndDueDateWidget,WorkItemTimeTrackingWidget,WorkItemWeightWidget,WorkItemHealthStatusWidget,WorkItemProgressWidget,WorkItemColorWidget,WorkItemHierarchyWidget UPDATE_WORK_ITEMGraphQL mutation needs to be extended with new widget fragments in responsehierarchyWidgetinput usesparent_id(nullable) +children_ids(array)
Linked Items (add_link/remove_link)
- Uses SEPARATE GraphQL mutations (
workItemAddLinkedItems/workItemRemoveLinkedItems), NOT widget input on update - Must normalize numeric IDs to GIDs before mutation (same pattern as other tools)
WorkItemLinkedItemsWidgetinterface needs update: nodes should includelinkType: stringfield- GraphQL already fetches
linkTypein queries (line 775) but interface (line 248-253) doesn't type it - Response should return updated work item with all current links
Files to Modify
| File | Changes |
|---|---|
src/entities/work-items/schema.ts |
Add new params to create/update schemas, add add_link/remove_link action schemas |
src/entities/work-items/handler.ts |
Handle new widget params in mutation builder, add link handlers |
src/graphql/workItems.ts |
Add WORK_ITEM_ADD_LINKED_ITEMS and WORK_ITEM_REMOVE_LINKED_ITEMS mutations, fix WorkItemLinkedItemsWidget interface |
src/entities/work-items/registry.ts |
Update tool description to mention linking |
src/__tests__/integration/work-items-widgets.test.ts |
New integration test file |
src/__tests__/unit/work-items/ |
Unit tests for new schema/handler logic |
docs/TOOLS.md |
Auto-regenerated after implementation |
docs/tools/index.md |
Update summary table |
GitLab API Source
Widget Inputs (update mutation)
- CE widgets:
app/graphql/mutations/work_items/update.rb - EE widgets:
ee/app/graphql/ee/mutations/work_items/update.rb - Hierarchy input:
hierarchy_update_input_type.rb—parentId,childrenIds,adjacentWorkItemId,relativePosition - Start/due date input:
start_and_due_date_update_input_type.rb—startDate,dueDate - Start/due date EE:
ee/.../start_and_due_date_update_input_type.rb— addsisFixed - Time tracking input:
time_tracking/time_tracking_input_type.rb—timeEstimate,timelog - Timelog input:
time_tracking/timelog_input_type.rb—timeSpent(required),spentAt,summary - Weight input:
ee/.../weight_input_type.rb—weight - Iteration input:
ee/.../iteration_input_type.rb—iterationId - Health status input:
ee/.../health_status_input_type.rb—healthStatus - Progress input:
ee/.../progress_input_type.rb—currentValue(required),startValue,endValue - Color input:
ee/.../color_input_type.rb—color
Linked Items (separate mutations)
- Add linked items:
app/graphql/mutations/work_items/linked_items/add.rb - Remove linked items:
app/graphql/mutations/work_items/linked_items/remove.rb - Link type enum:
RELATED,BLOCKS,IS_BLOCKED_BY - Input:
id(source GID),workItemsIds(array of target GIDs),linkType(enum)
Depends On
- feat(availability): extend tier matrix with per-parameter gating #136 (per-parameter tier gating in schema)
- feat(availability): use WidgetAvailability for version-based widget validation #137 (version-based widget validation)