@@ -13,6 +13,8 @@ import {
13
13
PullRequestCommentRequest ,
14
14
PullRequestCommentResponse ,
15
15
PullRequestFileContent ,
16
+ WorkItemRelation ,
17
+ WorkItemLink ,
16
18
} from './types.js' ;
17
19
18
20
/**
@@ -297,6 +299,105 @@ class AzureDevOpsService {
297
299
return attachments ;
298
300
}
299
301
302
+ /**
303
+ * Get all links associated with a work item (parent, child, related, etc.)
304
+ * @param workItemId The ID of the work item
305
+ * @returns Object with work item links grouped by relationship type
306
+ */
307
+ async getWorkItemLinks ( workItemId : number ) : Promise < Record < string , WorkItemLink [ ] > > {
308
+ await this . initialize ( ) ;
309
+
310
+ if ( ! this . workItemClient ) {
311
+ throw new Error ( 'Work item client not initialized' ) ;
312
+ }
313
+
314
+ // Get work item with relations
315
+ const workItem = await this . workItemClient . getWorkItem (
316
+ workItemId ,
317
+ undefined ,
318
+ undefined ,
319
+ 4 // 4 = WorkItemExpand.Relations in the SDK
320
+ ) ;
321
+
322
+ if ( ! workItem || ! workItem . relations ) {
323
+ return { } ;
324
+ }
325
+
326
+ // Filter for work item link relations (exclude attachments and hyperlinks)
327
+ const linkRelations = workItem . relations . filter (
328
+ ( relation : any ) => relation . rel . includes ( 'Link' ) &&
329
+ relation . rel !== 'AttachedFile' &&
330
+ relation . rel !== 'Hyperlink'
331
+ ) ;
332
+
333
+ // Group relations by relationship type
334
+ const groupedRelations : Record < string , WorkItemLink [ ] > = { } ;
335
+
336
+ linkRelations . forEach ( ( relation : any ) => {
337
+ const relType = relation . rel ;
338
+
339
+ // Extract work item ID from URL
340
+ // URL format is typically like: https://dev.azure.com/{org}/{project}/_apis/wit/workItems/{id}
341
+ let targetId = 0 ;
342
+ try {
343
+ const urlParts = relation . url . split ( '/' ) ;
344
+ targetId = parseInt ( urlParts [ urlParts . length - 1 ] , 10 ) ;
345
+ } catch ( error ) {
346
+ console . error ( 'Failed to extract work item ID from URL:' , relation . url ) ;
347
+ }
348
+
349
+ if ( ! groupedRelations [ relType ] ) {
350
+ groupedRelations [ relType ] = [ ] ;
351
+ }
352
+
353
+ const workItemLink : WorkItemLink = {
354
+ ...relation ,
355
+ targetId,
356
+ title : relation . attributes ?. name || `Work Item ${ targetId } `
357
+ } ;
358
+
359
+ groupedRelations [ relType ] . push ( workItemLink ) ;
360
+ } ) ;
361
+
362
+ return groupedRelations ;
363
+ }
364
+
365
+ /**
366
+ * Get all linked work items with their full details
367
+ * @param workItemId The ID of the work item to get links from
368
+ * @returns Array of work items that are linked to the specified work item
369
+ */
370
+ async getLinkedWorkItems ( workItemId : number ) : Promise < WorkItem [ ] > {
371
+ await this . initialize ( ) ;
372
+
373
+ if ( ! this . workItemClient ) {
374
+ throw new Error ( 'Work item client not initialized' ) ;
375
+ }
376
+
377
+ // First get all links
378
+ const linkGroups = await this . getWorkItemLinks ( workItemId ) ;
379
+
380
+ // Extract all target IDs from all link groups
381
+ const linkedIds : number [ ] = [ ] ;
382
+
383
+ Object . values ( linkGroups ) . forEach ( links => {
384
+ links . forEach ( link => {
385
+ if ( link . targetId > 0 ) {
386
+ linkedIds . push ( link . targetId ) ;
387
+ }
388
+ } ) ;
389
+ } ) ;
390
+
391
+ // If no linked items found, return empty array
392
+ if ( linkedIds . length === 0 ) {
393
+ return [ ] ;
394
+ }
395
+
396
+ // Get the full work item details for all linked items
397
+ const linkedWorkItems = await this . getWorkItems ( linkedIds ) ;
398
+ return linkedWorkItems ;
399
+ }
400
+
300
401
/**
301
402
* Get detailed changes for a pull request
302
403
*/
0 commit comments