@@ -22,6 +22,7 @@ namespace Quest2GitHub;
22
22
/// <param name="areaPath">The area path for work items from this repo</param>
23
23
/// <param name="importTriggerLabelText">The text of the label that triggers an import</param>
24
24
/// <param name="importedLabelText">The text of the label that indicates an issue has been imported</param>
25
+ /// <param name="removeLinkItemText">The text of the label that indicates an issue should be removed</param>
25
26
/// <param name="parentNodes">A dictionary of label / parent ID pairs.</param>
26
27
/// <remarks>
27
28
/// The OAuth token takes precedence over the GitHub token, if both are
@@ -36,6 +37,7 @@ public class QuestGitHubService(
36
37
string areaPath ,
37
38
string importTriggerLabelText ,
38
39
string importedLabelText ,
40
+ string removeLinkItemText ,
39
41
List < ParentForLabel > parentNodes ,
40
42
IEnumerable < LabelToTagMap > tagMap ) : IDisposable
41
43
{
@@ -46,6 +48,7 @@ public class QuestGitHubService(
46
48
47
49
private GitHubLabel ? _importTriggerLabel ;
48
50
private GitHubLabel ? _importedLabel ;
51
+ private GitHubLabel ? _removeLinkedItemLabel ;
49
52
private QuestIteration [ ] ? _allIterations ;
50
53
51
54
/// <summary>
@@ -57,7 +60,7 @@ public class QuestGitHubService(
57
60
/// <returns></returns>
58
61
public async Task ProcessIssues ( string organization , string repository , int duration )
59
62
{
60
- if ( _importTriggerLabel is null || _importedLabel is null )
63
+ if ( _importTriggerLabel is null || _importedLabel is null || _removeLinkedItemLabel is null )
61
64
{
62
65
await RetrieveLabelIdsAsync ( organization , repository ) ;
63
66
}
@@ -97,7 +100,7 @@ async Task ProcessItems(IAsyncEnumerable<QuestIssueOrPullRequest> items)
97
100
{
98
101
await foreach ( QuestIssueOrPullRequest item in items )
99
102
{
100
- if ( item . Labels . Any ( l => ( l . Id == _importTriggerLabel ? . Id ) || ( l . Id == _importedLabel ? . Id ) ) )
103
+ if ( item . Labels . Any ( l => ( l . Id == _importTriggerLabel ? . Id ) || ( l . Id == _importedLabel ? . Id ) || ( l . Id == _removeLinkedItemLabel ? . Id ) ) )
101
104
{
102
105
var issueProperties = new WorkItemProperties ( item , _allIterations , tagMap , parentNodes ) ;
103
106
@@ -157,7 +160,7 @@ async IAsyncEnumerable<QuestIssueOrPullRequest> QueryAllOpenIssuesOrPullRequests
157
160
/// <returns>A task representing the current operation</returns>
158
161
public async Task ProcessIssue ( string gitHubOrganization , string gitHubRepository , int issueNumber )
159
162
{
160
- if ( _importTriggerLabel is null || _importedLabel is null )
163
+ if ( _importTriggerLabel is null || _importedLabel is null || _removeLinkedItemLabel is null )
161
164
{
162
165
await RetrieveLabelIdsAsync ( gitHubOrganization , gitHubRepository ) ;
163
166
}
@@ -183,41 +186,23 @@ public async Task ProcessIssue(string gitHubOrganization, string gitHubRepositor
183
186
// Evaluate the labels to determine the right action.
184
187
bool request = ghIssue . Labels . Any ( l => l . Id == _importTriggerLabel ? . Id ) ;
185
188
bool sequestered = ghIssue . Labels . Any ( l => l . Id == _importedLabel ? . Id ) ;
189
+ bool vanquished = ghIssue . Labels . Any ( l => l . Id == _removeLinkedItemLabel ? . Id ) ;
186
190
// Only query AzDo if needed:
187
- QuestWorkItem ? questItem = ( request || sequestered )
191
+ QuestWorkItem ? questItem = ( request || sequestered || vanquished )
188
192
? await FindLinkedWorkItemAsync ( ghIssue )
189
193
: null ;
190
194
191
195
var issueProperties = new WorkItemProperties ( ghIssue , _allIterations , tagMap , parentNodes ) ;
192
196
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 )
197
+ Task workDone = ( request , sequestered , vanquished , questItem ) switch
199
198
{
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 )
218
- {
219
- await QuestWorkItem . UpdateWorkItemAsync ( questItem , ghIssue , _azdoClient , _ospoClient , issueProperties ) ;
220
- }
199
+ ( false , false , false , _ ) => Task . CompletedTask , // No labels. Do nothing.
200
+ ( _, _, true , null ) => Task . CompletedTask , // Unlink, but no link. Do nothing.
201
+ ( _, _, false , null ) => LinkIssueAsync ( ghIssue , issueProperties ) , // No link, but one of the link labels was applied.
202
+ ( _, _, true , not null ) => questItem . RemoveWorkItem ( ghIssue , _azdoClient , issueProperties ) , // Unlink.
203
+ ( _, _, false , not null ) => questItem . UpdateWorkItemAsync ( ghIssue , _azdoClient , _ospoClient , issueProperties ) , // update
204
+ } ;
205
+ await workDone ;
221
206
}
222
207
223
208
/// <summary>
@@ -338,6 +323,7 @@ private async Task RetrieveLabelIdsAsync(string org, string repo)
338
323
{
339
324
if ( label . Name == importTriggerLabelText ) _importTriggerLabel = label ;
340
325
if ( label . Name == importedLabelText ) _importedLabel = label ;
326
+ if ( label . Name == removeLinkItemText ) _removeLinkedItemLabel = label ;
341
327
}
342
328
}
343
329
0 commit comments