diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/TemplateActionRegistry.ts b/plugins/scaffolder-backend/src/scaffolder/actions/TemplateActionRegistry.ts index 2a841a564b..c9e163bfdd 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/TemplateActionRegistry.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/TemplateActionRegistry.ts @@ -38,4 +38,8 @@ export class TemplateActionRegistry { } return action; } + + list(): TemplateAction[] { + return [...this.actions.values()]; + } } diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 3d47d5123b..b77c832ca1 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -347,6 +347,15 @@ export async function createRouter( } }, ) + .get('/v2/actions', async (_req, res) => { + const actionsList = actionRegistry.list().map(action => { + return { + id: action.id, + schema: action.schema, + }; + }); + res.json(actionsList); + }) .post('/v2/tasks', async (req, res) => { const templateName: string = req.body.templateName; const values: TemplaterValues = req.body.values; diff --git a/plugins/scaffolder/src/api.ts b/plugins/scaffolder/src/api.ts index 2be4caa81f..4e03723272 100644 --- a/plugins/scaffolder/src/api.ts +++ b/plugins/scaffolder/src/api.ts @@ -25,7 +25,7 @@ import { } from '@backstage/core'; import { ScmIntegrations } from '@backstage/integration'; import ObservableImpl from 'zen-observable'; -import { ScaffolderTask, Status } from './types'; +import { ListActionsResponse, ScaffolderTask, Status } from './types'; export const scaffolderApiRef = createApiRef({ id: 'plugin.scaffolder.service', @@ -72,6 +72,9 @@ export interface ScaffolderApi { allowedHosts: string[]; }): Promise<{ type: string; title: string; host: string }[]>; + // Returns a list of all installed actions. + listActions(): Promise; + streamLogs({ taskId, after, @@ -227,4 +230,13 @@ export class ScaffolderClient implements ScaffolderApi { ); }); } + + /** + * @Returns ListActionsResponse containg all registered actions. + */ + async listActions(): Promise { + const baseUrl = await this.discoveryApi.getBaseUrl('scaffolder'); + const response = await fetch(`${baseUrl}/v2/actions`); + return await response.json(); + } } diff --git a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx new file mode 100644 index 0000000000..10bc593cf9 --- /dev/null +++ b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx @@ -0,0 +1,154 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { useAsync } from 'react-use'; +import { + useApi, + Progress, + Content, + Header, + Page, + ErrorPage, + Button, + ItemCardGrid, + ItemCardHeader, +} from '@backstage/core'; +import { scaffolderApiRef } from '../../api'; +import { + Card, + CardMedia, + CardContent, + CardActions, + Typography, + Paper, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + makeStyles, +} from '@material-ui/core'; +import { JSONSchema } from '@backstage/catalog-model'; + +const useStyles = makeStyles({ + table: { + // minWidth: 650, + }, +}); + +export const ActionsPage = () => { + const api = useApi(scaffolderApiRef); + const classes = useStyles(); + const { loading, value, error } = useAsync(async () => { + return api.listActions(); + }); + + if (loading) { + return ; + } + + if (error) { + return ( + + ); + } + + const formatRows = (input: JSONSchema) => { + const properties = input.properties; + if (!properties) { + return undefined; + } + const required = input.required ? input.required : []; + + return Object.entries(properties).map(entry => { + const [key, props] = entry; + const isRequired = required.includes(key); + return ( + + + {key} + {isRequired && *} + + {props.title} + {props.description} + {props.type} + + ); + }); + }; + + const items = value?.map(action => { + if (action.id.startsWith('legacy:')) { + return undefined; + } + return ( + <> + {action.id} + {action.schema?.input && ( + <> + Input + + + + + Name + Title + Description + Type + + + {formatRows(action.schema?.input)} +
+
+ + )} + {action.schema?.output && ( + <> + Output + + + + + Name + Title + Description + Type + + + {formatRows(action.schema?.output)} +
+
+ + )} + + ); + }); + + return ( + +
+ {items} + + ); +}; diff --git a/plugins/scaffolder/src/components/ActionsPage/index.ts b/plugins/scaffolder/src/components/ActionsPage/index.ts new file mode 100644 index 0000000000..64d548e37d --- /dev/null +++ b/plugins/scaffolder/src/components/ActionsPage/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { ActionsPage } from './ActionsPage'; diff --git a/plugins/scaffolder/src/components/Router.tsx b/plugins/scaffolder/src/components/Router.tsx index 12d4ecce82..9a525c0be4 100644 --- a/plugins/scaffolder/src/components/Router.tsx +++ b/plugins/scaffolder/src/components/Router.tsx @@ -19,11 +19,13 @@ import { Routes, Route } from 'react-router'; import { ScaffolderPage } from './ScaffolderPage'; import { TemplatePage } from './TemplatePage'; import { TaskPage } from './TaskPage'; +import { ActionsPage } from './ActionsPage'; export const Router = () => ( } /> } /> } /> + } /> ); diff --git a/plugins/scaffolder/src/types.ts b/plugins/scaffolder/src/types.ts index 0499f4aca2..886650859a 100644 --- a/plugins/scaffolder/src/types.ts +++ b/plugins/scaffolder/src/types.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { JSONSchema } from '@backstage/catalog-model'; import { JsonValue } from '@backstage/config'; export type Status = 'open' | 'processing' | 'failed' | 'completed'; @@ -54,3 +55,11 @@ export type ScaffolderTask = { lastHeartbeatAt: string; createdAt: string; }; + +export type ListActionsResponse = Array<{ + id: string; + schema?: { + input?: JSONSchema; + output?: JSONSchema; + }; +}>;