@elisra-devops/docgen-data-provider 1.126.0 → 1.127.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release.yml +6 -0
- package/bin/modules/GitDataProvider.d.ts +7 -0
- package/bin/modules/GitDataProvider.js +7 -0
- package/bin/modules/GitDataProvider.js.map +1 -1
- package/bin/modules/TicketsDataProvider.js +56 -24
- package/bin/modules/TicketsDataProvider.js.map +1 -1
- package/package.json +1 -1
- package/src/modules/GitDataProvider.ts +43 -36
- package/src/modules/TicketsDataProvider.ts +74 -43
|
@@ -33,6 +33,13 @@ export default class GitDataProvider {
|
|
|
33
33
|
return TFSServices.getItemContent(url, this.token, 'get');
|
|
34
34
|
} //GetGitRepoFromPrId
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Retrieves a JSON file from a Git repository
|
|
38
|
+
* @param projectName - The name of the project
|
|
39
|
+
* @param repoName - The name of the repository
|
|
40
|
+
* @param filePath - The path to the file
|
|
41
|
+
* @returns The JSON content of the file
|
|
42
|
+
*/
|
|
36
43
|
async GetJsonFileFromGitRepo(projectName: string, repoName: string, filePath: string) {
|
|
37
44
|
let url = `${this.orgUrl}${projectName}/_apis/git/repositories/${repoName}/items?path=${filePath}&includeContent=true`;
|
|
38
45
|
let res = await TFSServices.getItemContent(url, this.token, 'get');
|
|
@@ -64,7 +71,7 @@ export default class GitDataProvider {
|
|
|
64
71
|
const res = await TFSServices.getItemContent(url, this.token, 'get');
|
|
65
72
|
if (res && res.value && Array.isArray(res.value) && res.value.length > 0) {
|
|
66
73
|
const match = res.value.find(
|
|
67
|
-
(r: any) => r.name.split('/').pop().toLowerCase() === branch.toLowerCase()
|
|
74
|
+
(r: any) => r.name.split('/').pop().toLowerCase() === branch.toLowerCase(),
|
|
68
75
|
);
|
|
69
76
|
|
|
70
77
|
if (match) {
|
|
@@ -89,7 +96,7 @@ export default class GitDataProvider {
|
|
|
89
96
|
repoId: string,
|
|
90
97
|
fileName: string,
|
|
91
98
|
version: GitVersionDescriptor,
|
|
92
|
-
gitRepoUrl: string = ''
|
|
99
|
+
gitRepoUrl: string = '',
|
|
93
100
|
) {
|
|
94
101
|
// get a single tag
|
|
95
102
|
let versionFix = '';
|
|
@@ -125,7 +132,7 @@ export default class GitDataProvider {
|
|
|
125
132
|
async CheckIfItemExist(
|
|
126
133
|
gitApiUrl: string,
|
|
127
134
|
itemPath: string,
|
|
128
|
-
version: GitVersionDescriptor
|
|
135
|
+
version: GitVersionDescriptor,
|
|
129
136
|
): Promise<boolean> {
|
|
130
137
|
let safePath = encodeURIComponent(itemPath).replace(/%2F/g, '/');
|
|
131
138
|
let versionFixed = '';
|
|
@@ -163,7 +170,7 @@ export default class GitDataProvider {
|
|
|
163
170
|
async GetPullRequestsLinkedItemsInCommitRange(
|
|
164
171
|
projectId: string,
|
|
165
172
|
repositoryId: string,
|
|
166
|
-
commitRangeArray: any
|
|
173
|
+
commitRangeArray: any,
|
|
167
174
|
) {
|
|
168
175
|
let pullRequestsFilteredArray: any = [];
|
|
169
176
|
let ChangeSetsArray: any = [];
|
|
@@ -186,7 +193,7 @@ export default class GitDataProvider {
|
|
|
186
193
|
});
|
|
187
194
|
});
|
|
188
195
|
logger.info(
|
|
189
|
-
`filtered in commit range ${pullRequestsFilteredArray.length} pullrequests for repo: ${repositoryId}
|
|
196
|
+
`filtered in commit range ${pullRequestsFilteredArray.length} pullrequests for repo: ${repositoryId}`,
|
|
190
197
|
);
|
|
191
198
|
//extract linked items and append them to result
|
|
192
199
|
await Promise.all(
|
|
@@ -206,13 +213,13 @@ export default class GitDataProvider {
|
|
|
206
213
|
pullrequest: pr,
|
|
207
214
|
};
|
|
208
215
|
ChangeSetsArray.push(changeSet);
|
|
209
|
-
})
|
|
216
|
+
}),
|
|
210
217
|
);
|
|
211
218
|
}
|
|
212
219
|
} catch (error) {
|
|
213
220
|
logger.error(error);
|
|
214
221
|
}
|
|
215
|
-
})
|
|
222
|
+
}),
|
|
216
223
|
);
|
|
217
224
|
return ChangeSetsArray;
|
|
218
225
|
} //GetPullRequestsInCommitRange
|
|
@@ -223,12 +230,12 @@ export default class GitDataProvider {
|
|
|
223
230
|
commitRange: any,
|
|
224
231
|
linkedWiOptions: any,
|
|
225
232
|
includeUnlinkedCommits: boolean = false,
|
|
226
|
-
includePullRequestWorkItems: boolean = true
|
|
233
|
+
includePullRequestWorkItems: boolean = true,
|
|
227
234
|
) {
|
|
228
235
|
logger.info(
|
|
229
236
|
`GetItemsInCommitRange: includeUnlinkedCommits=${includeUnlinkedCommits}, includePullRequestWorkItems=${includePullRequestWorkItems}, commits=${
|
|
230
237
|
commitRange?.value?.length || 0
|
|
231
|
-
}
|
|
238
|
+
}`,
|
|
232
239
|
);
|
|
233
240
|
//get all items linked to commits
|
|
234
241
|
let res: any = [];
|
|
@@ -245,7 +252,7 @@ export default class GitDataProvider {
|
|
|
245
252
|
let populatedItem = await this.ticketsDataProvider.GetWorkItem(projectId, wi.id);
|
|
246
253
|
let linkedItems: LinkedRelation[] = await this.createLinkedRelatedItemsForSVD(
|
|
247
254
|
linkedWiOptions,
|
|
248
|
-
populatedItem
|
|
255
|
+
populatedItem,
|
|
249
256
|
);
|
|
250
257
|
let changeSet: any = { workItem: populatedItem, commit: commit, linkedItems };
|
|
251
258
|
commitChangesArray.push(changeSet);
|
|
@@ -267,7 +274,7 @@ export default class GitDataProvider {
|
|
|
267
274
|
}
|
|
268
275
|
}
|
|
269
276
|
logger.info(
|
|
270
|
-
`GetItemsInCommitRange: produced ${commitChangesArray.length} linked changes and ${commitsWithNoRelations.length} unlinked commits
|
|
277
|
+
`GetItemsInCommitRange: produced ${commitChangesArray.length} linked changes and ${commitsWithNoRelations.length} unlinked commits`,
|
|
271
278
|
);
|
|
272
279
|
//get all items and pr data from pr's in commit range - using the above function
|
|
273
280
|
let pullRequestsChangesArray: any[] = [];
|
|
@@ -277,7 +284,7 @@ export default class GitDataProvider {
|
|
|
277
284
|
const prLinkedItems = await this.GetPullRequestsLinkedItemsInCommitRange(
|
|
278
285
|
projectId,
|
|
279
286
|
repositoryId,
|
|
280
|
-
commitRange
|
|
287
|
+
commitRange,
|
|
281
288
|
);
|
|
282
289
|
({ workItemIds: prWorkItemIds, mergeCommitIds: prMergeCommitIds } =
|
|
283
290
|
this.collectPrWorkItemMetadata(prLinkedItems));
|
|
@@ -347,7 +354,7 @@ export default class GitDataProvider {
|
|
|
347
354
|
commitEntries.filter((entry: [string | undefined, any]): entry is [string, any] => {
|
|
348
355
|
const commitId = entry[0];
|
|
349
356
|
return typeof commitId === 'string' && commitId !== '';
|
|
350
|
-
})
|
|
357
|
+
}),
|
|
351
358
|
);
|
|
352
359
|
}
|
|
353
360
|
|
|
@@ -374,7 +381,7 @@ export default class GitDataProvider {
|
|
|
374
381
|
commitChangesArray: any[],
|
|
375
382
|
workItemCommitIds: Map<number, Set<string>>,
|
|
376
383
|
prWorkItemIds: Set<number>,
|
|
377
|
-
prMergeCommitIds: Set<string
|
|
384
|
+
prMergeCommitIds: Set<string>,
|
|
378
385
|
) {
|
|
379
386
|
if (prWorkItemIds.size === 0) return;
|
|
380
387
|
for (const change of commitChangesArray || []) {
|
|
@@ -398,7 +405,7 @@ export default class GitDataProvider {
|
|
|
398
405
|
async GetPullRequestsInCommitRangeWithoutLinkedItems(
|
|
399
406
|
projectId: string,
|
|
400
407
|
repositoryId: string,
|
|
401
|
-
commitRangeArray: any
|
|
408
|
+
commitRangeArray: any,
|
|
402
409
|
) {
|
|
403
410
|
let pullRequestsFilteredArray: any[] = [];
|
|
404
411
|
|
|
@@ -433,7 +440,7 @@ export default class GitDataProvider {
|
|
|
433
440
|
});
|
|
434
441
|
});
|
|
435
442
|
logger.info(
|
|
436
|
-
`filtered in commit range ${pullRequestsFilteredArray.length} pullrequests for repo: ${repositoryId}
|
|
443
|
+
`filtered in commit range ${pullRequestsFilteredArray.length} pullrequests for repo: ${repositoryId}`,
|
|
437
444
|
);
|
|
438
445
|
|
|
439
446
|
return pullRequestsFilteredArray;
|
|
@@ -461,7 +468,7 @@ export default class GitDataProvider {
|
|
|
461
468
|
let populatedItem = await this.ticketsDataProvider.GetWorkItem(projectId, wi.id);
|
|
462
469
|
let changeSet: any = { workItem: populatedItem, build: toBuildId };
|
|
463
470
|
linkedItemsArray.push(changeSet);
|
|
464
|
-
})
|
|
471
|
+
}),
|
|
465
472
|
);
|
|
466
473
|
return linkedItemsArray;
|
|
467
474
|
} //GetCommitForPipeline
|
|
@@ -473,12 +480,12 @@ export default class GitDataProvider {
|
|
|
473
480
|
targetRepo: any,
|
|
474
481
|
addedWorkItemByIdSet: Set<number>,
|
|
475
482
|
linkedWiOptions: any = undefined,
|
|
476
|
-
includeUnlinkedCommits: boolean = false
|
|
483
|
+
includeUnlinkedCommits: boolean = false,
|
|
477
484
|
) {
|
|
478
485
|
logger.info(
|
|
479
486
|
`getItemsForPipelineRange: includeUnlinkedCommits=${includeUnlinkedCommits}, extendedCommits=${
|
|
480
487
|
extendedCommits?.length || 0
|
|
481
|
-
}
|
|
488
|
+
}`,
|
|
482
489
|
);
|
|
483
490
|
let commitChangesArray: any[] = [];
|
|
484
491
|
let commitsWithNoRelations: any[] = [];
|
|
@@ -487,7 +494,7 @@ export default class GitDataProvider {
|
|
|
487
494
|
throw new Error('extended commits cannot be empty');
|
|
488
495
|
}
|
|
489
496
|
logger.debug(
|
|
490
|
-
`getItemsForPipelineRange: ${extendedCommits?.length} commits for ${JSON.stringify(targetRepo)}
|
|
497
|
+
`getItemsForPipelineRange: ${extendedCommits?.length} commits for ${JSON.stringify(targetRepo)}`,
|
|
491
498
|
);
|
|
492
499
|
//First fetch the repo web url
|
|
493
500
|
if (targetRepo.url) {
|
|
@@ -500,7 +507,7 @@ export default class GitDataProvider {
|
|
|
500
507
|
targetRepo['projectId'] = targetRepoProjectId ?? teamProject;
|
|
501
508
|
if (!targetRepoProjectId) {
|
|
502
509
|
logger.warn(
|
|
503
|
-
`getItemsForPipelineRange: repo response missing project.id for ${targetRepo.url}; falling back to pipeline teamProject=${teamProject}
|
|
510
|
+
`getItemsForPipelineRange: repo response missing project.id for ${targetRepo.url}; falling back to pipeline teamProject=${teamProject}`,
|
|
504
511
|
);
|
|
505
512
|
}
|
|
506
513
|
}
|
|
@@ -522,11 +529,11 @@ export default class GitDataProvider {
|
|
|
522
529
|
for (const wi of commit.workItems) {
|
|
523
530
|
const populatedWorkItem = await this.ticketsDataProvider.GetWorkItem(
|
|
524
531
|
targetRepo['projectId'],
|
|
525
|
-
wi.id
|
|
532
|
+
wi.id,
|
|
526
533
|
);
|
|
527
534
|
let linkedItems: LinkedRelation[] = await this.createLinkedRelatedItemsForSVD(
|
|
528
535
|
linkedWiOptions,
|
|
529
|
-
populatedWorkItem
|
|
536
|
+
populatedWorkItem,
|
|
530
537
|
);
|
|
531
538
|
let changeSet: any = {
|
|
532
539
|
workItem: populatedWorkItem,
|
|
@@ -554,7 +561,7 @@ export default class GitDataProvider {
|
|
|
554
561
|
logger.error(error.message);
|
|
555
562
|
}
|
|
556
563
|
logger.info(
|
|
557
|
-
`getItemsForPipelineRange: produced ${commitChangesArray.length} linked changes and ${commitsWithNoRelations.length} unlinked commits
|
|
564
|
+
`getItemsForPipelineRange: produced ${commitChangesArray.length} linked changes and ${commitsWithNoRelations.length} unlinked commits`,
|
|
558
565
|
);
|
|
559
566
|
return { commitChangesArray, commitsWithNoRelations };
|
|
560
567
|
}
|
|
@@ -614,7 +621,7 @@ export default class GitDataProvider {
|
|
|
614
621
|
wiItemType,
|
|
615
622
|
relatedItemContent.fields['System.Title'],
|
|
616
623
|
relatedItemContent._links?.html?.href || '',
|
|
617
|
-
relation.attributes['name'] || ''
|
|
624
|
+
relation.attributes['name'] || '',
|
|
618
625
|
);
|
|
619
626
|
|
|
620
627
|
const isAffectsRelation = relName.includes('Affects');
|
|
@@ -662,7 +669,7 @@ export default class GitDataProvider {
|
|
|
662
669
|
repositoryId: string,
|
|
663
670
|
fromDate: string,
|
|
664
671
|
toDate: string,
|
|
665
|
-
branchName?: string
|
|
672
|
+
branchName?: string,
|
|
666
673
|
) {
|
|
667
674
|
let url: string;
|
|
668
675
|
if (typeof branchName !== 'undefined') {
|
|
@@ -735,7 +742,7 @@ export default class GitDataProvider {
|
|
|
735
742
|
});
|
|
736
743
|
});
|
|
737
744
|
logger.info(
|
|
738
|
-
`filtered in prId range ${pullRequestsFilteredArray.length} pullrequests for repo: ${repositoryId}
|
|
745
|
+
`filtered in prId range ${pullRequestsFilteredArray.length} pullrequests for repo: ${repositoryId}`,
|
|
739
746
|
);
|
|
740
747
|
//extract linked items and append them to result
|
|
741
748
|
await Promise.all(
|
|
@@ -755,13 +762,13 @@ export default class GitDataProvider {
|
|
|
755
762
|
pullrequest: pr,
|
|
756
763
|
};
|
|
757
764
|
ChangeSetsArray.push(changeSet);
|
|
758
|
-
})
|
|
765
|
+
}),
|
|
759
766
|
);
|
|
760
767
|
}
|
|
761
768
|
} catch (error) {
|
|
762
769
|
logger.error(error);
|
|
763
770
|
}
|
|
764
|
-
})
|
|
771
|
+
}),
|
|
765
772
|
);
|
|
766
773
|
return ChangeSetsArray;
|
|
767
774
|
}
|
|
@@ -810,7 +817,7 @@ export default class GitDataProvider {
|
|
|
810
817
|
// If commit cannot be resolved, leave timestamp as 0
|
|
811
818
|
}
|
|
812
819
|
return { refItem, ts, dateStr };
|
|
813
|
-
})
|
|
820
|
+
}),
|
|
814
821
|
);
|
|
815
822
|
|
|
816
823
|
taggedWithDates.sort((a: any, b: any) => b.ts - a.ts);
|
|
@@ -837,7 +844,7 @@ export default class GitDataProvider {
|
|
|
837
844
|
// ignore and keep ts=0
|
|
838
845
|
}
|
|
839
846
|
return { refItem, ts, dateStr };
|
|
840
|
-
})
|
|
847
|
+
}),
|
|
841
848
|
);
|
|
842
849
|
|
|
843
850
|
branchesWithDates.sort((a: any, b: any) => b.ts - a.ts);
|
|
@@ -895,7 +902,7 @@ export default class GitDataProvider {
|
|
|
895
902
|
gitUrl: string,
|
|
896
903
|
itemVersion: GitVersionDescriptor,
|
|
897
904
|
compareVersion: GitVersionDescriptor,
|
|
898
|
-
specificItemPath: string = ''
|
|
905
|
+
specificItemPath: string = '',
|
|
899
906
|
) {
|
|
900
907
|
const allCommitsExtended: any[] = [];
|
|
901
908
|
let skipping = 0;
|
|
@@ -962,7 +969,7 @@ export default class GitDataProvider {
|
|
|
962
969
|
repoId: string,
|
|
963
970
|
targetVersion: GitVersionDescriptor,
|
|
964
971
|
sourceVersion: GitVersionDescriptor,
|
|
965
|
-
allCommitsExtended: any[]
|
|
972
|
+
allCommitsExtended: any[],
|
|
966
973
|
) {
|
|
967
974
|
let submodules: any[] = [];
|
|
968
975
|
try {
|
|
@@ -1044,20 +1051,20 @@ export default class GitDataProvider {
|
|
|
1044
1051
|
|
|
1045
1052
|
if (!sourceSha1) {
|
|
1046
1053
|
logger.warn(
|
|
1047
|
-
`${gitSubModuleName} pointer not exist in source version ${sourceVersion.versionType} ${sourceVersion.version} in repository ${gitRepoUrl}
|
|
1054
|
+
`${gitSubModuleName} pointer not exist in source version ${sourceVersion.versionType} ${sourceVersion.version} in repository ${gitRepoUrl}`,
|
|
1048
1055
|
);
|
|
1049
1056
|
continue;
|
|
1050
1057
|
}
|
|
1051
1058
|
|
|
1052
1059
|
if (!targetSha1) {
|
|
1053
1060
|
logger.warn(
|
|
1054
|
-
`${gitSubModuleName} pointer not exist in target version ${targetVersion.versionType} ${targetVersion.version} in repository ${gitRepoUrl}
|
|
1061
|
+
`${gitSubModuleName} pointer not exist in target version ${targetVersion.versionType} ${targetVersion.version} in repository ${gitRepoUrl}`,
|
|
1055
1062
|
);
|
|
1056
1063
|
continue;
|
|
1057
1064
|
}
|
|
1058
1065
|
if (sourceSha1 === targetSha1) {
|
|
1059
1066
|
logger.warn(
|
|
1060
|
-
`${gitSubModuleName} pointer is the same in source and target version in repository ${gitRepoUrl}
|
|
1067
|
+
`${gitSubModuleName} pointer is the same in source and target version in repository ${gitRepoUrl}`,
|
|
1061
1068
|
);
|
|
1062
1069
|
continue;
|
|
1063
1070
|
}
|
|
@@ -1757,17 +1757,25 @@ export default class TicketsDataProvider {
|
|
|
1757
1757
|
}
|
|
1758
1758
|
|
|
1759
1759
|
private shouldRetryHistoricalWithLowerVersion(error: any): boolean {
|
|
1760
|
+
if (error?._historicalFallbackExhausted) {
|
|
1761
|
+
return false;
|
|
1762
|
+
}
|
|
1760
1763
|
const status = Number(error?.response?.status || 0);
|
|
1761
1764
|
if (status === 401 || status === 403) {
|
|
1762
1765
|
return false;
|
|
1763
1766
|
}
|
|
1767
|
+
const message = String(error?.response?.data?.message || error?.message || '');
|
|
1768
|
+
// TF##### errors are semantic ADO rejections (invalid path, missing field, etc.) —
|
|
1769
|
+
// not api-version compatibility issues. Retrying wastes time and amplifies timeouts.
|
|
1770
|
+
if (/(^|\s)TF\d{4,6}:/i.test(message)) {
|
|
1771
|
+
return false;
|
|
1772
|
+
}
|
|
1764
1773
|
if (status >= 500) {
|
|
1765
1774
|
return true;
|
|
1766
1775
|
}
|
|
1767
1776
|
if ([400, 404, 405, 406, 410].includes(status)) {
|
|
1768
1777
|
return true;
|
|
1769
1778
|
}
|
|
1770
|
-
const message = String(error?.response?.data?.message || error?.message || '');
|
|
1771
1779
|
return /api[- ]?version/i.test(message);
|
|
1772
1780
|
}
|
|
1773
1781
|
|
|
@@ -1941,50 +1949,73 @@ export default class TicketsDataProvider {
|
|
|
1941
1949
|
queryId: string,
|
|
1942
1950
|
asOfIso: string,
|
|
1943
1951
|
): Promise<{ queryDefinition: any; queryResult: any; apiVersion: string | null }> {
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
);
|
|
1951
|
-
const queryDefinition = await TFSServices.getItemContent(queryDefUrl, this.token);
|
|
1952
|
-
|
|
1953
|
-
const wiqlText = String(queryDefinition?.wiql || '').trim();
|
|
1954
|
-
let queryResult: any = null;
|
|
1955
|
-
try {
|
|
1956
|
-
if (!wiqlText) {
|
|
1957
|
-
throw new Error(`Could not resolve WIQL text for query ${queryId}`);
|
|
1958
|
-
}
|
|
1959
|
-
const wiqlWithAsOf = this.appendAsOfToWiql(wiqlText, asOfIso);
|
|
1960
|
-
if (!wiqlWithAsOf) {
|
|
1961
|
-
throw new Error(`Could not build WIQL for historical query ${queryId}`);
|
|
1962
|
-
}
|
|
1963
|
-
const executeUrl = this.appendApiVersion(
|
|
1964
|
-
`${this.orgUrl}${project}/_apis/wit/wiql?$top=2147483646&timePrecision=true`,
|
|
1965
|
-
resolvedApiVersion,
|
|
1966
|
-
);
|
|
1967
|
-
queryResult = await TFSServices.getItemContent(executeUrl, this.token, 'post', {
|
|
1968
|
-
query: wiqlWithAsOf,
|
|
1969
|
-
});
|
|
1970
|
-
} catch (inlineWiqlError: any) {
|
|
1971
|
-
logger.warn(
|
|
1972
|
-
`[historical-query-execution] inline WIQL failed for query ${queryId}${
|
|
1973
|
-
resolvedApiVersion ? ` (api-version=${resolvedApiVersion})` : ''
|
|
1974
|
-
}, trying WIQL-by-id fallback: ${inlineWiqlError?.message || inlineWiqlError}`,
|
|
1975
|
-
);
|
|
1976
|
-
const executeByIdUrl = this.appendApiVersion(
|
|
1977
|
-
`${this.orgUrl}${project}/_apis/wit/wiql/${encodeURIComponent(
|
|
1978
|
-
queryId,
|
|
1979
|
-
)}?$top=2147483646&timePrecision=true&asOf=${encodeURIComponent(asOfIso)}`,
|
|
1952
|
+
try {
|
|
1953
|
+
const { apiVersion, result } = await this.withHistoricalApiVersionFallback(
|
|
1954
|
+
'historical-query-execution',
|
|
1955
|
+
async (resolvedApiVersion) => {
|
|
1956
|
+
const queryDefUrl = this.appendApiVersion(
|
|
1957
|
+
`${this.orgUrl}${project}/_apis/wit/queries/${encodeURIComponent(queryId)}?$expand=all`,
|
|
1980
1958
|
resolvedApiVersion,
|
|
1981
1959
|
);
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1960
|
+
const queryDefinition = await TFSServices.getItemContent(queryDefUrl, this.token);
|
|
1961
|
+
|
|
1962
|
+
const wiqlText = String(queryDefinition?.wiql || '').trim();
|
|
1963
|
+
let queryResult: any = null;
|
|
1964
|
+
try {
|
|
1965
|
+
if (!wiqlText) {
|
|
1966
|
+
throw new Error(`Could not resolve WIQL text for query ${queryId}`);
|
|
1967
|
+
}
|
|
1968
|
+
const wiqlWithAsOf = this.appendAsOfToWiql(wiqlText, asOfIso);
|
|
1969
|
+
if (!wiqlWithAsOf) {
|
|
1970
|
+
throw new Error(`Could not build WIQL for historical query ${queryId}`);
|
|
1971
|
+
}
|
|
1972
|
+
const executeUrl = this.appendApiVersion(
|
|
1973
|
+
`${this.orgUrl}${project}/_apis/wit/wiql?$top=2147483646&timePrecision=true`,
|
|
1974
|
+
resolvedApiVersion,
|
|
1975
|
+
);
|
|
1976
|
+
queryResult = await TFSServices.getItemContent(executeUrl, this.token, 'post', {
|
|
1977
|
+
query: wiqlWithAsOf,
|
|
1978
|
+
});
|
|
1979
|
+
} catch (inlineWiqlError: any) {
|
|
1980
|
+
logger.warn(
|
|
1981
|
+
`[historical-query-execution] inline WIQL failed for query ${queryId}${
|
|
1982
|
+
resolvedApiVersion ? ` (api-version=${resolvedApiVersion})` : ''
|
|
1983
|
+
}, trying WIQL-by-id fallback: ${inlineWiqlError?.message || inlineWiqlError}`,
|
|
1984
|
+
);
|
|
1985
|
+
const executeByIdUrl = this.appendApiVersion(
|
|
1986
|
+
`${this.orgUrl}${project}/_apis/wit/wiql/${encodeURIComponent(
|
|
1987
|
+
queryId,
|
|
1988
|
+
)}?$top=2147483646&timePrecision=true&asOf=${encodeURIComponent(asOfIso)}`,
|
|
1989
|
+
resolvedApiVersion,
|
|
1990
|
+
);
|
|
1991
|
+
try {
|
|
1992
|
+
queryResult = await TFSServices.getItemContent(executeByIdUrl, this.token);
|
|
1993
|
+
} catch (byIdError: any) {
|
|
1994
|
+
byIdError._historicalFallbackExhausted = true;
|
|
1995
|
+
throw byIdError;
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
return { queryDefinition, queryResult };
|
|
1999
|
+
},
|
|
2000
|
+
);
|
|
2001
|
+
return { ...result, apiVersion };
|
|
2002
|
+
} catch (err: any) {
|
|
2003
|
+
const raw = String(err?.response?.data?.message || err?.message || '');
|
|
2004
|
+
const tfMatch = raw.match(/(^|\s)(TF\d{4,6}):/i);
|
|
2005
|
+
if (tfMatch) {
|
|
2006
|
+
const tfCode = tfMatch[2];
|
|
2007
|
+
const friendly = new Error(
|
|
2008
|
+
`Azure DevOps rejected the historical query (${tfCode}): ${raw} ` +
|
|
2009
|
+
`The query references an area or iteration path that no longer exists. ` +
|
|
2010
|
+
`Please open the query in Azure DevOps and fix the filter.`,
|
|
2011
|
+
) as any;
|
|
2012
|
+
friendly.response = err?.response;
|
|
2013
|
+
friendly.status = err?.response?.status ?? err?.status ?? 400;
|
|
2014
|
+
friendly.code = tfCode;
|
|
2015
|
+
throw friendly;
|
|
2016
|
+
}
|
|
2017
|
+
throw err;
|
|
2018
|
+
}
|
|
1988
2019
|
}
|
|
1989
2020
|
|
|
1990
2021
|
private toHistoricalWorkItemSnapshot(project: string, workItem: any): HistoricalWorkItemSnapshot {
|