@elisra-devops/docgen-data-provider 1.94.0 → 1.95.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.
@@ -194,6 +194,15 @@ export default class ResultDataProvider {
194
194
  * Maps raw test point data to a simplified object.
195
195
  */
196
196
  private mapTestPointForCrossPlans;
197
+ /**
198
+ * Resolves a point timestamp that can be used as WIT `asOf` anchor.
199
+ * Returns an ISO string when a valid date exists, otherwise an empty string.
200
+ */
201
+ private resolvePointAsOfTimestamp;
202
+ /**
203
+ * Builds a normalized test point shape used by downstream result fetch flows.
204
+ */
205
+ private buildMappedTestPoint;
197
206
  getTestPointsForTestCases(projectName: string, testCaseId: string[]): Promise<any>;
198
207
  /**
199
208
  * Fetches test cases by suite ID.
@@ -201,11 +210,24 @@ export default class ResultDataProvider {
201
210
  private fetchTestCasesBySuiteId;
202
211
  private attachSuiteTestCaseContextToPoints;
203
212
  private extractWorkItemFieldsMap;
213
+ /**
214
+ * Reads a field value by reference name with case-insensitive key matching.
215
+ */
204
216
  private getFieldValueByName;
205
217
  private resolveSuiteTestCaseRevision;
206
218
  private buildWorkItemSnapshotFromSuiteTestCase;
207
219
  private fetchWorkItemByRevision;
220
+ private fetchWorkItemByAsOf;
208
221
  private fetchWorkItemLatest;
222
+ /**
223
+ * Executes a work-item GET by URL and applies consistent warning/error handling.
224
+ */
225
+ private fetchWorkItemByUrl;
226
+ /**
227
+ * Resolves runless test case data using ordered fallbacks:
228
+ * 1) point-based `asOf` snapshot, 2) explicit revision, 3) suite payload snapshot, 4) latest WI.
229
+ */
230
+ private resolveRunlessTestCaseData;
209
231
  /**
210
232
  * Fetches result data based on the Work Item Test Reporter.
211
233
  *
@@ -274,7 +274,7 @@ class ResultDataProvider {
274
274
  const planName = await this.fetchTestPlanName(testPlanId, projectName);
275
275
  const suites = await this.fetchTestSuites(testPlanId, projectName, selectedSuiteIds, true);
276
276
  const testData = await this.fetchTestData(suites, projectName, testPlanId, false);
277
- const runResults = await this.fetchAllResultDataTestReporter(testData, projectName, selectedFields, false, includeAllHistory);
277
+ const runResults = await this.fetchAllResultDataTestReporter(testData, projectName, selectedFields, false, includeAllHistory, true);
278
278
  const rows = this.alignStepsWithIterationsFlatReport(testData, runResults, true, testPlanId, planName);
279
279
  return { planId: testPlanId, planName, rows: rows || [] };
280
280
  }
@@ -2548,32 +2548,32 @@ class ResultDataProvider {
2548
2548
  */
2549
2549
  mapTestPoint(testPoint, projectName) {
2550
2550
  var _a, _b, _c, _d, _e;
2551
- return {
2551
+ const pointAsOfTimestamp = this.resolvePointAsOfTimestamp(testPoint);
2552
+ return this.buildMappedTestPoint({
2552
2553
  testPointId: testPoint.id,
2553
2554
  testCaseId: testPoint.testCaseReference.id,
2554
2555
  testCaseName: testPoint.testCaseReference.name,
2555
- testCaseUrl: `${this.orgUrl}${projectName}/_workitems/edit/${testPoint.testCaseReference.id}`,
2556
2556
  configurationName: (_a = testPoint.configuration) === null || _a === void 0 ? void 0 : _a.name,
2557
- outcome: ((_b = testPoint.results) === null || _b === void 0 ? void 0 : _b.outcome) || 'Not Run',
2557
+ outcome: (_b = testPoint.results) === null || _b === void 0 ? void 0 : _b.outcome,
2558
2558
  testSuite: testPoint.testSuite,
2559
2559
  lastRunId: (_c = testPoint.results) === null || _c === void 0 ? void 0 : _c.lastTestRunId,
2560
2560
  lastResultId: (_d = testPoint.results) === null || _d === void 0 ? void 0 : _d.lastResultId,
2561
2561
  lastResultDetails: (_e = testPoint.results) === null || _e === void 0 ? void 0 : _e.lastResultDetails,
2562
- };
2562
+ }, projectName, pointAsOfTimestamp);
2563
2563
  }
2564
2564
  /**
2565
2565
  * Maps raw test point data to a simplified object.
2566
2566
  */
2567
2567
  mapTestPointForCrossPlans(testPoint, projectName) {
2568
2568
  var _a, _b, _c;
2569
- return {
2569
+ const pointAsOfTimestamp = this.resolvePointAsOfTimestamp(testPoint);
2570
+ return this.buildMappedTestPoint({
2570
2571
  testPointId: testPoint.id,
2571
2572
  testCaseId: testPoint.testCase.id,
2572
2573
  testCaseName: testPoint.testCase.name,
2573
- testCaseUrl: `${this.orgUrl}${projectName}/_workitems/edit/${testPoint.testCase.id}`,
2574
- testSuite: testPoint.testSuite,
2575
2574
  configurationName: (_a = testPoint.configuration) === null || _a === void 0 ? void 0 : _a.name,
2576
- outcome: testPoint.outcome || 'Not Run',
2575
+ outcome: testPoint.outcome,
2576
+ testSuite: testPoint.testSuite,
2577
2577
  lastRunId: (_b = testPoint.lastTestRun) === null || _b === void 0 ? void 0 : _b.id,
2578
2578
  lastResultId: (_c = testPoint.lastResult) === null || _c === void 0 ? void 0 : _c.id,
2579
2579
  lastResultDetails: testPoint.lastResultDetails || {
@@ -2581,7 +2581,50 @@ class ResultDataProvider {
2581
2581
  dateCompleted: '0000-00-00T00:00:00.000Z',
2582
2582
  runBy: { displayName: 'No tester', id: '00000000-0000-0000-0000-000000000000' },
2583
2583
  },
2584
+ }, projectName, pointAsOfTimestamp);
2585
+ }
2586
+ /**
2587
+ * Resolves a point timestamp that can be used as WIT `asOf` anchor.
2588
+ * Returns an ISO string when a valid date exists, otherwise an empty string.
2589
+ */
2590
+ resolvePointAsOfTimestamp(testPoint) {
2591
+ const candidates = [
2592
+ testPoint === null || testPoint === void 0 ? void 0 : testPoint.lastUpdatedDate,
2593
+ testPoint === null || testPoint === void 0 ? void 0 : testPoint.lastUpdatedOn,
2594
+ testPoint === null || testPoint === void 0 ? void 0 : testPoint.updatedDate,
2595
+ testPoint === null || testPoint === void 0 ? void 0 : testPoint.updatedOn,
2596
+ ];
2597
+ for (const candidate of candidates) {
2598
+ const raw = String(candidate || '').trim();
2599
+ if (!raw)
2600
+ continue;
2601
+ const parsed = new Date(raw);
2602
+ if (!Number.isNaN(parsed.getTime())) {
2603
+ return parsed.toISOString();
2604
+ }
2605
+ }
2606
+ return '';
2607
+ }
2608
+ /**
2609
+ * Builds a normalized test point shape used by downstream result fetch flows.
2610
+ */
2611
+ buildMappedTestPoint(pointData, projectName, pointAsOfTimestamp) {
2612
+ const mappedPoint = {
2613
+ testPointId: pointData.testPointId,
2614
+ testCaseId: pointData.testCaseId,
2615
+ testCaseName: pointData.testCaseName,
2616
+ testCaseUrl: `${this.orgUrl}${projectName}/_workitems/edit/${pointData.testCaseId}`,
2617
+ configurationName: pointData.configurationName,
2618
+ outcome: pointData.outcome || 'Not Run',
2619
+ testSuite: pointData.testSuite,
2620
+ lastRunId: pointData.lastRunId,
2621
+ lastResultId: pointData.lastResultId,
2622
+ lastResultDetails: pointData.lastResultDetails,
2584
2623
  };
2624
+ if (pointAsOfTimestamp) {
2625
+ mappedPoint.pointAsOfTimestamp = pointAsOfTimestamp;
2626
+ }
2627
+ return mappedPoint;
2585
2628
  }
2586
2629
  // Helper method to get all test points for a test case
2587
2630
  async getTestPointsForTestCases(projectName, testCaseId) {
@@ -2639,6 +2682,9 @@ class ResultDataProvider {
2639
2682
  }
2640
2683
  return fields;
2641
2684
  }
2685
+ /**
2686
+ * Reads a field value by reference name with case-insensitive key matching.
2687
+ */
2642
2688
  getFieldValueByName(fields, fieldName) {
2643
2689
  if (!fields || typeof fields !== 'object')
2644
2690
  return undefined;
@@ -2711,13 +2757,19 @@ class ResultDataProvider {
2711
2757
  return null;
2712
2758
  const expandParam = expandAll ? '?$expand=all' : '';
2713
2759
  const url = `${this.orgUrl}${projectName}/_apis/wit/workItems/${id}/revisions/${rev}${expandParam}`;
2714
- try {
2715
- return await tfs_1.TFSServices.getItemContent(url, this.token);
2716
- }
2717
- catch (error) {
2718
- logger_1.default.warn(`Failed to fetch work item ${id} by revision ${rev}: ${(error === null || error === void 0 ? void 0 : error.message) || error}`);
2760
+ return this.fetchWorkItemByUrl(url, `work item ${id} by revision ${rev}`);
2761
+ }
2762
+ async fetchWorkItemByAsOf(projectName, workItemId, asOfTimestamp, expandAll = false) {
2763
+ const id = Number(workItemId || 0);
2764
+ const parsedAsOf = new Date(String(asOfTimestamp || '').trim());
2765
+ if (!Number.isFinite(id) || id <= 0 || Number.isNaN(parsedAsOf.getTime()))
2719
2766
  return null;
2767
+ const query = [`asOf=${encodeURIComponent(parsedAsOf.toISOString())}`];
2768
+ if (expandAll) {
2769
+ query.push(`$expand=all`);
2720
2770
  }
2771
+ const url = `${this.orgUrl}${projectName}/_apis/wit/workItems/${id}?${query.join('&')}`;
2772
+ return this.fetchWorkItemByUrl(url, `work item ${id} by asOf ${parsedAsOf.toISOString()}`);
2721
2773
  }
2722
2774
  async fetchWorkItemLatest(projectName, workItemId, expandAll = false) {
2723
2775
  const id = Number(workItemId || 0);
@@ -2725,14 +2777,37 @@ class ResultDataProvider {
2725
2777
  return null;
2726
2778
  const expandParam = expandAll ? '?$expand=all' : '';
2727
2779
  const url = `${this.orgUrl}${projectName}/_apis/wit/workItems/${id}${expandParam}`;
2780
+ return this.fetchWorkItemByUrl(url, `latest work item ${id}`);
2781
+ }
2782
+ /**
2783
+ * Executes a work-item GET by URL and applies consistent warning/error handling.
2784
+ */
2785
+ async fetchWorkItemByUrl(url, failureContext) {
2728
2786
  try {
2729
2787
  return await tfs_1.TFSServices.getItemContent(url, this.token);
2730
2788
  }
2731
2789
  catch (error) {
2732
- logger_1.default.warn(`Failed to fetch latest work item ${id}: ${(error === null || error === void 0 ? void 0 : error.message) || error}`);
2790
+ logger_1.default.warn(`Failed to fetch ${failureContext}: ${(error === null || error === void 0 ? void 0 : error.message) || error}`);
2733
2791
  return null;
2734
2792
  }
2735
2793
  }
2794
+ /**
2795
+ * Resolves runless test case data using ordered fallbacks:
2796
+ * 1) point-based `asOf` snapshot, 2) explicit revision, 3) suite payload snapshot, 4) latest WI.
2797
+ */
2798
+ async resolveRunlessTestCaseData(projectName, testCaseId, suiteTestCaseRevision, pointAsOfTimestamp, fallbackSnapshot, expandAll) {
2799
+ if (pointAsOfTimestamp) {
2800
+ const asOfSnapshot = await this.fetchWorkItemByAsOf(projectName, testCaseId, pointAsOfTimestamp, expandAll);
2801
+ if (asOfSnapshot)
2802
+ return asOfSnapshot;
2803
+ }
2804
+ const revisionSnapshot = await this.fetchWorkItemByRevision(projectName, testCaseId, suiteTestCaseRevision, expandAll);
2805
+ if (revisionSnapshot)
2806
+ return revisionSnapshot;
2807
+ if (fallbackSnapshot)
2808
+ return fallbackSnapshot;
2809
+ return this.fetchWorkItemLatest(projectName, testCaseId, expandAll);
2810
+ }
2736
2811
  /**
2737
2812
  * Fetches result data based on the Work Item Test Reporter.
2738
2813
  *
@@ -2747,7 +2822,7 @@ class ResultDataProvider {
2747
2822
  * @param isQueryMode - (Optional) A flag indicating whether the result data is being fetched in query mode.
2748
2823
  * @returns A promise that resolves to the fetched result data.
2749
2824
  */
2750
- async fetchResultDataBasedOnWiBase(projectName, runId, resultId, isTestReporter = false, selectedFields, isQueryMode, point, includeAllHistory = false) {
2825
+ async fetchResultDataBasedOnWiBase(projectName, runId, resultId, isTestReporter = false, selectedFields, isQueryMode, point, includeAllHistory = false, useRunlessAsOf = false) {
2751
2826
  var _a, _b, _c, _d, _e, _f, _g, _h, _j;
2752
2827
  try {
2753
2828
  let filteredFields = {};
@@ -2762,14 +2837,11 @@ class ResultDataProvider {
2762
2837
  const suiteTestCaseItem = point === null || point === void 0 ? void 0 : point.suiteTestCase;
2763
2838
  const testCaseId = Number((point === null || point === void 0 ? void 0 : point.testCaseId) || ((_a = suiteTestCaseItem === null || suiteTestCaseItem === void 0 ? void 0 : suiteTestCaseItem.workItem) === null || _a === void 0 ? void 0 : _a.id) || (suiteTestCaseItem === null || suiteTestCaseItem === void 0 ? void 0 : suiteTestCaseItem.testCaseId) || 0);
2764
2839
  const suiteTestCaseRevision = this.resolveSuiteTestCaseRevision(suiteTestCaseItem);
2840
+ const pointAsOfTimestamp = useRunlessAsOf
2841
+ ? String((point === null || point === void 0 ? void 0 : point.pointAsOfTimestamp) || '').trim()
2842
+ : '';
2765
2843
  const fallbackSnapshot = this.buildWorkItemSnapshotFromSuiteTestCase(suiteTestCaseItem, testCaseId, String((point === null || point === void 0 ? void 0 : point.testCaseName) || ''));
2766
- let testCaseData = await this.fetchWorkItemByRevision(projectName, testCaseId, suiteTestCaseRevision, isTestReporter);
2767
- if (!testCaseData) {
2768
- testCaseData = fallbackSnapshot;
2769
- }
2770
- if (!testCaseData) {
2771
- testCaseData = await this.fetchWorkItemLatest(projectName, testCaseId, isTestReporter);
2772
- }
2844
+ const testCaseData = await this.resolveRunlessTestCaseData(projectName, testCaseId, suiteTestCaseRevision, pointAsOfTimestamp, fallbackSnapshot, isTestReporter);
2773
2845
  if (!testCaseData) {
2774
2846
  logger_1.default.warn(`Could not resolve test case ${point.testCaseId} for runless point fallback.`);
2775
2847
  return null;
@@ -4045,8 +4117,8 @@ class ResultDataProvider {
4045
4117
  * @param selectedFields - (Optional) An array of field names to include in the result data.
4046
4118
  * @returns A promise that resolves to the fetched result data.
4047
4119
  */
4048
- async fetchResultDataBasedOnWiTestReporter(projectName, runId, resultId, selectedFields, isQueryMode, point, includeAllHistory = false) {
4049
- return this.fetchResultDataBasedOnWiBase(projectName, runId, resultId, true, selectedFields, isQueryMode, point, includeAllHistory);
4120
+ async fetchResultDataBasedOnWiTestReporter(projectName, runId, resultId, selectedFields, isQueryMode, point, includeAllHistory = false, useRunlessAsOf = false) {
4121
+ return this.fetchResultDataBasedOnWiBase(projectName, runId, resultId, true, selectedFields, isQueryMode, point, includeAllHistory, useRunlessAsOf);
4050
4122
  }
4051
4123
  /**
4052
4124
  * Fetches all result data for the test reporter by processing the provided test data.
@@ -4060,8 +4132,8 @@ class ResultDataProvider {
4060
4132
  * @param selectedFields - An optional array of field names to include in the result data.
4061
4133
  * @returns A promise that resolves to an array of processed result data.
4062
4134
  */
4063
- async fetchAllResultDataTestReporter(testData, projectName, selectedFields, isQueryMode, includeAllHistory = false) {
4064
- return this.fetchAllResultDataBase(testData, projectName, true, (projectName, testSuiteId, point, selectedFields, isQueryMode, includeAllHistory) => this.fetchResultDataForTestReporter(projectName, testSuiteId, point, selectedFields, isQueryMode, includeAllHistory), [selectedFields, isQueryMode, includeAllHistory]);
4135
+ async fetchAllResultDataTestReporter(testData, projectName, selectedFields, isQueryMode, includeAllHistory = false, useRunlessAsOf = false) {
4136
+ return this.fetchAllResultDataBase(testData, projectName, true, (projectName, testSuiteId, point, selectedFields, isQueryMode, includeAllHistory, useRunlessAsOf) => this.fetchResultDataForTestReporter(projectName, testSuiteId, point, selectedFields, isQueryMode, includeAllHistory, useRunlessAsOf), [selectedFields, isQueryMode, includeAllHistory, useRunlessAsOf]);
4065
4137
  }
4066
4138
  /**
4067
4139
  * Aligns test steps with iterations for the test reporter by processing test data and iterations
@@ -4169,8 +4241,8 @@ class ResultDataProvider {
4169
4241
  * @returns A promise that resolves to the formatted result data object containing details about the test case,
4170
4242
  * test suite, last run, iteration, and other selected fields.
4171
4243
  */
4172
- async fetchResultDataForTestReporter(projectName, testSuiteId, point, selectedFields, isQueryMode, includeAllHistory = false) {
4173
- return this.fetchResultDataBase(projectName, testSuiteId, point, (project, runId, resultId, fields, isQueryMode, point, includeAllHistory) => this.fetchResultDataBasedOnWiTestReporter(project, runId, resultId, fields, isQueryMode, point, includeAllHistory), (resultData, testSuiteId, point, selectedFields) => {
4244
+ async fetchResultDataForTestReporter(projectName, testSuiteId, point, selectedFields, isQueryMode, includeAllHistory = false, useRunlessAsOf = false) {
4245
+ return this.fetchResultDataBase(projectName, testSuiteId, point, (project, runId, resultId, fields, isQueryMode, point, includeAllHistory, useRunlessAsOf) => this.fetchResultDataBasedOnWiTestReporter(project, runId, resultId, fields, isQueryMode, point, includeAllHistory, useRunlessAsOf), (resultData, testSuiteId, point, selectedFields) => {
4174
4246
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w;
4175
4247
  const { lastRunId, lastResultId, configurationName, lastResultDetails } = point;
4176
4248
  try {
@@ -4263,7 +4335,7 @@ class ResultDataProvider {
4263
4335
  logger_1.default.error(`Error stack: ${err.stack}`);
4264
4336
  return null;
4265
4337
  }
4266
- }, [selectedFields, isQueryMode, point, includeAllHistory]);
4338
+ }, [selectedFields, isQueryMode, point, includeAllHistory, useRunlessAsOf]);
4267
4339
  }
4268
4340
  getTestOutcome(resultData) {
4269
4341
  // Default outcome if nothing else is available