From 2f5e0c52729efcff54f35bc9922874d2d3dfb4d9 Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Wed, 15 Sep 2021 07:35:43 -0500 Subject: [PATCH 01/18] Adding Azure DevOps backend plugin Signed-off-by: Andre Wanlin --- app-config.yaml | 6 + packages/backend/package.json | 9 +- packages/backend/src/index.ts | 3 + packages/backend/src/plugins/azuredevops.ts | 26 +++ plugins/azure-devops-backend/.eslintrc.js | 3 + plugins/azure-devops-backend/README.md | 27 +++ plugins/azure-devops-backend/config.d.ts | 38 ++++ plugins/azure-devops-backend/package.json | 40 ++++ .../src/api/AzureDevOpsApi.ts | 104 ++++++++++ plugins/azure-devops-backend/src/api/index.ts | 17 ++ plugins/azure-devops-backend/src/api/types.ts | 25 +++ plugins/azure-devops-backend/src/index.ts | 17 ++ plugins/azure-devops-backend/src/run.ts | 33 ++++ .../src/service/router.test.ts | 187 ++++++++++++++++++ .../src/service/router.ts | 110 +++++++++++ .../src/service/standaloneServer.ts | 57 ++++++ .../azure-devops-backend/src/setupTests.ts | 17 ++ 17 files changed, 715 insertions(+), 4 deletions(-) create mode 100644 packages/backend/src/plugins/azuredevops.ts create mode 100644 plugins/azure-devops-backend/.eslintrc.js create mode 100644 plugins/azure-devops-backend/README.md create mode 100644 plugins/azure-devops-backend/config.d.ts create mode 100644 plugins/azure-devops-backend/package.json create mode 100644 plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts create mode 100644 plugins/azure-devops-backend/src/api/index.ts create mode 100644 plugins/azure-devops-backend/src/api/types.ts create mode 100644 plugins/azure-devops-backend/src/index.ts create mode 100644 plugins/azure-devops-backend/src/run.ts create mode 100644 plugins/azure-devops-backend/src/service/router.test.ts create mode 100644 plugins/azure-devops-backend/src/service/router.ts create mode 100644 plugins/azure-devops-backend/src/service/standaloneServer.ts create mode 100644 plugins/azure-devops-backend/src/setupTests.ts diff --git a/app-config.yaml b/app-config.yaml index 2fb75f718e..49f7890675 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -400,3 +400,9 @@ jenkins: baseUrl: https://jenkins.example.com username: backstage-bot apiKey: 123456789abcdef0123456789abcedf012 + +azureDevOps: + host: dev.azure.com + token: ${AZURE_TOKEN} + organization: my-company + top: 50 \ No newline at end of file diff --git a/packages/backend/package.json b/packages/backend/package.json index 5346466ba1..ec52c6086c 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -30,10 +30,11 @@ "@backstage/config": "^0.1.10", "@backstage/integration": "^0.6.5", "@backstage/plugin-app-backend": "^0.3.16", - "@backstage/plugin-auth-backend": "^0.4.1", - "@backstage/plugin-badges-backend": "^0.1.10", - "@backstage/plugin-catalog-backend": "^0.14.0", - "@backstage/plugin-code-coverage-backend": "^0.1.11", + "@backstage/plugin-auth-backend": "^0.4.0", + "@backstage/plugin-azure-devops-backend": "^0.1.0", + "@backstage/plugin-badges-backend": "^0.1.9", + "@backstage/plugin-catalog-backend": "^0.13.8", + "@backstage/plugin-code-coverage-backend": "^0.1.10", "@backstage/plugin-graphql-backend": "^0.1.9", "@backstage/plugin-jenkins-backend": "^0.1.5", "@backstage/plugin-kubernetes-backend": "^0.3.16", diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 78c6c4c20b..f25ec5a14e 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -38,6 +38,7 @@ import { Config } from '@backstage/config'; import healthcheck from './plugins/healthcheck'; import { metricsInit, metricsHandler } from './metrics'; import auth from './plugins/auth'; +import azureDevOps from './plugins/azuredevops'; import catalog from './plugins/catalog'; import codeCoverage from './plugins/codecoverage'; import kubernetes from './plugins/kubernetes'; @@ -94,6 +95,7 @@ async function main() { ); const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); const authEnv = useHotMemoize(module, () => createEnv('auth')); + const azureDevOpsEnv = useHotMemoize(module, () => createEnv('azure-devops')); const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); const rollbarEnv = useHotMemoize(module, () => createEnv('rollbar')); const searchEnv = useHotMemoize(module, () => createEnv('search')); @@ -112,6 +114,7 @@ async function main() { apiRouter.use('/rollbar', await rollbar(rollbarEnv)); apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); apiRouter.use('/auth', await auth(authEnv)); + apiRouter.use('/azure-devops', await azureDevOps(azureDevOpsEnv)); apiRouter.use('/search', await search(searchEnv)); apiRouter.use('/techdocs', await techdocs(techdocsEnv)); apiRouter.use('/todo', await todo(todoEnv)); diff --git a/packages/backend/src/plugins/azuredevops.ts b/packages/backend/src/plugins/azuredevops.ts new file mode 100644 index 0000000000..e11a21ba2e --- /dev/null +++ b/packages/backend/src/plugins/azuredevops.ts @@ -0,0 +1,26 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createRouter } from '@backstage/plugin-azure-devops-backend'; +import { Router } from 'express'; +import type { PluginEnvironment } from '../types'; + +export default async function createPlugin({ + logger, + config, +}: PluginEnvironment): Promise { + return await createRouter({ logger, config }); +} diff --git a/plugins/azure-devops-backend/.eslintrc.js b/plugins/azure-devops-backend/.eslintrc.js new file mode 100644 index 0000000000..16a033dbc6 --- /dev/null +++ b/plugins/azure-devops-backend/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint.backend')], +}; diff --git a/plugins/azure-devops-backend/README.md b/plugins/azure-devops-backend/README.md new file mode 100644 index 0000000000..91c69b74ea --- /dev/null +++ b/plugins/azure-devops-backend/README.md @@ -0,0 +1,27 @@ +# Azure DevOps Backend + +Simple plugin that proxies requests to the [Azure DevOps](https://docs.microsoft.com/en-us/rest/api/azure/devops/?view=azure-devops-rest-6.1) API. + +## Setup + +The following values are read from the configuration file: + +```yaml +azureDevOps: + host: dev.azure.com + token: ${AZURE_TOKEN} + organization: my-company + top: 50 +``` + +Configuration Details: + +- `host` and `token` can be the same as the ones used for the `integration` section +- `AZURE_TOKEN` environment variable must be set to a [Personal Access Token](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=preview-page) with read access to both Code and Build +- `organization` is your Azure DevOps Organization name or for Azure DevOps Server (on-premise) this will be your Collection name +- `top` sets the max number of items to return - currently only applies to builds + +## Links + +- [Frontend part of the plugin](https://github.com/backstage/backstage/tree/master/plugins/azure-devops) +- [The Backstage homepage](https://backstage.io) diff --git a/plugins/azure-devops-backend/config.d.ts b/plugins/azure-devops-backend/config.d.ts new file mode 100644 index 0000000000..613881c675 --- /dev/null +++ b/plugins/azure-devops-backend/config.d.ts @@ -0,0 +1,38 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface Config { + /** Configuration options for the azure-devops-backend plugin */ + azureDevOps?: { + /** + * The hostname of the given Azure instance + */ + host: string; + /** + * Token used to authenticate requests. + * @visibility secret + */ + token?: string; + /** + * The organization of the given Azure instance + */ + organization: string; + /** + * The max number of items to return - applies to builds + */ + top: number; + }; +} diff --git a/plugins/azure-devops-backend/package.json b/plugins/azure-devops-backend/package.json new file mode 100644 index 0000000000..216f530a8b --- /dev/null +++ b/plugins/azure-devops-backend/package.json @@ -0,0 +1,40 @@ +{ + "name": "@backstage/plugin-azure-devops-backend", + "version": "0.1.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "scripts": { + "start": "backstage-cli backend:dev", + "build": "backstage-cli backend:build", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean" + }, + "dependencies": { + "@backstage/backend-common": "^0.9.2", + "@backstage/config": "^0.1.9", + "@types/express": "^4.17.6", + "azure-devops-node-api": "^11.0.1", + "express": "^4.17.1", + "express-promise-router": "^4.1.0", + "winston": "^3.2.1", + "yn": "^4.0.0" + }, + "devDependencies": { + "@backstage/cli": "^0.7.11", + "@types/supertest": "^2.0.8", + "supertest": "^4.0.2", + "msw": "^0.29.0" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts new file mode 100644 index 0000000000..a2b7518069 --- /dev/null +++ b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts @@ -0,0 +1,104 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Logger } from 'winston'; +import { WebApi } from 'azure-devops-node-api'; +import { RepoBuild } from './types'; +import { + BuildResult, + BuildStatus, +} from 'azure-devops-node-api/interfaces/BuildInterfaces'; + +export class AzureDevOpsApi { + constructor( + private readonly logger: Logger, + private readonly webApi: WebApi, + private readonly top: number, + ) {} + + async getGitRepository(projectName: string, repoName: string) { + if (this.logger) { + this.logger.info( + `Calling Azure DevOps REST API, getting Repository ${repoName} for Project ${projectName}`, + ); + } + + const client = await this.webApi.getGitApi(); + return client.getRepository(repoName, projectName); + } + + async getBuildList(projectName: string, repoId: string) { + if (this.logger) { + this.logger.info( + `Calling Azure DevOps REST API, getting up to ${this.top} Builds for Repository Id ${repoId} for Project ${projectName}`, + ); + } + + const client = await this.webApi.getBuildApi(); + return client.getBuilds( + projectName, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + this.top, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + repoId, + 'TfsGit', + ); + } + + async getRepoBuilds(projectName: string, repoName: string) { + if (this.logger) { + this.logger.info( + `Calling Azure DevOps REST API, getting up to ${this.top} Builds for Repository ${repoName} for Project ${projectName}`, + ); + } + + const gitRepository = await this.getGitRepository(projectName, repoName); + const buildList = await this.getBuildList( + projectName, + gitRepository.id as string, + ); + + const repoBuilds = buildList.map(build => { + const repoBuild: RepoBuild = { + id: build.id as number, + title: `${build.definition?.name} - ${build.buildNumber}`, + link: build._links?.web.href, + status: BuildStatus[build.status as BuildStatus], + result: BuildResult[build.result as BuildResult], + queueTime: build.queueTime as Date, + source: `${build.sourceBranch} (${build.sourceVersion?.substr(0, 8)})`, + }; + return repoBuild; + }); + + return repoBuilds; + } +} diff --git a/plugins/azure-devops-backend/src/api/index.ts b/plugins/azure-devops-backend/src/api/index.ts new file mode 100644 index 0000000000..1ff69dbb4a --- /dev/null +++ b/plugins/azure-devops-backend/src/api/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { AzureDevOpsApi } from './AzureDevOpsApi'; diff --git a/plugins/azure-devops-backend/src/api/types.ts b/plugins/azure-devops-backend/src/api/types.ts new file mode 100644 index 0000000000..981fbd4d1b --- /dev/null +++ b/plugins/azure-devops-backend/src/api/types.ts @@ -0,0 +1,25 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export type RepoBuild = { + id: number; + title: string; + link: string; + status: string; + result: string; + queueTime: Date; + source: string; +}; diff --git a/plugins/azure-devops-backend/src/index.ts b/plugins/azure-devops-backend/src/index.ts new file mode 100644 index 0000000000..ca73cb27ba --- /dev/null +++ b/plugins/azure-devops-backend/src/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './service/router'; diff --git a/plugins/azure-devops-backend/src/run.ts b/plugins/azure-devops-backend/src/run.ts new file mode 100644 index 0000000000..54d2716290 --- /dev/null +++ b/plugins/azure-devops-backend/src/run.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getRootLogger } from '@backstage/backend-common'; +import yn from 'yn'; +import { startStandaloneServer } from './service/standaloneServer'; + +const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7000; +const enableCors = yn(process.env.PLUGIN_CORS, { default: false }); +const logger = getRootLogger(); + +startStandaloneServer({ port, enableCors, logger }).catch(err => { + logger.error(err); + process.exit(1); +}); + +process.on('SIGINT', () => { + logger.info('CTRL+C pressed; exiting.'); + process.exit(0); +}); diff --git a/plugins/azure-devops-backend/src/service/router.test.ts b/plugins/azure-devops-backend/src/service/router.test.ts new file mode 100644 index 0000000000..644f8dd77c --- /dev/null +++ b/plugins/azure-devops-backend/src/service/router.test.ts @@ -0,0 +1,187 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getVoidLogger } from '@backstage/backend-common'; +import { ConfigReader } from '@backstage/config'; +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 { + Build, + BuildResult, + BuildStatus, +} from 'azure-devops-node-api/interfaces/BuildInterfaces'; + +describe('createRouter', () => { + let azureDevOpsApi: jest.Mocked; + let app: express.Express; + + beforeAll(async () => { + azureDevOpsApi = { + getGitRepository: jest.fn(), + getBuildList: jest.fn(), + getRepoBuilds: jest.fn(), + } as any; + const router = await createRouter({ + azureDevOpsApi, + logger: getVoidLogger(), + config: new ConfigReader({ + azureDevOps: { + token: 'foo', + host: 'host.com', + organization: 'myOrg', + top: 5, + }, + }), + }); + app = express().use(router); + }); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + describe('GET /health', () => { + it('returns ok', async () => { + const response = await request(app).get('/health'); + + expect(response.status).toEqual(200); + expect(response.body).toEqual({ + status: 'Healthy', + details: 'All required config has been provided', + }); + }); + }); + + describe('GET /repository/:projectName/:repoName', () => { + it('fetches a single repository', async () => { + const gitRepository: GitRepository = { + id: 'af4ae3af-e747-4129-9bbc-d1329f6b0998', + name: 'myRepo', + url: 'https://host.com/repo', + defaultBranch: 'refs/heads/develop', + sshUrl: 'ssh://host.com/repo', + webUrl: 'https://host.com/webRepo', + }; + + azureDevOpsApi.getGitRepository.mockResolvedValueOnce(gitRepository); + + const response = await request(app).get( + `/repository/${'myProject'}/${'myRepo'}`, + ); + + expect(response.status).toEqual(200); + expect(response.body).toEqual(gitRepository); + }); + }); + + describe('GET /builds/:projectName/:repoId', () => { + it('fetches a list of builds', async () => { + const firstBuild: Build = { + id: 1, + buildNumber: 'Build-1', + status: BuildStatus.Completed, + result: BuildResult.Succeeded, + queueTime: '2020-09-12T06:10:23.9325232Z' as unknown as Date, + sourceBranch: 'refs/heads/develop', + sourceVersion: '9bedf67800b2923982bdf60c89c57ce6fd2d9a1c', + }; + + const secondBuild: Build = { + id: 2, + buildNumber: 'Build-2', + status: BuildStatus.InProgress, + result: BuildResult.None, + queueTime: '2020-09-12T06:20:23.9325232Z' as unknown as Date, + sourceBranch: 'refs/heads/develop', + sourceVersion: '13c988d4f15e06bcdd0b0af290086a3079cdadb0', + }; + + const thirdBuild: Build = { + id: 3, + buildNumber: 'Build-3', + status: BuildStatus.Completed, + result: BuildResult.PartiallySucceeded, + queueTime: '2020-09-12T06:30:23.9325232Z' as unknown as Date, + sourceBranch: 'refs/heads/develop', + sourceVersion: 'f4f78b319c308600eab015a5d6529add21660dc1', + }; + + const builds: Build[] = [firstBuild, secondBuild, thirdBuild]; + + azureDevOpsApi.getBuildList.mockResolvedValueOnce(builds); + + const response = await request(app).get( + `/builds/${'myProject'}/${'af4ae3af-e747-4129-9bbc-d1329f6b0998'}`, + ); + + expect(response.status).toEqual(200); + expect(response.body).toEqual(builds); + }); + }); + + describe('GET /repo-builds/:projectName/:repoName', () => { + 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: 'completed', + result: 'partiallySucceeded', + queueTime: '2020-09-12T06:10:23.9325232Z' as unknown as Date, + 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: 'inProgress', + result: 'none', + queueTime: '2020-09-12T06:20:23.9325232Z' as unknown as Date, + 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: 'completed', + result: 'succeeded', + queueTime: '2020-09-12T06:30:23.9325232Z' as unknown as Date, + source: 'refs/heads/develop (9bedf678)', + }; + + const repoBuilds: RepoBuild[] = [ + firstRepoBuild, + secondRepoBuild, + thirdRepoBuild, + ]; + + azureDevOpsApi.getRepoBuilds.mockResolvedValueOnce(repoBuilds); + + const response = await request(app).get( + `/repo-builds/${'myProject'}/${'myRepo'}`, + ); + + expect(response.status).toEqual(200); + expect(response.body).toEqual(repoBuilds); + }); + }); +}); diff --git a/plugins/azure-devops-backend/src/service/router.ts b/plugins/azure-devops-backend/src/service/router.ts new file mode 100644 index 0000000000..ad35fcf06e --- /dev/null +++ b/plugins/azure-devops-backend/src/service/router.ts @@ -0,0 +1,110 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { errorHandler } from '@backstage/backend-common'; +import express from 'express'; +import Router from 'express-promise-router'; +import { Logger } from 'winston'; +import { Config } from '@backstage/config'; +import { getPersonalAccessTokenHandler, WebApi } from 'azure-devops-node-api'; +import { AzureDevOpsApi } from '../api'; + +export interface RouterOptions { + azureDevOpsApi?: AzureDevOpsApi; + logger: Logger; + config: Config; +} + +export async function createRouter( + options: RouterOptions, +): Promise { + const { logger } = options; + const config = options.config.getConfig('azureDevOps'); + + const token: string = config.getString('token'); + const host: string = config.getString('host'); + const organization: string = config.getString('organization'); + const top: number = config.getOptionalNumber('top') || 10; + + const authHandler = getPersonalAccessTokenHandler(token); + const webApi = new WebApi(`https://${host}/${organization}`, authHandler); + + const azureDevOpsApi = + options.azureDevOpsApi || new AzureDevOpsApi(logger, webApi, top); + + const router = Router(); + router.use(express.json()); + + router.get('/health', (_, response) => { + let code: number = 200; + let status: string = ''; + let details: string = ''; + + if (token && host && organization) { + code = 200; + status = 'Healthy'; + details = 'All required config has been provided'; + } + + if (!token) { + code = 500; + status = 'Unhealthy'; + details = 'Token is missing'; + } + + if (!host) { + code = 500; + status = 'Unhealthy'; + details = 'Host is missing'; + } + + if (!organization) { + code = 500; + status = 'Unhealthy'; + details = 'Organization is missing'; + } + + logger.info('PONG!'); + response.status(code).send({ status: status, details: details }); + }); + + router.get('/repository/:projectName/:repoName', async (req, res) => { + const { projectName, repoName } = req.params; + const gitRepository = await azureDevOpsApi.getGitRepository( + projectName, + repoName, + ); + res.status(200).send(gitRepository); + }); + + router.get('/builds/:projectName/:repoId', async (req, res) => { + const { projectName, repoId } = req.params; + const buildList = await azureDevOpsApi.getBuildList(projectName, repoId); + res.status(200).send(buildList); + }); + + router.get('/repo-builds/:projectName/:repoName', async (req, res) => { + const { projectName, repoName } = req.params; + const gitRepository = await azureDevOpsApi.getRepoBuilds( + projectName, + repoName, + ); + res.status(200).send(gitRepository); + }); + + router.use(errorHandler()); + return router; +} diff --git a/plugins/azure-devops-backend/src/service/standaloneServer.ts b/plugins/azure-devops-backend/src/service/standaloneServer.ts new file mode 100644 index 0000000000..de7a911318 --- /dev/null +++ b/plugins/azure-devops-backend/src/service/standaloneServer.ts @@ -0,0 +1,57 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + createServiceBuilder, + loadBackendConfig, +} from '@backstage/backend-common'; +import { Server } from 'http'; +import { Logger } from 'winston'; +import { createRouter } from './router'; + +export interface ServerOptions { + port: number; + enableCors: boolean; + logger: Logger; +} + +export async function startStandaloneServer( + options: ServerOptions, +): Promise { + const logger = options.logger.child({ service: 'azure-devops-backend' }); + const config = await loadBackendConfig({ logger, argv: process.argv }); + + logger.debug('Starting application server...'); + + const router = await createRouter({ + logger, + config, + }); + + let service = createServiceBuilder(module) + .setPort(options.port) + .addRouter('/azure-devops', router); + if (options.enableCors) { + service = service.enableCors({ origin: 'http://localhost:3000' }); + } + + return await service.start().catch(err => { + logger.error(err); + process.exit(1); + }); +} + +module.hot?.accept(); diff --git a/plugins/azure-devops-backend/src/setupTests.ts b/plugins/azure-devops-backend/src/setupTests.ts new file mode 100644 index 0000000000..d3232290a7 --- /dev/null +++ b/plugins/azure-devops-backend/src/setupTests.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export {}; From b1b7ddee39776202d5688bf1e3311915547e5763 Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Wed, 15 Sep 2021 08:37:13 -0500 Subject: [PATCH 02/18] Added missing new line Signed-off-by: Andre Wanlin --- app-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app-config.yaml b/app-config.yaml index 49f7890675..50c8039605 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -405,4 +405,4 @@ azureDevOps: host: dev.azure.com token: ${AZURE_TOKEN} organization: my-company - top: 50 \ No newline at end of file + top: 50 From 3e65c7823858d6076affb2cf0dbe3b2ee3bb7482 Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Wed, 15 Sep 2021 14:24:41 -0500 Subject: [PATCH 03/18] Added API Report Signed-off-by: Andre Wanlin --- plugins/azure-devops-backend/api-report.md | 33 ++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 plugins/azure-devops-backend/api-report.md diff --git a/plugins/azure-devops-backend/api-report.md b/plugins/azure-devops-backend/api-report.md new file mode 100644 index 0000000000..35f4ff606d --- /dev/null +++ b/plugins/azure-devops-backend/api-report.md @@ -0,0 +1,33 @@ +## API Report File for "@backstage/plugin-azure-devops-backend" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { Build } from 'azure-devops-node-api/interfaces/BuildInterfaces'; +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 { WebApi } from 'azure-devops-node-api'; + +// Warning: (ae-missing-release-tag) "createRouter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export function createRouter(options: RouterOptions): Promise; + +// Warning: (ae-missing-release-tag) "RouterOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface RouterOptions { + // Warning: (ae-forgotten-export) The symbol "AzureDevOpsApi" needs to be exported by the entry point index.d.ts + // + // (undocumented) + azureDevOpsApi?: AzureDevOpsApi; + // (undocumented) + config: Config; + // (undocumented) + logger: Logger_2; +} + +// (No @packageDocumentation comment for this package) +``` From 94e4ea6e9d95ffa140eddcadae6b48dca08c2233 Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Wed, 15 Sep 2021 14:41:15 -0500 Subject: [PATCH 04/18] Removed link causing issues with CI build Signed-off-by: Andre Wanlin --- plugins/azure-devops-backend/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/azure-devops-backend/README.md b/plugins/azure-devops-backend/README.md index 91c69b74ea..12a916eeef 100644 --- a/plugins/azure-devops-backend/README.md +++ b/plugins/azure-devops-backend/README.md @@ -23,5 +23,4 @@ Configuration Details: ## Links -- [Frontend part of the plugin](https://github.com/backstage/backstage/tree/master/plugins/azure-devops) - [The Backstage homepage](https://backstage.io) From 46a17f1dbe85e6394b4242836666da7668ab8a08 Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Fri, 17 Sep 2021 14:21:23 -0500 Subject: [PATCH 05/18] Fixed package settings Signed-off-by: Andre Wanlin --- plugins/azure-devops-backend/package.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/plugins/azure-devops-backend/package.json b/plugins/azure-devops-backend/package.json index 216f530a8b..c9acc33d77 100644 --- a/plugins/azure-devops-backend/package.json +++ b/plugins/azure-devops-backend/package.json @@ -4,6 +4,7 @@ "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", + "private": false, "publishConfig": { "access": "public", "main": "dist/index.cjs.js", @@ -34,7 +35,9 @@ "supertest": "^4.0.2", "msw": "^0.29.0" }, - "files": [ - "dist" - ] + "files": [ + "dist", + "config.d.ts" + ], + "configSchema": "config.d.ts" } From a599a8c4f26dfedecaaf8c6a8fa5d75d99c3323a Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Fri, 17 Sep 2021 14:24:19 -0500 Subject: [PATCH 06/18] Corrected year in file header Signed-off-by: Andre Wanlin --- plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts | 2 +- plugins/azure-devops-backend/src/api/index.ts | 2 +- plugins/azure-devops-backend/src/api/types.ts | 2 +- plugins/azure-devops-backend/src/index.ts | 2 +- plugins/azure-devops-backend/src/run.ts | 2 +- plugins/azure-devops-backend/src/service/router.test.ts | 2 +- plugins/azure-devops-backend/src/service/router.ts | 2 +- plugins/azure-devops-backend/src/service/standaloneServer.ts | 2 +- plugins/azure-devops-backend/src/setupTests.ts | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts index a2b7518069..d39c41448d 100644 --- a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts +++ b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/azure-devops-backend/src/api/index.ts b/plugins/azure-devops-backend/src/api/index.ts index 1ff69dbb4a..903c1bdb6a 100644 --- a/plugins/azure-devops-backend/src/api/index.ts +++ b/plugins/azure-devops-backend/src/api/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/azure-devops-backend/src/api/types.ts b/plugins/azure-devops-backend/src/api/types.ts index 981fbd4d1b..06068953ef 100644 --- a/plugins/azure-devops-backend/src/api/types.ts +++ b/plugins/azure-devops-backend/src/api/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/azure-devops-backend/src/index.ts b/plugins/azure-devops-backend/src/index.ts index ca73cb27ba..f2c8407292 100644 --- a/plugins/azure-devops-backend/src/index.ts +++ b/plugins/azure-devops-backend/src/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/azure-devops-backend/src/run.ts b/plugins/azure-devops-backend/src/run.ts index 54d2716290..addfdfd6d7 100644 --- a/plugins/azure-devops-backend/src/run.ts +++ b/plugins/azure-devops-backend/src/run.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/azure-devops-backend/src/service/router.test.ts b/plugins/azure-devops-backend/src/service/router.test.ts index 644f8dd77c..d237076e9c 100644 --- a/plugins/azure-devops-backend/src/service/router.test.ts +++ b/plugins/azure-devops-backend/src/service/router.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/azure-devops-backend/src/service/router.ts b/plugins/azure-devops-backend/src/service/router.ts index ad35fcf06e..8a71138653 100644 --- a/plugins/azure-devops-backend/src/service/router.ts +++ b/plugins/azure-devops-backend/src/service/router.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/azure-devops-backend/src/service/standaloneServer.ts b/plugins/azure-devops-backend/src/service/standaloneServer.ts index de7a911318..007ad32c62 100644 --- a/plugins/azure-devops-backend/src/service/standaloneServer.ts +++ b/plugins/azure-devops-backend/src/service/standaloneServer.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/azure-devops-backend/src/setupTests.ts b/plugins/azure-devops-backend/src/setupTests.ts index d3232290a7..a330613afb 100644 --- a/plugins/azure-devops-backend/src/setupTests.ts +++ b/plugins/azure-devops-backend/src/setupTests.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 0d2e75ba9d82ad825b06f42aca210f7c93e8118a Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Fri, 17 Sep 2021 14:37:15 -0500 Subject: [PATCH 07/18] Cleaning up router and its tests Signed-off-by: Andre Wanlin --- plugins/azure-devops-backend/src/service/router.test.ts | 6 +++--- plugins/azure-devops-backend/src/service/router.ts | 9 ++++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/plugins/azure-devops-backend/src/service/router.test.ts b/plugins/azure-devops-backend/src/service/router.test.ts index d237076e9c..7a72eae939 100644 --- a/plugins/azure-devops-backend/src/service/router.test.ts +++ b/plugins/azure-devops-backend/src/service/router.test.ts @@ -83,7 +83,7 @@ describe('createRouter', () => { azureDevOpsApi.getGitRepository.mockResolvedValueOnce(gitRepository); const response = await request(app).get( - `/repository/${'myProject'}/${'myRepo'}`, + '/repository/myProject/myRepo', ); expect(response.status).toEqual(200); @@ -128,7 +128,7 @@ describe('createRouter', () => { azureDevOpsApi.getBuildList.mockResolvedValueOnce(builds); const response = await request(app).get( - `/builds/${'myProject'}/${'af4ae3af-e747-4129-9bbc-d1329f6b0998'}`, + '/builds/myProject/af4ae3af-e747-4129-9bbc-d1329f6b0998', ); expect(response.status).toEqual(200); @@ -177,7 +177,7 @@ describe('createRouter', () => { azureDevOpsApi.getRepoBuilds.mockResolvedValueOnce(repoBuilds); const response = await request(app).get( - `/repo-builds/${'myProject'}/${'myRepo'}`, + '/repo-builds/myProject/myRepo', ); expect(response.status).toEqual(200); diff --git a/plugins/azure-devops-backend/src/service/router.ts b/plugins/azure-devops-backend/src/service/router.ts index 8a71138653..fd60192c0c 100644 --- a/plugins/azure-devops-backend/src/service/router.ts +++ b/plugins/azure-devops-backend/src/service/router.ts @@ -77,8 +77,7 @@ export async function createRouter( details = 'Organization is missing'; } - logger.info('PONG!'); - response.status(code).send({ status: status, details: details }); + response.status(code).json({ status: status, details: details }); }); router.get('/repository/:projectName/:repoName', async (req, res) => { @@ -87,13 +86,13 @@ export async function createRouter( projectName, repoName, ); - res.status(200).send(gitRepository); + res.status(200).json(gitRepository); }); router.get('/builds/:projectName/:repoId', async (req, res) => { const { projectName, repoId } = req.params; const buildList = await azureDevOpsApi.getBuildList(projectName, repoId); - res.status(200).send(buildList); + res.status(200).json(buildList); }); router.get('/repo-builds/:projectName/:repoName', async (req, res) => { @@ -102,7 +101,7 @@ export async function createRouter( projectName, repoName, ); - res.status(200).send(gitRepository); + res.status(200).json(gitRepository); }); router.use(errorHandler()); From b6c5102d46fa355e6411948e7899ac71d6399dcd Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Fri, 17 Sep 2021 15:33:45 -0500 Subject: [PATCH 08/18] Added expect for arguments Signed-off-by: Andre Wanlin --- .../src/service/router.test.ts | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/plugins/azure-devops-backend/src/service/router.test.ts b/plugins/azure-devops-backend/src/service/router.test.ts index 7a72eae939..c49776c047 100644 --- a/plugins/azure-devops-backend/src/service/router.test.ts +++ b/plugins/azure-devops-backend/src/service/router.test.ts @@ -82,10 +82,12 @@ describe('createRouter', () => { azureDevOpsApi.getGitRepository.mockResolvedValueOnce(gitRepository); - const response = await request(app).get( - '/repository/myProject/myRepo', - ); + const response = await request(app).get('/repository/myProject/myRepo'); + expect(azureDevOpsApi.getGitRepository.mock.calls[0]).toEqual([ + 'myProject', + 'myRepo', + ]); expect(response.status).toEqual(200); expect(response.body).toEqual(gitRepository); }); @@ -131,6 +133,10 @@ describe('createRouter', () => { '/builds/myProject/af4ae3af-e747-4129-9bbc-d1329f6b0998', ); + expect(azureDevOpsApi.getBuildList.mock.calls[0]).toEqual([ + 'myProject', + 'af4ae3af-e747-4129-9bbc-d1329f6b0998', + ]); expect(response.status).toEqual(200); expect(response.body).toEqual(builds); }); @@ -176,10 +182,12 @@ describe('createRouter', () => { azureDevOpsApi.getRepoBuilds.mockResolvedValueOnce(repoBuilds); - const response = await request(app).get( - '/repo-builds/myProject/myRepo', - ); + const response = await request(app).get('/repo-builds/myProject/myRepo'); + expect(azureDevOpsApi.getRepoBuilds.mock.calls[0]).toEqual([ + 'myProject', + 'myRepo', + ]); expect(response.status).toEqual(200); expect(response.body).toEqual(repoBuilds); }); From a18f2d2a96ff48095ed390205b5620a0920c2ce1 Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Fri, 17 Sep 2021 16:00:03 -0500 Subject: [PATCH 09/18] Moved top so it will be provided by the future frontend Signed-off-by: Andre Wanlin --- app-config.yaml | 1 - plugins/azure-devops-backend/README.md | 2 -- plugins/azure-devops-backend/config.d.ts | 4 ---- .../src/api/AzureDevOpsApi.ts | 20 ++++++++++--------- .../src/service/router.test.ts | 12 +++++++---- .../src/service/router.ts | 18 ++++++++++------- 6 files changed, 30 insertions(+), 27 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index 50c8039605..3e2ab8471d 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -405,4 +405,3 @@ azureDevOps: host: dev.azure.com token: ${AZURE_TOKEN} organization: my-company - top: 50 diff --git a/plugins/azure-devops-backend/README.md b/plugins/azure-devops-backend/README.md index 12a916eeef..4b278572d8 100644 --- a/plugins/azure-devops-backend/README.md +++ b/plugins/azure-devops-backend/README.md @@ -11,7 +11,6 @@ azureDevOps: host: dev.azure.com token: ${AZURE_TOKEN} organization: my-company - top: 50 ``` Configuration Details: @@ -19,7 +18,6 @@ Configuration Details: - `host` and `token` can be the same as the ones used for the `integration` section - `AZURE_TOKEN` environment variable must be set to a [Personal Access Token](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=preview-page) with read access to both Code and Build - `organization` is your Azure DevOps Organization name or for Azure DevOps Server (on-premise) this will be your Collection name -- `top` sets the max number of items to return - currently only applies to builds ## Links diff --git a/plugins/azure-devops-backend/config.d.ts b/plugins/azure-devops-backend/config.d.ts index 613881c675..9d4e896a89 100644 --- a/plugins/azure-devops-backend/config.d.ts +++ b/plugins/azure-devops-backend/config.d.ts @@ -30,9 +30,5 @@ export interface Config { * The organization of the given Azure instance */ organization: string; - /** - * The max number of items to return - applies to builds - */ - top: number; }; } diff --git a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts index d39c41448d..b814445d74 100644 --- a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts +++ b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts @@ -26,12 +26,11 @@ export class AzureDevOpsApi { constructor( private readonly logger: Logger, private readonly webApi: WebApi, - private readonly top: number, ) {} async getGitRepository(projectName: string, repoName: string) { if (this.logger) { - this.logger.info( + this.logger.debug( `Calling Azure DevOps REST API, getting Repository ${repoName} for Project ${projectName}`, ); } @@ -40,10 +39,12 @@ export class AzureDevOpsApi { return client.getRepository(repoName, projectName); } - async getBuildList(projectName: string, repoId: string) { + async getBuildList(projectName: string, repoId: string, top: string) { + const topBuilds: number = +top || 10; + if (this.logger) { - this.logger.info( - `Calling Azure DevOps REST API, getting up to ${this.top} Builds for Repository Id ${repoId} for Project ${projectName}`, + this.logger.debug( + `Calling Azure DevOps REST API, getting up to ${topBuilds} Builds for Repository Id ${repoId} for Project ${projectName}`, ); } @@ -61,7 +62,7 @@ export class AzureDevOpsApi { undefined, undefined, undefined, - this.top, + topBuilds, undefined, undefined, undefined, @@ -73,10 +74,10 @@ export class AzureDevOpsApi { ); } - async getRepoBuilds(projectName: string, repoName: string) { + async getRepoBuilds(projectName: string, repoName: string, top: string) { if (this.logger) { - this.logger.info( - `Calling Azure DevOps REST API, getting up to ${this.top} Builds for Repository ${repoName} for Project ${projectName}`, + this.logger.debug( + `Calling Azure DevOps REST API, getting up to ${top} Builds for Repository ${repoName} for Project ${projectName}`, ); } @@ -84,6 +85,7 @@ export class AzureDevOpsApi { const buildList = await this.getBuildList( projectName, gitRepository.id as string, + top, ); const repoBuilds = buildList.map(build => { diff --git a/plugins/azure-devops-backend/src/service/router.test.ts b/plugins/azure-devops-backend/src/service/router.test.ts index c49776c047..b8b888e41c 100644 --- a/plugins/azure-devops-backend/src/service/router.test.ts +++ b/plugins/azure-devops-backend/src/service/router.test.ts @@ -93,7 +93,7 @@ describe('createRouter', () => { }); }); - describe('GET /builds/:projectName/:repoId', () => { + describe('GET /builds/:projectName/:repoId/:top', () => { it('fetches a list of builds', async () => { const firstBuild: Build = { id: 1, @@ -130,19 +130,20 @@ describe('createRouter', () => { azureDevOpsApi.getBuildList.mockResolvedValueOnce(builds); const response = await request(app).get( - '/builds/myProject/af4ae3af-e747-4129-9bbc-d1329f6b0998', + '/builds/myProject/af4ae3af-e747-4129-9bbc-d1329f6b0998/50', ); expect(azureDevOpsApi.getBuildList.mock.calls[0]).toEqual([ 'myProject', 'af4ae3af-e747-4129-9bbc-d1329f6b0998', + '50', ]); expect(response.status).toEqual(200); expect(response.body).toEqual(builds); }); }); - describe('GET /repo-builds/:projectName/:repoName', () => { + describe('GET /repo-builds/:projectName/:repoName/:top', () => { it('fetches a list of repo builds', async () => { const firstRepoBuild: RepoBuild = { id: 1, @@ -182,11 +183,14 @@ describe('createRouter', () => { azureDevOpsApi.getRepoBuilds.mockResolvedValueOnce(repoBuilds); - const response = await request(app).get('/repo-builds/myProject/myRepo'); + const response = await request(app).get( + '/repo-builds/myProject/myRepo/50', + ); expect(azureDevOpsApi.getRepoBuilds.mock.calls[0]).toEqual([ 'myProject', 'myRepo', + '50', ]); expect(response.status).toEqual(200); expect(response.body).toEqual(repoBuilds); diff --git a/plugins/azure-devops-backend/src/service/router.ts b/plugins/azure-devops-backend/src/service/router.ts index fd60192c0c..c1548f7701 100644 --- a/plugins/azure-devops-backend/src/service/router.ts +++ b/plugins/azure-devops-backend/src/service/router.ts @@ -37,13 +37,12 @@ export async function createRouter( const token: string = config.getString('token'); const host: string = config.getString('host'); const organization: string = config.getString('organization'); - const top: number = config.getOptionalNumber('top') || 10; const authHandler = getPersonalAccessTokenHandler(token); const webApi = new WebApi(`https://${host}/${organization}`, authHandler); const azureDevOpsApi = - options.azureDevOpsApi || new AzureDevOpsApi(logger, webApi, top); + options.azureDevOpsApi || new AzureDevOpsApi(logger, webApi); const router = Router(); router.use(express.json()); @@ -89,17 +88,22 @@ export async function createRouter( res.status(200).json(gitRepository); }); - router.get('/builds/:projectName/:repoId', async (req, res) => { - const { projectName, repoId } = req.params; - const buildList = await azureDevOpsApi.getBuildList(projectName, repoId); + router.get('/builds/:projectName/:repoId/:top', async (req, res) => { + const { projectName, repoId, top } = req.params; + const buildList = await azureDevOpsApi.getBuildList( + projectName, + repoId, + top, + ); res.status(200).json(buildList); }); - router.get('/repo-builds/:projectName/:repoName', async (req, res) => { - const { projectName, repoName } = req.params; + router.get('/repo-builds/:projectName/:repoName/:top', async (req, res) => { + const { projectName, repoName, top } = req.params; const gitRepository = await azureDevOpsApi.getRepoBuilds( projectName, repoName, + top, ); res.status(200).json(gitRepository); }); From c6fdf864b3becaa7d0eeef8ca64c00777be911d7 Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Fri, 17 Sep 2021 16:25:58 -0500 Subject: [PATCH 10/18] Corrected imports Signed-off-by: Andre Wanlin --- .../azure-devops-backend/src/api/AzureDevOpsApi.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts index b814445d74..c4291d24d3 100644 --- a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts +++ b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts @@ -17,10 +17,6 @@ import { Logger } from 'winston'; import { WebApi } from 'azure-devops-node-api'; import { RepoBuild } from './types'; -import { - BuildResult, - BuildStatus, -} from 'azure-devops-node-api/interfaces/BuildInterfaces'; export class AzureDevOpsApi { constructor( @@ -90,12 +86,12 @@ export class AzureDevOpsApi { const repoBuilds = buildList.map(build => { const repoBuild: RepoBuild = { - id: build.id as number, + id: build.id, title: `${build.definition?.name} - ${build.buildNumber}`, link: build._links?.web.href, - status: BuildStatus[build.status as BuildStatus], - result: BuildResult[build.result as BuildResult], - queueTime: build.queueTime as Date, + status: build.status, + result: build.result, + queueTime: build.queueTime, source: `${build.sourceBranch} (${build.sourceVersion?.substr(0, 8)})`, }; return repoBuild; From 94373feca1b1072bd7dcea52df91d0627a8eb6b5 Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Fri, 17 Sep 2021 16:31:51 -0500 Subject: [PATCH 11/18] Type fixes Signed-off-by: Andre Wanlin --- plugins/azure-devops-backend/src/api/types.ts | 13 +++++++++---- .../azure-devops-backend/src/service/router.test.ts | 12 ++++++------ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/plugins/azure-devops-backend/src/api/types.ts b/plugins/azure-devops-backend/src/api/types.ts index 06068953ef..35d4e38ae1 100644 --- a/plugins/azure-devops-backend/src/api/types.ts +++ b/plugins/azure-devops-backend/src/api/types.ts @@ -14,12 +14,17 @@ * limitations under the License. */ +import { + BuildResult, + BuildStatus, +} from 'azure-devops-node-api/interfaces/BuildInterfaces'; + export type RepoBuild = { - id: number; + id?: number; title: string; link: string; - status: string; - result: string; - queueTime: Date; + status?: BuildStatus; + result?: BuildResult; + queueTime?: Date; source: string; }; diff --git a/plugins/azure-devops-backend/src/service/router.test.ts b/plugins/azure-devops-backend/src/service/router.test.ts index b8b888e41c..56693bccbd 100644 --- a/plugins/azure-devops-backend/src/service/router.test.ts +++ b/plugins/azure-devops-backend/src/service/router.test.ts @@ -149,8 +149,8 @@ describe('createRouter', () => { id: 1, title: 'My Build Definition - Build 1', link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', - status: 'completed', - result: 'partiallySucceeded', + status: BuildStatus.Completed, + result: BuildResult.PartiallySucceeded, queueTime: '2020-09-12T06:10:23.9325232Z' as unknown as Date, source: 'refs/heads/develop (f4f78b31)', }; @@ -159,8 +159,8 @@ describe('createRouter', () => { id: 2, title: 'My Build Definition - Build 2', link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=2', - status: 'inProgress', - result: 'none', + status: BuildStatus.InProgress, + result: BuildResult.None, queueTime: '2020-09-12T06:20:23.9325232Z' as unknown as Date, source: 'refs/heads/develop (13c988d4)', }; @@ -169,8 +169,8 @@ describe('createRouter', () => { id: 3, title: 'My Build Definition - Build 3', link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=3', - status: 'completed', - result: 'succeeded', + status: BuildStatus.Completed, + result: BuildResult.Succeeded, queueTime: '2020-09-12T06:30:23.9325232Z' as unknown as Date, source: 'refs/heads/develop (9bedf678)', }; From bb256781b07cfc7abd0a503d0fc34f88fe127ee5 Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Fri, 17 Sep 2021 16:42:34 -0500 Subject: [PATCH 12/18] Fixed up API Report Signed-off-by: Andre Wanlin --- plugins/azure-devops-backend/api-report.md | 41 ++++++++++++++++++- plugins/azure-devops-backend/src/api/index.ts | 1 + plugins/azure-devops-backend/src/index.ts | 3 +- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/plugins/azure-devops-backend/api-report.md b/plugins/azure-devops-backend/api-report.md index 35f4ff606d..513fd64f04 100644 --- a/plugins/azure-devops-backend/api-report.md +++ b/plugins/azure-devops-backend/api-report.md @@ -4,23 +4,60 @@ ```ts import { Build } from 'azure-devops-node-api/interfaces/BuildInterfaces'; +import { BuildResult } from 'azure-devops-node-api/interfaces/BuildInterfaces'; +import { BuildStatus } from 'azure-devops-node-api/interfaces/BuildInterfaces'; 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 { 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) +// +// @public (undocumented) +export class AzureDevOpsApi { + constructor(logger: Logger_2, webApi: WebApi); + // (undocumented) + getBuildList( + projectName: string, + repoId: string, + top: string, + ): Promise; + // (undocumented) + getGitRepository( + projectName: string, + repoName: string, + ): Promise; + // (undocumented) + getRepoBuilds( + projectName: string, + repoName: string, + top: string, + ): Promise; +} + // Warning: (ae-missing-release-tag) "createRouter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export function createRouter(options: RouterOptions): Promise; +// 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) +export type RepoBuild = { + id?: number; + title: string; + link: string; + status?: BuildStatus; + result?: BuildResult; + queueTime?: Date; + source: string; +}; + // Warning: (ae-missing-release-tag) "RouterOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export interface RouterOptions { - // Warning: (ae-forgotten-export) The symbol "AzureDevOpsApi" needs to be exported by the entry point index.d.ts - // // (undocumented) azureDevOpsApi?: AzureDevOpsApi; // (undocumented) diff --git a/plugins/azure-devops-backend/src/api/index.ts b/plugins/azure-devops-backend/src/api/index.ts index 903c1bdb6a..8faf37fe4e 100644 --- a/plugins/azure-devops-backend/src/api/index.ts +++ b/plugins/azure-devops-backend/src/api/index.ts @@ -15,3 +15,4 @@ */ export { AzureDevOpsApi } from './AzureDevOpsApi'; +export type { RepoBuild } from './types'; diff --git a/plugins/azure-devops-backend/src/index.ts b/plugins/azure-devops-backend/src/index.ts index f2c8407292..a7004ec73e 100644 --- a/plugins/azure-devops-backend/src/index.ts +++ b/plugins/azure-devops-backend/src/index.ts @@ -13,5 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +export { AzureDevOpsApi } from './api'; +export type { RepoBuild } from './api'; export * from './service/router'; From 74de9a0d1e8d1ba1caf6db61bbc5c90a5b2c7899 Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Fri, 17 Sep 2021 17:29:34 -0500 Subject: [PATCH 13/18] Corrected formatting Signed-off-by: Andre Wanlin --- plugins/azure-devops-backend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/azure-devops-backend/package.json b/plugins/azure-devops-backend/package.json index c9acc33d77..cfd70bed2c 100644 --- a/plugins/azure-devops-backend/package.json +++ b/plugins/azure-devops-backend/package.json @@ -35,7 +35,7 @@ "supertest": "^4.0.2", "msw": "^0.29.0" }, - "files": [ + "files": [ "dist", "config.d.ts" ], From f23349832b9bbbb38287bd8677ff62edc5882e68 Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Sat, 25 Sep 2021 11:48:37 -0500 Subject: [PATCH 14/18] Extracted function and added tests Signed-off-by: Andre Wanlin --- .../src/api/AzureDevOpsApi.test.ts | 97 +++++++++++++++++++ .../src/api/AzureDevOpsApi.ts | 28 +++--- 2 files changed, 114 insertions(+), 11 deletions(-) create mode 100644 plugins/azure-devops-backend/src/api/AzureDevOpsApi.test.ts diff --git a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.test.ts b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.test.ts new file mode 100644 index 0000000000..077badfba1 --- /dev/null +++ b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.test.ts @@ -0,0 +1,97 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { repoBuildFromBuild } from './AzureDevOpsApi'; +import { RepoBuild } from './types'; +import { + Build, + BuildResult, + BuildStatus, + DefinitionReference, +} from 'azure-devops-node-api/interfaces/BuildInterfaces'; + +describe('AzureDevOpsApi', () => { + describe('repoBuildFromBuild', () => { + 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 inputBuild: Build = { + id: 1, + buildNumber: 'Build-1', + status: BuildStatus.Completed, + result: BuildResult.Succeeded, + queueTime: new Date('2020-09-12T06:10:23.9325232Z'), + sourceBranch: 'refs/heads/develop', + sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c', + definition: inputBuildDefinition, + _links: inputLinks, + }; + + 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: new Date('2020-09-12T06:10:23.9325232Z'), + source: 'refs/heads/develop (f4f78b31)', + }; + + expect(repoBuildFromBuild(inputBuild)).toEqual(outputRepoBuild); + }); + }); + + describe('repoBuildFromBuild 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 inputBuild: Build = { + id: 1, + buildNumber: 'Build-1', + status: BuildStatus.Completed, + result: BuildResult.Succeeded, + queueTime: new Date('2020-09-12T06:10:23.9325232Z'), + sourceBranch: 'refs/heads/develop', + sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c', + definition: undefined, + _links: inputLinks, + }; + + 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: new Date('2020-09-12T06:10:23.9325232Z'), + source: 'refs/heads/develop (f4f78b31)', + }; + + expect(repoBuildFromBuild(inputBuild)).toEqual(outputRepoBuild); + }); + }); +}); diff --git a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts index c4291d24d3..a127770242 100644 --- a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts +++ b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts @@ -17,6 +17,7 @@ import { Logger } from 'winston'; import { WebApi } from 'azure-devops-node-api'; import { RepoBuild } from './types'; +import { Build } from 'azure-devops-node-api/interfaces/BuildInterfaces'; export class AzureDevOpsApi { constructor( @@ -84,19 +85,24 @@ export class AzureDevOpsApi { top, ); - const repoBuilds = buildList.map(build => { - const repoBuild: RepoBuild = { - id: build.id, - title: `${build.definition?.name} - ${build.buildNumber}`, - link: build._links?.web.href, - status: build.status, - result: build.result, - queueTime: build.queueTime, - source: `${build.sourceBranch} (${build.sourceVersion?.substr(0, 8)})`, - }; - return repoBuild; + const repoBuilds: RepoBuild[] = buildList.map(build => { + return repoBuildFromBuild(build); }); return repoBuilds; } } + +export function repoBuildFromBuild(build: Build) { + return { + id: build.id, + title: [build.definition?.name, build.buildNumber] + .filter(Boolean) + .join(' - '), + link: build._links?.web.href, + status: build.status, + result: build.result, + queueTime: build.queueTime, + source: `${build.sourceBranch} (${build.sourceVersion?.substr(0, 8)})`, + }; +} From a058de29e00645e38315a202318ff4165e9a9d2d Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Sat, 25 Sep 2021 11:51:17 -0500 Subject: [PATCH 15/18] Updated tests to use toHaveBeenCalledWith Signed-off-by: Andre Wanlin --- .../azure-devops-backend/src/service/router.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/azure-devops-backend/src/service/router.test.ts b/plugins/azure-devops-backend/src/service/router.test.ts index 56693bccbd..f2ae711af4 100644 --- a/plugins/azure-devops-backend/src/service/router.test.ts +++ b/plugins/azure-devops-backend/src/service/router.test.ts @@ -84,10 +84,10 @@ describe('createRouter', () => { const response = await request(app).get('/repository/myProject/myRepo'); - expect(azureDevOpsApi.getGitRepository.mock.calls[0]).toEqual([ + expect(azureDevOpsApi.getGitRepository).toHaveBeenCalledWith( 'myProject', 'myRepo', - ]); + ); expect(response.status).toEqual(200); expect(response.body).toEqual(gitRepository); }); @@ -133,11 +133,11 @@ describe('createRouter', () => { '/builds/myProject/af4ae3af-e747-4129-9bbc-d1329f6b0998/50', ); - expect(azureDevOpsApi.getBuildList.mock.calls[0]).toEqual([ + expect(azureDevOpsApi.getBuildList).toHaveBeenCalledWith( 'myProject', 'af4ae3af-e747-4129-9bbc-d1329f6b0998', '50', - ]); + ); expect(response.status).toEqual(200); expect(response.body).toEqual(builds); }); @@ -187,11 +187,11 @@ describe('createRouter', () => { '/repo-builds/myProject/myRepo/50', ); - expect(azureDevOpsApi.getRepoBuilds.mock.calls[0]).toEqual([ + expect(azureDevOpsApi.getRepoBuilds).toHaveBeenCalledWith( 'myProject', 'myRepo', '50', - ]); + ); expect(response.status).toEqual(200); expect(response.body).toEqual(repoBuilds); }); From 1984dab1016880177d1833bcfceb1a6ee9f21a70 Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Sat, 25 Sep 2021 15:07:47 -0500 Subject: [PATCH 16/18] Refactored top to be number from query string Signed-off-by: Andre Wanlin --- .../src/api/AzureDevOpsApi.ts | 10 +++--- .../src/service/router.test.ts | 32 +++++++++---------- .../src/service/router.ts | 12 ++++--- 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts index a127770242..13d5b391f6 100644 --- a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts +++ b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts @@ -36,12 +36,10 @@ export class AzureDevOpsApi { return client.getRepository(repoName, projectName); } - async getBuildList(projectName: string, repoId: string, top: string) { - const topBuilds: number = +top || 10; - + async getBuildList(projectName: string, repoId: string, top: number) { if (this.logger) { this.logger.debug( - `Calling Azure DevOps REST API, getting up to ${topBuilds} Builds for Repository Id ${repoId} for Project ${projectName}`, + `Calling Azure DevOps REST API, getting up to ${top} Builds for Repository Id ${repoId} for Project ${projectName}`, ); } @@ -59,7 +57,7 @@ export class AzureDevOpsApi { undefined, undefined, undefined, - topBuilds, + top, undefined, undefined, undefined, @@ -71,7 +69,7 @@ export class AzureDevOpsApi { ); } - async getRepoBuilds(projectName: string, repoName: string, top: string) { + async getRepoBuilds(projectName: string, repoName: string, top: number) { if (this.logger) { this.logger.debug( `Calling Azure DevOps REST API, getting up to ${top} Builds for Repository ${repoName} for Project ${projectName}`, diff --git a/plugins/azure-devops-backend/src/service/router.test.ts b/plugins/azure-devops-backend/src/service/router.test.ts index f2ae711af4..08d86c5cc8 100644 --- a/plugins/azure-devops-backend/src/service/router.test.ts +++ b/plugins/azure-devops-backend/src/service/router.test.ts @@ -93,14 +93,14 @@ describe('createRouter', () => { }); }); - describe('GET /builds/:projectName/:repoId/:top', () => { + describe('GET /builds/:projectName/:repoId', () => { it('fetches a list of builds', async () => { const firstBuild: Build = { id: 1, buildNumber: 'Build-1', status: BuildStatus.Completed, result: BuildResult.Succeeded, - queueTime: '2020-09-12T06:10:23.9325232Z' as unknown as Date, + queueTime: undefined, sourceBranch: 'refs/heads/develop', sourceVersion: '9bedf67800b2923982bdf60c89c57ce6fd2d9a1c', }; @@ -110,7 +110,7 @@ describe('createRouter', () => { buildNumber: 'Build-2', status: BuildStatus.InProgress, result: BuildResult.None, - queueTime: '2020-09-12T06:20:23.9325232Z' as unknown as Date, + queueTime: undefined, sourceBranch: 'refs/heads/develop', sourceVersion: '13c988d4f15e06bcdd0b0af290086a3079cdadb0', }; @@ -120,7 +120,7 @@ describe('createRouter', () => { buildNumber: 'Build-3', status: BuildStatus.Completed, result: BuildResult.PartiallySucceeded, - queueTime: '2020-09-12T06:30:23.9325232Z' as unknown as Date, + queueTime: undefined, sourceBranch: 'refs/heads/develop', sourceVersion: 'f4f78b319c308600eab015a5d6529add21660dc1', }; @@ -129,21 +129,21 @@ describe('createRouter', () => { azureDevOpsApi.getBuildList.mockResolvedValueOnce(builds); - const response = await request(app).get( - '/builds/myProject/af4ae3af-e747-4129-9bbc-d1329f6b0998/50', - ); + const response = await request(app) + .get('/builds/myProject/af4ae3af-e747-4129-9bbc-d1329f6b0998') + .query({ top: '40' }); expect(azureDevOpsApi.getBuildList).toHaveBeenCalledWith( 'myProject', 'af4ae3af-e747-4129-9bbc-d1329f6b0998', - '50', + 40, ); expect(response.status).toEqual(200); expect(response.body).toEqual(builds); }); }); - describe('GET /repo-builds/:projectName/:repoName/:top', () => { + describe('GET /repo-builds/:projectName/:repoName', () => { it('fetches a list of repo builds', async () => { const firstRepoBuild: RepoBuild = { id: 1, @@ -151,7 +151,7 @@ describe('createRouter', () => { link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', status: BuildStatus.Completed, result: BuildResult.PartiallySucceeded, - queueTime: '2020-09-12T06:10:23.9325232Z' as unknown as Date, + queueTime: undefined, source: 'refs/heads/develop (f4f78b31)', }; @@ -161,7 +161,7 @@ describe('createRouter', () => { link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=2', status: BuildStatus.InProgress, result: BuildResult.None, - queueTime: '2020-09-12T06:20:23.9325232Z' as unknown as Date, + queueTime: undefined, source: 'refs/heads/develop (13c988d4)', }; @@ -171,7 +171,7 @@ describe('createRouter', () => { link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=3', status: BuildStatus.Completed, result: BuildResult.Succeeded, - queueTime: '2020-09-12T06:30:23.9325232Z' as unknown as Date, + queueTime: undefined, source: 'refs/heads/develop (9bedf678)', }; @@ -183,14 +183,14 @@ describe('createRouter', () => { azureDevOpsApi.getRepoBuilds.mockResolvedValueOnce(repoBuilds); - const response = await request(app).get( - '/repo-builds/myProject/myRepo/50', - ); + const response = await request(app) + .get('/repo-builds/myProject/myRepo') + .query({ top: '50' }); expect(azureDevOpsApi.getRepoBuilds).toHaveBeenCalledWith( 'myProject', 'myRepo', - '50', + 50, ); expect(response.status).toEqual(200); expect(response.body).toEqual(repoBuilds); diff --git a/plugins/azure-devops-backend/src/service/router.ts b/plugins/azure-devops-backend/src/service/router.ts index c1548f7701..c223926848 100644 --- a/plugins/azure-devops-backend/src/service/router.ts +++ b/plugins/azure-devops-backend/src/service/router.ts @@ -22,6 +22,8 @@ import { Config } from '@backstage/config'; import { getPersonalAccessTokenHandler, WebApi } from 'azure-devops-node-api'; import { AzureDevOpsApi } from '../api'; +const DEFAULT_TOP: number = 10; + export interface RouterOptions { azureDevOpsApi?: AzureDevOpsApi; logger: Logger; @@ -88,8 +90,9 @@ export async function createRouter( res.status(200).json(gitRepository); }); - router.get('/builds/:projectName/:repoId/:top', async (req, res) => { - const { projectName, repoId, top } = req.params; + router.get('/builds/:projectName/:repoId', async (req, res) => { + const { projectName, repoId } = req.params; + const top = req.query.top ? Number(req.query.top) : DEFAULT_TOP; const buildList = await azureDevOpsApi.getBuildList( projectName, repoId, @@ -98,8 +101,9 @@ export async function createRouter( res.status(200).json(buildList); }); - router.get('/repo-builds/:projectName/:repoName/:top', async (req, res) => { - const { projectName, repoName, top } = req.params; + router.get('/repo-builds/:projectName/:repoName', async (req, res) => { + const { projectName, repoName } = req.params; + const top = req.query.top ? Number(req.query.top) : DEFAULT_TOP; const gitRepository = await azureDevOpsApi.getRepoBuilds( projectName, repoName, From e7d9bbb6a87147b425b980e0085d1fda5d0ad5a6 Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Sun, 26 Sep 2021 10:44:54 -0500 Subject: [PATCH 17/18] Fixed dependencies casuing the build to fail Signed-off-by: Andre Wanlin --- packages/backend/package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/backend/package.json b/packages/backend/package.json index ec52c6086c..19a3a282c0 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -30,11 +30,11 @@ "@backstage/config": "^0.1.10", "@backstage/integration": "^0.6.5", "@backstage/plugin-app-backend": "^0.3.16", - "@backstage/plugin-auth-backend": "^0.4.0", + "@backstage/plugin-auth-backend": "^0.4.1", "@backstage/plugin-azure-devops-backend": "^0.1.0", - "@backstage/plugin-badges-backend": "^0.1.9", - "@backstage/plugin-catalog-backend": "^0.13.8", - "@backstage/plugin-code-coverage-backend": "^0.1.10", + "@backstage/plugin-badges-backend": "^0.1.10", + "@backstage/plugin-catalog-backend": "^0.14.0", + "@backstage/plugin-code-coverage-backend": "^0.1.11", "@backstage/plugin-graphql-backend": "^0.1.9", "@backstage/plugin-jenkins-backend": "^0.1.5", "@backstage/plugin-kubernetes-backend": "^0.3.16", From 4836fd97ca2e2198e6d1817d42265b996f6291c1 Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Sun, 26 Sep 2021 10:45:24 -0500 Subject: [PATCH 18/18] Regenerated API Report Signed-off-by: Andre Wanlin --- plugins/azure-devops-backend/api-report.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/azure-devops-backend/api-report.md b/plugins/azure-devops-backend/api-report.md index 513fd64f04..4d66f769da 100644 --- a/plugins/azure-devops-backend/api-report.md +++ b/plugins/azure-devops-backend/api-report.md @@ -21,7 +21,7 @@ export class AzureDevOpsApi { getBuildList( projectName: string, repoId: string, - top: string, + top: number, ): Promise; // (undocumented) getGitRepository( @@ -32,7 +32,7 @@ export class AzureDevOpsApi { getRepoBuilds( projectName: string, repoName: string, - top: string, + top: number, ): Promise; }