Refactored to provide better API for Builds

Signed-off-by: Andre Wanlin <awanlin@rapidrtc.com>
This commit is contained in:
Andre Wanlin
2021-11-13 11:25:46 -06:00
parent a77526afcd
commit 868f1dae99
7 changed files with 801 additions and 406 deletions
+4 -3
View File
@@ -4,6 +4,8 @@
```ts
import { Build } from 'azure-devops-node-api/interfaces/BuildInterfaces';
import { BuildDefinitionReference } from 'azure-devops-node-api/interfaces/BuildInterfaces';
import { BuildRun } from '@backstage/plugin-azure-devops-common';
import { Config } from '@backstage/config';
import { DashboardPullRequest } from '@backstage/plugin-azure-devops-common';
import express from 'express';
@@ -25,9 +27,8 @@ export class AzureDevOpsApi {
// (undocumented)
getBuildList(
projectName: string,
definitions?: number[],
repoId?: string,
top?: number,
repoId: string,
top: number,
): Promise<Build[]>;
// (undocumented)
getDashboardPullRequests(
@@ -29,277 +29,510 @@ import {
GitPullRequest,
GitRepository,
} from 'azure-devops-node-api/interfaces/GitInterfaces';
import { mappedPullRequest, mappedRepoBuild } from './AzureDevOpsApi';
import {
mappedBuildRun,
mappedPullRequest,
mappedRepoBuild,
} from './AzureDevOpsApi';
import { IdentityRef } from 'azure-devops-node-api/interfaces/common/VSSInterfaces';
describe('AzureDevOpsApi', () => {
describe('mappedRepoBuild', () => {
it('should return RepoBuild from Build', () => {
const inputBuildDefinition: DefinitionReference = {
name: 'My Build Definition',
};
describe('mappedRepoBuild happy path', () => {
it('should return RepoBuild from Build', () => {
const inputBuildDefinition: DefinitionReference = {
name: 'My Build Definition',
};
const inputLinks: any = {
web: {
href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
},
};
const inputLinks: any = {
web: {
href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
},
};
const inputIdentityRef: IdentityRef = {
displayName: 'Jane Doe',
uniqueName: 'DOMAIN\\jdoe',
};
const inputIdentityRef: IdentityRef = {
displayName: 'Jane Doe',
uniqueName: 'DOMAIN\\jdoe',
};
const inputBuild: Build = {
id: 1,
buildNumber: 'Build-1',
status: BuildStatus.Completed,
result: BuildResult.Succeeded,
queueTime: new Date('2020-09-12T06:10:23.932Z'),
startTime: new Date('2020-09-12T06:15:23.932Z'),
finishTime: new Date('2020-09-12T06:20:23.932Z'),
sourceBranch: 'refs/heads/develop',
sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c',
definition: inputBuildDefinition,
_links: inputLinks,
requestedFor: inputIdentityRef,
};
const inputBuild: Build = {
id: 1,
buildNumber: 'Build-1',
status: BuildStatus.Completed,
result: BuildResult.Succeeded,
queueTime: new Date('2020-09-12T06:10:23.932Z'),
startTime: new Date('2020-09-12T06:15:23.932Z'),
finishTime: new Date('2020-09-12T06:20:23.932Z'),
sourceBranch: 'refs/heads/develop',
sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c',
definition: inputBuildDefinition,
_links: inputLinks,
requestedFor: inputIdentityRef,
};
const outputRepoBuild: RepoBuild = {
id: 1,
title: 'My Build Definition - Build-1',
link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
status: BuildStatus.Completed,
result: BuildResult.Succeeded,
queueTime: '2020-09-12T06:10:23.932Z',
startTime: '2020-09-12T06:15:23.932Z',
finishTime: '2020-09-12T06:20:23.932Z',
source: 'refs/heads/develop (f4f78b31)',
uniqueName: 'DOMAIN\\jdoe',
};
const outputRepoBuild: RepoBuild = {
id: 1,
title: 'My Build Definition - Build-1',
link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
status: BuildStatus.Completed,
result: BuildResult.Succeeded,
queueTime: '2020-09-12T06:10:23.932Z',
startTime: '2020-09-12T06:15:23.932Z',
finishTime: '2020-09-12T06:20:23.932Z',
source: 'refs/heads/develop (f4f78b31)',
uniqueName: 'DOMAIN\\jdoe',
};
expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild);
expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild);
});
});
});
describe('mappedRepoBuild with no Build definition name', () => {
it('should return RepoBuild with only Build Number for title', () => {
const inputLinks: any = {
web: {
href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
},
};
describe('mappedRepoBuild with no Build definition name', () => {
it('should return RepoBuild with only Build Number for title', () => {
const inputLinks: any = {
web: {
href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
},
};
const inputIdentityRef: IdentityRef = {
displayName: 'Jane Doe',
uniqueName: 'DOMAIN\\jdoe',
};
const inputIdentityRef: IdentityRef = {
displayName: 'Jane Doe',
uniqueName: 'DOMAIN\\jdoe',
};
const inputBuild: Build = {
id: 1,
buildNumber: 'Build-1',
status: BuildStatus.Completed,
result: BuildResult.Succeeded,
queueTime: new Date('2020-09-12T06:10:23.932Z'),
startTime: new Date('2020-09-12T06:15:23.932Z'),
finishTime: new Date('2020-09-12T06:20:23.932Z'),
sourceBranch: 'refs/heads/develop',
sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c',
definition: undefined,
_links: inputLinks,
requestedFor: inputIdentityRef,
};
const inputBuild: Build = {
id: 1,
buildNumber: 'Build-1',
status: BuildStatus.Completed,
result: BuildResult.Succeeded,
queueTime: new Date('2020-09-12T06:10:23.932Z'),
startTime: new Date('2020-09-12T06:15:23.932Z'),
finishTime: new Date('2020-09-12T06:20:23.932Z'),
sourceBranch: 'refs/heads/develop',
sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c',
definition: undefined,
_links: inputLinks,
requestedFor: inputIdentityRef,
};
const outputRepoBuild: RepoBuild = {
id: 1,
title: 'Build-1',
link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
status: BuildStatus.Completed,
result: BuildResult.Succeeded,
queueTime: '2020-09-12T06:10:23.932Z',
startTime: '2020-09-12T06:15:23.932Z',
finishTime: '2020-09-12T06:20:23.932Z',
source: 'refs/heads/develop (f4f78b31)',
uniqueName: 'DOMAIN\\jdoe',
};
const outputRepoBuild: RepoBuild = {
id: 1,
title: 'Build-1',
link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
status: BuildStatus.Completed,
result: BuildResult.Succeeded,
queueTime: '2020-09-12T06:10:23.932Z',
startTime: '2020-09-12T06:15:23.932Z',
finishTime: '2020-09-12T06:20:23.932Z',
source: 'refs/heads/develop (f4f78b31)',
uniqueName: 'DOMAIN\\jdoe',
};
expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild);
expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild);
});
});
});
describe('mappedRepoBuild with undefined status', () => {
it('should return BuildStatus of None for status', () => {
const inputLinks: any = {
web: {
href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
},
};
describe('mappedRepoBuild with undefined status', () => {
it('should return BuildStatus of None for status', () => {
const inputLinks: any = {
web: {
href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
},
};
const inputIdentityRef: IdentityRef = {
displayName: 'Jane Doe',
uniqueName: 'DOMAIN\\jdoe',
};
const inputIdentityRef: IdentityRef = {
displayName: 'Jane Doe',
uniqueName: 'DOMAIN\\jdoe',
};
const inputBuild: Build = {
id: 1,
buildNumber: 'Build-1',
status: undefined,
result: BuildResult.Succeeded,
queueTime: new Date('2020-09-12T06:10:23.932Z'),
startTime: new Date('2020-09-12T06:15:23.932Z'),
finishTime: new Date('2020-09-12T06:20:23.932Z'),
sourceBranch: 'refs/heads/develop',
sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c',
definition: undefined,
_links: inputLinks,
requestedFor: inputIdentityRef,
};
const inputBuild: Build = {
id: 1,
buildNumber: 'Build-1',
status: undefined,
result: BuildResult.Succeeded,
queueTime: new Date('2020-09-12T06:10:23.932Z'),
startTime: new Date('2020-09-12T06:15:23.932Z'),
finishTime: new Date('2020-09-12T06:20:23.932Z'),
sourceBranch: 'refs/heads/develop',
sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c',
definition: undefined,
_links: inputLinks,
requestedFor: inputIdentityRef,
};
const outputRepoBuild: RepoBuild = {
id: 1,
title: 'Build-1',
link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
status: BuildStatus.None,
result: BuildResult.Succeeded,
queueTime: '2020-09-12T06:10:23.932Z',
startTime: '2020-09-12T06:15:23.932Z',
finishTime: '2020-09-12T06:20:23.932Z',
source: 'refs/heads/develop (f4f78b31)',
uniqueName: 'DOMAIN\\jdoe',
};
const outputRepoBuild: RepoBuild = {
id: 1,
title: 'Build-1',
link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
status: BuildStatus.None,
result: BuildResult.Succeeded,
queueTime: '2020-09-12T06:10:23.932Z',
startTime: '2020-09-12T06:15:23.932Z',
finishTime: '2020-09-12T06:20:23.932Z',
source: 'refs/heads/develop (f4f78b31)',
uniqueName: 'DOMAIN\\jdoe',
};
expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild);
expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild);
});
});
});
describe('mappedRepoBuild with undefined result', () => {
it('should return BuildResult of None for result', () => {
const inputLinks: any = {
web: {
href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
},
};
describe('mappedRepoBuild with undefined result', () => {
it('should return BuildResult of None for result', () => {
const inputLinks: any = {
web: {
href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
},
};
const inputIdentityRef: IdentityRef = {
displayName: 'Jane Doe',
uniqueName: 'DOMAIN\\jdoe',
};
const inputIdentityRef: IdentityRef = {
displayName: 'Jane Doe',
uniqueName: 'DOMAIN\\jdoe',
};
const inputBuild: Build = {
id: 1,
buildNumber: 'Build-1',
status: BuildStatus.InProgress,
result: undefined,
queueTime: new Date('2020-09-12T06:10:23.932Z'),
startTime: new Date('2020-09-12T06:15:23.932Z'),
finishTime: new Date('2020-09-12T06:20:23.932Z'),
sourceBranch: 'refs/heads/develop',
sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c',
definition: undefined,
_links: inputLinks,
requestedFor: inputIdentityRef,
};
const inputBuild: Build = {
id: 1,
buildNumber: 'Build-1',
status: BuildStatus.InProgress,
result: undefined,
queueTime: new Date('2020-09-12T06:10:23.932Z'),
startTime: new Date('2020-09-12T06:15:23.932Z'),
finishTime: new Date('2020-09-12T06:20:23.932Z'),
sourceBranch: 'refs/heads/develop',
sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c',
definition: undefined,
_links: inputLinks,
requestedFor: inputIdentityRef,
};
const outputRepoBuild: RepoBuild = {
id: 1,
title: 'Build-1',
link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
status: BuildStatus.InProgress,
result: BuildResult.None,
queueTime: '2020-09-12T06:10:23.932Z',
startTime: '2020-09-12T06:15:23.932Z',
finishTime: '2020-09-12T06:20:23.932Z',
source: 'refs/heads/develop (f4f78b31)',
uniqueName: 'DOMAIN\\jdoe',
};
const outputRepoBuild: RepoBuild = {
id: 1,
title: 'Build-1',
link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
status: BuildStatus.InProgress,
result: BuildResult.None,
queueTime: '2020-09-12T06:10:23.932Z',
startTime: '2020-09-12T06:15:23.932Z',
finishTime: '2020-09-12T06:20:23.932Z',
source: 'refs/heads/develop (f4f78b31)',
uniqueName: 'DOMAIN\\jdoe',
};
expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild);
expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild);
});
});
});
describe('mappedRepoBuild with undefined link', () => {
it('should return empty string for link', () => {
const inputIdentityRef: IdentityRef = {
displayName: 'Jane Doe',
uniqueName: 'DOMAIN\\jdoe',
};
describe('mappedRepoBuild with undefined link', () => {
it('should return empty string for link', () => {
const inputIdentityRef: IdentityRef = {
displayName: 'Jane Doe',
uniqueName: 'DOMAIN\\jdoe',
};
const inputBuild: Build = {
id: 1,
buildNumber: 'Build-1',
status: BuildStatus.InProgress,
result: undefined,
queueTime: new Date('2020-09-12T06:10:23.932Z'),
startTime: new Date('2020-09-12T06:15:23.932Z'),
finishTime: new Date('2020-09-12T06:20:23.932Z'),
sourceBranch: 'refs/heads/develop',
sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c',
definition: undefined,
_links: undefined,
requestedFor: inputIdentityRef,
};
const inputBuild: Build = {
id: 1,
buildNumber: 'Build-1',
status: BuildStatus.InProgress,
result: undefined,
queueTime: new Date('2020-09-12T06:10:23.932Z'),
startTime: new Date('2020-09-12T06:15:23.932Z'),
finishTime: new Date('2020-09-12T06:20:23.932Z'),
sourceBranch: 'refs/heads/develop',
sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c',
definition: undefined,
_links: undefined,
requestedFor: inputIdentityRef,
};
const outputRepoBuild: RepoBuild = {
id: 1,
title: 'Build-1',
link: '',
status: BuildStatus.InProgress,
result: BuildResult.None,
queueTime: '2020-09-12T06:10:23.932Z',
startTime: '2020-09-12T06:15:23.932Z',
finishTime: '2020-09-12T06:20:23.932Z',
source: 'refs/heads/develop (f4f78b31)',
uniqueName: 'DOMAIN\\jdoe',
};
const outputRepoBuild: RepoBuild = {
id: 1,
title: 'Build-1',
link: '',
status: BuildStatus.InProgress,
result: BuildResult.None,
queueTime: '2020-09-12T06:10:23.932Z',
startTime: '2020-09-12T06:15:23.932Z',
finishTime: '2020-09-12T06:20:23.932Z',
source: 'refs/heads/develop (f4f78b31)',
uniqueName: 'DOMAIN\\jdoe',
};
expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild);
expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild);
});
});
});
describe('mappedPullRequest', () => {
it('should return PullRequest from GitPullRequest', () => {
const inputGitRepository: GitRepository = {
name: 'super-feature-repo',
};
describe('mappedPullRequest happy path', () => {
it('should return PullRequest from GitPullRequest', () => {
const inputGitRepository: GitRepository = {
name: 'super-feature-repo',
};
const inputIdentityRef: IdentityRef = {
displayName: 'Jane Doe',
uniqueName: 'DOMAIN\\jdoe',
};
const inputIdentityRef: IdentityRef = {
displayName: 'Jane Doe',
uniqueName: 'DOMAIN\\jdoe',
};
const inputPullRequest: GitPullRequest = {
pullRequestId: 7181,
repository: inputGitRepository,
title: 'My Awesome New Feature',
createdBy: inputIdentityRef,
creationDate: new Date('2020-09-12T06:10:23.932Z'),
sourceRefName: 'refs/heads/topic/super-awesome-feature',
targetRefName: 'refs/heads/main',
status: PullRequestStatus.Active,
isDraft: false,
};
const inputPullRequest: GitPullRequest = {
pullRequestId: 7181,
repository: inputGitRepository,
title: 'My Awesome New Feature',
createdBy: inputIdentityRef,
creationDate: new Date('2020-09-12T06:10:23.932Z'),
sourceRefName: 'refs/heads/topic/super-awesome-feature',
targetRefName: 'refs/heads/main',
status: PullRequestStatus.Active,
isDraft: false,
};
const inputBaseUrl =
'https://host.com/myOrg/_git/super-feature-repo/pullrequest';
const inputBaseUrl =
'https://host.com/myOrg/_git/super-feature-repo/pullrequest';
const outputPullRequest: PullRequest = {
pullRequestId: 7181,
repoName: 'super-feature-repo',
title: 'My Awesome New Feature',
uniqueName: 'DOMAIN\\jdoe',
createdBy: 'Jane Doe',
creationDate: '2020-09-12T06:10:23.932Z',
sourceRefName: 'refs/heads/topic/super-awesome-feature',
targetRefName: 'refs/heads/main',
status: PullRequestStatus.Active,
isDraft: false,
link: 'https://host.com/myOrg/_git/super-feature-repo/pullrequest/7181',
};
const outputPullRequest: PullRequest = {
pullRequestId: 7181,
repoName: 'super-feature-repo',
title: 'My Awesome New Feature',
uniqueName: 'DOMAIN\\jdoe',
createdBy: 'Jane Doe',
creationDate: '2020-09-12T06:10:23.932Z',
sourceRefName: 'refs/heads/topic/super-awesome-feature',
targetRefName: 'refs/heads/main',
status: PullRequestStatus.Active,
isDraft: false,
link: 'https://host.com/myOrg/_git/super-feature-repo/pullrequest/7181',
};
expect(mappedPullRequest(inputPullRequest, inputBaseUrl)).toEqual(
outputPullRequest,
);
expect(mappedPullRequest(inputPullRequest, inputBaseUrl)).toEqual(
outputPullRequest,
);
});
});
});
describe('mappedBuildRun', () => {
describe('mappedBuildRun happy path', () => {
it('should return RepoBuild from Build', () => {
const inputBuildDefinition: DefinitionReference = {
name: 'My Build Definition',
};
const inputLinks: any = {
web: {
href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
},
};
const inputIdentityRef: IdentityRef = {
displayName: 'Jane Doe',
uniqueName: 'DOMAIN\\jdoe',
};
const inputBuild: Build = {
id: 1,
buildNumber: 'Build-1',
status: BuildStatus.Completed,
result: BuildResult.Succeeded,
queueTime: new Date('2020-09-12T06:10:23.932Z'),
startTime: new Date('2020-09-12T06:15:23.932Z'),
finishTime: new Date('2020-09-12T06:20:23.932Z'),
sourceBranch: 'refs/heads/develop',
sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c',
definition: inputBuildDefinition,
_links: inputLinks,
requestedFor: inputIdentityRef,
};
const outputRepoBuild: RepoBuild = {
id: 1,
title: 'My Build Definition - Build-1',
link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
status: BuildStatus.Completed,
result: BuildResult.Succeeded,
queueTime: '2020-09-12T06:10:23.932Z',
startTime: '2020-09-12T06:15:23.932Z',
finishTime: '2020-09-12T06:20:23.932Z',
source: 'refs/heads/develop (f4f78b31)',
uniqueName: 'DOMAIN\\jdoe',
};
expect(mappedBuildRun(inputBuild)).toEqual(outputRepoBuild);
});
});
describe('mappedBuildRun with no Build definition name', () => {
it('should return RepoBuild with only Build Number for title', () => {
const inputLinks: any = {
web: {
href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
},
};
const inputIdentityRef: IdentityRef = {
displayName: 'Jane Doe',
uniqueName: 'DOMAIN\\jdoe',
};
const inputBuild: Build = {
id: 1,
buildNumber: 'Build-1',
status: BuildStatus.Completed,
result: BuildResult.Succeeded,
queueTime: new Date('2020-09-12T06:10:23.932Z'),
startTime: new Date('2020-09-12T06:15:23.932Z'),
finishTime: new Date('2020-09-12T06:20:23.932Z'),
sourceBranch: 'refs/heads/develop',
sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c',
definition: undefined,
_links: inputLinks,
requestedFor: inputIdentityRef,
};
const outputRepoBuild: RepoBuild = {
id: 1,
title: 'Build-1',
link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
status: BuildStatus.Completed,
result: BuildResult.Succeeded,
queueTime: '2020-09-12T06:10:23.932Z',
startTime: '2020-09-12T06:15:23.932Z',
finishTime: '2020-09-12T06:20:23.932Z',
source: 'refs/heads/develop (f4f78b31)',
uniqueName: 'DOMAIN\\jdoe',
};
expect(mappedBuildRun(inputBuild)).toEqual(outputRepoBuild);
});
});
describe('mappedBuildRun with undefined status', () => {
it('should return BuildStatus of None for status', () => {
const inputLinks: any = {
web: {
href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
},
};
const inputIdentityRef: IdentityRef = {
displayName: 'Jane Doe',
uniqueName: 'DOMAIN\\jdoe',
};
const inputBuild: Build = {
id: 1,
buildNumber: 'Build-1',
status: undefined,
result: BuildResult.Succeeded,
queueTime: new Date('2020-09-12T06:10:23.932Z'),
startTime: new Date('2020-09-12T06:15:23.932Z'),
finishTime: new Date('2020-09-12T06:20:23.932Z'),
sourceBranch: 'refs/heads/develop',
sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c',
definition: undefined,
_links: inputLinks,
requestedFor: inputIdentityRef,
};
const outputRepoBuild: RepoBuild = {
id: 1,
title: 'Build-1',
link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
status: BuildStatus.None,
result: BuildResult.Succeeded,
queueTime: '2020-09-12T06:10:23.932Z',
startTime: '2020-09-12T06:15:23.932Z',
finishTime: '2020-09-12T06:20:23.932Z',
source: 'refs/heads/develop (f4f78b31)',
uniqueName: 'DOMAIN\\jdoe',
};
expect(mappedBuildRun(inputBuild)).toEqual(outputRepoBuild);
});
});
describe('mappedBuildRun with undefined result', () => {
it('should return BuildResult of None for result', () => {
const inputLinks: any = {
web: {
href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
},
};
const inputIdentityRef: IdentityRef = {
displayName: 'Jane Doe',
uniqueName: 'DOMAIN\\jdoe',
};
const inputBuild: Build = {
id: 1,
buildNumber: 'Build-1',
status: BuildStatus.InProgress,
result: undefined,
queueTime: new Date('2020-09-12T06:10:23.932Z'),
startTime: new Date('2020-09-12T06:15:23.932Z'),
finishTime: new Date('2020-09-12T06:20:23.932Z'),
sourceBranch: 'refs/heads/develop',
sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c',
definition: undefined,
_links: inputLinks,
requestedFor: inputIdentityRef,
};
const outputRepoBuild: RepoBuild = {
id: 1,
title: 'Build-1',
link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
status: BuildStatus.InProgress,
result: BuildResult.None,
queueTime: '2020-09-12T06:10:23.932Z',
startTime: '2020-09-12T06:15:23.932Z',
finishTime: '2020-09-12T06:20:23.932Z',
source: 'refs/heads/develop (f4f78b31)',
uniqueName: 'DOMAIN\\jdoe',
};
expect(mappedBuildRun(inputBuild)).toEqual(outputRepoBuild);
});
});
describe('mappedBuildRun with undefined link', () => {
it('should return empty string for link', () => {
const inputIdentityRef: IdentityRef = {
displayName: 'Jane Doe',
uniqueName: 'DOMAIN\\jdoe',
};
const inputBuild: Build = {
id: 1,
buildNumber: 'Build-1',
status: BuildStatus.InProgress,
result: undefined,
queueTime: new Date('2020-09-12T06:10:23.932Z'),
startTime: new Date('2020-09-12T06:15:23.932Z'),
finishTime: new Date('2020-09-12T06:20:23.932Z'),
sourceBranch: 'refs/heads/develop',
sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c',
definition: undefined,
_links: undefined,
requestedFor: inputIdentityRef,
};
const outputRepoBuild: RepoBuild = {
id: 1,
title: 'Build-1',
link: '',
status: BuildStatus.InProgress,
result: BuildResult.None,
queueTime: '2020-09-12T06:10:23.932Z',
startTime: '2020-09-12T06:15:23.932Z',
finishTime: '2020-09-12T06:20:23.932Z',
source: 'refs/heads/develop (f4f78b31)',
uniqueName: 'DOMAIN\\jdoe',
};
expect(mappedBuildRun(inputBuild)).toEqual(outputRepoBuild);
});
});
});
});
@@ -14,8 +14,13 @@
* limitations under the License.
*/
import {
Build,
BuildDefinitionReference,
} from 'azure-devops-node-api/interfaces/BuildInterfaces';
import {
BuildResult,
BuildRun,
BuildStatus,
DashboardPullRequest,
Policy,
@@ -35,7 +40,6 @@ import {
getArtifactId,
} from '../utils';
import { Build, BuildDefinitionReference } from 'azure-devops-node-api/interfaces/BuildInterfaces';
import { Logger } from 'winston';
import { PolicyEvaluationRecord } from 'azure-devops-node-api/interfaces/PolicyInterfaces';
import { TeamMember } from 'azure-devops-node-api/interfaces/common/VSSInterfaces';
@@ -60,46 +64,19 @@ export class AzureDevOpsApi {
return client.getRepository(repoName, projectName);
}
public async getBuildDefinitions(
projectName: string,
definitionName: string,
): Promise<BuildDefinitionReference[]> {
this.logger?.debug(
`Calling Azure DevOps REST API, getting Build Definitions for ${definitionName} in Project ${projectName}`,
);
const client = await this.webApi.getBuildApi();
return client.getDefinitions(
projectName,
definitionName,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
);
}
public async getBuildList(
projectName: string,
definitions?: number[],
repoId?: string,
top?: number,
repoId: string,
top: number,
): Promise<Build[]> {
this.logger?.debug(
`Calling Azure DevOps REST API, getting up to ${top} Builds for Repository Id ${repoId} for Project ${projectName}`,
);
const client = await this.webApi.getBuildApi();
return client.getBuilds(
projectName,
definitions,
undefined,
undefined,
undefined,
undefined,
@@ -118,7 +95,7 @@ export class AzureDevOpsApi {
undefined,
undefined,
repoId,
repoId ? 'TfsGit' : undefined,
'TfsGit',
);
}
@@ -134,7 +111,6 @@ export class AzureDevOpsApi {
const gitRepository = await this.getGitRepository(projectName, repoName);
const buildList = await this.getBuildList(
projectName,
undefined,
gitRepository.id as string,
top,
);
@@ -146,34 +122,6 @@ export class AzureDevOpsApi {
return repoBuilds;
}
public async getDefinitionBuilds(
projectName: string,
definitionName: string,
top: number,
) {
this.logger?.debug(
`Calling Azure DevOps REST API, getting up to ${top} Builds for ${definitionName} in Project ${projectName}`,
);
const buildDefinitions = await this.getBuildDefinitions(
projectName,
definitionName,
);
const definitions = buildDefinitions.map(bd => bd.id) as number[];
const buildList = await this.getBuildList(
projectName,
definitions,
undefined,
top,
);
const repoBuilds: RepoBuild[] = buildList.map(build => {
return mappedRepoBuild(build);
});
return repoBuilds;
}
public async getPullRequests(
projectName: string,
repoName: string,
@@ -313,6 +261,101 @@ export class AzureDevOpsApi {
return teamMembers
.map(teamMember => teamMember.identity?.id)
.filter((id): id is string => Boolean(id));
public async getBuildDefinitions(
projectName: string,
definitionName: string,
): Promise<BuildDefinitionReference[]> {
this.logger?.debug(
`Calling Azure DevOps REST API, getting Build Definitions for ${definitionName} in Project ${projectName}`,
);
const client = await this.webApi.getBuildApi();
return client.getDefinitions(
projectName,
definitionName,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
);
}
public async getBuilds(
projectName: string,
top: number,
repoId?: string,
definitions?: number[],
): Promise<Build[]> {
this.logger?.debug(
`Calling Azure DevOps REST API, getting up to ${top} Builds for Repository Id ${repoId} for Project ${projectName}`,
);
const client = await this.webApi.getBuildApi();
return client.getBuilds(
projectName,
definitions,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
top,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
repoId,
repoId ? 'TfsGit' : undefined,
);
}
public async getBuildRuns(
projectName: string,
top: number,
repoName?: string,
definitionName?: string,
) {
let repoId: string | undefined;
let definitions: number[] | undefined;
if (repoName) {
const gitRepository = await this.getGitRepository(projectName, repoName);
repoId = gitRepository.id;
}
if (definitionName) {
const buildDefinitions = await this.getBuildDefinitions(
projectName,
definitionName,
);
definitions = buildDefinitions.map(bd => bd.id) as number[];
}
const builds = await this.getBuilds(projectName, top, repoId, definitions);
const buildRuns: BuildRun[] = builds.map(build => {
return mappedBuildRun(build);
});
return buildRuns;
}
}
@@ -351,3 +394,20 @@ export function mappedPullRequest(
link: `${linkBaseUrl}/${pullRequest.pullRequestId}`,
};
}
export function mappedBuildRun(build: Build): BuildRun {
return {
id: build.id,
title: [build.definition?.name, build.buildNumber]
.filter(Boolean)
.join(' - '),
link: build._links?.web.href ?? '',
status: build.status ?? BuildStatus.None,
result: build.result ?? BuildResult.None,
queueTime: build.queueTime?.toISOString(),
startTime: build.startTime?.toISOString(),
finishTime: build.finishTime?.toISOString(),
source: `${build.sourceBranch} (${build.sourceVersion?.substr(0, 8)})`,
uniqueName: build.requestedFor?.uniqueName ?? 'N/A',
};
}
@@ -14,8 +14,13 @@
* limitations under the License.
*/
import {
Build,
BuildDefinitionReference,
} from 'azure-devops-node-api/interfaces/BuildInterfaces';
import {
BuildResult,
BuildRun,
BuildStatus,
PullRequest,
PullRequestStatus,
@@ -23,7 +28,6 @@ import {
} from '@backstage/plugin-azure-devops-common';
import { AzureDevOpsApi } from '../api';
import { Build, BuildDefinitionReference } from 'azure-devops-node-api/interfaces/BuildInterfaces';
import { ConfigReader } from '@backstage/config';
import { GitRepository } from 'azure-devops-node-api/interfaces/GitInterfaces';
import { createRouter } from './router';
@@ -43,6 +47,8 @@ describe('createRouter', () => {
getRepoBuilds: jest.fn(),
getDefinitionBuilds: jest.fn(),
getPullRequests: jest.fn(),
getBuilds: jest.fn(),
getBuildRuns: jest.fn(),
} as any;
const router = await createRouter({
azureDevOpsApi,
@@ -138,7 +144,6 @@ describe('createRouter', () => {
expect(azureDevOpsApi.getBuildList).toHaveBeenCalledWith(
'myProject',
undefined,
'af4ae3af-e747-4129-9bbc-d1329f6b0998',
40,
);
@@ -147,32 +152,6 @@ describe('createRouter', () => {
});
});
describe('GET /build-definitions/:projectName/:definitionName', () => {
it('fetches a list of build definitions', async () => {
const inputDefinition: BuildDefinitionReference = {
id: 1,
name: 'myBuildDefinition',
};
const inputDefinitions: BuildDefinitionReference[] = [inputDefinition];
azureDevOpsApi.getBuildDefinitions.mockResolvedValueOnce(
inputDefinitions,
);
const response = await request(app).get(
'/build-definitions/myProject/myBuildDefinition',
);
expect(azureDevOpsApi.getBuildDefinitions).toHaveBeenCalledWith(
'myProject',
'myBuildDefinition',
);
expect(response.status).toEqual(200);
expect(response.body).toEqual(inputDefinitions);
});
});
describe('GET /repo-builds/:projectName/:repoName', () => {
it('fetches a list of repo builds', async () => {
const firstRepoBuild: RepoBuild = {
@@ -227,60 +206,6 @@ describe('createRouter', () => {
});
});
describe('GET /definition-builds/:projectName/:definitionName', () => {
it('fetches a list of repo builds', async () => {
const firstRepoBuild: RepoBuild = {
id: 1,
title: 'My Build Definition - Build 1',
link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
status: BuildStatus.Completed,
result: BuildResult.PartiallySucceeded,
queueTime: undefined,
source: 'refs/heads/develop (f4f78b31)',
};
const secondRepoBuild: RepoBuild = {
id: 2,
title: 'My Build Definition - Build 2',
link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=2',
status: BuildStatus.InProgress,
result: BuildResult.None,
queueTime: undefined,
source: 'refs/heads/develop (13c988d4)',
};
const thirdRepoBuild: RepoBuild = {
id: 3,
title: 'My Build Definition - Build 3',
link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=3',
status: BuildStatus.Completed,
result: BuildResult.Succeeded,
queueTime: undefined,
source: 'refs/heads/develop (9bedf678)',
};
const repoBuilds: RepoBuild[] = [
firstRepoBuild,
secondRepoBuild,
thirdRepoBuild,
];
azureDevOpsApi.getDefinitionBuilds.mockResolvedValueOnce(repoBuilds);
const response = await request(app)
.get('/definition-builds/myProject/myDefinition')
.query({ top: '30' });
expect(azureDevOpsApi.getDefinitionBuilds).toHaveBeenCalledWith(
'myProject',
'myDefinition',
30,
);
expect(response.status).toEqual(200);
expect(response.body).toEqual(repoBuilds);
});
});
describe('GET /pull-requests/:projectName/:repoName', () => {
it('fetches a list of pull requests', async () => {
const firstPullRequest: PullRequest = {
@@ -343,4 +268,142 @@ describe('createRouter', () => {
expect(response.body).toEqual(pullRequests);
});
});
describe('GET /build-definitions/:projectName/:definitionName', () => {
it('fetches a list of build definitions', async () => {
const inputDefinition: BuildDefinitionReference = {
id: 1,
name: 'myBuildDefinition',
};
const inputDefinitions: BuildDefinitionReference[] = [inputDefinition];
azureDevOpsApi.getBuildDefinitions.mockResolvedValueOnce(
inputDefinitions,
);
const response = await request(app).get(
'/build-definitions/myProject/myBuildDefinition',
);
expect(azureDevOpsApi.getBuildDefinitions).toHaveBeenCalledWith(
'myProject',
'myBuildDefinition',
);
expect(response.status).toEqual(200);
expect(response.body).toEqual(inputDefinitions);
});
});
describe('GET /builds/:projectName', () => {
describe('GET /builds/:projectName with repoName', () => {
it('fetches a list of build runs using repoName', async () => {
const firstBuildRun: BuildRun = {
id: 1,
title: 'My Build Definition - Build 1',
link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
status: BuildStatus.Completed,
result: BuildResult.PartiallySucceeded,
queueTime: '2020-09-12T06:10:23.932Z',
source: 'refs/heads/develop (f4f78b31)',
};
const secondBuildRun: BuildRun = {
id: 2,
title: 'My Build Definition - Build 2',
link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=2',
status: BuildStatus.InProgress,
result: BuildResult.None,
queueTime: '2020-09-12T06:10:23.932Z',
source: 'refs/heads/develop (13c988d4)',
};
const thirdBuildRun: BuildRun = {
id: 3,
title: 'My Build Definition - Build 3',
link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=3',
status: BuildStatus.Completed,
result: BuildResult.Succeeded,
queueTime: '2020-09-12T06:10:23.932Z',
source: 'refs/heads/develop (9bedf678)',
};
const buildRuns: BuildRun[] = [
firstBuildRun,
secondBuildRun,
thirdBuildRun,
];
azureDevOpsApi.getBuildRuns.mockResolvedValueOnce(buildRuns);
const response = await request(app)
.get('/builds/myProject')
.query({ top: '50', repoName: 'myRepo' });
expect(azureDevOpsApi.getBuildRuns).toHaveBeenCalledWith(
'myProject',
50,
'myRepo',
undefined,
);
expect(response.status).toEqual(200);
expect(response.body).toEqual(buildRuns);
});
});
describe('GET /builds/:projectName with definitionName', () => {
it('fetches a list of build runs using definitionName', async () => {
const firstBuildRun: BuildRun = {
id: 1,
title: 'My Build Definition - Build 1',
link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1',
status: BuildStatus.Completed,
result: BuildResult.PartiallySucceeded,
queueTime: '2020-09-12T06:10:23.932Z',
source: 'refs/heads/develop (f4f78b31)',
};
const secondBuildRun: BuildRun = {
id: 2,
title: 'My Build Definition - Build 2',
link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=2',
status: BuildStatus.InProgress,
result: BuildResult.None,
queueTime: '2020-09-12T06:10:23.932Z',
source: 'refs/heads/develop (13c988d4)',
};
const thirdBuildRun: BuildRun = {
id: 3,
title: 'My Build Definition - Build 3',
link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=3',
status: BuildStatus.Completed,
result: BuildResult.Succeeded,
queueTime: '2020-09-12T06:10:23.932Z',
source: 'refs/heads/develop (9bedf678)',
};
const buildRuns: BuildRun[] = [
firstBuildRun,
secondBuildRun,
thirdBuildRun,
];
azureDevOpsApi.getBuildRuns.mockResolvedValueOnce(buildRuns);
const response = await request(app)
.get('/builds/myProject')
.query({ top: '50', definitionName: 'myDefinition' });
expect(azureDevOpsApi.getBuildRuns).toHaveBeenCalledWith(
'myProject',
50,
undefined,
'myDefinition',
);
expect(response.status).toEqual(200);
expect(response.body).toEqual(buildRuns);
});
});
});
});
@@ -73,25 +73,12 @@ export async function createRouter(
const top = req.query.top ? Number(req.query.top) : DEFAULT_TOP;
const buildList = await azureDevOpsApi.getBuildList(
projectName,
undefined,
repoId,
top,
);
res.status(200).json(buildList);
});
router.get(
'/build-definitions/:projectName/:definitionName',
async (req, res) => {
const { projectName, definitionName } = req.params;
const buildDefinitionList = await azureDevOpsApi.getBuildDefinitions(
projectName,
definitionName,
);
res.status(200).json(buildDefinitionList);
},
);
router.get('/repo-builds/:projectName/:repoName', async (req, res) => {
const { projectName, repoName } = req.params;
@@ -106,20 +93,6 @@ export async function createRouter(
res.status(200).json(gitRepository);
});
router.get(
'/definition-builds/:projectName/:definitionName',
async (req, res) => {
const { projectName, definitionName } = req.params;
const top = req.query.top ? Number(req.query.top) : DEFAULT_TOP;
const gitRepository = await azureDevOpsApi.getDefinitionBuilds(
projectName,
definitionName,
top,
);
res.status(200).json(gitRepository);
},
);
router.get('/pull-requests/:projectName/:repoName', async (req, res) => {
const { projectName, repoName } = req.params;
@@ -171,6 +144,32 @@ export async function createRouter(
res.status(200).json(allTeams);
});
router.get(
'/build-definitions/:projectName/:definitionName',
async (req, res) => {
const { projectName, definitionName } = req.params;
const buildDefinitionList = await azureDevOpsApi.getBuildDefinitions(
projectName,
definitionName,
);
res.status(200).json(buildDefinitionList);
},
);
router.get('/builds/:projectName', async (req, res) => {
const { projectName } = req.params;
const repoName = req.query.repoName?.toString();
const definitionName = req.query.definitionName?.toString();
const top = req.query.top ? Number(req.query.top) : DEFAULT_TOP;
const builds = await azureDevOpsApi.getBuildRuns(
projectName,
top,
repoName,
definitionName,
);
res.status(200).json(builds);
});
router.use(errorHandler());
return router;
}