1
1
using DotNet . DocsTools . GitHubObjects ;
2
2
using DotNet . DocsTools . Utility ;
3
+ using Microsoft . DotnetOrg . Ospo ;
3
4
4
5
namespace Quest2GitHub . Models ;
5
6
@@ -123,7 +124,6 @@ public static async Task<QuestWorkItem> QueryWorkItem(QuestClient client, int wo
123
124
/// Create a work item from a GitHub issue.
124
125
/// </summary>
125
126
/// <param name="issue">The GitHub issue.</param>
126
- /// <param name="parentId">The ID of the parent ID</param>
127
127
/// <param name="questClient">The quest client.</param>
128
128
/// <param name="ospoClient">the MS open source programs office client.</param>
129
129
/// <param name="path">The path component for the area path.</param>
@@ -139,16 +139,18 @@ public static async Task<QuestWorkItem> QueryWorkItem(QuestClient client, int wo
139
139
/// Json element.
140
140
/// </remarks>
141
141
public static async Task < QuestWorkItem > CreateWorkItemAsync ( QuestIssueOrPullRequest issue ,
142
- int parentId ,
143
142
QuestClient questClient ,
144
143
OspoClient ? ospoClient ,
145
144
string path ,
146
145
string ? requestLabelNodeId ,
147
146
QuestIteration currentIteration ,
148
147
IEnumerable < QuestIteration > allIterations ,
149
- IEnumerable < LabelToTagMap > tagMap )
148
+ IEnumerable < LabelToTagMap > tagMap ,
149
+ IEnumerable < ParentForLabel > parentNodes ,
150
+ int defaultParentNode )
150
151
{
151
152
string areaPath = $ """ { questClient . QuestProject } \{ path } """ ;
153
+ int parentId = ParentIdFromIssue ( parentNodes , issue , defaultParentNode , allIterations ) ;
152
154
153
155
List < JsonPatchDocument > patchDocument =
154
156
[
@@ -399,6 +401,180 @@ public static string BuildDescriptionFromIssue(QuestIssueOrPullRequest issue, st
399
401
}
400
402
}
401
403
404
+ static internal async Task < QuestWorkItem ? > UpdateWorkItemAsync ( QuestWorkItem questItem ,
405
+ QuestIssueOrPullRequest ghIssue ,
406
+ QuestClient questClient ,
407
+ OspoClient ? ospoClient ,
408
+ IEnumerable < QuestIteration > allIterations ,
409
+ IEnumerable < LabelToTagMap > tagMap ,
410
+ IEnumerable < ParentForLabel > parentNodes ,
411
+ int defaultParentNode )
412
+ {
413
+ int parentId = ParentIdFromIssue ( parentNodes , ghIssue , defaultParentNode , allIterations ) ;
414
+ string ? ghAssigneeEmailAddress = await ghIssue . QueryAssignedMicrosoftEmailAddressAsync ( ospoClient ) ;
415
+ AzDoIdentity ? questAssigneeID = default ;
416
+ var proposedQuestState = questItem . State ;
417
+ if ( ghAssigneeEmailAddress ? . EndsWith ( "@microsoft.com" ) == true )
418
+ {
419
+ questAssigneeID = await questClient . GetIDFromEmail ( ghAssigneeEmailAddress ) ;
420
+ }
421
+ List < JsonPatchDocument > patchDocument = [ ] ;
422
+ if ( ( parentId != 0 ) && ( parentId != questItem . ParentWorkItemId ) )
423
+ {
424
+ if ( questItem . ParentWorkItemId != 0 )
425
+ {
426
+ // Remove the existing parent relation.
427
+ patchDocument . Add ( new JsonPatchDocument
428
+ {
429
+ Operation = Op . Remove ,
430
+ Path = "/relations/" + questItem . ParentRelationIndex ,
431
+ } ) ;
432
+ } ;
433
+ var parentRelation = new Relation
434
+ {
435
+ RelationName = "System.LinkTypes.Hierarchy-Reverse" ,
436
+ Url = $ "https://dev.azure.com/{ questClient . QuestOrg } /{ questClient . QuestProject } /_apis/wit/workItems/{ parentId } ",
437
+ Attributes =
438
+ {
439
+ [ "name" ] = "Parent" ,
440
+ [ "isLocked" ] = false
441
+ }
442
+ } ;
443
+
444
+ patchDocument . Add ( new JsonPatchDocument
445
+ {
446
+ Operation = Op . Add ,
447
+ Path = "/relations/-" ,
448
+ From = default ,
449
+ Value = parentRelation
450
+ } ) ;
451
+ }
452
+ if ( ( questAssigneeID is not null ) && ( questAssigneeID ? . Id != questItem . AssignedToId ) )
453
+ {
454
+ // build patch document for assignment.
455
+ JsonPatchDocument assignPatch = new ( )
456
+ {
457
+ Operation = Op . Add ,
458
+ Path = "/fields/System.AssignedTo" ,
459
+ Value = questAssigneeID ,
460
+ } ;
461
+ patchDocument . Add ( assignPatch ) ;
462
+ }
463
+ bool questItemOpen = questItem . State is not "Closed" ;
464
+ proposedQuestState = ghIssue . IsOpen ? "Committed" : "Closed" ;
465
+ if ( ghIssue . IsOpen != questItemOpen )
466
+ {
467
+
468
+ // When the issue is opened or closed,
469
+ // update the description. That picks up any new
470
+ // labels and comments.
471
+ patchDocument . Add ( new JsonPatchDocument
472
+ {
473
+ Operation = Op . Add ,
474
+ Path = "/fields/System.Description" ,
475
+ From = default ,
476
+ Value = BuildDescriptionFromIssue ( ghIssue , null )
477
+ } ) ;
478
+ }
479
+ StoryPointSize ? iterationSize = ghIssue . LatestStoryPointSize ( ) ;
480
+ QuestIteration ? iteration = iterationSize ? . ProjectIteration ( allIterations ) ;
481
+ if ( iterationSize != null )
482
+ {
483
+ Console . WriteLine ( $ "Latest GitHub sprint project: { iterationSize ? . Month } -{ iterationSize ? . CalendarYear } , size: { iterationSize ? . Size } ") ;
484
+ if ( ( iterationSize ? . IsPastIteration == true ) && ( ghIssue . IsOpen == true ) )
485
+ {
486
+ Console . WriteLine ( $ "Moving to the backlog / future iteration.") ;
487
+ iteration = QuestIteration . FutureIteration ( allIterations ) ;
488
+ proposedQuestState = "New" ;
489
+ }
490
+ }
491
+ else
492
+ {
493
+ Console . WriteLine ( "No GitHub sprint project found - using current iteration." ) ;
494
+ }
495
+ if ( proposedQuestState != questItem . State )
496
+ {
497
+ patchDocument . Add ( new JsonPatchDocument
498
+ {
499
+ Operation = Op . Add ,
500
+ Path = "/fields/System.State" ,
501
+ Value = proposedQuestState ,
502
+ } ) ;
503
+ }
504
+ if ( ( iteration is not null ) && ( iteration . Path != questItem . IterationPath ) )
505
+ {
506
+ patchDocument . Add ( new JsonPatchDocument
507
+ {
508
+ Operation = Op . Add ,
509
+ Path = "/fields/System.IterationPath" ,
510
+ Value = iteration . Path ,
511
+ } ) ;
512
+ }
513
+ if ( ( iterationSize ? . QuestStoryPoint ( ) is not null ) && ( iterationSize . QuestStoryPoint ( ) != questItem . StoryPoints ) )
514
+ {
515
+ patchDocument . Add ( new JsonPatchDocument
516
+ {
517
+ Operation = Op . Add ,
518
+ From = default ,
519
+ Path = "/fields/Microsoft.VSTS.Scheduling.StoryPoints" ,
520
+ Value = iterationSize . QuestStoryPoint ( ) ,
521
+ } ) ;
522
+ }
523
+ int ? priority = ghIssue . GetPriority ( iterationSize ) ;
524
+ if ( priority . HasValue && priority != questItem . Priority )
525
+ {
526
+ patchDocument . Add ( new JsonPatchDocument
527
+ {
528
+ Operation = Op . Add ,
529
+ Path = "/fields/Microsoft.VSTS.Common.Priority" ,
530
+ Value = priority . Value
531
+ } ) ;
532
+ }
533
+ var tags = from t in ghIssue . WorkItemTagsForIssue ( tagMap )
534
+ where ! questItem . Tags . Contains ( t )
535
+ select t ;
536
+ if ( tags . Any ( ) )
537
+ {
538
+ string azDoTags = string . Join ( ";" , tags ) ;
539
+ patchDocument . Add ( new JsonPatchDocument
540
+ {
541
+ Operation = Op . Add ,
542
+ Path = "/fields/System.Tags" ,
543
+ Value = azDoTags
544
+ } ) ;
545
+ }
546
+
547
+ QuestWorkItem ? newItem = default ;
548
+ if ( patchDocument . Count != 0 )
549
+ {
550
+ JsonElement jsonDocument = await questClient . PatchWorkItem ( questItem . Id , patchDocument ) ;
551
+ newItem = QuestWorkItem . WorkItemFromJson ( jsonDocument ) ;
552
+ }
553
+ if ( ! ghIssue . IsOpen && ( ghIssue . ClosingPRUrl is not null ) )
554
+ {
555
+ newItem = await questItem . AddClosingPR ( questClient , ghIssue . ClosingPRUrl ) ?? newItem ;
556
+ }
557
+ return newItem ;
558
+ }
559
+
560
+ static private int ParentIdFromIssue ( IEnumerable < ParentForLabel > parentNodes , QuestIssueOrPullRequest ghIssue , int defaultParentNode , IEnumerable < QuestIteration > allIterations )
561
+ {
562
+ var iteration = ghIssue . LatestStoryPointSize ( ) ? . ProjectIteration ( allIterations ) ;
563
+
564
+ foreach ( ParentForLabel pair in parentNodes )
565
+ {
566
+ if ( ghIssue . Labels . Any ( l => l . Name == pair . Label ) || ( pair . Label is null ) )
567
+ {
568
+ if ( ( pair . Semester is null ) || ( iteration ? . IsInSemester ( pair . Semester ) is true ) )
569
+ {
570
+ return pair . ParentNodeId ;
571
+ }
572
+ }
573
+ }
574
+ return defaultParentNode ;
575
+ }
576
+
577
+
402
578
/// <summary>
403
579
/// Construct a work item from the JSON document.
404
580
/// </summary>
0 commit comments