@elisra-devops/docgen-data-provider 1.117.0 → 1.119.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.
@@ -46,7 +46,6 @@ export default class PipelinesDataProvider {
46
46
  * @param pipelineId Pipeline/build definition id.
47
47
  * @param toPipelineRunId Target run/build id. Candidates must be older than this id.
48
48
  * @param targetPipeline Full target pipeline run details, used for repository/branch matching.
49
- * @param searchPrevPipelineFromDifferentCommit When true, skip candidates from the same commit.
50
49
  * @param fromStage Optional stage name. When set, only previous runs with this successful stage match.
51
50
  */
52
51
  public async findPreviousPipeline(
@@ -54,7 +53,6 @@ export default class PipelinesDataProvider {
54
53
  pipelineId: string,
55
54
  toPipelineRunId: number,
56
55
  targetPipeline: any,
57
- searchPrevPipelineFromDifferentCommit: boolean,
58
56
  fromStage: string = ''
59
57
  ) {
60
58
  if (!fromStage) {
@@ -62,8 +60,7 @@ export default class PipelinesDataProvider {
62
60
  teamProject,
63
61
  pipelineId,
64
62
  toPipelineRunId,
65
- targetPipeline,
66
- searchPrevPipelineFromDifferentCommit
63
+ targetPipeline
67
64
  );
68
65
  if (previousBuildId) {
69
66
  return previousBuildId;
@@ -89,7 +86,7 @@ export default class PipelinesDataProvider {
89
86
  continue;
90
87
  }
91
88
 
92
- if (this.isMatchingPipeline(fromPipeline, targetPipeline, searchPrevPipelineFromDifferentCommit)) {
89
+ if (this.isMatchingPipeline(fromPipeline, targetPipeline)) {
93
90
  return pipelineRun.id;
94
91
  }
95
92
  }
@@ -109,8 +106,7 @@ export default class PipelinesDataProvider {
109
106
  teamProject: string,
110
107
  definitionId: string,
111
108
  toBuildId: number,
112
- targetPipeline: any,
113
- searchPrevPipelineFromDifferentCommit: boolean
109
+ targetPipeline: any
114
110
  ): Promise<number | undefined> {
115
111
  const targetRepo = this.getPrimaryPipelineRepository(targetPipeline);
116
112
  const targetBranch = this.normalizeBranchName(targetRepo?.refName);
@@ -121,7 +117,6 @@ export default class PipelinesDataProvider {
121
117
  definitionId,
122
118
  toBuildId,
123
119
  targetPipeline,
124
- searchPrevPipelineFromDifferentCommit,
125
120
  targetBranch
126
121
  );
127
122
  if (sameBranchResult.status === 'failed') {
@@ -136,8 +131,7 @@ export default class PipelinesDataProvider {
136
131
  teamProject,
137
132
  definitionId,
138
133
  toBuildId,
139
- targetPipeline,
140
- searchPrevPipelineFromDifferentCommit
134
+ targetPipeline
141
135
  );
142
136
  if (anyBranchResult.status === 'failed') {
143
137
  throw anyBranchResult.error;
@@ -157,7 +151,6 @@ export default class PipelinesDataProvider {
157
151
  definitionId: string,
158
152
  toBuildId: number,
159
153
  targetPipeline: any,
160
- searchPrevPipelineFromDifferentCommit: boolean,
161
154
  branchName?: string
162
155
  ): Promise<PreviousDiscoveryResult> {
163
156
  const targetRepo = this.getPrimaryPipelineRepository(targetPipeline);
@@ -189,7 +182,6 @@ export default class PipelinesDataProvider {
189
182
  build,
190
183
  targetRepo,
191
184
  toBuildId,
192
- searchPrevPipelineFromDifferentCommit,
193
185
  branchName
194
186
  )
195
187
  );
@@ -234,14 +226,12 @@ export default class PipelinesDataProvider {
234
226
  * Validates a Builds API candidate against the target run.
235
227
  *
236
228
  * A candidate must be older than the target, completed successfully, from the same
237
- * repository, and optionally from the required branch. When the caller asks for a
238
- * different commit, candidates with the same source version are excluded.
229
+ * repository, and optionally from the required branch.
239
230
  */
240
231
  private isMatchingPreviousBuild(
241
232
  build: any,
242
233
  targetRepo: any,
243
234
  toBuildId: number,
244
- searchPrevPipelineFromDifferentCommit: boolean,
245
235
  requiredBranch?: string
246
236
  ): boolean {
247
237
  const buildId = Number(build?.id);
@@ -255,10 +245,6 @@ export default class PipelinesDataProvider {
255
245
 
256
246
  if (requiredBranch && build?.sourceBranch !== requiredBranch) return false;
257
247
 
258
- if (build?.sourceVersion && targetRepo?.version && build.sourceVersion === targetRepo.version) {
259
- return !searchPrevPipelineFromDifferentCommit;
260
- }
261
-
262
248
  return true;
263
249
  }
264
250
 
@@ -467,17 +453,15 @@ export default class PipelinesDataProvider {
467
453
  }
468
454
 
469
455
  /**
470
- * Determines if two pipelines match based on their repository and version information.
456
+ * Determines if two pipelines match based on their repository and branch.
471
457
  *
472
458
  * @param fromPipeline - The source pipeline to compare.
473
459
  * @param targetPipeline - The target pipeline to compare against.
474
- * @param searchPrevPipelineFromDifferentCommit - A flag indicating whether to search for a previous pipeline from a different commit.
475
- * @returns `true` if the pipelines match based on the repository and version criteria; otherwise, `false`.
460
+ * @returns `true` if the pipelines share the same repository and ref; otherwise, `false`.
476
461
  */
477
462
  private isMatchingPipeline(
478
463
  fromPipeline: PipelineRun,
479
- targetPipeline: PipelineRun,
480
- searchPrevPipelineFromDifferentCommit: boolean
464
+ targetPipeline: PipelineRun
481
465
  ): boolean {
482
466
  if (!fromPipeline?.resources?.repositories || !targetPipeline?.resources?.repositories) {
483
467
  return false;
@@ -500,10 +484,6 @@ export default class PipelinesDataProvider {
500
484
  return false;
501
485
  }
502
486
 
503
- if (fromRepo.version === targetRepo.version) {
504
- return !searchPrevPipelineFromDifferentCommit;
505
- }
506
-
507
487
  return fromRepo.refName === targetRepo.refName;
508
488
  }
509
489
 
@@ -995,6 +975,18 @@ export default class PipelinesDataProvider {
995
975
  * @param gitDataProviderInstance - An instance of GitDataProvider to fetch repository details.
996
976
  * @returns A promise that resolves to an array of unique resource repositories.
997
977
  */
978
+ /**
979
+ * Rebuilds a repository API URL using the project name rather than the UUID that TFS
980
+ * returns in repo.url. On-prem TFS indexes WI-to-commit associations by project name, so
981
+ * commitsbatch requests must use the name form to receive populated workItems arrays.
982
+ */
983
+ private buildRepoApiUrl(repo: Repository): string {
984
+ const projectName = repo.project?.name;
985
+ return projectName
986
+ ? `${this.orgUrl}${encodeURIComponent(projectName)}/_apis/git/repositories/${repo.id}`
987
+ : repo.url;
988
+ }
989
+
998
990
  public async getPipelineResourceRepositoriesFromObject(
999
991
  inPipeline: PipelineRun,
1000
992
  gitDataProviderInstance: GitDataProvider
@@ -1015,10 +1007,11 @@ export default class PipelinesDataProvider {
1015
1007
  if (!repoId) continue;
1016
1008
 
1017
1009
  const repo: Repository = await gitDataProviderInstance.GetGitRepoFromRepoId(repoId);
1010
+ const repoApiUrl = this.buildRepoApiUrl(repo);
1018
1011
  const resourceRepository: ResourceRepository = {
1019
1012
  repoName: repo.name,
1020
1013
  repoSha1: resourceRepo.version,
1021
- url: repo.url,
1014
+ url: repoApiUrl,
1022
1015
  };
1023
1016
  const key = String(repoId);
1024
1017
  if (!resourceRepositoriesById.has(key)) {
@@ -52,14 +52,9 @@ describe('PipelinesDataProvider', () => {
52
52
  // Create test method to access private method
53
53
  const invokeIsMatchingPipeline = (
54
54
  fromPipeline: PipelineRun,
55
- targetPipeline: PipelineRun,
56
- searchPrevPipelineFromDifferentCommit: boolean
55
+ targetPipeline: PipelineRun
57
56
  ): boolean => {
58
- return (pipelinesDataProvider as any).isMatchingPipeline(
59
- fromPipeline,
60
- targetPipeline,
61
- searchPrevPipelineFromDifferentCommit
62
- );
57
+ return (pipelinesDataProvider as any).isMatchingPipeline(fromPipeline, targetPipeline);
63
58
  };
64
59
 
65
60
  it('should return false when repository IDs are different', () => {
@@ -93,13 +88,13 @@ describe('PipelinesDataProvider', () => {
93
88
  } as unknown as PipelineRun;
94
89
 
95
90
  // Act
96
- const result = invokeIsMatchingPipeline(fromPipeline, targetPipeline, false);
91
+ const result = invokeIsMatchingPipeline(fromPipeline, targetPipeline);
97
92
 
98
93
  // Assert
99
94
  expect(result).toBe(false);
100
95
  });
101
96
 
102
- it('should return true when versions are the same and searchPrevPipelineFromDifferentCommit is false', () => {
97
+ it('should return true when versions are the same and refNames match', () => {
103
98
  // Arrange
104
99
  const fromPipeline = {
105
100
  resources: {
@@ -130,49 +125,12 @@ describe('PipelinesDataProvider', () => {
130
125
  } as unknown as PipelineRun;
131
126
 
132
127
  // Act
133
- const result = invokeIsMatchingPipeline(fromPipeline, targetPipeline, false);
128
+ const result = invokeIsMatchingPipeline(fromPipeline, targetPipeline);
134
129
 
135
130
  // Assert
136
131
  expect(result).toBe(true);
137
132
  });
138
133
 
139
- it('should return false when versions are the same and searchPrevPipelineFromDifferentCommit is true', () => {
140
- // Arrange
141
- const fromPipeline = {
142
- resources: {
143
- repositories: {
144
- '0': {
145
- self: {
146
- repository: { id: 'repo1' },
147
- version: 'v1',
148
- refName: 'refs/heads/main',
149
- },
150
- },
151
- },
152
- },
153
- } as unknown as PipelineRun;
154
-
155
- const targetPipeline = {
156
- resources: {
157
- repositories: {
158
- '0': {
159
- self: {
160
- repository: { id: 'repo1' },
161
- version: 'v1',
162
- refName: 'refs/heads/main',
163
- },
164
- },
165
- },
166
- },
167
- } as unknown as PipelineRun;
168
-
169
- // Act
170
- const result = invokeIsMatchingPipeline(fromPipeline, targetPipeline, true);
171
-
172
- // Assert
173
- expect(result).toBe(false);
174
- });
175
-
176
134
  it('should return true when refNames match but versions differ', () => {
177
135
  // Arrange
178
136
  const fromPipeline = {
@@ -204,7 +162,7 @@ describe('PipelinesDataProvider', () => {
204
162
  } as unknown as PipelineRun;
205
163
 
206
164
  // Act
207
- const result = invokeIsMatchingPipeline(fromPipeline, targetPipeline, true);
165
+ const result = invokeIsMatchingPipeline(fromPipeline, targetPipeline);
208
166
 
209
167
  // Assert
210
168
  expect(result).toBe(true);
@@ -237,7 +195,7 @@ describe('PipelinesDataProvider', () => {
237
195
  } as unknown as PipelineRun;
238
196
 
239
197
  // Act
240
- const result = invokeIsMatchingPipeline(fromPipeline, targetPipeline, false);
198
+ const result = invokeIsMatchingPipeline(fromPipeline, targetPipeline);
241
199
 
242
200
  // Assert
243
201
  expect(result).toBe(true);
@@ -270,7 +228,7 @@ describe('PipelinesDataProvider', () => {
270
228
  },
271
229
  } as unknown as PipelineRun;
272
230
 
273
- const result = invokeIsMatchingPipeline(fromPipeline, targetPipeline, false);
231
+ const result = invokeIsMatchingPipeline(fromPipeline, targetPipeline);
274
232
  expect(result).toBe(true);
275
233
  });
276
234
  });
@@ -1176,8 +1134,10 @@ describe('PipelinesDataProvider', () => {
1176
1134
  } as unknown as PipelineRun;
1177
1135
 
1178
1136
  const mockRepo = {
1137
+ id: 'repo-123',
1179
1138
  name: 'MyRepo',
1180
1139
  url: 'https://dev.azure.com/org/project/_git/MyRepo',
1140
+ // no project field → falls back to repo.url
1181
1141
  };
1182
1142
 
1183
1143
  const mockGitDataProvider = {
@@ -1199,6 +1159,78 @@ describe('PipelinesDataProvider', () => {
1199
1159
  });
1200
1160
  });
1201
1161
 
1162
+ it('should use project-name URL when repo response includes project.name (on-prem TFS WI fix)', async () => {
1163
+ // On-prem TFS canonicalizes repo.url to a project-UUID form, but commitsbatch only
1164
+ // resolves workItems when the URL uses the project name. This test verifies the URL
1165
+ // is rewritten to project-name form when project.name is available.
1166
+ const inPipeline = {
1167
+ resources: {
1168
+ repositories: {
1169
+ self: {
1170
+ repository: { id: 'b671b0fa-1111-2222-3333-444444444444', type: 'TfsGit' },
1171
+ version: 'abc123',
1172
+ },
1173
+ },
1174
+ },
1175
+ } as unknown as PipelineRun;
1176
+
1177
+ const mockRepo = {
1178
+ id: 'b671b0fa-1111-2222-3333-444444444444',
1179
+ name: 'eden1',
1180
+ url: 'http://elis-tfs:8080/tfs/ElisraCollection/5d662fe5-uuid/_apis/git/repositories/b671b0fa-1111-2222-3333-444444444444',
1181
+ project: { id: '5d662fe5-uuid', name: 'TestProject-CMMI' },
1182
+ };
1183
+
1184
+ const mockGitDataProvider = {
1185
+ GetGitRepoFromRepoId: jest.fn().mockResolvedValue(mockRepo),
1186
+ } as unknown as GitDataProvider;
1187
+
1188
+ // Act
1189
+ const result = await pipelinesDataProvider.getPipelineResourceRepositoriesFromObject(
1190
+ inPipeline,
1191
+ mockGitDataProvider
1192
+ );
1193
+
1194
+ // Assert — URL must use project NAME, not UUID
1195
+ expect(result).toHaveLength(1);
1196
+ expect((result as any[])[0].url).toContain('TestProject-CMMI');
1197
+ expect((result as any[])[0].url).not.toContain('5d662fe5-uuid');
1198
+ expect((result as any[])[0].url).toContain('b671b0fa-1111-2222-3333-444444444444');
1199
+ });
1200
+
1201
+ it('should encode project name when rebuilding repository API URL', async () => {
1202
+ const inPipeline = {
1203
+ resources: {
1204
+ repositories: {
1205
+ self: {
1206
+ repository: { id: 'repo-123', type: 'TfsGit' },
1207
+ version: 'abc123',
1208
+ },
1209
+ },
1210
+ },
1211
+ } as unknown as PipelineRun;
1212
+
1213
+ const mockRepo = {
1214
+ id: 'repo-123',
1215
+ name: 'MyRepo',
1216
+ url: 'http://elis-tfs:8080/tfs/ElisraCollection/project-id/_apis/git/repositories/repo-123',
1217
+ project: { id: 'project-id', name: 'Project With Spaces' },
1218
+ };
1219
+
1220
+ const mockGitDataProvider = {
1221
+ GetGitRepoFromRepoId: jest.fn().mockResolvedValue(mockRepo),
1222
+ } as unknown as GitDataProvider;
1223
+
1224
+ const result = await pipelinesDataProvider.getPipelineResourceRepositoriesFromObject(
1225
+ inPipeline,
1226
+ mockGitDataProvider
1227
+ );
1228
+
1229
+ expect((result as any[])[0].url).toBe(
1230
+ 'https://dev.azure.com/orgname/Project%20With%20Spaces/_apis/git/repositories/repo-123'
1231
+ );
1232
+ });
1233
+
1202
1234
  it('should skip non-azureReposGit repositories', async () => {
1203
1235
  // Arrange
1204
1236
  const inPipeline = {
@@ -1270,8 +1302,7 @@ describe('PipelinesDataProvider', () => {
1270
1302
  teamProject,
1271
1303
  pipelineId,
1272
1304
  toPipelineRunId,
1273
- targetPipeline,
1274
- false
1305
+ targetPipeline
1275
1306
  );
1276
1307
 
1277
1308
  // Assert
@@ -1315,8 +1346,7 @@ describe('PipelinesDataProvider', () => {
1315
1346
  teamProject,
1316
1347
  pipelineId,
1317
1348
  toPipelineRunId,
1318
- targetPipeline,
1319
- true
1349
+ targetPipeline
1320
1350
  );
1321
1351
  expect(res).toBe(99);
1322
1352
  });
@@ -1342,7 +1372,6 @@ describe('PipelinesDataProvider', () => {
1342
1372
  pipelineId,
1343
1373
  toPipelineRunId,
1344
1374
  targetPipeline,
1345
- false,
1346
1375
  'Deploy'
1347
1376
  );
1348
1377
 
@@ -1368,8 +1397,7 @@ describe('PipelinesDataProvider', () => {
1368
1397
  teamProject,
1369
1398
  pipelineId,
1370
1399
  toPipelineRunId,
1371
- targetPipeline,
1372
- false
1400
+ targetPipeline
1373
1401
  );
1374
1402
 
1375
1403
  expect(res).toBeUndefined();
@@ -1430,8 +1458,7 @@ describe('PipelinesDataProvider', () => {
1430
1458
  teamProject,
1431
1459
  pipelineId,
1432
1460
  toPipelineRunId,
1433
- targetPipeline,
1434
- true
1461
+ targetPipeline
1435
1462
  );
1436
1463
 
1437
1464
  // Assert
@@ -1453,8 +1480,7 @@ describe('PipelinesDataProvider', () => {
1453
1480
  'project1',
1454
1481
  '123',
1455
1482
  100,
1456
- targetPipelineRun,
1457
- true
1483
+ targetPipelineRun
1458
1484
  );
1459
1485
 
1460
1486
  expect(result).toBe(80);
@@ -1477,8 +1503,7 @@ describe('PipelinesDataProvider', () => {
1477
1503
  'project1',
1478
1504
  '123',
1479
1505
  100,
1480
- targetPipelineRun,
1481
- true
1506
+ targetPipelineRun
1482
1507
  );
1483
1508
 
1484
1509
  expect(result).toBe(90);
@@ -1503,8 +1528,7 @@ describe('PipelinesDataProvider', () => {
1503
1528
  'project1',
1504
1529
  '123',
1505
1530
  100,
1506
- targetPipelineRun,
1507
- true
1531
+ targetPipelineRun
1508
1532
  );
1509
1533
 
1510
1534
  expect(result).toBe(95);
@@ -1533,8 +1557,7 @@ describe('PipelinesDataProvider', () => {
1533
1557
  'project1',
1534
1558
  '123',
1535
1559
  100,
1536
- targetPipelineRun,
1537
- true
1560
+ targetPipelineRun
1538
1561
  );
1539
1562
 
1540
1563
  const url = (TFSServices.getItemContentWithHeaders as jest.Mock).mock.calls[0][0];
@@ -1549,7 +1572,7 @@ describe('PipelinesDataProvider', () => {
1549
1572
  (TFSServices.getItemContentWithHeaders as jest.Mock).mockRejectedValueOnce(new Error('same branch failed'));
1550
1573
 
1551
1574
  await expect(
1552
- pipelinesDataProvider.findPreviousPipeline('project1', '123', 100, targetPipelineRun, true)
1575
+ pipelinesDataProvider.findPreviousPipeline('project1', '123', 100, targetPipelineRun)
1553
1576
  ).rejects.toThrow('same branch failed');
1554
1577
 
1555
1578
  expect(TFSServices.getItemContentWithHeaders).toHaveBeenCalledTimes(1);
@@ -1568,7 +1591,7 @@ describe('PipelinesDataProvider', () => {
1568
1591
  .mockRejectedValueOnce(new Error('fallback failed'));
1569
1592
 
1570
1593
  await expect(
1571
- pipelinesDataProvider.findPreviousPipeline('project1', '123', 100, targetPipelineRun, true)
1594
+ pipelinesDataProvider.findPreviousPipeline('project1', '123', 100, targetPipelineRun)
1572
1595
  ).rejects.toThrow('fallback failed');
1573
1596
 
1574
1597
  expect(TFSServices.getItemContentWithHeaders).toHaveBeenCalledTimes(2);
@@ -1586,7 +1609,7 @@ describe('PipelinesDataProvider', () => {
1586
1609
  .mockRejectedValueOnce(new Error('build page failed'));
1587
1610
 
1588
1611
  await expect(
1589
- pipelinesDataProvider.findPreviousPipeline('project1', '123', 100, targetPipelineRun, true)
1612
+ pipelinesDataProvider.findPreviousPipeline('project1', '123', 100, targetPipelineRun)
1590
1613
  ).rejects.toThrow('build page failed');
1591
1614
  });
1592
1615
 
@@ -1599,7 +1622,7 @@ describe('PipelinesDataProvider', () => {
1599
1622
  );
1600
1623
 
1601
1624
  await expect(
1602
- pipelinesDataProvider.findPreviousPipeline('project1', '123', 100, targetPipelineRun, true)
1625
+ pipelinesDataProvider.findPreviousPipeline('project1', '123', 100, targetPipelineRun)
1603
1626
  ).rejects.toThrow('Pipeline discovery exceeded 50 pages');
1604
1627
  });
1605
1628
  });