@elisra-devops/docgen-data-provider 1.62.0 → 1.63.1
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/bin/helpers/tfs.d.ts +4 -0
- package/bin/helpers/tfs.js +14 -0
- package/bin/helpers/tfs.js.map +1 -1
- package/bin/modules/GitDataProvider.js +7 -3
- package/bin/modules/GitDataProvider.js.map +1 -1
- package/bin/modules/PipelinesDataProvider.d.ts +7 -0
- package/bin/modules/PipelinesDataProvider.js +32 -0
- package/bin/modules/PipelinesDataProvider.js.map +1 -1
- package/bin/modules/TicketsDataProvider.d.ts +34 -1
- package/bin/modules/TicketsDataProvider.js +405 -28
- package/bin/modules/TicketsDataProvider.js.map +1 -1
- package/package.json +1 -1
- package/src/helpers/tfs.ts +24 -0
- package/src/modules/GitDataProvider.ts +12 -0
- package/src/modules/PipelinesDataProvider.ts +34 -0
- package/src/modules/TicketsDataProvider.ts +525 -40
|
@@ -107,6 +107,7 @@ class TicketsDataProvider {
|
|
|
107
107
|
* @returns
|
|
108
108
|
*/
|
|
109
109
|
async GetSharedQueries(project, path, docType = '') {
|
|
110
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
|
|
110
111
|
let url;
|
|
111
112
|
try {
|
|
112
113
|
if (path === '')
|
|
@@ -115,22 +116,166 @@ class TicketsDataProvider {
|
|
|
115
116
|
url = `${this.orgUrl}${project}/_apis/wit/queries/${path}?$depth=2&$expand=all`;
|
|
116
117
|
let queries = await tfs_1.TFSServices.getItemContent(url, this.token);
|
|
117
118
|
logger_1.default.debug(`doctype: ${docType}`);
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
const normalizedDocType = (docType || '').toLowerCase();
|
|
120
|
+
const queriesWithChildren = await this.ensureQueryChildren(queries);
|
|
121
|
+
switch (normalizedDocType) {
|
|
122
|
+
case 'std': {
|
|
123
|
+
const { root: stdRoot, found: stdRootFound } = await this.getDocTypeRoot(queriesWithChildren, 'std');
|
|
124
|
+
logger_1.default.debug(`[GetSharedQueries][std] using ${stdRootFound ? 'dedicated folder' : 'root queries'}`);
|
|
125
|
+
// Each branch describes the dedicated folder names, the fetch routine, and how to validate results.
|
|
126
|
+
const stdBranches = await this.fetchDocTypeBranches(queriesWithChildren, stdRoot, [
|
|
127
|
+
{
|
|
128
|
+
id: 'reqToTest',
|
|
129
|
+
label: '[GetSharedQueries][std][req-to-test]',
|
|
130
|
+
folderNames: [
|
|
131
|
+
'requirement - test',
|
|
132
|
+
'requirement to test case',
|
|
133
|
+
'requirement to test',
|
|
134
|
+
'req to test',
|
|
135
|
+
],
|
|
136
|
+
fetcher: (folder) => this.fetchLinkedReqTestQueries(folder, false),
|
|
137
|
+
validator: (result) => this.hasAnyQueryTree(result === null || result === void 0 ? void 0 : result.reqTestTree),
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
id: 'testToReq',
|
|
141
|
+
label: '[GetSharedQueries][std][test-to-req]',
|
|
142
|
+
folderNames: [
|
|
143
|
+
'test - requirement',
|
|
144
|
+
'test to requirement',
|
|
145
|
+
'test case to requirement',
|
|
146
|
+
'test to req',
|
|
147
|
+
],
|
|
148
|
+
fetcher: (folder) => this.fetchLinkedReqTestQueries(folder, true),
|
|
149
|
+
validator: (result) => this.hasAnyQueryTree(result === null || result === void 0 ? void 0 : result.testReqTree),
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
id: 'mom',
|
|
153
|
+
label: '[GetSharedQueries][std][mom]',
|
|
154
|
+
folderNames: ['linked mom', 'mom'],
|
|
155
|
+
fetcher: (folder) => this.fetchLinkedMomQueries(folder),
|
|
156
|
+
validator: (result) => this.hasAnyQueryTree(result === null || result === void 0 ? void 0 : result.linkedMomTree),
|
|
157
|
+
},
|
|
158
|
+
]);
|
|
159
|
+
const reqToTestResult = stdBranches['reqToTest'];
|
|
160
|
+
const testToReqResult = stdBranches['testToReq'];
|
|
161
|
+
const momResult = stdBranches['mom'];
|
|
162
|
+
const reqTestQueries = {
|
|
163
|
+
reqTestTree: (_b = (_a = reqToTestResult === null || reqToTestResult === void 0 ? void 0 : reqToTestResult.result) === null || _a === void 0 ? void 0 : _a.reqTestTree) !== null && _b !== void 0 ? _b : null,
|
|
164
|
+
testReqTree: (_f = (_d = (_c = testToReqResult === null || testToReqResult === void 0 ? void 0 : testToReqResult.result) === null || _c === void 0 ? void 0 : _c.testReqTree) !== null && _d !== void 0 ? _d : (_e = reqToTestResult === null || reqToTestResult === void 0 ? void 0 : reqToTestResult.result) === null || _e === void 0 ? void 0 : _e.testReqTree) !== null && _f !== void 0 ? _f : null,
|
|
165
|
+
};
|
|
166
|
+
const linkedMomQueries = {
|
|
167
|
+
linkedMomTree: (_h = (_g = momResult === null || momResult === void 0 ? void 0 : momResult.result) === null || _g === void 0 ? void 0 : _g.linkedMomTree) !== null && _h !== void 0 ? _h : null,
|
|
168
|
+
};
|
|
122
169
|
return { reqTestQueries, linkedMomQueries };
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const
|
|
170
|
+
}
|
|
171
|
+
case 'str': {
|
|
172
|
+
const { root: strRoot, found: strRootFound } = await this.getDocTypeRoot(queriesWithChildren, 'str');
|
|
173
|
+
logger_1.default.debug(`[GetSharedQueries][str] using ${strRootFound ? 'dedicated folder' : 'root queries'}`);
|
|
174
|
+
const strBranches = await this.fetchDocTypeBranches(queriesWithChildren, strRoot, [
|
|
175
|
+
{
|
|
176
|
+
id: 'reqToTest',
|
|
177
|
+
label: '[GetSharedQueries][str][req-to-test]',
|
|
178
|
+
folderNames: [
|
|
179
|
+
'requirement - test',
|
|
180
|
+
'requirement to test case',
|
|
181
|
+
'requirement to test',
|
|
182
|
+
'req to test',
|
|
183
|
+
],
|
|
184
|
+
fetcher: (folder) => this.fetchLinkedReqTestQueries(folder, false),
|
|
185
|
+
validator: (result) => this.hasAnyQueryTree(result === null || result === void 0 ? void 0 : result.reqTestTree),
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
id: 'testToReq',
|
|
189
|
+
label: '[GetSharedQueries][str][test-to-req]',
|
|
190
|
+
folderNames: [
|
|
191
|
+
'test - requirement',
|
|
192
|
+
'test to requirement',
|
|
193
|
+
'test case to requirement',
|
|
194
|
+
'test to req',
|
|
195
|
+
],
|
|
196
|
+
fetcher: (folder) => this.fetchLinkedReqTestQueries(folder, true),
|
|
197
|
+
validator: (result) => this.hasAnyQueryTree(result === null || result === void 0 ? void 0 : result.testReqTree),
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
id: 'openPcrToTest',
|
|
201
|
+
label: '[GetSharedQueries][str][open-pcr-to-test]',
|
|
202
|
+
folderNames: ['open pcr to test case', 'open pcr to test', 'open pcr - test', 'open pcr'],
|
|
203
|
+
fetcher: (folder) => this.fetchLinkedOpenPcrTestQueries(folder, false),
|
|
204
|
+
validator: (result) => this.hasAnyQueryTree(result === null || result === void 0 ? void 0 : result.OpenPcrToTestTree),
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
id: 'testToOpenPcr',
|
|
208
|
+
label: '[GetSharedQueries][str][test-to-open-pcr]',
|
|
209
|
+
folderNames: ['test case to open pcr', 'test to open pcr', 'test - open pcr', 'open pcr'],
|
|
210
|
+
fetcher: (folder) => this.fetchLinkedOpenPcrTestQueries(folder, true),
|
|
211
|
+
validator: (result) => this.hasAnyQueryTree(result === null || result === void 0 ? void 0 : result.TestToOpenPcrTree),
|
|
212
|
+
},
|
|
213
|
+
]);
|
|
214
|
+
const strReqToTest = strBranches['reqToTest'];
|
|
215
|
+
const strTestToReq = strBranches['testToReq'];
|
|
216
|
+
const strOpenPcrToTest = strBranches['openPcrToTest'];
|
|
217
|
+
const strTestToOpenPcr = strBranches['testToOpenPcr'];
|
|
218
|
+
const reqTestTrees = {
|
|
219
|
+
reqTestTree: (_k = (_j = strReqToTest === null || strReqToTest === void 0 ? void 0 : strReqToTest.result) === null || _j === void 0 ? void 0 : _j.reqTestTree) !== null && _k !== void 0 ? _k : null,
|
|
220
|
+
testReqTree: (_p = (_m = (_l = strTestToReq === null || strTestToReq === void 0 ? void 0 : strTestToReq.result) === null || _l === void 0 ? void 0 : _l.testReqTree) !== null && _m !== void 0 ? _m : (_o = strReqToTest === null || strReqToTest === void 0 ? void 0 : strReqToTest.result) === null || _o === void 0 ? void 0 : _o.testReqTree) !== null && _p !== void 0 ? _p : null,
|
|
221
|
+
};
|
|
222
|
+
const openPcrTestTrees = {
|
|
223
|
+
OpenPcrToTestTree: (_r = (_q = strOpenPcrToTest === null || strOpenPcrToTest === void 0 ? void 0 : strOpenPcrToTest.result) === null || _q === void 0 ? void 0 : _q.OpenPcrToTestTree) !== null && _r !== void 0 ? _r : null,
|
|
224
|
+
TestToOpenPcrTree: (_v = (_t = (_s = strTestToOpenPcr === null || strTestToOpenPcr === void 0 ? void 0 : strTestToOpenPcr.result) === null || _s === void 0 ? void 0 : _s.TestToOpenPcrTree) !== null && _t !== void 0 ? _t : (_u = strOpenPcrToTest === null || strOpenPcrToTest === void 0 ? void 0 : strOpenPcrToTest.result) === null || _u === void 0 ? void 0 : _u.TestToOpenPcrTree) !== null && _v !== void 0 ? _v : null,
|
|
225
|
+
};
|
|
126
226
|
return { reqTestTrees, openPcrTestTrees };
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
227
|
+
}
|
|
228
|
+
case 'test-reporter': {
|
|
229
|
+
const { root: testReporterRoot, found: testReporterFound } = await this.getDocTypeRoot(queriesWithChildren, 'test-reporter');
|
|
230
|
+
logger_1.default.debug(`[GetSharedQueries][test-reporter] using ${testReporterFound ? 'dedicated folder' : 'root queries'}`);
|
|
231
|
+
const testReporterBranches = await this.fetchDocTypeBranches(queriesWithChildren, testReporterRoot, [
|
|
232
|
+
{
|
|
233
|
+
id: 'testReporter',
|
|
234
|
+
label: '[GetSharedQueries][test-reporter]',
|
|
235
|
+
folderNames: ['test reporter', 'test-reporter'],
|
|
236
|
+
fetcher: (folder) => this.fetchTestReporterQueries(folder),
|
|
237
|
+
validator: (result) => this.hasAnyQueryTree(result === null || result === void 0 ? void 0 : result.testAssociatedTree),
|
|
238
|
+
},
|
|
239
|
+
]);
|
|
240
|
+
const testReporterFetch = testReporterBranches['testReporter'];
|
|
241
|
+
return (_w = testReporterFetch === null || testReporterFetch === void 0 ? void 0 : testReporterFetch.result) !== null && _w !== void 0 ? _w : { testAssociatedTree: null };
|
|
242
|
+
}
|
|
130
243
|
case 'srs':
|
|
131
|
-
return await this.fetchSrsQueries(
|
|
132
|
-
case 'svd':
|
|
133
|
-
|
|
244
|
+
return await this.fetchSrsQueries(queriesWithChildren);
|
|
245
|
+
case 'svd': {
|
|
246
|
+
const { root: svdRoot, found } = await this.getDocTypeRoot(queriesWithChildren, 'svd');
|
|
247
|
+
if (!found) {
|
|
248
|
+
logger_1.default.debug('[GetSharedQueries][svd] dedicated folder not found, using fallback tree');
|
|
249
|
+
}
|
|
250
|
+
const svdBranches = await this.fetchDocTypeBranches(queriesWithChildren, svdRoot, [
|
|
251
|
+
{
|
|
252
|
+
id: 'systemOverview',
|
|
253
|
+
label: '[GetSharedQueries][svd][system-overview]',
|
|
254
|
+
folderNames: ['system overview'],
|
|
255
|
+
fetcher: async (folder) => {
|
|
256
|
+
const { tree1 } = await this.structureAllQueryPath(folder);
|
|
257
|
+
return tree1;
|
|
258
|
+
},
|
|
259
|
+
validator: (result) => !!result,
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
id: 'knownBugs',
|
|
263
|
+
label: '[GetSharedQueries][svd][known-bugs]',
|
|
264
|
+
folderNames: ['known bugs', 'known bug'],
|
|
265
|
+
fetcher: async (folder) => {
|
|
266
|
+
const { tree2 } = await this.structureAllQueryPath(folder);
|
|
267
|
+
return tree2;
|
|
268
|
+
},
|
|
269
|
+
validator: (result) => !!result,
|
|
270
|
+
},
|
|
271
|
+
]);
|
|
272
|
+
const systemOverviewFetch = svdBranches['systemOverview'];
|
|
273
|
+
const knownBugsFetch = svdBranches['knownBugs'];
|
|
274
|
+
return {
|
|
275
|
+
systemOverviewQueryTree: (_x = systemOverviewFetch === null || systemOverviewFetch === void 0 ? void 0 : systemOverviewFetch.result) !== null && _x !== void 0 ? _x : null,
|
|
276
|
+
knownBugsQueryTree: (_y = knownBugsFetch === null || knownBugsFetch === void 0 ? void 0 : knownBugsFetch.result) !== null && _y !== void 0 ? _y : null,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
134
279
|
default:
|
|
135
280
|
break;
|
|
136
281
|
}
|
|
@@ -205,6 +350,30 @@ class TicketsDataProvider {
|
|
|
205
350
|
]);
|
|
206
351
|
return { linkedMomTree };
|
|
207
352
|
}
|
|
353
|
+
hasAnyQueryTree(result) {
|
|
354
|
+
const inspect = (value) => {
|
|
355
|
+
if (!value) {
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
if (Array.isArray(value)) {
|
|
359
|
+
return value.some(inspect);
|
|
360
|
+
}
|
|
361
|
+
if (typeof value === 'object') {
|
|
362
|
+
if (value.isValidQuery || value.wiql || value.queryType) {
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
if ('roots' in value && Array.isArray(value.roots) && value.roots.length > 0) {
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
if ('children' in value && Array.isArray(value.children) && value.children.length > 0) {
|
|
369
|
+
return true;
|
|
370
|
+
}
|
|
371
|
+
return Object.values(value).some(inspect);
|
|
372
|
+
}
|
|
373
|
+
return false;
|
|
374
|
+
};
|
|
375
|
+
return inspect(result);
|
|
376
|
+
}
|
|
208
377
|
/**
|
|
209
378
|
* Fetches and structures linked queries related to open PCR (Problem Change Request) tests.
|
|
210
379
|
*
|
|
@@ -231,10 +400,6 @@ class TicketsDataProvider {
|
|
|
231
400
|
const { tree1: tree1, tree2: testAssociatedTree } = await this.structureFetchedQueries(queries, true, null, ['Requirement', 'Bug', 'Change Request'], ['Test Case']);
|
|
232
401
|
return { testAssociatedTree };
|
|
233
402
|
}
|
|
234
|
-
async fetchAnyQueries(queries) {
|
|
235
|
-
const { tree1: systemOverviewQueryTree, tree2: knownBugsQueryTree } = await this.structureAllQueryPath(queries);
|
|
236
|
-
return { systemOverviewQueryTree, knownBugsQueryTree };
|
|
237
|
-
}
|
|
238
403
|
async fetchSystemRequirementQueries(queries, excludedFolderNames = []) {
|
|
239
404
|
const { tree1: systemRequirementsQueryTree } = await this.structureFetchedQueries(queries, false, null, ['Epic', 'Feature', 'Requirement'], [], undefined, undefined, true, // Enable processing of both tree and direct link queries (excluding flat queries)
|
|
240
405
|
excludedFolderNames);
|
|
@@ -330,6 +495,219 @@ class TicketsDataProvider {
|
|
|
330
495
|
const normalizedName = childName.toLowerCase();
|
|
331
496
|
return (parentWithChildren.children.find((child) => child.isFolder && (child.name || '').toLowerCase() === normalizedName) || null);
|
|
332
497
|
}
|
|
498
|
+
/**
|
|
499
|
+
* Performs a breadth-first walk starting at `parent` to locate the nearest folder whose
|
|
500
|
+
* name matches any of the provided candidates (case-insensitive). Exact matches win; if none
|
|
501
|
+
* are found the first partial match encountered is returned. When no candidates are located,
|
|
502
|
+
* the method yields `null`.
|
|
503
|
+
*/
|
|
504
|
+
async findChildFolderByPossibleNames(parent, possibleNames) {
|
|
505
|
+
var _a, _b, _c, _d;
|
|
506
|
+
if (!parent || !(possibleNames === null || possibleNames === void 0 ? void 0 : possibleNames.length)) {
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
const normalizedNames = possibleNames.map((name) => name.toLowerCase());
|
|
510
|
+
const isMatch = (candidate, value) => value === candidate;
|
|
511
|
+
const isPartialMatch = (candidate, value) => value.includes(candidate);
|
|
512
|
+
const tryMatch = (folder, matcher) => {
|
|
513
|
+
const folderName = ((folder === null || folder === void 0 ? void 0 : folder.name) || '').toLowerCase();
|
|
514
|
+
return normalizedNames.some((candidate) => matcher(candidate, folderName));
|
|
515
|
+
};
|
|
516
|
+
const parentWithChildren = await this.ensureQueryChildren(parent);
|
|
517
|
+
if (!((_a = parentWithChildren === null || parentWithChildren === void 0 ? void 0 : parentWithChildren.children) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
518
|
+
return null;
|
|
519
|
+
}
|
|
520
|
+
const queue = [];
|
|
521
|
+
const visited = new Set();
|
|
522
|
+
let partialCandidate = null;
|
|
523
|
+
// Seed the queue with direct children so we prefer closer matches before walking deeper.
|
|
524
|
+
for (const child of parentWithChildren.children) {
|
|
525
|
+
if (!(child === null || child === void 0 ? void 0 : child.isFolder)) {
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
const childId = (_b = child.id) !== null && _b !== void 0 ? _b : `${child.name}-${Math.random()}`;
|
|
529
|
+
queue.push(child);
|
|
530
|
+
visited.add(childId);
|
|
531
|
+
}
|
|
532
|
+
const considerFolder = async (folder) => {
|
|
533
|
+
if (tryMatch(folder, isMatch)) {
|
|
534
|
+
return await this.ensureQueryChildren(folder);
|
|
535
|
+
}
|
|
536
|
+
if (!partialCandidate && tryMatch(folder, isPartialMatch)) {
|
|
537
|
+
partialCandidate = await this.ensureQueryChildren(folder);
|
|
538
|
+
}
|
|
539
|
+
return null;
|
|
540
|
+
};
|
|
541
|
+
for (const child of queue) {
|
|
542
|
+
const match = await considerFolder(child);
|
|
543
|
+
if (match) {
|
|
544
|
+
return match;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
while (queue.length > 0) {
|
|
548
|
+
const current = queue.shift();
|
|
549
|
+
if (!current) {
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
const currentWithChildren = await this.ensureQueryChildren(current);
|
|
553
|
+
if (!currentWithChildren) {
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
const match = await considerFolder(currentWithChildren);
|
|
557
|
+
if (match) {
|
|
558
|
+
return match;
|
|
559
|
+
}
|
|
560
|
+
// Breadth-first expansion so we climb the hierarchy gradually.
|
|
561
|
+
if ((_c = currentWithChildren.children) === null || _c === void 0 ? void 0 : _c.length) {
|
|
562
|
+
for (const child of currentWithChildren.children) {
|
|
563
|
+
if (!(child === null || child === void 0 ? void 0 : child.isFolder)) {
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
const childId = (_d = child.id) !== null && _d !== void 0 ? _d : `${child.name}-${Math.random()}`;
|
|
567
|
+
if (!visited.has(childId)) {
|
|
568
|
+
visited.add(childId);
|
|
569
|
+
queue.push(child);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
return partialCandidate;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Executes `fetcher` against `startingFolder` and, if the validator deems the result empty,
|
|
578
|
+
* climbs ancestor folders toward `rootQueries` until a satisfactory result is produced.
|
|
579
|
+
* The first successful folder short-circuits the search; otherwise the final attempt is
|
|
580
|
+
* returned to preserve legacy behavior.
|
|
581
|
+
*/
|
|
582
|
+
async fetchWithAncestorFallback(rootQueries, startingFolder, fetcher, logContext, validator) {
|
|
583
|
+
var _a;
|
|
584
|
+
const rootWithChildren = await this.ensureQueryChildren(rootQueries);
|
|
585
|
+
const candidates = await this.buildFallbackChain(rootWithChildren, startingFolder);
|
|
586
|
+
const evaluate = validator !== null && validator !== void 0 ? validator : ((res) => this.hasAnyQueryTree(res));
|
|
587
|
+
let lastResult = null;
|
|
588
|
+
let lastFolder = startingFolder !== null && startingFolder !== void 0 ? startingFolder : rootWithChildren;
|
|
589
|
+
for (const candidate of candidates) {
|
|
590
|
+
const enrichedCandidate = await this.ensureQueryChildren(candidate);
|
|
591
|
+
const candidateName = (_a = enrichedCandidate === null || enrichedCandidate === void 0 ? void 0 : enrichedCandidate.name) !== null && _a !== void 0 ? _a : '<root>';
|
|
592
|
+
logger_1.default.debug(`${logContext} trying folder: ${candidateName}`);
|
|
593
|
+
lastResult = await fetcher(enrichedCandidate);
|
|
594
|
+
lastFolder = enrichedCandidate;
|
|
595
|
+
if (evaluate(lastResult)) {
|
|
596
|
+
logger_1.default.debug(`${logContext} using folder: ${candidateName}`);
|
|
597
|
+
return { result: lastResult, usedFolder: enrichedCandidate };
|
|
598
|
+
}
|
|
599
|
+
logger_1.default.debug(`${logContext} folder ${candidateName} produced no results, ascending`);
|
|
600
|
+
}
|
|
601
|
+
logger_1.default.debug(`${logContext} no folders yielded results, returning last attempt`);
|
|
602
|
+
return { result: lastResult, usedFolder: lastFolder };
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Applies `fetchWithAncestorFallback` to each configured branch, resolving dedicated folders
|
|
606
|
+
* when available and emitting a map keyed by branch id. Each outcome includes both the
|
|
607
|
+
* resulting payload and the specific folder that satisfied the fallback chain.
|
|
608
|
+
*/
|
|
609
|
+
async fetchDocTypeBranches(queriesWithChildren, docRoot, branches) {
|
|
610
|
+
var _a, _b, _c, _d, _e, _f;
|
|
611
|
+
const results = {};
|
|
612
|
+
const effectiveDocRoot = docRoot !== null && docRoot !== void 0 ? docRoot : queriesWithChildren;
|
|
613
|
+
for (const branch of branches) {
|
|
614
|
+
const fallbackStart = (_a = branch.fallbackStart) !== null && _a !== void 0 ? _a : effectiveDocRoot;
|
|
615
|
+
let startingFolder = fallbackStart;
|
|
616
|
+
let startingName = (_b = startingFolder === null || startingFolder === void 0 ? void 0 : startingFolder.name) !== null && _b !== void 0 ? _b : '<root>';
|
|
617
|
+
// Attempt to locate a more specific child folder, falling back to the provided root if absent.
|
|
618
|
+
if (((_c = branch.folderNames) === null || _c === void 0 ? void 0 : _c.length) && effectiveDocRoot) {
|
|
619
|
+
const resolvedFolder = await this.findChildFolderByPossibleNames(effectiveDocRoot, branch.folderNames);
|
|
620
|
+
if (resolvedFolder) {
|
|
621
|
+
startingFolder = resolvedFolder;
|
|
622
|
+
startingName = (_d = resolvedFolder === null || resolvedFolder === void 0 ? void 0 : resolvedFolder.name) !== null && _d !== void 0 ? _d : '<root>';
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
logger_1.default.debug(`${branch.label} starting folder: ${startingName}`);
|
|
626
|
+
const fetchOutcome = await this.fetchWithAncestorFallback(queriesWithChildren, startingFolder, branch.fetcher, branch.label, branch.validator);
|
|
627
|
+
logger_1.default.debug(`${branch.label} final folder: ${(_f = (_e = fetchOutcome.usedFolder) === null || _e === void 0 ? void 0 : _e.name) !== null && _f !== void 0 ? _f : '<root>'}`);
|
|
628
|
+
results[branch.id] = fetchOutcome;
|
|
629
|
+
}
|
|
630
|
+
return results;
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Constructs an ordered list of folders to probe during fallback. The sequence starts at
|
|
634
|
+
* `startingFolder` (if provided) and walks upward through ancestors to the root query tree,
|
|
635
|
+
* ensuring no folder id appears twice.
|
|
636
|
+
*/
|
|
637
|
+
async buildFallbackChain(rootQueries, startingFolder) {
|
|
638
|
+
const chain = [];
|
|
639
|
+
const seen = new Set();
|
|
640
|
+
const pushUnique = (node) => {
|
|
641
|
+
var _a;
|
|
642
|
+
if (!node) {
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
const id = (_a = node.id) !== null && _a !== void 0 ? _a : '__root__';
|
|
646
|
+
if (seen.has(id)) {
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
seen.add(id);
|
|
650
|
+
chain.push(node);
|
|
651
|
+
};
|
|
652
|
+
if (startingFolder === null || startingFolder === void 0 ? void 0 : startingFolder.id) {
|
|
653
|
+
const path = await this.findPathToNode(rootQueries, startingFolder.id);
|
|
654
|
+
if (path) {
|
|
655
|
+
for (let i = path.length - 1; i >= 0; i--) {
|
|
656
|
+
pushUnique(path[i]);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
else {
|
|
660
|
+
pushUnique(startingFolder);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
else if (startingFolder) {
|
|
664
|
+
pushUnique(startingFolder);
|
|
665
|
+
}
|
|
666
|
+
pushUnique(rootQueries);
|
|
667
|
+
return chain;
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Recursively searches the query tree for the node with the provided id and returns the
|
|
671
|
+
* path (root → target). Nodes are enriched with children on demand and a visited set guards
|
|
672
|
+
* against cycles within malformed data.
|
|
673
|
+
*/
|
|
674
|
+
async findPathToNode(currentNode, targetId, visited = new Set()) {
|
|
675
|
+
var _a;
|
|
676
|
+
if (!currentNode) {
|
|
677
|
+
return null;
|
|
678
|
+
}
|
|
679
|
+
const currentId = (_a = currentNode.id) !== null && _a !== void 0 ? _a : '__root__';
|
|
680
|
+
if (visited.has(currentId)) {
|
|
681
|
+
return null;
|
|
682
|
+
}
|
|
683
|
+
visited.add(currentId);
|
|
684
|
+
if (currentNode.id === targetId) {
|
|
685
|
+
return [currentNode];
|
|
686
|
+
}
|
|
687
|
+
const enrichedNode = await this.ensureQueryChildren(currentNode);
|
|
688
|
+
const children = enrichedNode === null || enrichedNode === void 0 ? void 0 : enrichedNode.children;
|
|
689
|
+
if (!(children === null || children === void 0 ? void 0 : children.length)) {
|
|
690
|
+
return null;
|
|
691
|
+
}
|
|
692
|
+
for (const child of children) {
|
|
693
|
+
const path = await this.findPathToNode(child, targetId, visited);
|
|
694
|
+
if (path) {
|
|
695
|
+
return [enrichedNode, ...path];
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
return null;
|
|
699
|
+
}
|
|
700
|
+
async getDocTypeRoot(rootQueries, docTypeName) {
|
|
701
|
+
if (!rootQueries) {
|
|
702
|
+
return { root: rootQueries, found: false };
|
|
703
|
+
}
|
|
704
|
+
const docTypeFolder = await this.findQueryFolderByName(rootQueries, docTypeName);
|
|
705
|
+
if (docTypeFolder) {
|
|
706
|
+
const folderWithChildren = await this.ensureQueryChildren(docTypeFolder);
|
|
707
|
+
return { root: folderWithChildren, found: true };
|
|
708
|
+
}
|
|
709
|
+
return { root: rootQueries, found: false };
|
|
710
|
+
}
|
|
333
711
|
async ensureQueryChildren(node) {
|
|
334
712
|
if (!node || !node.hasChildren || node.children) {
|
|
335
713
|
return node;
|
|
@@ -410,29 +788,29 @@ class TicketsDataProvider {
|
|
|
410
788
|
}
|
|
411
789
|
}
|
|
412
790
|
// Step 2: Fetch all work items in parallel with concurrency limit
|
|
413
|
-
const allSourcePromises = Array.from(sourceIds).map(id => this.limit(() => {
|
|
414
|
-
const relation = workItemRelations.find(r => { var _a; return (!r.source && r.target.id === id) || ((
|
|
791
|
+
const allSourcePromises = Array.from(sourceIds).map((id) => this.limit(() => {
|
|
792
|
+
const relation = workItemRelations.find((r) => { var _a; return (!r.source && r.target.id === id) || ((_a = r.source) === null || _a === void 0 ? void 0 : _a.id) === id; });
|
|
415
793
|
return this.fetchWIForQueryResult(relation, columnsToShowMap, columnSourceMap, true);
|
|
416
794
|
}));
|
|
417
|
-
const allTargetPromises = Array.from(targetIds).map(id => this.limit(() => {
|
|
418
|
-
const relation = workItemRelations.find(r => { var _a; return ((_a = r.target) === null || _a === void 0 ? void 0 : _a.id) === id; });
|
|
795
|
+
const allTargetPromises = Array.from(targetIds).map((id) => this.limit(() => {
|
|
796
|
+
const relation = workItemRelations.find((r) => { var _a; return ((_a = r.target) === null || _a === void 0 ? void 0 : _a.id) === id; });
|
|
419
797
|
return this.fetchWIForQueryResult(relation, columnsToShowMap, columnTargetsMap, true);
|
|
420
798
|
}));
|
|
421
799
|
// Wait for all fetches to complete in parallel (with concurrency control)
|
|
422
800
|
const [sourceWorkItems, targetWorkItems] = await Promise.all([
|
|
423
801
|
Promise.all(allSourcePromises),
|
|
424
|
-
Promise.all(allTargetPromises)
|
|
802
|
+
Promise.all(allTargetPromises),
|
|
425
803
|
]);
|
|
426
804
|
// Build lookup maps
|
|
427
805
|
const sourceWorkItemMap = new Map();
|
|
428
|
-
sourceWorkItems.forEach(wi => {
|
|
806
|
+
sourceWorkItems.forEach((wi) => {
|
|
429
807
|
sourceWorkItemMap.set(wi.id, wi);
|
|
430
808
|
if (!lookupMap.has(wi.id)) {
|
|
431
809
|
lookupMap.set(wi.id, wi);
|
|
432
810
|
}
|
|
433
811
|
});
|
|
434
812
|
const targetWorkItemMap = new Map();
|
|
435
|
-
targetWorkItems.forEach(wi => {
|
|
813
|
+
targetWorkItems.forEach((wi) => {
|
|
436
814
|
targetWorkItemMap.set(wi.id, wi);
|
|
437
815
|
if (!lookupMap.has(wi.id)) {
|
|
438
816
|
lookupMap.set(wi.id, wi);
|
|
@@ -510,9 +888,9 @@ class TicketsDataProvider {
|
|
|
510
888
|
// Fetch all work items in parallel with concurrency limit
|
|
511
889
|
const wiSet = new Set();
|
|
512
890
|
if (workItems) {
|
|
513
|
-
const fetchPromises = workItems.map(workItem => this.limit(() => this.fetchWIForQueryResult(workItem, columnsToShowMap, fieldsToIncludeMap, false)));
|
|
891
|
+
const fetchPromises = workItems.map((workItem) => this.limit(() => this.fetchWIForQueryResult(workItem, columnsToShowMap, fieldsToIncludeMap, false)));
|
|
514
892
|
const fetchedWorkItems = await Promise.all(fetchPromises);
|
|
515
|
-
fetchedWorkItems.forEach(wi => wiSet.add(wi));
|
|
893
|
+
fetchedWorkItems.forEach((wi) => wiSet.add(wi));
|
|
516
894
|
}
|
|
517
895
|
columnsToShowMap.clear();
|
|
518
896
|
return {
|
|
@@ -1342,8 +1720,7 @@ class TicketsDataProvider {
|
|
|
1342
1720
|
continue; // Skip non-requirement work items
|
|
1343
1721
|
}
|
|
1344
1722
|
// Get the requirement type field
|
|
1345
|
-
const rawRequirementType = fullWi.fields['Microsoft.VSTS.CMMI.RequirementType'] ||
|
|
1346
|
-
'';
|
|
1723
|
+
const rawRequirementType = fullWi.fields['Microsoft.VSTS.CMMI.RequirementType'] || '';
|
|
1347
1724
|
// Normalize and trim the requirement type
|
|
1348
1725
|
const trimmedType = String(rawRequirementType).trim();
|
|
1349
1726
|
// Map to the standard category header
|