@@ -22,10 +22,12 @@ import { Logger } from '../../../../system/logger';
22
22
import type { LogScope } from '../../../../system/logger.scope' ;
23
23
import { getLogScope } from '../../../../system/logger.scope' ;
24
24
import { maybeStopWatch } from '../../../../system/stopwatch' ;
25
- import type { WorkItem } from './models' ;
25
+ import type { AzureWorkItemState , AzureWorkItemStateCategory , WorkItem } from './models' ;
26
+ import { azureWorkItemsStateCategoryToState , isClosedAzureWorkItemStateCategory } from './models' ;
26
27
27
28
export class AzureDevOpsApi implements Disposable {
28
29
private readonly _disposable : Disposable ;
30
+ private _workItemStates : WorkItemStates = new WorkItemStates ( ) ;
29
31
30
32
constructor ( _container : Container ) {
31
33
this . _disposable = configuration . onDidChangeAny ( e => {
@@ -54,8 +56,7 @@ export class AzureDevOpsApi implements Disposable {
54
56
55
57
private resetCaches ( ) : void {
56
58
this . _proxyAgent = null ;
57
- // this._defaults.clear();
58
- // this._enterpriseVersions.clear();
59
+ this . _workItemStates . clear ( ) ;
59
60
}
60
61
61
62
@debug < AzureDevOpsApi [ 'getIssueOrPullRequest' ] > ( { args : { 0 : p => p . name , 1 : '<token>' } } )
@@ -86,15 +87,27 @@ export class AzureDevOpsApi implements Disposable {
86
87
) ;
87
88
88
89
if ( issueResult != null ) {
90
+ const issueType = issueResult . fields [ 'System.WorkItemType' ] ;
91
+ const state = issueResult . fields [ 'System.State' ] ;
92
+ const stateCategory = await this . getWorkItemStateCategory (
93
+ issueType ,
94
+ state ,
95
+ provider ,
96
+ token ,
97
+ owner ,
98
+ repo ,
99
+ options ,
100
+ ) ;
101
+
89
102
return {
90
103
id : issueResult . id . toString ( ) ,
91
104
type : 'issue' ,
92
105
nodeId : issueResult . id . toString ( ) ,
93
106
provider : provider ,
94
107
createdDate : new Date ( issueResult . fields [ 'System.CreatedDate' ] ) ,
95
108
updatedDate : new Date ( issueResult . fields [ 'System.ChangedDate' ] ) ,
96
- state : issueResult . fields [ 'System.State' ] === 'Closed' ? 'closed' : 'opened' ,
97
- closed : issueResult . fields [ 'System.State' ] === 'Closed' ,
109
+ state : azureWorkItemsStateCategoryToState ( stateCategory ) ,
110
+ closed : isClosedAzureWorkItemStateCategory ( stateCategory ) ,
98
111
title : issueResult . fields [ 'System.Title' ] ,
99
112
url : issueResult . _links . html . href ,
100
113
} ;
@@ -107,6 +120,60 @@ export class AzureDevOpsApi implements Disposable {
107
120
}
108
121
}
109
122
123
+ public async getWorkItemStateCategory (
124
+ issueType : string ,
125
+ state : string ,
126
+ provider : Provider ,
127
+ token : string ,
128
+ owner : string ,
129
+ repo : string ,
130
+ options : {
131
+ baseUrl : string ;
132
+ } ,
133
+ ) : Promise < AzureWorkItemStateCategory | undefined > {
134
+ const [ projectName ] = repo . split ( '/' ) ;
135
+ const project = `${ owner } /${ projectName } ` ;
136
+ const category = this . _workItemStates . getStateCategory ( project , issueType , state ) ;
137
+ if ( category != null ) return category ;
138
+
139
+ const states = await this . retrieveWorkItemTypeStates ( issueType , provider , token , owner , repo , options ) ;
140
+ this . _workItemStates . saveTypeStates ( project , issueType , states ) ;
141
+
142
+ return this . _workItemStates . getStateCategory ( project , issueType , state ) ;
143
+ }
144
+
145
+ private async retrieveWorkItemTypeStates (
146
+ workItemType : string ,
147
+ provider : Provider ,
148
+ token : string ,
149
+ owner : string ,
150
+ repo : string ,
151
+ options : {
152
+ baseUrl : string ;
153
+ } ,
154
+ ) : Promise < AzureWorkItemState [ ] > {
155
+ const scope = getLogScope ( ) ;
156
+ const [ projectName ] = repo . split ( '/' ) ;
157
+
158
+ try {
159
+ const issueResult = await this . request < { value : AzureWorkItemState [ ] ; count : number } > (
160
+ provider ,
161
+ token ,
162
+ options ?. baseUrl ,
163
+ `${ owner } /${ projectName } /_apis/wit/workItemTypes/${ workItemType } /states` ,
164
+ {
165
+ method : 'GET' ,
166
+ } ,
167
+ scope ,
168
+ ) ;
169
+
170
+ return issueResult ?. value ?? [ ] ;
171
+ } catch ( ex ) {
172
+ Logger . error ( ex , scope ) ;
173
+ return [ ] ;
174
+ }
175
+ }
176
+
110
177
private async request < T > (
111
178
provider : Provider ,
112
179
token : string ,
@@ -227,3 +294,51 @@ export class AzureDevOpsApi implements Disposable {
227
294
}
228
295
}
229
296
}
297
+
298
+ class WorkItemStates {
299
+ private readonly _categories = new Map < string , AzureWorkItemStateCategory > ( ) ;
300
+ private readonly _types = new Map < string , AzureWorkItemState [ ] > ( ) ;
301
+
302
+ // TODO@sergeibbb : we might need some logic for invalidating
303
+ public getStateCategory (
304
+ project : string ,
305
+ workItemType : string ,
306
+ stateName : string ,
307
+ ) : AzureWorkItemStateCategory | undefined {
308
+ return this . _categories . get ( this . getStateKey ( project , workItemType , stateName ) ) ;
309
+ }
310
+
311
+ public clear ( ) : void {
312
+ this . _categories . clear ( ) ;
313
+ this . _types . clear ( ) ;
314
+ }
315
+
316
+ public saveTypeStates ( project : string , workItemType : string , states : AzureWorkItemState [ ] ) : void {
317
+ this . clearTypeStates ( project , workItemType ) ;
318
+ this . _types . set ( this . getTypeKey ( project , workItemType ) , states ) ;
319
+ for ( const state of states ) {
320
+ this . _categories . set ( this . getStateKey ( project , workItemType , state . name ) , state . category ) ;
321
+ }
322
+ }
323
+
324
+ public hasTypeStates ( project : string , workItemType : string ) : boolean {
325
+ return this . _types . has ( this . getTypeKey ( project , workItemType ) ) ;
326
+ }
327
+
328
+ private clearTypeStates ( project : string , workItemType : string ) : void {
329
+ const states = this . _types . get ( this . getTypeKey ( project , workItemType ) ) ;
330
+ if ( states == null ) return ;
331
+ for ( const state of states ) {
332
+ this . _categories . delete ( this . getStateKey ( project , workItemType , state . name ) ) ;
333
+ }
334
+ }
335
+
336
+ private getStateKey ( project : string , workItemType : string , stateName : string ) : string {
337
+ // By stringifying the pair as JSON we make sure that all possible special characters are escaped
338
+ return JSON . stringify ( [ project , workItemType , stateName ] ) ;
339
+ }
340
+
341
+ private getTypeKey ( project : string , workItemType : string ) : string {
342
+ return JSON . stringify ( [ project , workItemType ] ) ;
343
+ }
344
+ }
0 commit comments