@elisra-devops/docgen-data-provider 1.29.2 → 1.30.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.
@@ -3,6 +3,7 @@ import { TFSServices } from '../helpers/tfs';
3
3
  import { OpenPcrRequest, TestSteps } from '../models/tfs-data';
4
4
  import logger from '../utils/logger';
5
5
  import Utils from '../utils/testStepParserHelper';
6
+ import TicketsDataProvider from './TicketsDataProvider';
6
7
  const pLimit = require('p-limit');
7
8
  /**
8
9
  * Provides methods to fetch, process, and summarize test data from Azure DevOps.
@@ -31,10 +32,12 @@ export default class ResultDataProvider {
31
32
  token: string = '';
32
33
  private limit = pLimit(10);
33
34
  private testStepParserHelper: Utils;
35
+ private testToAssociatedItemMap: Map<number, Set<any>>;
34
36
  constructor(orgUrl: string, token: string) {
35
37
  this.orgUrl = orgUrl;
36
38
  this.token = token;
37
39
  this.testStepParserHelper = new Utils(orgUrl, token);
40
+ this.testToAssociatedItemMap = new Map<number, Set<any>>();
38
41
  }
39
42
 
40
43
  /**
@@ -222,7 +225,9 @@ export default class ResultDataProvider {
222
225
  selectedFields: string[],
223
226
  allowCrossTestPlan: boolean,
224
227
  enableRunTestCaseFilter: boolean,
225
- enableRunStepStatusFilter: boolean
228
+ enableRunStepStatusFilter: boolean,
229
+ linkedQueryRequest: any,
230
+ errorFilterMode: string = 'none'
226
231
  ) {
227
232
  const fetchedTestResults: any[] = [];
228
233
  logger.debug(
@@ -230,6 +235,7 @@ export default class ResultDataProvider {
230
235
  );
231
236
  logger.debug(`Selected suite IDs: ${selectedSuiteIds}`);
232
237
  try {
238
+ const ticketsDataProvider = new TicketsDataProvider(this.orgUrl, this.token);
233
239
  logger.debug(`Fetching Plan info for test plan ID: ${testPlanId}, project name: ${projectName}`);
234
240
  const plan = await this.fetchTestPlanName(testPlanId, projectName);
235
241
  logger.debug(`Fetching Test suites for test plan ID: ${testPlanId}, project name: ${projectName}`);
@@ -237,7 +243,22 @@ export default class ResultDataProvider {
237
243
  logger.debug(`Fetching test data for test plan ID: ${testPlanId}, project name: ${projectName}`);
238
244
  const testData = await this.fetchTestData(suites, projectName, testPlanId, allowCrossTestPlan);
239
245
  logger.debug(`Fetching Run results for test data, project name: ${projectName}`);
240
- const runResults = await this.fetchAllResultDataTestReporter(testData, projectName, selectedFields);
246
+ const isQueryMode = linkedQueryRequest.linkedQueryMode === 'query';
247
+ if (isQueryMode) {
248
+ // Fetch associated items
249
+ await ticketsDataProvider.GetQueryResultsFromWiql(
250
+ linkedQueryRequest.testAssociatedQuery.wiql.href,
251
+ true,
252
+ this.testToAssociatedItemMap
253
+ );
254
+ }
255
+
256
+ const runResults = await this.fetchAllResultDataTestReporter(
257
+ testData,
258
+ projectName,
259
+ selectedFields,
260
+ isQueryMode
261
+ );
241
262
  logger.debug(`Aligning steps with iterations for test reporter results`);
242
263
  const testReporterData = this.alignStepsWithIterationsTestReporter(
243
264
  testData,
@@ -247,6 +268,31 @@ export default class ResultDataProvider {
247
268
  );
248
269
  // Apply filters sequentially based on enabled flags
249
270
  let filteredResults = testReporterData;
271
+ //TODO: think of a way to filter out the fields that are not selected from this part of the code and not from the fetchAllResultDataTestReporter function
272
+ if (errorFilterMode !== 'none') {
273
+ const onlyFailedTestCaseCondition = (result: any) =>
274
+ result && result.testCase?.result?.resultMessage?.includes('Failed');
275
+ const onlyFailedTestStepsCondition = (result: any) => result && result.stepStatus === 'Failed';
276
+
277
+ switch (errorFilterMode) {
278
+ case 'onlyTestCaseResult':
279
+ logger.debug(`Filtering test reporter results for only test case result`);
280
+ filteredResults = filteredResults.filter(onlyFailedTestCaseCondition);
281
+ break;
282
+ case 'onlyTestStepsResult':
283
+ logger.debug(`Filtering test reporter results for only test steps result`);
284
+ filteredResults = filteredResults.filter(onlyFailedTestStepsCondition);
285
+ break;
286
+ case 'both':
287
+ logger.debug(`Filtering test reporter results for both test case and test steps result`);
288
+ filteredResults = filteredResults
289
+ ?.filter(onlyFailedTestCaseCondition)
290
+ ?.filter(onlyFailedTestStepsCondition);
291
+ break;
292
+ default:
293
+ break;
294
+ }
295
+ }
250
296
 
251
297
  // filter: Test step run status
252
298
  if (enableRunStepStatusFilter) {
@@ -575,12 +621,27 @@ export default class ResultDataProvider {
575
621
  return testCases;
576
622
  }
577
623
 
624
+ /**
625
+ * Fetches result data based on the Work Item Test Reporter.
626
+ *
627
+ * This method retrieves detailed result data for a specific test run and result ID,
628
+ * including related work items, selected fields, and additional processing options.
629
+ *
630
+ * @param projectName - The name of the project containing the test run.
631
+ * @param runId - The unique identifier of the test run.
632
+ * @param resultId - The unique identifier of the test result.
633
+ * @param isTestReporter - (Optional) A flag indicating whether the result data is being fetched for the Test Reporter.
634
+ * @param selectedFields - (Optional) An array of field names to include in the result data.
635
+ * @param isQueryMode - (Optional) A flag indicating whether the result data is being fetched in query mode.
636
+ * @returns A promise that resolves to the fetched result data.
637
+ */
578
638
  private async fetchResultDataBasedOnWiBase(
579
639
  projectName: string,
580
640
  runId: string,
581
641
  resultId: string,
582
642
  isTestReporter: boolean = false,
583
- selectedFields?: string[]
643
+ selectedFields?: string[],
644
+ isQueryMode?: boolean
584
645
  ): Promise<any> {
585
646
  try {
586
647
  if (runId === '0' || resultId === '0') {
@@ -602,54 +663,34 @@ export default class ResultDataProvider {
602
663
  let relatedBugs: any[] = [];
603
664
  let relatedCRs: any[] = [];
604
665
  // Process selected fields if provided
605
- if (selectedFields?.length && isTestReporter) {
606
- const filtered = selectedFields
607
- ?.filter((field: string) => field.includes('@testCaseWorkItemField'))
608
- ?.map((field: string) => field.split('@')[0]);
609
- const selectedFieldSet = new Set(filtered);
610
-
611
- if (selectedFieldSet.size !== 0) {
612
- // Process related requirements if needed
666
+ if (isTestReporter) {
667
+ // Process related requirements if needed
668
+ if (isQueryMode) {
669
+ this.appendQueryRelations(resultData, relatedRequirements, relatedBugs, relatedCRs);
670
+ } else {
671
+ const filteredLinkedFields = selectedFields
672
+ ?.filter((field: string) => field.includes('@linked'))
673
+ ?.map((field: string) => field.split('@')[0]);
674
+ const selectedLinkedFieldSet = new Set(filteredLinkedFields);
613
675
  const { relations } = wiByRevision;
614
676
  if (relations) {
615
- for (const relation of relations) {
616
- if (
617
- relation.rel?.includes('System.LinkTypes') ||
618
- relation.rel?.includes('Microsoft.VSTS.Common.TestedBy')
619
- ) {
620
- const relatedUrl = relation.url;
621
- try {
622
- const wi = await TFSServices.getItemContent(relatedUrl, this.token);
623
- if (wi.fields['System.WorkItemType'] === 'Requirement') {
624
- const { id, fields, _links } = wi;
625
- const requirementTitle = fields['System.Title'];
626
- const customerFieldKey = Object.keys(fields).find((key) =>
627
- key.toLowerCase().includes('customer')
628
- );
629
- const customerId = customerFieldKey ? fields[customerFieldKey] : undefined;
630
- const url = _links.html.href;
631
- relatedRequirements.push({ id, requirementTitle, customerId, url });
632
- } else if (wi.fields['System.WorkItemType'] === 'Bug') {
633
- const { id, fields, _links } = wi;
634
- const bugTitle = fields['System.Title'];
635
- const url = _links.html.href;
636
- relatedBugs.push({ id, bugTitle, url });
637
- } else if (wi.fields['System.WorkItemType'] === 'Change Request') {
638
- const { id, fields, _links } = wi;
639
- const crTitle = fields['System.Title'];
640
- const url = _links.html.href;
641
- relatedCRs.push({ id, crTitle, url });
642
- }
643
- } catch (err: any) {
644
- logger.error(
645
- `Could not append related work item to test case ${wiByRevision.id}: ${err.message}`
646
- );
647
- }
648
- }
649
- }
677
+ await this.appendLinkedRelations(
678
+ relations,
679
+ relatedRequirements,
680
+ relatedBugs,
681
+ relatedCRs,
682
+ wiByRevision,
683
+ selectedLinkedFieldSet
684
+ );
650
685
  }
651
-
652
- // Filter fields based on selected field set
686
+ selectedLinkedFieldSet.clear();
687
+ }
688
+ const filteredTestCaseFields = selectedFields
689
+ ?.filter((field: string) => field.includes('@testCaseWorkItemField'))
690
+ ?.map((field: string) => field.split('@')[0]);
691
+ const selectedFieldSet = new Set(filteredTestCaseFields);
692
+ // Filter fields based on selected field set
693
+ if (selectedFieldSet.size !== 0) {
653
694
  filteredFields = Object.keys(wiByRevision.fields)
654
695
  .filter((key) => selectedFieldSet.has(key))
655
696
  .reduce((obj: any, key) => {
@@ -657,6 +698,7 @@ export default class ResultDataProvider {
657
698
  return obj;
658
699
  }, {});
659
700
  }
701
+ selectedFieldSet.clear();
660
702
  }
661
703
  return {
662
704
  ...resultData,
@@ -681,6 +723,95 @@ export default class ResultDataProvider {
681
723
  return result && result.stepStatus === 'Not Run';
682
724
  };
683
725
 
726
+ private appendQueryRelations(
727
+ resultData: any,
728
+ relatedRequirements: any[],
729
+ relatedBugs: any[],
730
+ relatedCRs: any[]
731
+ ) {
732
+ if (this.testToAssociatedItemMap.size !== 0) {
733
+ const relatedItemSet = this.testToAssociatedItemMap.get(Number(resultData.testCase.id));
734
+ if (relatedItemSet) {
735
+ for (const relatedItem of relatedItemSet) {
736
+ const { id, fields, _links } = relatedItem;
737
+ let url = '';
738
+ switch (fields['System.WorkItemType']) {
739
+ case 'Requirement':
740
+ const requirementTitle = fields['System.Title'];
741
+ url = _links.html.href;
742
+ relatedRequirements.push({ id, requirementTitle, url });
743
+ break;
744
+ case 'Bug':
745
+ const bugTitle = fields['System.Title'];
746
+ url = _links.html.href;
747
+ relatedBugs.push({ id, bugTitle, url });
748
+ break;
749
+ case 'Change Request':
750
+ const crTitle = fields['System.Title'];
751
+ url = _links.html.href;
752
+ relatedCRs.push({ id, crTitle, url });
753
+ break;
754
+ }
755
+ }
756
+ }
757
+ }
758
+ }
759
+
760
+ private async appendLinkedRelations(
761
+ relations: any,
762
+ relatedRequirements: any[],
763
+ relatedBugs: any[],
764
+ relatedCRs: any[],
765
+ wiByRevision: any,
766
+ selectedLinkedFieldSet: Set<string>
767
+ ) {
768
+ for (const relation of relations) {
769
+ if (
770
+ relation.rel?.includes('System.LinkTypes') ||
771
+ relation.rel?.includes('Microsoft.VSTS.Common.TestedBy')
772
+ ) {
773
+ const relatedUrl = relation.url;
774
+ try {
775
+ const wi = await TFSServices.getItemContent(relatedUrl, this.token);
776
+ if (wi.fields['System.State'] === 'Closed') {
777
+ continue;
778
+ }
779
+ if (
780
+ selectedLinkedFieldSet.has('associatedRequirement') &&
781
+ wi.fields['System.WorkItemType'] === 'Requirement'
782
+ ) {
783
+ const { id, fields, _links } = wi;
784
+ const requirementTitle = fields['System.Title'];
785
+ const customerFieldKey = Object.keys(fields).find((key) =>
786
+ key.toLowerCase().includes('customer')
787
+ );
788
+ const customerId = customerFieldKey ? fields[customerFieldKey] : undefined;
789
+ const url = _links.html.href;
790
+ relatedRequirements.push({ id, requirementTitle, customerId, url });
791
+ } else if (
792
+ selectedLinkedFieldSet.has('associatedBug') &&
793
+ wi.fields['System.WorkItemType'] === 'Bug'
794
+ ) {
795
+ const { id, fields, _links } = wi;
796
+ const bugTitle = fields['System.Title'];
797
+ const url = _links.html.href;
798
+ relatedBugs.push({ id, bugTitle, url });
799
+ } else if (
800
+ selectedLinkedFieldSet.has('associatedCR') &&
801
+ wi.fields['System.WorkItemType'] === 'Change Request'
802
+ ) {
803
+ const { id, fields, _links } = wi;
804
+ const crTitle = fields['System.Title'];
805
+ const url = _links.html.href;
806
+ relatedCRs.push({ id, crTitle, url });
807
+ }
808
+ } catch (err: any) {
809
+ logger.error(`Could not append related work item to test case ${wiByRevision.id}: ${err.message}`);
810
+ }
811
+ }
812
+ }
813
+ }
814
+
684
815
  /**
685
816
  * Fetches result data based on the specified work item (WI) details.
686
817
  *
@@ -1536,9 +1667,10 @@ export default class ResultDataProvider {
1536
1667
  projectName: string,
1537
1668
  runId: string,
1538
1669
  resultId: string,
1539
- selectedFields?: string[]
1670
+ selectedFields?: string[],
1671
+ isQueryMode?: boolean
1540
1672
  ): Promise<any> {
1541
- return this.fetchResultDataBasedOnWiBase(projectName, runId, resultId, true, selectedFields);
1673
+ return this.fetchResultDataBasedOnWiBase(projectName, runId, resultId, true, selectedFields, isQueryMode);
1542
1674
  }
1543
1675
 
1544
1676
  /**
@@ -1556,14 +1688,15 @@ export default class ResultDataProvider {
1556
1688
  private async fetchAllResultDataTestReporter(
1557
1689
  testData: any[],
1558
1690
  projectName: string,
1559
- selectedFields?: string[]
1691
+ selectedFields?: string[],
1692
+ isQueryMode?: boolean
1560
1693
  ): Promise<any[]> {
1561
1694
  return this.fetchAllResultDataBase(
1562
1695
  testData,
1563
1696
  projectName,
1564
- (projectName, testSuiteId, point, selectedFields) =>
1565
- this.fetchResultDataForTestReporter(projectName, testSuiteId, point, selectedFields),
1566
- [selectedFields]
1697
+ (projectName, testSuiteId, point, selectedFields, isQueryMode) =>
1698
+ this.fetchResultDataForTestReporter(projectName, testSuiteId, point, selectedFields, isQueryMode),
1699
+ [selectedFields, isQueryMode]
1567
1700
  );
1568
1701
  }
1569
1702
 
@@ -1573,7 +1706,7 @@ export default class ResultDataProvider {
1573
1706
  *
1574
1707
  * @param testData - An array of test data objects to be processed.
1575
1708
  * @param iterations - An array of iteration objects to align with the test data.
1576
- * @param selectedFields - An array of selected fields to determine which properties to include in the result.
1709
+ * @param selectedFields - An array of selected fields to determine which properties to include in the response.
1577
1710
  * @returns An array of structured result objects containing aligned test steps and iterations.
1578
1711
  *
1579
1712
  * The method uses a base alignment function and provides custom logic for:
@@ -1604,35 +1737,38 @@ export default class ResultDataProvider {
1604
1737
  actionResult,
1605
1738
  filteredFields = new Set(),
1606
1739
  }) => {
1607
- const baseObj = {
1740
+ const baseObj: any = {
1608
1741
  suiteName: testItem.testGroupName,
1609
1742
  testCase: {
1610
1743
  id: point.testCaseId,
1611
1744
  title: point.testCaseName,
1612
1745
  url: point.testCaseUrl,
1613
1746
  result: fetchedTestCase.testCaseResult,
1614
- comment: fetchedTestCase.iteration?.comment,
1747
+ comment: fetchedTestCase.comment,
1615
1748
  },
1616
1749
  priority: fetchedTestCase.priority,
1617
1750
  runBy: fetchedTestCase.runBy,
1618
1751
  activatedBy: fetchedTestCase.activatedBy,
1619
- assignedTo: fetchedTestCase.assignedTo,
1620
- subSystem: fetchedTestCase.subSystem,
1621
1752
  failureType: fetchedTestCase.failureType,
1622
- automationStatus: fetchedTestCase.automationStatus,
1623
1753
  executionDate: fetchedTestCase.executionDate,
1624
1754
  configurationName: fetchedTestCase.configurationName,
1625
1755
  errorMessage: fetchedTestCase.errorMessage,
1626
1756
  relatedRequirements: fetchedTestCase.relatedRequirements,
1627
1757
  relatedBugs: fetchedTestCase.relatedBugs,
1628
1758
  relatedCRs: fetchedTestCase.relatedCRs,
1759
+ ...fetchedTestCase.customFields,
1629
1760
  };
1630
1761
 
1631
1762
  // If we have action results, add step-specific properties
1632
1763
  if (actionResult) {
1633
1764
  return {
1634
1765
  ...baseObj,
1635
- stepNo: actionResult.stepPosition,
1766
+ stepNo:
1767
+ filteredFields.has('includeSteps') ||
1768
+ filteredFields.has('stepRunStatus') ||
1769
+ filteredFields.has('testStepComment')
1770
+ ? actionResult.stepPosition
1771
+ : undefined,
1636
1772
  stepAction: filteredFields.has('includeSteps') ? actionResult.action : undefined,
1637
1773
  stepExpected: filteredFields.has('includeSteps') ? actionResult.expected : undefined,
1638
1774
  stepStatus: filteredFields.has('stepRunStatus')
@@ -1663,14 +1799,15 @@ export default class ResultDataProvider {
1663
1799
  projectName: string,
1664
1800
  testSuiteId: string,
1665
1801
  point: any,
1666
- selectedFields?: string[]
1802
+ selectedFields?: string[],
1803
+ isQueryMode?: boolean
1667
1804
  ) {
1668
1805
  return this.fetchResultDataBase(
1669
1806
  projectName,
1670
1807
  testSuiteId,
1671
1808
  point,
1672
- (project, runId, resultId, fields) =>
1673
- this.fetchResultDataBasedOnWiTestReporter(project, runId, resultId, fields),
1809
+ (project, runId, resultId, fields, isQueryMode) =>
1810
+ this.fetchResultDataBasedOnWiTestReporter(project, runId, resultId, fields, isQueryMode),
1674
1811
  (resultData, testSuiteId, point, selectedFields) => {
1675
1812
  const { lastRunId, lastResultId, configurationName, lastResultDetails } = point;
1676
1813
  try {
@@ -1678,7 +1815,7 @@ export default class ResultDataProvider {
1678
1815
  resultData.iterationDetails.length > 0
1679
1816
  ? resultData.iterationDetails[resultData.iterationDetails.length - 1]
1680
1817
  : undefined;
1681
- const resultDataResponse = {
1818
+ const resultDataResponse: any = {
1682
1819
  testCaseName: `${resultData.testCase.name} - ${resultData.testCase.id}`,
1683
1820
  testCaseId: resultData.testCase.id,
1684
1821
  testSuiteName: `${resultData.testSuite.name}`,
@@ -1688,26 +1825,41 @@ export default class ResultDataProvider {
1688
1825
  iteration,
1689
1826
  testCaseRevision: resultData.testCaseRevision,
1690
1827
  resolution: resultData.resolutionState,
1691
- automationStatus: resultData.filteredFields['Microsoft.VSTS.TCM.AutomationStatus'] || undefined,
1692
- analysisAttachments: resultData.analysisAttachments,
1693
1828
  failureType: undefined as string | undefined,
1694
- comment: undefined as string | undefined,
1695
1829
  priority: undefined,
1696
- assignedTo: resultData.filteredFields['System.AssignedTo'],
1697
- subSystem: resultData.filteredFields['Custom.SubSystem'],
1698
1830
  runBy: undefined as string | undefined,
1699
1831
  executionDate: undefined as string | undefined,
1700
1832
  testCaseResult: undefined as any | undefined,
1833
+ comment: undefined as string | undefined,
1701
1834
  errorMessage: undefined as string | undefined,
1702
1835
  configurationName: undefined as string | undefined,
1703
- relatedRequirements: undefined,
1704
- relatedBugs: undefined,
1705
- relatedCRs: undefined,
1836
+ relatedRequirements: resultData.relatedRequirements || undefined,
1837
+ relatedBugs: resultData.relatedBugs || undefined,
1838
+ relatedCRs: resultData.relatedCRs || undefined,
1706
1839
  lastRunResult: undefined as any,
1840
+ customFields: {}, // Create an object to store custom fields
1707
1841
  };
1708
1842
 
1843
+ // Process all custom fields from resultData.filteredFields
1844
+ if (resultData.filteredFields) {
1845
+ for (const [fieldName, fieldValue] of Object.entries(resultData.filteredFields)) {
1846
+ if (fieldValue !== undefined && fieldValue !== null) {
1847
+ // Convert Microsoft.VSTS.TCM.AutomationStatus to automationStatus
1848
+ // or System.AssignedTo to assignedTo
1849
+ const nameParts = fieldName.split('.');
1850
+ let propertyName = nameParts[nameParts.length - 1];
1851
+
1852
+ // Convert to camelCase (first letter lowercase)
1853
+ propertyName = propertyName.charAt(0).toLowerCase() + propertyName.slice(1);
1854
+
1855
+ // Add to customFields object
1856
+ resultDataResponse.customFields[propertyName] = fieldValue;
1857
+ }
1858
+ }
1859
+ }
1860
+
1709
1861
  const filteredFields = selectedFields
1710
- ?.filter((field: string) => field.includes('@runResultField') || field.includes('@linked'))
1862
+ ?.filter((field: string) => field.includes('@runResultField'))
1711
1863
  ?.map((field: string) => field.split('@')[0]);
1712
1864
 
1713
1865
  if (filteredFields && filteredFields.length > 0) {
@@ -1732,34 +1884,22 @@ export default class ResultDataProvider {
1732
1884
  url: `${this.orgUrl}${projectName}/_testManagement/runs?runId=${lastRunId}&_a=resultSummary&resultId=${lastResultId}`,
1733
1885
  };
1734
1886
  }
1735
-
1887
+ case 'testCaseComment':
1888
+ resultDataResponse.comment = iteration?.comment;
1736
1889
  break;
1737
1890
  case 'failureType':
1738
1891
  resultDataResponse.failureType = resultData.failureType;
1739
1892
  break;
1740
- case 'testCaseComment':
1741
- resultDataResponse.comment = resultData.comment || undefined;
1742
- break;
1743
1893
  case 'runBy':
1744
1894
  const runBy = lastResultDetails.runBy.displayName;
1745
1895
  resultDataResponse.runBy = runBy;
1746
1896
  break;
1747
-
1748
1897
  case 'executionDate':
1749
1898
  resultDataResponse.executionDate = lastResultDetails.dateCompleted;
1750
1899
  break;
1751
1900
  case 'configurationName':
1752
1901
  resultDataResponse.configurationName = configurationName;
1753
1902
  break;
1754
- case 'associatedRequirement':
1755
- resultDataResponse.relatedRequirements = resultData.relatedRequirements;
1756
- break;
1757
- case 'associatedBug':
1758
- resultDataResponse.relatedBugs = resultData.relatedBugs;
1759
- break;
1760
- case 'associatedCR':
1761
- resultDataResponse.relatedCRs = resultData.relatedCRs;
1762
- break;
1763
1903
  default:
1764
1904
  logger.debug(`Field ${field} not handled`);
1765
1905
  break;
@@ -1773,7 +1913,7 @@ export default class ResultDataProvider {
1773
1913
  return null;
1774
1914
  }
1775
1915
  },
1776
- [selectedFields]
1916
+ [selectedFields, isQueryMode]
1777
1917
  );
1778
1918
  }
1779
1919
  }
@@ -118,14 +118,17 @@ export default class TicketsDataProvider {
118
118
  else url = `${this.orgUrl}${project}/_apis/wit/queries/${path}?$depth=2&$expand=all`;
119
119
  let queries: any = await TFSServices.getItemContent(url, this.token);
120
120
  logger.debug(`doctype: ${docType}`);
121
- switch (docType) {
122
- case 'STD':
121
+ switch (docType?.toLowerCase()) {
122
+ case 'std':
123
123
  return await this.fetchLinkedReqTestQueries(queries, false);
124
- case 'STR':
124
+ case 'str':
125
125
  const reqTestTrees = await this.fetchLinkedReqTestQueries(queries, false);
126
126
  const openPcrTestTrees = await this.fetchLinkedOpenPcrTestQueries(queries, false);
127
127
  return { reqTestTrees, openPcrTestTrees };
128
- case 'SVD':
128
+ case 'test-reporter':
129
+ const testAssociatedTree = await this.fetchTestReporterQueries(queries);
130
+ return { testAssociatedTree };
131
+ case 'svd':
129
132
  return await this.fetchAnyQueries(queries);
130
133
  default:
131
134
  break;
@@ -136,6 +139,42 @@ export default class TicketsDataProvider {
136
139
  }
137
140
  }
138
141
 
142
+ /**
143
+ * Fetches the fields of a specific work item type.
144
+ *
145
+ * @param project - The project name.
146
+ * @param itemType - The work item type.
147
+ * @returns An array of objects containing the field name and reference name.
148
+ */
149
+
150
+ async GetFieldsByType(project: string, itemType: string) {
151
+ try {
152
+ let url = `${this.orgUrl}${project}/_apis/wit/workitemtypes/${itemType}/fields`;
153
+ const { value: fields } = await TFSServices.getItemContent(url, this.token);
154
+ return (
155
+ fields
156
+ // filter out the fields that are not relevant for the user
157
+ .filter(
158
+ (field: any) =>
159
+ field.name !== 'ID' &&
160
+ field.name !== 'Title' &&
161
+ field.name !== 'Description' &&
162
+ field.name !== 'Work Item Type' &&
163
+ field.name !== 'Steps'
164
+ )
165
+ .map((field: any) => {
166
+ return {
167
+ text: `${field.name} (${itemType})`,
168
+ key: field.referenceName,
169
+ };
170
+ })
171
+ );
172
+ } catch (err: any) {
173
+ logger.error(`Error occurred during fetching fields by type: ${err.message}`);
174
+ throw err;
175
+ }
176
+ }
177
+
139
178
  /**
140
179
  * fetches linked queries
141
180
  * @param queries fetched queries
@@ -177,6 +216,22 @@ export default class TicketsDataProvider {
177
216
  return { OpenPcrToTestTree, TestToOpenPcrTree };
178
217
  }
179
218
 
219
+ /**
220
+ * fetches test reporter queries
221
+ * @param queries fetched queries
222
+ * @returns
223
+ */
224
+ private async fetchTestReporterQueries(queries: any) {
225
+ const { tree1: tree1, tree2: testAssociatedTree } = await this.structureFetchedQueries(
226
+ queries,
227
+ true,
228
+ null,
229
+ ['Requirement', 'Bug', 'Change Request'],
230
+ ['Test Case']
231
+ );
232
+ return { testAssociatedTree };
233
+ }
234
+
180
235
  private async fetchAnyQueries(queries: any) {
181
236
  const { tree1: systemOverviewQueryTree, tree2: knownBugsQueryTree } = await this.structureAllQueryPath(
182
237
  queries