Added pull requests to backend
Signed-off-by: Andre Wanlin <awanlin@rapidrtc.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-azure-devops-backend': patch
|
||||
---
|
||||
|
||||
Expands the Azure DevOps backend plugin to provide pull request data to be used by the front end plugin
|
||||
@@ -10,6 +10,7 @@ import { Config } from '@backstage/config';
|
||||
import express from 'express';
|
||||
import { GitRepository } from 'azure-devops-node-api/interfaces/GitInterfaces';
|
||||
import { Logger as Logger_2 } from 'winston';
|
||||
import { PullRequestStatus } from 'azure-devops-node-api/interfaces/GitInterfaces';
|
||||
import { WebApi } from 'azure-devops-node-api';
|
||||
|
||||
// Warning: (ae-missing-release-tag) "AzureDevOpsApi" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
@@ -29,6 +30,13 @@ export class AzureDevOpsApi {
|
||||
repoName: string,
|
||||
): Promise<GitRepository>;
|
||||
// (undocumented)
|
||||
getPullRequests(
|
||||
projectName: string,
|
||||
repoName: string,
|
||||
top: number,
|
||||
status: PullRequestStatus,
|
||||
): Promise<PullRequest[]>;
|
||||
// (undocumented)
|
||||
getRepoBuilds(
|
||||
projectName: string,
|
||||
repoName: string,
|
||||
@@ -41,6 +49,22 @@ export class AzureDevOpsApi {
|
||||
// @public (undocumented)
|
||||
export function createRouter(options: RouterOptions): Promise<express.Router>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "PullRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type PullRequest = {
|
||||
pullRequestId?: number;
|
||||
repoName?: string;
|
||||
title?: string;
|
||||
createdBy?: string;
|
||||
creationDate?: Date;
|
||||
sourceRefName?: string;
|
||||
targetRefName?: string;
|
||||
status?: PullRequestStatus;
|
||||
isDraft?: boolean;
|
||||
link: string;
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "RepoBuild" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
|
||||
@@ -13,17 +13,23 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { repoBuildFromBuild } from './AzureDevOpsApi';
|
||||
import { RepoBuild } from './types';
|
||||
import { mappedPullRequest, mappedRepoBuild } from './AzureDevOpsApi';
|
||||
import { PullRequest, RepoBuild } from './types';
|
||||
import {
|
||||
Build,
|
||||
BuildResult,
|
||||
BuildStatus,
|
||||
DefinitionReference,
|
||||
} from 'azure-devops-node-api/interfaces/BuildInterfaces';
|
||||
import {
|
||||
GitPullRequest,
|
||||
PullRequestStatus,
|
||||
} from 'azure-devops-node-api/interfaces/GitInterfaces';
|
||||
import { GitRepository } from 'azure-devops-node-api/interfaces/TfvcInterfaces';
|
||||
import { IdentityRef } from 'azure-devops-node-api/interfaces/common/VSSInterfaces';
|
||||
|
||||
describe('AzureDevOpsApi', () => {
|
||||
describe('repoBuildFromBuild', () => {
|
||||
describe('mappedRepoBuild', () => {
|
||||
it('should return RepoBuild from Build', () => {
|
||||
const inputBuildDefinition: DefinitionReference = {
|
||||
name: 'My Build Definition',
|
||||
@@ -57,11 +63,11 @@ describe('AzureDevOpsApi', () => {
|
||||
source: 'refs/heads/develop (f4f78b31)',
|
||||
};
|
||||
|
||||
expect(repoBuildFromBuild(inputBuild)).toEqual(outputRepoBuild);
|
||||
expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild);
|
||||
});
|
||||
});
|
||||
|
||||
describe('repoBuildFromBuild with no Build definition name', () => {
|
||||
describe('mappedRepoBuild with no Build definition name', () => {
|
||||
it('should return RepoBuild with only Build Number for title', () => {
|
||||
const inputLinks: any = {
|
||||
web: {
|
||||
@@ -91,11 +97,11 @@ describe('AzureDevOpsApi', () => {
|
||||
source: 'refs/heads/develop (f4f78b31)',
|
||||
};
|
||||
|
||||
expect(repoBuildFromBuild(inputBuild)).toEqual(outputRepoBuild);
|
||||
expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild);
|
||||
});
|
||||
});
|
||||
|
||||
describe('repoBuildFromBuild with undefined status', () => {
|
||||
describe('mappedRepoBuild with undefined status', () => {
|
||||
it('should return BuildStatus of None for status', () => {
|
||||
const inputLinks: any = {
|
||||
web: {
|
||||
@@ -125,11 +131,11 @@ describe('AzureDevOpsApi', () => {
|
||||
source: 'refs/heads/develop (f4f78b31)',
|
||||
};
|
||||
|
||||
expect(repoBuildFromBuild(inputBuild)).toEqual(outputRepoBuild);
|
||||
expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild);
|
||||
});
|
||||
});
|
||||
|
||||
describe('repoBuildFromBuild with undefined result', () => {
|
||||
describe('mappedRepoBuild with undefined result', () => {
|
||||
it('should return BuildResult of None for result', () => {
|
||||
const inputLinks: any = {
|
||||
web: {
|
||||
@@ -159,11 +165,11 @@ describe('AzureDevOpsApi', () => {
|
||||
source: 'refs/heads/develop (f4f78b31)',
|
||||
};
|
||||
|
||||
expect(repoBuildFromBuild(inputBuild)).toEqual(outputRepoBuild);
|
||||
expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild);
|
||||
});
|
||||
});
|
||||
|
||||
describe('repoBuildFromBuild with undefined link', () => {
|
||||
describe('mappedRepoBuild with undefined link', () => {
|
||||
it('should return empty string for link', () => {
|
||||
const inputBuild: Build = {
|
||||
id: 1,
|
||||
@@ -187,7 +193,51 @@ describe('AzureDevOpsApi', () => {
|
||||
source: 'refs/heads/develop (f4f78b31)',
|
||||
};
|
||||
|
||||
expect(repoBuildFromBuild(inputBuild)).toEqual(outputRepoBuild);
|
||||
expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mappedPullRequest', () => {
|
||||
it('should return PullRequest from GitPullRequest', () => {
|
||||
const inputGitRepository: GitRepository = {
|
||||
name: 'super-feature-repo',
|
||||
};
|
||||
|
||||
const inputIdentityRef: IdentityRef = {
|
||||
displayName: 'Jane Doe',
|
||||
};
|
||||
|
||||
const inputPullRequest: GitPullRequest = {
|
||||
pullRequestId: 7181,
|
||||
repository: inputGitRepository,
|
||||
title: 'My Awesome New Feature',
|
||||
createdBy: inputIdentityRef,
|
||||
creationDate: new Date('2020-09-12T06:10:23.9325232Z'),
|
||||
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 outputPullRequest: PullRequest = {
|
||||
pullRequestId: 7181,
|
||||
repoName: 'super-feature-repo',
|
||||
title: 'My Awesome New Feature',
|
||||
createdBy: 'Jane Doe',
|
||||
creationDate: new Date('2020-09-12T06:10:23.9325232Z'),
|
||||
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,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,12 +16,17 @@
|
||||
|
||||
import { Logger } from 'winston';
|
||||
import { WebApi } from 'azure-devops-node-api';
|
||||
import { RepoBuild } from './types';
|
||||
import {
|
||||
Build,
|
||||
BuildResult,
|
||||
BuildStatus,
|
||||
} from 'azure-devops-node-api/interfaces/BuildInterfaces';
|
||||
import {
|
||||
GitPullRequest,
|
||||
GitPullRequestSearchCriteria,
|
||||
PullRequestStatus,
|
||||
} from 'azure-devops-node-api/interfaces/GitInterfaces';
|
||||
import { PullRequest, RepoBuild } from './types';
|
||||
|
||||
export class AzureDevOpsApi {
|
||||
constructor(
|
||||
@@ -88,14 +93,47 @@ export class AzureDevOpsApi {
|
||||
);
|
||||
|
||||
const repoBuilds: RepoBuild[] = buildList.map(build => {
|
||||
return repoBuildFromBuild(build);
|
||||
return mappedRepoBuild(build);
|
||||
});
|
||||
|
||||
return repoBuilds;
|
||||
}
|
||||
|
||||
async getPullRequests(
|
||||
projectName: string,
|
||||
repoName: string,
|
||||
top: number,
|
||||
status: PullRequestStatus,
|
||||
) {
|
||||
if (this.logger) {
|
||||
this.logger.debug(
|
||||
`Calling Azure DevOps REST API, getting up to ${top} Pull Requests for Repository ${repoName} for Project ${projectName}`,
|
||||
);
|
||||
}
|
||||
|
||||
const gitRepository = await this.getGitRepository(projectName, repoName);
|
||||
const client = await this.webApi.getGitApi();
|
||||
const searchCriteria: GitPullRequestSearchCriteria = {
|
||||
status: status,
|
||||
};
|
||||
const gitPullRequests = await client.getPullRequests(
|
||||
gitRepository.id as string,
|
||||
searchCriteria,
|
||||
projectName,
|
||||
undefined,
|
||||
undefined,
|
||||
top,
|
||||
);
|
||||
const linkBaseUrl = `${this.webApi.serverUrl}/${projectName}/_git/${repoName}/pullrequest`;
|
||||
const pullRequests: PullRequest[] = gitPullRequests.map(gitPullRequest => {
|
||||
return mappedPullRequest(gitPullRequest, linkBaseUrl);
|
||||
});
|
||||
|
||||
return pullRequests;
|
||||
}
|
||||
}
|
||||
|
||||
export function repoBuildFromBuild(build: Build) {
|
||||
export function mappedRepoBuild(build: Build) {
|
||||
return {
|
||||
id: build.id,
|
||||
title: [build.definition?.name, build.buildNumber]
|
||||
@@ -108,3 +146,21 @@ export function repoBuildFromBuild(build: Build) {
|
||||
source: `${build.sourceBranch} (${build.sourceVersion?.substr(0, 8)})`,
|
||||
};
|
||||
}
|
||||
|
||||
export function mappedPullRequest(
|
||||
pullRequest: GitPullRequest,
|
||||
linkBaseUrl: string,
|
||||
) {
|
||||
return {
|
||||
pullRequestId: pullRequest.pullRequestId,
|
||||
repoName: pullRequest.repository?.name,
|
||||
title: pullRequest.title,
|
||||
createdBy: pullRequest.createdBy?.displayName,
|
||||
creationDate: pullRequest.creationDate,
|
||||
sourceRefName: pullRequest.sourceRefName,
|
||||
targetRefName: pullRequest.targetRefName,
|
||||
status: pullRequest.status,
|
||||
isDraft: pullRequest.isDraft,
|
||||
link: `${linkBaseUrl}/${pullRequest.pullRequestId}`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,4 +15,4 @@
|
||||
*/
|
||||
|
||||
export { AzureDevOpsApi } from './AzureDevOpsApi';
|
||||
export type { RepoBuild } from './types';
|
||||
export type { RepoBuild, PullRequest } from './types';
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
BuildResult,
|
||||
BuildStatus,
|
||||
} from 'azure-devops-node-api/interfaces/BuildInterfaces';
|
||||
import { PullRequestStatus } from 'azure-devops-node-api/interfaces/GitInterfaces';
|
||||
|
||||
export type RepoBuild = {
|
||||
id?: number;
|
||||
@@ -28,3 +29,16 @@ export type RepoBuild = {
|
||||
queueTime?: Date;
|
||||
source: string;
|
||||
};
|
||||
|
||||
export type PullRequest = {
|
||||
pullRequestId?: number;
|
||||
repoName?: string;
|
||||
title?: string;
|
||||
createdBy?: string;
|
||||
creationDate?: Date;
|
||||
sourceRefName?: string;
|
||||
targetRefName?: string;
|
||||
status?: PullRequestStatus;
|
||||
isDraft?: boolean;
|
||||
link: string;
|
||||
};
|
||||
|
||||
@@ -14,5 +14,5 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
export { AzureDevOpsApi } from './api';
|
||||
export type { RepoBuild } from './api';
|
||||
export type { RepoBuild, PullRequest } from './api';
|
||||
export * from './service/router';
|
||||
|
||||
@@ -20,8 +20,11 @@ import express from 'express';
|
||||
import request from 'supertest';
|
||||
import { AzureDevOpsApi } from '../api';
|
||||
import { createRouter } from './router';
|
||||
import { RepoBuild } from '../api/types';
|
||||
import { GitRepository } from 'azure-devops-node-api/interfaces/GitInterfaces';
|
||||
import { PullRequest, RepoBuild } from '../api/types';
|
||||
import {
|
||||
GitRepository,
|
||||
PullRequestStatus,
|
||||
} from 'azure-devops-node-api/interfaces/GitInterfaces';
|
||||
import {
|
||||
Build,
|
||||
BuildResult,
|
||||
@@ -37,6 +40,7 @@ describe('createRouter', () => {
|
||||
getGitRepository: jest.fn(),
|
||||
getBuildList: jest.fn(),
|
||||
getRepoBuilds: jest.fn(),
|
||||
getPullRequests: jest.fn(),
|
||||
} as any;
|
||||
const router = await createRouter({
|
||||
azureDevOpsApi,
|
||||
@@ -193,4 +197,68 @@ describe('createRouter', () => {
|
||||
expect(response.body).toEqual(repoBuilds);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /pull-requests/:projectName/:repoName', () => {
|
||||
it('fetches a list of pull requests', async () => {
|
||||
const firstPullRequest: PullRequest = {
|
||||
pullRequestId: 7181,
|
||||
repoName: 'super-feature-repo',
|
||||
title: 'My Awesome New Feature',
|
||||
createdBy: 'Jane Doe',
|
||||
creationDate: undefined,
|
||||
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 secondPullRequest: PullRequest = {
|
||||
pullRequestId: 7182,
|
||||
repoName: 'super-feature-repo',
|
||||
title: 'Refactoring My Awesome New Feature',
|
||||
createdBy: 'Jane Doe',
|
||||
creationDate: undefined,
|
||||
sourceRefName: 'refs/heads/topic/refactor-super-awesome-feature',
|
||||
targetRefName: 'refs/heads/main',
|
||||
status: PullRequestStatus.Active,
|
||||
isDraft: false,
|
||||
link: 'https://host.com/myOrg/_git/super-feature-repo/pullrequest/7182',
|
||||
};
|
||||
|
||||
const thirdPullRequest: PullRequest = {
|
||||
pullRequestId: 7183,
|
||||
repoName: 'super-feature-repo',
|
||||
title: 'Bug Fix for My Awesome New Feature',
|
||||
createdBy: 'Jane Doe',
|
||||
creationDate: undefined,
|
||||
sourceRefName: 'refs/heads/topic/fix-super-awesome-feature',
|
||||
targetRefName: 'refs/heads/main',
|
||||
status: PullRequestStatus.Active,
|
||||
isDraft: false,
|
||||
link: 'https://host.com/myOrg/_git/super-feature-repo/pullrequest/7183',
|
||||
};
|
||||
|
||||
const pullRequests: PullRequest[] = [
|
||||
firstPullRequest,
|
||||
secondPullRequest,
|
||||
thirdPullRequest,
|
||||
];
|
||||
|
||||
azureDevOpsApi.getPullRequests.mockResolvedValueOnce(pullRequests);
|
||||
|
||||
const response = await request(app)
|
||||
.get('/pull-requests/myProject/myRepo')
|
||||
.query({ top: '50', status: 1 });
|
||||
|
||||
expect(azureDevOpsApi.getPullRequests).toHaveBeenCalledWith(
|
||||
'myProject',
|
||||
'myRepo',
|
||||
50,
|
||||
1,
|
||||
);
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual(pullRequests);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,6 +21,7 @@ import { Logger } from 'winston';
|
||||
import { Config } from '@backstage/config';
|
||||
import { getPersonalAccessTokenHandler, WebApi } from 'azure-devops-node-api';
|
||||
import { AzureDevOpsApi } from '../api';
|
||||
import { PullRequestStatus } from 'azure-devops-node-api/interfaces/GitInterfaces';
|
||||
|
||||
const DEFAULT_TOP: number = 10;
|
||||
|
||||
@@ -84,6 +85,21 @@ export async function createRouter(
|
||||
res.status(200).json(gitRepository);
|
||||
});
|
||||
|
||||
router.get('/pull-requests/:projectName/:repoName', async (req, res) => {
|
||||
const { projectName, repoName } = req.params;
|
||||
const top = req.query.top ? Number(req.query.top) : DEFAULT_TOP;
|
||||
const status = req.query.status
|
||||
? Number(req.query.status)
|
||||
: PullRequestStatus.Active;
|
||||
const gitPullRequest = await azureDevOpsApi.getPullRequests(
|
||||
projectName,
|
||||
repoName,
|
||||
top,
|
||||
status,
|
||||
);
|
||||
res.status(200).json(gitPullRequest);
|
||||
});
|
||||
|
||||
router.use(errorHandler());
|
||||
return router;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user