@elisra-devops/docgen-data-provider 1.26.6 → 1.28.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.
@@ -1,8 +1,8 @@
1
- import { log } from 'winston';
1
+ import DataProviderUtils from '../utils/DataProviderUtils';
2
2
  import { TFSServices } from '../helpers/tfs';
3
- import { TestSteps } from '../models/tfs-data';
3
+ import { OpenPcrRequest, TestSteps } from '../models/tfs-data';
4
4
  import logger from '../utils/logger';
5
- import TestStepParserHelper from '../utils/testStepParserHelper';
5
+ import Utils from '../utils/testStepParserHelper';
6
6
  const pLimit = require('p-limit');
7
7
  /**
8
8
  * Provides methods to fetch, process, and summarize test data from Azure DevOps.
@@ -30,11 +30,11 @@ export default class ResultDataProvider {
30
30
  orgUrl: string = '';
31
31
  token: string = '';
32
32
  private limit = pLimit(10);
33
- private testStepParserHelper: TestStepParserHelper;
33
+ private testStepParserHelper: Utils;
34
34
  constructor(orgUrl: string, token: string) {
35
35
  this.orgUrl = orgUrl;
36
36
  this.token = token;
37
- this.testStepParserHelper = new TestStepParserHelper(orgUrl, token);
37
+ this.testStepParserHelper = new Utils(orgUrl, token);
38
38
  }
39
39
 
40
40
  /**
@@ -46,14 +46,20 @@ export default class ResultDataProvider {
46
46
  selectedSuiteIds?: number[],
47
47
  addConfiguration: boolean = false,
48
48
  isHierarchyGroupName: boolean = false,
49
- includeOpenPCRs: boolean = false,
49
+ openPcrRequest: OpenPcrRequest | null = null,
50
50
  includeTestLog: boolean = false,
51
51
  stepExecution?: any,
52
52
  stepAnalysis?: any,
53
53
  includeHardCopyRun: boolean = false
54
- ): Promise<any[]> {
54
+ ): Promise<{
55
+ combinedResults: any[];
56
+ openPcrToTestCaseTraceMap: Map<string, string[]>;
57
+ testCaseToOpenPcrTraceMap: Map<string, string[]>;
58
+ }> {
55
59
  const combinedResults: any[] = [];
56
60
  try {
61
+ const openPcrToTestCaseTraceMap = new Map<string, string[]>();
62
+ const testCaseToOpenPcrTraceMap = new Map<string, string[]>();
57
63
  // Fetch test suites
58
64
  const suites = await this.fetchTestSuites(
59
65
  testPlanId,
@@ -131,9 +137,14 @@ export default class ResultDataProvider {
131
137
  skin: 'detailed-test-result-table',
132
138
  });
133
139
 
134
- if (includeOpenPCRs) {
140
+ if (openPcrRequest?.openPcrMode === 'linked') {
135
141
  //5. Open PCRs data (only if enabled)
136
- await this.fetchOpenPcrData(testResultsSummary, projectName, combinedResults);
142
+ await this.fetchOpenPcrData(
143
+ testResultsSummary,
144
+ projectName,
145
+ openPcrToTestCaseTraceMap,
146
+ testCaseToOpenPcrTraceMap
147
+ );
137
148
  }
138
149
 
139
150
  //6. Test Log (only if enabled)
@@ -181,7 +192,7 @@ export default class ResultDataProvider {
181
192
  });
182
193
  }
183
194
 
184
- return combinedResults;
195
+ return { combinedResults, openPcrToTestCaseTraceMap, testCaseToOpenPcrTraceMap };
185
196
  } catch (error: any) {
186
197
  logger.error(`Error during getCombinedResultsSummary: ${error.message}`);
187
198
  if (error.response) {
@@ -456,39 +467,12 @@ export default class ResultDataProvider {
456
467
  return testCases;
457
468
  }
458
469
 
459
- /**
460
- * Fetches result data based on a work item base (WiBase) for a specific test run and result.
461
- *
462
- * @param projectName - The name of the project in Azure DevOps.
463
- * @param runId - The ID of the test run.
464
- * @param resultId - The ID of the test result.
465
- * @param options - Optional parameters for customizing the data retrieval.
466
- * @param options.expandWorkItem - If true, expands all fields of the work item.
467
- * @param options.selectedFields - An array of field names to filter the work item fields.
468
- * @param options.processRelatedRequirements - If true, processes related requirements linked to the work item.
469
- * @param options.includeFullErrorStack - If true, includes the full error stack in the logs when an error occurs.
470
- * @returns A promise that resolves to an object containing the fetched result data, including:
471
- * - `stepsResultXml`: The test steps result in XML format.
472
- * - `analysisAttachments`: Attachments related to the test result analysis.
473
- * - `testCaseRevision`: The revision number of the test case.
474
- * - `filteredFields`: The filtered fields from the work item based on the selected fields.
475
- * - `relatedRequirements`: An array of related requirements with details such as ID, title, customer ID, and URL.
476
- * - `relatedBugs`: An array of related bugs with details such as ID, title, and URL.
477
- * If an error occurs, logs the error and returns `null`.
478
- *
479
- * @throws Logs an error message if the data retrieval fails.
480
- */
481
470
  private async fetchResultDataBasedOnWiBase(
482
471
  projectName: string,
483
472
  runId: string,
484
473
  resultId: string,
485
- options: {
486
- expandWorkItem?: boolean;
487
- selectedFields?: string[];
488
- processRelatedRequirements?: boolean;
489
- processRelatedBugs?: boolean;
490
- includeFullErrorStack?: boolean;
491
- } = {}
474
+ isTestReporter: boolean = false,
475
+ selectedFields?: string[]
492
476
  ): Promise<any> {
493
477
  try {
494
478
  const url = `${this.orgUrl}${projectName}/_apis/test/runs/${runId}/results/${resultId}?detailsToInclude=Iterations`;
@@ -498,21 +482,16 @@ export default class ResultDataProvider {
498
482
  const { value: analysisAttachments } = await TFSServices.getItemContent(attachmentsUrl, this.token);
499
483
 
500
484
  // Build workItem URL with optional expand parameter
501
- const expandParam = options.expandWorkItem ? '?$expand=all' : '';
485
+ const expandParam = isTestReporter ? '?$expand=all' : '';
502
486
  const wiUrl = `${this.orgUrl}${projectName}/_apis/wit/workItems/${resultData.testCase.id}/revisions/${resultData.testCaseRevision}${expandParam}`;
503
487
  const wiByRevision = await TFSServices.getItemContent(wiUrl, this.token);
504
488
  let filteredFields: any = {};
505
489
  let relatedRequirements: any[] = [];
506
490
  let relatedBugs: any[] = [];
507
- //TODO: Add CR support as well, and also add the logic to fetch the CR details
508
- // TODO: Add logic for grabbing the relations from cross projects
509
-
491
+ let relatedCRs: any[] = [];
510
492
  // Process selected fields if provided
511
- if (
512
- options.selectedFields?.length &&
513
- (options.processRelatedRequirements || options.processRelatedBugs)
514
- ) {
515
- const filtered = options.selectedFields
493
+ if (selectedFields?.length && isTestReporter) {
494
+ const filtered = selectedFields
516
495
  ?.filter((field: string) => field.includes('@testCaseWorkItemField'))
517
496
  ?.map((field: string) => field.split('@')[0]);
518
497
  const selectedFieldSet = new Set(filtered);
@@ -523,7 +502,7 @@ export default class ResultDataProvider {
523
502
  if (relations) {
524
503
  for (const relation of relations) {
525
504
  if (
526
- relation.rel?.includes('System.LinkTypes.Hierarchy') ||
505
+ relation.rel?.includes('System.LinkTypes') ||
527
506
  relation.rel?.includes('Microsoft.VSTS.Common.TestedBy')
528
507
  ) {
529
508
  const relatedUrl = relation.url;
@@ -542,6 +521,11 @@ export default class ResultDataProvider {
542
521
  const bugTitle = fields['System.Title'];
543
522
  const url = _links.html.href;
544
523
  relatedBugs.push({ id, bugTitle, url });
524
+ } else if (wi.fields['System.WorkItemType'] === 'Change Request') {
525
+ const { id, fields, _links } = wi;
526
+ const crTitle = fields['System.Title'];
527
+ const url = _links.html.href;
528
+ relatedCRs.push({ id, crTitle, url });
545
529
  }
546
530
  }
547
531
  }
@@ -564,10 +548,11 @@ export default class ResultDataProvider {
564
548
  filteredFields,
565
549
  relatedRequirements,
566
550
  relatedBugs,
551
+ relatedCRs,
567
552
  };
568
553
  } catch (error: any) {
569
554
  logger.error(`Error while fetching run result: ${error.message}`);
570
- if (options.includeFullErrorStack) {
555
+ if (isTestReporter) {
571
556
  logger.error(`Error stack: ${error.stack}`);
572
557
  }
573
558
  return null;
@@ -1122,21 +1107,32 @@ export default class ResultDataProvider {
1122
1107
  * Fetching Open PCRs data
1123
1108
  */
1124
1109
 
1125
- private async fetchOpenPcrData(testItems: any[], projectName: string, combinedResults: any[]) {
1110
+ private async fetchOpenPcrData(
1111
+ testItems: any[],
1112
+ projectName: string,
1113
+ openPcrToTestCaseTraceMap: Map<string, string[]>,
1114
+ testCaseToOpenPcrTraceMap: Map<string, string[]>
1115
+ ) {
1126
1116
  const linkedWorkItems = await this.fetchLinkedWi(projectName, testItems);
1127
- const flatOpenPcrsItems = linkedWorkItems
1128
- .filter((item) => item.linkItems.length > 0)
1129
- .flatMap((item) => {
1130
- const { linkItems, ...restItem } = item;
1131
- return linkItems.map((linkedItem: any) => ({ ...restItem, ...linkedItem }));
1132
- });
1133
- if (flatOpenPcrsItems?.length > 0) {
1134
- // Add openPCR to combined results
1135
- combinedResults.push({
1136
- contentControl: 'open-pcr-content-control',
1137
- data: flatOpenPcrsItems,
1138
- skin: 'open-pcr-table',
1117
+ for (const wi of linkedWorkItems) {
1118
+ const { linkItems, ...restItem } = wi;
1119
+ const stringifiedTestCase = JSON.stringify({
1120
+ id: restItem.testId,
1121
+ title: restItem.testName,
1122
+ testCaseUrl: restItem.testCaseUrl,
1123
+ runStatus: restItem.runStatus,
1139
1124
  });
1125
+ for (const linkedItem of linkItems) {
1126
+ const stringifiedPcr = JSON.stringify({
1127
+ pcrId: linkedItem.pcrId,
1128
+ workItemType: linkedItem.workItemType,
1129
+ title: linkedItem.title,
1130
+ severity: linkedItem.severity,
1131
+ pcrUrl: linkedItem.pcrUrl,
1132
+ });
1133
+ DataProviderUtils.addToTraceMap(openPcrToTestCaseTraceMap, stringifiedPcr, stringifiedTestCase);
1134
+ DataProviderUtils.addToTraceMap(testCaseToOpenPcrTraceMap, stringifiedTestCase, stringifiedPcr);
1135
+ }
1140
1136
  }
1141
1137
  }
1142
1138
 
@@ -1346,6 +1342,7 @@ export default class ResultDataProvider {
1346
1342
  testGroupName: testPoint.testGroupName,
1347
1343
  testId: testPoint.testCaseId,
1348
1344
  testName: testPoint.testCaseName,
1345
+ testCaseUrl: testPoint.testCaseUrl,
1349
1346
  runStatus: !includeHardCopyRun ? this.convertRunStatus(testPoint.outcome) : '',
1350
1347
  };
1351
1348
 
@@ -1374,13 +1371,7 @@ export default class ResultDataProvider {
1374
1371
  resultId: string,
1375
1372
  selectedFields?: string[]
1376
1373
  ): Promise<any> {
1377
- return this.fetchResultDataBasedOnWiBase(projectName, runId, resultId, {
1378
- expandWorkItem: true,
1379
- selectedFields,
1380
- processRelatedRequirements: true,
1381
- processRelatedBugs: true,
1382
- includeFullErrorStack: true,
1383
- });
1374
+ return this.fetchResultDataBasedOnWiBase(projectName, runId, resultId, true, selectedFields);
1384
1375
  }
1385
1376
 
1386
1377
  /**
@@ -1459,6 +1450,7 @@ export default class ResultDataProvider {
1459
1450
  runBy: fetchedTestCase.runBy,
1460
1451
  activatedBy: fetchedTestCase.activatedBy,
1461
1452
  assignedTo: fetchedTestCase.assignedTo,
1453
+ subSystem: fetchedTestCase.subSystem,
1462
1454
  failureType: fetchedTestCase.failureType,
1463
1455
  automationStatus: fetchedTestCase.automationStatus,
1464
1456
  executionDate: fetchedTestCase.executionDate,
@@ -1466,6 +1458,7 @@ export default class ResultDataProvider {
1466
1458
  errorMessage: fetchedTestCase.errorMessage,
1467
1459
  relatedRequirements: fetchedTestCase.relatedRequirements,
1468
1460
  relatedBugs: fetchedTestCase.relatedBugs,
1461
+ relatedCRs: fetchedTestCase.relatedCRs,
1469
1462
  };
1470
1463
 
1471
1464
  // If we have action results, add step-specific properties
@@ -1518,7 +1511,6 @@ export default class ResultDataProvider {
1518
1511
  resultData.iterationDetails.length > 0
1519
1512
  ? resultData.iterationDetails[resultData.iterationDetails.length - 1]
1520
1513
  : undefined;
1521
-
1522
1514
  const resultDataResponse = {
1523
1515
  testCaseName: `${resultData.testCase.name} - ${resultData.testCase.id}`,
1524
1516
  testCaseId: resultData.testCase.id,
@@ -1534,6 +1526,8 @@ export default class ResultDataProvider {
1534
1526
  failureType: undefined as string | undefined,
1535
1527
  comment: undefined as string | undefined,
1536
1528
  priority: undefined,
1529
+ assignedTo: resultData.filteredFields['System.AssignedTo'],
1530
+ subSystem: resultData.filteredFields['Custom.SubSystem'],
1537
1531
  runBy: undefined as string | undefined,
1538
1532
  executionDate: undefined as string | undefined,
1539
1533
  testCaseResult: undefined as any | undefined,
@@ -1541,6 +1535,7 @@ export default class ResultDataProvider {
1541
1535
  configurationName: undefined as string | undefined,
1542
1536
  relatedRequirements: undefined,
1543
1537
  relatedBugs: undefined,
1538
+ relatedCRs: undefined,
1544
1539
  lastRunResult: undefined as any,
1545
1540
  };
1546
1541
 
@@ -1582,6 +1577,7 @@ export default class ResultDataProvider {
1582
1577
  const runBy = lastResultDetails.runBy.displayName;
1583
1578
  resultDataResponse.runBy = runBy;
1584
1579
  break;
1580
+
1585
1581
  case 'executionDate':
1586
1582
  resultDataResponse.executionDate = lastResultDetails.dateCompleted;
1587
1583
  break;
@@ -1594,6 +1590,9 @@ export default class ResultDataProvider {
1594
1590
  case 'associatedBug':
1595
1591
  resultDataResponse.relatedBugs = resultData.relatedBugs;
1596
1592
  break;
1593
+ case 'associatedCR':
1594
+ resultDataResponse.relatedCRs = resultData.relatedCRs;
1595
+ break;
1597
1596
  default:
1598
1597
  logger.debug(`Field ${field} not handled`);
1599
1598
  break;
@@ -4,20 +4,21 @@ import { TestSteps, createMomRelation, createRequirementRelation } from '../mode
4
4
  import { TestCase } from '../models/tfs-data';
5
5
  import * as xml2js from 'xml2js';
6
6
  import logger from '../utils/logger';
7
- import TestStepParserHelper from '../utils/testStepParserHelper';
7
+ import Utils from '../utils/testStepParserHelper';
8
+ import DataProviderUtils from '../utils/DataProviderUtils';
8
9
  const pLimit = require('p-limit');
9
10
 
10
11
  export default class TestDataProvider {
11
12
  orgUrl: string = '';
12
13
  token: string = '';
13
- private testStepParserHelper: TestStepParserHelper;
14
+ private testStepParserHelper: Utils;
14
15
  private cache = new Map<string, any>(); // Cache for API responses
15
16
  private limit = pLimit(10);
16
17
 
17
18
  constructor(orgUrl: string, token: string) {
18
19
  this.orgUrl = orgUrl;
19
20
  this.token = token;
20
- this.testStepParserHelper = new TestStepParserHelper(orgUrl, token);
21
+ this.testStepParserHelper = new Utils(orgUrl, token);
21
22
  }
22
23
 
23
24
  private async fetchWithCache(url: string, ttlMs = 60000): Promise<any> {
@@ -226,10 +227,14 @@ export default class TestDataProvider {
226
227
  const stringifiedRequirement = JSON.stringify(newRequirementRelation);
227
228
 
228
229
  // Add the test case to the requirement-to-test-case trace map
229
- this.addToMap(requirementToTestCaseTraceMap, stringifiedRequirement, stringifiedTestCase);
230
+ DataProviderUtils.addToTraceMap(
231
+ requirementToTestCaseTraceMap,
232
+ stringifiedRequirement,
233
+ stringifiedTestCase
234
+ );
230
235
 
231
236
  // Add the requirement to the test-case-to-requirements trace map
232
- this.addToMap(
237
+ DataProviderUtils.addToTraceMap(
233
238
  testCaseToRequirementsTraceMap,
234
239
  stringifiedTestCase,
235
240
  stringifiedRequirement
@@ -120,9 +120,11 @@ export default class TicketsDataProvider {
120
120
  logger.debug(`doctype: ${docType}`);
121
121
  switch (docType) {
122
122
  case 'STD':
123
- return await this.fetchLinkedQueries(queries, false);
123
+ return await this.fetchLinkedReqTestQueries(queries, false);
124
124
  case 'STR':
125
- return await this.fetchLinkedQueries(queries, true);
125
+ const reqTestTrees = await this.fetchLinkedReqTestQueries(queries, false);
126
+ const openPcrTestTrees = await this.fetchLinkedOpenPcrTestQueries(queries, false);
127
+ return { reqTestTrees, openPcrTestTrees };
126
128
  case 'SVD':
127
129
  return await this.fetchAnyQueries(queries);
128
130
  default:
@@ -140,13 +142,41 @@ export default class TicketsDataProvider {
140
142
  * @param onlyTestReq get only test req
141
143
  * @returns ReqTestTree and TestReqTree
142
144
  */
143
- private async fetchLinkedQueries(queries: any, onlyTestReq: boolean = false) {
144
- const { tree1: reqTestTree, tree2: testReqTree } = await this.structureReqTestQueries(
145
+ private async fetchLinkedReqTestQueries(queries: any, onlyTestReq: boolean = false) {
146
+ const { tree1: reqTestTree, tree2: testReqTree } = await this.structureFetchedQueries(
145
147
  queries,
146
- onlyTestReq
148
+ onlyTestReq,
149
+ null,
150
+ ['Requirement'],
151
+ ['Test Case']
147
152
  );
148
153
  return { reqTestTree, testReqTree };
149
154
  }
155
+
156
+ /**
157
+ * Fetches and structures linked queries related to open PCR (Problem Change Request) tests.
158
+ *
159
+ * This method retrieves and organizes the relationships between "Test Case" and
160
+ * other entities such as "Bug" and "Change Request" into two tree structures.
161
+ *
162
+ * @param queries - The input queries to be processed and structured.
163
+ * @param onlySourceSide - A flag indicating whether to process only the source side of the queries.
164
+ * Defaults to `false`.
165
+ * @returns An object containing two tree structures:
166
+ * - `OpenPcrToTestTree`: The tree representing the relationship from Open PCR to Test Case.
167
+ * - `TestToOpenPcrTree`: The tree representing the relationship from Test Case to Open PCR.
168
+ */
169
+ private async fetchLinkedOpenPcrTestQueries(queries: any, onlySourceSide: boolean = false) {
170
+ const { tree1: OpenPcrToTestTree, tree2: TestToOpenPcrTree } = await this.structureFetchedQueries(
171
+ queries,
172
+ onlySourceSide,
173
+ null,
174
+ ['Bug', 'Change Request'],
175
+ ['Test Case']
176
+ );
177
+ return { OpenPcrToTestTree, TestToOpenPcrTree };
178
+ }
179
+
150
180
  private async fetchAnyQueries(queries: any) {
151
181
  const { tree1: systemOverviewQueryTree, tree2: knownBugsQueryTree } = await this.structureAllQueryPath(
152
182
  queries
@@ -756,12 +786,25 @@ export default class TicketsDataProvider {
756
786
  }
757
787
 
758
788
  /**
759
- * Recursively structures the query list for the requirement to test case and test case to requirement queries
789
+ * Recursively structures fetched queries into two hierarchical trees (tree1 and tree2)
790
+ * based on specific conditions. It processes both leaf and non-leaf nodes, fetching
791
+ * children if necessary, and builds the trees by matching source and target conditions.
792
+ *
793
+ * @param rootQuery - The root query object to process. It may contain children or be a leaf node.
794
+ * @param onlyTestReq - A boolean flag indicating whether to exclude requirement-to-test-case queries.
795
+ * @param parentId - The ID of the parent node, used to maintain the hierarchy. Defaults to `null`.
796
+ * @param sources - An array of source strings used to match queries.
797
+ * @param targets - An array of target strings used to match queries.
798
+ * @returns A promise that resolves to an object containing two trees (`tree1` and `tree2`),
799
+ * or `null` for each tree if no valid nodes are found.
800
+ * @throws Logs an error if an exception occurs during processing.
760
801
  */
761
- private async structureReqTestQueries(
802
+ private async structureFetchedQueries(
762
803
  rootQuery: any,
763
804
  onlyTestReq: boolean,
764
- parentId: any = null
805
+ parentId: any = null,
806
+ sources: string[],
807
+ targets: string[]
765
808
  ): Promise<any> {
766
809
  try {
767
810
  if (!rootQuery.hasChildren) {
@@ -770,7 +813,7 @@ export default class TicketsDataProvider {
770
813
  let tree1Node = null;
771
814
  let tree2Node = null;
772
815
  // Check if the query is a requirement to test case query
773
- if (!onlyTestReq && this.matchesReqTestCondition(wiql)) {
816
+ if (!onlyTestReq && this.matchesSourceTargetCondition(wiql, sources, targets)) {
774
817
  tree1Node = {
775
818
  id: rootQuery.id,
776
819
  pId: parentId,
@@ -781,7 +824,7 @@ export default class TicketsDataProvider {
781
824
  isValidQuery: true,
782
825
  };
783
826
  }
784
- if (this.matchesTestReqCondition(wiql)) {
827
+ if (this.matchesSourceTargetCondition(wiql, targets, sources)) {
785
828
  tree2Node = {
786
829
  id: rootQuery.id,
787
830
  pId: parentId,
@@ -802,13 +845,15 @@ export default class TicketsDataProvider {
802
845
  const queryUrl = `${rootQuery.url}?$depth=2&$expand=all`;
803
846
  const currentQuery = await TFSServices.getItemContent(queryUrl, this.token);
804
847
  return currentQuery
805
- ? await this.structureReqTestQueries(currentQuery, onlyTestReq, currentQuery.id)
848
+ ? await this.structureFetchedQueries(currentQuery, onlyTestReq, currentQuery.id, sources, targets)
806
849
  : { tree1: null, tree2: null };
807
850
  }
808
851
 
809
852
  // Process children recursively
810
853
  const childResults = await Promise.all(
811
- rootQuery.children.map((child: any) => this.structureReqTestQueries(child, onlyTestReq, rootQuery.id))
854
+ rootQuery.children.map((child: any) =>
855
+ this.structureFetchedQueries(child, onlyTestReq, rootQuery.id, sources, targets)
856
+ )
812
857
  );
813
858
 
814
859
  // Build tree1
@@ -848,20 +893,36 @@ export default class TicketsDataProvider {
848
893
  }
849
894
  }
850
895
 
851
- // check if the query is a requirement to test case query
852
- private matchesReqTestCondition(wiql: string): boolean {
853
- return (
854
- wiql.includes("Source.[System.WorkItemType] = 'Requirement'") &&
855
- wiql.includes("Target.[System.WorkItemType] = 'Test Case'")
856
- );
857
- }
896
+ /**
897
+ * Determines whether the given WIQL (Work Item Query Language) string matches the specified
898
+ * source and target conditions. It checks if the WIQL contains references to the specified
899
+ * source and target work item types.
900
+ *
901
+ * @param wiql - The WIQL string to evaluate.
902
+ * @param source - An array of source work item types to check for in the WIQL.
903
+ * @param target - An array of target work item types to check for in the WIQL.
904
+ * @returns A boolean indicating whether the WIQL includes at least one source work item type
905
+ * and at least one target work item type.
906
+ */
907
+ private matchesSourceTargetCondition(wiql: string, source: string[], target: string[]): boolean {
908
+ let isSourceIncluded = false;
909
+ let isTargetIncluded = false;
910
+
911
+ for (const src of source) {
912
+ if (wiql.includes(`Source.[System.WorkItemType] = '${src}'`)) {
913
+ isSourceIncluded = true;
914
+ break;
915
+ }
916
+ }
858
917
 
859
- // check if the query is a test case to requirement query
860
- private matchesTestReqCondition(wiql: string): boolean {
861
- return (
862
- wiql.includes("Source.[System.WorkItemType] = 'Test Case'") &&
863
- wiql.includes("Target.[System.WorkItemType] = 'Requirement'")
864
- );
918
+ for (const tgt of target) {
919
+ if (wiql.includes(`Target.[System.WorkItemType] = '${tgt}'`)) {
920
+ isTargetIncluded = true;
921
+ break;
922
+ }
923
+ }
924
+
925
+ return isSourceIncluded && isTargetIncluded;
865
926
  }
866
927
 
867
928
  // check if the query is a bug query