@elisra-devops/docgen-data-provider 1.113.0 → 1.116.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.
- package/.github/workflows/release.yml +15 -6
- package/bin/modules/GitDataProvider.js +9 -6
- package/bin/modules/GitDataProvider.js.map +1 -1
- package/bin/modules/PipelinesDataProvider.d.ts +4 -2
- package/bin/modules/PipelinesDataProvider.js +11 -5
- package/bin/modules/PipelinesDataProvider.js.map +1 -1
- package/bin/tests/modules/gitDataProvider.test.js +58 -0
- package/bin/tests/modules/gitDataProvider.test.js.map +1 -1
- package/bin/tests/modules/pipelineDataProvider.test.js +28 -0
- package/bin/tests/modules/pipelineDataProvider.test.js.map +1 -1
- package/package.json +1 -1
- package/src/modules/GitDataProvider.ts +8 -3
- package/src/modules/PipelinesDataProvider.ts +11 -5
- package/src/tests/modules/gitDataProvider.test.ts +84 -0
- package/src/tests/modules/pipelineDataProvider.test.ts +31 -0
|
@@ -492,11 +492,16 @@ export default class GitDataProvider {
|
|
|
492
492
|
//First fetch the repo web url
|
|
493
493
|
if (targetRepo.url) {
|
|
494
494
|
const repoData = await TFSServices.getItemContent(targetRepo.url, this.token);
|
|
495
|
-
const repoWebUrl = repoData
|
|
496
|
-
const targetRepoProjectId = repoData
|
|
495
|
+
const repoWebUrl = repoData?._links?.web?.href;
|
|
496
|
+
const targetRepoProjectId = repoData?.project?.id;
|
|
497
497
|
if (repoWebUrl) {
|
|
498
498
|
targetRepo['url'] = repoWebUrl;
|
|
499
|
-
|
|
499
|
+
}
|
|
500
|
+
targetRepo['projectId'] = targetRepoProjectId ?? teamProject;
|
|
501
|
+
if (!targetRepoProjectId) {
|
|
502
|
+
logger.warn(
|
|
503
|
+
`getItemsForPipelineRange: repo response missing project.id for ${targetRepo.url}; falling back to pipeline teamProject=${teamProject}`
|
|
504
|
+
);
|
|
500
505
|
}
|
|
501
506
|
}
|
|
502
507
|
//Then extend the commit information with the related WIs
|
|
@@ -219,13 +219,15 @@ export default class PipelinesDataProvider {
|
|
|
219
219
|
/**
|
|
220
220
|
* Extracts the primary repository from a pipeline run details response.
|
|
221
221
|
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
222
|
+
* The ADO Pipelines Runs API returns resources.repositories as a named-key object where
|
|
223
|
+
* 'self' is the pipeline's own checkout (e.g. { self: {...}, AppRepo: {...} }). Some older
|
|
224
|
+
* in-process representations wrap it as an array ({ 0: { self: {...} } }) and classic/designer
|
|
225
|
+
* pipelines use __designer_repo.
|
|
224
226
|
*/
|
|
225
227
|
private getPrimaryPipelineRepository(pipeline: any): any {
|
|
226
228
|
const repositories = pipeline?.resources?.repositories;
|
|
227
229
|
if (!repositories) return undefined;
|
|
228
|
-
return repositories[0]?.self || repositories.__designer_repo;
|
|
230
|
+
return repositories.self || repositories[0]?.self || repositories.__designer_repo;
|
|
229
231
|
}
|
|
230
232
|
|
|
231
233
|
/**
|
|
@@ -482,9 +484,13 @@ export default class PipelinesDataProvider {
|
|
|
482
484
|
}
|
|
483
485
|
|
|
484
486
|
const fromRepo =
|
|
485
|
-
fromPipeline.resources.repositories
|
|
487
|
+
fromPipeline.resources.repositories.self ||
|
|
488
|
+
fromPipeline.resources.repositories[0]?.self ||
|
|
489
|
+
fromPipeline.resources.repositories.__designer_repo;
|
|
486
490
|
const targetRepo =
|
|
487
|
-
targetPipeline.resources.repositories
|
|
491
|
+
targetPipeline.resources.repositories.self ||
|
|
492
|
+
targetPipeline.resources.repositories[0]?.self ||
|
|
493
|
+
targetPipeline.resources.repositories.__designer_repo;
|
|
488
494
|
|
|
489
495
|
if (!fromRepo?.repository?.id || !targetRepo?.repository?.id) {
|
|
490
496
|
return false;
|
|
@@ -1334,6 +1334,90 @@ describe('GitDataProvider - getItemsForPipelineRange', () => {
|
|
|
1334
1334
|
// Assert - should only have 1 work item (no duplicates)
|
|
1335
1335
|
expect(result.commitChangesArray).toHaveLength(1);
|
|
1336
1336
|
});
|
|
1337
|
+
|
|
1338
|
+
it('cross-project repo: should resolve WIs using teamProject fallback when _links.web.href is missing', async () => {
|
|
1339
|
+
// Real ADO cross-project resource-pipeline repos can return repo metadata without
|
|
1340
|
+
// _links.web.href. Previously this left targetRepo.projectId undefined, causing
|
|
1341
|
+
// GetWorkItem to call /{orgUrl}undefined/... and silently swallow the 404.
|
|
1342
|
+
const pipelineTeamProject = 'TestProject-CMMI';
|
|
1343
|
+
const extendedCommits = [
|
|
1344
|
+
{
|
|
1345
|
+
commit: {
|
|
1346
|
+
commitId: 'abc1234',
|
|
1347
|
+
workItems: [{ id: 42 }],
|
|
1348
|
+
committer: { date: '2024-01-01', name: 'Dev' },
|
|
1349
|
+
comment: 'cross-project fix',
|
|
1350
|
+
},
|
|
1351
|
+
},
|
|
1352
|
+
];
|
|
1353
|
+
const targetRepo = { url: 'https://dev.azure.com/org/OtherProject/_apis/git/repositories/repo-id' };
|
|
1354
|
+
const addedWorkItemByIdSet = new Set<number>();
|
|
1355
|
+
|
|
1356
|
+
// Repo lives in a different project; response has no _links.web.href
|
|
1357
|
+
const mockRepoDataNoCrossLink = {
|
|
1358
|
+
project: { id: 'cross-project-id' },
|
|
1359
|
+
// no _links at all
|
|
1360
|
+
};
|
|
1361
|
+
const mockPopulatedWI = { id: 42, fields: { 'System.Title': 'Cross-project WI' } };
|
|
1362
|
+
|
|
1363
|
+
(TFSServices.getItemContent as jest.Mock)
|
|
1364
|
+
.mockResolvedValueOnce(mockRepoDataNoCrossLink) // repo metadata call
|
|
1365
|
+
.mockResolvedValueOnce(mockPopulatedWI); // GetWorkItem call
|
|
1366
|
+
|
|
1367
|
+
const result = await gitDataProvider.getItemsForPipelineRange(
|
|
1368
|
+
pipelineTeamProject,
|
|
1369
|
+
extendedCommits,
|
|
1370
|
+
targetRepo,
|
|
1371
|
+
addedWorkItemByIdSet
|
|
1372
|
+
);
|
|
1373
|
+
|
|
1374
|
+
expect(result.commitChangesArray).toHaveLength(1);
|
|
1375
|
+
expect(result.commitChangesArray[0].workItem.id).toBe(42);
|
|
1376
|
+
// GetWorkItem should have been called with the repo's own projectId from repoData.project.id
|
|
1377
|
+
expect(TFSServices.getItemContent).toHaveBeenCalledWith(
|
|
1378
|
+
expect.stringContaining('cross-project-id'),
|
|
1379
|
+
expect.any(String)
|
|
1380
|
+
);
|
|
1381
|
+
});
|
|
1382
|
+
|
|
1383
|
+
it('cross-project repo: should fall back to teamProject when repoData.project.id is also missing', async () => {
|
|
1384
|
+
const pipelineTeamProject = 'TestProject-CMMI';
|
|
1385
|
+
const extendedCommits = [
|
|
1386
|
+
{
|
|
1387
|
+
commit: {
|
|
1388
|
+
commitId: 'def5678',
|
|
1389
|
+
workItems: [{ id: 99 }],
|
|
1390
|
+
committer: { date: '2024-01-02', name: 'Dev' },
|
|
1391
|
+
comment: 'fallback test',
|
|
1392
|
+
},
|
|
1393
|
+
},
|
|
1394
|
+
];
|
|
1395
|
+
const targetRepo = { url: 'https://dev.azure.com/org/SomeProject/_apis/git/repositories/repo-id2' };
|
|
1396
|
+
const addedWorkItemByIdSet = new Set<number>();
|
|
1397
|
+
|
|
1398
|
+
// Response with neither _links.web.href nor project.id
|
|
1399
|
+
const mockRepoDataEmpty = {};
|
|
1400
|
+
const mockPopulatedWI = { id: 99, fields: { 'System.Title': 'Fallback WI' } };
|
|
1401
|
+
|
|
1402
|
+
(TFSServices.getItemContent as jest.Mock)
|
|
1403
|
+
.mockResolvedValueOnce(mockRepoDataEmpty)
|
|
1404
|
+
.mockResolvedValueOnce(mockPopulatedWI);
|
|
1405
|
+
|
|
1406
|
+
const result = await gitDataProvider.getItemsForPipelineRange(
|
|
1407
|
+
pipelineTeamProject,
|
|
1408
|
+
extendedCommits,
|
|
1409
|
+
targetRepo,
|
|
1410
|
+
addedWorkItemByIdSet
|
|
1411
|
+
);
|
|
1412
|
+
|
|
1413
|
+
expect(result.commitChangesArray).toHaveLength(1);
|
|
1414
|
+
expect(result.commitChangesArray[0].workItem.id).toBe(99);
|
|
1415
|
+
// GetWorkItem should have been called with the pipeline teamProject as fallback
|
|
1416
|
+
expect(TFSServices.getItemContent).toHaveBeenCalledWith(
|
|
1417
|
+
expect.stringContaining(pipelineTeamProject),
|
|
1418
|
+
expect.any(String)
|
|
1419
|
+
);
|
|
1420
|
+
});
|
|
1337
1421
|
});
|
|
1338
1422
|
|
|
1339
1423
|
describe('GitDataProvider - GetItemsInPullRequestRange', () => {
|
|
@@ -242,6 +242,37 @@ describe('PipelinesDataProvider', () => {
|
|
|
242
242
|
// Assert
|
|
243
243
|
expect(result).toBe(true);
|
|
244
244
|
});
|
|
245
|
+
|
|
246
|
+
it('should resolve self from real ADO Runs API format (repositories.self, not repositories[0].self)', () => {
|
|
247
|
+
// Real ADO Pipelines Runs API returns repositories as a named-key object:
|
|
248
|
+
// { self: {...}, DevOpsTemplates: {...} } — NOT an array.
|
|
249
|
+
const fromPipeline = {
|
|
250
|
+
resources: {
|
|
251
|
+
repositories: {
|
|
252
|
+
self: {
|
|
253
|
+
repository: { id: 'repo1' },
|
|
254
|
+
version: 'v1',
|
|
255
|
+
refName: 'refs/heads/main',
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
} as unknown as PipelineRun;
|
|
260
|
+
|
|
261
|
+
const targetPipeline = {
|
|
262
|
+
resources: {
|
|
263
|
+
repositories: {
|
|
264
|
+
self: {
|
|
265
|
+
repository: { id: 'repo1' },
|
|
266
|
+
version: 'v2',
|
|
267
|
+
refName: 'refs/heads/main',
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
} as unknown as PipelineRun;
|
|
272
|
+
|
|
273
|
+
const result = invokeIsMatchingPipeline(fromPipeline, targetPipeline, false);
|
|
274
|
+
expect(result).toBe(true);
|
|
275
|
+
});
|
|
245
276
|
});
|
|
246
277
|
|
|
247
278
|
describe('getPipelineRunDetails', () => {
|