@elisra-devops/docgen-data-provider 1.132.0 → 1.134.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/bin/modules/ResultDataProvider.d.ts +1 -1
- package/bin/modules/ResultDataProvider.js +17 -12
- package/bin/modules/ResultDataProvider.js.map +1 -1
- package/bin/modules/TicketsDataProvider.d.ts +12 -0
- package/bin/modules/TicketsDataProvider.js +116 -0
- package/bin/modules/TicketsDataProvider.js.map +1 -1
- package/bin/tests/modules/ticketsDataProvider.traceColumns.test.d.ts +1 -0
- package/bin/tests/modules/ticketsDataProvider.traceColumns.test.js +147 -0
- package/bin/tests/modules/ticketsDataProvider.traceColumns.test.js.map +1 -0
- package/package.json +1 -1
- package/src/modules/ResultDataProvider.ts +32 -15
- package/src/modules/TicketsDataProvider.ts +125 -0
- package/src/tests/modules/ticketsDataProvider.traceColumns.test.ts +192 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tfs_1 = require("../../helpers/tfs");
|
|
4
|
+
const TicketsDataProvider_1 = require("../../modules/TicketsDataProvider");
|
|
5
|
+
const logger_1 = require("../../utils/logger");
|
|
6
|
+
jest.mock('../../helpers/tfs');
|
|
7
|
+
jest.mock('../../utils/logger');
|
|
8
|
+
const mockGetItemContent = tfs_1.TFSServices.getItemContent;
|
|
9
|
+
// Minimal OneHop query result shape
|
|
10
|
+
const makeQueryResult = (columns, relations) => ({
|
|
11
|
+
columns,
|
|
12
|
+
workItemRelations: relations,
|
|
13
|
+
queryType: 'oneHop',
|
|
14
|
+
});
|
|
15
|
+
// Root relation (source = the root WI, no .source field)
|
|
16
|
+
const rootRelation = (targetId, targetUrl) => ({
|
|
17
|
+
source: null,
|
|
18
|
+
target: { id: targetId, url: targetUrl },
|
|
19
|
+
});
|
|
20
|
+
// Link relation (source WI → target WI)
|
|
21
|
+
const linkRelation = (sourceId, targetId, targetUrl) => ({
|
|
22
|
+
source: { id: sourceId },
|
|
23
|
+
target: { id: targetId, url: targetUrl },
|
|
24
|
+
});
|
|
25
|
+
const REQ_COLS = [
|
|
26
|
+
{ referenceName: 'System.Id', name: 'ID' },
|
|
27
|
+
{ referenceName: 'System.Title', name: 'Title' },
|
|
28
|
+
{ referenceName: 'Custom.CustomerRequirementId', name: 'CustomerRequirementId' },
|
|
29
|
+
{ referenceName: 'Microsoft.VSTS.Common.Priority', name: 'Priority' },
|
|
30
|
+
];
|
|
31
|
+
const TC_COLS = [
|
|
32
|
+
{ referenceName: 'System.Id', name: 'ID' },
|
|
33
|
+
{ referenceName: 'System.Title', name: 'Title' },
|
|
34
|
+
{ referenceName: 'Microsoft.VSTS.TCM.AutomationStatus', name: 'Automation Status' },
|
|
35
|
+
{ referenceName: 'Microsoft.VSTS.Common.Priority', name: 'Priority' },
|
|
36
|
+
];
|
|
37
|
+
const REQ_FIELDS_SCHEMA = {
|
|
38
|
+
value: [
|
|
39
|
+
{ referenceName: 'System.Id' },
|
|
40
|
+
{ referenceName: 'System.Title' },
|
|
41
|
+
{ referenceName: 'Custom.CustomerRequirementId' },
|
|
42
|
+
{ referenceName: 'Microsoft.VSTS.Common.Priority' },
|
|
43
|
+
],
|
|
44
|
+
};
|
|
45
|
+
const TC_FIELDS_SCHEMA = {
|
|
46
|
+
value: [
|
|
47
|
+
{ referenceName: 'System.Id' },
|
|
48
|
+
{ referenceName: 'System.Title' },
|
|
49
|
+
{ referenceName: 'Microsoft.VSTS.TCM.AutomationStatus' },
|
|
50
|
+
{ referenceName: 'Microsoft.VSTS.Common.Priority' },
|
|
51
|
+
// Note: Custom.CustomerRequirementId is NOT in TC schema
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
describe('TicketsDataProvider.GetTraceColumnsByType', () => {
|
|
55
|
+
let provider;
|
|
56
|
+
beforeEach(() => {
|
|
57
|
+
jest.clearAllMocks();
|
|
58
|
+
provider = new TicketsDataProvider_1.default('https://dev.azure.com/org/', 'mock-token');
|
|
59
|
+
});
|
|
60
|
+
it('splits columns correctly: CustomerRequirementId in Requirement only, not Test Case', async () => {
|
|
61
|
+
const reqRelations = [
|
|
62
|
+
rootRelation(1, 'https://ado/wi/1'),
|
|
63
|
+
linkRelation(1, 101, 'https://ado/wi/101'),
|
|
64
|
+
];
|
|
65
|
+
const reqQueryResult = makeQueryResult(REQ_COLS, reqRelations);
|
|
66
|
+
mockGetItemContent
|
|
67
|
+
.mockResolvedValueOnce(reqQueryResult) // reqTest wiql fetch
|
|
68
|
+
.mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Requirement' } }) // source WI sample
|
|
69
|
+
.mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Test Case' } }) // target WI sample
|
|
70
|
+
.mockResolvedValueOnce(REQ_FIELDS_SCHEMA) // Requirement type fields
|
|
71
|
+
.mockResolvedValueOnce(TC_FIELDS_SCHEMA); // Test Case type fields
|
|
72
|
+
const result = await provider.GetTraceColumnsByType('https://ado/wiql/reqtest', undefined, 'my-project');
|
|
73
|
+
const reqRefs = result.Requirement.map((c) => c.referenceName);
|
|
74
|
+
const tcRefs = result['Test Case'].map((c) => c.referenceName);
|
|
75
|
+
expect(reqRefs).toContain('Custom.CustomerRequirementId');
|
|
76
|
+
expect(tcRefs).not.toContain('Custom.CustomerRequirementId');
|
|
77
|
+
expect(reqRefs).toContain('Microsoft.VSTS.Common.Priority');
|
|
78
|
+
expect(tcRefs).toContain('Microsoft.VSTS.Common.Priority');
|
|
79
|
+
});
|
|
80
|
+
it('merges columns from both queries (reqTest + testReq), deduped', async () => {
|
|
81
|
+
const reqRelations = [rootRelation(1, 'https://ado/wi/1'), linkRelation(1, 101, 'https://ado/wi/101')];
|
|
82
|
+
const testRelations = [rootRelation(101, 'https://ado/wi/101'), linkRelation(101, 1, 'https://ado/wi/1')];
|
|
83
|
+
const extraCol = { referenceName: 'System.AreaPath', name: 'Area Path' };
|
|
84
|
+
mockGetItemContent
|
|
85
|
+
.mockResolvedValueOnce(makeQueryResult(REQ_COLS, reqRelations)) // reqTest wiql
|
|
86
|
+
.mockResolvedValueOnce(makeQueryResult([...TC_COLS, extraCol], testRelations)) // testReq wiql
|
|
87
|
+
.mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Requirement' } }) // source sample reqTest
|
|
88
|
+
.mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Test Case' } }) // target sample reqTest
|
|
89
|
+
.mockResolvedValueOnce(REQ_FIELDS_SCHEMA)
|
|
90
|
+
.mockResolvedValueOnce({ value: [...TC_FIELDS_SCHEMA.value, { referenceName: 'System.AreaPath' }] });
|
|
91
|
+
const result = await provider.GetTraceColumnsByType('https://ado/wiql/reqtest', 'https://ado/wiql/testreq', 'my-project');
|
|
92
|
+
const tcRefs = result['Test Case'].map((c) => c.referenceName);
|
|
93
|
+
expect(tcRefs).toContain('System.AreaPath'); // came only from testReq columns
|
|
94
|
+
});
|
|
95
|
+
it('raw display names passed through without rename (no CustomerRequirementId → Customer ID)', async () => {
|
|
96
|
+
const reqRelations = [rootRelation(1, 'https://ado/wi/1'), linkRelation(1, 101, 'https://ado/wi/101')];
|
|
97
|
+
mockGetItemContent
|
|
98
|
+
.mockResolvedValueOnce(makeQueryResult(REQ_COLS, reqRelations))
|
|
99
|
+
.mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Requirement' } })
|
|
100
|
+
.mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Test Case' } })
|
|
101
|
+
.mockResolvedValueOnce(REQ_FIELDS_SCHEMA)
|
|
102
|
+
.mockResolvedValueOnce(TC_FIELDS_SCHEMA);
|
|
103
|
+
const result = await provider.GetTraceColumnsByType('https://ado/wiql/reqtest', undefined, 'proj');
|
|
104
|
+
const custCol = result.Requirement.find((c) => c.referenceName === 'Custom.CustomerRequirementId');
|
|
105
|
+
expect(custCol === null || custCol === void 0 ? void 0 : custCol.name).toBe('CustomerRequirementId'); // raw ADO name, NOT 'Customer ID'
|
|
106
|
+
});
|
|
107
|
+
it('gracefully degrades when WIT type cannot be sampled (no relations) — returns merged columns both sides', async () => {
|
|
108
|
+
const emptyRelations = [];
|
|
109
|
+
mockGetItemContent.mockResolvedValueOnce(makeQueryResult(REQ_COLS, emptyRelations));
|
|
110
|
+
const result = await provider.GetTraceColumnsByType('https://ado/wiql/reqtest', undefined, 'proj');
|
|
111
|
+
const reqRefs = result.Requirement.map((c) => c.referenceName);
|
|
112
|
+
const tcRefs = result['Test Case'].map((c) => c.referenceName);
|
|
113
|
+
// Both sides get the merged declared columns as fallback
|
|
114
|
+
expect(reqRefs).toContain('Custom.CustomerRequirementId');
|
|
115
|
+
expect(tcRefs).toContain('Custom.CustomerRequirementId');
|
|
116
|
+
});
|
|
117
|
+
it('returns empty arrays when wiql fetch fails', async () => {
|
|
118
|
+
mockGetItemContent.mockRejectedValueOnce(new Error('Network error'));
|
|
119
|
+
const result = await provider.GetTraceColumnsByType('https://ado/wiql/reqtest', undefined, 'proj');
|
|
120
|
+
expect(result).toEqual({ Requirement: [], 'Test Case': [] });
|
|
121
|
+
expect(logger_1.default.error).toHaveBeenCalled();
|
|
122
|
+
});
|
|
123
|
+
it('returns empty arrays when schema fetch fails', async () => {
|
|
124
|
+
const reqRelations = [rootRelation(1, 'https://ado/wi/1'), linkRelation(1, 101, 'https://ado/wi/101')];
|
|
125
|
+
mockGetItemContent
|
|
126
|
+
.mockResolvedValueOnce(makeQueryResult(REQ_COLS, reqRelations))
|
|
127
|
+
.mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Requirement' } })
|
|
128
|
+
.mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Test Case' } })
|
|
129
|
+
.mockRejectedValueOnce(new Error('ADO 404')); // schema fetch fails
|
|
130
|
+
const result = await provider.GetTraceColumnsByType('https://ado/wiql/reqtest', undefined, 'proj');
|
|
131
|
+
expect(result).toEqual({ Requirement: [], 'Test Case': [] });
|
|
132
|
+
});
|
|
133
|
+
it('handles undefined schema value gracefully (no crash on null response)', async () => {
|
|
134
|
+
const reqRelations = [rootRelation(1, 'https://ado/wi/1'), linkRelation(1, 101, 'https://ado/wi/101')];
|
|
135
|
+
mockGetItemContent
|
|
136
|
+
.mockResolvedValueOnce(makeQueryResult(REQ_COLS, reqRelations))
|
|
137
|
+
.mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Requirement' } })
|
|
138
|
+
.mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Test Case' } })
|
|
139
|
+
.mockResolvedValueOnce(undefined) // schema returns undefined (null guard fix)
|
|
140
|
+
.mockResolvedValueOnce(undefined);
|
|
141
|
+
const result = await provider.GetTraceColumnsByType('https://ado/wiql/reqtest', undefined, 'proj');
|
|
142
|
+
// Both field sets are empty → both sides empty after intersection
|
|
143
|
+
expect(result.Requirement).toEqual([]);
|
|
144
|
+
expect(result['Test Case']).toEqual([]);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
//# sourceMappingURL=ticketsDataProvider.traceColumns.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ticketsDataProvider.traceColumns.test.js","sourceRoot":"","sources":["../../../src/tests/modules/ticketsDataProvider.traceColumns.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -290,7 +290,8 @@ export default class ResultDataProvider {
|
|
|
290
290
|
enableRunStepStatusFilter: boolean,
|
|
291
291
|
linkedQueryRequest: any,
|
|
292
292
|
errorFilterMode: string = 'none',
|
|
293
|
-
includeAllHistory: boolean = false
|
|
293
|
+
includeAllHistory: boolean = false,
|
|
294
|
+
useLatestTestCaseProperties: boolean = false
|
|
294
295
|
) {
|
|
295
296
|
const fetchedTestResults: any[] = [];
|
|
296
297
|
logger.debug(
|
|
@@ -322,7 +323,9 @@ export default class ResultDataProvider {
|
|
|
322
323
|
projectName,
|
|
323
324
|
selectedFields,
|
|
324
325
|
isQueryMode,
|
|
325
|
-
includeAllHistory
|
|
326
|
+
includeAllHistory,
|
|
327
|
+
false,
|
|
328
|
+
useLatestTestCaseProperties
|
|
326
329
|
);
|
|
327
330
|
logger.debug(`Aligning steps with iterations for test reporter results`);
|
|
328
331
|
const testReporterData = this.alignStepsWithIterationsTestReporter(
|
|
@@ -3656,10 +3659,16 @@ export default class ResultDataProvider {
|
|
|
3656
3659
|
suiteTestCaseRevision: number,
|
|
3657
3660
|
pointAsOfTimestamp: string,
|
|
3658
3661
|
fallbackSnapshot: any,
|
|
3659
|
-
expandAll: boolean
|
|
3662
|
+
expandAll: boolean,
|
|
3663
|
+
useLatestTestCaseProperties: boolean = false
|
|
3660
3664
|
): Promise<any | null> {
|
|
3661
3665
|
let bestSnapshotWithoutSteps: any | null = null;
|
|
3662
3666
|
|
|
3667
|
+
if (useLatestTestCaseProperties) {
|
|
3668
|
+
const latest = await this.fetchWorkItemLatest(projectName, testCaseId, expandAll);
|
|
3669
|
+
if (latest) return latest;
|
|
3670
|
+
}
|
|
3671
|
+
|
|
3663
3672
|
if (pointAsOfTimestamp) {
|
|
3664
3673
|
const asOfSnapshot = await this.fetchWorkItemByAsOf(
|
|
3665
3674
|
projectName,
|
|
@@ -3721,14 +3730,15 @@ export default class ResultDataProvider {
|
|
|
3721
3730
|
isQueryMode?: boolean,
|
|
3722
3731
|
point?: any,
|
|
3723
3732
|
includeAllHistory: boolean = false,
|
|
3724
|
-
useRunlessAsOf: boolean = false
|
|
3733
|
+
useRunlessAsOf: boolean = false,
|
|
3734
|
+
useLatestTestCaseProperties: boolean = false
|
|
3725
3735
|
): Promise<any> {
|
|
3726
3736
|
try {
|
|
3727
3737
|
let filteredFields: any = {};
|
|
3728
3738
|
let relatedRequirements: any[] = [];
|
|
3729
3739
|
let relatedBugs: any[] = [];
|
|
3730
3740
|
let relatedCRs: any[] = [];
|
|
3731
|
-
if (runId === '0' || resultId === '0') {
|
|
3741
|
+
if (useLatestTestCaseProperties || runId === '0' || resultId === '0') {
|
|
3732
3742
|
if (!point) {
|
|
3733
3743
|
logger.warn(`Invalid run result ${runId} or result ${resultId}`);
|
|
3734
3744
|
return null;
|
|
@@ -3752,7 +3762,8 @@ export default class ResultDataProvider {
|
|
|
3752
3762
|
suiteTestCaseRevision,
|
|
3753
3763
|
pointAsOfTimestamp,
|
|
3754
3764
|
fallbackSnapshot,
|
|
3755
|
-
isTestReporter
|
|
3765
|
+
isTestReporter,
|
|
3766
|
+
useLatestTestCaseProperties
|
|
3756
3767
|
);
|
|
3757
3768
|
if (!testCaseData) {
|
|
3758
3769
|
logger.warn(`Could not resolve test case ${point.testCaseId} for runless point fallback.`);
|
|
@@ -5465,7 +5476,8 @@ export default class ResultDataProvider {
|
|
|
5465
5476
|
isQueryMode?: boolean,
|
|
5466
5477
|
point?: any,
|
|
5467
5478
|
includeAllHistory: boolean = false,
|
|
5468
|
-
useRunlessAsOf: boolean = false
|
|
5479
|
+
useRunlessAsOf: boolean = false,
|
|
5480
|
+
useLatestTestCaseProperties: boolean = false
|
|
5469
5481
|
): Promise<any> {
|
|
5470
5482
|
return this.fetchResultDataBasedOnWiBase(
|
|
5471
5483
|
projectName,
|
|
@@ -5476,7 +5488,8 @@ export default class ResultDataProvider {
|
|
|
5476
5488
|
isQueryMode,
|
|
5477
5489
|
point,
|
|
5478
5490
|
includeAllHistory,
|
|
5479
|
-
useRunlessAsOf
|
|
5491
|
+
useRunlessAsOf,
|
|
5492
|
+
useLatestTestCaseProperties
|
|
5480
5493
|
);
|
|
5481
5494
|
}
|
|
5482
5495
|
|
|
@@ -5498,13 +5511,14 @@ export default class ResultDataProvider {
|
|
|
5498
5511
|
selectedFields?: string[],
|
|
5499
5512
|
isQueryMode?: boolean,
|
|
5500
5513
|
includeAllHistory: boolean = false,
|
|
5501
|
-
useRunlessAsOf: boolean = false
|
|
5514
|
+
useRunlessAsOf: boolean = false,
|
|
5515
|
+
useLatestTestCaseProperties: boolean = false
|
|
5502
5516
|
): Promise<any[]> {
|
|
5503
5517
|
return this.fetchAllResultDataBase(
|
|
5504
5518
|
testData,
|
|
5505
5519
|
projectName,
|
|
5506
5520
|
true,
|
|
5507
|
-
(projectName, testSuiteId, point, selectedFields, isQueryMode, includeAllHistory, useRunlessAsOf) =>
|
|
5521
|
+
(projectName, testSuiteId, point, selectedFields, isQueryMode, includeAllHistory, useRunlessAsOf, useLatestTestCaseProperties) =>
|
|
5508
5522
|
this.fetchResultDataForTestReporter(
|
|
5509
5523
|
projectName,
|
|
5510
5524
|
testSuiteId,
|
|
@@ -5512,9 +5526,10 @@ export default class ResultDataProvider {
|
|
|
5512
5526
|
selectedFields,
|
|
5513
5527
|
isQueryMode,
|
|
5514
5528
|
includeAllHistory,
|
|
5515
|
-
useRunlessAsOf
|
|
5529
|
+
useRunlessAsOf,
|
|
5530
|
+
useLatestTestCaseProperties
|
|
5516
5531
|
),
|
|
5517
|
-
[selectedFields, isQueryMode, includeAllHistory, useRunlessAsOf]
|
|
5532
|
+
[selectedFields, isQueryMode, includeAllHistory, useRunlessAsOf, useLatestTestCaseProperties]
|
|
5518
5533
|
);
|
|
5519
5534
|
}
|
|
5520
5535
|
|
|
@@ -5673,13 +5688,14 @@ export default class ResultDataProvider {
|
|
|
5673
5688
|
selectedFields?: string[],
|
|
5674
5689
|
isQueryMode?: boolean,
|
|
5675
5690
|
includeAllHistory: boolean = false,
|
|
5676
|
-
useRunlessAsOf: boolean = false
|
|
5691
|
+
useRunlessAsOf: boolean = false,
|
|
5692
|
+
useLatestTestCaseProperties: boolean = false
|
|
5677
5693
|
) {
|
|
5678
5694
|
return this.fetchResultDataBase(
|
|
5679
5695
|
projectName,
|
|
5680
5696
|
testSuiteId,
|
|
5681
5697
|
point,
|
|
5682
|
-
(project, runId, resultId, fields, isQueryMode, point, includeAllHistory, useRunlessAsOf) =>
|
|
5698
|
+
(project, runId, resultId, fields, isQueryMode, point, includeAllHistory, useRunlessAsOf, useLatestTestCaseProperties) =>
|
|
5683
5699
|
this.fetchResultDataBasedOnWiTestReporter(
|
|
5684
5700
|
project,
|
|
5685
5701
|
runId,
|
|
@@ -5688,7 +5704,8 @@ export default class ResultDataProvider {
|
|
|
5688
5704
|
isQueryMode,
|
|
5689
5705
|
point,
|
|
5690
5706
|
includeAllHistory,
|
|
5691
|
-
useRunlessAsOf
|
|
5707
|
+
useRunlessAsOf,
|
|
5708
|
+
useLatestTestCaseProperties
|
|
5692
5709
|
),
|
|
5693
5710
|
(resultData, testSuiteId, point, selectedFields) => {
|
|
5694
5711
|
const { lastRunId, lastResultId, configurationName, lastResultDetails } = point;
|
|
@@ -3478,4 +3478,129 @@ export default class TicketsDataProvider {
|
|
|
3478
3478
|
throw err;
|
|
3479
3479
|
}
|
|
3480
3480
|
}
|
|
3481
|
+
|
|
3482
|
+
/**
|
|
3483
|
+
* Returns per-side valid column lists for trace analysis Column Settings dialog.
|
|
3484
|
+
* Uses the WIT field schema (~6 ADO calls, constant cost) to determine which
|
|
3485
|
+
* declared query columns are valid for the Requirement side vs the Test Case side.
|
|
3486
|
+
*
|
|
3487
|
+
* Known limitation: assumes one WIT type per side (determined by sampling the first
|
|
3488
|
+
* source + first target work item). Trace queries are single-type per side in practice.
|
|
3489
|
+
*/
|
|
3490
|
+
async GetTraceColumnsByType(
|
|
3491
|
+
reqTestWiqlHref: string | undefined,
|
|
3492
|
+
testReqWiqlHref: string | undefined,
|
|
3493
|
+
project: string,
|
|
3494
|
+
): Promise<{ Requirement: any[]; 'Test Case': any[] }> {
|
|
3495
|
+
const normalizeCols = (columns: any[]): any[] =>
|
|
3496
|
+
(columns || []).map((c: any) => ({
|
|
3497
|
+
referenceName: c.referenceName,
|
|
3498
|
+
name: c.name,
|
|
3499
|
+
}));
|
|
3500
|
+
|
|
3501
|
+
const mergeCols = (a: any[], b: any[]): any[] => {
|
|
3502
|
+
const seen = new Set<string>();
|
|
3503
|
+
const result: any[] = [];
|
|
3504
|
+
for (const col of [...a, ...b]) {
|
|
3505
|
+
if (!seen.has(col.referenceName)) {
|
|
3506
|
+
seen.add(col.referenceName);
|
|
3507
|
+
result.push(col);
|
|
3508
|
+
}
|
|
3509
|
+
}
|
|
3510
|
+
return result;
|
|
3511
|
+
};
|
|
3512
|
+
|
|
3513
|
+
// Sample one work item to read its System.WorkItemType.
|
|
3514
|
+
// In OneHop query relations: !relation.source means root (target = source WI);
|
|
3515
|
+
// relation.source present means link (target = target WI).
|
|
3516
|
+
const sampleWitType = async (relations: any[], isSource: boolean): Promise<string | undefined> => {
|
|
3517
|
+
let url: string | undefined;
|
|
3518
|
+
if (isSource) {
|
|
3519
|
+
const rootRel = relations.find((r) => !r.source && r.target?.url);
|
|
3520
|
+
url = rootRel?.target?.url;
|
|
3521
|
+
} else {
|
|
3522
|
+
const linkRel = relations.find((r) => r.source && r.target?.url);
|
|
3523
|
+
url = linkRel?.target?.url;
|
|
3524
|
+
}
|
|
3525
|
+
if (!url) return undefined;
|
|
3526
|
+
try {
|
|
3527
|
+
const wi: any = await TFSServices.getItemContent(url, this.token);
|
|
3528
|
+
return wi?.fields?.['System.WorkItemType'];
|
|
3529
|
+
} catch {
|
|
3530
|
+
return undefined;
|
|
3531
|
+
}
|
|
3532
|
+
};
|
|
3533
|
+
|
|
3534
|
+
const fetchTypeFieldSet = async (typeName: string): Promise<Set<string>> => {
|
|
3535
|
+
const query = new URLSearchParams({ 'api-version': '5.1' }).toString();
|
|
3536
|
+
const url = `${this.orgUrl}${encodeURIComponent(project)}/_apis/wit/workitemtypes/${encodeURIComponent(typeName)}/fields?${query}`;
|
|
3537
|
+
const res = await TFSServices.getItemContent(url, this.token);
|
|
3538
|
+
const fields = res?.value ?? [];
|
|
3539
|
+
return new Set<string>((fields as any[]).map((f: any) => f.referenceName));
|
|
3540
|
+
};
|
|
3541
|
+
|
|
3542
|
+
try {
|
|
3543
|
+
// Step 1: fetch declared columns from provided query hrefs
|
|
3544
|
+
let reqTestCols: any[] = [];
|
|
3545
|
+
let testReqCols: any[] = [];
|
|
3546
|
+
let reqTestRelations: any[] = [];
|
|
3547
|
+
let testReqRelations: any[] = [];
|
|
3548
|
+
|
|
3549
|
+
if (reqTestWiqlHref) {
|
|
3550
|
+
const qr: any = await TFSServices.getItemContent(reqTestWiqlHref, this.token);
|
|
3551
|
+
reqTestCols = normalizeCols(qr?.columns || []);
|
|
3552
|
+
reqTestRelations = qr?.workItemRelations || [];
|
|
3553
|
+
}
|
|
3554
|
+
if (testReqWiqlHref) {
|
|
3555
|
+
const qr: any = await TFSServices.getItemContent(testReqWiqlHref, this.token);
|
|
3556
|
+
testReqCols = normalizeCols(qr?.columns || []);
|
|
3557
|
+
testReqRelations = qr?.workItemRelations || [];
|
|
3558
|
+
}
|
|
3559
|
+
|
|
3560
|
+
const declaredCols = mergeCols(reqTestCols, testReqCols);
|
|
3561
|
+
if (declaredCols.length === 0) {
|
|
3562
|
+
return { Requirement: [], 'Test Case': [] };
|
|
3563
|
+
}
|
|
3564
|
+
|
|
3565
|
+
// Step 2: sample WIT type names
|
|
3566
|
+
// reqTest: source = Requirement, target = Test Case
|
|
3567
|
+
// testReq: source = Test Case, target = Requirement
|
|
3568
|
+
let reqTypeName: string | undefined;
|
|
3569
|
+
let tcTypeName: string | undefined;
|
|
3570
|
+
|
|
3571
|
+
if (reqTestRelations.length > 0) {
|
|
3572
|
+
[reqTypeName, tcTypeName] = await Promise.all([
|
|
3573
|
+
sampleWitType(reqTestRelations, true),
|
|
3574
|
+
sampleWitType(reqTestRelations, false),
|
|
3575
|
+
]);
|
|
3576
|
+
}
|
|
3577
|
+
if ((!reqTypeName || !tcTypeName) && testReqRelations.length > 0) {
|
|
3578
|
+
const [tSrc, tTgt] = await Promise.all([
|
|
3579
|
+
sampleWitType(testReqRelations, true),
|
|
3580
|
+
sampleWitType(testReqRelations, false),
|
|
3581
|
+
]);
|
|
3582
|
+
if (!tcTypeName) tcTypeName = tSrc;
|
|
3583
|
+
if (!reqTypeName) reqTypeName = tTgt;
|
|
3584
|
+
}
|
|
3585
|
+
|
|
3586
|
+
if (!reqTypeName || !tcTypeName) {
|
|
3587
|
+
logger.warn('GetTraceColumnsByType: could not determine WIT type names — returning merged columns');
|
|
3588
|
+
return { Requirement: declaredCols, 'Test Case': declaredCols };
|
|
3589
|
+
}
|
|
3590
|
+
|
|
3591
|
+
// Step 3: fetch WIT field schemas and intersect with declared columns
|
|
3592
|
+
const [reqFieldSet, tcFieldSet] = await Promise.all([
|
|
3593
|
+
fetchTypeFieldSet(reqTypeName),
|
|
3594
|
+
fetchTypeFieldSet(tcTypeName),
|
|
3595
|
+
]);
|
|
3596
|
+
|
|
3597
|
+
return {
|
|
3598
|
+
Requirement: declaredCols.filter((c) => reqFieldSet.has(c.referenceName)),
|
|
3599
|
+
'Test Case': declaredCols.filter((c) => tcFieldSet.has(c.referenceName)),
|
|
3600
|
+
};
|
|
3601
|
+
} catch (err: any) {
|
|
3602
|
+
logger.error(`GetTraceColumnsByType failed: ${err.message}`);
|
|
3603
|
+
return { Requirement: [], 'Test Case': [] };
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3481
3606
|
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { TFSServices } from '../../helpers/tfs';
|
|
2
|
+
import TicketsDataProvider from '../../modules/TicketsDataProvider';
|
|
3
|
+
import logger from '../../utils/logger';
|
|
4
|
+
|
|
5
|
+
jest.mock('../../helpers/tfs');
|
|
6
|
+
jest.mock('../../utils/logger');
|
|
7
|
+
|
|
8
|
+
const mockGetItemContent = TFSServices.getItemContent as jest.Mock;
|
|
9
|
+
|
|
10
|
+
// Minimal OneHop query result shape
|
|
11
|
+
const makeQueryResult = (columns: any[], relations: any[]) => ({
|
|
12
|
+
columns,
|
|
13
|
+
workItemRelations: relations,
|
|
14
|
+
queryType: 'oneHop',
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// Root relation (source = the root WI, no .source field)
|
|
18
|
+
const rootRelation = (targetId: number, targetUrl: string) => ({
|
|
19
|
+
source: null,
|
|
20
|
+
target: { id: targetId, url: targetUrl },
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Link relation (source WI → target WI)
|
|
24
|
+
const linkRelation = (sourceId: number, targetId: number, targetUrl: string) => ({
|
|
25
|
+
source: { id: sourceId },
|
|
26
|
+
target: { id: targetId, url: targetUrl },
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const REQ_COLS = [
|
|
30
|
+
{ referenceName: 'System.Id', name: 'ID' },
|
|
31
|
+
{ referenceName: 'System.Title', name: 'Title' },
|
|
32
|
+
{ referenceName: 'Custom.CustomerRequirementId', name: 'CustomerRequirementId' },
|
|
33
|
+
{ referenceName: 'Microsoft.VSTS.Common.Priority', name: 'Priority' },
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const TC_COLS = [
|
|
37
|
+
{ referenceName: 'System.Id', name: 'ID' },
|
|
38
|
+
{ referenceName: 'System.Title', name: 'Title' },
|
|
39
|
+
{ referenceName: 'Microsoft.VSTS.TCM.AutomationStatus', name: 'Automation Status' },
|
|
40
|
+
{ referenceName: 'Microsoft.VSTS.Common.Priority', name: 'Priority' },
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
const REQ_FIELDS_SCHEMA = {
|
|
44
|
+
value: [
|
|
45
|
+
{ referenceName: 'System.Id' },
|
|
46
|
+
{ referenceName: 'System.Title' },
|
|
47
|
+
{ referenceName: 'Custom.CustomerRequirementId' },
|
|
48
|
+
{ referenceName: 'Microsoft.VSTS.Common.Priority' },
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const TC_FIELDS_SCHEMA = {
|
|
53
|
+
value: [
|
|
54
|
+
{ referenceName: 'System.Id' },
|
|
55
|
+
{ referenceName: 'System.Title' },
|
|
56
|
+
{ referenceName: 'Microsoft.VSTS.TCM.AutomationStatus' },
|
|
57
|
+
{ referenceName: 'Microsoft.VSTS.Common.Priority' },
|
|
58
|
+
// Note: Custom.CustomerRequirementId is NOT in TC schema
|
|
59
|
+
],
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
describe('TicketsDataProvider.GetTraceColumnsByType', () => {
|
|
63
|
+
let provider: TicketsDataProvider;
|
|
64
|
+
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
jest.clearAllMocks();
|
|
67
|
+
provider = new TicketsDataProvider('https://dev.azure.com/org/', 'mock-token');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('splits columns correctly: CustomerRequirementId in Requirement only, not Test Case', async () => {
|
|
71
|
+
const reqRelations = [
|
|
72
|
+
rootRelation(1, 'https://ado/wi/1'),
|
|
73
|
+
linkRelation(1, 101, 'https://ado/wi/101'),
|
|
74
|
+
];
|
|
75
|
+
const reqQueryResult = makeQueryResult(REQ_COLS, reqRelations);
|
|
76
|
+
|
|
77
|
+
mockGetItemContent
|
|
78
|
+
.mockResolvedValueOnce(reqQueryResult) // reqTest wiql fetch
|
|
79
|
+
.mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Requirement' } }) // source WI sample
|
|
80
|
+
.mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Test Case' } }) // target WI sample
|
|
81
|
+
.mockResolvedValueOnce(REQ_FIELDS_SCHEMA) // Requirement type fields
|
|
82
|
+
.mockResolvedValueOnce(TC_FIELDS_SCHEMA); // Test Case type fields
|
|
83
|
+
|
|
84
|
+
const result = await provider.GetTraceColumnsByType(
|
|
85
|
+
'https://ado/wiql/reqtest',
|
|
86
|
+
undefined,
|
|
87
|
+
'my-project',
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const reqRefs = result.Requirement.map((c: any) => c.referenceName);
|
|
91
|
+
const tcRefs = result['Test Case'].map((c: any) => c.referenceName);
|
|
92
|
+
|
|
93
|
+
expect(reqRefs).toContain('Custom.CustomerRequirementId');
|
|
94
|
+
expect(tcRefs).not.toContain('Custom.CustomerRequirementId');
|
|
95
|
+
expect(reqRefs).toContain('Microsoft.VSTS.Common.Priority');
|
|
96
|
+
expect(tcRefs).toContain('Microsoft.VSTS.Common.Priority');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('merges columns from both queries (reqTest + testReq), deduped', async () => {
|
|
100
|
+
const reqRelations = [rootRelation(1, 'https://ado/wi/1'), linkRelation(1, 101, 'https://ado/wi/101')];
|
|
101
|
+
const testRelations = [rootRelation(101, 'https://ado/wi/101'), linkRelation(101, 1, 'https://ado/wi/1')];
|
|
102
|
+
|
|
103
|
+
const extraCol = { referenceName: 'System.AreaPath', name: 'Area Path' };
|
|
104
|
+
|
|
105
|
+
mockGetItemContent
|
|
106
|
+
.mockResolvedValueOnce(makeQueryResult(REQ_COLS, reqRelations)) // reqTest wiql
|
|
107
|
+
.mockResolvedValueOnce(makeQueryResult([...TC_COLS, extraCol], testRelations)) // testReq wiql
|
|
108
|
+
.mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Requirement' } }) // source sample reqTest
|
|
109
|
+
.mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Test Case' } }) // target sample reqTest
|
|
110
|
+
.mockResolvedValueOnce(REQ_FIELDS_SCHEMA)
|
|
111
|
+
.mockResolvedValueOnce({ value: [...TC_FIELDS_SCHEMA.value, { referenceName: 'System.AreaPath' }] });
|
|
112
|
+
|
|
113
|
+
const result = await provider.GetTraceColumnsByType(
|
|
114
|
+
'https://ado/wiql/reqtest',
|
|
115
|
+
'https://ado/wiql/testreq',
|
|
116
|
+
'my-project',
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const tcRefs = result['Test Case'].map((c: any) => c.referenceName);
|
|
120
|
+
expect(tcRefs).toContain('System.AreaPath'); // came only from testReq columns
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('raw display names passed through without rename (no CustomerRequirementId → Customer ID)', async () => {
|
|
124
|
+
const reqRelations = [rootRelation(1, 'https://ado/wi/1'), linkRelation(1, 101, 'https://ado/wi/101')];
|
|
125
|
+
|
|
126
|
+
mockGetItemContent
|
|
127
|
+
.mockResolvedValueOnce(makeQueryResult(REQ_COLS, reqRelations))
|
|
128
|
+
.mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Requirement' } })
|
|
129
|
+
.mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Test Case' } })
|
|
130
|
+
.mockResolvedValueOnce(REQ_FIELDS_SCHEMA)
|
|
131
|
+
.mockResolvedValueOnce(TC_FIELDS_SCHEMA);
|
|
132
|
+
|
|
133
|
+
const result = await provider.GetTraceColumnsByType('https://ado/wiql/reqtest', undefined, 'proj');
|
|
134
|
+
|
|
135
|
+
const custCol = result.Requirement.find((c: any) => c.referenceName === 'Custom.CustomerRequirementId');
|
|
136
|
+
expect(custCol?.name).toBe('CustomerRequirementId'); // raw ADO name, NOT 'Customer ID'
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('gracefully degrades when WIT type cannot be sampled (no relations) — returns merged columns both sides', async () => {
|
|
140
|
+
const emptyRelations: any[] = [];
|
|
141
|
+
|
|
142
|
+
mockGetItemContent.mockResolvedValueOnce(makeQueryResult(REQ_COLS, emptyRelations));
|
|
143
|
+
|
|
144
|
+
const result = await provider.GetTraceColumnsByType('https://ado/wiql/reqtest', undefined, 'proj');
|
|
145
|
+
|
|
146
|
+
const reqRefs = result.Requirement.map((c: any) => c.referenceName);
|
|
147
|
+
const tcRefs = result['Test Case'].map((c: any) => c.referenceName);
|
|
148
|
+
// Both sides get the merged declared columns as fallback
|
|
149
|
+
expect(reqRefs).toContain('Custom.CustomerRequirementId');
|
|
150
|
+
expect(tcRefs).toContain('Custom.CustomerRequirementId');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('returns empty arrays when wiql fetch fails', async () => {
|
|
154
|
+
mockGetItemContent.mockRejectedValueOnce(new Error('Network error'));
|
|
155
|
+
|
|
156
|
+
const result = await provider.GetTraceColumnsByType('https://ado/wiql/reqtest', undefined, 'proj');
|
|
157
|
+
|
|
158
|
+
expect(result).toEqual({ Requirement: [], 'Test Case': [] });
|
|
159
|
+
expect((logger.error as jest.Mock)).toHaveBeenCalled();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('returns empty arrays when schema fetch fails', async () => {
|
|
163
|
+
const reqRelations = [rootRelation(1, 'https://ado/wi/1'), linkRelation(1, 101, 'https://ado/wi/101')];
|
|
164
|
+
|
|
165
|
+
mockGetItemContent
|
|
166
|
+
.mockResolvedValueOnce(makeQueryResult(REQ_COLS, reqRelations))
|
|
167
|
+
.mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Requirement' } })
|
|
168
|
+
.mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Test Case' } })
|
|
169
|
+
.mockRejectedValueOnce(new Error('ADO 404')); // schema fetch fails
|
|
170
|
+
|
|
171
|
+
const result = await provider.GetTraceColumnsByType('https://ado/wiql/reqtest', undefined, 'proj');
|
|
172
|
+
|
|
173
|
+
expect(result).toEqual({ Requirement: [], 'Test Case': [] });
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('handles undefined schema value gracefully (no crash on null response)', async () => {
|
|
177
|
+
const reqRelations = [rootRelation(1, 'https://ado/wi/1'), linkRelation(1, 101, 'https://ado/wi/101')];
|
|
178
|
+
|
|
179
|
+
mockGetItemContent
|
|
180
|
+
.mockResolvedValueOnce(makeQueryResult(REQ_COLS, reqRelations))
|
|
181
|
+
.mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Requirement' } })
|
|
182
|
+
.mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Test Case' } })
|
|
183
|
+
.mockResolvedValueOnce(undefined) // schema returns undefined (null guard fix)
|
|
184
|
+
.mockResolvedValueOnce(undefined);
|
|
185
|
+
|
|
186
|
+
const result = await provider.GetTraceColumnsByType('https://ado/wiql/reqtest', undefined, 'proj');
|
|
187
|
+
|
|
188
|
+
// Both field sets are empty → both sides empty after intersection
|
|
189
|
+
expect(result.Requirement).toEqual([]);
|
|
190
|
+
expect(result['Test Case']).toEqual([]);
|
|
191
|
+
});
|
|
192
|
+
});
|