1
1
using System . Xml . XPath ;
2
2
using DotNet . DocsTools . GitHubObjects ;
3
3
using DotNet . DocsTools . GraphQLQueries ;
4
+ using Org . BouncyCastle . Asn1 . Ocsp ;
4
5
5
6
namespace Quest2GitHub ;
6
7
@@ -22,6 +23,7 @@ namespace Quest2GitHub;
22
23
/// <param name="areaPath">The area path for work items from this repo</param>
23
24
/// <param name="importTriggerLabelText">The text of the label that triggers an import</param>
24
25
/// <param name="importedLabelText">The text of the label that indicates an issue has been imported</param>
26
+ /// <param name="removeLinkItemText">The text of the label that indicates an issue should be removed</param>
25
27
/// <param name="parentNodes">A dictionary of label / parent ID pairs.</param>
26
28
/// <remarks>
27
29
/// The OAuth token takes precedence over the GitHub token, if both are
@@ -36,6 +38,7 @@ public class QuestGitHubService(
36
38
string areaPath ,
37
39
string importTriggerLabelText ,
38
40
string importedLabelText ,
41
+ string removeLinkItemText ,
39
42
List < ParentForLabel > parentNodes ,
40
43
IEnumerable < LabelToTagMap > tagMap ) : IDisposable
41
44
{
@@ -46,6 +49,7 @@ public class QuestGitHubService(
46
49
47
50
private GitHubLabel ? _importTriggerLabel ;
48
51
private GitHubLabel ? _importedLabel ;
52
+ private GitHubLabel ? _removeLinkedItemLabel ;
49
53
private QuestIteration [ ] ? _allIterations ;
50
54
51
55
/// <summary>
@@ -57,7 +61,7 @@ public class QuestGitHubService(
57
61
/// <returns></returns>
58
62
public async Task ProcessIssues ( string organization , string repository , int duration )
59
63
{
60
- if ( _importTriggerLabel is null || _importedLabel is null )
64
+ if ( _importTriggerLabel is null || _importedLabel is null || _removeLinkedItemLabel is null )
61
65
{
62
66
await RetrieveLabelIdsAsync ( organization , repository ) ;
63
67
}
@@ -97,23 +101,26 @@ async Task ProcessItems(IAsyncEnumerable<QuestIssueOrPullRequest> items)
97
101
{
98
102
await foreach ( QuestIssueOrPullRequest item in items )
99
103
{
100
- if ( item . Labels . Any ( l => ( l . Id == _importTriggerLabel ? . Id ) || ( l . Id == _importedLabel ? . Id ) ) )
104
+ if ( item . Labels . Any ( l => ( l . Id == _importTriggerLabel ? . Id ) || ( l . Id == _importedLabel ? . Id ) || ( l . Id == _removeLinkedItemLabel ? . Id ) ) )
101
105
{
106
+ bool request = item . Labels . Any ( l => l . Id == _importTriggerLabel ? . Id ) ;
107
+ bool sequestered = item . Labels . Any ( l => l . Id == _importedLabel ? . Id ) ;
108
+ bool vanquished = item . Labels . Any ( l => l . Id == _removeLinkedItemLabel ? . Id ) ;
109
+ // Only query AzDo if needed:
110
+ QuestWorkItem ? questItem = ( request || sequestered || vanquished )
111
+ ? await FindLinkedWorkItemAsync ( item )
112
+ : null ;
102
113
var issueProperties = new WorkItemProperties ( item , _allIterations , tagMap , parentNodes ) ;
103
114
104
115
Console . WriteLine ( $ "{ item . Number } : { item . Title } , { issueProperties . IssueLogString } ") ;
105
- // Console.WriteLine(item);
106
- QuestWorkItem ? questItem = await FindLinkedWorkItemAsync ( item ) ;
107
- if ( questItem != null )
116
+ Task workDone = ( request , sequestered , vanquished , questItem ) switch
108
117
{
109
- await QuestWorkItem . UpdateWorkItemAsync ( questItem , item , _azdoClient , _ospoClient , issueProperties ) ;
110
- }
111
- else
112
- {
113
- questItem = await LinkIssueAsync ( item , issueProperties ) ;
114
- // Because some fields can't be set in the initial creation, update the item.
115
- await QuestWorkItem . UpdateWorkItemAsync ( questItem , item , _azdoClient , _ospoClient , issueProperties ) ;
116
- }
118
+ ( false , false , false , _ ) => Task . CompletedTask , // No labels. Do nothing.
119
+ ( _, _, true , null ) => Task . CompletedTask , // Unlink, but no link. Do nothing.
120
+ ( _, _, false , null ) => LinkIssueAsync ( item , issueProperties ) , // No link, but one of the link labels was applied.
121
+ ( _, _, true , not null ) => questItem . RemoveWorkItem ( item , _azdoClient , issueProperties ) , // Unlink.
122
+ ( _, _, false , not null ) => questItem . UpdateWorkItemAsync ( item , _azdoClient , _ospoClient , issueProperties ) , // update
123
+ } ;
117
124
totalImport ++ ;
118
125
}
119
126
else
@@ -157,7 +164,7 @@ async IAsyncEnumerable<QuestIssueOrPullRequest> QueryAllOpenIssuesOrPullRequests
157
164
/// <returns>A task representing the current operation</returns>
158
165
public async Task ProcessIssue ( string gitHubOrganization , string gitHubRepository , int issueNumber )
159
166
{
160
- if ( _importTriggerLabel is null || _importedLabel is null )
167
+ if ( _importTriggerLabel is null || _importedLabel is null || _removeLinkedItemLabel is null )
161
168
{
162
169
await RetrieveLabelIdsAsync ( gitHubOrganization , gitHubRepository ) ;
163
170
}
@@ -183,41 +190,23 @@ public async Task ProcessIssue(string gitHubOrganization, string gitHubRepositor
183
190
// Evaluate the labels to determine the right action.
184
191
bool request = ghIssue . Labels . Any ( l => l . Id == _importTriggerLabel ? . Id ) ;
185
192
bool sequestered = ghIssue . Labels . Any ( l => l . Id == _importedLabel ? . Id ) ;
193
+ bool vanquished = ghIssue . Labels . Any ( l => l . Id == _removeLinkedItemLabel ? . Id ) ;
186
194
// Only query AzDo if needed:
187
- QuestWorkItem ? questItem = ( request || sequestered )
195
+ QuestWorkItem ? questItem = ( request || sequestered || vanquished )
188
196
? await FindLinkedWorkItemAsync ( ghIssue )
189
197
: null ;
190
198
191
199
var issueProperties = new WorkItemProperties ( ghIssue , _allIterations , tagMap , parentNodes ) ;
192
200
193
- // The order here is important to avoid a race condition that causes
194
- // an issue to be triggered multiple times.
195
- // First, if an issue is open and the trigger label is added, link or
196
- // update. Update is safe, because it will only update the quest issue's
197
- // state or assigned field. That can't trigger a new GH action run.
198
- if ( request )
199
- {
200
- if ( questItem is null )
201
- {
202
- questItem = await LinkIssueAsync ( ghIssue , issueProperties ) ;
203
- }
204
- else if ( questItem is not null )
205
- {
206
- // This allows a human to force a manual update: just add the trigger label.
207
- // Note that it updates even if the item is closed.
208
- await QuestWorkItem . UpdateWorkItemAsync ( questItem , ghIssue , _azdoClient , _ospoClient , issueProperties ) ;
209
-
210
- }
211
- // Next, if the item is already linked, consider any updates.
212
- // It's important that adding the linked label is the last
213
- // mutation done in the linking process. That way, the GH Action
214
- // does get triggered again. The second trigger will check for any updates
215
- // a human made to assigned or state while the initial run was taking place.
216
- }
217
- else if ( sequestered && questItem is not null )
201
+ Task workDone = ( request , sequestered , vanquished , questItem ) switch
218
202
{
219
- await QuestWorkItem . UpdateWorkItemAsync ( questItem , ghIssue , _azdoClient , _ospoClient , issueProperties ) ;
220
- }
203
+ ( false , false , false , _ ) => Task . CompletedTask , // No labels. Do nothing.
204
+ ( _, _, true , null ) => Task . CompletedTask , // Unlink, but no link. Do nothing.
205
+ ( _, _, false , null ) => LinkIssueAsync ( ghIssue , issueProperties ) , // No link, but one of the link labels was applied.
206
+ ( _, _, true , not null ) => questItem . RemoveWorkItem ( ghIssue , _azdoClient , issueProperties ) , // Unlink.
207
+ ( _, _, false , not null ) => questItem . UpdateWorkItemAsync ( ghIssue , _azdoClient , _ospoClient , issueProperties ) , // update
208
+ } ;
209
+ await workDone ;
221
210
}
222
211
223
212
/// <summary>
@@ -322,6 +311,9 @@ private async Task<QuestWorkItem> LinkIssueAsync(QuestIssueOrPullRequest issueOr
322
311
var prMutation = new Mutation < SequesteredPullRequestMutation , SequesterVariables > ( ghClient ) ;
323
312
await prMutation . PerformMutation ( new SequesterVariables ( pr . Id , _importTriggerLabel ? . Id ?? "" , _importedLabel ? . Id ?? "" , updatedBody ) ) ;
324
313
}
314
+
315
+ // Because some fields can't be set when an item is created, go through an update cycle:
316
+ await questItem . UpdateWorkItemAsync ( issueOrPullRequest , _azdoClient , _ospoClient , issueProperties ) ;
325
317
return questItem ;
326
318
}
327
319
else
@@ -338,6 +330,7 @@ private async Task RetrieveLabelIdsAsync(string org, string repo)
338
330
{
339
331
if ( label . Name == importTriggerLabelText ) _importTriggerLabel = label ;
340
332
if ( label . Name == importedLabelText ) _importedLabel = label ;
333
+ if ( label . Name == removeLinkItemText ) _removeLinkedItemLabel = label ;
341
334
}
342
335
}
343
336
0 commit comments