@elisra-devops/docgen-data-provider 1.105.0 → 1.107.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 +28 -8
- package/README.md +7 -0
- package/bin/modules/TicketsDataProvider.d.ts +39 -1
- package/bin/modules/TicketsDataProvider.js +571 -23
- package/bin/modules/TicketsDataProvider.js.map +1 -1
- package/bin/tests/modules/ticketsDataProvider.historical.test.d.ts +1 -0
- package/bin/tests/modules/ticketsDataProvider.historical.test.js +478 -0
- package/bin/tests/modules/ticketsDataProvider.historical.test.js.map +1 -0
- package/bin/tests/modules/ticketsDataProvider.test.js +162 -1
- package/bin/tests/modules/ticketsDataProvider.test.js.map +1 -1
- package/package.json +1 -1
- package/src/modules/TicketsDataProvider.ts +798 -95
- package/src/tests/modules/ticketsDataProvider.historical.test.ts +543 -0
- package/src/tests/modules/ticketsDataProvider.test.ts +259 -44
|
@@ -10,6 +10,24 @@ const tfs_data_5 = require("../models/tfs-data");
|
|
|
10
10
|
const tfs_data_6 = require("../models/tfs-data");
|
|
11
11
|
const logger_1 = require("../utils/logger");
|
|
12
12
|
const pLimit = require('p-limit');
|
|
13
|
+
const HISTORICAL_WIT_API_VERSIONS = ['7.1', '5.1', null];
|
|
14
|
+
const HISTORICAL_BATCH_MAX_IDS = 200;
|
|
15
|
+
const HISTORICAL_WORK_ITEM_FIELDS = [
|
|
16
|
+
'System.Id',
|
|
17
|
+
'System.WorkItemType',
|
|
18
|
+
'System.Title',
|
|
19
|
+
'System.State',
|
|
20
|
+
'System.AreaPath',
|
|
21
|
+
'System.IterationPath',
|
|
22
|
+
'System.Rev',
|
|
23
|
+
'System.ChangedDate',
|
|
24
|
+
'System.Description',
|
|
25
|
+
'Microsoft.VSTS.TCM.Steps',
|
|
26
|
+
'Elisra.TestPhase',
|
|
27
|
+
'Custom.TestPhase',
|
|
28
|
+
];
|
|
29
|
+
/** Default fields fetched per work item in tree/flat query parsing. */
|
|
30
|
+
const WI_DEFAULT_FIELDS = 'System.Description,System.Title,Microsoft.VSTS.TCM.ReproSteps,Microsoft.VSTS.CMMI.Symptom';
|
|
13
31
|
class TicketsDataProvider {
|
|
14
32
|
constructor(orgUrl, token) {
|
|
15
33
|
this.orgUrl = '';
|
|
@@ -163,14 +181,24 @@ class TicketsDataProvider {
|
|
|
163
181
|
* @param docType document type
|
|
164
182
|
* @returns
|
|
165
183
|
*/
|
|
184
|
+
normalizeSharedQueriesPath(path) {
|
|
185
|
+
const raw = String(path || '').trim();
|
|
186
|
+
if (!raw)
|
|
187
|
+
return '';
|
|
188
|
+
const normalized = raw.toLowerCase();
|
|
189
|
+
if (normalized === 'shared' || normalized === 'shared queries')
|
|
190
|
+
return '';
|
|
191
|
+
return raw;
|
|
192
|
+
}
|
|
166
193
|
async GetSharedQueries(project, path, docType = '') {
|
|
167
194
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
|
|
168
195
|
let url;
|
|
169
196
|
try {
|
|
170
|
-
|
|
197
|
+
const normalizedPath = this.normalizeSharedQueriesPath(path);
|
|
198
|
+
if (normalizedPath === '')
|
|
171
199
|
url = `${this.orgUrl}${project}/_apis/wit/queries/Shared%20Queries?$depth=2&$expand=all`;
|
|
172
200
|
else
|
|
173
|
-
url = `${this.orgUrl}${project}/_apis/wit/queries/${
|
|
201
|
+
url = `${this.orgUrl}${project}/_apis/wit/queries/${normalizedPath}?$depth=2&$expand=all`;
|
|
174
202
|
let queries = await tfs_1.TFSServices.getItemContent(url, this.token);
|
|
175
203
|
logger_1.default.debug(`doctype: ${docType}`);
|
|
176
204
|
const normalizedDocType = (docType || '').toLowerCase();
|
|
@@ -310,6 +338,8 @@ class TicketsDataProvider {
|
|
|
310
338
|
}
|
|
311
339
|
case 'srs':
|
|
312
340
|
return await this.fetchSrsQueries(queriesWithChildren);
|
|
341
|
+
case 'sysrs':
|
|
342
|
+
return await this.fetchSysRsQueries(queriesWithChildren);
|
|
313
343
|
case 'svd': {
|
|
314
344
|
const { root: svdRoot, found } = await this.getDocTypeRoot(queriesWithChildren, 'svd');
|
|
315
345
|
if (!found) {
|
|
@@ -344,6 +374,13 @@ class TicketsDataProvider {
|
|
|
344
374
|
knownBugsQueryTree: (_y = knownBugsFetch === null || knownBugsFetch === void 0 ? void 0 : knownBugsFetch.result) !== null && _y !== void 0 ? _y : null,
|
|
345
375
|
};
|
|
346
376
|
}
|
|
377
|
+
case 'historical-query':
|
|
378
|
+
case 'historical': {
|
|
379
|
+
const { tree1 } = await this.structureAllQueryPath(queriesWithChildren);
|
|
380
|
+
return {
|
|
381
|
+
historicalQueryTree: tree1 ? [tree1] : [],
|
|
382
|
+
};
|
|
383
|
+
}
|
|
347
384
|
default:
|
|
348
385
|
break;
|
|
349
386
|
}
|
|
@@ -513,6 +550,33 @@ class TicketsDataProvider {
|
|
|
513
550
|
softwareToSystemRequirementsQueries,
|
|
514
551
|
};
|
|
515
552
|
}
|
|
553
|
+
async fetchSysRsQueries(rootQueries) {
|
|
554
|
+
const { root: sysRsRoot, found: sysRsRootFound } = await this.getDocTypeRoot(rootQueries, 'sysrs');
|
|
555
|
+
logger_1.default.debug(`[GetSharedQueries][sysrs] using ${sysRsRootFound ? 'dedicated folder' : 'root queries'}`);
|
|
556
|
+
const systemRequirementsQueries = await this.fetchSystemRequirementQueries(sysRsRoot, [
|
|
557
|
+
'System To Customer',
|
|
558
|
+
'System To Subsystem',
|
|
559
|
+
]);
|
|
560
|
+
const systemToCustomerFolder = await this.findChildFolderByPossibleNames(sysRsRoot, [
|
|
561
|
+
'system to customer',
|
|
562
|
+
'system-to-customer',
|
|
563
|
+
'system customer',
|
|
564
|
+
'subsystem to system',
|
|
565
|
+
'customer to system',
|
|
566
|
+
]);
|
|
567
|
+
const systemToSubsystemFolder = await this.findChildFolderByPossibleNames(sysRsRoot, [
|
|
568
|
+
'system to subsystem',
|
|
569
|
+
'system-to-subsystem',
|
|
570
|
+
'system subsystem',
|
|
571
|
+
]);
|
|
572
|
+
const subsystemToSystemRequirementsQueries = await this.fetchRequirementsTraceQueriesForFolder(systemToCustomerFolder);
|
|
573
|
+
const systemToSubsystemRequirementsQueries = await this.fetchRequirementsTraceQueriesForFolder(systemToSubsystemFolder);
|
|
574
|
+
return {
|
|
575
|
+
systemRequirementsQueries,
|
|
576
|
+
subsystemToSystemRequirementsQueries,
|
|
577
|
+
systemToSubsystemRequirementsQueries,
|
|
578
|
+
};
|
|
579
|
+
}
|
|
516
580
|
/**
|
|
517
581
|
* Fetches and structures linked queries related to requirements traceability with area path filtering.
|
|
518
582
|
*
|
|
@@ -530,8 +594,7 @@ class TicketsDataProvider {
|
|
|
530
594
|
*/
|
|
531
595
|
async fetchLinkedRequirementsTraceQueries(queries, onlySourceSide = false) {
|
|
532
596
|
const { tree1: SystemToSoftwareRequirementsTree, tree2: SoftwareToSystemRequirementsTree } = await this.structureFetchedQueries(queries, onlySourceSide, null, ['epic', 'feature', 'requirement'], ['epic', 'feature', 'requirement'], 'sys', // Source area filter for tree1: System area paths
|
|
533
|
-
'soft'
|
|
534
|
-
);
|
|
597
|
+
'soft');
|
|
535
598
|
return { SystemToSoftwareRequirementsTree, SoftwareToSystemRequirementsTree };
|
|
536
599
|
}
|
|
537
600
|
async fetchRequirementsTraceQueriesForFolder(folder) {
|
|
@@ -802,7 +865,7 @@ class TicketsDataProvider {
|
|
|
802
865
|
Object.assign(node, refreshedNode);
|
|
803
866
|
return node;
|
|
804
867
|
}
|
|
805
|
-
async GetQueryResultsFromWiql(wiqlHref = '', displayAsTable = false, testCaseToRelatedWiMap) {
|
|
868
|
+
async GetQueryResultsFromWiql(wiqlHref = '', displayAsTable = false, testCaseToRelatedWiMap, fetchAllFields = false) {
|
|
806
869
|
try {
|
|
807
870
|
if (!wiqlHref) {
|
|
808
871
|
throw new Error('Incorrect WIQL Link');
|
|
@@ -816,13 +879,13 @@ class TicketsDataProvider {
|
|
|
816
879
|
case tfs_data_3.QueryType.OneHop:
|
|
817
880
|
return displayAsTable
|
|
818
881
|
? await this.parseDirectLinkedQueryResultForTableFormat(queryResult, testCaseToRelatedWiMap)
|
|
819
|
-
: await this.parseTreeQueryResult(queryResult);
|
|
882
|
+
: await this.parseTreeQueryResult(queryResult, fetchAllFields);
|
|
820
883
|
case tfs_data_3.QueryType.Tree:
|
|
821
|
-
return await this.parseTreeQueryResult(queryResult);
|
|
884
|
+
return await this.parseTreeQueryResult(queryResult, fetchAllFields);
|
|
822
885
|
case tfs_data_3.QueryType.Flat:
|
|
823
886
|
return displayAsTable
|
|
824
887
|
? await this.parseFlatQueryResultForTableFormat(queryResult)
|
|
825
|
-
: await this.parseFlatQueryResult(queryResult);
|
|
888
|
+
: await this.parseFlatQueryResult(queryResult, fetchAllFields);
|
|
826
889
|
default:
|
|
827
890
|
break;
|
|
828
891
|
}
|
|
@@ -980,7 +1043,7 @@ class TicketsDataProvider {
|
|
|
980
1043
|
fieldsToIncludeMap,
|
|
981
1044
|
};
|
|
982
1045
|
}
|
|
983
|
-
async parseTreeQueryResult(queryResult) {
|
|
1046
|
+
async parseTreeQueryResult(queryResult, fetchAllFields = false) {
|
|
984
1047
|
const { workItemRelations } = queryResult;
|
|
985
1048
|
if (!workItemRelations)
|
|
986
1049
|
return null;
|
|
@@ -993,10 +1056,10 @@ class TicketsDataProvider {
|
|
|
993
1056
|
for (const rel of workItemRelations) {
|
|
994
1057
|
const t = rel.target;
|
|
995
1058
|
if (!allItems[t.id])
|
|
996
|
-
await this.initTreeQueryResultItem(t, allItems);
|
|
1059
|
+
await this.initTreeQueryResultItem(t, allItems, fetchAllFields);
|
|
997
1060
|
// Also initialize source nodes if they exist
|
|
998
1061
|
if (rel.source && !allItems[rel.source.id]) {
|
|
999
|
-
await this.initTreeQueryResultItem(rel.source, allItems);
|
|
1062
|
+
await this.initTreeQueryResultItem(rel.source, allItems, fetchAllFields);
|
|
1000
1063
|
}
|
|
1001
1064
|
if (rel.rel === null && rel.source === null) {
|
|
1002
1065
|
if (!rootSet.has(t.id)) {
|
|
@@ -1022,11 +1085,11 @@ class TicketsDataProvider {
|
|
|
1022
1085
|
// Nodes should already be initialized, but double-check
|
|
1023
1086
|
if (!allItems[parentId]) {
|
|
1024
1087
|
logger_1.default.warn(`Parent ${parentId} not found, initializing now`);
|
|
1025
|
-
await this.initTreeQueryResultItem(rel.source, allItems);
|
|
1088
|
+
await this.initTreeQueryResultItem(rel.source, allItems, fetchAllFields);
|
|
1026
1089
|
}
|
|
1027
1090
|
if (!allItems[childId]) {
|
|
1028
1091
|
logger_1.default.warn(`Child ${childId} not found, initializing now`);
|
|
1029
|
-
await this.initTreeQueryResultItem(rel.target, allItems);
|
|
1092
|
+
await this.initTreeQueryResultItem(rel.target, allItems, fetchAllFields);
|
|
1030
1093
|
}
|
|
1031
1094
|
const parent = allItems[parentId];
|
|
1032
1095
|
parent._childrenSet || (parent._childrenSet = new Set());
|
|
@@ -1053,32 +1116,38 @@ class TicketsDataProvider {
|
|
|
1053
1116
|
allItems, // all fetched items (including those not in hierarchy)
|
|
1054
1117
|
};
|
|
1055
1118
|
}
|
|
1056
|
-
async initTreeQueryResultItem(item, allItems) {
|
|
1119
|
+
async initTreeQueryResultItem(item, allItems, fetchAllFields = false) {
|
|
1057
1120
|
var _a, _b;
|
|
1058
|
-
const urlWi = `${item.url}?fields
|
|
1121
|
+
const urlWi = fetchAllFields ? `${item.url}` : `${item.url}?fields=${WI_DEFAULT_FIELDS}`;
|
|
1059
1122
|
const wi = await tfs_1.TFSServices.getItemContent(urlWi, this.token);
|
|
1123
|
+
const fields = (wi === null || wi === void 0 ? void 0 : wi.fields) || {};
|
|
1060
1124
|
// need to fetch the WI with only the the title, the web URL and the description
|
|
1061
1125
|
allItems[item.id] = {
|
|
1062
1126
|
id: item.id,
|
|
1063
|
-
title:
|
|
1064
|
-
description: (_b = (_a =
|
|
1127
|
+
title: fields['System.Title'] || '',
|
|
1128
|
+
description: (_b = (_a = fields['Microsoft.VSTS.CMMI.Symptom']) !== null && _a !== void 0 ? _a : fields['System.Description']) !== null && _b !== void 0 ? _b : '',
|
|
1065
1129
|
htmlUrl: wi._links.html.href,
|
|
1130
|
+
fields,
|
|
1131
|
+
workItemType: fields['System.WorkItemType'] || '',
|
|
1066
1132
|
children: [],
|
|
1067
1133
|
};
|
|
1068
1134
|
}
|
|
1069
|
-
async initFlatQueryResultItem(item, workItemMap) {
|
|
1135
|
+
async initFlatQueryResultItem(item, workItemMap, fetchAllFields = false) {
|
|
1070
1136
|
var _a, _b;
|
|
1071
|
-
const urlWi = `${item.url}?fields
|
|
1137
|
+
const urlWi = fetchAllFields ? `${item.url}` : `${item.url}?fields=${WI_DEFAULT_FIELDS}`;
|
|
1072
1138
|
const wi = await tfs_1.TFSServices.getItemContent(urlWi, this.token);
|
|
1139
|
+
const fields = (wi === null || wi === void 0 ? void 0 : wi.fields) || {};
|
|
1073
1140
|
// need to fetch the WI with only the the title, the web URL and the description
|
|
1074
1141
|
workItemMap.set(item.id, {
|
|
1075
1142
|
id: item.id,
|
|
1076
|
-
title:
|
|
1077
|
-
description: (_b = (_a =
|
|
1143
|
+
title: fields['System.Title'] || '',
|
|
1144
|
+
description: (_b = (_a = fields['Microsoft.VSTS.CMMI.Symptom']) !== null && _a !== void 0 ? _a : fields['System.Description']) !== null && _b !== void 0 ? _b : '',
|
|
1078
1145
|
htmlUrl: wi._links.html.href,
|
|
1146
|
+
fields,
|
|
1147
|
+
workItemType: fields['System.WorkItemType'] || '',
|
|
1079
1148
|
});
|
|
1080
1149
|
}
|
|
1081
|
-
async parseFlatQueryResult(queryResult) {
|
|
1150
|
+
async parseFlatQueryResult(queryResult, fetchAllFields = false) {
|
|
1082
1151
|
const { workItems } = queryResult;
|
|
1083
1152
|
if (!workItems) {
|
|
1084
1153
|
logger_1.default.warn(`No work items were found for this requested query`);
|
|
@@ -1088,7 +1157,7 @@ class TicketsDataProvider {
|
|
|
1088
1157
|
const workItemsResultMap = new Map();
|
|
1089
1158
|
for (const wi of workItems) {
|
|
1090
1159
|
if (!workItemsResultMap.has(wi.id)) {
|
|
1091
|
-
await this.initFlatQueryResultItem(wi, workItemsResultMap);
|
|
1160
|
+
await this.initFlatQueryResultItem(wi, workItemsResultMap, fetchAllFields);
|
|
1092
1161
|
}
|
|
1093
1162
|
}
|
|
1094
1163
|
return [...workItemsResultMap.values()];
|
|
@@ -1153,6 +1222,485 @@ class TicketsDataProvider {
|
|
|
1153
1222
|
var wiql = querie._links.wiql;
|
|
1154
1223
|
return await this.GetQueryResultsByWiqlHref(wiql.href, project);
|
|
1155
1224
|
}
|
|
1225
|
+
normalizeHistoricalAsOf(value) {
|
|
1226
|
+
const raw = String(value || '').trim();
|
|
1227
|
+
if (!raw) {
|
|
1228
|
+
throw new Error('asOf date-time is required');
|
|
1229
|
+
}
|
|
1230
|
+
const parsed = new Date(raw);
|
|
1231
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
1232
|
+
throw new Error(`Invalid date-time value: ${raw}`);
|
|
1233
|
+
}
|
|
1234
|
+
return parsed.toISOString();
|
|
1235
|
+
}
|
|
1236
|
+
normalizeHistoricalCompareValue(value) {
|
|
1237
|
+
if (value == null) {
|
|
1238
|
+
return '';
|
|
1239
|
+
}
|
|
1240
|
+
if (typeof value === 'string') {
|
|
1241
|
+
return value.replace(/\s+/g, ' ').trim();
|
|
1242
|
+
}
|
|
1243
|
+
if (Array.isArray(value)) {
|
|
1244
|
+
return value
|
|
1245
|
+
.map((item) => this.normalizeHistoricalCompareValue(item))
|
|
1246
|
+
.filter((item) => item !== '')
|
|
1247
|
+
.sort((a, b) => a.localeCompare(b))
|
|
1248
|
+
.join('; ');
|
|
1249
|
+
}
|
|
1250
|
+
if (typeof value === 'object') {
|
|
1251
|
+
const fallback = value;
|
|
1252
|
+
if (typeof fallback.displayName === 'string') {
|
|
1253
|
+
return this.normalizeHistoricalCompareValue(fallback.displayName);
|
|
1254
|
+
}
|
|
1255
|
+
if (typeof fallback.name === 'string') {
|
|
1256
|
+
return this.normalizeHistoricalCompareValue(fallback.name);
|
|
1257
|
+
}
|
|
1258
|
+
return this.normalizeHistoricalCompareValue(JSON.stringify(value));
|
|
1259
|
+
}
|
|
1260
|
+
return String(value).trim();
|
|
1261
|
+
}
|
|
1262
|
+
normalizeTestPhaseValue(value) {
|
|
1263
|
+
const rendered = this.normalizeHistoricalCompareValue(value);
|
|
1264
|
+
if (!rendered) {
|
|
1265
|
+
return '';
|
|
1266
|
+
}
|
|
1267
|
+
const parts = rendered
|
|
1268
|
+
.split(/[;,]/)
|
|
1269
|
+
.map((part) => part.trim())
|
|
1270
|
+
.filter((part) => part.length > 0);
|
|
1271
|
+
if (parts.length <= 1) {
|
|
1272
|
+
return rendered;
|
|
1273
|
+
}
|
|
1274
|
+
const unique = Array.from(new Set(parts));
|
|
1275
|
+
unique.sort((a, b) => a.localeCompare(b));
|
|
1276
|
+
return unique.join('; ');
|
|
1277
|
+
}
|
|
1278
|
+
isTestCaseType(workItemType) {
|
|
1279
|
+
const normalized = String(workItemType || '').trim().toLowerCase();
|
|
1280
|
+
return normalized === 'test case' || normalized === 'testcase';
|
|
1281
|
+
}
|
|
1282
|
+
toHistoricalRevision(value) {
|
|
1283
|
+
const n = Number(value);
|
|
1284
|
+
if (!Number.isFinite(n)) {
|
|
1285
|
+
return null;
|
|
1286
|
+
}
|
|
1287
|
+
return n;
|
|
1288
|
+
}
|
|
1289
|
+
extractHistoricalWorkItemIds(queryResult) {
|
|
1290
|
+
var _a, _b;
|
|
1291
|
+
const ids = new Set();
|
|
1292
|
+
const pushId = (candidate) => {
|
|
1293
|
+
const n = Number(candidate);
|
|
1294
|
+
if (Number.isFinite(n)) {
|
|
1295
|
+
ids.add(n);
|
|
1296
|
+
}
|
|
1297
|
+
};
|
|
1298
|
+
const workItems = Array.isArray(queryResult === null || queryResult === void 0 ? void 0 : queryResult.workItems) ? queryResult.workItems : [];
|
|
1299
|
+
for (const workItem of workItems) {
|
|
1300
|
+
pushId(workItem === null || workItem === void 0 ? void 0 : workItem.id);
|
|
1301
|
+
}
|
|
1302
|
+
const workItemRelations = Array.isArray(queryResult === null || queryResult === void 0 ? void 0 : queryResult.workItemRelations)
|
|
1303
|
+
? queryResult.workItemRelations
|
|
1304
|
+
: [];
|
|
1305
|
+
for (const relation of workItemRelations) {
|
|
1306
|
+
pushId((_a = relation === null || relation === void 0 ? void 0 : relation.source) === null || _a === void 0 ? void 0 : _a.id);
|
|
1307
|
+
pushId((_b = relation === null || relation === void 0 ? void 0 : relation.target) === null || _b === void 0 ? void 0 : _b.id);
|
|
1308
|
+
}
|
|
1309
|
+
return Array.from(ids.values()).sort((a, b) => a - b);
|
|
1310
|
+
}
|
|
1311
|
+
appendAsOfToWiql(wiql, asOfIso) {
|
|
1312
|
+
const raw = String(wiql || '').trim();
|
|
1313
|
+
if (!raw) {
|
|
1314
|
+
return '';
|
|
1315
|
+
}
|
|
1316
|
+
const withoutAsOf = raw.replace(/\s+ASOF\s+'[^']*'/i, '');
|
|
1317
|
+
return `${withoutAsOf} ASOF '${asOfIso}'`;
|
|
1318
|
+
}
|
|
1319
|
+
appendApiVersion(url, apiVersion) {
|
|
1320
|
+
if (!apiVersion) {
|
|
1321
|
+
return url;
|
|
1322
|
+
}
|
|
1323
|
+
const separator = url.includes('?') ? '&' : '?';
|
|
1324
|
+
return `${url}${separator}api-version=${encodeURIComponent(apiVersion)}`;
|
|
1325
|
+
}
|
|
1326
|
+
shouldRetryHistoricalWithLowerVersion(error) {
|
|
1327
|
+
var _a, _b, _c;
|
|
1328
|
+
const status = Number(((_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.status) || 0);
|
|
1329
|
+
if (status === 401 || status === 403) {
|
|
1330
|
+
return false;
|
|
1331
|
+
}
|
|
1332
|
+
if (status >= 500) {
|
|
1333
|
+
return true;
|
|
1334
|
+
}
|
|
1335
|
+
if ([400, 404, 405, 406, 410].includes(status)) {
|
|
1336
|
+
return true;
|
|
1337
|
+
}
|
|
1338
|
+
const message = String(((_c = (_b = error === null || error === void 0 ? void 0 : error.response) === null || _b === void 0 ? void 0 : _b.data) === null || _c === void 0 ? void 0 : _c.message) || (error === null || error === void 0 ? void 0 : error.message) || '');
|
|
1339
|
+
return /api[- ]?version/i.test(message);
|
|
1340
|
+
}
|
|
1341
|
+
async withHistoricalApiVersionFallback(operation, task) {
|
|
1342
|
+
var _a;
|
|
1343
|
+
let lastError = null;
|
|
1344
|
+
for (const apiVersion of HISTORICAL_WIT_API_VERSIONS) {
|
|
1345
|
+
try {
|
|
1346
|
+
const result = await task(apiVersion);
|
|
1347
|
+
return { apiVersion, result };
|
|
1348
|
+
}
|
|
1349
|
+
catch (error) {
|
|
1350
|
+
lastError = error;
|
|
1351
|
+
const status = Number(((_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.status) || 0);
|
|
1352
|
+
const apiVersionLabel = apiVersion || 'default';
|
|
1353
|
+
logger_1.default.warn(`[${operation}] failed with api-version=${apiVersionLabel}${status ? ` (status ${status})` : ''}`);
|
|
1354
|
+
if (!this.shouldRetryHistoricalWithLowerVersion(error) ||
|
|
1355
|
+
apiVersion === HISTORICAL_WIT_API_VERSIONS[HISTORICAL_WIT_API_VERSIONS.length - 1]) {
|
|
1356
|
+
throw error;
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
throw lastError || new Error(`[${operation}] Failed to execute historical request`);
|
|
1361
|
+
}
|
|
1362
|
+
chunkHistoricalWorkItemIds(ids) {
|
|
1363
|
+
const chunks = [];
|
|
1364
|
+
for (let i = 0; i < ids.length; i += HISTORICAL_BATCH_MAX_IDS) {
|
|
1365
|
+
chunks.push(ids.slice(i, i + HISTORICAL_BATCH_MAX_IDS));
|
|
1366
|
+
}
|
|
1367
|
+
return chunks;
|
|
1368
|
+
}
|
|
1369
|
+
normalizeHistoricalQueryPath(path) {
|
|
1370
|
+
const rawPath = String(path || '').trim();
|
|
1371
|
+
const normalizedRoot = rawPath.toLowerCase();
|
|
1372
|
+
if (rawPath === '' ||
|
|
1373
|
+
normalizedRoot === 'shared' ||
|
|
1374
|
+
normalizedRoot === 'shared queries') {
|
|
1375
|
+
return 'Shared%20Queries';
|
|
1376
|
+
}
|
|
1377
|
+
const segments = rawPath
|
|
1378
|
+
.replace(/\\/g, '/')
|
|
1379
|
+
.split('/')
|
|
1380
|
+
.filter((segment) => segment.trim() !== '')
|
|
1381
|
+
.map((segment) => {
|
|
1382
|
+
try {
|
|
1383
|
+
return encodeURIComponent(decodeURIComponent(segment));
|
|
1384
|
+
}
|
|
1385
|
+
catch (error) {
|
|
1386
|
+
return encodeURIComponent(segment);
|
|
1387
|
+
}
|
|
1388
|
+
});
|
|
1389
|
+
return segments.join('/');
|
|
1390
|
+
}
|
|
1391
|
+
normalizeHistoricalQueryRoot(root) {
|
|
1392
|
+
if (!root)
|
|
1393
|
+
return root;
|
|
1394
|
+
if (Array.isArray(root === null || root === void 0 ? void 0 : root.children) || typeof (root === null || root === void 0 ? void 0 : root.isFolder) === 'boolean') {
|
|
1395
|
+
return root;
|
|
1396
|
+
}
|
|
1397
|
+
if (Array.isArray(root === null || root === void 0 ? void 0 : root.value)) {
|
|
1398
|
+
return {
|
|
1399
|
+
id: 'root',
|
|
1400
|
+
name: 'Shared Queries',
|
|
1401
|
+
isFolder: true,
|
|
1402
|
+
children: root.value,
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
return root;
|
|
1406
|
+
}
|
|
1407
|
+
async fetchHistoricalWorkItemsBatch(project, ids, asOf, apiVersion) {
|
|
1408
|
+
const workItemsBatchUrl = this.appendApiVersion(`${this.orgUrl}${project}/_apis/wit/workitemsbatch`, apiVersion);
|
|
1409
|
+
const idChunks = this.chunkHistoricalWorkItemIds(ids);
|
|
1410
|
+
try {
|
|
1411
|
+
const batchResponses = await Promise.all(idChunks.map((idChunk) => this.limit(() => tfs_1.TFSServices.getItemContent(workItemsBatchUrl, this.token, 'post', {
|
|
1412
|
+
ids: idChunk,
|
|
1413
|
+
asOf,
|
|
1414
|
+
$expand: 'Relations',
|
|
1415
|
+
fields: HISTORICAL_WORK_ITEM_FIELDS,
|
|
1416
|
+
}))));
|
|
1417
|
+
return batchResponses.flatMap((batch) => (Array.isArray(batch === null || batch === void 0 ? void 0 : batch.value) ? batch.value : []));
|
|
1418
|
+
}
|
|
1419
|
+
catch (error) {
|
|
1420
|
+
logger_1.default.warn(`[historical-workitems] workitemsbatch failed${apiVersion ? ` (api-version=${apiVersion})` : ''}, falling back to per-item retrieval: ${(error === null || error === void 0 ? void 0 : error.message) || error}`);
|
|
1421
|
+
const asOfParam = encodeURIComponent(asOf);
|
|
1422
|
+
const responses = await Promise.all(ids.map((id) => this.limit(() => {
|
|
1423
|
+
const itemUrl = this.appendApiVersion(`${this.orgUrl}${project}/_apis/wit/workitems/${id}?$expand=Relations&asOf=${asOfParam}`, apiVersion);
|
|
1424
|
+
return tfs_1.TFSServices.getItemContent(itemUrl, this.token);
|
|
1425
|
+
})));
|
|
1426
|
+
return responses.filter((item) => item && typeof item === 'object');
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
async executeHistoricalQueryAtAsOf(project, queryId, asOfIso) {
|
|
1430
|
+
const { apiVersion, result } = await this.withHistoricalApiVersionFallback('historical-query-execution', async (resolvedApiVersion) => {
|
|
1431
|
+
const queryDefUrl = this.appendApiVersion(`${this.orgUrl}${project}/_apis/wit/queries/${encodeURIComponent(queryId)}?$expand=all`, resolvedApiVersion);
|
|
1432
|
+
const queryDefinition = await tfs_1.TFSServices.getItemContent(queryDefUrl, this.token);
|
|
1433
|
+
const wiqlText = String((queryDefinition === null || queryDefinition === void 0 ? void 0 : queryDefinition.wiql) || '').trim();
|
|
1434
|
+
let queryResult = null;
|
|
1435
|
+
try {
|
|
1436
|
+
if (!wiqlText) {
|
|
1437
|
+
throw new Error(`Could not resolve WIQL text for query ${queryId}`);
|
|
1438
|
+
}
|
|
1439
|
+
const wiqlWithAsOf = this.appendAsOfToWiql(wiqlText, asOfIso);
|
|
1440
|
+
if (!wiqlWithAsOf) {
|
|
1441
|
+
throw new Error(`Could not build WIQL for historical query ${queryId}`);
|
|
1442
|
+
}
|
|
1443
|
+
const executeUrl = this.appendApiVersion(`${this.orgUrl}${project}/_apis/wit/wiql?$top=2147483646&timePrecision=true`, resolvedApiVersion);
|
|
1444
|
+
queryResult = await tfs_1.TFSServices.getItemContent(executeUrl, this.token, 'post', {
|
|
1445
|
+
query: wiqlWithAsOf,
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1448
|
+
catch (inlineWiqlError) {
|
|
1449
|
+
logger_1.default.warn(`[historical-query-execution] inline WIQL failed for query ${queryId}${resolvedApiVersion ? ` (api-version=${resolvedApiVersion})` : ''}, trying WIQL-by-id fallback: ${(inlineWiqlError === null || inlineWiqlError === void 0 ? void 0 : inlineWiqlError.message) || inlineWiqlError}`);
|
|
1450
|
+
const executeByIdUrl = this.appendApiVersion(`${this.orgUrl}${project}/_apis/wit/wiql/${encodeURIComponent(queryId)}?$top=2147483646&timePrecision=true&asOf=${encodeURIComponent(asOfIso)}`, resolvedApiVersion);
|
|
1451
|
+
queryResult = await tfs_1.TFSServices.getItemContent(executeByIdUrl, this.token);
|
|
1452
|
+
}
|
|
1453
|
+
return { queryDefinition, queryResult };
|
|
1454
|
+
});
|
|
1455
|
+
return Object.assign(Object.assign({}, result), { apiVersion });
|
|
1456
|
+
}
|
|
1457
|
+
toHistoricalWorkItemSnapshot(project, workItem) {
|
|
1458
|
+
var _a, _b, _c, _d;
|
|
1459
|
+
const fields = ((workItem === null || workItem === void 0 ? void 0 : workItem.fields) || {});
|
|
1460
|
+
const id = Number((workItem === null || workItem === void 0 ? void 0 : workItem.id) || fields['System.Id'] || 0);
|
|
1461
|
+
const workItemType = this.normalizeHistoricalCompareValue(fields['System.WorkItemType']);
|
|
1462
|
+
const testPhaseRaw = (_c = (_b = (_a = fields['Elisra.TestPhase']) !== null && _a !== void 0 ? _a : fields['Custom.TestPhase']) !== null && _b !== void 0 ? _b : fields['Elisra.Testphase']) !== null && _c !== void 0 ? _c : fields['Custom.Testphase'];
|
|
1463
|
+
const relatedLinkCount = Array.isArray(workItem === null || workItem === void 0 ? void 0 : workItem.relations) ? workItem.relations.length : 0;
|
|
1464
|
+
return {
|
|
1465
|
+
id,
|
|
1466
|
+
workItemType,
|
|
1467
|
+
title: this.normalizeHistoricalCompareValue(fields['System.Title']),
|
|
1468
|
+
state: this.normalizeHistoricalCompareValue(fields['System.State']),
|
|
1469
|
+
areaPath: this.normalizeHistoricalCompareValue(fields['System.AreaPath']),
|
|
1470
|
+
iterationPath: this.normalizeHistoricalCompareValue(fields['System.IterationPath']),
|
|
1471
|
+
versionId: this.toHistoricalRevision((_d = workItem === null || workItem === void 0 ? void 0 : workItem.rev) !== null && _d !== void 0 ? _d : fields['System.Rev']),
|
|
1472
|
+
versionTimestamp: this.normalizeHistoricalCompareValue(fields['System.ChangedDate']),
|
|
1473
|
+
description: this.normalizeHistoricalCompareValue(fields['System.Description']),
|
|
1474
|
+
steps: this.normalizeHistoricalCompareValue(fields['Microsoft.VSTS.TCM.Steps']),
|
|
1475
|
+
testPhase: this.normalizeTestPhaseValue(testPhaseRaw),
|
|
1476
|
+
relatedLinkCount,
|
|
1477
|
+
workItemUrl: `${this.orgUrl}${project}/_workitems/edit/${id}`,
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
async getHistoricalSnapshot(project, queryId, asOfInput) {
|
|
1481
|
+
const asOf = this.normalizeHistoricalAsOf(asOfInput);
|
|
1482
|
+
const { queryDefinition, queryResult, apiVersion } = await this.executeHistoricalQueryAtAsOf(project, queryId, asOf);
|
|
1483
|
+
const ids = this.extractHistoricalWorkItemIds(queryResult);
|
|
1484
|
+
if (ids.length === 0) {
|
|
1485
|
+
return {
|
|
1486
|
+
queryId,
|
|
1487
|
+
queryName: String((queryDefinition === null || queryDefinition === void 0 ? void 0 : queryDefinition.name) || queryId),
|
|
1488
|
+
asOf,
|
|
1489
|
+
total: 0,
|
|
1490
|
+
rows: [],
|
|
1491
|
+
snapshotMap: new Map(),
|
|
1492
|
+
};
|
|
1493
|
+
}
|
|
1494
|
+
const values = await this.fetchHistoricalWorkItemsBatch(project, ids, asOf, apiVersion);
|
|
1495
|
+
const rows = values
|
|
1496
|
+
.map((workItem) => this.toHistoricalWorkItemSnapshot(project, workItem))
|
|
1497
|
+
.sort((a, b) => a.id - b.id);
|
|
1498
|
+
const snapshotMap = new Map();
|
|
1499
|
+
for (const row of rows) {
|
|
1500
|
+
snapshotMap.set(row.id, row);
|
|
1501
|
+
}
|
|
1502
|
+
return {
|
|
1503
|
+
queryId,
|
|
1504
|
+
queryName: String((queryDefinition === null || queryDefinition === void 0 ? void 0 : queryDefinition.name) || queryId),
|
|
1505
|
+
asOf,
|
|
1506
|
+
total: rows.length,
|
|
1507
|
+
rows,
|
|
1508
|
+
snapshotMap,
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
collectHistoricalQueries(root, parentPath = '') {
|
|
1512
|
+
const items = [];
|
|
1513
|
+
if (!root) {
|
|
1514
|
+
return items;
|
|
1515
|
+
}
|
|
1516
|
+
const name = String((root === null || root === void 0 ? void 0 : root.name) || '').trim();
|
|
1517
|
+
const currentPath = parentPath && name ? `${parentPath}/${name}` : name || parentPath;
|
|
1518
|
+
if (!(root === null || root === void 0 ? void 0 : root.isFolder)) {
|
|
1519
|
+
const id = String((root === null || root === void 0 ? void 0 : root.id) || '').trim();
|
|
1520
|
+
if (id) {
|
|
1521
|
+
items.push({
|
|
1522
|
+
id,
|
|
1523
|
+
queryName: name || id,
|
|
1524
|
+
path: parentPath || 'Shared Queries',
|
|
1525
|
+
});
|
|
1526
|
+
}
|
|
1527
|
+
return items;
|
|
1528
|
+
}
|
|
1529
|
+
const children = Array.isArray(root === null || root === void 0 ? void 0 : root.children) ? root.children : [];
|
|
1530
|
+
for (const child of children) {
|
|
1531
|
+
items.push(...this.collectHistoricalQueries(child, currentPath || 'Shared Queries'));
|
|
1532
|
+
}
|
|
1533
|
+
return items;
|
|
1534
|
+
}
|
|
1535
|
+
/**
|
|
1536
|
+
* Returns a flat list of shared queries for historical/as-of execution.
|
|
1537
|
+
*/
|
|
1538
|
+
async GetHistoricalQueries(project, path = 'shared') {
|
|
1539
|
+
const normalizedPath = this.normalizeHistoricalQueryPath(path);
|
|
1540
|
+
// Azure DevOps WIT query tree endpoint enforces $depth range 0..2.
|
|
1541
|
+
const depth = 2;
|
|
1542
|
+
const { result: root } = await this.withHistoricalApiVersionFallback('historical-queries-list', (apiVersion) => {
|
|
1543
|
+
const url = this.appendApiVersion(`${this.orgUrl}${project}/_apis/wit/queries/${normalizedPath}?$depth=${depth}&$expand=all`, apiVersion);
|
|
1544
|
+
return tfs_1.TFSServices.getItemContent(url, this.token);
|
|
1545
|
+
});
|
|
1546
|
+
const normalizedRoot = this.normalizeHistoricalQueryRoot(root);
|
|
1547
|
+
const items = this.collectHistoricalQueries(normalizedRoot).filter((query) => query.id !== '');
|
|
1548
|
+
items.sort((a, b) => {
|
|
1549
|
+
const byPath = a.path.localeCompare(b.path);
|
|
1550
|
+
if (byPath !== 0)
|
|
1551
|
+
return byPath;
|
|
1552
|
+
return a.queryName.localeCompare(b.queryName);
|
|
1553
|
+
});
|
|
1554
|
+
return items;
|
|
1555
|
+
}
|
|
1556
|
+
/**
|
|
1557
|
+
* Runs a shared query as-of a specific date-time and returns a flat work-item table snapshot.
|
|
1558
|
+
*/
|
|
1559
|
+
async GetHistoricalQueryResults(queryId, project, asOfInput) {
|
|
1560
|
+
const snapshot = await this.getHistoricalSnapshot(project, queryId, asOfInput);
|
|
1561
|
+
return {
|
|
1562
|
+
queryId: snapshot.queryId,
|
|
1563
|
+
queryName: snapshot.queryName,
|
|
1564
|
+
asOf: snapshot.asOf,
|
|
1565
|
+
total: snapshot.total,
|
|
1566
|
+
rows: snapshot.rows.map((row) => ({
|
|
1567
|
+
id: row.id,
|
|
1568
|
+
workItemType: row.workItemType,
|
|
1569
|
+
title: row.title,
|
|
1570
|
+
state: row.state,
|
|
1571
|
+
areaPath: row.areaPath,
|
|
1572
|
+
iterationPath: row.iterationPath,
|
|
1573
|
+
versionId: row.versionId,
|
|
1574
|
+
versionTimestamp: row.versionTimestamp,
|
|
1575
|
+
workItemUrl: row.workItemUrl,
|
|
1576
|
+
})),
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
/**
|
|
1580
|
+
* Compares a shared query between two date-time baselines using the feature's noise-control fields.
|
|
1581
|
+
*/
|
|
1582
|
+
async CompareHistoricalQueryResults(queryId, project, baselineAsOfInput, compareToAsOfInput) {
|
|
1583
|
+
const [baseline, compareTo] = await Promise.all([
|
|
1584
|
+
this.getHistoricalSnapshot(project, queryId, baselineAsOfInput),
|
|
1585
|
+
this.getHistoricalSnapshot(project, queryId, compareToAsOfInput),
|
|
1586
|
+
]);
|
|
1587
|
+
const allIds = new Set([
|
|
1588
|
+
...Array.from(baseline.snapshotMap.keys()),
|
|
1589
|
+
...Array.from(compareTo.snapshotMap.keys()),
|
|
1590
|
+
]);
|
|
1591
|
+
const sortedIds = Array.from(allIds.values()).sort((a, b) => a - b);
|
|
1592
|
+
const rows = sortedIds.map((id) => {
|
|
1593
|
+
const baselineRow = baseline.snapshotMap.get(id) || null;
|
|
1594
|
+
const compareToRow = compareTo.snapshotMap.get(id) || null;
|
|
1595
|
+
const workItemType = (compareToRow === null || compareToRow === void 0 ? void 0 : compareToRow.workItemType) || (baselineRow === null || baselineRow === void 0 ? void 0 : baselineRow.workItemType) || '';
|
|
1596
|
+
const isTestCase = this.isTestCaseType(workItemType);
|
|
1597
|
+
if (baselineRow && !compareToRow) {
|
|
1598
|
+
return {
|
|
1599
|
+
id,
|
|
1600
|
+
workItemType,
|
|
1601
|
+
title: baselineRow.title,
|
|
1602
|
+
baselineRevisionId: baselineRow.versionId,
|
|
1603
|
+
compareToRevisionId: null,
|
|
1604
|
+
compareStatus: 'Deleted',
|
|
1605
|
+
changedFields: [],
|
|
1606
|
+
differences: [],
|
|
1607
|
+
workItemUrl: baselineRow.workItemUrl,
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1610
|
+
if (!baselineRow && compareToRow) {
|
|
1611
|
+
return {
|
|
1612
|
+
id,
|
|
1613
|
+
workItemType,
|
|
1614
|
+
title: compareToRow.title,
|
|
1615
|
+
baselineRevisionId: null,
|
|
1616
|
+
compareToRevisionId: compareToRow.versionId,
|
|
1617
|
+
compareStatus: 'Added',
|
|
1618
|
+
changedFields: [],
|
|
1619
|
+
differences: [],
|
|
1620
|
+
workItemUrl: compareToRow.workItemUrl,
|
|
1621
|
+
};
|
|
1622
|
+
}
|
|
1623
|
+
const safeBaseline = baselineRow;
|
|
1624
|
+
const safeCompareTo = compareToRow;
|
|
1625
|
+
const changedFields = [];
|
|
1626
|
+
if (safeBaseline.description !== safeCompareTo.description) {
|
|
1627
|
+
changedFields.push('Description');
|
|
1628
|
+
}
|
|
1629
|
+
if (safeBaseline.title !== safeCompareTo.title) {
|
|
1630
|
+
changedFields.push('Title');
|
|
1631
|
+
}
|
|
1632
|
+
if (safeBaseline.state !== safeCompareTo.state) {
|
|
1633
|
+
changedFields.push('State');
|
|
1634
|
+
}
|
|
1635
|
+
if (isTestCase && safeBaseline.steps !== safeCompareTo.steps) {
|
|
1636
|
+
changedFields.push('Steps');
|
|
1637
|
+
}
|
|
1638
|
+
if (safeBaseline.testPhase !== safeCompareTo.testPhase) {
|
|
1639
|
+
changedFields.push('Test Phase');
|
|
1640
|
+
}
|
|
1641
|
+
if (isTestCase && safeBaseline.relatedLinkCount !== safeCompareTo.relatedLinkCount) {
|
|
1642
|
+
changedFields.push('Related Link Count');
|
|
1643
|
+
}
|
|
1644
|
+
const differences = changedFields.map((field) => {
|
|
1645
|
+
switch (field) {
|
|
1646
|
+
case 'Description':
|
|
1647
|
+
return { field, baseline: safeBaseline.description, compareTo: safeCompareTo.description };
|
|
1648
|
+
case 'Title':
|
|
1649
|
+
return { field, baseline: safeBaseline.title, compareTo: safeCompareTo.title };
|
|
1650
|
+
case 'State':
|
|
1651
|
+
return { field, baseline: safeBaseline.state, compareTo: safeCompareTo.state };
|
|
1652
|
+
case 'Steps':
|
|
1653
|
+
return { field, baseline: safeBaseline.steps, compareTo: safeCompareTo.steps };
|
|
1654
|
+
case 'Test Phase':
|
|
1655
|
+
return { field, baseline: safeBaseline.testPhase, compareTo: safeCompareTo.testPhase };
|
|
1656
|
+
case 'Related Link Count':
|
|
1657
|
+
return {
|
|
1658
|
+
field,
|
|
1659
|
+
baseline: String(safeBaseline.relatedLinkCount),
|
|
1660
|
+
compareTo: String(safeCompareTo.relatedLinkCount),
|
|
1661
|
+
};
|
|
1662
|
+
default:
|
|
1663
|
+
return { field, baseline: '', compareTo: '' };
|
|
1664
|
+
}
|
|
1665
|
+
});
|
|
1666
|
+
return {
|
|
1667
|
+
id,
|
|
1668
|
+
workItemType,
|
|
1669
|
+
title: safeCompareTo.title || safeBaseline.title,
|
|
1670
|
+
baselineRevisionId: safeBaseline.versionId,
|
|
1671
|
+
compareToRevisionId: safeCompareTo.versionId,
|
|
1672
|
+
compareStatus: changedFields.length > 0 ? 'Changed' : 'No changes',
|
|
1673
|
+
changedFields,
|
|
1674
|
+
differences,
|
|
1675
|
+
workItemUrl: safeCompareTo.workItemUrl || safeBaseline.workItemUrl,
|
|
1676
|
+
};
|
|
1677
|
+
});
|
|
1678
|
+
const summary = rows.reduce((acc, row) => {
|
|
1679
|
+
if (row.compareStatus === 'Added')
|
|
1680
|
+
acc.addedCount += 1;
|
|
1681
|
+
else if (row.compareStatus === 'Deleted')
|
|
1682
|
+
acc.deletedCount += 1;
|
|
1683
|
+
else if (row.compareStatus === 'Changed')
|
|
1684
|
+
acc.changedCount += 1;
|
|
1685
|
+
else
|
|
1686
|
+
acc.noChangeCount += 1;
|
|
1687
|
+
return acc;
|
|
1688
|
+
}, { addedCount: 0, deletedCount: 0, changedCount: 0, noChangeCount: 0 });
|
|
1689
|
+
return {
|
|
1690
|
+
queryId,
|
|
1691
|
+
queryName: compareTo.queryName || baseline.queryName || queryId,
|
|
1692
|
+
baseline: {
|
|
1693
|
+
asOf: baseline.asOf,
|
|
1694
|
+
total: baseline.total,
|
|
1695
|
+
},
|
|
1696
|
+
compareTo: {
|
|
1697
|
+
asOf: compareTo.asOf,
|
|
1698
|
+
total: compareTo.total,
|
|
1699
|
+
},
|
|
1700
|
+
summary: Object.assign(Object.assign({}, summary), { updatedCount: summary.changedCount }),
|
|
1701
|
+
rows,
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1156
1704
|
async PopulateWorkItemsByIds(workItemsArray = [], projectName = '') {
|
|
1157
1705
|
let url = `${this.orgUrl}${projectName}/_apis/wit/workitemsbatch`;
|
|
1158
1706
|
let res = [];
|