Skip to content

Commit 4992daf

Browse files
authored
Merge pull request #128 from microsoft/users/danhellem/fix-issue-69
Added description and resilency changes to link work item to pr. Use …
2 parents 87fccad + 1372f72 commit 4992daf

File tree

2 files changed

+82
-29
lines changed

2 files changed

+82
-29
lines changed

src/tools/workitems.ts

Lines changed: 16 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -304,21 +304,20 @@ function configureWorkItemTools(
304304
WORKITEM_TOOLS.link_work_item_to_pull_request,
305305
"Link a single work item to an existing pull request.",
306306
{
307-
project: z.string().describe,
307+
project: z.string().describe("The name or ID of the Azure DevOps project."),
308308
repositoryId: z.string().describe("The ID of the repository containing the pull request. Do not use the repository name here, use the ID instead."),
309309
pullRequestId: z.number().describe("The ID of the pull request to link to."),
310310
workItemId: z.number().describe("The ID of the work item to link to the pull request."),
311311
},
312312
async ({ project, repositoryId, pullRequestId, workItemId }) => {
313-
const connection = await connectionProvider();
314-
const workItemTrackingApi = await connection.getWorkItemTrackingApi();
315313
try {
314+
const connection = await connectionProvider();
315+
const workItemTrackingApi = await connection.getWorkItemTrackingApi();
316+
316317
// Create artifact link relation using vstfs format
317318
// Format: vstfs:///Git/PullRequestId/{project}/{repositoryId}/{pullRequestId}
318319
const artifactPathValue = `${project}/${repositoryId}/${pullRequestId}`;
319-
const vstfsUrl = `vstfs:///Git/PullRequestId/${encodeURIComponent(
320-
artifactPathValue
321-
)}`;
320+
const vstfsUrl = `vstfs:///Git/PullRequestId/${encodeURIComponent(artifactPathValue)}`;
322321

323322
// Use the PATCH document format for adding a relation
324323
const patchDocument = [
@@ -336,13 +335,17 @@ function configureWorkItemTools(
336335
];
337336

338337
// Use the WorkItem API to update the work item with the new relation
339-
await workItemTrackingApi.updateWorkItem(
338+
const workItem = await workItemTrackingApi.updateWorkItem(
340339
{},
341340
patchDocument,
342341
workItemId,
343342
project
344343
);
345344

345+
if (!workItem) {
346+
return { content: [{ type: "text", text: "Work item update failed" }], isError: true };
347+
}
348+
346349
return {
347350
content: [
348351
{
@@ -359,27 +362,12 @@ function configureWorkItemTools(
359362
},
360363
],
361364
};
362-
} catch (error) {
363-
console.error(
364-
`Error linking work item ${workItemId} to PR ${pullRequestId}:`,
365-
error
366-
);
367-
368-
return {
369-
content: [
370-
{
371-
type: "text",
372-
text: JSON.stringify(
373-
{
374-
workItemId,
375-
pullRequestId,
376-
success: false,
377-
},
378-
null,
379-
2
380-
),
381-
},
382-
],
365+
} catch (error) {
366+
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
367+
368+
return {
369+
content: [{ type: "text", text: `Error linking work item to pull request: ${errorMessage}` }],
370+
isError: true
383371
};
384372
}
385373
}

test/src/tools/workitems.test.ts

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,7 @@ describe("configureWorkItemTools", () => {
526526
if (!call)throw new Error("wit_link_work_item_to_pull_request tool not registered");
527527
const [, , , handler] = call;
528528

529-
(mockWorkItemTrackingApi.updateWorkItem as jest.Mock).mockResolvedValue([
529+
(mockWorkItemTrackingApi.updateWorkItem as jest.Mock).mockResolvedValue([
530530
_mockWorkItem,
531531
]);
532532

@@ -575,6 +575,71 @@ describe("configureWorkItemTools", () => {
575575
)
576576
);
577577
});
578+
579+
it("should handle errors from updateWorkItem and return a descriptive error", async () => {
580+
configureWorkItemTools(server, tokenProvider, connectionProvider);
581+
const call = (server.tool as jest.Mock).mock.calls.find(
582+
([toolName]) => toolName === "wit_link_work_item_to_pull_request"
583+
);
584+
585+
if (!call) throw new Error("wit_link_work_item_to_pull_request tool not registered");
586+
587+
const [, , , handler] = call;
588+
(mockWorkItemTrackingApi.updateWorkItem as jest.Mock).mockRejectedValue(new Error("API failure"));
589+
590+
const params = {
591+
project: "Contoso",
592+
repositoryId: 12345,
593+
pullRequestId: 67890,
594+
workItemId: 131489,
595+
};
596+
const result = await handler(params);
597+
598+
expect(result.isError).toBe(true);
599+
expect(result.content[0].text).toContain("API failure");
600+
});
601+
602+
it("should encode special characters in project and repositoryId for vstfsUrl", async () => {
603+
configureWorkItemTools(server, tokenProvider, connectionProvider);
604+
const call = (server.tool as jest.Mock).mock.calls.find(
605+
([toolName]) => toolName === "wit_link_work_item_to_pull_request"
606+
);
607+
if (!call) throw new Error("wit_link_work_item_to_pull_request tool not registered");
608+
609+
const [, , , handler] = call;
610+
(mockWorkItemTrackingApi.updateWorkItem as jest.Mock).mockResolvedValue([
611+
_mockWorkItem,
612+
]);
613+
614+
const params = {
615+
project: "Contoso Project",
616+
repositoryId: "repo/with/slash",
617+
pullRequestId: 67890,
618+
workItemId: 131489,
619+
};
620+
const artifactPathValue = `${params.project}/${params.repositoryId}/${params.pullRequestId}`;
621+
const vstfsUrl = `vstfs:///Git/PullRequestId/${encodeURIComponent(artifactPathValue)}`;
622+
const document = [
623+
{
624+
op: "add",
625+
path: "/relations/-",
626+
value: {
627+
rel: "ArtifactLink",
628+
url: vstfsUrl,
629+
attributes: {
630+
name: "Pull Request",
631+
},
632+
},
633+
},
634+
];
635+
await handler(params);
636+
expect(mockWorkItemTrackingApi.updateWorkItem).toHaveBeenCalledWith(
637+
{},
638+
document,
639+
params.workItemId,
640+
params.project,
641+
);
642+
});
578643
});
579644

580645
describe("get_work_items_for_iteration tool", () => {

0 commit comments

Comments
 (0)