@elisra-devops/docgen-data-provider 1.35.0 → 1.36.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,6 +1,6 @@
1
1
  import DataProviderUtils from '../utils/DataProviderUtils';
2
2
  import { TFSServices } from '../helpers/tfs';
3
- import { OpenPcrRequest, TestSteps } from '../models/tfs-data';
3
+ import { OpenPcrRequest, PlainTestResult, TestSteps } from '../models/tfs-data';
4
4
  import logger from '../utils/logger';
5
5
  import Utils from '../utils/testStepParserHelper';
6
6
  import TicketsDataProvider from './TicketsDataProvider';
@@ -584,6 +584,7 @@ export default class ResultDataProvider {
584
584
  testCaseUrl: `${this.orgUrl}${projectName}/_workitems/edit/${testPoint.testCaseReference.id}`,
585
585
  configurationName: testPoint.configuration?.name,
586
586
  outcome: testPoint.results?.outcome || 'Not Run',
587
+ testSuite: testPoint.testSuite,
587
588
  lastRunId: testPoint.results?.lastTestRunId,
588
589
  lastResultId: testPoint.results?.lastResultId,
589
590
  lastResultDetails: testPoint.results?.lastResultDetails,
@@ -598,6 +599,7 @@ export default class ResultDataProvider {
598
599
  testCaseId: testPoint.testCase.id,
599
600
  testCaseName: testPoint.testCase.name,
600
601
  testCaseUrl: `${this.orgUrl}${projectName}/_workitems/edit/${testPoint.testCase.id}`,
602
+ testSuite: testPoint.testSuite,
601
603
  configurationName: testPoint.configuration?.name,
602
604
  outcome: testPoint.outcome || 'Not Run',
603
605
  lastRunId: testPoint.lastTestRun?.id,
@@ -657,12 +659,76 @@ export default class ResultDataProvider {
657
659
  resultId: string,
658
660
  isTestReporter: boolean = false,
659
661
  selectedFields?: string[],
660
- isQueryMode?: boolean
662
+ isQueryMode?: boolean,
663
+ point?: any
661
664
  ): Promise<any> {
662
665
  try {
666
+ let filteredFields: any = {};
667
+ let relatedRequirements: any[] = [];
668
+ let relatedBugs: any[] = [];
669
+ let relatedCRs: any[] = [];
663
670
  if (runId === '0' || resultId === '0') {
664
- logger.warn(`Invalid runId or resultId: ${runId}, ${resultId}`);
665
- return null;
671
+ if (!point) {
672
+ logger.warn(`Invalid run result ${runId} or result ${resultId}`);
673
+ return null;
674
+ }
675
+ logger.warn(`Current Test point for Test case ${point.testCaseId} is in Active state`);
676
+ const url = `${this.orgUrl}${projectName}/_apis/wit/workItems/${point.testCaseId}?$expand=all`;
677
+ const testCaseData = await TFSServices.getItemContent(url, this.token);
678
+ const newResultData: PlainTestResult = {
679
+ id: 0,
680
+ outcome: point.outcome,
681
+ revision: testCaseData?.rev || 1,
682
+ testCase: { id: point.testCaseId, name: point.testCaseName },
683
+ state: testCaseData?.fields?.['System.State'] || 'Active',
684
+ priority: testCaseData?.fields?.['Microsoft.VSTS.TCM.Priority'] || 0,
685
+ createdDate: testCaseData?.fields?.['System.CreatedDate'] || '0001-01-01T00:00:00',
686
+ testSuite: point.testSuite,
687
+ failureType: 'None',
688
+ };
689
+ if (isQueryMode) {
690
+ this.appendQueryRelations(point.testCaseId, relatedRequirements, relatedBugs, relatedCRs);
691
+ } else {
692
+ const filteredLinkedFields = selectedFields
693
+ ?.filter((field: string) => field.includes('@linked'))
694
+ ?.map((field: string) => field.split('@')[0]);
695
+ const selectedLinkedFieldSet = new Set(filteredLinkedFields);
696
+ const { relations } = testCaseData;
697
+ if (relations) {
698
+ await this.appendLinkedRelations(
699
+ relations,
700
+ relatedRequirements,
701
+ relatedBugs,
702
+ relatedCRs,
703
+ testCaseData,
704
+ selectedLinkedFieldSet
705
+ );
706
+ }
707
+ selectedLinkedFieldSet.clear();
708
+ }
709
+ const filteredTestCaseFields = selectedFields
710
+ ?.filter((field: string) => field.includes('@testCaseWorkItemField'))
711
+ ?.map((field: string) => field.split('@')[0]);
712
+ const selectedFieldSet = new Set(filteredTestCaseFields);
713
+ // Filter fields based on selected field set
714
+ if (selectedFieldSet.size !== 0) {
715
+ filteredFields = Object.keys(testCaseData.fields)
716
+ .filter((key) => selectedFieldSet.has(key))
717
+ .reduce((obj: any, key) => {
718
+ obj[key] = testCaseData.fields[key]?.displayName ?? testCaseData.fields[key];
719
+ return obj;
720
+ }, {});
721
+ }
722
+ selectedFieldSet.clear();
723
+ return {
724
+ ...newResultData,
725
+ stepsResultXml: testCaseData.fields['Microsoft.VSTS.TCM.Steps'] || undefined,
726
+ testCaseRevision: testCaseData.rev,
727
+ filteredFields,
728
+ relatedRequirements,
729
+ relatedBugs,
730
+ relatedCRs,
731
+ };
666
732
  }
667
733
  const url = `${this.orgUrl}${projectName}/_apis/test/runs/${runId}/results/${resultId}?detailsToInclude=Iterations`;
668
734
  const resultData = await TFSServices.getItemContent(url, this.token);
@@ -674,15 +740,12 @@ export default class ResultDataProvider {
674
740
  const expandParam = isTestReporter ? '?$expand=all' : '';
675
741
  const wiUrl = `${this.orgUrl}${projectName}/_apis/wit/workItems/${resultData.testCase.id}/revisions/${resultData.testCaseRevision}${expandParam}`;
676
742
  const wiByRevision = await TFSServices.getItemContent(wiUrl, this.token);
677
- let filteredFields: any = {};
678
- let relatedRequirements: any[] = [];
679
- let relatedBugs: any[] = [];
680
- let relatedCRs: any[] = [];
743
+
681
744
  // Process selected fields if provided
682
745
  if (isTestReporter) {
683
746
  // Process related requirements if needed
684
747
  if (isQueryMode) {
685
- this.appendQueryRelations(resultData, relatedRequirements, relatedBugs, relatedCRs);
748
+ this.appendQueryRelations(resultData.testCase.id, relatedRequirements, relatedBugs, relatedCRs);
686
749
  } else {
687
750
  const filteredLinkedFields = selectedFields
688
751
  ?.filter((field: string) => field.includes('@linked'))
@@ -718,6 +781,7 @@ export default class ResultDataProvider {
718
781
  }
719
782
  return {
720
783
  ...resultData,
784
+
721
785
  stepsResultXml: wiByRevision.fields['Microsoft.VSTS.TCM.Steps'] || undefined,
722
786
  analysisAttachments,
723
787
  testCaseRevision: resultData.testCaseRevision,
@@ -740,13 +804,13 @@ export default class ResultDataProvider {
740
804
  };
741
805
 
742
806
  private appendQueryRelations(
743
- resultData: any,
807
+ testCaseId: any,
744
808
  relatedRequirements: any[],
745
809
  relatedBugs: any[],
746
810
  relatedCRs: any[]
747
811
  ) {
748
812
  if (this.testToAssociatedItemMap.size !== 0) {
749
- const relatedItemSet = this.testToAssociatedItemMap.get(Number(resultData.testCase.id));
813
+ const relatedItemSet = this.testToAssociatedItemMap.get(Number(testCaseId));
750
814
  if (relatedItemSet) {
751
815
  for (const relatedItem of relatedItemSet) {
752
816
  const { id, fields, _links } = relatedItem;
@@ -943,6 +1007,7 @@ export default class ResultDataProvider {
943
1007
  ?.filter((field: string) => field.includes('@stepsRunProperties'))
944
1008
  ?.map((field: string) => field.split('@')[0]) || []
945
1009
  );
1010
+ const iterationsMap = this.createIterationsMap(iterations, isTestReporter, includeNotRunTestCases);
946
1011
 
947
1012
  for (const testItem of testData) {
948
1013
  for (const point of testItem.testPointsItems) {
@@ -957,25 +1022,20 @@ export default class ResultDataProvider {
957
1022
  continue;
958
1023
  }
959
1024
  }
960
-
961
- if (includeNotRunTestCases && !point.lastRunId && !point.lastResultId) {
962
- this.AppendResults(options, testCase, filteredFields, testItem, point, detailedResults);
963
- } else {
964
- const iterationsMap = this.createIterationsMap(iterations, testCase.workItem.id, isTestReporter);
965
-
966
- const iterationKey = `${point.lastRunId}-${point.lastResultId}-${testCase.workItem.id}`;
967
- const fetchedTestCase =
968
- iterationsMap[iterationKey] || (includeNotRunTestCases ? testCase : undefined);
969
-
970
- // First check if fetchedTestCase exists
971
- if (!fetchedTestCase) continue;
972
-
973
- // Then separately check for iteration only if configured not to include items without iterations
974
- if (!includeItemsWithNoIterations && !fetchedTestCase.iteration) continue;
975
-
976
- // Determine if we should process at step level
977
- this.AppendResults(options, fetchedTestCase, filteredFields, testItem, point, detailedResults);
978
- }
1025
+ const iterationKey =
1026
+ !point.lastRunId || !point.lastResultId
1027
+ ? `${testCase.workItem.id}`
1028
+ : `${point.lastRunId}-${point.lastResultId}-${testCase.workItem.id}`;
1029
+ const fetchedTestCase =
1030
+ iterationsMap[iterationKey] || (includeNotRunTestCases ? testCase : undefined);
1031
+ // First check if fetchedTestCase exists
1032
+ if (!fetchedTestCase) continue;
1033
+
1034
+ // Then separately check for iteration only if configured not to include items without iterations
1035
+ if (!includeItemsWithNoIterations && !fetchedTestCase.iteration) continue;
1036
+
1037
+ // Determine if we should process at step level
1038
+ this.AppendResults(options, fetchedTestCase, filteredFields, testItem, point, detailedResults);
979
1039
  }
980
1040
  }
981
1041
  return detailedResults;
@@ -1090,15 +1150,18 @@ export default class ResultDataProvider {
1090
1150
  */
1091
1151
  private createIterationsMap(
1092
1152
  iterations: any[],
1093
- testCaseId: number,
1094
- isTestReporter: boolean
1153
+ isTestReporter: boolean,
1154
+ includeNotRunTestCases: boolean
1095
1155
  ): Record<string, any> {
1096
1156
  return iterations.reduce((map, iterationItem) => {
1097
1157
  if (
1098
1158
  (isTestReporter && iterationItem.lastRunId && iterationItem.lastResultId) ||
1099
1159
  iterationItem.iteration
1100
1160
  ) {
1101
- const key = `${iterationItem.lastRunId}-${iterationItem.lastResultId}-${testCaseId}`;
1161
+ const key = `${iterationItem.lastRunId}-${iterationItem.lastResultId}-${iterationItem.testCaseId}`;
1162
+ map[key] = iterationItem;
1163
+ } else if (includeNotRunTestCases) {
1164
+ const key = `${iterationItem.testCaseId}`;
1102
1165
  map[key] = iterationItem;
1103
1166
  }
1104
1167
  return map;
@@ -1154,6 +1217,7 @@ export default class ResultDataProvider {
1154
1217
  private async fetchAllResultDataBase(
1155
1218
  testData: any[],
1156
1219
  projectName: string,
1220
+ isTestReporter: boolean,
1157
1221
  fetchStrategy: (projectName: string, testSuiteId: string, point: any, ...args: any[]) => Promise<any>,
1158
1222
  additionalArgs: any[] = []
1159
1223
  ): Promise<any[]> {
@@ -1164,9 +1228,9 @@ export default class ResultDataProvider {
1164
1228
  const { testSuiteId, testPointsItems } = item;
1165
1229
 
1166
1230
  // Filter and sort valid points
1167
- const validPoints = testPointsItems.filter(
1168
- (point: any) => point && point.lastRunId && point.lastResultId
1169
- );
1231
+ const validPoints = isTestReporter
1232
+ ? testPointsItems
1233
+ : testPointsItems.filter((point: any) => point && point.lastRunId && point.lastResultId);
1170
1234
 
1171
1235
  // Fetch results for each point sequentially
1172
1236
  for (const point of validPoints) {
@@ -1185,7 +1249,7 @@ export default class ResultDataProvider {
1185
1249
  * Fetches result data for all test points within the given test data, sequentially to avoid context-related errors.
1186
1250
  */
1187
1251
  private async fetchAllResultData(testData: any[], projectName: string): Promise<any[]> {
1188
- return this.fetchAllResultDataBase(testData, projectName, (projectName, testSuiteId, point) =>
1252
+ return this.fetchAllResultDataBase(testData, projectName, false, (projectName, testSuiteId, point) =>
1189
1253
  this.fetchResultData(projectName, testSuiteId, point)
1190
1254
  );
1191
1255
  }
@@ -1219,69 +1283,69 @@ export default class ResultDataProvider {
1219
1283
  createResponseObject: (resultData: any, testSuiteId: string, point: any, ...args: any[]) => any,
1220
1284
  additionalArgs: any[] = []
1221
1285
  ): Promise<any> {
1222
- const { lastRunId, lastResultId } = point;
1223
- if (!lastRunId || !lastResultId) {
1224
- logger.warn(`Invalid lastRunId or lastResultId for point: ${JSON.stringify(point)}`);
1225
- return null;
1226
- } else if (lastRunId === '0' || lastResultId === '0') {
1227
- logger.warn(`Invalid lastRunId or lastResultId: ${lastRunId}, ${lastResultId}`);
1228
- return null;
1229
- }
1230
- const resultData = await fetchResultMethod(
1231
- projectName,
1232
- lastRunId.toString(),
1233
- lastResultId.toString(),
1234
- ...additionalArgs
1235
- );
1236
-
1237
- const iteration =
1238
- resultData.iterationDetails.length > 0
1239
- ? resultData.iterationDetails[resultData.iterationDetails.length - 1]
1240
- : undefined;
1286
+ try {
1287
+ const { lastRunId, lastResultId } = point;
1241
1288
 
1242
- if (resultData.stepsResultXml && iteration) {
1243
- const actionResultsWithSharedModels = iteration.actionResults.filter(
1244
- (result: any) => result.sharedStepModel
1289
+ const resultData = await fetchResultMethod(
1290
+ projectName,
1291
+ lastRunId?.toString() || '0',
1292
+ lastResultId?.toString() || '0',
1293
+ ...additionalArgs
1245
1294
  );
1246
1295
 
1247
- const sharedStepIdToRevisionLookupMap: Map<number, number> = new Map();
1296
+ const iteration =
1297
+ resultData.iterationDetails?.length > 0
1298
+ ? resultData.iterationDetails[resultData.iterationDetails.length - 1]
1299
+ : undefined;
1248
1300
 
1249
- if (actionResultsWithSharedModels?.length > 0) {
1250
- actionResultsWithSharedModels.forEach((actionResult: any) => {
1251
- const { sharedStepModel } = actionResult;
1252
- sharedStepIdToRevisionLookupMap.set(Number(sharedStepModel.id), Number(sharedStepModel.revision));
1253
- });
1254
- }
1255
- const stepsList = await this.testStepParserHelper.parseTestSteps(
1256
- resultData.stepsResultXml,
1257
- sharedStepIdToRevisionLookupMap
1258
- );
1301
+ if (resultData.stepsResultXml && iteration) {
1302
+ const actionResultsWithSharedModels = iteration.actionResults.filter(
1303
+ (result: any) => result.sharedStepModel
1304
+ );
1259
1305
 
1260
- sharedStepIdToRevisionLookupMap.clear();
1306
+ const sharedStepIdToRevisionLookupMap: Map<number, number> = new Map();
1261
1307
 
1262
- const stepMap = new Map<string, TestSteps>();
1263
- for (const step of stepsList) {
1264
- stepMap.set(step.stepId.toString(), step);
1265
- }
1308
+ if (actionResultsWithSharedModels?.length > 0) {
1309
+ actionResultsWithSharedModels.forEach((actionResult: any) => {
1310
+ const { sharedStepModel } = actionResult;
1311
+ sharedStepIdToRevisionLookupMap.set(Number(sharedStepModel.id), Number(sharedStepModel.revision));
1312
+ });
1313
+ }
1314
+ const stepsList = await this.testStepParserHelper.parseTestSteps(
1315
+ resultData.stepsResultXml,
1316
+ sharedStepIdToRevisionLookupMap
1317
+ );
1318
+
1319
+ sharedStepIdToRevisionLookupMap.clear();
1320
+
1321
+ const stepMap = new Map<string, TestSteps>();
1322
+ for (const step of stepsList) {
1323
+ stepMap.set(step.stepId.toString(), step);
1324
+ }
1266
1325
 
1267
- for (const actionResult of iteration.actionResults) {
1268
- const step = stepMap.get(actionResult.stepIdentifier);
1269
- if (step) {
1270
- actionResult.stepPosition = step.stepPosition;
1271
- actionResult.action = step.action;
1272
- actionResult.expected = step.expected;
1273
- actionResult.isSharedStepTitle = step.isSharedStepTitle;
1326
+ for (const actionResult of iteration.actionResults) {
1327
+ const step = stepMap.get(actionResult.stepIdentifier);
1328
+ if (step) {
1329
+ actionResult.stepPosition = step.stepPosition;
1330
+ actionResult.action = step.action;
1331
+ actionResult.expected = step.expected;
1332
+ actionResult.isSharedStepTitle = step.isSharedStepTitle;
1333
+ }
1274
1334
  }
1335
+ //Sort by step position
1336
+ iteration.actionResults = iteration.actionResults
1337
+ .filter((result: any) => result.stepPosition)
1338
+ .sort((a: any, b: any) => this.compareActionResults(a.stepPosition, b.stepPosition));
1275
1339
  }
1276
- //Sort by step position
1277
- iteration.actionResults = iteration.actionResults
1278
- .filter((result: any) => result.stepPosition)
1279
- .sort((a: any, b: any) => this.compareActionResults(a.stepPosition, b.stepPosition));
1280
- }
1281
1340
 
1282
- return resultData?.testCase
1283
- ? createResponseObject(resultData, testSuiteId, point, ...additionalArgs)
1284
- : null;
1341
+ return resultData?.testCase
1342
+ ? createResponseObject(resultData, testSuiteId, point, ...additionalArgs)
1343
+ : null;
1344
+ } catch (error: any) {
1345
+ logger.error(`Error occurred for point ${point.testCaseId}: ${error.message}`);
1346
+ logger.error(`Stack trace: ${error.stack}`);
1347
+ return null;
1348
+ }
1285
1349
  }
1286
1350
 
1287
1351
  /**
@@ -1301,7 +1365,7 @@ export default class ResultDataProvider {
1301
1365
  lastRunId: point.lastRunId,
1302
1366
  lastResultId: point.lastResultId,
1303
1367
  iteration:
1304
- resultData.iterationDetails.length > 0
1368
+ resultData.iterationDetails?.length > 0
1305
1369
  ? resultData.iterationDetails[resultData.iterationDetails.length - 1]
1306
1370
  : undefined,
1307
1371
  testCaseRevision: resultData.testCaseRevision,
@@ -1698,9 +1762,18 @@ export default class ResultDataProvider {
1698
1762
  runId: string,
1699
1763
  resultId: string,
1700
1764
  selectedFields?: string[],
1701
- isQueryMode?: boolean
1765
+ isQueryMode?: boolean,
1766
+ point?: any
1702
1767
  ): Promise<any> {
1703
- return this.fetchResultDataBasedOnWiBase(projectName, runId, resultId, true, selectedFields, isQueryMode);
1768
+ return this.fetchResultDataBasedOnWiBase(
1769
+ projectName,
1770
+ runId,
1771
+ resultId,
1772
+ true,
1773
+ selectedFields,
1774
+ isQueryMode,
1775
+ point
1776
+ );
1704
1777
  }
1705
1778
 
1706
1779
  /**
@@ -1724,6 +1797,7 @@ export default class ResultDataProvider {
1724
1797
  return this.fetchAllResultDataBase(
1725
1798
  testData,
1726
1799
  projectName,
1800
+ true,
1727
1801
  (projectName, testSuiteId, point, selectedFields, isQueryMode) =>
1728
1802
  this.fetchResultDataForTestReporter(projectName, testSuiteId, point, selectedFields, isQueryMode),
1729
1803
  [selectedFields, isQueryMode]
@@ -1833,16 +1907,15 @@ export default class ResultDataProvider {
1833
1907
  projectName,
1834
1908
  testSuiteId,
1835
1909
  point,
1836
- (project, runId, resultId, fields, isQueryMode) =>
1837
- this.fetchResultDataBasedOnWiTestReporter(project, runId, resultId, fields, isQueryMode),
1910
+ (project, runId, resultId, fields, isQueryMode, point) =>
1911
+ this.fetchResultDataBasedOnWiTestReporter(project, runId, resultId, fields, isQueryMode, point),
1838
1912
  (resultData, testSuiteId, point, selectedFields) => {
1839
1913
  const { lastRunId, lastResultId, configurationName, lastResultDetails } = point;
1840
1914
  try {
1841
1915
  const iteration =
1842
- resultData.iterationDetails.length > 0
1843
- ? resultData.iterationDetails[resultData.iterationDetails.length - 1]
1916
+ resultData.iterationDetails?.length > 0
1917
+ ? resultData.iterationDetails[resultData.iterationDetails?.length - 1]
1844
1918
  : undefined;
1845
-
1846
1919
  const resultDataResponse: any = {
1847
1920
  testCaseName: `${resultData.testCase.name} - ${resultData.testCase.id}`,
1848
1921
  testCaseId: resultData.testCase.id,
@@ -1883,10 +1956,7 @@ export default class ResultDataProvider {
1883
1956
  resultDataResponse.priority = resultData.priority;
1884
1957
  break;
1885
1958
  case 'testCaseResult':
1886
- const outcome =
1887
- resultData.iterationDetails[resultData.iterationDetails.length - 1]?.outcome ||
1888
- resultData.outcome ||
1889
- 'NotApplicable';
1959
+ const outcome = this.getTestOutcome(resultData);
1890
1960
  if (lastRunId === undefined || lastResultId === undefined) {
1891
1961
  resultDataResponse.testCaseResult = {
1892
1962
  resultMessage: `${this.convertRunStatus(outcome)}`,
@@ -1927,10 +1997,27 @@ export default class ResultDataProvider {
1927
1997
  return null;
1928
1998
  }
1929
1999
  },
1930
- [selectedFields, isQueryMode]
2000
+ [selectedFields, isQueryMode, point]
1931
2001
  );
1932
2002
  }
1933
2003
 
2004
+ private getTestOutcome(resultData: any) {
2005
+ // Default outcome if nothing else is available
2006
+ const defaultOutcome = 'NotApplicable';
2007
+
2008
+ // Check if we have iteration details
2009
+ const hasIterationDetails = resultData?.iterationDetails && resultData.iterationDetails.length > 0;
2010
+
2011
+ if (hasIterationDetails) {
2012
+ // Get the last iteration's outcome if available
2013
+ const lastIteration = resultData.iterationDetails[resultData.iterationDetails.length - 1];
2014
+ return lastIteration?.outcome || resultData.outcome || defaultOutcome;
2015
+ }
2016
+
2017
+ // No iteration details, use result outcome or default
2018
+ return resultData.outcome || defaultOutcome;
2019
+ }
2020
+
1934
2021
  private standardCustomField(fieldsToProcess: any, selectedColumns?: any[]): any {
1935
2022
  const customFields: any = {};
1936
2023
  if (selectedColumns) {