@elisra-devops/docgen-data-provider 1.131.0 → 1.133.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.
@@ -14,8 +14,11 @@ export default class JfrogDataProvider {
14
14
  }
15
15
 
16
16
  async getServiceConnectionUrlByConnectionId(teamProject: string, connectionId: string) {
17
- let url = `${this.orgUrl}${teamProject}/_apis/serviceendpoint/endpoints/${connectionId}?api-version=6`;
17
+ let url = `${this.orgUrl}${teamProject}/_apis/serviceendpoint/endpoints/${connectionId}?api-version=7.1`;
18
18
  const serviceConnectionResponse = await TFSServices.getItemContent(url, this.tfsToken);
19
+ if (!serviceConnectionResponse?.url) {
20
+ throw new Error(`Service connection ${connectionId} returned no URL — identity may lack service-endpoint read permission`);
21
+ }
19
22
  logger.debug(`service connection url ${JSON.stringify(serviceConnectionResponse.url)}`);
20
23
  return serviceConnectionResponse.url;
21
24
  }
@@ -23,6 +26,11 @@ export default class JfrogDataProvider {
23
26
  async getCiDataFromJfrog(jfrogUrl: string, buildName: string, buildVersion: string): Promise<string> {
24
27
  let jfrogHeader: any = {};
25
28
  try {
29
+ if (!jfrogUrl) {
30
+ throw new Error(
31
+ `JFrog service connection URL is unresolved for build "${buildName}" — the caller identity may lack service-endpoint read permission`
32
+ );
33
+ }
26
34
  if (this.jfrogToken !== '') {
27
35
  jfrogHeader['Authorization'] = `Bearer ${this.jfrogToken}`;
28
36
  }
@@ -42,6 +50,9 @@ export default class JfrogDataProvider {
42
50
  this.jfrogToken !== ''
43
51
  ? await TFSServices.getJfrogRequest(getCiRequestUrl, jfrogHeader)
44
52
  : await TFSServices.getJfrogRequest(getCiRequestUrl);
53
+ if (!getCiResponse?.buildInfo?.url) {
54
+ throw new Error(`JFrog response for build "${buildName}${buildVersion}" is missing buildInfo.url`);
55
+ }
45
56
  logger.debug(`CI Url from JFROG: ${getCiResponse.buildInfo.url}`);
46
57
  return getCiResponse.buildInfo.url;
47
58
  } catch (err: any) {
@@ -290,7 +290,8 @@ export default class ResultDataProvider {
290
290
  enableRunStepStatusFilter: boolean,
291
291
  linkedQueryRequest: any,
292
292
  errorFilterMode: string = 'none',
293
- includeAllHistory: boolean = false
293
+ includeAllHistory: boolean = false,
294
+ useLatestTestCaseProperties: boolean = false
294
295
  ) {
295
296
  const fetchedTestResults: any[] = [];
296
297
  logger.debug(
@@ -322,7 +323,9 @@ export default class ResultDataProvider {
322
323
  projectName,
323
324
  selectedFields,
324
325
  isQueryMode,
325
- includeAllHistory
326
+ includeAllHistory,
327
+ false,
328
+ useLatestTestCaseProperties
326
329
  );
327
330
  logger.debug(`Aligning steps with iterations for test reporter results`);
328
331
  const testReporterData = this.alignStepsWithIterationsTestReporter(
@@ -3656,10 +3659,16 @@ export default class ResultDataProvider {
3656
3659
  suiteTestCaseRevision: number,
3657
3660
  pointAsOfTimestamp: string,
3658
3661
  fallbackSnapshot: any,
3659
- expandAll: boolean
3662
+ expandAll: boolean,
3663
+ useLatestTestCaseProperties: boolean = false
3660
3664
  ): Promise<any | null> {
3661
3665
  let bestSnapshotWithoutSteps: any | null = null;
3662
3666
 
3667
+ if (useLatestTestCaseProperties) {
3668
+ const latest = await this.fetchWorkItemLatest(projectName, testCaseId, expandAll);
3669
+ if (latest) return latest;
3670
+ }
3671
+
3663
3672
  if (pointAsOfTimestamp) {
3664
3673
  const asOfSnapshot = await this.fetchWorkItemByAsOf(
3665
3674
  projectName,
@@ -3721,14 +3730,15 @@ export default class ResultDataProvider {
3721
3730
  isQueryMode?: boolean,
3722
3731
  point?: any,
3723
3732
  includeAllHistory: boolean = false,
3724
- useRunlessAsOf: boolean = false
3733
+ useRunlessAsOf: boolean = false,
3734
+ useLatestTestCaseProperties: boolean = false
3725
3735
  ): Promise<any> {
3726
3736
  try {
3727
3737
  let filteredFields: any = {};
3728
3738
  let relatedRequirements: any[] = [];
3729
3739
  let relatedBugs: any[] = [];
3730
3740
  let relatedCRs: any[] = [];
3731
- if (runId === '0' || resultId === '0') {
3741
+ if (useLatestTestCaseProperties || runId === '0' || resultId === '0') {
3732
3742
  if (!point) {
3733
3743
  logger.warn(`Invalid run result ${runId} or result ${resultId}`);
3734
3744
  return null;
@@ -3752,7 +3762,8 @@ export default class ResultDataProvider {
3752
3762
  suiteTestCaseRevision,
3753
3763
  pointAsOfTimestamp,
3754
3764
  fallbackSnapshot,
3755
- isTestReporter
3765
+ isTestReporter,
3766
+ useLatestTestCaseProperties
3756
3767
  );
3757
3768
  if (!testCaseData) {
3758
3769
  logger.warn(`Could not resolve test case ${point.testCaseId} for runless point fallback.`);
@@ -5465,7 +5476,8 @@ export default class ResultDataProvider {
5465
5476
  isQueryMode?: boolean,
5466
5477
  point?: any,
5467
5478
  includeAllHistory: boolean = false,
5468
- useRunlessAsOf: boolean = false
5479
+ useRunlessAsOf: boolean = false,
5480
+ useLatestTestCaseProperties: boolean = false
5469
5481
  ): Promise<any> {
5470
5482
  return this.fetchResultDataBasedOnWiBase(
5471
5483
  projectName,
@@ -5476,7 +5488,8 @@ export default class ResultDataProvider {
5476
5488
  isQueryMode,
5477
5489
  point,
5478
5490
  includeAllHistory,
5479
- useRunlessAsOf
5491
+ useRunlessAsOf,
5492
+ useLatestTestCaseProperties
5480
5493
  );
5481
5494
  }
5482
5495
 
@@ -5498,13 +5511,14 @@ export default class ResultDataProvider {
5498
5511
  selectedFields?: string[],
5499
5512
  isQueryMode?: boolean,
5500
5513
  includeAllHistory: boolean = false,
5501
- useRunlessAsOf: boolean = false
5514
+ useRunlessAsOf: boolean = false,
5515
+ useLatestTestCaseProperties: boolean = false
5502
5516
  ): Promise<any[]> {
5503
5517
  return this.fetchAllResultDataBase(
5504
5518
  testData,
5505
5519
  projectName,
5506
5520
  true,
5507
- (projectName, testSuiteId, point, selectedFields, isQueryMode, includeAllHistory, useRunlessAsOf) =>
5521
+ (projectName, testSuiteId, point, selectedFields, isQueryMode, includeAllHistory, useRunlessAsOf, useLatestTestCaseProperties) =>
5508
5522
  this.fetchResultDataForTestReporter(
5509
5523
  projectName,
5510
5524
  testSuiteId,
@@ -5512,9 +5526,10 @@ export default class ResultDataProvider {
5512
5526
  selectedFields,
5513
5527
  isQueryMode,
5514
5528
  includeAllHistory,
5515
- useRunlessAsOf
5529
+ useRunlessAsOf,
5530
+ useLatestTestCaseProperties
5516
5531
  ),
5517
- [selectedFields, isQueryMode, includeAllHistory, useRunlessAsOf]
5532
+ [selectedFields, isQueryMode, includeAllHistory, useRunlessAsOf, useLatestTestCaseProperties]
5518
5533
  );
5519
5534
  }
5520
5535
 
@@ -5673,13 +5688,14 @@ export default class ResultDataProvider {
5673
5688
  selectedFields?: string[],
5674
5689
  isQueryMode?: boolean,
5675
5690
  includeAllHistory: boolean = false,
5676
- useRunlessAsOf: boolean = false
5691
+ useRunlessAsOf: boolean = false,
5692
+ useLatestTestCaseProperties: boolean = false
5677
5693
  ) {
5678
5694
  return this.fetchResultDataBase(
5679
5695
  projectName,
5680
5696
  testSuiteId,
5681
5697
  point,
5682
- (project, runId, resultId, fields, isQueryMode, point, includeAllHistory, useRunlessAsOf) =>
5698
+ (project, runId, resultId, fields, isQueryMode, point, includeAllHistory, useRunlessAsOf, useLatestTestCaseProperties) =>
5683
5699
  this.fetchResultDataBasedOnWiTestReporter(
5684
5700
  project,
5685
5701
  runId,
@@ -5688,7 +5704,8 @@ export default class ResultDataProvider {
5688
5704
  isQueryMode,
5689
5705
  point,
5690
5706
  includeAllHistory,
5691
- useRunlessAsOf
5707
+ useRunlessAsOf,
5708
+ useLatestTestCaseProperties
5692
5709
  ),
5693
5710
  (resultData, testSuiteId, point, selectedFields) => {
5694
5711
  const { lastRunId, lastResultId, configurationName, lastResultDetails } = point;
@@ -2365,7 +2365,7 @@ export default class TicketsDataProvider {
2365
2365
  const postBatch = (currentIds: any[]) =>
2366
2366
  this.withHistoricalApiVersionFallback('populate-workitems-batch', (apiVersion) =>
2367
2367
  TFSServices.getItemContent(this.appendApiVersion(baseUrl, apiVersion), this.token, 'post', {
2368
- $expand: 'Relations',
2368
+ $expand: 'All',
2369
2369
  ids: currentIds,
2370
2370
  }),
2371
2371
  ).then(({ result }) => result);
@@ -33,7 +33,7 @@ describe('JfrogDataProvider', () => {
33
33
 
34
34
  // Assert
35
35
  expect(TFSServices.getItemContent).toHaveBeenCalledWith(
36
- `${mockOrgUrl}${mockTeamProject}/_apis/serviceendpoint/endpoints/${mockConnectionId}?api-version=6`,
36
+ `${mockOrgUrl}${mockTeamProject}/_apis/serviceendpoint/endpoints/${mockConnectionId}?api-version=7.1`,
37
37
  mockTfsToken
38
38
  );
39
39
  expect(logger.debug).toHaveBeenCalledWith(`service connection url "${mockResponse.url}"`);
@@ -1129,8 +1129,8 @@ describe('GitDataProvider - GetItemsInCommitRange', () => {
1129
1129
  const mockPRsResponse = { count: 0, value: [] };
1130
1130
 
1131
1131
  (TFSServices.getItemContent as jest.Mock)
1132
- .mockResolvedValueOnce(mockPopulatedWI)
1133
- .mockResolvedValueOnce(mockPRsResponse);
1132
+ .mockResolvedValueOnce({ value: [mockPopulatedWI] }) // batch POST for work items
1133
+ .mockResolvedValueOnce(mockPRsResponse); // GetPullRequestsLinkedItemsInCommitRange (default: includePullRequestWorkItems=true)
1134
1134
 
1135
1135
  // Act
1136
1136
  const result = await gitDataProvider.GetItemsInCommitRange(projectId, repoId, commitRange, null, false);
@@ -1248,7 +1248,7 @@ describe('GitDataProvider - getItemsForPipelineRange', () => {
1248
1248
 
1249
1249
  (TFSServices.getItemContent as jest.Mock)
1250
1250
  .mockResolvedValueOnce(mockRepoData)
1251
- .mockResolvedValueOnce(mockPopulatedWI);
1251
+ .mockResolvedValueOnce({ value: [mockPopulatedWI] }); // batch POST for work items
1252
1252
 
1253
1253
  // Act
1254
1254
  const result = await gitDataProvider.getItemsForPipelineRange(
@@ -1320,8 +1320,7 @@ describe('GitDataProvider - getItemsForPipelineRange', () => {
1320
1320
 
1321
1321
  (TFSServices.getItemContent as jest.Mock)
1322
1322
  .mockResolvedValueOnce(mockRepoData)
1323
- .mockResolvedValueOnce(mockPopulatedWI)
1324
- .mockResolvedValueOnce(mockPopulatedWI);
1323
+ .mockResolvedValueOnce({ value: [mockPopulatedWI] }); // single batch POST (WI id=1 deduped)
1325
1324
 
1326
1325
  // Act
1327
1326
  const result = await gitDataProvider.getItemsForPipelineRange(
@@ -1361,8 +1360,8 @@ describe('GitDataProvider - getItemsForPipelineRange', () => {
1361
1360
  const mockPopulatedWI = { id: 42, fields: { 'System.Title': 'Cross-project WI' } };
1362
1361
 
1363
1362
  (TFSServices.getItemContent as jest.Mock)
1364
- .mockResolvedValueOnce(mockRepoDataNoCrossLink) // repo metadata call
1365
- .mockResolvedValueOnce(mockPopulatedWI); // GetWorkItem call
1363
+ .mockResolvedValueOnce(mockRepoDataNoCrossLink) // repo metadata call
1364
+ .mockResolvedValueOnce({ value: [mockPopulatedWI] }); // batch POST for work items
1366
1365
 
1367
1366
  const result = await gitDataProvider.getItemsForPipelineRange(
1368
1367
  pipelineTeamProject,
@@ -1373,10 +1372,12 @@ describe('GitDataProvider - getItemsForPipelineRange', () => {
1373
1372
 
1374
1373
  expect(result.commitChangesArray).toHaveLength(1);
1375
1374
  expect(result.commitChangesArray[0].workItem.id).toBe(42);
1376
- // GetWorkItem should have been called with the repo's own projectId from repoData.project.id
1375
+ // batch POST URL contains the repo's projectId (cross-project-id)
1377
1376
  expect(TFSServices.getItemContent).toHaveBeenCalledWith(
1378
1377
  expect.stringContaining('cross-project-id'),
1379
- expect.any(String)
1378
+ expect.any(String),
1379
+ 'post',
1380
+ expect.objectContaining({ ids: expect.any(Array) })
1380
1381
  );
1381
1382
  });
1382
1383
 
@@ -1401,7 +1402,7 @@ describe('GitDataProvider - getItemsForPipelineRange', () => {
1401
1402
 
1402
1403
  (TFSServices.getItemContent as jest.Mock)
1403
1404
  .mockResolvedValueOnce(mockRepoDataEmpty)
1404
- .mockResolvedValueOnce(mockPopulatedWI);
1405
+ .mockResolvedValueOnce({ value: [mockPopulatedWI] }); // batch POST for work items
1405
1406
 
1406
1407
  const result = await gitDataProvider.getItemsForPipelineRange(
1407
1408
  pipelineTeamProject,
@@ -1412,10 +1413,12 @@ describe('GitDataProvider - getItemsForPipelineRange', () => {
1412
1413
 
1413
1414
  expect(result.commitChangesArray).toHaveLength(1);
1414
1415
  expect(result.commitChangesArray[0].workItem.id).toBe(99);
1415
- // GetWorkItem should have been called with the pipeline teamProject as fallback
1416
+ // batch POST URL contains the fallback teamProject
1416
1417
  expect(TFSServices.getItemContent).toHaveBeenCalledWith(
1417
1418
  expect.stringContaining(pipelineTeamProject),
1418
- expect.any(String)
1419
+ expect.any(String),
1420
+ 'post',
1421
+ expect.objectContaining({ ids: expect.any(Array) })
1419
1422
  );
1420
1423
  expect(logger.warn).toHaveBeenCalledWith(
1421
1424
  expect.stringContaining(`falling back to pipeline teamProject=${pipelineTeamProject}`)
@@ -1774,9 +1777,9 @@ describe('GitDataProvider - createLinkedRelatedItemsForSVD (via GetItemsInCommit
1774
1777
  const mockPRsResponse = { count: 0, value: [] };
1775
1778
 
1776
1779
  (TFSServices.getItemContent as jest.Mock)
1777
- .mockResolvedValueOnce(mockPopulatedWI)
1778
- .mockResolvedValueOnce(mockRelatedWI)
1779
- .mockResolvedValueOnce(mockPRsResponse);
1780
+ .mockResolvedValueOnce({ value: [mockPopulatedWI] }) // batch POST for work items
1781
+ .mockResolvedValueOnce(mockRelatedWI) // GetWorkItemByUrl (linked relation)
1782
+ .mockResolvedValueOnce(mockPRsResponse); // GetPullRequestsLinkedItemsInCommitRange
1780
1783
 
1781
1784
  // Act
1782
1785
  const result = await gitDataProvider.GetItemsInCommitRange(
@@ -1826,8 +1829,8 @@ describe('GitDataProvider - createLinkedRelatedItemsForSVD (via GetItemsInCommit
1826
1829
  const mockPRsResponse = { count: 0, value: [] };
1827
1830
 
1828
1831
  (TFSServices.getItemContent as jest.Mock)
1829
- .mockResolvedValueOnce(mockPopulatedWI)
1830
- .mockResolvedValueOnce(mockPRsResponse);
1832
+ .mockResolvedValueOnce({ value: [mockPopulatedWI] }) // batch POST for work items
1833
+ .mockResolvedValueOnce(mockPRsResponse); // GetPullRequestsLinkedItemsInCommitRange
1831
1834
 
1832
1835
  // Act
1833
1836
  const result = await gitDataProvider.GetItemsInCommitRange(
@@ -1877,8 +1880,8 @@ describe('GitDataProvider - createLinkedRelatedItemsForSVD (via GetItemsInCommit
1877
1880
  const mockPRsResponse = { count: 0, value: [] };
1878
1881
 
1879
1882
  (TFSServices.getItemContent as jest.Mock)
1880
- .mockResolvedValueOnce(mockPopulatedWI)
1881
- .mockResolvedValueOnce(mockPRsResponse);
1883
+ .mockResolvedValueOnce({ value: [mockPopulatedWI] }) // batch POST for work items
1884
+ .mockResolvedValueOnce(mockPRsResponse); // GetPullRequestsLinkedItemsInCommitRange
1882
1885
 
1883
1886
  // Act
1884
1887
  const result = await gitDataProvider.GetItemsInCommitRange(
@@ -1936,9 +1939,9 @@ describe('GitDataProvider - createLinkedRelatedItemsForSVD (via GetItemsInCommit
1936
1939
  const mockPRsResponse = { count: 0, value: [] };
1937
1940
 
1938
1941
  (TFSServices.getItemContent as jest.Mock)
1939
- .mockResolvedValueOnce(mockPopulatedWI)
1940
- .mockResolvedValueOnce(mockRelatedWI)
1941
- .mockResolvedValueOnce(mockPRsResponse);
1942
+ .mockResolvedValueOnce({ value: [mockPopulatedWI] }) // batch POST for work items
1943
+ .mockResolvedValueOnce(mockRelatedWI) // GetWorkItemByUrl (linked relation)
1944
+ .mockResolvedValueOnce(mockPRsResponse); // GetPullRequestsLinkedItemsInCommitRange
1942
1945
 
1943
1946
  // Act
1944
1947
  const result = await gitDataProvider.GetItemsInCommitRange(
@@ -2105,9 +2108,8 @@ describe('GitDataProvider - duplicate work item removal', () => {
2105
2108
  const mockPRsResponse = { count: 0, value: [] };
2106
2109
 
2107
2110
  (TFSServices.getItemContent as jest.Mock)
2108
- .mockResolvedValueOnce(mockPopulatedWI)
2109
- .mockResolvedValueOnce(mockPopulatedWI)
2110
- .mockResolvedValueOnce(mockPRsResponse);
2111
+ .mockResolvedValueOnce({ value: [mockPopulatedWI] }) // batch POST (WI id=1 deduped from 2 commits)
2112
+ .mockResolvedValueOnce(mockPRsResponse); // GetPullRequestsLinkedItemsInCommitRange
2111
2113
 
2112
2114
  // Act
2113
2115
  const result = await gitDataProvider.GetItemsInCommitRange(projectId, repoId, commitRange, null, false);