@elisra-devops/docgen-data-provider 1.11.0 → 1.12.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/models/tfs-data.d.ts +1 -0
- package/bin/modules/PipelinesDataProvider.d.ts +114 -3
- package/bin/modules/PipelinesDataProvider.js +206 -40
- package/bin/modules/PipelinesDataProvider.js.map +1 -1
- package/bin/modules/TicketsDataProvider.d.ts +19 -2
- package/bin/modules/TicketsDataProvider.js +60 -15
- package/bin/modules/TicketsDataProvider.js.map +1 -1
- package/bin/modules/test/pipelineDataProvider.test.js +19 -19
- package/bin/modules/test/pipelineDataProvider.test.js.map +1 -1
- package/package.json +1 -1
- package/src/models/tfs-data.ts +1 -0
- package/src/modules/PipelinesDataProvider.ts +241 -53
- package/src/modules/TicketsDataProvider.ts +81 -17
- package/src/modules/test/pipelineDataProvider.test.ts +30 -47
|
@@ -14,65 +14,182 @@ export default class PipelinesDataProvider {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
public async findPreviousPipeline(
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
teamProject: string,
|
|
18
|
+
pipelineId: string,
|
|
19
19
|
toPipelineRunId: number,
|
|
20
|
-
|
|
21
|
-
searchPrevPipelineFromDifferentCommit: boolean
|
|
20
|
+
targetPipeline: any,
|
|
21
|
+
searchPrevPipelineFromDifferentCommit: boolean,
|
|
22
|
+
fromStage: string = ''
|
|
22
23
|
) {
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
const pipelineRuns = await this.GetPipelineRunHistory(teamProject, pipelineId);
|
|
25
|
+
if (!pipelineRuns.value) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
25
28
|
|
|
26
29
|
for (const pipelineRun of pipelineRuns.value) {
|
|
27
|
-
if (pipelineRun
|
|
30
|
+
if (this.isInvalidPipelineRun(pipelineRun, toPipelineRunId, fromStage)) {
|
|
28
31
|
continue;
|
|
29
32
|
}
|
|
30
|
-
|
|
33
|
+
|
|
34
|
+
if (fromStage && !(await this.isStageSuccessful(pipelineRun, teamProject, fromStage))) {
|
|
31
35
|
continue;
|
|
32
36
|
}
|
|
33
37
|
|
|
34
|
-
const fromPipeline = await this.
|
|
38
|
+
const fromPipeline = await this.getPipelineRunDetails(teamProject, Number(pipelineId), pipelineRun.id);
|
|
35
39
|
if (!fromPipeline.resources.repositories) {
|
|
36
40
|
continue;
|
|
37
41
|
}
|
|
38
|
-
const fromPipelineRepositories = fromPipeline.resources.repositories;
|
|
39
|
-
logger.debug(`from pipeline repositories ${JSON.stringify(fromPipelineRepositories)}`);
|
|
40
|
-
const toPipelineRepositories = toPipeline.resources.repositories;
|
|
41
|
-
logger.debug(`to pipeline repositories ${JSON.stringify(toPipelineRepositories)}`);
|
|
42
|
-
|
|
43
|
-
const fromPipeLineSelfRepo =
|
|
44
|
-
'__designer_repo' in fromPipelineRepositories
|
|
45
|
-
? fromPipelineRepositories['__designer_repo']
|
|
46
|
-
: 'self' in fromPipelineRepositories
|
|
47
|
-
? fromPipelineRepositories['self']
|
|
48
|
-
: undefined;
|
|
49
|
-
const toPipeLineSelfRepo =
|
|
50
|
-
'__designer_repo' in toPipelineRepositories
|
|
51
|
-
? toPipelineRepositories['__designer_repo']
|
|
52
|
-
: 'self' in toPipelineRepositories
|
|
53
|
-
? toPipelineRepositories['self']
|
|
54
|
-
: undefined;
|
|
55
|
-
if (
|
|
56
|
-
fromPipeLineSelfRepo.repository.id === toPipeLineSelfRepo.repository.id &&
|
|
57
|
-
fromPipeLineSelfRepo.version === toPipeLineSelfRepo.version
|
|
58
|
-
) {
|
|
59
|
-
if (searchPrevPipelineFromDifferentCommit) {
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
42
|
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
fromPipeLineSelfRepo.refName !== toPipeLineSelfRepo.refName
|
|
67
|
-
) {
|
|
68
|
-
continue;
|
|
43
|
+
if (this.isMatchingPipeline(fromPipeline, targetPipeline, searchPrevPipelineFromDifferentCommit)) {
|
|
44
|
+
return pipelineRun.id;
|
|
69
45
|
}
|
|
70
|
-
|
|
71
|
-
return pipelineRun.id;
|
|
72
46
|
}
|
|
73
47
|
return undefined;
|
|
74
48
|
}
|
|
75
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Determines if a pipeline run is invalid based on various conditions.
|
|
52
|
+
*
|
|
53
|
+
* @param pipelineRun - The pipeline run object to evaluate.
|
|
54
|
+
* @param toPipelineRunId - The pipeline run ID to compare against.
|
|
55
|
+
* @param fromStage - The stage from which the pipeline run originated.
|
|
56
|
+
* @returns `true` if the pipeline run is considered invalid, `false` otherwise.
|
|
57
|
+
*/
|
|
58
|
+
private isInvalidPipelineRun(pipelineRun: any, toPipelineRunId: number, fromStage: string): boolean {
|
|
59
|
+
return (
|
|
60
|
+
pipelineRun.id >= toPipelineRunId ||
|
|
61
|
+
['canceled', 'failed', 'canceling'].includes(pipelineRun.result) ||
|
|
62
|
+
(pipelineRun.result === 'unknown' && !fromStage) ||
|
|
63
|
+
(pipelineRun.result !== 'succeeded' && !fromStage)
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Checks if a specific stage in a pipeline run was successful.
|
|
69
|
+
*
|
|
70
|
+
* @param pipelineRun - The pipeline run object containing details of the run.
|
|
71
|
+
* @param teamProject - The name of the team project.
|
|
72
|
+
* @param fromStage - The name of the stage to check.
|
|
73
|
+
* @returns A promise that resolves to a boolean indicating whether the stage was successful.
|
|
74
|
+
*/
|
|
75
|
+
private async isStageSuccessful(
|
|
76
|
+
pipelineRun: any,
|
|
77
|
+
teamProject: string,
|
|
78
|
+
fromStage: string
|
|
79
|
+
): Promise<boolean> {
|
|
80
|
+
const fromPipelineStage = await this.getPipelineStageName(pipelineRun, teamProject, fromStage);
|
|
81
|
+
return (
|
|
82
|
+
fromPipelineStage && fromPipelineStage.state === 'completed' && fromPipelineStage.result === 'succeeded'
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Determines if two pipelines match based on their repository and version information.
|
|
88
|
+
*
|
|
89
|
+
* @param fromPipeline - The source pipeline to compare.
|
|
90
|
+
* @param targetPipeline - The target pipeline to compare against.
|
|
91
|
+
* @param searchPrevPipelineFromDifferentCommit - A flag indicating whether to search for a previous pipeline from a different commit.
|
|
92
|
+
* @returns `true` if the pipelines match based on the repository and version criteria; otherwise, `false`.
|
|
93
|
+
*/
|
|
94
|
+
private isMatchingPipeline(
|
|
95
|
+
fromPipeline: PipelineRun,
|
|
96
|
+
targetPipeline: PipelineRun,
|
|
97
|
+
searchPrevPipelineFromDifferentCommit: boolean
|
|
98
|
+
): boolean {
|
|
99
|
+
const fromRepo =
|
|
100
|
+
fromPipeline.resources.repositories[0].self || fromPipeline.resources.repositories.__designer_repo;
|
|
101
|
+
const targetRepo =
|
|
102
|
+
targetPipeline.resources.repositories[0].self || targetPipeline.resources.repositories.__designer_repo;
|
|
103
|
+
|
|
104
|
+
if (fromRepo.repository.id !== targetRepo.repository.id) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (fromRepo.version === targetRepo.version) {
|
|
109
|
+
return !searchPrevPipelineFromDifferentCommit;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return fromRepo.refName === targetRepo.refName;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Retrieves a set of pipeline resources from a given pipeline run object.
|
|
117
|
+
*
|
|
118
|
+
* @param inPipeline - The pipeline run object containing resources.
|
|
119
|
+
* @returns A promise that resolves to an array of unique pipeline resource objects.
|
|
120
|
+
*
|
|
121
|
+
* The function performs the following steps:
|
|
122
|
+
* 1. Initializes an empty set to store unique pipeline resources.
|
|
123
|
+
* 2. Checks if the input pipeline has any resources of type pipelines.
|
|
124
|
+
* 3. Iterates over each pipeline resource and processes it.
|
|
125
|
+
* 4. Fixes the URL of the pipeline resource to match the build API format.
|
|
126
|
+
* 5. Fetches the build details using the fixed URL.
|
|
127
|
+
* 6. If the build response is valid and matches the criteria, adds the pipeline resource to the set.
|
|
128
|
+
* 7. Returns an array of unique pipeline resources.
|
|
129
|
+
*
|
|
130
|
+
* The returned pipeline resource object contains the following properties:
|
|
131
|
+
* - name: The alias name of the resource pipeline.
|
|
132
|
+
* - buildId: The ID of the resource pipeline.
|
|
133
|
+
* - definitionId: The ID of the build definition.
|
|
134
|
+
* - buildNumber: The build number.
|
|
135
|
+
* - teamProject: The name of the team project.
|
|
136
|
+
* - provider: The type of repository provider.
|
|
137
|
+
*
|
|
138
|
+
* @throws Will log an error message if there is an issue fetching the pipeline resource.
|
|
139
|
+
*/
|
|
140
|
+
public async getPipelineResourcePipelinesFromObject(inPipeline: PipelineRun) {
|
|
141
|
+
const resourcePipelines: Set<any> = new Set();
|
|
142
|
+
|
|
143
|
+
if (!inPipeline.resources.pipelines) {
|
|
144
|
+
return resourcePipelines;
|
|
145
|
+
}
|
|
146
|
+
const pipelines = inPipeline.resources.pipelines;
|
|
147
|
+
|
|
148
|
+
const pipelineEntries = Object.entries(pipelines);
|
|
149
|
+
|
|
150
|
+
await Promise.all(
|
|
151
|
+
pipelineEntries.map(async ([resourcePipelineAlias, resource]) => {
|
|
152
|
+
const resourcePipelineObj = (resource as any).pipeline;
|
|
153
|
+
const resourcePipelineName = resourcePipelineAlias;
|
|
154
|
+
let urlBeforeFix = resourcePipelineObj.url;
|
|
155
|
+
urlBeforeFix = urlBeforeFix.substring(0, urlBeforeFix.indexOf('?revision'));
|
|
156
|
+
const fixedUrl = urlBeforeFix.replace('/_apis/pipelines/', '/_apis/build/builds/');
|
|
157
|
+
let buildResponse: any;
|
|
158
|
+
try {
|
|
159
|
+
buildResponse = await TFSServices.getItemContent(fixedUrl, this.token, 'get');
|
|
160
|
+
} catch (err: any) {
|
|
161
|
+
logger.error(`Error fetching pipeline ${resourcePipelineName} : ${err.message}`);
|
|
162
|
+
}
|
|
163
|
+
if (
|
|
164
|
+
buildResponse &&
|
|
165
|
+
buildResponse.definition.type === 'build' &&
|
|
166
|
+
buildResponse.repository.type === 'TfsGit'
|
|
167
|
+
) {
|
|
168
|
+
let resourcePipelineToAdd = {
|
|
169
|
+
name: resourcePipelineName,
|
|
170
|
+
buildId: resourcePipelineObj.id,
|
|
171
|
+
definitionId: buildResponse.definition.id,
|
|
172
|
+
buildNumber: buildResponse.buildNumber,
|
|
173
|
+
teamProject: buildResponse.project.name,
|
|
174
|
+
provider: buildResponse.repository.type,
|
|
175
|
+
};
|
|
176
|
+
if (!resourcePipelines.has(resourcePipelineToAdd)) {
|
|
177
|
+
resourcePipelines.add(resourcePipelineToAdd);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
return [...resourcePipelines];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Retrieves a set of resource repositories from a given pipeline object.
|
|
188
|
+
*
|
|
189
|
+
* @param inPipeline - The pipeline run object containing resource information.
|
|
190
|
+
* @param gitDataProviderInstance - An instance of GitDataProvider to fetch repository details.
|
|
191
|
+
* @returns A promise that resolves to an array of unique resource repositories.
|
|
192
|
+
*/
|
|
76
193
|
public async getPipelineResourceRepositoriesFromObject(
|
|
77
194
|
inPipeline: PipelineRun,
|
|
78
195
|
gitDataProviderInstance: GitDataProvider
|
|
@@ -103,12 +220,27 @@ export default class PipelinesDataProvider {
|
|
|
103
220
|
return [...resourceRepositories];
|
|
104
221
|
}
|
|
105
222
|
|
|
106
|
-
|
|
223
|
+
/**
|
|
224
|
+
* Retrieves the details of a specific pipeline build by its build ID.
|
|
225
|
+
*
|
|
226
|
+
* @param projectName - The name of the project that contains the pipeline.
|
|
227
|
+
* @param buildId - The unique identifier of the build to retrieve.
|
|
228
|
+
* @returns A promise that resolves to the content of the build details.
|
|
229
|
+
*/
|
|
230
|
+
async getPipelineBuildByBuildId(projectName: string, buildId: number) {
|
|
107
231
|
let url = `${this.orgUrl}${projectName}/_apis/build/builds/${buildId}`;
|
|
108
232
|
return TFSServices.getItemContent(url, this.token, 'get');
|
|
109
233
|
} //GetCommitForPipeline
|
|
110
234
|
|
|
111
|
-
|
|
235
|
+
/**
|
|
236
|
+
* Retrieves the details of a specific pipeline run.
|
|
237
|
+
*
|
|
238
|
+
* @param projectName - The name of the project containing the pipeline.
|
|
239
|
+
* @param pipelineId - The ID of the pipeline.
|
|
240
|
+
* @param runId - The ID of the pipeline run.
|
|
241
|
+
* @returns A promise that resolves to the content of the pipeline run.
|
|
242
|
+
*/
|
|
243
|
+
async getPipelineRunDetails(projectName: string, pipelineId: number, runId: number): Promise<PipelineRun> {
|
|
112
244
|
let url = `${this.orgUrl}${projectName}/_apis/pipelines/${pipelineId}/runs/${runId}`;
|
|
113
245
|
return TFSServices.getItemContent(url, this.token);
|
|
114
246
|
}
|
|
@@ -126,6 +258,19 @@ export default class PipelinesDataProvider {
|
|
|
126
258
|
return res;
|
|
127
259
|
}
|
|
128
260
|
|
|
261
|
+
/**
|
|
262
|
+
* Retrieves an artifact by build ID from a specified project.
|
|
263
|
+
*
|
|
264
|
+
* @param {string} projectName - The name of the project.
|
|
265
|
+
* @param {string} buildId - The ID of the build.
|
|
266
|
+
* @param {string} artifactName - The name of the artifact to retrieve.
|
|
267
|
+
* @returns {Promise<any>} A promise that resolves to the artifact data.
|
|
268
|
+
* @throws Will throw an error if the retrieval process fails.
|
|
269
|
+
*
|
|
270
|
+
* @example
|
|
271
|
+
* const artifact = await GetArtifactByBuildId('MyProject', '12345', 'MyArtifact');
|
|
272
|
+
* console.log(artifact);
|
|
273
|
+
*/
|
|
129
274
|
async GetArtifactByBuildId(projectName: string, buildId: string, artifactName: string): Promise<any> {
|
|
130
275
|
try {
|
|
131
276
|
logger.info(
|
|
@@ -149,6 +294,13 @@ export default class PipelinesDataProvider {
|
|
|
149
294
|
}
|
|
150
295
|
}
|
|
151
296
|
|
|
297
|
+
/**
|
|
298
|
+
* Retrieves a release by its release ID for a given project.
|
|
299
|
+
*
|
|
300
|
+
* @param projectName - The name of the project.
|
|
301
|
+
* @param releaseId - The ID of the release to retrieve.
|
|
302
|
+
* @returns A promise that resolves to the release data.
|
|
303
|
+
*/
|
|
152
304
|
async GetReleaseByReleaseId(projectName: string, releaseId: number): Promise<any> {
|
|
153
305
|
let url = `${this.orgUrl}${projectName}/_apis/release/releases/${releaseId}`;
|
|
154
306
|
if (url.startsWith('https://dev.azure.com')) {
|
|
@@ -157,18 +309,30 @@ export default class PipelinesDataProvider {
|
|
|
157
309
|
return TFSServices.getItemContent(url, this.token, 'get', null, null);
|
|
158
310
|
}
|
|
159
311
|
|
|
312
|
+
/**
|
|
313
|
+
* Retrieves the run history of a specified pipeline within a project.
|
|
314
|
+
*
|
|
315
|
+
* @param projectName - The name of the project containing the pipeline.
|
|
316
|
+
* @param pipelineId - The ID of the pipeline to retrieve the run history for.
|
|
317
|
+
* @returns An object containing the count of successful runs and an array of successful run details.
|
|
318
|
+
* @throws Will log an error message if the pipeline run history could not be fetched.
|
|
319
|
+
*/
|
|
160
320
|
async GetPipelineRunHistory(projectName: string, pipelineId: string) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
321
|
+
try {
|
|
322
|
+
let url: string = `${this.orgUrl}${projectName}/_apis/pipelines/${pipelineId}/runs`;
|
|
323
|
+
let res: any = await TFSServices.getItemContent(url, this.token, 'get', null, null);
|
|
324
|
+
//Filter successful builds only
|
|
325
|
+
let { value } = res;
|
|
326
|
+
if (value) {
|
|
327
|
+
const successfulRunHistory = value.filter(
|
|
328
|
+
(run: any) => run.result !== 'failed' || run.result !== 'canceled'
|
|
329
|
+
);
|
|
330
|
+
return { count: successfulRunHistory.length, value: successfulRunHistory };
|
|
331
|
+
}
|
|
332
|
+
return res;
|
|
333
|
+
} catch (err: any) {
|
|
334
|
+
logger.error(`Could not fetch Pipeline Run History: ${err.message}`);
|
|
170
335
|
}
|
|
171
|
-
return res;
|
|
172
336
|
}
|
|
173
337
|
|
|
174
338
|
async GetReleaseHistory(projectName: string, definitionId: string) {
|
|
@@ -217,4 +381,28 @@ export default class PipelinesDataProvider {
|
|
|
217
381
|
}
|
|
218
382
|
return artifactInfo;
|
|
219
383
|
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Get stage name
|
|
387
|
+
* @param pipelineRunId requested pipeline run id
|
|
388
|
+
* @param teamProject requested team project
|
|
389
|
+
* @param stageName stage name to search for in the pipeline
|
|
390
|
+
* @returns
|
|
391
|
+
*/
|
|
392
|
+
private async getPipelineStageName(pipelineRunId: number, teamProject: string, stageName: string) {
|
|
393
|
+
let url = `${this.orgUrl}${teamProject}/_apis/build/builds/${pipelineRunId}/timeline?api-version=6.0`;
|
|
394
|
+
try {
|
|
395
|
+
const getPipelineLogsResponse = await TFSServices.getItemContent(url, this.token, 'get');
|
|
396
|
+
|
|
397
|
+
const { records } = getPipelineLogsResponse;
|
|
398
|
+
for (const record of records) {
|
|
399
|
+
if (record.type === 'Stage' && record.name === stageName) {
|
|
400
|
+
return record;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
} catch (err: any) {
|
|
404
|
+
logger.error(`Error fetching pipeline ${pipelineRunId} with url ${url} : ${err.message}`);
|
|
405
|
+
return undefined;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
220
408
|
}
|
|
@@ -97,7 +97,13 @@ export default class TicketsDataProvider {
|
|
|
97
97
|
}
|
|
98
98
|
return linkList;
|
|
99
99
|
}
|
|
100
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Getting shared queries
|
|
102
|
+
* @param project project name
|
|
103
|
+
* @param path query path
|
|
104
|
+
* @param docType document type
|
|
105
|
+
* @returns
|
|
106
|
+
*/
|
|
101
107
|
async GetSharedQueries(project: string, path: string, docType: string = ''): Promise<any> {
|
|
102
108
|
let url;
|
|
103
109
|
try {
|
|
@@ -108,7 +114,9 @@ export default class TicketsDataProvider {
|
|
|
108
114
|
logger.debug(`doctype: ${docType}`);
|
|
109
115
|
switch (docType) {
|
|
110
116
|
case 'STD':
|
|
111
|
-
return await this.
|
|
117
|
+
return await this.fetchLinkedQueries(queries, false);
|
|
118
|
+
case 'STR':
|
|
119
|
+
return await this.fetchLinkedQueries(queries, true);
|
|
112
120
|
case 'SVD':
|
|
113
121
|
return await this.fetchAnyQueries(queries);
|
|
114
122
|
default:
|
|
@@ -120,8 +128,17 @@ export default class TicketsDataProvider {
|
|
|
120
128
|
}
|
|
121
129
|
}
|
|
122
130
|
|
|
123
|
-
|
|
124
|
-
|
|
131
|
+
/**
|
|
132
|
+
* fetches linked queries
|
|
133
|
+
* @param queries fetched queries
|
|
134
|
+
* @param onlyTestReq get only test req
|
|
135
|
+
* @returns ReqTestTree and TestReqTree
|
|
136
|
+
*/
|
|
137
|
+
private async fetchLinkedQueries(queries: any, onlyTestReq: boolean = false) {
|
|
138
|
+
const { tree1: reqTestTree, tree2: testReqTree } = await this.structureReqTestQueries(
|
|
139
|
+
queries,
|
|
140
|
+
onlyTestReq
|
|
141
|
+
);
|
|
125
142
|
return { reqTestTree, testReqTree };
|
|
126
143
|
}
|
|
127
144
|
private async fetchAnyQueries(queries: any) {
|
|
@@ -131,7 +148,11 @@ export default class TicketsDataProvider {
|
|
|
131
148
|
return { systemOverviewQueryTree, knownBugsQueryTree };
|
|
132
149
|
}
|
|
133
150
|
|
|
134
|
-
async GetQueryResultsFromWiql(
|
|
151
|
+
async GetQueryResultsFromWiql(
|
|
152
|
+
wiqlHref: string = '',
|
|
153
|
+
displayAsTable: boolean = false,
|
|
154
|
+
testCaseToRequirementMap: Map<number, Set<any>>
|
|
155
|
+
): Promise<any> {
|
|
135
156
|
try {
|
|
136
157
|
if (!wiqlHref) {
|
|
137
158
|
throw new Error('Incorrect WIQL Link');
|
|
@@ -145,7 +166,7 @@ export default class TicketsDataProvider {
|
|
|
145
166
|
switch (queryResult.queryType) {
|
|
146
167
|
case QueryType.OneHop:
|
|
147
168
|
return displayAsTable
|
|
148
|
-
? await this.parseDirectLinkedQueryResultForTableFormat(queryResult)
|
|
169
|
+
? await this.parseDirectLinkedQueryResultForTableFormat(queryResult, testCaseToRequirementMap)
|
|
149
170
|
: await this.parseTreeQueryResult(queryResult);
|
|
150
171
|
case QueryType.Tree:
|
|
151
172
|
return await this.parseTreeQueryResult(queryResult);
|
|
@@ -161,7 +182,10 @@ export default class TicketsDataProvider {
|
|
|
161
182
|
}
|
|
162
183
|
}
|
|
163
184
|
|
|
164
|
-
private async parseDirectLinkedQueryResultForTableFormat(
|
|
185
|
+
private async parseDirectLinkedQueryResultForTableFormat(
|
|
186
|
+
queryResult: QueryTree,
|
|
187
|
+
testCaseToRequirementMap: Map<number, Set<any>>
|
|
188
|
+
) {
|
|
165
189
|
const { columns, workItemRelations } = queryResult;
|
|
166
190
|
|
|
167
191
|
if (workItemRelations?.length === 0) {
|
|
@@ -205,8 +229,8 @@ export default class TicketsDataProvider {
|
|
|
205
229
|
}
|
|
206
230
|
|
|
207
231
|
// Get relation source from lookup
|
|
208
|
-
const
|
|
209
|
-
if (!
|
|
232
|
+
const sourceWorkItem = lookupMap.get(relation.source.id);
|
|
233
|
+
if (!sourceWorkItem) {
|
|
210
234
|
throw new Error('Source relation has no mapping');
|
|
211
235
|
}
|
|
212
236
|
|
|
@@ -216,9 +240,14 @@ export default class TicketsDataProvider {
|
|
|
216
240
|
columnTargetsMap,
|
|
217
241
|
true
|
|
218
242
|
);
|
|
219
|
-
|
|
243
|
+
//In case if source is a test case
|
|
244
|
+
this.mapTestCaseToRequirement(sourceWorkItem, testCaseToRequirementMap, targetWi);
|
|
245
|
+
|
|
246
|
+
//In case of target is a test case
|
|
247
|
+
this.mapTestCaseToRequirement(targetWi, testCaseToRequirementMap, sourceWorkItem);
|
|
248
|
+
const targets: any = sourceTargetsMap.get(sourceWorkItem) || [];
|
|
220
249
|
targets.push(targetWi);
|
|
221
|
-
sourceTargetsMap.set(
|
|
250
|
+
sourceTargetsMap.set(sourceWorkItem, targets);
|
|
222
251
|
}
|
|
223
252
|
}
|
|
224
253
|
|
|
@@ -230,6 +259,27 @@ export default class TicketsDataProvider {
|
|
|
230
259
|
};
|
|
231
260
|
}
|
|
232
261
|
|
|
262
|
+
private mapTestCaseToRequirement(
|
|
263
|
+
testCaseItem: any,
|
|
264
|
+
testCaseToRequirementMap: Map<number, Set<any>>,
|
|
265
|
+
RequirementWi: any
|
|
266
|
+
) {
|
|
267
|
+
if (testCaseItem.fields['System.WorkItemType'] == 'Test Case') {
|
|
268
|
+
if (!testCaseToRequirementMap.has(testCaseItem.id)) {
|
|
269
|
+
testCaseToRequirementMap.set(testCaseItem.id, new Set());
|
|
270
|
+
}
|
|
271
|
+
const requirementSet = testCaseToRequirementMap.get(testCaseItem.id);
|
|
272
|
+
if (requirementSet) {
|
|
273
|
+
// Check if there's already an item with the same ID
|
|
274
|
+
const alreadyExists = [...requirementSet].some((reqItem) => reqItem.id === RequirementWi.id);
|
|
275
|
+
|
|
276
|
+
if (!alreadyExists) {
|
|
277
|
+
requirementSet.add(RequirementWi);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
233
283
|
private async parseFlatQueryResultForTableFormat(queryResult: QueryTree) {
|
|
234
284
|
const { columns, workItems } = queryResult;
|
|
235
285
|
|
|
@@ -539,6 +589,7 @@ export default class TicketsDataProvider {
|
|
|
539
589
|
return queryResult;
|
|
540
590
|
}
|
|
541
591
|
|
|
592
|
+
//Build columns
|
|
542
593
|
BuildColumns(results: any, queryResult: Query) {
|
|
543
594
|
for (var i = 0; i < results.columns.length; i++) {
|
|
544
595
|
queryResult.columns[i] = new Column();
|
|
@@ -592,12 +643,14 @@ export default class TicketsDataProvider {
|
|
|
592
643
|
}
|
|
593
644
|
}
|
|
594
645
|
|
|
646
|
+
//Get work item attachments
|
|
595
647
|
async GetWorkitemAttachmentsJSONData(project: string, attachmentId: string) {
|
|
596
648
|
let wiuRL = `${this.orgUrl}${project}/_apis/wit/attachments/${attachmentId}`;
|
|
597
649
|
let attachment = await TFSServices.getItemContent(wiuRL, this.token);
|
|
598
650
|
return attachment;
|
|
599
651
|
}
|
|
600
652
|
|
|
653
|
+
//Update work item
|
|
601
654
|
async UpdateWorkItem(projectName: string, wiBody: any, workItemId: number, byPass: boolean) {
|
|
602
655
|
let res: any;
|
|
603
656
|
let url: string = `${this.orgUrl}${projectName}/_apis/wit/workitems/${workItemId}?bypassRules=${String(
|
|
@@ -696,15 +749,22 @@ export default class TicketsDataProvider {
|
|
|
696
749
|
}
|
|
697
750
|
}
|
|
698
751
|
|
|
699
|
-
|
|
752
|
+
/**
|
|
753
|
+
* Recursively structures the query list for the requirement to test case and test case to requirement queries
|
|
754
|
+
*/
|
|
755
|
+
private async structureReqTestQueries(
|
|
756
|
+
rootQuery: any,
|
|
757
|
+
onlyTestReq: boolean,
|
|
758
|
+
parentId: any = null
|
|
759
|
+
): Promise<any> {
|
|
700
760
|
try {
|
|
701
761
|
if (!rootQuery.hasChildren) {
|
|
702
762
|
if (!rootQuery.isFolder && rootQuery.queryType === 'oneHop') {
|
|
703
763
|
const wiql = rootQuery.wiql;
|
|
704
764
|
let tree1Node = null;
|
|
705
765
|
let tree2Node = null;
|
|
706
|
-
|
|
707
|
-
if (this.matchesReqTestCondition(wiql)) {
|
|
766
|
+
// Check if the query is a requirement to test case query
|
|
767
|
+
if (!onlyTestReq && this.matchesReqTestCondition(wiql)) {
|
|
708
768
|
tree1Node = {
|
|
709
769
|
id: rootQuery.id,
|
|
710
770
|
pId: parentId,
|
|
@@ -731,18 +791,18 @@ export default class TicketsDataProvider {
|
|
|
731
791
|
return { tree1: null, tree2: null };
|
|
732
792
|
}
|
|
733
793
|
}
|
|
734
|
-
|
|
794
|
+
// If the query has children, but they are not loaded, fetch them
|
|
735
795
|
if (!rootQuery.children) {
|
|
736
796
|
const queryUrl = `${rootQuery.url}?$depth=2&$expand=all`;
|
|
737
797
|
const currentQuery = await TFSServices.getItemContent(queryUrl, this.token);
|
|
738
798
|
return currentQuery
|
|
739
|
-
? await this.structureReqTestQueries(currentQuery, currentQuery.id)
|
|
799
|
+
? await this.structureReqTestQueries(currentQuery, onlyTestReq, currentQuery.id)
|
|
740
800
|
: { tree1: null, tree2: null };
|
|
741
801
|
}
|
|
742
802
|
|
|
743
803
|
// Process children recursively
|
|
744
804
|
const childResults = await Promise.all(
|
|
745
|
-
rootQuery.children.map((child: any) => this.structureReqTestQueries(child, rootQuery.id))
|
|
805
|
+
rootQuery.children.map((child: any) => this.structureReqTestQueries(child, onlyTestReq, rootQuery.id))
|
|
746
806
|
);
|
|
747
807
|
|
|
748
808
|
// Build tree1
|
|
@@ -782,6 +842,7 @@ export default class TicketsDataProvider {
|
|
|
782
842
|
}
|
|
783
843
|
}
|
|
784
844
|
|
|
845
|
+
// check if the query is a requirement to test case query
|
|
785
846
|
private matchesReqTestCondition(wiql: string): boolean {
|
|
786
847
|
return (
|
|
787
848
|
wiql.includes("Source.[System.WorkItemType] = 'Requirement'") &&
|
|
@@ -789,6 +850,7 @@ export default class TicketsDataProvider {
|
|
|
789
850
|
);
|
|
790
851
|
}
|
|
791
852
|
|
|
853
|
+
// check if the query is a test case to requirement query
|
|
792
854
|
private matchesTestReqCondition(wiql: string): boolean {
|
|
793
855
|
return (
|
|
794
856
|
wiql.includes("Source.[System.WorkItemType] = 'Test Case'") &&
|
|
@@ -796,10 +858,12 @@ export default class TicketsDataProvider {
|
|
|
796
858
|
);
|
|
797
859
|
}
|
|
798
860
|
|
|
861
|
+
// check if the query is a bug query
|
|
799
862
|
private matchesBugCondition(wiql: string): boolean {
|
|
800
863
|
return wiql.includes(`[System.WorkItemType] = 'Bug'`);
|
|
801
864
|
}
|
|
802
865
|
|
|
866
|
+
// Filter the fields based on the columns to filter map
|
|
803
867
|
private filterFieldsByColumns(
|
|
804
868
|
item: any,
|
|
805
869
|
columnsToFilterMap: Map<string, string>,
|