efficient-gitlab-mcp-server 2.3.0 → 2.5.0
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/dist/index.js +1423 -23
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -53296,6 +53296,10 @@ var CATEGORIES = [
|
|
|
53296
53296
|
name: "webhooks",
|
|
53297
53297
|
description: "List project webhooks and their recent events."
|
|
53298
53298
|
},
|
|
53299
|
+
{
|
|
53300
|
+
name: "work-items",
|
|
53301
|
+
description: "Work items via GraphQL: create, update, convert types, manage hierarchy, notes, statuses, custom fields, and incident timeline events."
|
|
53302
|
+
},
|
|
53299
53303
|
{
|
|
53300
53304
|
name: "graphql",
|
|
53301
53305
|
description: "Execute arbitrary GraphQL queries against the GitLab API."
|
|
@@ -54119,6 +54123,27 @@ function registerIssueTools(server, logger2) {
|
|
|
54119
54123
|
return tools;
|
|
54120
54124
|
}
|
|
54121
54125
|
// src/tools/merge-requests.ts
|
|
54126
|
+
var MAX_PATTERN_LENGTH = 200;
|
|
54127
|
+
var NESTED_QUANTIFIER_RE = /(\+|\*|\{)\s*(\+|\*|\{)/;
|
|
54128
|
+
function safeCompilePatterns(patterns) {
|
|
54129
|
+
return patterns.map((p) => {
|
|
54130
|
+
if (p.length > MAX_PATTERN_LENGTH || NESTED_QUANTIFIER_RE.test(p))
|
|
54131
|
+
return null;
|
|
54132
|
+
try {
|
|
54133
|
+
return new RegExp(p);
|
|
54134
|
+
} catch {
|
|
54135
|
+
return null;
|
|
54136
|
+
}
|
|
54137
|
+
}).filter((re) => re !== null);
|
|
54138
|
+
}
|
|
54139
|
+
function filterDiffsByPatterns(diffs, patterns) {
|
|
54140
|
+
if (!patterns?.length)
|
|
54141
|
+
return diffs;
|
|
54142
|
+
const regexes = safeCompilePatterns(patterns);
|
|
54143
|
+
if (regexes.length === 0)
|
|
54144
|
+
return diffs;
|
|
54145
|
+
return diffs.filter((d) => !regexes.some((re) => re.test(d.new_path) || (d.old_path ? re.test(d.old_path) : false)));
|
|
54146
|
+
}
|
|
54122
54147
|
var GetMergeRequestSchema = exports_external.object({
|
|
54123
54148
|
project_id: exports_external.string().describe("Project ID or URL-encoded path"),
|
|
54124
54149
|
merge_request_iid: exports_external.number().optional().describe("Merge request IID"),
|
|
@@ -54173,7 +54198,8 @@ var GetMergeRequestDiffsSchema = exports_external.object({
|
|
|
54173
54198
|
project_id: exports_external.string().describe("Project ID or URL-encoded path"),
|
|
54174
54199
|
merge_request_iid: exports_external.number().describe("Merge request IID"),
|
|
54175
54200
|
page: exports_external.number().optional().describe("Page number"),
|
|
54176
|
-
per_page: exports_external.number().optional().describe("Results per page")
|
|
54201
|
+
per_page: exports_external.number().optional().describe("Results per page"),
|
|
54202
|
+
excluded_file_patterns: exports_external.array(exports_external.string()).optional().describe('Array of regex patterns to exclude files. Examples: ["^vendor/", "\\.pb\\.go$"]')
|
|
54177
54203
|
});
|
|
54178
54204
|
var ListMergeRequestDiscussionsSchema = exports_external.object({
|
|
54179
54205
|
project_id: exports_external.string().describe("Project ID or URL-encoded path"),
|
|
@@ -54461,20 +54487,24 @@ function registerMergeRequestTools(server, logger2) {
|
|
|
54461
54487
|
tools.set("merge_merge_request", toolRef5);
|
|
54462
54488
|
const toolRef6 = server.registerTool("get_merge_request_diffs", {
|
|
54463
54489
|
title: "Get Merge Request Diffs",
|
|
54464
|
-
description: "Get the changes/diffs of a merge request",
|
|
54490
|
+
description: "Get the changes/diffs of a merge request. Supports excluded_file_patterns filtering using regex.",
|
|
54465
54491
|
inputSchema: {
|
|
54466
54492
|
project_id: exports_external.string().describe("Project ID or URL-encoded path"),
|
|
54467
54493
|
merge_request_iid: exports_external.number().describe("Merge request IID"),
|
|
54468
54494
|
page: exports_external.number().optional().describe("Page number"),
|
|
54469
|
-
per_page: exports_external.number().optional().describe("Results per page")
|
|
54495
|
+
per_page: exports_external.number().optional().describe("Results per page"),
|
|
54496
|
+
excluded_file_patterns: exports_external.array(exports_external.string()).optional().describe('Array of regex patterns to exclude files. Examples: ["^vendor/", "\\.pb\\.go$"]')
|
|
54470
54497
|
},
|
|
54471
54498
|
annotations: { readOnlyHint: true }
|
|
54472
54499
|
}, async (params) => {
|
|
54473
54500
|
const args = GetMergeRequestDiffsSchema.parse(params);
|
|
54474
54501
|
const projectId = encodeProjectId(args.project_id);
|
|
54475
54502
|
const query = buildQueryString({ page: args.page, per_page: args.per_page });
|
|
54476
|
-
const
|
|
54477
|
-
|
|
54503
|
+
const mr = await defaultClient.get(`/projects/${projectId}/merge_requests/${args.merge_request_iid}/changes${query}`);
|
|
54504
|
+
const changes = filterDiffsByPatterns(mr.changes ?? [], args.excluded_file_patterns);
|
|
54505
|
+
return {
|
|
54506
|
+
content: [{ type: "text", text: JSON.stringify({ ...mr, changes }, null, 2) }]
|
|
54507
|
+
};
|
|
54478
54508
|
});
|
|
54479
54509
|
toolRef6.disable();
|
|
54480
54510
|
tools.set("get_merge_request_diffs", toolRef6);
|
|
@@ -54704,24 +54734,7 @@ function registerMergeRequestTools(server, logger2) {
|
|
|
54704
54734
|
renamed_file: c.renamed_file,
|
|
54705
54735
|
deleted_file: c.deleted_file
|
|
54706
54736
|
}));
|
|
54707
|
-
|
|
54708
|
-
const patterns = [];
|
|
54709
|
-
for (const p of args.excluded_file_patterns) {
|
|
54710
|
-
try {
|
|
54711
|
-
patterns.push(new RegExp(p));
|
|
54712
|
-
} catch {
|
|
54713
|
-
return {
|
|
54714
|
-
content: [
|
|
54715
|
-
{
|
|
54716
|
-
type: "text",
|
|
54717
|
-
text: JSON.stringify({ error: `Invalid regex pattern: ${p}` }, null, 2)
|
|
54718
|
-
}
|
|
54719
|
-
]
|
|
54720
|
-
};
|
|
54721
|
-
}
|
|
54722
|
-
}
|
|
54723
|
-
files = files.filter((f) => !patterns.some((re) => re.test(f.new_path) || re.test(f.old_path)));
|
|
54724
|
-
}
|
|
54737
|
+
files = filterDiffsByPatterns(files, args.excluded_file_patterns);
|
|
54725
54738
|
return { content: [{ type: "text", text: JSON.stringify(files, null, 2) }] };
|
|
54726
54739
|
});
|
|
54727
54740
|
toolRef18.disable();
|
|
@@ -57624,6 +57637,1392 @@ function registerWikiTools(server, logger2) {
|
|
|
57624
57637
|
logger2.debug("Wiki tools registered", { count: tools.size });
|
|
57625
57638
|
return tools;
|
|
57626
57639
|
}
|
|
57640
|
+
// src/tools/work-items.ts
|
|
57641
|
+
var workItemTypeEnum = exports_external.string().transform((v) => v.toLowerCase()).pipe(exports_external.enum([
|
|
57642
|
+
"issue",
|
|
57643
|
+
"task",
|
|
57644
|
+
"incident",
|
|
57645
|
+
"test_case",
|
|
57646
|
+
"epic",
|
|
57647
|
+
"key_result",
|
|
57648
|
+
"objective",
|
|
57649
|
+
"requirement",
|
|
57650
|
+
"ticket"
|
|
57651
|
+
]));
|
|
57652
|
+
var WORK_ITEM_TYPE_NAMES = {
|
|
57653
|
+
issue: "Issue",
|
|
57654
|
+
task: "Task",
|
|
57655
|
+
incident: "Incident",
|
|
57656
|
+
test_case: "Test Case",
|
|
57657
|
+
epic: "Epic",
|
|
57658
|
+
key_result: "Key Result",
|
|
57659
|
+
objective: "Objective",
|
|
57660
|
+
requirement: "Requirement",
|
|
57661
|
+
ticket: "Ticket"
|
|
57662
|
+
};
|
|
57663
|
+
var WorkItemParamsSchema = exports_external.object({
|
|
57664
|
+
project_id: exports_external.coerce.string().describe("Project ID or URL-encoded path"),
|
|
57665
|
+
iid: exports_external.coerce.number().describe("The internal ID (IID) of the work item")
|
|
57666
|
+
});
|
|
57667
|
+
var GetWorkItemSchema = WorkItemParamsSchema;
|
|
57668
|
+
var ListWorkItemsSchema = exports_external.object({
|
|
57669
|
+
project_id: exports_external.coerce.string().describe("Project ID or URL-encoded path"),
|
|
57670
|
+
types: exports_external.array(workItemTypeEnum).optional().describe("Filter by work item types. If not set, returns all types."),
|
|
57671
|
+
state: exports_external.enum(["opened", "closed"]).optional().describe("Filter by state"),
|
|
57672
|
+
search: exports_external.string().optional().describe("Search in title and description"),
|
|
57673
|
+
assignee_usernames: exports_external.array(exports_external.string()).optional().describe("Filter by assignee usernames"),
|
|
57674
|
+
label_names: exports_external.array(exports_external.string()).optional().describe("Filter by label names"),
|
|
57675
|
+
first: exports_external.coerce.number().optional().default(20).describe("Number of items to return (max 100). Default 20."),
|
|
57676
|
+
after: exports_external.string().optional().describe("Cursor for pagination (from previous response's endCursor)")
|
|
57677
|
+
});
|
|
57678
|
+
var CreateWorkItemSchema = exports_external.object({
|
|
57679
|
+
project_id: exports_external.coerce.string().describe("Project ID or URL-encoded path"),
|
|
57680
|
+
title: exports_external.string().describe("Title of the work item"),
|
|
57681
|
+
type: workItemTypeEnum.optional().default("issue").describe("Type of work item to create. Defaults to 'issue'."),
|
|
57682
|
+
description: exports_external.string().optional().describe("Description of the work item (Markdown supported)"),
|
|
57683
|
+
labels: exports_external.array(exports_external.string()).optional().describe("Array of label names to assign"),
|
|
57684
|
+
assignee_usernames: exports_external.array(exports_external.string()).optional().describe("Array of usernames to assign"),
|
|
57685
|
+
parent_iid: exports_external.coerce.number().optional().describe("IID of the parent work item to set hierarchy"),
|
|
57686
|
+
weight: exports_external.coerce.number().optional().describe("Weight of the work item"),
|
|
57687
|
+
health_status: exports_external.enum(["onTrack", "needsAttention", "atRisk"]).optional().describe("Set health status"),
|
|
57688
|
+
start_date: exports_external.string().optional().describe("Start date in YYYY-MM-DD format"),
|
|
57689
|
+
due_date: exports_external.string().optional().describe("Due date in YYYY-MM-DD format"),
|
|
57690
|
+
milestone_id: exports_external.string().optional().describe("Milestone ID (GitLab global ID format, e.g. 'gid://gitlab/Milestone/123', or numeric ID)"),
|
|
57691
|
+
iteration_id: exports_external.string().optional().describe("Iteration ID (e.g. 'gid://gitlab/Iteration/123' or numeric ID). Use list_group_iterations to find available iterations."),
|
|
57692
|
+
confidential: exports_external.coerce.boolean().optional().describe("Set confidentiality")
|
|
57693
|
+
});
|
|
57694
|
+
var UpdateWorkItemSchema = WorkItemParamsSchema.extend({
|
|
57695
|
+
title: exports_external.string().optional().describe("New title"),
|
|
57696
|
+
description: exports_external.string().optional().describe("New description (Markdown supported)"),
|
|
57697
|
+
add_labels: exports_external.array(exports_external.string()).optional().describe("Label names to add"),
|
|
57698
|
+
remove_labels: exports_external.array(exports_external.string()).optional().describe("Label names to remove"),
|
|
57699
|
+
assignee_usernames: exports_external.array(exports_external.string()).optional().describe("Set assignees by username (replaces existing)"),
|
|
57700
|
+
state_event: exports_external.enum(["close", "reopen"]).optional().describe("Close or reopen the work item"),
|
|
57701
|
+
weight: exports_external.coerce.number().optional().describe("Set weight (issues, tasks, epics only)"),
|
|
57702
|
+
status: exports_external.string().optional().describe("Set status by ID. Use list_work_item_statuses to get available status IDs."),
|
|
57703
|
+
parent_iid: exports_external.coerce.number().optional().describe("Set parent work item by IID. Use with parent_project_id if parent is in a different project."),
|
|
57704
|
+
parent_project_id: exports_external.coerce.string().optional().describe("Project ID or path of the parent work item (defaults to same project as the work item)"),
|
|
57705
|
+
remove_parent: exports_external.coerce.boolean().optional().describe("Set to true to remove the parent from hierarchy"),
|
|
57706
|
+
children_to_add: exports_external.array(exports_external.object({
|
|
57707
|
+
project_id: exports_external.coerce.string().describe("Project ID or path of the child work item"),
|
|
57708
|
+
iid: exports_external.coerce.number().describe("IID of the child work item")
|
|
57709
|
+
})).optional().describe("Array of children to add to this work item's hierarchy"),
|
|
57710
|
+
children_to_remove: exports_external.array(exports_external.object({
|
|
57711
|
+
project_id: exports_external.coerce.string().describe("Project ID or path of the child work item"),
|
|
57712
|
+
iid: exports_external.coerce.number().describe("IID of the child work item")
|
|
57713
|
+
})).optional().describe("Array of children to remove from this work item's hierarchy"),
|
|
57714
|
+
health_status: exports_external.enum(["onTrack", "needsAttention", "atRisk"]).optional().describe("Set health status on issues and epics"),
|
|
57715
|
+
start_date: exports_external.string().optional().describe("Start date in YYYY-MM-DD format"),
|
|
57716
|
+
due_date: exports_external.string().optional().describe("Due date in YYYY-MM-DD format"),
|
|
57717
|
+
milestone_id: exports_external.string().optional().describe("Milestone ID (GitLab global ID format, e.g. 'gid://gitlab/Milestone/123', or numeric ID)"),
|
|
57718
|
+
iteration_id: exports_external.string().optional().describe("Iteration ID (e.g. 'gid://gitlab/Iteration/123' or numeric ID). Use list_group_iterations to find available iterations."),
|
|
57719
|
+
confidential: exports_external.coerce.boolean().optional().describe("Set confidentiality"),
|
|
57720
|
+
linked_items_to_add: exports_external.array(exports_external.object({
|
|
57721
|
+
project_id: exports_external.coerce.string().describe("Project ID or path of the work item to link"),
|
|
57722
|
+
iid: exports_external.coerce.number().describe("IID of the work item to link"),
|
|
57723
|
+
link_type: exports_external.enum(["RELATED", "BLOCKED_BY", "BLOCKS"]).optional().default("RELATED").describe("Link type: RELATED, BLOCKED_BY, or BLOCKS. Defaults to RELATED.")
|
|
57724
|
+
})).optional().describe("Work items to link"),
|
|
57725
|
+
linked_items_to_remove: exports_external.array(exports_external.object({
|
|
57726
|
+
project_id: exports_external.coerce.string().describe("Project ID or path of the linked work item to remove"),
|
|
57727
|
+
iid: exports_external.coerce.number().describe("IID of the linked work item to remove")
|
|
57728
|
+
})).optional().describe("Linked work items to remove"),
|
|
57729
|
+
custom_fields: exports_external.array(exports_external.object({
|
|
57730
|
+
custom_field_id: exports_external.string().describe("Custom field ID (e.g. 'gid://gitlab/IssuablesCustomField/123' or numeric ID)"),
|
|
57731
|
+
text_value: exports_external.string().optional().describe("Text value (for text fields)"),
|
|
57732
|
+
number_value: exports_external.coerce.number().optional().describe("Number value (for number fields)"),
|
|
57733
|
+
selected_option_ids: exports_external.array(exports_external.string()).optional().describe("Selected option IDs (for select fields)"),
|
|
57734
|
+
date_value: exports_external.string().optional().describe("Date value in YYYY-MM-DD format (for date fields)")
|
|
57735
|
+
})).optional().describe("Custom field values to set"),
|
|
57736
|
+
severity: exports_external.enum(["UNKNOWN", "LOW", "MEDIUM", "HIGH", "CRITICAL"]).optional().describe("Incident only: set severity level"),
|
|
57737
|
+
escalation_status: exports_external.enum(["TRIGGERED", "ACKNOWLEDGED", "RESOLVED", "IGNORED"]).optional().describe("Incident only: set escalation status")
|
|
57738
|
+
});
|
|
57739
|
+
var ConvertWorkItemTypeSchema = exports_external.object({
|
|
57740
|
+
project_id: exports_external.coerce.string().describe("Project ID or URL-encoded path"),
|
|
57741
|
+
iid: exports_external.coerce.number().describe("The internal ID of the work item"),
|
|
57742
|
+
new_type: workItemTypeEnum.describe("The target work item type to convert to")
|
|
57743
|
+
});
|
|
57744
|
+
var ListWorkItemStatusesSchema = exports_external.object({
|
|
57745
|
+
project_id: exports_external.coerce.string().describe("Project ID or URL-encoded path"),
|
|
57746
|
+
work_item_type: workItemTypeEnum.optional().default("issue").describe("The work item type to list available statuses for. Defaults to 'issue'.")
|
|
57747
|
+
});
|
|
57748
|
+
var ListWorkItemNotesSchema = exports_external.object({
|
|
57749
|
+
project_id: exports_external.coerce.string().describe("Project ID or URL-encoded path"),
|
|
57750
|
+
iid: exports_external.coerce.number().describe("The internal ID of the work item"),
|
|
57751
|
+
page_size: exports_external.coerce.number().optional().default(20).describe("Number of discussions to return (default 20)"),
|
|
57752
|
+
after: exports_external.string().optional().describe("Cursor for pagination"),
|
|
57753
|
+
sort: exports_external.enum(["CREATED_ASC", "CREATED_DESC"]).optional().default("CREATED_ASC").describe("Sort order for discussions")
|
|
57754
|
+
});
|
|
57755
|
+
var CreateWorkItemNoteSchema = exports_external.object({
|
|
57756
|
+
project_id: exports_external.coerce.string().describe("Project ID or URL-encoded path"),
|
|
57757
|
+
iid: exports_external.coerce.number().describe("The internal ID of the work item"),
|
|
57758
|
+
body: exports_external.string().describe("Note body (Markdown supported)"),
|
|
57759
|
+
internal: exports_external.coerce.boolean().optional().default(false).describe("Create as internal/confidential note (only visible to project members)"),
|
|
57760
|
+
discussion_id: exports_external.string().optional().describe("Discussion ID to reply to (for threaded replies). If omitted, creates a new top-level note.")
|
|
57761
|
+
});
|
|
57762
|
+
var MoveWorkItemSchema = exports_external.object({
|
|
57763
|
+
project_id: exports_external.coerce.string().describe("Project ID or URL-encoded path of the source project"),
|
|
57764
|
+
iid: exports_external.coerce.number().describe("The internal ID of the work item to move"),
|
|
57765
|
+
target_project_id: exports_external.coerce.string().describe("Project ID or URL-encoded path of the target project")
|
|
57766
|
+
});
|
|
57767
|
+
var ListCustomFieldDefinitionsSchema = exports_external.object({
|
|
57768
|
+
project_id: exports_external.coerce.string().describe("Project ID or URL-encoded path"),
|
|
57769
|
+
work_item_type: workItemTypeEnum.optional().default("issue").describe("The work item type to list custom field definitions for. Defaults to 'issue'.")
|
|
57770
|
+
});
|
|
57771
|
+
var GetTimelineEventsSchema = exports_external.object({
|
|
57772
|
+
project_id: exports_external.coerce.string().describe("Project ID or URL-encoded path"),
|
|
57773
|
+
incident_iid: exports_external.coerce.number().describe("The internal ID (IID) of the incident")
|
|
57774
|
+
});
|
|
57775
|
+
var CreateTimelineEventSchema = exports_external.object({
|
|
57776
|
+
project_id: exports_external.coerce.string().describe("Project ID or URL-encoded path"),
|
|
57777
|
+
incident_iid: exports_external.coerce.number().describe("The internal ID (IID) of the incident"),
|
|
57778
|
+
note: exports_external.string().describe("Description of the timeline event (Markdown supported)"),
|
|
57779
|
+
occurred_at: exports_external.string().describe("When the event occurred in ISO 8601 format (e.g. '2026-03-15T09:00:00.000Z')"),
|
|
57780
|
+
tag_names: exports_external.array(exports_external.enum([
|
|
57781
|
+
"Start time",
|
|
57782
|
+
"End time",
|
|
57783
|
+
"Impact detected",
|
|
57784
|
+
"Response initiated",
|
|
57785
|
+
"Impact mitigated",
|
|
57786
|
+
"Cause identified"
|
|
57787
|
+
])).optional().describe("Timeline event tags to attach. Available: 'Start time', 'End time', 'Impact detected', 'Response initiated', 'Impact mitigated', 'Cause identified'.")
|
|
57788
|
+
});
|
|
57789
|
+
async function resolveProjectPath(projectId) {
|
|
57790
|
+
const project = await defaultClient.get(`/projects/${encodeProjectId(projectId)}`);
|
|
57791
|
+
return project.path_with_namespace;
|
|
57792
|
+
}
|
|
57793
|
+
async function resolveWorkItemGID(projectId, issueIid) {
|
|
57794
|
+
const projectPath = await resolveProjectPath(projectId);
|
|
57795
|
+
const data = await defaultClient.graphql(`query($path: ID!, $iid: String!) {
|
|
57796
|
+
namespace(fullPath: $path) {
|
|
57797
|
+
workItem(iid: $iid) { id }
|
|
57798
|
+
}
|
|
57799
|
+
}`, { path: projectPath, iid: String(issueIid) });
|
|
57800
|
+
if (!data.namespace?.workItem?.id) {
|
|
57801
|
+
throw new Error(`Work item #${issueIid} not found in project ${projectPath}`);
|
|
57802
|
+
}
|
|
57803
|
+
return { workItemGID: data.namespace.workItem.id, projectPath };
|
|
57804
|
+
}
|
|
57805
|
+
async function resolveNamesToIds(projectPath, labelNames, usernames) {
|
|
57806
|
+
if (!labelNames?.length && !usernames?.length) {
|
|
57807
|
+
return { labelIds: [], userIds: [] };
|
|
57808
|
+
}
|
|
57809
|
+
const data = await defaultClient.graphql(`query($path: ID!, $usernames: [String!]!) {
|
|
57810
|
+
project(fullPath: $path) { labels(includeAncestorGroups: true, first: 250) { nodes { id title } } }
|
|
57811
|
+
users(usernames: $usernames) { nodes { id username } }
|
|
57812
|
+
}`, { path: projectPath, usernames: usernames || [] });
|
|
57813
|
+
const labelIds = (labelNames || []).map((name) => {
|
|
57814
|
+
const label = data.project.labels.nodes.find((l) => l.title === name);
|
|
57815
|
+
if (!label)
|
|
57816
|
+
throw new Error(`Label '${name}' not found in project`);
|
|
57817
|
+
return label.id;
|
|
57818
|
+
});
|
|
57819
|
+
const userIds = (usernames || []).map((name) => {
|
|
57820
|
+
const user = data.users.nodes.find((u) => u.username === name);
|
|
57821
|
+
if (!user)
|
|
57822
|
+
throw new Error(`User '${name}' not found`);
|
|
57823
|
+
return user.id;
|
|
57824
|
+
});
|
|
57825
|
+
return { labelIds, userIds };
|
|
57826
|
+
}
|
|
57827
|
+
async function resolveWorkItemTypeGID(projectPath, typeName) {
|
|
57828
|
+
const targetName = WORK_ITEM_TYPE_NAMES[typeName];
|
|
57829
|
+
if (!targetName)
|
|
57830
|
+
throw new Error(`Unknown work item type: ${typeName}`);
|
|
57831
|
+
const data = await defaultClient.graphql(`query($path: ID!) {
|
|
57832
|
+
namespace(fullPath: $path) {
|
|
57833
|
+
workItemTypes { nodes { id name } }
|
|
57834
|
+
}
|
|
57835
|
+
}`, { path: projectPath });
|
|
57836
|
+
const typeNode = data.namespace?.workItemTypes?.nodes?.find((n) => n.name === targetName);
|
|
57837
|
+
if (!typeNode) {
|
|
57838
|
+
throw new Error(`Work item type '${targetName}' not found in project ${projectPath}`);
|
|
57839
|
+
}
|
|
57840
|
+
return typeNode.id;
|
|
57841
|
+
}
|
|
57842
|
+
async function updateIncidentSeverity(projectPath, incidentIid, severity) {
|
|
57843
|
+
const data = await defaultClient.graphql(`mutation($projectPath: ID!, $severity: IssuableSeverity!, $iid: String!) {
|
|
57844
|
+
issueSetSeverity(input: { iid: $iid, severity: $severity, projectPath: $projectPath }) {
|
|
57845
|
+
errors
|
|
57846
|
+
}
|
|
57847
|
+
}`, { projectPath, severity, iid: String(incidentIid) });
|
|
57848
|
+
if (data.issueSetSeverity.errors?.length > 0) {
|
|
57849
|
+
throw new Error(`Failed to set severity: ${data.issueSetSeverity.errors.join(", ")}`);
|
|
57850
|
+
}
|
|
57851
|
+
}
|
|
57852
|
+
async function updateIncidentEscalationStatus(projectPath, incidentIid, status) {
|
|
57853
|
+
const data = await defaultClient.graphql(`mutation($projectPath: ID!, $status: IssueEscalationStatus!, $iid: String!) {
|
|
57854
|
+
issueSetEscalationStatus(input: { projectPath: $projectPath, status: $status, iid: $iid }) {
|
|
57855
|
+
errors
|
|
57856
|
+
}
|
|
57857
|
+
}`, { projectPath, status, iid: String(incidentIid) });
|
|
57858
|
+
if (data.issueSetEscalationStatus.errors?.length > 0) {
|
|
57859
|
+
throw new Error(`Failed to set escalation status: ${data.issueSetEscalationStatus.errors.join(", ")}`);
|
|
57860
|
+
}
|
|
57861
|
+
}
|
|
57862
|
+
async function removeIssueParent(projectId, issueIid) {
|
|
57863
|
+
const { workItemGID } = await resolveWorkItemGID(projectId, issueIid);
|
|
57864
|
+
const data = await defaultClient.graphql(`mutation($id: WorkItemID!) {
|
|
57865
|
+
workItemUpdate(input: { id: $id, hierarchyWidget: { parentId: null } }) {
|
|
57866
|
+
workItem { id }
|
|
57867
|
+
errors
|
|
57868
|
+
}
|
|
57869
|
+
}`, { id: workItemGID });
|
|
57870
|
+
if (data.workItemUpdate.errors?.length > 0) {
|
|
57871
|
+
throw new Error(`Failed to remove parent: ${data.workItemUpdate.errors.join(", ")}`);
|
|
57872
|
+
}
|
|
57873
|
+
}
|
|
57874
|
+
function toGID(id, type) {
|
|
57875
|
+
return id.startsWith("gid://") ? id : `gid://gitlab/${type}/${id}`;
|
|
57876
|
+
}
|
|
57877
|
+
async function getWorkItem(projectId, iid) {
|
|
57878
|
+
const projectPath = await resolveProjectPath(projectId);
|
|
57879
|
+
const data = await defaultClient.graphql(`query($path: ID!, $iid: String!) {
|
|
57880
|
+
namespace(fullPath: $path) {
|
|
57881
|
+
workItem(iid: $iid) {
|
|
57882
|
+
id iid title state description webUrl confidential
|
|
57883
|
+
author { username }
|
|
57884
|
+
createdAt closedAt
|
|
57885
|
+
workItemType { name }
|
|
57886
|
+
widgets {
|
|
57887
|
+
__typename
|
|
57888
|
+
... on WorkItemWidgetHierarchy {
|
|
57889
|
+
hasChildren hasParent
|
|
57890
|
+
parent { id iid title webUrl workItemType { name } namespace { fullPath } }
|
|
57891
|
+
children { nodes { id iid title state webUrl workItemType { name } namespace { fullPath } } }
|
|
57892
|
+
}
|
|
57893
|
+
... on WorkItemWidgetStatus { status { id name category color iconName position } }
|
|
57894
|
+
... on WorkItemWidgetCustomFields {
|
|
57895
|
+
customFieldValues {
|
|
57896
|
+
__typename
|
|
57897
|
+
customField { id name fieldType }
|
|
57898
|
+
... on WorkItemNumberFieldValue { value }
|
|
57899
|
+
... on WorkItemTextFieldValue { value }
|
|
57900
|
+
... on WorkItemSelectFieldValue { selectedOptions { id value } }
|
|
57901
|
+
}
|
|
57902
|
+
}
|
|
57903
|
+
... on WorkItemWidgetLabels { labels { nodes { id title color } } }
|
|
57904
|
+
... on WorkItemWidgetAssignees { assignees { nodes { id username name } } }
|
|
57905
|
+
... on WorkItemWidgetWeight { weight rolledUpWeight rolledUpCompletedWeight }
|
|
57906
|
+
... on WorkItemWidgetHealthStatus { healthStatus }
|
|
57907
|
+
... on WorkItemWidgetStartAndDueDate { startDate dueDate }
|
|
57908
|
+
... on WorkItemWidgetMilestone { milestone { id title } }
|
|
57909
|
+
... on WorkItemWidgetLinkedItems {
|
|
57910
|
+
blocked blockedByCount blockingCount
|
|
57911
|
+
linkedItems { nodes { linkType workItem { id iid title state webUrl workItemType { name } namespace { fullPath } } } }
|
|
57912
|
+
}
|
|
57913
|
+
... on WorkItemWidgetTimeTracking { timeEstimate totalTimeSpent }
|
|
57914
|
+
... on WorkItemWidgetDevelopment {
|
|
57915
|
+
willAutoCloseByMergeRequest
|
|
57916
|
+
relatedBranches { nodes { name } }
|
|
57917
|
+
relatedMergeRequests { nodes { iid title webUrl state sourceBranch } }
|
|
57918
|
+
closingMergeRequests { nodes { mergeRequest { iid title webUrl state sourceBranch } } }
|
|
57919
|
+
featureFlags { nodes { name active } }
|
|
57920
|
+
}
|
|
57921
|
+
... on WorkItemWidgetIteration {
|
|
57922
|
+
iteration { id title startDate dueDate webUrl iterationCadence { id title } }
|
|
57923
|
+
}
|
|
57924
|
+
... on WorkItemWidgetProgress { progress }
|
|
57925
|
+
... on WorkItemWidgetColor { color textColor }
|
|
57926
|
+
}
|
|
57927
|
+
}
|
|
57928
|
+
}
|
|
57929
|
+
}`, { path: projectPath, iid: String(iid) });
|
|
57930
|
+
if (!data.namespace?.workItem) {
|
|
57931
|
+
throw new Error(`Work item #${iid} not found in project ${projectPath}`);
|
|
57932
|
+
}
|
|
57933
|
+
return flattenWorkItem(data.namespace.workItem);
|
|
57934
|
+
}
|
|
57935
|
+
function findWidget(widgets, typeName) {
|
|
57936
|
+
return widgets.find((w) => w.__typename === typeName);
|
|
57937
|
+
}
|
|
57938
|
+
function extractCoreWidgets(widgets, result) {
|
|
57939
|
+
const statusW = findWidget(widgets, "WorkItemWidgetStatus");
|
|
57940
|
+
if (statusW?.status)
|
|
57941
|
+
result.status = {
|
|
57942
|
+
name: statusW.status.name,
|
|
57943
|
+
id: statusW.status.id,
|
|
57944
|
+
category: statusW.status.category
|
|
57945
|
+
};
|
|
57946
|
+
const labelsW = findWidget(widgets, "WorkItemWidgetLabels");
|
|
57947
|
+
const labels = (labelsW?.labels?.nodes || []).map((l) => l.title);
|
|
57948
|
+
if (labels.length > 0)
|
|
57949
|
+
result.labels = labels;
|
|
57950
|
+
const assigneesW = findWidget(widgets, "WorkItemWidgetAssignees");
|
|
57951
|
+
const assignees = (assigneesW?.assignees?.nodes || []).map((a) => a.username);
|
|
57952
|
+
if (assignees.length > 0)
|
|
57953
|
+
result.assignees = assignees;
|
|
57954
|
+
const weightW = findWidget(widgets, "WorkItemWidgetWeight");
|
|
57955
|
+
if (weightW?.weight != null) {
|
|
57956
|
+
result.weight = weightW.weight;
|
|
57957
|
+
if (weightW.rolledUpWeight != null)
|
|
57958
|
+
result.rolledUpWeight = weightW.rolledUpWeight;
|
|
57959
|
+
if (weightW.rolledUpCompletedWeight != null)
|
|
57960
|
+
result.rolledUpCompletedWeight = weightW.rolledUpCompletedWeight;
|
|
57961
|
+
}
|
|
57962
|
+
const healthW = findWidget(widgets, "WorkItemWidgetHealthStatus");
|
|
57963
|
+
if (healthW?.healthStatus)
|
|
57964
|
+
result.healthStatus = healthW.healthStatus;
|
|
57965
|
+
}
|
|
57966
|
+
function extractMetadataWidgets(widgets, result) {
|
|
57967
|
+
const datesW = findWidget(widgets, "WorkItemWidgetStartAndDueDate");
|
|
57968
|
+
if (datesW?.startDate)
|
|
57969
|
+
result.startDate = datesW.startDate;
|
|
57970
|
+
if (datesW?.dueDate)
|
|
57971
|
+
result.dueDate = datesW.dueDate;
|
|
57972
|
+
const milestoneW = findWidget(widgets, "WorkItemWidgetMilestone");
|
|
57973
|
+
if (milestoneW?.milestone)
|
|
57974
|
+
result.milestone = { id: milestoneW.milestone.id, title: milestoneW.milestone.title };
|
|
57975
|
+
const iterW = findWidget(widgets, "WorkItemWidgetIteration");
|
|
57976
|
+
if (iterW?.iteration) {
|
|
57977
|
+
result.iteration = {
|
|
57978
|
+
id: iterW.iteration.id,
|
|
57979
|
+
title: iterW.iteration.title,
|
|
57980
|
+
startDate: iterW.iteration.startDate,
|
|
57981
|
+
dueDate: iterW.iteration.dueDate
|
|
57982
|
+
};
|
|
57983
|
+
}
|
|
57984
|
+
const progressW = findWidget(widgets, "WorkItemWidgetProgress");
|
|
57985
|
+
if (progressW?.progress != null)
|
|
57986
|
+
result.progress = progressW.progress;
|
|
57987
|
+
const colorW = findWidget(widgets, "WorkItemWidgetColor");
|
|
57988
|
+
if (colorW?.color)
|
|
57989
|
+
result.color = colorW.color;
|
|
57990
|
+
const timeW = findWidget(widgets, "WorkItemWidgetTimeTracking");
|
|
57991
|
+
if (timeW?.timeEstimate > 0)
|
|
57992
|
+
result.timeEstimate = timeW.timeEstimate;
|
|
57993
|
+
if (timeW?.totalTimeSpent > 0)
|
|
57994
|
+
result.totalTimeSpent = timeW.totalTimeSpent;
|
|
57995
|
+
}
|
|
57996
|
+
function extractHierarchyWidget(widgets, result) {
|
|
57997
|
+
const hierW = findWidget(widgets, "WorkItemWidgetHierarchy");
|
|
57998
|
+
if (hierW?.parent)
|
|
57999
|
+
result.parent = {
|
|
58000
|
+
iid: hierW.parent.iid,
|
|
58001
|
+
title: hierW.parent.title,
|
|
58002
|
+
type: hierW.parent.workItemType?.name,
|
|
58003
|
+
project: hierW.parent.namespace?.fullPath,
|
|
58004
|
+
webUrl: hierW.parent.webUrl
|
|
58005
|
+
};
|
|
58006
|
+
const children = hierW?.children?.nodes || [];
|
|
58007
|
+
if (children.length > 0)
|
|
58008
|
+
result.children = children.map((c) => ({
|
|
58009
|
+
iid: c.iid,
|
|
58010
|
+
title: c.title,
|
|
58011
|
+
state: c.state,
|
|
58012
|
+
type: c.workItemType?.name,
|
|
58013
|
+
project: c.namespace?.fullPath,
|
|
58014
|
+
webUrl: c.webUrl
|
|
58015
|
+
}));
|
|
58016
|
+
}
|
|
58017
|
+
function extractLinkedItemsWidget(widgets, result) {
|
|
58018
|
+
const linkedW = findWidget(widgets, "WorkItemWidgetLinkedItems");
|
|
58019
|
+
if (linkedW?.blocked)
|
|
58020
|
+
result.blocked = true;
|
|
58021
|
+
if (linkedW?.blockedByCount > 0)
|
|
58022
|
+
result.blockedByCount = linkedW.blockedByCount;
|
|
58023
|
+
if (linkedW?.blockingCount > 0)
|
|
58024
|
+
result.blockingCount = linkedW.blockingCount;
|
|
58025
|
+
const linkedNodes = linkedW?.linkedItems?.nodes || [];
|
|
58026
|
+
if (linkedNodes.length > 0) {
|
|
58027
|
+
result.linkedItems = linkedNodes.map((n) => ({
|
|
58028
|
+
linkType: n.linkType,
|
|
58029
|
+
iid: n.workItem?.iid,
|
|
58030
|
+
title: n.workItem?.title,
|
|
58031
|
+
state: n.workItem?.state,
|
|
58032
|
+
type: n.workItem?.workItemType?.name,
|
|
58033
|
+
project: n.workItem?.namespace?.fullPath,
|
|
58034
|
+
webUrl: n.workItem?.webUrl
|
|
58035
|
+
}));
|
|
58036
|
+
}
|
|
58037
|
+
}
|
|
58038
|
+
function extractDevelopmentWidget(widgets, result) {
|
|
58039
|
+
const devW = findWidget(widgets, "WorkItemWidgetDevelopment");
|
|
58040
|
+
const relatedMRs = devW?.relatedMergeRequests?.nodes || [];
|
|
58041
|
+
const closingMRs = (devW?.closingMergeRequests?.nodes || []).map((n) => n.mergeRequest);
|
|
58042
|
+
const branches = devW?.relatedBranches?.nodes || [];
|
|
58043
|
+
const flags = devW?.featureFlags?.nodes || [];
|
|
58044
|
+
if (relatedMRs.length > 0 || closingMRs.length > 0 || branches.length > 0 || flags.length > 0) {
|
|
58045
|
+
const dev = {};
|
|
58046
|
+
if (relatedMRs.length > 0)
|
|
58047
|
+
dev.relatedMergeRequests = relatedMRs;
|
|
58048
|
+
if (closingMRs.length > 0)
|
|
58049
|
+
dev.closingMergeRequests = closingMRs;
|
|
58050
|
+
if (branches.length > 0)
|
|
58051
|
+
dev.relatedBranches = branches.map((b) => b.name);
|
|
58052
|
+
if (flags.length > 0)
|
|
58053
|
+
dev.featureFlags = flags;
|
|
58054
|
+
result.development = dev;
|
|
58055
|
+
}
|
|
58056
|
+
}
|
|
58057
|
+
function extractCustomFieldsWidget(widgets, result) {
|
|
58058
|
+
const cfW = findWidget(widgets, "WorkItemWidgetCustomFields");
|
|
58059
|
+
const cfValues = (cfW?.customFieldValues || []).filter((cfv) => cfv.value != null || cfv.selectedOptions != null);
|
|
58060
|
+
if (cfValues.length > 0) {
|
|
58061
|
+
result.customFields = cfValues.map((cfv) => ({
|
|
58062
|
+
name: cfv.customField?.name,
|
|
58063
|
+
type: cfv.customField?.fieldType,
|
|
58064
|
+
value: cfv.value ?? cfv.selectedOptions ?? null
|
|
58065
|
+
}));
|
|
58066
|
+
}
|
|
58067
|
+
}
|
|
58068
|
+
function extractRelationWidgets(widgets, result) {
|
|
58069
|
+
extractHierarchyWidget(widgets, result);
|
|
58070
|
+
extractLinkedItemsWidget(widgets, result);
|
|
58071
|
+
extractDevelopmentWidget(widgets, result);
|
|
58072
|
+
extractCustomFieldsWidget(widgets, result);
|
|
58073
|
+
}
|
|
58074
|
+
function flattenWorkItem(wi) {
|
|
58075
|
+
const widgets = wi.widgets || [];
|
|
58076
|
+
const result = {
|
|
58077
|
+
id: wi.id,
|
|
58078
|
+
iid: wi.iid,
|
|
58079
|
+
title: wi.title,
|
|
58080
|
+
state: wi.state,
|
|
58081
|
+
type: wi.workItemType?.name,
|
|
58082
|
+
webUrl: wi.webUrl
|
|
58083
|
+
};
|
|
58084
|
+
if (wi.description)
|
|
58085
|
+
result.description = wi.description;
|
|
58086
|
+
if (wi.confidential)
|
|
58087
|
+
result.confidential = true;
|
|
58088
|
+
if (wi.author?.username)
|
|
58089
|
+
result.author = wi.author.username;
|
|
58090
|
+
if (wi.createdAt)
|
|
58091
|
+
result.createdAt = wi.createdAt;
|
|
58092
|
+
if (wi.closedAt)
|
|
58093
|
+
result.closedAt = wi.closedAt;
|
|
58094
|
+
extractCoreWidgets(widgets, result);
|
|
58095
|
+
extractMetadataWidgets(widgets, result);
|
|
58096
|
+
extractRelationWidgets(widgets, result);
|
|
58097
|
+
return result;
|
|
58098
|
+
}
|
|
58099
|
+
function flattenWorkItemSummary(wi) {
|
|
58100
|
+
const widgets = wi.widgets || [];
|
|
58101
|
+
const item = {
|
|
58102
|
+
iid: wi.iid,
|
|
58103
|
+
title: wi.title,
|
|
58104
|
+
state: wi.state,
|
|
58105
|
+
type: wi.workItemType?.name,
|
|
58106
|
+
webUrl: wi.webUrl
|
|
58107
|
+
};
|
|
58108
|
+
const statusW = findWidget(widgets, "WorkItemWidgetStatus");
|
|
58109
|
+
if (statusW?.status)
|
|
58110
|
+
item.status = statusW.status.name;
|
|
58111
|
+
const labelsW = findWidget(widgets, "WorkItemWidgetLabels");
|
|
58112
|
+
const labels = (labelsW?.labels?.nodes || []).map((l) => l.title);
|
|
58113
|
+
if (labels.length > 0)
|
|
58114
|
+
item.labels = labels;
|
|
58115
|
+
const assigneesW = findWidget(widgets, "WorkItemWidgetAssignees");
|
|
58116
|
+
const assignees = (assigneesW?.assignees?.nodes || []).map((a) => a.username);
|
|
58117
|
+
if (assignees.length > 0)
|
|
58118
|
+
item.assignees = assignees;
|
|
58119
|
+
const weightW = findWidget(widgets, "WorkItemWidgetWeight");
|
|
58120
|
+
if (weightW?.weight != null)
|
|
58121
|
+
item.weight = weightW.weight;
|
|
58122
|
+
const healthW = findWidget(widgets, "WorkItemWidgetHealthStatus");
|
|
58123
|
+
if (healthW?.healthStatus)
|
|
58124
|
+
item.healthStatus = healthW.healthStatus;
|
|
58125
|
+
const datesW = findWidget(widgets, "WorkItemWidgetStartAndDueDate");
|
|
58126
|
+
if (datesW?.startDate)
|
|
58127
|
+
item.startDate = datesW.startDate;
|
|
58128
|
+
if (datesW?.dueDate)
|
|
58129
|
+
item.dueDate = datesW.dueDate;
|
|
58130
|
+
const milestoneW = findWidget(widgets, "WorkItemWidgetMilestone");
|
|
58131
|
+
if (milestoneW?.milestone)
|
|
58132
|
+
item.milestone = milestoneW.milestone.title;
|
|
58133
|
+
return item;
|
|
58134
|
+
}
|
|
58135
|
+
async function listWorkItems(projectId, options) {
|
|
58136
|
+
const projectPath = await resolveProjectPath(projectId);
|
|
58137
|
+
const typeMap = {
|
|
58138
|
+
issue: "ISSUE",
|
|
58139
|
+
task: "TASK",
|
|
58140
|
+
incident: "INCIDENT",
|
|
58141
|
+
test_case: "TEST_CASE",
|
|
58142
|
+
epic: "EPIC",
|
|
58143
|
+
key_result: "KEY_RESULT",
|
|
58144
|
+
objective: "OBJECTIVE",
|
|
58145
|
+
requirement: "REQUIREMENT",
|
|
58146
|
+
ticket: "TICKET"
|
|
58147
|
+
};
|
|
58148
|
+
const variables = {
|
|
58149
|
+
path: projectPath,
|
|
58150
|
+
first: options.first || 20
|
|
58151
|
+
};
|
|
58152
|
+
if (options.types?.length)
|
|
58153
|
+
variables.types = options.types.map((t) => typeMap[t] || t.replace(/ /g, "_").toUpperCase());
|
|
58154
|
+
if (options.state)
|
|
58155
|
+
variables.state = options.state === "opened" ? "opened" : "closed";
|
|
58156
|
+
if (options.search)
|
|
58157
|
+
variables.search = options.search;
|
|
58158
|
+
if (options.assignee_usernames?.length)
|
|
58159
|
+
variables.assigneeUsernames = options.assignee_usernames;
|
|
58160
|
+
if (options.label_names?.length)
|
|
58161
|
+
variables.labelName = options.label_names;
|
|
58162
|
+
if (options.after)
|
|
58163
|
+
variables.after = options.after;
|
|
58164
|
+
const data = await defaultClient.graphql(`query($path: ID!, $types: [IssueType!], $state: IssuableState, $search: String, $assigneeUsernames: [String!], $labelName: [String!], $first: Int, $after: String) {
|
|
58165
|
+
project(fullPath: $path) {
|
|
58166
|
+
workItems(types: $types, state: $state, search: $search, assigneeUsernames: $assigneeUsernames, labelName: $labelName, first: $first, after: $after) {
|
|
58167
|
+
nodes {
|
|
58168
|
+
id iid title state webUrl workItemType { name }
|
|
58169
|
+
widgets {
|
|
58170
|
+
__typename
|
|
58171
|
+
... on WorkItemWidgetStatus { status { id name category color } }
|
|
58172
|
+
... on WorkItemWidgetLabels { labels { nodes { title } } }
|
|
58173
|
+
... on WorkItemWidgetAssignees { assignees { nodes { username } } }
|
|
58174
|
+
... on WorkItemWidgetWeight { weight }
|
|
58175
|
+
... on WorkItemWidgetHealthStatus { healthStatus }
|
|
58176
|
+
... on WorkItemWidgetStartAndDueDate { startDate dueDate }
|
|
58177
|
+
... on WorkItemWidgetMilestone { milestone { id title } }
|
|
58178
|
+
}
|
|
58179
|
+
}
|
|
58180
|
+
pageInfo { hasNextPage endCursor }
|
|
58181
|
+
}
|
|
58182
|
+
}
|
|
58183
|
+
}`, variables);
|
|
58184
|
+
const workItems = data.project?.workItems?.nodes || [];
|
|
58185
|
+
const pageInfo = data.project?.workItems?.pageInfo || {};
|
|
58186
|
+
const items = workItems.map(flattenWorkItemSummary);
|
|
58187
|
+
return { items, pageInfo };
|
|
58188
|
+
}
|
|
58189
|
+
function addCommonWidgetFields(builder, options) {
|
|
58190
|
+
if (options.weight !== undefined) {
|
|
58191
|
+
builder.varDefs.push("$weight: Int");
|
|
58192
|
+
builder.inputParts.push("weightWidget: { weight: $weight }");
|
|
58193
|
+
builder.variables.weight = options.weight;
|
|
58194
|
+
}
|
|
58195
|
+
if (options.health_status !== undefined) {
|
|
58196
|
+
builder.varDefs.push("$healthStatus: HealthStatus");
|
|
58197
|
+
builder.inputParts.push("healthStatusWidget: { healthStatus: $healthStatus }");
|
|
58198
|
+
builder.variables.healthStatus = options.health_status;
|
|
58199
|
+
}
|
|
58200
|
+
if (options.start_date !== undefined || options.due_date !== undefined) {
|
|
58201
|
+
const dateParts = [];
|
|
58202
|
+
if (options.start_date !== undefined) {
|
|
58203
|
+
builder.varDefs.push("$startDate: Date");
|
|
58204
|
+
dateParts.push("startDate: $startDate");
|
|
58205
|
+
builder.variables.startDate = options.start_date;
|
|
58206
|
+
}
|
|
58207
|
+
if (options.due_date !== undefined) {
|
|
58208
|
+
builder.varDefs.push("$dueDate: Date");
|
|
58209
|
+
dateParts.push("dueDate: $dueDate");
|
|
58210
|
+
builder.variables.dueDate = options.due_date;
|
|
58211
|
+
}
|
|
58212
|
+
builder.inputParts.push(`startAndDueDateWidget: { ${dateParts.join(", ")} }`);
|
|
58213
|
+
}
|
|
58214
|
+
if (options.milestone_id !== undefined) {
|
|
58215
|
+
builder.varDefs.push("$milestoneId: MilestoneID");
|
|
58216
|
+
builder.inputParts.push("milestoneWidget: { milestoneId: $milestoneId }");
|
|
58217
|
+
builder.variables.milestoneId = toGID(options.milestone_id, "Milestone");
|
|
58218
|
+
}
|
|
58219
|
+
if (options.iteration_id !== undefined) {
|
|
58220
|
+
builder.varDefs.push("$iterationId: IterationID");
|
|
58221
|
+
builder.inputParts.push("iterationWidget: { iterationId: $iterationId }");
|
|
58222
|
+
builder.variables.iterationId = toGID(options.iteration_id, "Iteration");
|
|
58223
|
+
}
|
|
58224
|
+
if (options.confidential !== undefined) {
|
|
58225
|
+
builder.varDefs.push("$confidential: Boolean");
|
|
58226
|
+
builder.inputParts.push("confidential: $confidential");
|
|
58227
|
+
builder.variables.confidential = options.confidential;
|
|
58228
|
+
}
|
|
58229
|
+
}
|
|
58230
|
+
async function createWorkItem(projectId, options) {
|
|
58231
|
+
const projectPath = await resolveProjectPath(projectId);
|
|
58232
|
+
const typeName = options.type || "issue";
|
|
58233
|
+
const typeGID = await resolveWorkItemTypeGID(projectPath, typeName);
|
|
58234
|
+
const builder = {
|
|
58235
|
+
varDefs: ["$projectPath: ID!", "$title: String!", "$typeId: WorkItemsTypeID!"],
|
|
58236
|
+
inputParts: ["namespacePath: $projectPath", "title: $title", "workItemTypeId: $typeId"],
|
|
58237
|
+
variables: { projectPath, title: options.title, typeId: typeGID }
|
|
58238
|
+
};
|
|
58239
|
+
if (options.description !== undefined) {
|
|
58240
|
+
builder.varDefs.push("$description: String!");
|
|
58241
|
+
builder.inputParts.push("descriptionWidget: { description: $description }");
|
|
58242
|
+
builder.variables.description = options.description;
|
|
58243
|
+
}
|
|
58244
|
+
const { labelIds, userIds } = await resolveNamesToIds(projectPath, options.labels, options.assignee_usernames);
|
|
58245
|
+
if (labelIds.length > 0) {
|
|
58246
|
+
builder.varDefs.push("$labelIds: [LabelID!]!");
|
|
58247
|
+
builder.inputParts.push("labelsWidget: { labelIds: $labelIds }");
|
|
58248
|
+
builder.variables.labelIds = labelIds;
|
|
58249
|
+
}
|
|
58250
|
+
if (options.parent_iid !== undefined) {
|
|
58251
|
+
const { workItemGID: parentGID } = await resolveWorkItemGID(projectId, options.parent_iid);
|
|
58252
|
+
builder.varDefs.push("$parentId: WorkItemID");
|
|
58253
|
+
builder.inputParts.push("hierarchyWidget: { parentId: $parentId }");
|
|
58254
|
+
builder.variables.parentId = parentGID;
|
|
58255
|
+
}
|
|
58256
|
+
if (userIds.length > 0) {
|
|
58257
|
+
builder.varDefs.push("$assigneeIds: [UserID!]!");
|
|
58258
|
+
builder.inputParts.push("assigneesWidget: { assigneeIds: $assigneeIds }");
|
|
58259
|
+
builder.variables.assigneeIds = userIds;
|
|
58260
|
+
}
|
|
58261
|
+
addCommonWidgetFields(builder, options);
|
|
58262
|
+
const mutation = `mutation(${builder.varDefs.join(", ")}) {
|
|
58263
|
+
workItemCreate(input: { ${builder.inputParts.join(", ")} }) {
|
|
58264
|
+
workItem { id iid title webUrl workItemType { name } }
|
|
58265
|
+
errors
|
|
58266
|
+
}
|
|
58267
|
+
}`;
|
|
58268
|
+
const data = await defaultClient.graphql(mutation, builder.variables);
|
|
58269
|
+
if (data.workItemCreate.errors?.length > 0) {
|
|
58270
|
+
throw new Error(`Failed to create work item: ${data.workItemCreate.errors.join(", ")}`);
|
|
58271
|
+
}
|
|
58272
|
+
const wi = data.workItemCreate.workItem;
|
|
58273
|
+
return {
|
|
58274
|
+
id: wi.id,
|
|
58275
|
+
iid: wi.iid,
|
|
58276
|
+
title: wi.title,
|
|
58277
|
+
type: wi.workItemType?.name,
|
|
58278
|
+
webUrl: wi.webUrl
|
|
58279
|
+
};
|
|
58280
|
+
}
|
|
58281
|
+
async function buildUpdateMutationFields(projectId, projectPath, workItemGID, options) {
|
|
58282
|
+
const builder = {
|
|
58283
|
+
varDefs: ["$id: WorkItemID!"],
|
|
58284
|
+
inputParts: ["id: $id"],
|
|
58285
|
+
variables: { id: workItemGID }
|
|
58286
|
+
};
|
|
58287
|
+
if (options.title !== undefined) {
|
|
58288
|
+
builder.varDefs.push("$title: String");
|
|
58289
|
+
builder.inputParts.push("title: $title");
|
|
58290
|
+
builder.variables.title = options.title;
|
|
58291
|
+
}
|
|
58292
|
+
if (options.description !== undefined) {
|
|
58293
|
+
builder.varDefs.push("$description: String!");
|
|
58294
|
+
builder.inputParts.push("descriptionWidget: { description: $description }");
|
|
58295
|
+
builder.variables.description = options.description;
|
|
58296
|
+
}
|
|
58297
|
+
addUpdateLabelsAndAssignees(builder, projectPath, options);
|
|
58298
|
+
if (options.state_event !== undefined) {
|
|
58299
|
+
builder.varDefs.push("$stateEvent: WorkItemStateEvent");
|
|
58300
|
+
builder.inputParts.push("stateEvent: $stateEvent");
|
|
58301
|
+
builder.variables.stateEvent = options.state_event === "close" ? "CLOSE" : "REOPEN";
|
|
58302
|
+
}
|
|
58303
|
+
if (options.status !== undefined) {
|
|
58304
|
+
builder.varDefs.push("$status: WorkItemsStatusesStatusID");
|
|
58305
|
+
builder.inputParts.push("statusWidget: { status: $status }");
|
|
58306
|
+
builder.variables.status = options.status;
|
|
58307
|
+
}
|
|
58308
|
+
addCommonWidgetFields(builder, {
|
|
58309
|
+
weight: options.weight,
|
|
58310
|
+
health_status: options.health_status,
|
|
58311
|
+
start_date: options.start_date,
|
|
58312
|
+
due_date: options.due_date,
|
|
58313
|
+
milestone_id: options.milestone_id,
|
|
58314
|
+
iteration_id: options.iteration_id,
|
|
58315
|
+
confidential: options.confidential
|
|
58316
|
+
});
|
|
58317
|
+
addUpdateCustomFields(builder, options);
|
|
58318
|
+
await addUpdateHierarchy(builder, projectId, options);
|
|
58319
|
+
return builder;
|
|
58320
|
+
}
|
|
58321
|
+
async function addUpdateLabelsAndAssignees(builder, projectPath, options) {
|
|
58322
|
+
const allLabelNames = [
|
|
58323
|
+
...options.add_labels || [],
|
|
58324
|
+
...options.remove_labels || []
|
|
58325
|
+
];
|
|
58326
|
+
const needsResolve = allLabelNames.length > 0 || options.assignee_usernames?.length;
|
|
58327
|
+
const { labelIds: resolvedLabelIds, userIds } = needsResolve ? await resolveNamesToIds(projectPath, allLabelNames.length > 0 ? allLabelNames : undefined, options.assignee_usernames) : { labelIds: [], userIds: [] };
|
|
58328
|
+
if (options.add_labels || options.remove_labels) {
|
|
58329
|
+
const labelParts = [];
|
|
58330
|
+
let offset = 0;
|
|
58331
|
+
if (options.add_labels?.length) {
|
|
58332
|
+
const addIds = resolvedLabelIds.slice(0, options.add_labels.length);
|
|
58333
|
+
offset = options.add_labels.length;
|
|
58334
|
+
builder.varDefs.push("$addLabelIds: [LabelID!]");
|
|
58335
|
+
labelParts.push("addLabelIds: $addLabelIds");
|
|
58336
|
+
builder.variables.addLabelIds = addIds;
|
|
58337
|
+
}
|
|
58338
|
+
if (options.remove_labels?.length) {
|
|
58339
|
+
const removeIds = resolvedLabelIds.slice(offset);
|
|
58340
|
+
builder.varDefs.push("$removeLabelIds: [LabelID!]");
|
|
58341
|
+
labelParts.push("removeLabelIds: $removeLabelIds");
|
|
58342
|
+
builder.variables.removeLabelIds = removeIds;
|
|
58343
|
+
}
|
|
58344
|
+
if (labelParts.length > 0) {
|
|
58345
|
+
builder.inputParts.push(`labelsWidget: { ${labelParts.join(", ")} }`);
|
|
58346
|
+
}
|
|
58347
|
+
}
|
|
58348
|
+
if (userIds.length > 0) {
|
|
58349
|
+
builder.varDefs.push("$assigneeIds: [UserID!]!");
|
|
58350
|
+
builder.inputParts.push("assigneesWidget: { assigneeIds: $assigneeIds }");
|
|
58351
|
+
builder.variables.assigneeIds = userIds;
|
|
58352
|
+
}
|
|
58353
|
+
}
|
|
58354
|
+
function addUpdateCustomFields(builder, options) {
|
|
58355
|
+
if (!options.custom_fields || !options.custom_fields.length)
|
|
58356
|
+
return;
|
|
58357
|
+
const cfInput = options.custom_fields.map((cf) => {
|
|
58358
|
+
const val = {
|
|
58359
|
+
customFieldId: toGID(cf.custom_field_id, "IssuablesCustomField")
|
|
58360
|
+
};
|
|
58361
|
+
if (cf.text_value !== undefined)
|
|
58362
|
+
val.textValue = cf.text_value;
|
|
58363
|
+
if (cf.number_value !== undefined)
|
|
58364
|
+
val.numberValue = cf.number_value;
|
|
58365
|
+
if (cf.selected_option_ids !== undefined)
|
|
58366
|
+
val.selectedOptionIds = cf.selected_option_ids;
|
|
58367
|
+
if (cf.date_value !== undefined)
|
|
58368
|
+
val.dateValue = cf.date_value;
|
|
58369
|
+
return val;
|
|
58370
|
+
});
|
|
58371
|
+
builder.varDefs.push("$customFieldsWidget: [WorkItemWidgetCustomFieldValueInputType!]");
|
|
58372
|
+
builder.inputParts.push("customFieldsWidget: $customFieldsWidget");
|
|
58373
|
+
builder.variables.customFieldsWidget = cfInput;
|
|
58374
|
+
}
|
|
58375
|
+
async function addUpdateHierarchy(builder, projectId, options) {
|
|
58376
|
+
if (options.remove_parent) {
|
|
58377
|
+
builder.inputParts.push("hierarchyWidget: { parentId: null }");
|
|
58378
|
+
} else if (options.parent_iid !== undefined) {
|
|
58379
|
+
const parentProjectId = options.parent_project_id || projectId;
|
|
58380
|
+
const { workItemGID: parentGID } = await resolveWorkItemGID(parentProjectId, options.parent_iid);
|
|
58381
|
+
builder.varDefs.push("$parentId: WorkItemID");
|
|
58382
|
+
builder.inputParts.push("hierarchyWidget: { parentId: $parentId }");
|
|
58383
|
+
builder.variables.parentId = parentGID;
|
|
58384
|
+
}
|
|
58385
|
+
}
|
|
58386
|
+
async function handleChildrenToAdd(workItemGID, options) {
|
|
58387
|
+
const childrenToAdd = options.children_to_add;
|
|
58388
|
+
if (!childrenToAdd?.length)
|
|
58389
|
+
return;
|
|
58390
|
+
const childGIDs = [];
|
|
58391
|
+
for (const child of childrenToAdd) {
|
|
58392
|
+
const { workItemGID: childGID } = await resolveWorkItemGID(child.project_id, child.iid);
|
|
58393
|
+
childGIDs.push(childGID);
|
|
58394
|
+
}
|
|
58395
|
+
const addData = await defaultClient.graphql(`mutation($id: WorkItemID!, $childrenIds: [WorkItemID!]!) {
|
|
58396
|
+
workItemUpdate(input: { id: $id, hierarchyWidget: { childrenIds: $childrenIds } }) { errors }
|
|
58397
|
+
}`, { id: workItemGID, childrenIds: childGIDs });
|
|
58398
|
+
if (addData.workItemUpdate.errors?.length > 0) {
|
|
58399
|
+
throw new Error(`Failed to add children: ${addData.workItemUpdate.errors.join(", ")}`);
|
|
58400
|
+
}
|
|
58401
|
+
}
|
|
58402
|
+
async function handleChildrenToRemove(options) {
|
|
58403
|
+
const childrenToRemove = options.children_to_remove;
|
|
58404
|
+
if (!childrenToRemove?.length)
|
|
58405
|
+
return;
|
|
58406
|
+
for (const child of childrenToRemove) {
|
|
58407
|
+
await removeIssueParent(child.project_id, child.iid);
|
|
58408
|
+
}
|
|
58409
|
+
}
|
|
58410
|
+
async function handleLinkedItemsToAdd(workItemGID, options) {
|
|
58411
|
+
const linkedToAdd = options.linked_items_to_add;
|
|
58412
|
+
if (!linkedToAdd?.length)
|
|
58413
|
+
return;
|
|
58414
|
+
const groupedByType = {};
|
|
58415
|
+
for (const item of linkedToAdd) {
|
|
58416
|
+
const linkType = item.link_type || "RELATED";
|
|
58417
|
+
if (!groupedByType[linkType])
|
|
58418
|
+
groupedByType[linkType] = [];
|
|
58419
|
+
const { workItemGID: targetGID } = await resolveWorkItemGID(item.project_id, item.iid);
|
|
58420
|
+
groupedByType[linkType].push(targetGID);
|
|
58421
|
+
}
|
|
58422
|
+
for (const [linkType, targetGIDs] of Object.entries(groupedByType)) {
|
|
58423
|
+
const addLinkedData = await defaultClient.graphql(`mutation($id: WorkItemID!, $workItemsIds: [WorkItemID!]!, $linkType: WorkItemRelatedLinkType!) {
|
|
58424
|
+
workItemAddLinkedItems(input: { id: $id, workItemsIds: $workItemsIds, linkType: $linkType }) { errors }
|
|
58425
|
+
}`, { id: workItemGID, workItemsIds: targetGIDs, linkType });
|
|
58426
|
+
if (addLinkedData.workItemAddLinkedItems.errors?.length > 0) {
|
|
58427
|
+
throw new Error(`Failed to add linked items: ${addLinkedData.workItemAddLinkedItems.errors.join(", ")}`);
|
|
58428
|
+
}
|
|
58429
|
+
}
|
|
58430
|
+
}
|
|
58431
|
+
async function handleLinkedItemsToRemove(workItemGID, options) {
|
|
58432
|
+
const linkedToRemove = options.linked_items_to_remove;
|
|
58433
|
+
if (!linkedToRemove?.length)
|
|
58434
|
+
return;
|
|
58435
|
+
const targetGIDs = [];
|
|
58436
|
+
for (const item of linkedToRemove) {
|
|
58437
|
+
const { workItemGID: targetGID } = await resolveWorkItemGID(item.project_id, item.iid);
|
|
58438
|
+
targetGIDs.push(targetGID);
|
|
58439
|
+
}
|
|
58440
|
+
const removeLinkedData = await defaultClient.graphql(`mutation($id: WorkItemID!, $workItemsIds: [WorkItemID!]!) {
|
|
58441
|
+
workItemRemoveLinkedItems(input: { id: $id, workItemsIds: $workItemsIds }) { errors }
|
|
58442
|
+
}`, { id: workItemGID, workItemsIds: targetGIDs });
|
|
58443
|
+
if (removeLinkedData.workItemRemoveLinkedItems.errors?.length > 0) {
|
|
58444
|
+
throw new Error(`Failed to remove linked items: ${removeLinkedData.workItemRemoveLinkedItems.errors.join(", ")}`);
|
|
58445
|
+
}
|
|
58446
|
+
}
|
|
58447
|
+
async function handleUpdateSideEffects(workItemGID, projectPath, iid, options) {
|
|
58448
|
+
await handleChildrenToAdd(workItemGID, options);
|
|
58449
|
+
await handleChildrenToRemove(options);
|
|
58450
|
+
await handleLinkedItemsToAdd(workItemGID, options);
|
|
58451
|
+
await handleLinkedItemsToRemove(workItemGID, options);
|
|
58452
|
+
if (options.severity !== undefined) {
|
|
58453
|
+
await updateIncidentSeverity(projectPath, iid, options.severity);
|
|
58454
|
+
}
|
|
58455
|
+
if (options.escalation_status !== undefined) {
|
|
58456
|
+
await updateIncidentEscalationStatus(projectPath, iid, options.escalation_status);
|
|
58457
|
+
}
|
|
58458
|
+
}
|
|
58459
|
+
function flattenUpdateWidgets(wi) {
|
|
58460
|
+
const widgets = wi?.widgets || [];
|
|
58461
|
+
const statusW = findWidget(widgets, "WorkItemWidgetStatus");
|
|
58462
|
+
const labelsW = findWidget(widgets, "WorkItemWidgetLabels");
|
|
58463
|
+
const assigneesW = findWidget(widgets, "WorkItemWidgetAssignees");
|
|
58464
|
+
const weightW = findWidget(widgets, "WorkItemWidgetWeight");
|
|
58465
|
+
const hierarchyW = findWidget(widgets, "WorkItemWidgetHierarchy");
|
|
58466
|
+
const healthStatusW = findWidget(widgets, "WorkItemWidgetHealthStatus");
|
|
58467
|
+
const datesW = findWidget(widgets, "WorkItemWidgetStartAndDueDate");
|
|
58468
|
+
const milestoneW = findWidget(widgets, "WorkItemWidgetMilestone");
|
|
58469
|
+
return {
|
|
58470
|
+
id: wi.id,
|
|
58471
|
+
iid: wi.iid,
|
|
58472
|
+
title: wi.title,
|
|
58473
|
+
state: wi.state,
|
|
58474
|
+
type: wi.workItemType?.name,
|
|
58475
|
+
webUrl: wi.webUrl,
|
|
58476
|
+
status: statusW?.status || null,
|
|
58477
|
+
labels: (labelsW?.labels?.nodes || []).map((l) => l.title),
|
|
58478
|
+
assignees: (assigneesW?.assignees?.nodes || []).map((a) => a.username),
|
|
58479
|
+
weight: weightW?.weight ?? null,
|
|
58480
|
+
parent: hierarchyW?.parent || null,
|
|
58481
|
+
healthStatus: healthStatusW?.healthStatus || null,
|
|
58482
|
+
startDate: datesW?.startDate || null,
|
|
58483
|
+
dueDate: datesW?.dueDate || null,
|
|
58484
|
+
milestone: milestoneW?.milestone || null
|
|
58485
|
+
};
|
|
58486
|
+
}
|
|
58487
|
+
function buildSideEffectCounts(options) {
|
|
58488
|
+
const counts = {
|
|
58489
|
+
children_added: options.children_to_add?.length || 0,
|
|
58490
|
+
children_removed: options.children_to_remove?.length || 0,
|
|
58491
|
+
linked_items_added: options.linked_items_to_add?.length || 0,
|
|
58492
|
+
linked_items_removed: options.linked_items_to_remove?.length || 0
|
|
58493
|
+
};
|
|
58494
|
+
if (options.severity !== undefined)
|
|
58495
|
+
counts.severity = options.severity;
|
|
58496
|
+
if (options.escalation_status !== undefined)
|
|
58497
|
+
counts.escalation_status = options.escalation_status;
|
|
58498
|
+
return counts;
|
|
58499
|
+
}
|
|
58500
|
+
async function updateWorkItem(projectId, iid, options) {
|
|
58501
|
+
const { workItemGID, projectPath } = await resolveWorkItemGID(projectId, iid);
|
|
58502
|
+
const builder = await buildUpdateMutationFields(projectId, projectPath, workItemGID, options);
|
|
58503
|
+
const mutation = `mutation(${builder.varDefs.join(", ")}) {
|
|
58504
|
+
workItemUpdate(input: { ${builder.inputParts.join(", ")} }) {
|
|
58505
|
+
workItem {
|
|
58506
|
+
id iid title state webUrl workItemType { name }
|
|
58507
|
+
widgets {
|
|
58508
|
+
__typename
|
|
58509
|
+
... on WorkItemWidgetStatus { status { id name category color } }
|
|
58510
|
+
... on WorkItemWidgetLabels { labels { nodes { title } } }
|
|
58511
|
+
... on WorkItemWidgetAssignees { assignees { nodes { username } } }
|
|
58512
|
+
... on WorkItemWidgetWeight { weight }
|
|
58513
|
+
... on WorkItemWidgetHierarchy { parent { id title workItemType { name } } }
|
|
58514
|
+
... on WorkItemWidgetHealthStatus { healthStatus }
|
|
58515
|
+
... on WorkItemWidgetStartAndDueDate { startDate dueDate }
|
|
58516
|
+
... on WorkItemWidgetMilestone { milestone { id title } }
|
|
58517
|
+
}
|
|
58518
|
+
}
|
|
58519
|
+
errors
|
|
58520
|
+
}
|
|
58521
|
+
}`;
|
|
58522
|
+
const data = await defaultClient.graphql(mutation, builder.variables);
|
|
58523
|
+
if (data.workItemUpdate.errors?.length > 0) {
|
|
58524
|
+
throw new Error(`Failed to update work item: ${data.workItemUpdate.errors.join(", ")}`);
|
|
58525
|
+
}
|
|
58526
|
+
await handleUpdateSideEffects(workItemGID, projectPath, iid, options);
|
|
58527
|
+
return {
|
|
58528
|
+
...flattenUpdateWidgets(data.workItemUpdate.workItem),
|
|
58529
|
+
...buildSideEffectCounts(options)
|
|
58530
|
+
};
|
|
58531
|
+
}
|
|
58532
|
+
async function convertWorkItemType(projectId, iid, newType) {
|
|
58533
|
+
const { workItemGID, projectPath } = await resolveWorkItemGID(projectId, iid);
|
|
58534
|
+
const workItemTypeGID = await resolveWorkItemTypeGID(projectPath, newType);
|
|
58535
|
+
const data = await defaultClient.graphql(`mutation($id: WorkItemID!, $typeId: WorkItemsTypeID!) {
|
|
58536
|
+
workItemConvert(input: { id: $id, workItemTypeId: $typeId }) {
|
|
58537
|
+
workItem { id workItemType { name } }
|
|
58538
|
+
errors
|
|
58539
|
+
}
|
|
58540
|
+
}`, { id: workItemGID, typeId: workItemTypeGID });
|
|
58541
|
+
if (data.workItemConvert.errors?.length > 0) {
|
|
58542
|
+
throw new Error(`Conversion failed: ${data.workItemConvert.errors.join(", ")}`);
|
|
58543
|
+
}
|
|
58544
|
+
const converted = data.workItemConvert.workItem;
|
|
58545
|
+
if (!converted) {
|
|
58546
|
+
throw new Error("Conversion succeeded but returned no work item");
|
|
58547
|
+
}
|
|
58548
|
+
return {
|
|
58549
|
+
id: converted.id,
|
|
58550
|
+
type: converted.workItemType.name
|
|
58551
|
+
};
|
|
58552
|
+
}
|
|
58553
|
+
async function listWorkItemStatuses(projectId, workItemType = "issue") {
|
|
58554
|
+
const projectPath = await resolveProjectPath(projectId);
|
|
58555
|
+
const typeName = WORK_ITEM_TYPE_NAMES[workItemType] || "Issue";
|
|
58556
|
+
const data = await defaultClient.graphql(`query($path: ID!, $typeName: IssueType) {
|
|
58557
|
+
namespace(fullPath: $path) {
|
|
58558
|
+
workItemTypes(name: $typeName) {
|
|
58559
|
+
nodes {
|
|
58560
|
+
id name
|
|
58561
|
+
supportedConversionTypes { id name }
|
|
58562
|
+
widgetDefinitions {
|
|
58563
|
+
__typename
|
|
58564
|
+
... on WorkItemWidgetDefinitionStatus {
|
|
58565
|
+
allowedStatuses { id name iconName color position }
|
|
58566
|
+
}
|
|
58567
|
+
... on WorkItemWidgetDefinitionHierarchy {
|
|
58568
|
+
allowedChildTypes { nodes { id name } }
|
|
58569
|
+
allowedParentTypes { nodes { id name } }
|
|
58570
|
+
}
|
|
58571
|
+
}
|
|
58572
|
+
}
|
|
58573
|
+
}
|
|
58574
|
+
}
|
|
58575
|
+
}`, { path: projectPath, typeName: typeName.replace(/ /g, "_").toUpperCase() });
|
|
58576
|
+
const typeNodes = data.namespace?.workItemTypes?.nodes;
|
|
58577
|
+
if (!typeNodes?.length) {
|
|
58578
|
+
throw new Error(`Work item type '${typeName}' not found in project`);
|
|
58579
|
+
}
|
|
58580
|
+
const typeNode = typeNodes[0];
|
|
58581
|
+
const statusWidget = findWidget(typeNode.widgetDefinitions || [], "WorkItemWidgetDefinitionStatus");
|
|
58582
|
+
const statuses = statusWidget?.allowedStatuses || [];
|
|
58583
|
+
const hierarchyWidget = findWidget(typeNode.widgetDefinitions || [], "WorkItemWidgetDefinitionHierarchy");
|
|
58584
|
+
const result = {
|
|
58585
|
+
work_item_type: typeNode.name,
|
|
58586
|
+
statuses_available: statuses.length > 0,
|
|
58587
|
+
statuses
|
|
58588
|
+
};
|
|
58589
|
+
const conversionTypes = typeNode.supportedConversionTypes || [];
|
|
58590
|
+
if (conversionTypes.length > 0)
|
|
58591
|
+
result.supported_conversion_types = conversionTypes.map((t) => t.name);
|
|
58592
|
+
const childTypes = hierarchyWidget?.allowedChildTypes?.nodes || [];
|
|
58593
|
+
const parentTypes = hierarchyWidget?.allowedParentTypes?.nodes || [];
|
|
58594
|
+
if (childTypes.length > 0)
|
|
58595
|
+
result.allowed_child_types = childTypes.map((t) => t.name);
|
|
58596
|
+
if (parentTypes.length > 0)
|
|
58597
|
+
result.allowed_parent_types = parentTypes.map((t) => t.name);
|
|
58598
|
+
return result;
|
|
58599
|
+
}
|
|
58600
|
+
async function listCustomFieldDefinitions(projectId, workItemType = "issue") {
|
|
58601
|
+
const projectPath = await resolveProjectPath(projectId);
|
|
58602
|
+
const typeName = WORK_ITEM_TYPE_NAMES[workItemType] || "Issue";
|
|
58603
|
+
const data = await defaultClient.graphql(`query($path: ID!, $typeName: IssueType) {
|
|
58604
|
+
namespace(fullPath: $path) {
|
|
58605
|
+
workItemTypes(name: $typeName) {
|
|
58606
|
+
nodes {
|
|
58607
|
+
id name
|
|
58608
|
+
widgetDefinitions {
|
|
58609
|
+
__typename
|
|
58610
|
+
... on WorkItemWidgetDefinitionCustomFields {
|
|
58611
|
+
customFieldValues {
|
|
58612
|
+
customField { id name fieldType selectOptions { id value } workItemTypes { id name } }
|
|
58613
|
+
}
|
|
58614
|
+
}
|
|
58615
|
+
}
|
|
58616
|
+
}
|
|
58617
|
+
}
|
|
58618
|
+
}
|
|
58619
|
+
}`, { path: projectPath, typeName: typeName.replace(/ /g, "_").toUpperCase() });
|
|
58620
|
+
const typeNodes = data.namespace?.workItemTypes?.nodes;
|
|
58621
|
+
if (!typeNodes?.length) {
|
|
58622
|
+
throw new Error(`Work item type '${typeName}' not found in project`);
|
|
58623
|
+
}
|
|
58624
|
+
const typeNode = typeNodes[0];
|
|
58625
|
+
const cfWidget = findWidget(typeNode.widgetDefinitions || [], "WorkItemWidgetDefinitionCustomFields");
|
|
58626
|
+
const fields = (cfWidget?.customFieldValues || []).map((cfv) => {
|
|
58627
|
+
const cf = cfv.customField;
|
|
58628
|
+
const field = { id: cf?.id, name: cf?.name, type: cf?.fieldType };
|
|
58629
|
+
const opts = cf?.selectOptions || [];
|
|
58630
|
+
if (opts.length > 0)
|
|
58631
|
+
field.selectOptions = opts;
|
|
58632
|
+
const types = (cf?.workItemTypes || []).map((t) => t.name);
|
|
58633
|
+
if (types.length > 0)
|
|
58634
|
+
field.workItemTypes = types;
|
|
58635
|
+
return field;
|
|
58636
|
+
});
|
|
58637
|
+
return { work_item_type: typeNode.name, custom_fields: fields };
|
|
58638
|
+
}
|
|
58639
|
+
async function moveWorkItem(projectId, iid, targetProjectId) {
|
|
58640
|
+
const projectPath = await resolveProjectPath(projectId);
|
|
58641
|
+
const targetPath = await resolveProjectPath(targetProjectId);
|
|
58642
|
+
const data = await defaultClient.graphql(`mutation($projectPath: ID!, $iid: String!, $targetProjectPath: ID!) {
|
|
58643
|
+
issueMove(input: { projectPath: $projectPath, iid: $iid, targetProjectPath: $targetProjectPath }) {
|
|
58644
|
+
issue { id iid webUrl }
|
|
58645
|
+
errors
|
|
58646
|
+
}
|
|
58647
|
+
}`, { projectPath, iid: String(iid), targetProjectPath: targetPath });
|
|
58648
|
+
if (data.issueMove.errors?.length > 0) {
|
|
58649
|
+
throw new Error(`Failed to move work item: ${data.issueMove.errors.join(", ")}`);
|
|
58650
|
+
}
|
|
58651
|
+
return data.issueMove.issue;
|
|
58652
|
+
}
|
|
58653
|
+
async function listWorkItemNotes(projectId, iid, options = {}) {
|
|
58654
|
+
const projectPath = await resolveProjectPath(projectId);
|
|
58655
|
+
const data = await defaultClient.graphql(`query($path: ID!, $iid: String!, $pageSize: Int, $after: String, $sort: WorkItemDiscussionsSort) {
|
|
58656
|
+
namespace(fullPath: $path) {
|
|
58657
|
+
workItem(iid: $iid) {
|
|
58658
|
+
id
|
|
58659
|
+
widgets(onlyTypes: [NOTES]) {
|
|
58660
|
+
... on WorkItemWidgetNotes {
|
|
58661
|
+
discussionLocked
|
|
58662
|
+
discussions(first: $pageSize, after: $after, filter: ALL_NOTES, sort: $sort) {
|
|
58663
|
+
pageInfo { hasNextPage endCursor }
|
|
58664
|
+
nodes {
|
|
58665
|
+
id resolved resolvable
|
|
58666
|
+
notes {
|
|
58667
|
+
nodes { id body system internal createdAt lastEditedAt author { username } }
|
|
58668
|
+
}
|
|
58669
|
+
}
|
|
58670
|
+
}
|
|
58671
|
+
}
|
|
58672
|
+
}
|
|
58673
|
+
}
|
|
58674
|
+
}
|
|
58675
|
+
}`, {
|
|
58676
|
+
path: projectPath,
|
|
58677
|
+
iid: String(iid),
|
|
58678
|
+
pageSize: options.page_size || 20,
|
|
58679
|
+
after: options.after || null,
|
|
58680
|
+
sort: options.sort || "CREATED_ASC"
|
|
58681
|
+
});
|
|
58682
|
+
const workItem = data.namespace?.workItem;
|
|
58683
|
+
if (!workItem) {
|
|
58684
|
+
throw new Error(`Work item #${iid} not found in project ${projectPath}`);
|
|
58685
|
+
}
|
|
58686
|
+
const notesWidget = (workItem.widgets || []).find((w) => w.discussions);
|
|
58687
|
+
const discussions = notesWidget?.discussions;
|
|
58688
|
+
const items = (discussions?.nodes || []).map((d) => {
|
|
58689
|
+
const notes = (d.notes?.nodes || []).map((n) => {
|
|
58690
|
+
const note = {
|
|
58691
|
+
id: n.id,
|
|
58692
|
+
author: n.author?.username,
|
|
58693
|
+
body: n.body,
|
|
58694
|
+
createdAt: n.createdAt
|
|
58695
|
+
};
|
|
58696
|
+
if (n.system)
|
|
58697
|
+
note.system = true;
|
|
58698
|
+
if (n.internal)
|
|
58699
|
+
note.internal = true;
|
|
58700
|
+
if (n.lastEditedAt)
|
|
58701
|
+
note.lastEditedAt = n.lastEditedAt;
|
|
58702
|
+
return note;
|
|
58703
|
+
});
|
|
58704
|
+
const discussion = { id: d.id, notes };
|
|
58705
|
+
if (d.resolved)
|
|
58706
|
+
discussion.resolved = true;
|
|
58707
|
+
if (d.resolvable)
|
|
58708
|
+
discussion.resolvable = true;
|
|
58709
|
+
return discussion;
|
|
58710
|
+
});
|
|
58711
|
+
return { discussions: items, pageInfo: discussions?.pageInfo || {} };
|
|
58712
|
+
}
|
|
58713
|
+
async function createWorkItemNote(projectId, iid, body, options = {}) {
|
|
58714
|
+
const { workItemGID } = await resolveWorkItemGID(projectId, iid);
|
|
58715
|
+
const varDefs = ["$notableId: NotableID!", "$body: String!"];
|
|
58716
|
+
const inputParts = ["notableId: $notableId", "body: $body"];
|
|
58717
|
+
const variables = { notableId: workItemGID, body };
|
|
58718
|
+
if (options.internal) {
|
|
58719
|
+
varDefs.push("$internal: Boolean");
|
|
58720
|
+
inputParts.push("internal: $internal");
|
|
58721
|
+
variables.internal = true;
|
|
58722
|
+
}
|
|
58723
|
+
if (options.discussion_id) {
|
|
58724
|
+
varDefs.push("$discussionId: DiscussionID");
|
|
58725
|
+
inputParts.push("discussionId: $discussionId");
|
|
58726
|
+
variables.discussionId = options.discussion_id;
|
|
58727
|
+
}
|
|
58728
|
+
const data = await defaultClient.graphql(`mutation(${varDefs.join(", ")}) {
|
|
58729
|
+
createNote(input: { ${inputParts.join(", ")} }) {
|
|
58730
|
+
note { id body discussion { id } }
|
|
58731
|
+
errors
|
|
58732
|
+
}
|
|
58733
|
+
}`, variables);
|
|
58734
|
+
if (data.createNote.errors?.length > 0) {
|
|
58735
|
+
throw new Error(`Failed to create note: ${data.createNote.errors.join(", ")}`);
|
|
58736
|
+
}
|
|
58737
|
+
return data.createNote.note;
|
|
58738
|
+
}
|
|
58739
|
+
async function getTimelineEvents(projectId, incidentIid) {
|
|
58740
|
+
const { workItemGID, projectPath } = await resolveWorkItemGID(projectId, incidentIid);
|
|
58741
|
+
const incidentGID = workItemGID.replace("/WorkItem/", "/Issue/");
|
|
58742
|
+
const data = await defaultClient.graphql(`query($fullPath: ID!, $incidentId: IssueID!) {
|
|
58743
|
+
project(fullPath: $fullPath) {
|
|
58744
|
+
incidentManagementTimelineEvents(incidentId: $incidentId) {
|
|
58745
|
+
nodes { id note noteHtml action occurredAt createdAt timelineEventTags { nodes { id name } } }
|
|
58746
|
+
}
|
|
58747
|
+
}
|
|
58748
|
+
}`, { fullPath: projectPath, incidentId: incidentGID });
|
|
58749
|
+
const events = data.project?.incidentManagementTimelineEvents?.nodes || [];
|
|
58750
|
+
return events.map((e) => {
|
|
58751
|
+
const event = {
|
|
58752
|
+
id: e.id,
|
|
58753
|
+
note: e.note,
|
|
58754
|
+
action: e.action,
|
|
58755
|
+
occurredAt: e.occurredAt,
|
|
58756
|
+
createdAt: e.createdAt
|
|
58757
|
+
};
|
|
58758
|
+
if (e.noteHtml)
|
|
58759
|
+
event.noteHtml = e.noteHtml;
|
|
58760
|
+
const tags = (e.timelineEventTags?.nodes || []).map((t) => t.name);
|
|
58761
|
+
if (tags.length > 0)
|
|
58762
|
+
event.tags = tags;
|
|
58763
|
+
return event;
|
|
58764
|
+
});
|
|
58765
|
+
}
|
|
58766
|
+
async function createTimelineEvent(projectId, incidentIid, note, occurredAt, tagNames) {
|
|
58767
|
+
const { workItemGID } = await resolveWorkItemGID(projectId, incidentIid);
|
|
58768
|
+
const incidentGID = workItemGID.replace("/WorkItem/", "/Issue/");
|
|
58769
|
+
const input = { incidentId: incidentGID, note, occurredAt };
|
|
58770
|
+
if (tagNames?.length)
|
|
58771
|
+
input.timelineEventTagNames = tagNames;
|
|
58772
|
+
const data = await defaultClient.graphql(`mutation CreateTimelineEvent($input: TimelineEventCreateInput!) {
|
|
58773
|
+
timelineEventCreate(input: $input) {
|
|
58774
|
+
timelineEvent { id note noteHtml action occurredAt createdAt timelineEventTags { nodes { id name } } }
|
|
58775
|
+
errors
|
|
58776
|
+
}
|
|
58777
|
+
}`, { input });
|
|
58778
|
+
if (data.timelineEventCreate.errors?.length > 0) {
|
|
58779
|
+
throw new Error(`Failed to create timeline event: ${data.timelineEventCreate.errors.join(", ")}`);
|
|
58780
|
+
}
|
|
58781
|
+
const e = data.timelineEventCreate.timelineEvent;
|
|
58782
|
+
const result = {
|
|
58783
|
+
id: e.id,
|
|
58784
|
+
note: e.note,
|
|
58785
|
+
action: e.action,
|
|
58786
|
+
occurredAt: e.occurredAt,
|
|
58787
|
+
createdAt: e.createdAt
|
|
58788
|
+
};
|
|
58789
|
+
if (e.noteHtml)
|
|
58790
|
+
result.noteHtml = e.noteHtml;
|
|
58791
|
+
const tags = (e.timelineEventTags?.nodes || []).map((t) => t.name);
|
|
58792
|
+
if (tags.length > 0)
|
|
58793
|
+
result.tags = tags;
|
|
58794
|
+
return result;
|
|
58795
|
+
}
|
|
58796
|
+
function jsonResponse(data) {
|
|
58797
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
58798
|
+
}
|
|
58799
|
+
function registerWorkItemTools(server, logger2) {
|
|
58800
|
+
logger2.debug("Registering work item tools");
|
|
58801
|
+
const tools = new Map;
|
|
58802
|
+
const t1 = server.registerTool("get_work_item", {
|
|
58803
|
+
title: "Get Work Item",
|
|
58804
|
+
description: "Get a single work item with full details including status, hierarchy (parent/children), type, labels, assignees, and all widgets.",
|
|
58805
|
+
inputSchema: {
|
|
58806
|
+
project_id: exports_external.coerce.string().describe("Project ID or URL-encoded path"),
|
|
58807
|
+
iid: exports_external.coerce.number().describe("The internal ID (IID) of the work item")
|
|
58808
|
+
}
|
|
58809
|
+
}, async (params) => {
|
|
58810
|
+
const args = GetWorkItemSchema.parse(params);
|
|
58811
|
+
return jsonResponse(await getWorkItem(args.project_id, args.iid));
|
|
58812
|
+
});
|
|
58813
|
+
t1.disable();
|
|
58814
|
+
tools.set("get_work_item", t1);
|
|
58815
|
+
const t2 = server.registerTool("list_work_items", {
|
|
58816
|
+
title: "List Work Items",
|
|
58817
|
+
description: "List work items in a project with filters (type, state, search, assignees, labels). Returns items with status and hierarchy info.",
|
|
58818
|
+
inputSchema: {
|
|
58819
|
+
project_id: exports_external.coerce.string().describe("Project ID or URL-encoded path"),
|
|
58820
|
+
types: exports_external.array(workItemTypeEnum).optional().describe("Filter by work item types"),
|
|
58821
|
+
state: exports_external.enum(["opened", "closed"]).optional().describe("Filter by state"),
|
|
58822
|
+
search: exports_external.string().optional().describe("Search in title and description"),
|
|
58823
|
+
assignee_usernames: exports_external.array(exports_external.string()).optional().describe("Filter by assignee usernames"),
|
|
58824
|
+
label_names: exports_external.array(exports_external.string()).optional().describe("Filter by label names"),
|
|
58825
|
+
first: exports_external.coerce.number().optional().default(20).describe("Number of items to return (max 100)"),
|
|
58826
|
+
after: exports_external.string().optional().describe("Cursor for pagination")
|
|
58827
|
+
}
|
|
58828
|
+
}, async (params) => {
|
|
58829
|
+
const args = ListWorkItemsSchema.parse(params);
|
|
58830
|
+
const { project_id, ...opts } = args;
|
|
58831
|
+
return jsonResponse(await listWorkItems(project_id, opts));
|
|
58832
|
+
});
|
|
58833
|
+
t2.disable();
|
|
58834
|
+
tools.set("list_work_items", t2);
|
|
58835
|
+
const t3 = server.registerTool("create_work_item", {
|
|
58836
|
+
title: "Create Work Item",
|
|
58837
|
+
description: "Create a new work item (issue, task, incident, test_case, epic, key_result, objective, requirement, ticket). Supports setting title, description, labels, assignees, weight, parent, health status, start/due dates, milestone, and confidentiality.",
|
|
58838
|
+
inputSchema: {
|
|
58839
|
+
project_id: exports_external.coerce.string().describe("Project ID or URL-encoded path"),
|
|
58840
|
+
title: exports_external.string().describe("Title of the work item"),
|
|
58841
|
+
type: workItemTypeEnum.optional().default("issue").describe("Type of work item"),
|
|
58842
|
+
description: exports_external.string().optional().describe("Description (Markdown supported)"),
|
|
58843
|
+
labels: exports_external.array(exports_external.string()).optional().describe("Label names to assign"),
|
|
58844
|
+
assignee_usernames: exports_external.array(exports_external.string()).optional().describe("Usernames to assign"),
|
|
58845
|
+
parent_iid: exports_external.coerce.number().optional().describe("IID of parent work item"),
|
|
58846
|
+
weight: exports_external.coerce.number().optional().describe("Weight"),
|
|
58847
|
+
health_status: exports_external.enum(["onTrack", "needsAttention", "atRisk"]).optional().describe("Health status"),
|
|
58848
|
+
start_date: exports_external.string().optional().describe("Start date (YYYY-MM-DD)"),
|
|
58849
|
+
due_date: exports_external.string().optional().describe("Due date (YYYY-MM-DD)"),
|
|
58850
|
+
milestone_id: exports_external.string().optional().describe("Milestone ID"),
|
|
58851
|
+
iteration_id: exports_external.string().optional().describe("Iteration ID"),
|
|
58852
|
+
confidential: exports_external.coerce.boolean().optional().describe("Set confidentiality")
|
|
58853
|
+
}
|
|
58854
|
+
}, async (params) => {
|
|
58855
|
+
const args = CreateWorkItemSchema.parse(params);
|
|
58856
|
+
const { project_id, ...opts } = args;
|
|
58857
|
+
return jsonResponse(await createWorkItem(project_id, opts));
|
|
58858
|
+
});
|
|
58859
|
+
t3.disable();
|
|
58860
|
+
tools.set("create_work_item", t3);
|
|
58861
|
+
const t4 = server.registerTool("update_work_item", {
|
|
58862
|
+
title: "Update Work Item",
|
|
58863
|
+
description: "Update a work item. Can modify title, description, labels, assignees, weight, state, status, parent hierarchy, children, health status, start/due dates, milestone, confidentiality, linked items, and custom fields.",
|
|
58864
|
+
inputSchema: {
|
|
58865
|
+
project_id: exports_external.coerce.string().describe("Project ID or URL-encoded path"),
|
|
58866
|
+
iid: exports_external.coerce.number().describe("The internal ID (IID) of the work item"),
|
|
58867
|
+
title: exports_external.string().optional().describe("New title"),
|
|
58868
|
+
description: exports_external.string().optional().describe("New description"),
|
|
58869
|
+
add_labels: exports_external.array(exports_external.string()).optional().describe("Label names to add"),
|
|
58870
|
+
remove_labels: exports_external.array(exports_external.string()).optional().describe("Label names to remove"),
|
|
58871
|
+
assignee_usernames: exports_external.array(exports_external.string()).optional().describe("Set assignees by username"),
|
|
58872
|
+
state_event: exports_external.enum(["close", "reopen"]).optional().describe("Close or reopen"),
|
|
58873
|
+
weight: exports_external.coerce.number().optional().describe("Set weight"),
|
|
58874
|
+
status: exports_external.string().optional().describe("Set status by ID"),
|
|
58875
|
+
parent_iid: exports_external.coerce.number().optional().describe("Set parent by IID"),
|
|
58876
|
+
parent_project_id: exports_external.coerce.string().optional().describe("Parent project ID"),
|
|
58877
|
+
remove_parent: exports_external.coerce.boolean().optional().describe("Remove parent"),
|
|
58878
|
+
health_status: exports_external.enum(["onTrack", "needsAttention", "atRisk"]).optional().describe("Health status"),
|
|
58879
|
+
start_date: exports_external.string().optional().describe("Start date (YYYY-MM-DD)"),
|
|
58880
|
+
due_date: exports_external.string().optional().describe("Due date (YYYY-MM-DD)"),
|
|
58881
|
+
milestone_id: exports_external.string().optional().describe("Milestone ID"),
|
|
58882
|
+
iteration_id: exports_external.string().optional().describe("Iteration ID"),
|
|
58883
|
+
confidential: exports_external.coerce.boolean().optional().describe("Confidentiality"),
|
|
58884
|
+
severity: exports_external.enum(["UNKNOWN", "LOW", "MEDIUM", "HIGH", "CRITICAL"]).optional().describe("Incident severity"),
|
|
58885
|
+
escalation_status: exports_external.enum(["TRIGGERED", "ACKNOWLEDGED", "RESOLVED", "IGNORED"]).optional().describe("Incident escalation status")
|
|
58886
|
+
}
|
|
58887
|
+
}, async (params) => {
|
|
58888
|
+
const args = UpdateWorkItemSchema.parse(params);
|
|
58889
|
+
const { project_id, iid: wiIid, ...opts } = args;
|
|
58890
|
+
return jsonResponse(await updateWorkItem(project_id, wiIid, opts));
|
|
58891
|
+
});
|
|
58892
|
+
t4.disable();
|
|
58893
|
+
tools.set("update_work_item", t4);
|
|
58894
|
+
const t5 = server.registerTool("convert_work_item_type", {
|
|
58895
|
+
title: "Convert Work Item Type",
|
|
58896
|
+
description: "Convert a work item to a different type (e.g. issue to task, task to incident).",
|
|
58897
|
+
inputSchema: {
|
|
58898
|
+
project_id: exports_external.coerce.string().describe("Project ID or URL-encoded path"),
|
|
58899
|
+
iid: exports_external.coerce.number().describe("The internal ID of the work item"),
|
|
58900
|
+
new_type: workItemTypeEnum.describe("The target work item type")
|
|
58901
|
+
}
|
|
58902
|
+
}, async (params) => {
|
|
58903
|
+
const args = ConvertWorkItemTypeSchema.parse(params);
|
|
58904
|
+
return jsonResponse(await convertWorkItemType(args.project_id, args.iid, args.new_type));
|
|
58905
|
+
});
|
|
58906
|
+
t5.disable();
|
|
58907
|
+
tools.set("convert_work_item_type", t5);
|
|
58908
|
+
const t6 = server.registerTool("list_work_item_statuses", {
|
|
58909
|
+
title: "List Work Item Statuses",
|
|
58910
|
+
description: "List available statuses for a work item type in a project. Requires GitLab Premium/Ultimate with configurable statuses.",
|
|
58911
|
+
inputSchema: {
|
|
58912
|
+
project_id: exports_external.coerce.string().describe("Project ID or URL-encoded path"),
|
|
58913
|
+
work_item_type: workItemTypeEnum.optional().default("issue").describe("Work item type")
|
|
58914
|
+
}
|
|
58915
|
+
}, async (params) => {
|
|
58916
|
+
const args = ListWorkItemStatusesSchema.parse(params);
|
|
58917
|
+
return jsonResponse(await listWorkItemStatuses(args.project_id, args.work_item_type));
|
|
58918
|
+
});
|
|
58919
|
+
t6.disable();
|
|
58920
|
+
tools.set("list_work_item_statuses", t6);
|
|
58921
|
+
const t7 = server.registerTool("list_custom_field_definitions", {
|
|
58922
|
+
title: "List Custom Field Definitions",
|
|
58923
|
+
description: "List available custom field definitions for a work item type in a project. Returns field names, types, and IDs needed for setting custom fields via update_work_item.",
|
|
58924
|
+
inputSchema: {
|
|
58925
|
+
project_id: exports_external.coerce.string().describe("Project ID or URL-encoded path"),
|
|
58926
|
+
work_item_type: workItemTypeEnum.optional().default("issue").describe("Work item type")
|
|
58927
|
+
}
|
|
58928
|
+
}, async (params) => {
|
|
58929
|
+
const args = ListCustomFieldDefinitionsSchema.parse(params);
|
|
58930
|
+
return jsonResponse(await listCustomFieldDefinitions(args.project_id, args.work_item_type));
|
|
58931
|
+
});
|
|
58932
|
+
t7.disable();
|
|
58933
|
+
tools.set("list_custom_field_definitions", t7);
|
|
58934
|
+
const t8 = server.registerTool("move_work_item", {
|
|
58935
|
+
title: "Move Work Item",
|
|
58936
|
+
description: "Move a work item (issue, task, etc.) to a different project. Uses GitLab GraphQL issueMove mutation.",
|
|
58937
|
+
inputSchema: {
|
|
58938
|
+
project_id: exports_external.coerce.string().describe("Source project ID or path"),
|
|
58939
|
+
iid: exports_external.coerce.number().describe("The internal ID of the work item"),
|
|
58940
|
+
target_project_id: exports_external.coerce.string().describe("Target project ID or path")
|
|
58941
|
+
}
|
|
58942
|
+
}, async (params) => {
|
|
58943
|
+
const args = MoveWorkItemSchema.parse(params);
|
|
58944
|
+
return jsonResponse(await moveWorkItem(args.project_id, args.iid, args.target_project_id));
|
|
58945
|
+
});
|
|
58946
|
+
t8.disable();
|
|
58947
|
+
tools.set("move_work_item", t8);
|
|
58948
|
+
const t9 = server.registerTool("list_work_item_notes", {
|
|
58949
|
+
title: "List Work Item Notes",
|
|
58950
|
+
description: "List notes and discussions on a work item. Returns threaded discussions with author, body, timestamps, and system/internal flags.",
|
|
58951
|
+
inputSchema: {
|
|
58952
|
+
project_id: exports_external.coerce.string().describe("Project ID or URL-encoded path"),
|
|
58953
|
+
iid: exports_external.coerce.number().describe("The internal ID of the work item"),
|
|
58954
|
+
page_size: exports_external.coerce.number().optional().default(20).describe("Number of discussions to return"),
|
|
58955
|
+
after: exports_external.string().optional().describe("Cursor for pagination"),
|
|
58956
|
+
sort: exports_external.enum(["CREATED_ASC", "CREATED_DESC"]).optional().default("CREATED_ASC").describe("Sort order")
|
|
58957
|
+
}
|
|
58958
|
+
}, async (params) => {
|
|
58959
|
+
const args = ListWorkItemNotesSchema.parse(params);
|
|
58960
|
+
return jsonResponse(await listWorkItemNotes(args.project_id, args.iid, {
|
|
58961
|
+
page_size: args.page_size,
|
|
58962
|
+
after: args.after,
|
|
58963
|
+
sort: args.sort
|
|
58964
|
+
}));
|
|
58965
|
+
});
|
|
58966
|
+
t9.disable();
|
|
58967
|
+
tools.set("list_work_item_notes", t9);
|
|
58968
|
+
const t10 = server.registerTool("create_work_item_note", {
|
|
58969
|
+
title: "Create Work Item Note",
|
|
58970
|
+
description: "Add a note/comment to a work item. Supports Markdown, internal notes, and threaded replies.",
|
|
58971
|
+
inputSchema: {
|
|
58972
|
+
project_id: exports_external.coerce.string().describe("Project ID or URL-encoded path"),
|
|
58973
|
+
iid: exports_external.coerce.number().describe("The internal ID of the work item"),
|
|
58974
|
+
body: exports_external.string().describe("Note body (Markdown supported)"),
|
|
58975
|
+
internal: exports_external.coerce.boolean().optional().default(false).describe("Internal note"),
|
|
58976
|
+
discussion_id: exports_external.string().optional().describe("Discussion ID for threaded reply")
|
|
58977
|
+
}
|
|
58978
|
+
}, async (params) => {
|
|
58979
|
+
const args = CreateWorkItemNoteSchema.parse(params);
|
|
58980
|
+
return jsonResponse(await createWorkItemNote(args.project_id, args.iid, args.body, {
|
|
58981
|
+
internal: args.internal,
|
|
58982
|
+
discussion_id: args.discussion_id
|
|
58983
|
+
}));
|
|
58984
|
+
});
|
|
58985
|
+
t10.disable();
|
|
58986
|
+
tools.set("create_work_item_note", t10);
|
|
58987
|
+
const t11 = server.registerTool("get_timeline_events", {
|
|
58988
|
+
title: "Get Timeline Events",
|
|
58989
|
+
description: "List timeline events for an incident. Returns chronological events with notes, timestamps, and tags.",
|
|
58990
|
+
inputSchema: {
|
|
58991
|
+
project_id: exports_external.coerce.string().describe("Project ID or URL-encoded path"),
|
|
58992
|
+
incident_iid: exports_external.coerce.number().describe("The internal ID of the incident")
|
|
58993
|
+
}
|
|
58994
|
+
}, async (params) => {
|
|
58995
|
+
const args = GetTimelineEventsSchema.parse(params);
|
|
58996
|
+
return jsonResponse(await getTimelineEvents(args.project_id, args.incident_iid));
|
|
58997
|
+
});
|
|
58998
|
+
t11.disable();
|
|
58999
|
+
tools.set("get_timeline_events", t11);
|
|
59000
|
+
const t12 = server.registerTool("create_timeline_event", {
|
|
59001
|
+
title: "Create Timeline Event",
|
|
59002
|
+
description: "Create a timeline event on an incident. Supports tags: 'Start time', 'End time', 'Impact detected', 'Response initiated', 'Impact mitigated', 'Cause identified'.",
|
|
59003
|
+
inputSchema: {
|
|
59004
|
+
project_id: exports_external.coerce.string().describe("Project ID or URL-encoded path"),
|
|
59005
|
+
incident_iid: exports_external.coerce.number().describe("The internal ID of the incident"),
|
|
59006
|
+
note: exports_external.string().describe("Description of the timeline event (Markdown supported)"),
|
|
59007
|
+
occurred_at: exports_external.string().describe("When the event occurred (ISO 8601 format)"),
|
|
59008
|
+
tag_names: exports_external.array(exports_external.enum([
|
|
59009
|
+
"Start time",
|
|
59010
|
+
"End time",
|
|
59011
|
+
"Impact detected",
|
|
59012
|
+
"Response initiated",
|
|
59013
|
+
"Impact mitigated",
|
|
59014
|
+
"Cause identified"
|
|
59015
|
+
])).optional().describe("Timeline event tags")
|
|
59016
|
+
}
|
|
59017
|
+
}, async (params) => {
|
|
59018
|
+
const args = CreateTimelineEventSchema.parse(params);
|
|
59019
|
+
return jsonResponse(await createTimelineEvent(args.project_id, args.incident_iid, args.note, args.occurred_at, args.tag_names));
|
|
59020
|
+
});
|
|
59021
|
+
t12.disable();
|
|
59022
|
+
tools.set("create_timeline_event", t12);
|
|
59023
|
+
logger2.debug("Work item tools registered", { count: tools.size });
|
|
59024
|
+
return tools;
|
|
59025
|
+
}
|
|
57627
59026
|
// src/server/index.ts
|
|
57628
59027
|
function createMcpServer(config3, logger2) {
|
|
57629
59028
|
const mcpServer = new McpServer({
|
|
@@ -57646,6 +59045,7 @@ function createMcpServer(config3, logger2) {
|
|
|
57646
59045
|
toolsByCategory.set("search", registerSearchTools(mcpServer, logger2));
|
|
57647
59046
|
toolsByCategory.set("releases", registerReleaseTools(mcpServer, logger2));
|
|
57648
59047
|
toolsByCategory.set("webhooks", registerWebhookTools(mcpServer, logger2));
|
|
59048
|
+
toolsByCategory.set("work-items", registerWorkItemTools(mcpServer, logger2));
|
|
57649
59049
|
toolsByCategory.set("graphql", registerGraphqlTools(mcpServer, logger2));
|
|
57650
59050
|
if (config3.useGitlabWiki) {
|
|
57651
59051
|
toolsByCategory.set("wiki", registerWikiTools(mcpServer, logger2));
|