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