@@ -22,10 +22,12 @@ import { Logger } from '../../../../system/logger';
2222import type { LogScope } from '../../../../system/logger.scope' ;
2323import { getLogScope } from '../../../../system/logger.scope' ;
2424import { maybeStopWatch } from '../../../../system/stopwatch' ;
25- import type { WorkItem } from './models' ;
25+ import type { AzureWorkItemState , AzureWorkItemStateCategory , WorkItem } from './models' ;
26+ import { azureWorkItemsStateCategoryToState , isClosedAzureWorkItemStateCategory } from './models' ;
2627
2728export class AzureDevOpsApi implements Disposable {
2829 private readonly _disposable : Disposable ;
30+ private _workItemStates : WorkItemStates = new WorkItemStates ( ) ;
2931
3032 constructor ( _container : Container ) {
3133 this . _disposable = configuration . onDidChangeAny ( e => {
@@ -54,8 +56,7 @@ export class AzureDevOpsApi implements Disposable {
5456
5557 private resetCaches ( ) : void {
5658 this . _proxyAgent = null ;
57- // this._defaults.clear();
58- // this._enterpriseVersions.clear();
59+ this . _workItemStates . clear ( ) ;
5960 }
6061
6162 @debug < AzureDevOpsApi [ 'getIssueOrPullRequest' ] > ( { args : { 0 : p => p . name , 1 : '<token>' } } )
@@ -86,15 +87,27 @@ export class AzureDevOpsApi implements Disposable {
8687 ) ;
8788
8889 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+
89102 return {
90103 id : issueResult . id . toString ( ) ,
91104 type : 'issue' ,
92105 nodeId : issueResult . id . toString ( ) ,
93106 provider : provider ,
94107 createdDate : new Date ( issueResult . fields [ 'System.CreatedDate' ] ) ,
95108 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 ) ,
98111 title : issueResult . fields [ 'System.Title' ] ,
99112 url : issueResult . _links . html . href ,
100113 } ;
@@ -107,6 +120,60 @@ export class AzureDevOpsApi implements Disposable {
107120 }
108121 }
109122
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+
110177 private async request < T > (
111178 provider : Provider ,
112179 token : string ,
@@ -227,3 +294,51 @@ export class AzureDevOpsApi implements Disposable {
227294 }
228295 }
229296}
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