From 3671ae9bcd98d1e02ad566ca426f17d8c7bfa6e3 Mon Sep 17 00:00:00 2001 From: Julio Zynger Date: Thu, 23 Dec 2021 16:35:33 +0100 Subject: [PATCH 01/20] Add GoCD plugin Signed-off-by: Julio Zynger --- plugins/gocd/.eslintrc.js | 3 + plugins/gocd/README.md | 53 ++++ plugins/gocd/config.d.ts | 25 ++ plugins/gocd/dev/index.tsx | 19 ++ plugins/gocd/package.json | 66 +++++ plugins/gocd/src/api/gocdApi.client.ts | 33 +++ plugins/gocd/src/api/gocdApi.model.ts | 119 ++++++++ plugins/gocd/src/api/gocdApi.ts | 22 ++ .../GoCdBuildsComponent.test.tsx | 53 ++++ .../GoCdBuildsComponent.tsx | 118 ++++++++ .../components/GoCdBuildsComponent/index.ts | 20 ++ .../GoCdBuildsTable/GoCdBuildsTable.tsx | 264 ++++++++++++++++++ plugins/gocd/src/components/Select/Select.tsx | 82 ++++++ plugins/gocd/src/components/Select/index.ts | 17 ++ plugins/gocd/src/extensions.ts | 29 ++ plugins/gocd/src/index.ts | 21 ++ plugins/gocd/src/plugin.test.ts | 22 ++ plugins/gocd/src/plugin.ts | 41 +++ plugins/gocd/src/routes.ts | 20 ++ plugins/gocd/src/setupTests.ts | 17 ++ 20 files changed, 1044 insertions(+) create mode 100644 plugins/gocd/.eslintrc.js create mode 100644 plugins/gocd/README.md create mode 100644 plugins/gocd/config.d.ts create mode 100644 plugins/gocd/dev/index.tsx create mode 100644 plugins/gocd/package.json create mode 100644 plugins/gocd/src/api/gocdApi.client.ts create mode 100644 plugins/gocd/src/api/gocdApi.model.ts create mode 100644 plugins/gocd/src/api/gocdApi.ts create mode 100644 plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.test.tsx create mode 100644 plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.tsx create mode 100644 plugins/gocd/src/components/GoCdBuildsComponent/index.ts create mode 100644 plugins/gocd/src/components/GoCdBuildsTable/GoCdBuildsTable.tsx create mode 100644 plugins/gocd/src/components/Select/Select.tsx create mode 100644 plugins/gocd/src/components/Select/index.ts create mode 100644 plugins/gocd/src/extensions.ts create mode 100644 plugins/gocd/src/index.ts create mode 100644 plugins/gocd/src/plugin.test.ts create mode 100644 plugins/gocd/src/plugin.ts create mode 100644 plugins/gocd/src/routes.ts create mode 100644 plugins/gocd/src/setupTests.ts diff --git a/plugins/gocd/.eslintrc.js b/plugins/gocd/.eslintrc.js new file mode 100644 index 0000000000..13573efa9c --- /dev/null +++ b/plugins/gocd/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint')], +}; diff --git a/plugins/gocd/README.md b/plugins/gocd/README.md new file mode 100644 index 0000000000..9dcf23705d --- /dev/null +++ b/plugins/gocd/README.md @@ -0,0 +1,53 @@ +# GoCD + +Welcome to the GoCD plugin! + +- View recent GoCD Builds + +## Installation + +GoCD Plugin exposes an entity tab component named `EntityGoCdContent`. You can include it in the +[`EntityPage.tsx`](https://github.com/backstage/backstage/blob/master/packages/app/src/components/catalog/EntityPage.tsx)`: + +```tsx +// At the top imports +import { EntityGoCdContent } from '@backstage/plugin-gocd'; + +// Farther down at the component declaration +const componentEntityPage = ( + + {/* Place the following section where you want the tab to appear */} + + + +``` + +Now your plugin should be visible as a tab at the top of the entity pages, +specifically for components that are of the type `component`. +However, it warns of a missing `gocd.org/pipelines` annotation. + +Add the annotation to your component [catalog-info.yaml](https://github.com/backstage/backstage/blob/master/catalog-info.yaml). You can refer to multiple GoCD pipelines by defining their names separated by commas, as shown in the highlighted example below: + +```yaml +metadata: + annotations: + gocd.org/pipelines: '[,]' +``` + +The plugin requires to configure a GoCD API proxy with a `GOCD_AUTH_TOKEN` for authentication in the [app-config.yaml](https://github.com/backstage/backstage/blob/master/app-config.yaml): + +```yaml +proxy: + '/gocd': + target: '/go/api' + allowedMethods: ['GET'] + headers: + Authorization: ${GOCD_AUTH_TOKEN} +``` + +You should also include the `gocd` section to allow for the plugin to redirect back to GoCD pipelines in your deployed instance: + +```yaml +gocd: + baseUrl: +``` diff --git a/plugins/gocd/config.d.ts b/plugins/gocd/config.d.ts new file mode 100644 index 0000000000..6f4f4f3dc3 --- /dev/null +++ b/plugins/gocd/config.d.ts @@ -0,0 +1,25 @@ +/* + * 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. + */ +export interface Config { + /** Configurations for the GoCD plugin */ + gocd: { + /** + * The base url of the GoCD installation. + * @visibility frontend + */ + baseUrl: string; + }; +} diff --git a/plugins/gocd/dev/index.tsx b/plugins/gocd/dev/index.tsx new file mode 100644 index 0000000000..e821173774 --- /dev/null +++ b/plugins/gocd/dev/index.tsx @@ -0,0 +1,19 @@ +/* + * 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 { createDevApp } from '@backstage/dev-utils'; +import { gocdPlugin } from '../src/plugin'; + +createDevApp().registerPlugin(gocdPlugin).render(); diff --git a/plugins/gocd/package.json b/plugins/gocd/package.json new file mode 100644 index 0000000000..f94dcf6790 --- /dev/null +++ b/plugins/gocd/package.json @@ -0,0 +1,66 @@ +{ + "name": "@backstage/plugin-gocd", + "description": "A Backstage plugin that integrates towards GoCD", + "version": "0.1.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": false, + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/gocd" + }, + "scripts": { + "build": "backstage-cli plugin:build", + "start": "backstage-cli plugin:serve", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "diff": "backstage-cli plugin:diff", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean" + }, + "dependencies": { + "@backstage/catalog-model": "^0.9.1", + "@backstage/core-components": "^0.8.0", + "@backstage/core-plugin-api": "^0.3.0", + "@backstage/plugin-catalog-react": "^0.6.0", + "@backstage/theme": "^0.2.10", + "@material-ui/core": "^4.12.2", + "@material-ui/icons": "^4.9.1", + "@material-ui/lab": "4.0.0-alpha.57", + "@types/lodash": "^4.14.173", + "@types/luxon": "^2.0.4", + "lodash": "^4.17.21", + "luxon": "^2.0.2", + "qs": "^6.10.1", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-use": "^17.2.4" + }, + "devDependencies": { + "@backstage/cli": "^0.10.0", + "@backstage/core-app-api": "^0.2.0", + "@backstage/dev-utils": "^0.2.14", + "@backstage/test-utils": "^0.1.23", + "@testing-library/jest-dom": "^5.10.1", + "@testing-library/react": "^11.2.5", + "@testing-library/user-event": "^13.1.8", + "@types/jest": "^26.0.7", + "@types/node": "^14.14.32", + "cross-fetch": "^3.0.6", + "msw": "^0.35.0" + }, + "files": [ + "dist", + "config.d.ts" + ], + "configSchema": "config.d.ts" +} diff --git a/plugins/gocd/src/api/gocdApi.client.ts b/plugins/gocd/src/api/gocdApi.client.ts new file mode 100644 index 0000000000..3907281ff4 --- /dev/null +++ b/plugins/gocd/src/api/gocdApi.client.ts @@ -0,0 +1,33 @@ +/* + * 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 { GoCdApi } from './gocdApi'; +import { GoCdApiError, PipelineHistory } from './gocdApi.model'; +import { DiscoveryApi } from '@backstage/core-plugin-api'; + +export class GoCdClientApi implements GoCdApi { + constructor(private readonly discoveryApi: DiscoveryApi) {} + + async getPipelineHistory( + pipelineName: string, + ): Promise { + const baseUrl = await this.discoveryApi.getBaseUrl('proxy'); + const pipelineHistoryResponse = await fetch( + `${baseUrl}/gocd/pipelines/${pipelineName}/history`, + ); + + return await pipelineHistoryResponse.json(); + } +} diff --git a/plugins/gocd/src/api/gocdApi.model.ts b/plugins/gocd/src/api/gocdApi.model.ts new file mode 100644 index 0000000000..8bb9a481e6 --- /dev/null +++ b/plugins/gocd/src/api/gocdApi.model.ts @@ -0,0 +1,119 @@ +/* + * 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. + */ +export interface GoCdApiError { + message: string; +} + +export interface PipelineHistory { + _links: Links; + pipelines: Pipeline[]; +} + +export interface Links { + next: Next; +} + +export interface Next { + href: string; +} + +export interface Pipeline { + name: string; + counter: number; + label: string; + natural_order?: number; + can_run?: boolean; + preparing_to_schedule?: boolean; + comment: string | null; + scheduled_date?: number; + build_cause?: BuildCause; + stages: Stage[]; +} + +export interface BuildCause { + trigger_message: string; + trigger_forced: boolean; + approver: string; + material_revisions: MaterialRevision[]; +} + +export interface MaterialRevision { + changed: boolean; + material: Material; + modifications: Modification[]; +} + +export interface Material { + name: string; + fingerprint: string; + type: string; + description: string; +} + +export interface Modification { + revision: string; + modified_time: number; + user_name: string; + comment: string | null; + email_address: string | null; +} + +export interface Stage { + result?: string; + status: string; + rerun_of_counter?: number | null; + name: string; + counter: string; + scheduled: boolean; + approval_type?: string | null; + approved_by?: string | null; + operate_permission?: boolean; + can_run?: boolean; + jobs: Job[]; +} + +export interface Job { + name: string; + scheduled_date?: number; + state: string; + result: string; +} + +export enum GoCdBuildResultStatus { + running, + successful, + warning, + aborted, + error, + pending, +} + +export interface GoCdBuildStageResult { + status: GoCdBuildResultStatus; + text: string; +} + +export interface GoCdBuildResult { + id: number; + source: string; + stages: GoCdBuildStageResult[]; + buildSlug: string; + message: string; + pipeline: string; + author: string | undefined; + commitHash: string; + triggerTime: number | undefined; +} diff --git a/plugins/gocd/src/api/gocdApi.ts b/plugins/gocd/src/api/gocdApi.ts new file mode 100644 index 0000000000..a9883a0c66 --- /dev/null +++ b/plugins/gocd/src/api/gocdApi.ts @@ -0,0 +1,22 @@ +/* + * 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 { GoCdApiError, PipelineHistory } from './gocdApi.model'; + +export interface GoCdApi { + getPipelineHistory( + pipelineName: string, + ): Promise; +} diff --git a/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.test.tsx b/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.test.tsx new file mode 100644 index 0000000000..f35155d6ae --- /dev/null +++ b/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.test.tsx @@ -0,0 +1,53 @@ +/* + * 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 React from 'react'; +import { render } from '@testing-library/react'; +import { GoCdBuildsComponent } from './GoCdBuildsComponent'; +import { GoCdApi } from '../../api/gocdApi'; + +let entityValue: { + entity: { metadata: { annotations?: { [key: string]: string } } }; +}; + +const mockApiClient: GoCdApi = { + getPipelineHistory: jest.fn(async () => ({ + _links: { next: { href: 'some-href' } }, + pipelines: [], + })), +}; + +jest.mock('@backstage/plugin-catalog-react', () => ({ + useEntity: () => { + return entityValue; + }, +})); + +jest.mock('@backstage/core-plugin-api', () => ({ + ...jest.requireActual('@backstage/core-plugin-api'), + useApi: () => mockApiClient, +})); + +describe('GoCdArtifactsComponent', () => { + entityValue = { entity: { metadata: {} } }; + + const renderComponent = () => render(); + + it('should display an empty state if an app annotation is missing', async () => { + const rendered = renderComponent(); + + expect(await rendered.findByText('Missing Annotation')).toBeInTheDocument(); + }); +}); diff --git a/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.tsx b/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.tsx new file mode 100644 index 0000000000..a12af86bfd --- /dev/null +++ b/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.tsx @@ -0,0 +1,118 @@ +/* + * 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 React, { useState } from 'react'; +import { Entity } from '@backstage/catalog-model'; +import { useEntity } from '@backstage/plugin-catalog-react'; +import { + Content, + ContentHeader, + MissingAnnotationEmptyState, + EmptyState, + Page, +} from '@backstage/core-components'; +import { useApi, configApiRef } from '@backstage/core-plugin-api'; +import { useAsync } from 'react-use'; +import { gocdApiRef } from '../../plugin'; +import { GoCdBuildsTable } from '../GoCdBuildsTable/GoCdBuildsTable'; +import { GoCdApiError, PipelineHistory } from '../../api/gocdApi.model'; +import { Item, Select } from '../Select'; + +export const GOCD_APP_ANNOTATION = 'gocd.org/pipelines'; + +export const isGoCdAvailable = (entity: Entity): boolean => + Boolean(entity.metadata.annotations?.[GOCD_APP_ANNOTATION]); + +export const GoCdBuildsComponent = (): JSX.Element => { + const { entity } = useEntity(); + const config = useApi(configApiRef); + const rawPipelines: string[] | undefined = ( + entity.metadata.annotations?.[GOCD_APP_ANNOTATION] as string | undefined + ) + ?.split(',') + .map(p => p.trim()); + const gocdApi = useApi(gocdApiRef); + + const [selectedPipeline, setSelectedPipeline] = useState( + rawPipelines ? rawPipelines[0] : '', + ); + + const { + value: pipelineHistory, + loading, + error, + } = useAsync(async (): Promise => { + return await gocdApi.getPipelineHistory(selectedPipeline); + }, [selectedPipeline]); + + const onSelectedPipelineChanged = (pipeline: string) => { + setSelectedPipeline(pipeline); + }; + + const getSelectionItems = (pipelines: string[]): Item[] => { + return pipelines.map(p => ({ label: p, value: p })); + }; + + function isError( + apiResult: PipelineHistory | GoCdApiError | undefined, + ): apiResult is GoCdApiError { + return (apiResult as GoCdApiError)?.message !== undefined; + } + + if (!rawPipelines) { + return ; + } + + if (isError(pipelineHistory)) { + return ( + + ); + } + + if (!loading && !pipelineHistory) { + return ( + + ); + } + + return ( + + + + + {renderItems(items)} + + + ); +}; diff --git a/plugins/gocd/src/components/Select/index.ts b/plugins/gocd/src/components/Select/index.ts new file mode 100644 index 0000000000..1503c54cdf --- /dev/null +++ b/plugins/gocd/src/components/Select/index.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ +export { SelectComponent as Select } from './Select'; +export type { Item } from './Select'; diff --git a/plugins/gocd/src/extensions.ts b/plugins/gocd/src/extensions.ts new file mode 100644 index 0000000000..8b005a9c38 --- /dev/null +++ b/plugins/gocd/src/extensions.ts @@ -0,0 +1,29 @@ +/* + * 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 { gocdPlugin } from './plugin'; +import { createComponentExtension } from '@backstage/core-plugin-api'; + +export const EntityGoCdContent = gocdPlugin.provide( + createComponentExtension({ + name: 'EntityGoCdContent', + component: { + lazy: () => + import('./components/GoCdBuildsComponent').then( + m => m.GoCdBuildsComponent, + ), + }, + }), +); diff --git a/plugins/gocd/src/index.ts b/plugins/gocd/src/index.ts new file mode 100644 index 0000000000..97b0fb6e80 --- /dev/null +++ b/plugins/gocd/src/index.ts @@ -0,0 +1,21 @@ +/* + * 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. + */ +export { gocdPlugin } from './plugin'; +export { EntityGoCdContent } from './extensions'; +export { + GOCD_APP_ANNOTATION, + isGoCdAvailable, +} from './components/GoCdBuildsComponent'; diff --git a/plugins/gocd/src/plugin.test.ts b/plugins/gocd/src/plugin.test.ts new file mode 100644 index 0000000000..02b52a00b6 --- /dev/null +++ b/plugins/gocd/src/plugin.test.ts @@ -0,0 +1,22 @@ +/* + * 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 { gocdPlugin } from './plugin'; + +describe('gocd', () => { + it('should export plugin', () => { + expect(gocdPlugin).toBeDefined(); + }); +}); diff --git a/plugins/gocd/src/plugin.ts b/plugins/gocd/src/plugin.ts new file mode 100644 index 0000000000..91d229a5ab --- /dev/null +++ b/plugins/gocd/src/plugin.ts @@ -0,0 +1,41 @@ +/* + * 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 { GoCdClientApi } from './api/gocdApi.client'; +import { GoCdApi } from './api/gocdApi'; +import { + discoveryApiRef, + createApiRef, + createApiFactory, + createPlugin, +} from '@backstage/core-plugin-api'; + +export const gocdApiRef = createApiRef({ + id: 'plugin.gocd.service', + description: 'Used by the GoCD plugin to retrieve information about builds.', +}); + +export const gocdPlugin = createPlugin({ + id: 'gocd', + apis: [ + createApiFactory({ + api: gocdApiRef, + deps: { discoveryApi: discoveryApiRef }, + factory: ({ discoveryApi }) => { + return new GoCdClientApi(discoveryApi); + }, + }), + ], +}); diff --git a/plugins/gocd/src/routes.ts b/plugins/gocd/src/routes.ts new file mode 100644 index 0000000000..06513e0aa7 --- /dev/null +++ b/plugins/gocd/src/routes.ts @@ -0,0 +1,20 @@ +/* + * 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 { createRouteRef } from '@backstage/core-plugin-api'; + +export const rootRouteRef = createRouteRef({ + title: 'gocd', +}); diff --git a/plugins/gocd/src/setupTests.ts b/plugins/gocd/src/setupTests.ts new file mode 100644 index 0000000000..fc6dbd98f8 --- /dev/null +++ b/plugins/gocd/src/setupTests.ts @@ -0,0 +1,17 @@ +/* + * 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 '@testing-library/jest-dom'; +import 'cross-fetch/polyfill'; From 2d5be0975d92d09d0acf6ebec05f2fc74b41e16a Mon Sep 17 00:00:00 2001 From: Julio Zynger Date: Thu, 23 Dec 2021 16:40:30 +0100 Subject: [PATCH 02/20] Add GoCD entry to plugins marketplace Signed-off-by: Julio Zynger --- microsite/data/plugins/gocd.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 microsite/data/plugins/gocd.yaml diff --git a/microsite/data/plugins/gocd.yaml b/microsite/data/plugins/gocd.yaml new file mode 100644 index 0000000000..7bf24d75fd --- /dev/null +++ b/microsite/data/plugins/gocd.yaml @@ -0,0 +1,12 @@ +--- +title: GoCD +author: SoundCloud +authorUrl: https://github.com/soundcloud +category: CI/CD +description: GoCD is an open-source tool which is used in software development to help teams and organizations automate the continuous delivery of software. +documentation: https://github.com/backstage/backstage/tree/master/plugins/gocd +iconUrl: https://pics.freeicons.io/uploads/icons/png/13646383971540553613-512.png +npmPackageName: '@backstage/plugin-gocd' +tags: + - ci + - cd From cc30c202eac418bf31a534c0983e0c0f02376173 Mon Sep 17 00:00:00 2001 From: Julio Zynger Date: Thu, 23 Dec 2021 17:01:11 +0100 Subject: [PATCH 03/20] Integrate plugin with Backstage installation Signed-off-by: Julio Zynger --- app-config.yaml | 9 +++++++++ packages/app/package.json | 1 + packages/app/src/components/catalog/EntityPage.tsx | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/app-config.yaml b/app-config.yaml index 2837d97d2f..a0cc6a48b4 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -107,6 +107,12 @@ proxy: headers: Authorization: ${AIRFLOW_BASIC_AUTH_HEADER} + '/gocd': + target: https://your.gocd.instance.com/go/api + allowedMethods: ['GET'] + headers: + Authorization: ${GOCD_AUTH_TOKEN} + organization: name: My Company @@ -450,3 +456,6 @@ azureDevOps: apacheAirflow: baseUrl: https://your.airflow.instance.com + +gocd: + baseUrl: https://your.gocd.instance.com \ No newline at end of file diff --git a/packages/app/package.json b/packages/app/package.json index 1dd48eb7bd..efcfed0c7b 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -26,6 +26,7 @@ "@backstage/plugin-explore": "^0.3.23", "@backstage/plugin-gcp-projects": "^0.3.11", "@backstage/plugin-github-actions": "^0.4.28", + "@backstage/plugin-gocd": "^0.1.0", "@backstage/plugin-graphiql": "^0.2.25", "@backstage/plugin-home": "^0.4.8", "@backstage/plugin-jenkins": "^0.5.15", diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index 455cd188fb..60dc0dc979 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -129,6 +129,7 @@ import { EntityNewRelicDashboardContent, EntityNewRelicDashboardCard, } from '@backstage/plugin-newrelic-dashboard'; +import { EntityGoCdContent, isGoCdAvailable } from '@backstage/plugin-gocd'; import React, { ReactNode, useMemo, useState } from 'react'; @@ -185,6 +186,10 @@ export const cicdContent = ( + + + + From e14896fb63efa261cb51f21d7d626841498ee8de Mon Sep 17 00:00:00 2001 From: Julio Zynger Date: Thu, 23 Dec 2021 17:01:28 +0100 Subject: [PATCH 04/20] Update well-known annotations Signed-off-by: Julio Zynger --- .../software-catalog/well-known-annotations.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/features/software-catalog/well-known-annotations.md b/docs/features/software-catalog/well-known-annotations.md index c3af6caf2d..26a8710820 100644 --- a/docs/features/software-catalog/well-known-annotations.md +++ b/docs/features/software-catalog/well-known-annotations.md @@ -204,6 +204,24 @@ browser when viewing that user. This annotation can be used on a [User entity](descriptor-format.md#kind-user) to note that it originated from that user on GitHub. +### gocd.org/pipelines + +```yaml +# Example: +metadata: + annotations: + gocd.org/pipelines: backstage,backstage-pr,backstage-builder +``` + +The value of this annotation is a comma-separated list of the GoCD pipeline +names to fetch CI/CD information for. + +The pipeline name is usually defined in the `gocd.yml` file for the pipeline +definition. + +Specifying this annotation will enable GoCD related features in Backstage for +that entity. + ### sentry.io/project-slug ```yaml From 6fc1d9bbafb725a7a401d0d583829d59021ba3ee Mon Sep 17 00:00:00 2001 From: Julio Zynger Date: Thu, 23 Dec 2021 17:04:02 +0100 Subject: [PATCH 05/20] Update README and app-config Signed-off-by: Julio Zynger --- app-config.yaml | 6 ++++-- plugins/gocd/README.md | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index a0cc6a48b4..3eb6311463 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -110,8 +110,10 @@ proxy: '/gocd': target: https://your.gocd.instance.com/go/api allowedMethods: ['GET'] + allowedHeaders: ['Authorization', 'Accept'] headers: - Authorization: ${GOCD_AUTH_TOKEN} + Authorization: Basic ${GOCD_AUTH_TOKEN} + Accept: application/vnd.go.cd+json organization: name: My Company @@ -458,4 +460,4 @@ apacheAirflow: baseUrl: https://your.airflow.instance.com gocd: - baseUrl: https://your.gocd.instance.com \ No newline at end of file + baseUrl: https://your.gocd.instance.com diff --git a/plugins/gocd/README.md b/plugins/gocd/README.md index 9dcf23705d..96329e7e13 100644 --- a/plugins/gocd/README.md +++ b/plugins/gocd/README.md @@ -41,8 +41,10 @@ proxy: '/gocd': target: '/go/api' allowedMethods: ['GET'] + allowedHeaders: ['Authorization', 'Accept'] headers: - Authorization: ${GOCD_AUTH_TOKEN} + Authorization: Basic ${GOCD_AUTH_TOKEN} + Accept: application/vnd.go.cd+json ``` You should also include the `gocd` section to allow for the plugin to redirect back to GoCD pipelines in your deployed instance: From d9aaecd1cb6f4aaf2c91b4a18d03856cf6aec2cc Mon Sep 17 00:00:00 2001 From: Julio Zynger Date: Thu, 23 Dec 2021 17:32:37 +0100 Subject: [PATCH 06/20] Add changeset Signed-off-by: Julio Zynger --- .changeset/fluffy-tomatoes-rescue.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fluffy-tomatoes-rescue.md diff --git a/.changeset/fluffy-tomatoes-rescue.md b/.changeset/fluffy-tomatoes-rescue.md new file mode 100644 index 0000000000..4d446070a0 --- /dev/null +++ b/.changeset/fluffy-tomatoes-rescue.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-gocd': patch +--- + +Add GoCD plugin for CI/CD. From 016331eb7e54bba8a30ae1df9fa460c49abbf314 Mon Sep 17 00:00:00 2001 From: Julio Zynger Date: Thu, 23 Dec 2021 17:57:42 +0100 Subject: [PATCH 07/20] Add api-report.md Signed-off-by: Julio Zynger --- plugins/gocd/api-report.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 plugins/gocd/api-report.md diff --git a/plugins/gocd/api-report.md b/plugins/gocd/api-report.md new file mode 100644 index 0000000000..c2a27eeb30 --- /dev/null +++ b/plugins/gocd/api-report.md @@ -0,0 +1,32 @@ +## API Report File for "@backstage/plugin-gocd" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +/// + +import { BackstagePlugin } from '@backstage/core-plugin-api'; +import { Entity } from '@backstage/catalog-model'; + +// Warning: (ae-missing-release-tag) "EntityGoCdContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const EntityGoCdContent: () => JSX.Element; + +// Warning: (ae-missing-release-tag) "GOCD_APP_ANNOTATION" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const GOCD_APP_ANNOTATION = 'gocd.org/pipelines'; + +// Warning: (ae-missing-release-tag) "gocdPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const gocdPlugin: BackstagePlugin<{}, {}>; + +// Warning: (ae-missing-release-tag) "isGoCdAvailable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const isGoCdAvailable: (entity: Entity) => boolean; + +// (No @packageDocumentation comment for this package) +``` From b7b2d09cf5f3d0e9a48cad51a183149e0eaf8925 Mon Sep 17 00:00:00 2001 From: Julio Zynger Date: Thu, 23 Dec 2021 18:16:38 +0100 Subject: [PATCH 08/20] Move devDependencies Signed-off-by: Julio Zynger --- plugins/gocd/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/gocd/package.json b/plugins/gocd/package.json index f94dcf6790..ff6bef99f2 100644 --- a/plugins/gocd/package.json +++ b/plugins/gocd/package.json @@ -36,8 +36,6 @@ "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", - "@types/lodash": "^4.14.173", - "@types/luxon": "^2.0.4", "lodash": "^4.17.21", "luxon": "^2.0.2", "qs": "^6.10.1", @@ -54,6 +52,8 @@ "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", + "@types/lodash": "^4.14.173", + "@types/luxon": "^2.0.4", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", "msw": "^0.35.0" From f06933c3b49b99768bb826bd5ceed37e2b40469e Mon Sep 17 00:00:00 2001 From: Julio Zynger Date: Fri, 24 Dec 2021 11:43:32 +0100 Subject: [PATCH 09/20] Set up dependency versions Signed-off-by: Julio Zynger --- plugins/gocd/package.json | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/plugins/gocd/package.json b/plugins/gocd/package.json index ff6bef99f2..aac6d3d297 100644 --- a/plugins/gocd/package.json +++ b/plugins/gocd/package.json @@ -29,25 +29,26 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.1", - "@backstage/core-components": "^0.8.0", - "@backstage/core-plugin-api": "^0.3.0", + "@backstage/core-components": "^0.8.2", + "@backstage/core-plugin-api": "^0.4.0", "@backstage/plugin-catalog-react": "^0.6.0", - "@backstage/theme": "^0.2.10", + "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", "lodash": "^4.17.21", "luxon": "^2.0.2", "qs": "^6.10.1", - "react": "^16.13.1", - "react-dom": "^16.13.1", "react-use": "^17.2.4" }, + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0" + }, "devDependencies": { - "@backstage/cli": "^0.10.0", - "@backstage/core-app-api": "^0.2.0", - "@backstage/dev-utils": "^0.2.14", - "@backstage/test-utils": "^0.1.23", + "@backstage/cli": "^0.10.3", + "@backstage/core-app-api": "^0.3.0", + "@backstage/dev-utils": "^0.2.15", + "@backstage/test-utils": "^0.2.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", From 108a0cf2fb046553c3c3530150cf162aac8e9c29 Mon Sep 17 00:00:00 2001 From: Julio Zynger Date: Fri, 24 Dec 2021 12:22:52 +0100 Subject: [PATCH 10/20] Remove unneeded properties from plugin definition Signed-off-by: Julio Zynger --- plugins/gocd/src/plugin.ts | 1 - plugins/gocd/src/routes.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/gocd/src/plugin.ts b/plugins/gocd/src/plugin.ts index 91d229a5ab..7bb051edee 100644 --- a/plugins/gocd/src/plugin.ts +++ b/plugins/gocd/src/plugin.ts @@ -24,7 +24,6 @@ import { export const gocdApiRef = createApiRef({ id: 'plugin.gocd.service', - description: 'Used by the GoCD plugin to retrieve information about builds.', }); export const gocdPlugin = createPlugin({ diff --git a/plugins/gocd/src/routes.ts b/plugins/gocd/src/routes.ts index 06513e0aa7..71e431c040 100644 --- a/plugins/gocd/src/routes.ts +++ b/plugins/gocd/src/routes.ts @@ -16,5 +16,5 @@ import { createRouteRef } from '@backstage/core-plugin-api'; export const rootRouteRef = createRouteRef({ - title: 'gocd', + id: 'gocd', }); From 0781c324d5058f2a3a89383f7046ca6059c52ecf Mon Sep 17 00:00:00 2001 From: Julio Zynger Date: Mon, 3 Jan 2022 15:15:24 +0100 Subject: [PATCH 11/20] Move accept header to client instead of proxy Signed-off-by: Julio Zynger --- app-config.yaml | 3 +-- plugins/gocd/README.md | 3 +-- plugins/gocd/src/api/gocdApi.client.ts | 5 +++++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index 3eb6311463..359aecf4d2 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -110,10 +110,9 @@ proxy: '/gocd': target: https://your.gocd.instance.com/go/api allowedMethods: ['GET'] - allowedHeaders: ['Authorization', 'Accept'] + allowedHeaders: ['Authorization'] headers: Authorization: Basic ${GOCD_AUTH_TOKEN} - Accept: application/vnd.go.cd+json organization: name: My Company diff --git a/plugins/gocd/README.md b/plugins/gocd/README.md index 96329e7e13..d17b625581 100644 --- a/plugins/gocd/README.md +++ b/plugins/gocd/README.md @@ -41,10 +41,9 @@ proxy: '/gocd': target: '/go/api' allowedMethods: ['GET'] - allowedHeaders: ['Authorization', 'Accept'] + allowedHeaders: ['Authorization'] headers: Authorization: Basic ${GOCD_AUTH_TOKEN} - Accept: application/vnd.go.cd+json ``` You should also include the `gocd` section to allow for the plugin to redirect back to GoCD pipelines in your deployed instance: diff --git a/plugins/gocd/src/api/gocdApi.client.ts b/plugins/gocd/src/api/gocdApi.client.ts index 3907281ff4..d74cf9e055 100644 --- a/plugins/gocd/src/api/gocdApi.client.ts +++ b/plugins/gocd/src/api/gocdApi.client.ts @@ -26,6 +26,11 @@ export class GoCdClientApi implements GoCdApi { const baseUrl = await this.discoveryApi.getBaseUrl('proxy'); const pipelineHistoryResponse = await fetch( `${baseUrl}/gocd/pipelines/${pipelineName}/history`, + { + headers: { + Accept: 'application/vnd.go.cd+json', + }, + }, ); return await pipelineHistoryResponse.json(); From 2d44ec02c278a8080e3a5c36b61e1a2c3ef84f8a Mon Sep 17 00:00:00 2001 From: Julio Zynger Date: Mon, 3 Jan 2022 15:20:25 +0100 Subject: [PATCH 12/20] Rename annotation Signed-off-by: Julio Zynger --- .../GoCdBuildsComponent/GoCdBuildsComponent.tsx | 17 +++++++++++++---- .../src/components/GoCdBuildsComponent/index.ts | 2 +- plugins/gocd/src/index.ts | 2 +- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.tsx b/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.tsx index a12af86bfd..b7938a30b1 100644 --- a/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.tsx +++ b/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.tsx @@ -30,16 +30,23 @@ import { GoCdBuildsTable } from '../GoCdBuildsTable/GoCdBuildsTable'; import { GoCdApiError, PipelineHistory } from '../../api/gocdApi.model'; import { Item, Select } from '../Select'; -export const GOCD_APP_ANNOTATION = 'gocd.org/pipelines'; +/** + * Constant storing GoCD pipelines annotation. + * + * @public + */ +export const GOCD_PIPELINES_ANNOTATION = 'gocd.org/pipelines'; export const isGoCdAvailable = (entity: Entity): boolean => - Boolean(entity.metadata.annotations?.[GOCD_APP_ANNOTATION]); + Boolean(entity.metadata.annotations?.[GOCD_PIPELINES_ANNOTATION]); export const GoCdBuildsComponent = (): JSX.Element => { const { entity } = useEntity(); const config = useApi(configApiRef); const rawPipelines: string[] | undefined = ( - entity.metadata.annotations?.[GOCD_APP_ANNOTATION] as string | undefined + entity.metadata.annotations?.[GOCD_PIPELINES_ANNOTATION] as + | string + | undefined ) ?.split(',') .map(p => p.trim()); @@ -72,7 +79,9 @@ export const GoCdBuildsComponent = (): JSX.Element => { } if (!rawPipelines) { - return ; + return ( + + ); } if (isError(pipelineHistory)) { diff --git a/plugins/gocd/src/components/GoCdBuildsComponent/index.ts b/plugins/gocd/src/components/GoCdBuildsComponent/index.ts index 9085ca62ba..662d2b4038 100644 --- a/plugins/gocd/src/components/GoCdBuildsComponent/index.ts +++ b/plugins/gocd/src/components/GoCdBuildsComponent/index.ts @@ -16,5 +16,5 @@ export { GoCdBuildsComponent, isGoCdAvailable, - GOCD_APP_ANNOTATION, + GOCD_PIPELINES_ANNOTATION, } from './GoCdBuildsComponent'; diff --git a/plugins/gocd/src/index.ts b/plugins/gocd/src/index.ts index 97b0fb6e80..c92e899546 100644 --- a/plugins/gocd/src/index.ts +++ b/plugins/gocd/src/index.ts @@ -16,6 +16,6 @@ export { gocdPlugin } from './plugin'; export { EntityGoCdContent } from './extensions'; export { - GOCD_APP_ANNOTATION, + GOCD_PIPELINES_ANNOTATION, isGoCdAvailable, } from './components/GoCdBuildsComponent'; From 3a7b77697f98b9c8adfbaf5b59905e366ef61a68 Mon Sep 17 00:00:00 2001 From: Julio Zynger Date: Mon, 3 Jan 2022 15:23:38 +0100 Subject: [PATCH 13/20] Add public annotations to api-report Signed-off-by: Julio Zynger --- .../components/GoCdBuildsComponent/GoCdBuildsComponent.tsx | 5 +++++ plugins/gocd/src/extensions.ts | 5 +++++ plugins/gocd/src/plugin.ts | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.tsx b/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.tsx index b7938a30b1..46e5d09260 100644 --- a/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.tsx +++ b/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.tsx @@ -37,6 +37,11 @@ import { Item, Select } from '../Select'; */ export const GOCD_PIPELINES_ANNOTATION = 'gocd.org/pipelines'; +/** + * Returns true if GoCD annotation is present in the given entity. + * + * @public + */ export const isGoCdAvailable = (entity: Entity): boolean => Boolean(entity.metadata.annotations?.[GOCD_PIPELINES_ANNOTATION]); diff --git a/plugins/gocd/src/extensions.ts b/plugins/gocd/src/extensions.ts index 8b005a9c38..55624c9d7e 100644 --- a/plugins/gocd/src/extensions.ts +++ b/plugins/gocd/src/extensions.ts @@ -16,6 +16,11 @@ import { gocdPlugin } from './plugin'; import { createComponentExtension } from '@backstage/core-plugin-api'; +/** + * GoCD builds table component. + * + * @public + */ export const EntityGoCdContent = gocdPlugin.provide( createComponentExtension({ name: 'EntityGoCdContent', diff --git a/plugins/gocd/src/plugin.ts b/plugins/gocd/src/plugin.ts index 7bb051edee..01081978f4 100644 --- a/plugins/gocd/src/plugin.ts +++ b/plugins/gocd/src/plugin.ts @@ -26,6 +26,11 @@ export const gocdApiRef = createApiRef({ id: 'plugin.gocd.service', }); +/** + * Plugin definition. + * + * @public + */ export const gocdPlugin = createPlugin({ id: 'gocd', apis: [ From e783290cdd1b4457b67a0f4fe01dd1c4e7b571ce Mon Sep 17 00:00:00 2001 From: Julio Zynger Date: Mon, 3 Jan 2022 15:26:07 +0100 Subject: [PATCH 14/20] Update api-report Signed-off-by: Julio Zynger --- plugins/gocd/api-report.md | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/plugins/gocd/api-report.md b/plugins/gocd/api-report.md index c2a27eeb30..de2c76e06b 100644 --- a/plugins/gocd/api-report.md +++ b/plugins/gocd/api-report.md @@ -8,24 +8,16 @@ import { BackstagePlugin } from '@backstage/core-plugin-api'; import { Entity } from '@backstage/catalog-model'; -// Warning: (ae-missing-release-tag) "EntityGoCdContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export const EntityGoCdContent: () => JSX.Element; -// Warning: (ae-missing-release-tag) "GOCD_APP_ANNOTATION" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const GOCD_APP_ANNOTATION = 'gocd.org/pipelines'; +// @public +export const GOCD_PIPELINES_ANNOTATION = 'gocd.org/pipelines'; -// Warning: (ae-missing-release-tag) "gocdPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export const gocdPlugin: BackstagePlugin<{}, {}>; -// Warning: (ae-missing-release-tag) "isGoCdAvailable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export const isGoCdAvailable: (entity: Entity) => boolean; // (No @packageDocumentation comment for this package) From 8ca42b7b4993974d2f9b17ecc88d41253d696814 Mon Sep 17 00:00:00 2001 From: Julio Zynger Date: Mon, 3 Jan 2022 15:30:16 +0100 Subject: [PATCH 15/20] Clarify Basic auth token generation Signed-off-by: Julio Zynger --- app-config.yaml | 2 +- plugins/gocd/README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index 359aecf4d2..b43a229ae9 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -112,7 +112,7 @@ proxy: allowedMethods: ['GET'] allowedHeaders: ['Authorization'] headers: - Authorization: Basic ${GOCD_AUTH_TOKEN} + Authorization: Basic ${GOCD_AUTH_CREDENTIALS} organization: name: My Company diff --git a/plugins/gocd/README.md b/plugins/gocd/README.md index d17b625581..105d93e830 100644 --- a/plugins/gocd/README.md +++ b/plugins/gocd/README.md @@ -34,7 +34,7 @@ metadata: gocd.org/pipelines: '[,]' ``` -The plugin requires to configure a GoCD API proxy with a `GOCD_AUTH_TOKEN` for authentication in the [app-config.yaml](https://github.com/backstage/backstage/blob/master/app-config.yaml): +The plugin requires to configure a GoCD API proxy with a `GOCD_AUTH_CREDENTIALS` for authentication in the [app-config.yaml](https://github.com/backstage/backstage/blob/master/app-config.yaml). Its value is an opaque token you can obtain directly from your GoCD instance, in the shape `base64(user + ':' + pass)`. For example, a user "root" and password "root" would become `base64('root:root') = cm9vdDpyb290`: ```yaml proxy: @@ -43,7 +43,7 @@ proxy: allowedMethods: ['GET'] allowedHeaders: ['Authorization'] headers: - Authorization: Basic ${GOCD_AUTH_TOKEN} + Authorization: Basic ${GOCD_AUTH_CREDENTIALS} ``` You should also include the `gocd` section to allow for the plugin to redirect back to GoCD pipelines in your deployed instance: From eee13c54c5c0272d4777a1aed353142de99f3f7d Mon Sep 17 00:00:00 2001 From: Julio Zynger Date: Mon, 3 Jan 2022 15:33:12 +0100 Subject: [PATCH 16/20] Throw errors when response is not ok Signed-off-by: Julio Zynger --- plugins/gocd/package.json | 1 + plugins/gocd/src/api/gocdApi.client.ts | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/plugins/gocd/package.json b/plugins/gocd/package.json index aac6d3d297..0af395637b 100644 --- a/plugins/gocd/package.json +++ b/plugins/gocd/package.json @@ -31,6 +31,7 @@ "@backstage/catalog-model": "^0.9.1", "@backstage/core-components": "^0.8.2", "@backstage/core-plugin-api": "^0.4.0", + "@backstage/errors": "^0.1.4", "@backstage/plugin-catalog-react": "^0.6.0", "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.12.2", diff --git a/plugins/gocd/src/api/gocdApi.client.ts b/plugins/gocd/src/api/gocdApi.client.ts index d74cf9e055..1f4955a80d 100644 --- a/plugins/gocd/src/api/gocdApi.client.ts +++ b/plugins/gocd/src/api/gocdApi.client.ts @@ -16,6 +16,7 @@ import { GoCdApi } from './gocdApi'; import { GoCdApiError, PipelineHistory } from './gocdApi.model'; import { DiscoveryApi } from '@backstage/core-plugin-api'; +import { ResponseError } from '@backstage/errors'; export class GoCdClientApi implements GoCdApi { constructor(private readonly discoveryApi: DiscoveryApi) {} @@ -33,6 +34,10 @@ export class GoCdClientApi implements GoCdApi { }, ); + if (!pipelineHistoryResponse.ok) { + throw await ResponseError.fromResponse(pipelineHistoryResponse); + } + return await pipelineHistoryResponse.json(); } } From 7feff487e20642f3d54c23dfdf0a69c238dab8c1 Mon Sep 17 00:00:00 2001 From: Julio Zynger Date: Mon, 3 Jan 2022 15:40:06 +0100 Subject: [PATCH 17/20] Replace manual jest mocking with helpers Signed-off-by: Julio Zynger --- .../GoCdBuildsComponent.test.tsx | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.test.tsx b/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.test.tsx index f35155d6ae..66cc8132f8 100644 --- a/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.test.tsx +++ b/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.test.tsx @@ -14,14 +14,13 @@ * limitations under the License. */ import React from 'react'; -import { render } from '@testing-library/react'; +import { Entity } from '@backstage/catalog-model'; +import { EntityProvider } from '@backstage/plugin-catalog-react'; +import { renderWithEffects, TestApiProvider } from '@backstage/test-utils'; import { GoCdBuildsComponent } from './GoCdBuildsComponent'; +import { gocdApiRef } from '../../plugin'; import { GoCdApi } from '../../api/gocdApi'; -let entityValue: { - entity: { metadata: { annotations?: { [key: string]: string } } }; -}; - const mockApiClient: GoCdApi = { getPipelineHistory: jest.fn(async () => ({ _links: { next: { href: 'some-href' } }, @@ -29,24 +28,20 @@ const mockApiClient: GoCdApi = { })), }; -jest.mock('@backstage/plugin-catalog-react', () => ({ - useEntity: () => { - return entityValue; - }, -})); - -jest.mock('@backstage/core-plugin-api', () => ({ - ...jest.requireActual('@backstage/core-plugin-api'), - useApi: () => mockApiClient, -})); - describe('GoCdArtifactsComponent', () => { - entityValue = { entity: { metadata: {} } }; + const entityValue = { entity: { metadata: {} } as Entity }; - const renderComponent = () => render(); + const renderComponent = () => + renderWithEffects( + + + + + , + ); it('should display an empty state if an app annotation is missing', async () => { - const rendered = renderComponent(); + const rendered = await renderComponent(); expect(await rendered.findByText('Missing Annotation')).toBeInTheDocument(); }); From 78ebd2baee7032d4e9f90948af223173ba67459f Mon Sep 17 00:00:00 2001 From: Julio Zynger Date: Mon, 3 Jan 2022 16:17:32 +0100 Subject: [PATCH 18/20] Adjust test to include configApi Signed-off-by: Julio Zynger --- .../GoCdBuildsComponent.test.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.test.tsx b/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.test.tsx index 66cc8132f8..7f16f40a6a 100644 --- a/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.test.tsx +++ b/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.test.tsx @@ -15,6 +15,8 @@ */ import React from 'react'; import { Entity } from '@backstage/catalog-model'; +import { ConfigReader } from '@backstage/core-app-api'; +import { ConfigApi, configApiRef } from '@backstage/core-plugin-api'; import { EntityProvider } from '@backstage/plugin-catalog-react'; import { renderWithEffects, TestApiProvider } from '@backstage/test-utils'; import { GoCdBuildsComponent } from './GoCdBuildsComponent'; @@ -29,11 +31,21 @@ const mockApiClient: GoCdApi = { }; describe('GoCdArtifactsComponent', () => { + const configApi: ConfigApi = new ConfigReader({ + gocd: { + baseUrl: 'gocd.baseurl.com', + }, + }); const entityValue = { entity: { metadata: {} } as Entity }; const renderComponent = () => renderWithEffects( - + From 1fc15f3ed33175596db9b50e75ed6815fcee3980 Mon Sep 17 00:00:00 2001 From: Julio Zynger Date: Mon, 3 Jan 2022 16:39:06 +0100 Subject: [PATCH 19/20] Sync with latest template Signed-off-by: Julio Zynger --- plugins/gocd/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/gocd/package.json b/plugins/gocd/package.json index 0af395637b..9db98cd7d2 100644 --- a/plugins/gocd/package.json +++ b/plugins/gocd/package.json @@ -46,10 +46,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.10.3", + "@backstage/cli": "^0.10.4", "@backstage/core-app-api": "^0.3.0", "@backstage/dev-utils": "^0.2.15", - "@backstage/test-utils": "^0.2.0", + "@backstage/test-utils": "^0.2.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", From 6a1e2bf90f176774c16b3c53b0b481d56362b590 Mon Sep 17 00:00:00 2001 From: Julio Zynger Date: Wed, 5 Jan 2022 18:41:43 +0100 Subject: [PATCH 20/20] Sync with app template Signed-off-by: Julio Zynger --- plugins/gocd/package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/gocd/package.json b/plugins/gocd/package.json index 9db98cd7d2..d07931916d 100644 --- a/plugins/gocd/package.json +++ b/plugins/gocd/package.json @@ -29,8 +29,8 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.1", - "@backstage/core-components": "^0.8.2", - "@backstage/core-plugin-api": "^0.4.0", + "@backstage/core-components": "^0.8.3", + "@backstage/core-plugin-api": "^0.4.1", "@backstage/errors": "^0.1.4", "@backstage/plugin-catalog-react": "^0.6.0", "@backstage/theme": "^0.2.14", @@ -46,9 +46,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.10.4", - "@backstage/core-app-api": "^0.3.0", - "@backstage/dev-utils": "^0.2.15", + "@backstage/cli": "^0.10.5", + "@backstage/core-app-api": "^0.3.1", + "@backstage/dev-utils": "^0.2.16", "@backstage/test-utils": "^0.2.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5",