diff --git a/.changeset/honest-nails-bake.md b/.changeset/honest-nails-bake.md new file mode 100644 index 0000000000..03d5a080c9 --- /dev/null +++ b/.changeset/honest-nails-bake.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-octopus-deploy': minor +--- + +Initial version diff --git a/plugins/octopus-deploy/.eslintrc.js b/plugins/octopus-deploy/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/octopus-deploy/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/octopus-deploy/README.md b/plugins/octopus-deploy/README.md new file mode 100644 index 0000000000..abc0559896 --- /dev/null +++ b/plugins/octopus-deploy/README.md @@ -0,0 +1,61 @@ +# Octopus Deploy Plugin + +Welcome to the octopus-deploy plugin! + +## Features + +- Display the deployment status of the most recent releases for a project in Octopus Deploy straight from the Backstage catalog + +## Getting started + +This plugin (currently) uses the Backstage proxy to securely communicate with the Octopus Deploy API. + +To use it, you will need to generate an [API Key](https://octopus.com/docs/octopus-rest-api/how-to-create-an-api-key) within Octopus Deploy. + +1. Add the following to your app-config.yaml to enable the proxy: + +``` +// app-config.yaml +proxy: + '/octopus-deploy': + target: '' + headers: + X-Octopus-ApiKey: ${OCTOPUS_API_KEY} +``` + +2. Add the following to `EntityPage.tsx` to display Octopus Releases + +``` +// In packages/app/src/components/catalog/EntityPage.tsx +import { + isOctopusDeployAvailable + EntityOctopusDeployContent +} from '@backstage/plugin-octopus-deploy'; + +const cicdContent = ( + + {/* other components... */} + + + + +) +``` + +3. Add `octopus.com/project-id` annotation in catalog descriptor file + +To obtain a projects ID you will have to query the Octopus API. In the future we'll add support for using a projects slug as well. + +``` +// catalog-info.yaml +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + # ... + annotations: + octopus.com/project-id: Projects-102 +spec: + type: service +``` + +All set , you will be able to see the plugin in action! diff --git a/plugins/octopus-deploy/api-report.md b/plugins/octopus-deploy/api-report.md new file mode 100644 index 0000000000..82512ebcef --- /dev/null +++ b/plugins/octopus-deploy/api-report.md @@ -0,0 +1,86 @@ +## API Report File for "@backstage/plugin-octopus-deploy" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +/// + +import { ApiRef } from '@backstage/core-plugin-api'; +import { BackstagePlugin } from '@backstage/core-plugin-api'; +import { DiscoveryApi } from '@backstage/core-plugin-api'; +import { Entity } from '@backstage/catalog-model'; +import { FetchApi } from '@backstage/core-plugin-api'; + +// @public (undocumented) +export const EntityOctopusDeployContent: (props: { + defaultLimit?: number | undefined; +}) => JSX.Element; + +// @public (undocumented) +export const isOctopusDeployAvailable: (entity: Entity) => boolean; + +// @public (undocumented) +export const OCTOPUS_DEPLOY_PROJECT_ID_ANNOTATION = 'octopus.com/project-id'; + +// @public (undocumented) +export interface OctopusDeployApi { + // (undocumented) + getReleaseProgression( + projectId: string, + releaseHistoryCount: number, + ): Promise; +} + +// @public (undocumented) +export const octopusDeployApiRef: ApiRef; + +// @public (undocumented) +export class OctopusDeployClient implements OctopusDeployApi { + constructor(options: { + discoveryApi: DiscoveryApi; + fetchApi: FetchApi; + proxyPathBase?: string; + }); + // (undocumented) + getReleaseProgression( + projectId: string, + releaseHistoryCount: number, + ): Promise; +} + +// @public (undocumented) +export type OctopusDeployment = { + State: string; +}; + +// @public (undocumented) +export const octopusDeployPlugin: BackstagePlugin<{}, {}, {}>; + +// @public (undocumented) +export type OctopusEnvironment = { + Id: string; + Name: string; +}; + +// @public (undocumented) +export type OctopusProgression = { + Environments: OctopusEnvironment[]; + Releases: OctopusReleaseProgression[]; +}; + +// @public (undocumented) +export type OctopusRelease = { + Id: string; + Version: string; +}; + +// @public (undocumented) +export type OctopusReleaseProgression = { + Release: OctopusRelease; + Deployments: { + [key: string]: OctopusDeployment[]; + }; +}; + +// (No @packageDocumentation comment for this package) +``` diff --git a/plugins/octopus-deploy/dev/index.tsx b/plugins/octopus-deploy/dev/index.tsx new file mode 100644 index 0000000000..d3d6cb3215 --- /dev/null +++ b/plugins/octopus-deploy/dev/index.tsx @@ -0,0 +1,28 @@ +/* + * Copyright 2023 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 { createDevApp } from '@backstage/dev-utils'; +import { octopusDeployPlugin } from '../src/plugin'; +import { EntityPageOctopusDeploy } from '../src/components/EntityPageOctopusDeploy'; + +createDevApp() + .registerPlugin(octopusDeployPlugin) + .addPage({ + element: , + title: 'Root Page', + path: '/octopus-deploy', + }) + .render(); diff --git a/plugins/octopus-deploy/package.json b/plugins/octopus-deploy/package.json new file mode 100644 index 0000000000..3a8e3213ea --- /dev/null +++ b/plugins/octopus-deploy/package.json @@ -0,0 +1,53 @@ +{ + "name": "@backstage/plugin-octopus-deploy", + "version": "0.0.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "backstage": { + "role": "frontend-plugin" + }, + "scripts": { + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "clean": "backstage-cli package clean", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack" + }, + "dependencies": { + "@backstage/catalog-model": "workspace:^", + "@backstage/core-components": "workspace:^", + "@backstage/core-plugin-api": "workspace:^", + "@backstage/plugin-catalog-react": "workspace:^", + "@backstage/theme": "workspace:^", + "@material-ui/core": "^4.9.13", + "@material-ui/icons": "^4.9.1", + "@material-ui/lab": "4.0.0-alpha.57", + "react-use": "^17.2.4" + }, + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0" + }, + "devDependencies": { + "@backstage/cli": "workspace:^", + "@backstage/core-app-api": "workspace:^", + "@backstage/dev-utils": "workspace:^", + "@backstage/test-utils": "workspace:^", + "@testing-library/jest-dom": "^5.10.1", + "@testing-library/react": "^12.1.3", + "@testing-library/user-event": "^14.0.0", + "@types/node": "*", + "cross-fetch": "^3.1.5", + "msw": "^0.49.0" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/octopus-deploy/src/api/index.ts b/plugins/octopus-deploy/src/api/index.ts new file mode 100644 index 0000000000..9cba49d6c0 --- /dev/null +++ b/plugins/octopus-deploy/src/api/index.ts @@ -0,0 +1,118 @@ +/* + * Copyright 2023 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 { + createApiRef, + DiscoveryApi, + FetchApi, +} from '@backstage/core-plugin-api'; + +/** @public */ +export type OctopusProgression = { + Environments: OctopusEnvironment[]; + Releases: OctopusReleaseProgression[]; +}; + +/** @public */ +export type OctopusEnvironment = { + Id: string; + Name: string; +}; + +/** @public */ +export type OctopusReleaseProgression = { + Release: OctopusRelease; + Deployments: { [key: string]: OctopusDeployment[] }; +}; + +/** @public */ +export type OctopusRelease = { + Id: string; + Version: string; +}; + +/** @public */ +export type OctopusDeployment = { + State: string; +}; + +/** @public */ +export const octopusDeployApiRef = createApiRef({ + id: 'plugin.octopusdeploy.service', +}); + +const DEFAULT_PROXY_PATH_BASE = '/octopus-deploy'; + +/** @public */ +export interface OctopusDeployApi { + getReleaseProgression( + projectId: string, + releaseHistoryCount: number, + ): Promise; +} + +/** @public */ +export class OctopusDeployClient implements OctopusDeployApi { + private readonly discoveryApi: DiscoveryApi; + private readonly fetchApi: FetchApi; + private readonly proxyPathBase: string; + + constructor(options: { + discoveryApi: DiscoveryApi; + fetchApi: FetchApi; + proxyPathBase?: string; + }) { + this.discoveryApi = options.discoveryApi; + this.fetchApi = options.fetchApi; + this.proxyPathBase = options.proxyPathBase ?? DEFAULT_PROXY_PATH_BASE; + } + + async getReleaseProgression( + projectId: string, + releaseHistoryCount: number, + ): Promise { + const url = await this.getApiUrl(projectId, releaseHistoryCount); + + const response = await this.fetchApi.fetch(url); + + let responseJson; + + try { + responseJson = await response.json(); + } catch (e) { + responseJson = { releases: [] }; + } + + if (response.status !== 200) { + throw new Error( + `Error communicating with Octopus Deploy: ${ + responseJson?.error?.title || response.statusText + }`, + ); + } + + return responseJson; + } + + private async getApiUrl(projectId: string, releaseHistoryCount: number) { + const proxyUrl = await this.discoveryApi.getBaseUrl('proxy'); + const queryParameters = new URLSearchParams({ + releaseHistoryCount: releaseHistoryCount.toString(), + }); + return `${proxyUrl}${this.proxyPathBase}/projects/${encodeURIComponent( + projectId, + )}/progression?${queryParameters}`; + } +} diff --git a/plugins/octopus-deploy/src/components/EntityPageOctopusDeploy/EntityPageOctopusDeploy.tsx b/plugins/octopus-deploy/src/components/EntityPageOctopusDeploy/EntityPageOctopusDeploy.tsx new file mode 100644 index 0000000000..de28edb9e0 --- /dev/null +++ b/plugins/octopus-deploy/src/components/EntityPageOctopusDeploy/EntityPageOctopusDeploy.tsx @@ -0,0 +1,40 @@ +/* + * Copyright 2023 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 { useEntity } from '@backstage/plugin-catalog-react'; +import { useReleases } from '../../hooks/useReleases'; +import { getAnnotationFromEntity } from '../../utils/getAnnotationFromEntity'; +import React from 'react'; +import { ReleaseTable } from '../ReleaseTable'; + +export const EntityPageOctopusDeploy = (props: { defaultLimit?: number }) => { + const { entity } = useEntity(); + + const projectId = getAnnotationFromEntity(entity); + + const { environments, releases, loading, error } = useReleases( + projectId, + props.defaultLimit ?? 3, + ); + + return ( + + ); +}; diff --git a/plugins/octopus-deploy/src/components/EntityPageOctopusDeploy/index.ts b/plugins/octopus-deploy/src/components/EntityPageOctopusDeploy/index.ts new file mode 100644 index 0000000000..e168735130 --- /dev/null +++ b/plugins/octopus-deploy/src/components/EntityPageOctopusDeploy/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2023 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 { EntityPageOctopusDeploy } from './EntityPageOctopusDeploy'; diff --git a/plugins/octopus-deploy/src/components/ReleaseTable/ReleaseTable.tsx b/plugins/octopus-deploy/src/components/ReleaseTable/ReleaseTable.tsx new file mode 100644 index 0000000000..dfda291e1a --- /dev/null +++ b/plugins/octopus-deploy/src/components/ReleaseTable/ReleaseTable.tsx @@ -0,0 +1,150 @@ +/* + * Copyright 2023 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 { Box, Typography } from '@material-ui/core'; +import { OctopusEnvironment, OctopusReleaseProgression } from '../../api'; + +import React from 'react'; +import { + ResponseErrorPanel, + StatusAborted, + StatusError, + StatusOK, + StatusPending, + StatusRunning, + StatusWarning, + Table, + TableColumn, +} from '@backstage/core-components'; + +type ReleaseTableProps = { + environments?: OctopusEnvironment[]; + releases?: OctopusReleaseProgression[]; + loading: boolean; + error?: Error; +}; + +export const getDeploymentStatusComponent = (state: string | undefined) => { + switch (state) { + case 'Success': + return ( + + Success + + ); + case 'Queued': + return ( + + Queued + + ); + case 'Executing': + return ( + + Executing + + ); + case 'Failed': + return ( + + Failed + + ); + case 'Cancelling': + return ( + + Cancelling + + ); + case 'Canceled': + return ( + + Canceled + + ); + case 'TimedOut': + return ( + + Timed Out + + ); + default: + return ( + + Unknown + + ); + } +}; + +export const ReleaseTable = ({ + environments, + releases, + loading, + error, +}: ReleaseTableProps) => { + if (error) { + return ; + } + + const columns: TableColumn[] = [ + { + title: 'Version', + field: 'Release.Version', + highlight: false, + width: 'auto', + }, + ...(environments?.map(env => ({ + title: env.Name, + width: 'auto', + render: (row: Partial) => { + const deploymentsForEnvironment = row.Deployments + ? row.Deployments[env.Id] + : null; + if (deploymentsForEnvironment) { + return ( + + + {getDeploymentStatusComponent( + deploymentsForEnvironment[0].State, + )} + + + ); + } + return ; + }, + })) ?? []), + ]; + + return ( + + Octopus Deploy - Releases ({releases ? releases.length : 0}) + + } + data={releases ?? []} + /> + ); +}; diff --git a/plugins/octopus-deploy/src/components/ReleaseTable/index.ts b/plugins/octopus-deploy/src/components/ReleaseTable/index.ts new file mode 100644 index 0000000000..d0c679a48e --- /dev/null +++ b/plugins/octopus-deploy/src/components/ReleaseTable/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2023 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 { ReleaseTable } from './ReleaseTable'; diff --git a/plugins/octopus-deploy/src/constants.ts b/plugins/octopus-deploy/src/constants.ts new file mode 100644 index 0000000000..3bdc48252f --- /dev/null +++ b/plugins/octopus-deploy/src/constants.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2023 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. + */ + +/** @public */ +export const OCTOPUS_DEPLOY_PROJECT_ID_ANNOTATION = 'octopus.com/project-id'; diff --git a/plugins/octopus-deploy/src/hooks/useReleases.ts b/plugins/octopus-deploy/src/hooks/useReleases.ts new file mode 100644 index 0000000000..742ce78dca --- /dev/null +++ b/plugins/octopus-deploy/src/hooks/useReleases.ts @@ -0,0 +1,45 @@ +/* + * Copyright 2023 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 { useApi } from '@backstage/core-plugin-api'; +import useAsync from 'react-use/lib/useAsync'; +import { + octopusDeployApiRef, + OctopusEnvironment, + OctopusReleaseProgression, +} from '../api'; + +export function useReleases( + projectId: string, + releaseHistoryCount: number, +): { + environments?: OctopusEnvironment[]; + releases?: OctopusReleaseProgression[]; + loading: boolean; + error?: Error; +} { + const api = useApi(octopusDeployApiRef); + + const { value, loading, error } = useAsync(() => { + return api.getReleaseProgression(projectId, releaseHistoryCount); + }, [api, projectId, releaseHistoryCount]); + + return { + environments: value?.Environments, + releases: value?.Releases, + loading, + error, + }; +} diff --git a/plugins/octopus-deploy/src/index.ts b/plugins/octopus-deploy/src/index.ts new file mode 100644 index 0000000000..e3264efaf0 --- /dev/null +++ b/plugins/octopus-deploy/src/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright 2023 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 { + octopusDeployPlugin, + EntityOctopusDeployContent, + isOctopusDeployAvailable, +} from './plugin'; + +export * from './api'; + +export { OCTOPUS_DEPLOY_PROJECT_ID_ANNOTATION } from './constants'; diff --git a/plugins/octopus-deploy/src/plugin.test.ts b/plugins/octopus-deploy/src/plugin.test.ts new file mode 100644 index 0000000000..a18e74f689 --- /dev/null +++ b/plugins/octopus-deploy/src/plugin.test.ts @@ -0,0 +1,22 @@ +/* + * Copyright 2023 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 { octopusDeployPlugin } from './plugin'; + +describe('octopus-deploy', () => { + it('should export plugin', () => { + expect(octopusDeployPlugin).toBeDefined(); + }); +}); diff --git a/plugins/octopus-deploy/src/plugin.ts b/plugins/octopus-deploy/src/plugin.ts new file mode 100644 index 0000000000..9365f93bdb --- /dev/null +++ b/plugins/octopus-deploy/src/plugin.ts @@ -0,0 +1,58 @@ +/* + * Copyright 2023 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 { OCTOPUS_DEPLOY_PROJECT_ID_ANNOTATION } from './constants'; +import { octopusDeployEntityContentRouteRef } from './routes'; + +import { OctopusDeployClient, octopusDeployApiRef } from './api'; + +import { + createApiFactory, + createPlugin, + createRoutableExtension, + discoveryApiRef, + fetchApiRef, +} from '@backstage/core-plugin-api'; + +import { Entity } from '@backstage/catalog-model'; + +/** @public */ +export const isOctopusDeployAvailable = (entity: Entity) => + Boolean(entity.metadata.annotations?.[OCTOPUS_DEPLOY_PROJECT_ID_ANNOTATION]); + +/** @public */ +export const octopusDeployPlugin = createPlugin({ + id: 'octopus-deploy', + apis: [ + createApiFactory({ + api: octopusDeployApiRef, + deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef }, + factory: ({ discoveryApi, fetchApi }) => + new OctopusDeployClient({ discoveryApi, fetchApi }), + }), + ], +}); + +/** @public */ +export const EntityOctopusDeployContent = octopusDeployPlugin.provide( + createRoutableExtension({ + name: 'EntityOctopusDeployContent', + component: () => + import('./components/EntityPageOctopusDeploy').then( + m => m.EntityPageOctopusDeploy, + ), + mountPoint: octopusDeployEntityContentRouteRef, + }), +); diff --git a/plugins/octopus-deploy/src/routes.ts b/plugins/octopus-deploy/src/routes.ts new file mode 100644 index 0000000000..824b6ae113 --- /dev/null +++ b/plugins/octopus-deploy/src/routes.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2023 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 octopusDeployEntityContentRouteRef = createRouteRef({ + id: 'octopus-deploy-entity-content', +}); diff --git a/plugins/octopus-deploy/src/setupTests.ts b/plugins/octopus-deploy/src/setupTests.ts new file mode 100644 index 0000000000..73dd8dce47 --- /dev/null +++ b/plugins/octopus-deploy/src/setupTests.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2023 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'; diff --git a/plugins/octopus-deploy/src/utils/getAnnotationFromEntity.ts b/plugins/octopus-deploy/src/utils/getAnnotationFromEntity.ts new file mode 100644 index 0000000000..e59a32235f --- /dev/null +++ b/plugins/octopus-deploy/src/utils/getAnnotationFromEntity.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2023 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 { OCTOPUS_DEPLOY_PROJECT_ID_ANNOTATION } from '../constants'; + +import { Entity } from '@backstage/catalog-model'; + +export function getAnnotationFromEntity(entity: Entity): string { + const annotation = + entity.metadata.annotations?.[OCTOPUS_DEPLOY_PROJECT_ID_ANNOTATION]; + if (!annotation) { + throw new Error( + `Value for annotation ${OCTOPUS_DEPLOY_PROJECT_ID_ANNOTATION} was not found`, + ); + } + + return annotation; +} diff --git a/yarn.lock b/yarn.lock index da27fdfa72..395164b46b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6953,6 +6953,34 @@ __metadata: languageName: unknown linkType: soft +"@backstage/plugin-octopus-deploy@workspace:plugins/octopus-deploy": + version: 0.0.0-use.local + resolution: "@backstage/plugin-octopus-deploy@workspace:plugins/octopus-deploy" + dependencies: + "@backstage/catalog-model": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/core-app-api": "workspace:^" + "@backstage/core-components": "workspace:^" + "@backstage/core-plugin-api": "workspace:^" + "@backstage/dev-utils": "workspace:^" + "@backstage/plugin-catalog-react": "workspace:^" + "@backstage/test-utils": "workspace:^" + "@backstage/theme": "workspace:^" + "@material-ui/core": ^4.9.13 + "@material-ui/icons": ^4.9.1 + "@material-ui/lab": 4.0.0-alpha.57 + "@testing-library/jest-dom": ^5.10.1 + "@testing-library/react": ^12.1.3 + "@testing-library/user-event": ^14.0.0 + "@types/node": "*" + cross-fetch: ^3.1.5 + msw: ^0.49.0 + react-use: ^17.2.4 + peerDependencies: + react: ^16.13.1 || ^17.0.0 + languageName: unknown + linkType: soft + "@backstage/plugin-org-react@workspace:plugins/org-react": version: 0.0.0-use.local resolution: "@backstage/plugin-org-react@workspace:plugins/org-react"