@elisra-devops/docgen-data-provider 1.130.0 → 1.132.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) {
@@ -514,6 +514,37 @@ export default class TicketsDataProvider {
514
514
  }
515
515
  }
516
516
 
517
+ /**
518
+ * Returns the project's work item type names and the complete set of valid state names.
519
+ * Used to validate SVD work-item filter options before generation so callers receive
520
+ * a descriptive error instead of a silent zero-result document.
521
+ *
522
+ * Types come from GET _apis/wit/workitemtypes (value[].name).
523
+ * States are extracted from the transitions map keys on the same response — each key
524
+ * is a state name — avoiding N extra per-type state calls.
525
+ *
526
+ * @param project - The project name.
527
+ * @returns An object with `types` (work item type names) and `states` (all state names across types).
528
+ */
529
+ async GetWorkItemTypeStates(project: string): Promise<{ types: string[]; states: string[] }> {
530
+ try {
531
+ const url = `${this.orgUrl}${project}/_apis/wit/workitemtypes`;
532
+ const { value: workItemTypes } = await TFSServices.getItemContent(url, this.token);
533
+ const types: string[] = (workItemTypes ?? []).map((t: any) => t?.name).filter(Boolean);
534
+ // transitions is a map whose keys are state names (e.g. { "Active": [...], "Closed": [...] })
535
+ const stateSet = new Set<string>();
536
+ for (const t of workItemTypes ?? []) {
537
+ for (const stateName of Object.keys(t?.transitions ?? {})) {
538
+ if (stateName) stateSet.add(stateName);
539
+ }
540
+ }
541
+ return { types, states: Array.from(stateSet) };
542
+ } catch (err: any) {
543
+ logger.error(`Error fetching work item type states: ${err.message}`);
544
+ throw err;
545
+ }
546
+ }
547
+
517
548
  /**
518
549
  * fetches linked queries
519
550
  * @param queries fetched queries
@@ -2334,7 +2365,7 @@ export default class TicketsDataProvider {
2334
2365
  const postBatch = (currentIds: any[]) =>
2335
2366
  this.withHistoricalApiVersionFallback('populate-workitems-batch', (apiVersion) =>
2336
2367
  TFSServices.getItemContent(this.appendApiVersion(baseUrl, apiVersion), this.token, 'post', {
2337
- $expand: 'Relations',
2368
+ $expand: 'All',
2338
2369
  ids: currentIds,
2339
2370
  }),
2340
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);