diff --git a/plugins/octopus-deploy/README.md b/plugins/octopus-deploy/README.md index ee7d9d8543..f6de7dc392 100644 --- a/plugins/octopus-deploy/README.md +++ b/plugins/octopus-deploy/README.md @@ -41,7 +41,9 @@ octopusdeploy: webBaseUrl: "" ``` -2. Add the following to `EntityPage.tsx` to display Octopus Releases +#### Adding the Entities + +1. Add the following to `EntityPage.tsx` to display Octopus Releases ``` // In packages/app/src/components/catalog/EntityPage.tsx @@ -60,7 +62,7 @@ const cicdContent = ( ) ``` -3. Add `octopus.com/project-id` annotation in the catalog descriptor file. +2. Add `octopus.com/project-id` annotation in the 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. @@ -93,3 +95,39 @@ spec: You can get the ID of the space from the URL in the Octopus Deploy UI. All set, you will be able to see the plugin in action! + +### Adding Scaffolder field extensions + +To add the Octopus Deploy custom fields extensions, add the following to your `App.tsx`: + +```tsx +// In packages/app/src/App.tsx +import { OctopusDeployDropdownFieldExtension } from '@backstage/plugin-octopus-deploy'; +const routes = ( + + ... + }> + + + + + ... + +``` + +To use the Octopus Deploy custom field extensions, add the following to your `template.yaml`: + +```yaml +# In the template.yaml +apiVersion: scaffolder.backstage.io/v1beta3 +kind: Template +metadata: + # ... + parameters: + - title: Octopus Project Group + properties: + projectName: + title: Octopus Project Group + type: string + ui:field: OctopusDeployProjectGroupDropdown +``` diff --git a/plugins/octopus-deploy/package.json b/plugins/octopus-deploy/package.json index 7f76cdcd27..49488e6515 100644 --- a/plugins/octopus-deploy/package.json +++ b/plugins/octopus-deploy/package.json @@ -37,9 +37,12 @@ "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", + "@backstage/plugin-scaffolder": "workspace:^", + "@backstage/plugin-scaffolder-react": "workspace:^", "@material-ui/core": "^4.9.13", "@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0", - "react-use": "^17.2.4" + "react-use": "^17.2.4", + "zod": "^3.22.4" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/plugins/octopus-deploy/src/api/index.ts b/plugins/octopus-deploy/src/api/index.ts index cb5d428308..15451cd79f 100644 --- a/plugins/octopus-deploy/src/api/index.ts +++ b/plugins/octopus-deploy/src/api/index.ts @@ -62,6 +62,13 @@ export type OctopusProject = { Links: OctopusLinks; }; +/** @public */ +export type OctopusProjectGroup = { + Id: string; + Name: string; + Description: string; +}; + /** @public */ export type OctopusPluginConfig = { WebUiBaseUrl: string; @@ -82,6 +89,7 @@ export interface OctopusDeployApi { releaseHistoryCount: number; }): Promise; getProjectInfo(projectReference: ProjectReference): Promise; + getProjectGroups(): Promise; getConfig(): Promise; } @@ -156,6 +164,30 @@ export class OctopusDeployClient implements OctopusDeployApi { return responseJson; } + async getProjectGroups(): Promise { + const proxyUrl = await this.discoveryApi.getBaseUrl('proxy'); + const url = `${proxyUrl}${this.proxyPathBase}/projectgroups/all`; + 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; + } + async getConfig(): Promise { return { WebUiBaseUrl: this.configApi.getString(WEB_UI_BASE_URL_CONFIG_KEY), diff --git a/plugins/octopus-deploy/src/components/ScaffolderDropdown/ProjectGroupDropdown.tsx b/plugins/octopus-deploy/src/components/ScaffolderDropdown/ProjectGroupDropdown.tsx new file mode 100644 index 0000000000..7d70bcd28e --- /dev/null +++ b/plugins/octopus-deploy/src/components/ScaffolderDropdown/ProjectGroupDropdown.tsx @@ -0,0 +1,74 @@ +/* + * 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 FormControl from '@material-ui/core/FormControl'; +import { InputLabel, Input, FormHelperText } from '@material-ui/core'; +import { Select, SelectItem } from '@backstage/core-components'; +import { useProjectGroups } from '../../hooks/useProjectGroups'; +import { ProjectGroupDropdownProps } from './schema'; + +export const ProjectGroupDropdown = ({ + onChange, + rawErrors, + required, + formData, +}: ProjectGroupDropdownProps) => { + const [selectedProjectGroup, setSelectedProjectGroup] = + React.useState(''); + const { projectGroups } = useProjectGroups(); + + const projectGroupItems: SelectItem[] = projectGroups + ? projectGroups.map(pg => ({ label: pg.Name, value: pg.Name })) + : []; + + const updateProjectGroup = (pg: string) => { + setSelectedProjectGroup(pg); + onChange(pg); + }; + + return ( + 0 && !formData} + > + {projectGroups?.length ? ( + onChange(e.target.value)} + value={formData} + /> + + )} + + The project group of the within Octopus Deploy. + + + ); +}; diff --git a/plugins/octopus-deploy/src/components/ScaffolderDropdown/index.ts b/plugins/octopus-deploy/src/components/ScaffolderDropdown/index.ts new file mode 100644 index 0000000000..ce790d0674 --- /dev/null +++ b/plugins/octopus-deploy/src/components/ScaffolderDropdown/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 { ProjectGroupDropdown } from './ProjectGroupDropdown'; diff --git a/plugins/octopus-deploy/src/components/ScaffolderDropdown/schema.ts b/plugins/octopus-deploy/src/components/ScaffolderDropdown/schema.ts new file mode 100644 index 0000000000..651133bced --- /dev/null +++ b/plugins/octopus-deploy/src/components/ScaffolderDropdown/schema.ts @@ -0,0 +1,25 @@ +/* + * Copyright 2024 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 z from 'zod'; +import { makeFieldSchemaFromZod } from '@backstage/plugin-scaffolder'; + +const ProjectGroupDropdownFieldSchema = makeFieldSchemaFromZod(z.string()); + +export const ProjectGroupDropdownSchema = + ProjectGroupDropdownFieldSchema.schema; + +export type ProjectGroupDropdownProps = + typeof ProjectGroupDropdownFieldSchema.type; diff --git a/plugins/octopus-deploy/src/hooks/useProjectGroups.ts b/plugins/octopus-deploy/src/hooks/useProjectGroups.ts new file mode 100644 index 0000000000..a4dce172c0 --- /dev/null +++ b/plugins/octopus-deploy/src/hooks/useProjectGroups.ts @@ -0,0 +1,36 @@ +/* + * 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, OctopusProjectGroup } from '../api'; + +export function useProjectGroups(): { + projectGroups?: OctopusProjectGroup[]; + loading: boolean; + error?: Error; +} { + const api = useApi(octopusDeployApiRef); + + const { value, loading, error } = useAsync(() => { + return api.getProjectGroups(); + }, [api]); + + return { + projectGroups: value, + loading, + error, + }; +} diff --git a/plugins/octopus-deploy/src/plugin.ts b/plugins/octopus-deploy/src/plugin.ts index 39b2b49fb7..ebb3238254 100644 --- a/plugins/octopus-deploy/src/plugin.ts +++ b/plugins/octopus-deploy/src/plugin.ts @@ -27,7 +27,11 @@ import { configApiRef, } from '@backstage/core-plugin-api'; +import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; + import { Entity } from '@backstage/catalog-model'; +import { ProjectGroupDropdown } from './components/ScaffolderDropdown'; +import { ProjectGroupDropdownSchema } from './components/ScaffolderDropdown/schema'; /** @public */ export const isOctopusDeployAvailable = (entity: Entity) => @@ -61,3 +65,11 @@ export const EntityOctopusDeployContent = octopusDeployPlugin.provide( mountPoint: octopusDeployEntityContentRouteRef, }), ); + +export const OctopusDeployDropdownFieldExtension = octopusDeployPlugin.provide( + createScaffolderFieldExtension({ + name: 'OctopusDeployProjectGroupDropdown', + schema: ProjectGroupDropdownSchema, + component: ProjectGroupDropdown, + }), +); diff --git a/yarn.lock b/yarn.lock index 46e5db8616..56b2b7e617 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7961,12 +7961,15 @@ __metadata: "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" "@backstage/plugin-catalog-react": "workspace:^" + "@backstage/plugin-scaffolder": "workspace:^" + "@backstage/plugin-scaffolder-react": "workspace:^" "@material-ui/core": ^4.9.13 "@testing-library/dom": ^9.0.0 "@testing-library/jest-dom": ^6.0.0 "@testing-library/react": ^14.0.0 "@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0 react-use: ^17.2.4 + zod: ^3.22.4 peerDependencies: react: ^16.13.1 || ^17.0.0 || ^18.0.0 react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0