diff --git a/app-config.yaml b/app-config.yaml index dd72051189..17bc057bac 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -118,6 +118,11 @@ proxy: headers: Authorization: 'Api-Token ${DYNATRACE_ACCESS_TOKEN}' + '/stackstorm': + target: https://your.stackstorm.instance.com/api + headers: + St2-Api-Key: ${ST2_API_KEY} + organization: name: My Company @@ -443,3 +448,6 @@ gocd: permission: enabled: true + +stackstorm: + webUrl: 'https://your.stackstorm.webui.com' diff --git a/microsite/data/plugins/stackstorm.yaml b/microsite/data/plugins/stackstorm.yaml new file mode 100644 index 0000000000..282117d1fe --- /dev/null +++ b/microsite/data/plugins/stackstorm.yaml @@ -0,0 +1,13 @@ +--- +title: StackStorm +author: ExpediaGroup +authorUrl: https://github.com/ExpediaGroup +category: Automation +description: Manage StackStorm workflow executions from within Backstage. +documentation: https://github.com/backstage/backstage/tree/master/plugins/stackstorm +npmPackageName: '@backstage/plugin-stackstorm' +tags: + - stackstorm + - st2 + - automation + - workflow diff --git a/packages/app/package.json b/packages/app/package.json index f6631ed05b..8ae7164e0a 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -56,6 +56,7 @@ "@backstage/plugin-sentry": "workspace:^", "@backstage/plugin-shortcuts": "workspace:^", "@backstage/plugin-stack-overflow": "workspace:^", + "@backstage/plugin-stackstorm": "workspace:^", "@backstage/plugin-tech-insights": "workspace:^", "@backstage/plugin-tech-radar": "workspace:^", "@backstage/plugin-techdocs": "workspace:^", diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 8984a69259..a39430a5eb 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -107,6 +107,7 @@ import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common' import { PlaylistIndexPage } from '@backstage/plugin-playlist'; import { TwoColumnLayout } from './components/scaffolder/customScaffolderLayouts'; import { ScoreBoardPage } from '@oriflame/backstage-plugin-score-card'; +import { StackstormPage } from '@backstage/plugin-stackstorm'; const app = createApp({ apis, @@ -279,6 +280,7 @@ const routes = ( } /> } /> } /> + } /> ); diff --git a/plugins/stackstorm/.eslintrc.js b/plugins/stackstorm/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/stackstorm/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/stackstorm/README.md b/plugins/stackstorm/README.md new file mode 100644 index 0000000000..1671e6150f --- /dev/null +++ b/plugins/stackstorm/README.md @@ -0,0 +1,60 @@ +# StackStorm Plugin + +Welcome to the StackStorm plugin! + +A Backstage integration for the [StackStorm](https://docs.stackstorm.com/overview.html). +This plugin allows you to display a list of executions, view execution details, +browse installed packs, actions and more. + +## Getting started + +To get started, first you need a running instance of StackStorm. +One of the quickest ways is [running StackStorm with Docker](https://docs.stackstorm.com/install/docker.html). + +### Installation + +1. Install the plugin with `yarn` in the root of your Backstage directory + +```bash +yarn --cwd packages/app add @backstage/plugin-stackstorm +``` + +2. Import and use the plugin in `packages/app/src/App.tsx` + +```tsx +import { StackstormPage } from '@backstage/plugin-stackstorm'; + +const routes = ( + + {/* ...other routes */} + } /> + +``` + +### Configuration + +1. Configure `webUrl` for links to the StackStorm Web UI in `app-config.yaml`. + +```yaml +stackstorm: + webUrl: 'https://your.stackstorm.webui.com' +``` + +2. Configure `stackstorm` proxy + This plugin uses the Backstage proxy to securely communicate with StackStorm API. + Add the following to your `app-config.yaml` to enable this configuration: + +```yaml +proxy: + '/stackstorm': + target: https://your.stackstorm.instance.com/api + headers: + St2-Api-Key: ${ST2_API_KEY} +``` + +In your production deployment of Backstage, you would also need to ensure that +you've set the `ST2_API_KEY` environment variable before starting +the backend. + +Read more about how to find or generate this key in the +[StackStorm Authentication Documentation](https://docs.stackstorm.com/authentication.html#api-keys). diff --git a/plugins/stackstorm/config.d.ts b/plugins/stackstorm/config.d.ts new file mode 100644 index 0000000000..cf7a2e2253 --- /dev/null +++ b/plugins/stackstorm/config.d.ts @@ -0,0 +1,38 @@ +/* + * 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 interface Config { + stackstorm?: { + /** + * StackStorm API base url + * Used for local testing to bypass CORS + * @visibility frontend + */ + baseUrl?: string; + /** + * StackStorm API key + * Used for local testing to bypass CORS + * Do not set this in production deployment + * @visibility frontend + */ + key?: string; + /** + * StackStorm Web UI url + * Used in links to StackStorm web UI + * @visibility frontend + */ + webUrl?: string; + }; +} diff --git a/plugins/stackstorm/dev/index.tsx b/plugins/stackstorm/dev/index.tsx new file mode 100644 index 0000000000..33bd89adda --- /dev/null +++ b/plugins/stackstorm/dev/index.tsx @@ -0,0 +1,27 @@ +/* + * 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 { stackstormPlugin, StackstormPage } from '../src/plugin'; + +createDevApp() + .registerPlugin(stackstormPlugin) + .addPage({ + element: , + title: 'Root Page', + path: '/stackstorm', + }) + .render(); diff --git a/plugins/stackstorm/package.json b/plugins/stackstorm/package.json new file mode 100644 index 0000000000..921cb07c29 --- /dev/null +++ b/plugins/stackstorm/package.json @@ -0,0 +1,65 @@ +{ + "name": "@backstage/plugin-stackstorm", + "description": "A Backstage plugin that integrates towards StackStorm", + "version": "0.1.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" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/stackstorm" + }, + "keywords": [ + "backstage", + "stackstorm", + "st2" + ], + "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/core-components": "workspace:^", + "@backstage/core-plugin-api": "workspace:^", + "@backstage/theme": "workspace:^", + "@material-ui/core": "^4.12.2", + "@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", + "config.d.ts" + ], + "configSchema": "config.d.ts" +} diff --git a/plugins/stackstorm/src/api/StackStormClient.ts b/plugins/stackstorm/src/api/StackStormClient.ts new file mode 100644 index 0000000000..c93901ce43 --- /dev/null +++ b/plugins/stackstorm/src/api/StackStormClient.ts @@ -0,0 +1,78 @@ +/* + * 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 { Action, Execution, Pack, StackStormApi } from './types'; +import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api'; + +export class StackStormClient implements StackStormApi { + private readonly discoveryApi: DiscoveryApi; + private readonly identityApi: IdentityApi; + + constructor({ + discoveryApi, + identityApi, + }: { + discoveryApi: DiscoveryApi; + identityApi: IdentityApi; + }) { + this.discoveryApi = discoveryApi; + this.identityApi = identityApi; + } + + private async get(input: string): Promise { + const { token: idToken } = await this.identityApi.getCredentials(); + const apiUrl = `${await this.discoveryApi.getBaseUrl('proxy')}/stackstorm`; + const response = await fetch(`${apiUrl}${input}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + ...(idToken && { Authorization: `Bearer ${idToken}` }), + }, + }); + + if (!response.ok) throw new Error(`Unable to get data: ${response.status}`); + return await response.json(); + } + + async getExecutions(limit?: number, offset?: number): Promise { + const params = { + limit: limit?.toString() || '25', + offset: offset?.toString() || '0', + include_attributes: + 'id,status,start_timestamp,action.ref,action.name,rule.ref', + parent: 'null', + }; + const path = `/executions?${new URLSearchParams(params)}`; + return this.get(path); + } + + async getExecution(id: string): Promise { + const path = `/executions/${id}`; + return this.get(path); + } + + async getPacks(): Promise { + return this.get('/packs'); + } + + async getActions(pack: string): Promise { + const params = { + include_attributes: 'id,ref,name,pack,description,runner_type', + pack: pack, + }; + const path = `/actions?${new URLSearchParams(params)}`; + return this.get(path); + } +} diff --git a/plugins/stackstorm/src/api/index.ts b/plugins/stackstorm/src/api/index.ts new file mode 100644 index 0000000000..2291fb1f73 --- /dev/null +++ b/plugins/stackstorm/src/api/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 { stackStormApiRef } from './types'; +export type { + Action, + Execution, + ExecutionLog, + Pack, + StackStormApi, +} from './types'; +export { StackStormClient } from './StackStormClient'; diff --git a/plugins/stackstorm/src/api/types.ts b/plugins/stackstorm/src/api/types.ts new file mode 100644 index 0000000000..8a3036d3ca --- /dev/null +++ b/plugins/stackstorm/src/api/types.ts @@ -0,0 +1,59 @@ +/* + * 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 } from '@backstage/core-plugin-api'; + +export const stackStormApiRef = createApiRef({ + id: 'plugin.stackstorm.service', +}); + +export type Execution = { + id: string; + action: Action; + status: string; + start_timestamp: Date; + end_timestamp: Date; + result: object; + parameters: object; + elapsed_seconds: number; + log: ExecutionLog[]; +}; + +export type ExecutionLog = { + status: string; + timestamp: Date; +}; + +export type Action = { + id: string; + name: string; + ref: string; + pack: string; + description: string; + runner_type: string; +}; + +export type Pack = { + ref: string; + description: string; + version: string; +}; + +export interface StackStormApi { + getExecutions(limit: number, offset: number): Promise; + getExecution(id: string): Promise; + getPacks(): Promise; + getActions(pack: string): Promise; +} diff --git a/plugins/stackstorm/src/components/ActionsList/ActionsList.tsx b/plugins/stackstorm/src/components/ActionsList/ActionsList.tsx new file mode 100644 index 0000000000..014e8c31a3 --- /dev/null +++ b/plugins/stackstorm/src/components/ActionsList/ActionsList.tsx @@ -0,0 +1,161 @@ +/* + * 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, { useState } from 'react'; +import useAsync from 'react-use/lib/useAsync'; +import { Progress } from '@backstage/core-components'; +import { useApi, configApiRef } from '@backstage/core-plugin-api'; +import { + List, + ListItemText, + Collapse, + ListItem, + ListItemSecondaryAction, + ListItemIcon, +} from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import ExpandMore from '@material-ui/icons/ExpandMore'; +import ExpandLess from '@material-ui/icons/ExpandLess'; +import { Alert } from '@material-ui/lab'; +import { Action, Pack, stackStormApiRef } from '../../api'; + +const useStyles = makeStyles(theme => ({ + root: { + width: '100%', + backgroundColor: theme.palette.background.paper, + }, + actions: { + borderBottom: `2px solid ${theme.palette.divider}`, + }, + nested: { + paddingLeft: theme.spacing(8), + paddingRight: theme.spacing(4), + }, + icon: { + minWidth: '34px', + }, +})); + +type ActionItemsProps = { + pack: Pack; +}; + +export const ActionItems = ({ pack }: ActionItemsProps) => { + const classes = useStyles(); + const st2 = useApi(stackStormApiRef); + const config = useApi(configApiRef); + + const { value, loading, error } = useAsync(async (): Promise => { + const data = await st2.getActions(pack.ref); + return data; + }, []); + + if (loading) { + return ; + } else if (error) { + return {error.message}; + } + + return ( + + {(value || []).map(a => { + return ( + + window.open( + `${config.getString('stackstorm.webUrl')}/?#/actions/${a.ref}`, + '_blank', + ) + } + > + + {a.runner_type} + + ); + })} + + ); +}; + +type PackListItemProps = { + pack: Pack; + opened: boolean; + onClick: (ref: string) => any; +}; + +export const PackListItem = ({ pack, opened, onClick }: PackListItemProps) => { + const classes = useStyles(); + + return ( + <> + onClick(pack.ref)}> + + {opened ? : } + + + + version: {pack.version} + + + + + + + ); +}; + +export const ActionsList = () => { + const st2 = useApi(stackStormApiRef); + + const classes = useStyles(); + const [expanded, setExpanded] = useState([]); + + const onClick = (ref: string) => { + setExpanded(refs => + refs.includes(ref) ? refs.filter(r => r !== ref) : refs.concat(ref), + ); + }; + + const { value, loading, error } = useAsync(async (): Promise => { + const data = await st2.getPacks(); + return data; + }, []); + + if (loading) { + return ; + } else if (error) { + return {error.message}; + } + + return ( + + {(value || []).map(p => { + return ( + + ); + })} + + ); +}; diff --git a/plugins/stackstorm/src/components/ActionsList/index.ts b/plugins/stackstorm/src/components/ActionsList/index.ts new file mode 100644 index 0000000000..1606d1be3d --- /dev/null +++ b/plugins/stackstorm/src/components/ActionsList/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 { ActionsList } from './ActionsList'; diff --git a/plugins/stackstorm/src/components/ExecutionsTable/ExecutionPanel.tsx b/plugins/stackstorm/src/components/ExecutionsTable/ExecutionPanel.tsx new file mode 100644 index 0000000000..76f11aaf73 --- /dev/null +++ b/plugins/stackstorm/src/components/ExecutionsTable/ExecutionPanel.tsx @@ -0,0 +1,165 @@ +/* + * 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 { CodeSnippet, Progress } from '@backstage/core-components'; +import { configApiRef, useApi } from '@backstage/core-plugin-api'; +import { Execution, stackStormApiRef } from '../../api'; +import useAsync from 'react-use/lib/useAsync'; +import { Alert } from '@material-ui/lab'; +import { + Button, + Card, + CardActions, + CardContent, + makeStyles, + Table, + TableBody, + TableCell, + TableRow, + Typography, + withStyles, +} from '@material-ui/core'; +import { Status } from './Status'; + +const useStyles = makeStyles(theme => ({ + table: { + maxWidth: '50%', + flex: 'i', + }, + title: { + paddingTop: theme.spacing(2), + fontSize: 14, + fontWeight: 'bold', + textTransform: 'uppercase', + }, + card: { + borderBottom: `2px solid ${theme.palette.divider}`, + }, +})); + +const THead = withStyles(() => ({ + root: { + paddingLeft: 0, + }, +}))(TableCell); + +const TRow = withStyles(theme => ({ + root: { + '&:nth-of-type(odd)': { + backgroundColor: theme.palette.background.paper, + }, + }, +}))(TableRow); + +const ExecutionCard = ({ e }: { e: Execution }) => { + const config = useApi(configApiRef); + const classes = useStyles(); + + const webUrl = `${config.getString('stackstorm.webUrl')}/?#/history/${e.id}`; + + return ( + + + + + + + Name + + {e.action.ref} + + + + Status + + + + + + + + Execution ID + + {e.id} + + + + Started + + {new Date(e.start_timestamp).toUTCString()} + + + + Finished + + {new Date(e.end_timestamp).toUTCString()} + + + + Execution Time + + {Math.round(e.elapsed_seconds)} s + + + + Runner + + {e.action.runner_type} + + +
+ + Action Output + + + + Action Input + + +
+ + + +
+ ); +}; + +export const ExecutionPanel = ({ id }: { id: string }) => { + const st2 = useApi(stackStormApiRef); + + const { value, loading, error } = useAsync(async (): Promise => { + const data = await st2.getExecution(id); + return data; + }, []); + + if (loading) { + return ; + } else if (error) { + return {error.message}; + } + + return ; +}; diff --git a/plugins/stackstorm/src/components/ExecutionsTable/ExecutionsTable.tsx b/plugins/stackstorm/src/components/ExecutionsTable/ExecutionsTable.tsx new file mode 100644 index 0000000000..4dbd64399f --- /dev/null +++ b/plugins/stackstorm/src/components/ExecutionsTable/ExecutionsTable.tsx @@ -0,0 +1,130 @@ +/* + * 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, { useEffect, useState } from 'react'; +import { Link, Table, TableColumn } from '@backstage/core-components'; +import { useApi, configApiRef, errorApiRef } from '@backstage/core-plugin-api'; +import { Execution, stackStormApiRef } from '../../api'; +import { Status } from './Status'; +import { ExecutionPanel } from './ExecutionPanel'; + +type DenseTableProps = { + executions: Execution[]; + loading: boolean; + page: number; + pageSize: number; + onPageChange: (page: number) => void; + onRowsPerPageChange: (rows: number) => void; +}; + +export const DenseTable = ({ + executions, + loading, + page, + pageSize, + onPageChange, + onRowsPerPageChange, +}: DenseTableProps) => { + const config = useApi(configApiRef); + + const columns: TableColumn[] = [ + { + title: 'Status', + field: 'status', + render: e => , + }, + { + title: 'Time', + field: 'start_timestamp', + render: e => `${new Date(e.start_timestamp).toUTCString()}`, + }, + { title: 'Name', field: 'action.ref' }, + { + title: 'Execution ID', + field: 'id', + render: e => ( + + {e.id} + + ), + }, + ]; + + const count = + pageSize > executions.length + ? (page + 1) * pageSize + executions.length - pageSize + : (page + 1) * pageSize + 1; + + return ( + { + return ; + }} + /> + ); +}; + +export const ExecutionsTable = () => { + const st2 = useApi(stackStormApiRef); + const errorApi = useApi(errorApiRef); + const [page, setPage] = useState(0); + const [rowsPerPage, setRowsPerPage] = useState(10); + const [data, setData] = useState([]); + const [loading, setLoading] = useState(false); + + useEffect(() => { + const getData = async () => { + setLoading(true); + await st2 + .getExecutions(rowsPerPage, page * rowsPerPage) + .then(d => { + setData(d); + }) + .catch(err => { + errorApi.post(err); + }); + setLoading(false); + }; + getData(); + }, [errorApi, page, rowsPerPage, st2]); + + return ( + + ); +}; diff --git a/plugins/stackstorm/src/components/ExecutionsTable/Status.tsx b/plugins/stackstorm/src/components/ExecutionsTable/Status.tsx new file mode 100644 index 0000000000..99a94df2c8 --- /dev/null +++ b/plugins/stackstorm/src/components/ExecutionsTable/Status.tsx @@ -0,0 +1,57 @@ +/* + * 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 { + StatusOK, + StatusError, + StatusRunning, + StatusWarning, +} from '@backstage/core-components'; +import React from 'react'; + +export const Status = ({ status }: { status: string | undefined }) => { + if (status === undefined) return null; + const st = status.toLocaleLowerCase('en-US'); + switch (status) { + case 'succeeded': + case 'enabled': + case 'complete': + return ( + <> + {st} + + ); + case 'scheduled': + case 'running': + return ( + <> + {st} + + ); + case 'failed': + case 'error': + return ( + <> + {st} + + ); + default: + return ( + <> + {st} + + ); + } +}; diff --git a/plugins/stackstorm/src/components/ExecutionsTable/index.ts b/plugins/stackstorm/src/components/ExecutionsTable/index.ts new file mode 100644 index 0000000000..402d8e557d --- /dev/null +++ b/plugins/stackstorm/src/components/ExecutionsTable/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 { ExecutionsTable } from './ExecutionsTable'; diff --git a/plugins/stackstorm/src/components/PacksTable/PacksTable.tsx b/plugins/stackstorm/src/components/PacksTable/PacksTable.tsx new file mode 100644 index 0000000000..b150aeec4d --- /dev/null +++ b/plugins/stackstorm/src/components/PacksTable/PacksTable.tsx @@ -0,0 +1,59 @@ +/* + * 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 { Progress, Table, TableColumn } from '@backstage/core-components'; +import { Alert } from '@material-ui/lab'; +import useAsync from 'react-use/lib/useAsync'; +import { useApi } from '@backstage/core-plugin-api'; +import { Pack, stackStormApiRef } from '../../api'; + +type DenseTableProps = { + packs: Pack[]; +}; + +export const DenseTable = ({ packs }: DenseTableProps) => { + const columns: TableColumn[] = [ + { title: 'Name', field: 'ref' }, + { title: 'Description', field: 'description' }, + { title: 'Version', field: 'version' }, + ]; + + return ( +
+ ); +}; + +export const PacksTable = () => { + const st2 = useApi(stackStormApiRef); + + const { value, loading, error } = useAsync(async (): Promise => { + const data = await st2.getPacks(); + return data; + }, []); + + if (loading) { + return ; + } else if (error) { + return {error.message}; + } + + return ; +}; diff --git a/plugins/stackstorm/src/components/PacksTable/index.ts b/plugins/stackstorm/src/components/PacksTable/index.ts new file mode 100644 index 0000000000..378e32a5d5 --- /dev/null +++ b/plugins/stackstorm/src/components/PacksTable/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 { PacksTable } from './PacksTable'; diff --git a/plugins/stackstorm/src/components/StackStormHome/StackStormHome.test.tsx b/plugins/stackstorm/src/components/StackStormHome/StackStormHome.test.tsx new file mode 100644 index 0000000000..6249fa71e8 --- /dev/null +++ b/plugins/stackstorm/src/components/StackStormHome/StackStormHome.test.tsx @@ -0,0 +1,38 @@ +/* + * 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 { StackStormHome } from './StackStormHome'; +import { screen } from '@testing-library/react'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { StackStormApi, stackStormApiRef } from '../../api'; + +describe('StackStormHome', () => { + const mockApi: jest.Mocked = { + getExecutions: jest.fn().mockResolvedValue([]), + getExecution: jest.fn().mockResolvedValue({}), + getPacks: jest.fn().mockResolvedValue([]), + getActions: jest.fn().mockResolvedValue([]), + } as any; + + it('should render', async () => { + await renderInTestApp( + + + , + ); + expect(screen.getByText('Welcome to StackStorm!')).toBeInTheDocument(); + }); +}); diff --git a/plugins/stackstorm/src/components/StackStormHome/StackStormHome.tsx b/plugins/stackstorm/src/components/StackStormHome/StackStormHome.tsx new file mode 100644 index 0000000000..a059505c2b --- /dev/null +++ b/plugins/stackstorm/src/components/StackStormHome/StackStormHome.tsx @@ -0,0 +1,68 @@ +/* + * 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 { Grid, IconButton } from '@material-ui/core'; +import { + Header, + Page, + HeaderLabel, + TabbedLayout, + Content, +} from '@backstage/core-components'; +import LibraryBooks from '@material-ui/icons/LibraryBooks'; +import { ExecutionsTable } from '../ExecutionsTable'; +import { PacksTable } from '../PacksTable/PacksTable'; +import { ActionsList } from '../ActionsList'; + +export const StackStormHome = () => ( + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+); diff --git a/plugins/stackstorm/src/components/StackStormHome/index.ts b/plugins/stackstorm/src/components/StackStormHome/index.ts new file mode 100644 index 0000000000..27c6db48ab --- /dev/null +++ b/plugins/stackstorm/src/components/StackStormHome/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 { StackStormHome } from './StackStormHome'; diff --git a/plugins/stackstorm/src/index.ts b/plugins/stackstorm/src/index.ts new file mode 100644 index 0000000000..aff9e2d78a --- /dev/null +++ b/plugins/stackstorm/src/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 { stackstormPlugin, StackstormPage } from './plugin'; diff --git a/plugins/stackstorm/src/plugin.test.ts b/plugins/stackstorm/src/plugin.test.ts new file mode 100644 index 0000000000..9ef2f438c1 --- /dev/null +++ b/plugins/stackstorm/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 { stackstormPlugin } from './plugin'; + +describe('stackstorm', () => { + it('should export plugin', () => { + expect(stackstormPlugin).toBeDefined(); + }); +}); diff --git a/plugins/stackstorm/src/plugin.ts b/plugins/stackstorm/src/plugin.ts new file mode 100644 index 0000000000..012c78372b --- /dev/null +++ b/plugins/stackstorm/src/plugin.ts @@ -0,0 +1,55 @@ +/* + * 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 { + createApiFactory, + createPlugin, + createRoutableExtension, + discoveryApiRef, + identityApiRef, +} from '@backstage/core-plugin-api'; +import { stackStormApiRef, StackStormClient } from './api'; + +import { rootRouteRef } from './routes'; + +export const stackstormPlugin = createPlugin({ + id: 'stackstorm', + apis: [ + createApiFactory({ + api: stackStormApiRef, + deps: { + discoveryApi: discoveryApiRef, + identityApi: identityApiRef, + }, + factory: ({ discoveryApi, identityApi }) => + new StackStormClient({ + discoveryApi, + identityApi, + }), + }), + ], + routes: { + root: rootRouteRef, + }, +}); + +export const StackstormPage = stackstormPlugin.provide( + createRoutableExtension({ + name: 'StackstormPage', + component: () => + import('./components/StackStormHome').then(m => m.StackStormHome), + mountPoint: rootRouteRef, + }), +); diff --git a/plugins/stackstorm/src/routes.ts b/plugins/stackstorm/src/routes.ts new file mode 100644 index 0000000000..90cf7760bc --- /dev/null +++ b/plugins/stackstorm/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 rootRouteRef = createRouteRef({ + id: 'stackstorm', +}); diff --git a/plugins/stackstorm/src/setupTests.ts b/plugins/stackstorm/src/setupTests.ts new file mode 100644 index 0000000000..73dd8dce47 --- /dev/null +++ b/plugins/stackstorm/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/yarn.lock b/yarn.lock index 4e56cb9839..5c8cdcad98 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7955,6 +7955,32 @@ __metadata: languageName: unknown linkType: soft +"@backstage/plugin-stackstorm@workspace:^, @backstage/plugin-stackstorm@workspace:plugins/stackstorm": + version: 0.0.0-use.local + resolution: "@backstage/plugin-stackstorm@workspace:plugins/stackstorm" + dependencies: + "@backstage/cli": "workspace:^" + "@backstage/core-app-api": "workspace:^" + "@backstage/core-components": "workspace:^" + "@backstage/core-plugin-api": "workspace:^" + "@backstage/dev-utils": "workspace:^" + "@backstage/test-utils": "workspace:^" + "@backstage/theme": "workspace:^" + "@material-ui/core": ^4.12.2 + "@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-tech-insights-backend-module-jsonfc@workspace:^, @backstage/plugin-tech-insights-backend-module-jsonfc@workspace:plugins/tech-insights-backend-module-jsonfc": version: 0.0.0-use.local resolution: "@backstage/plugin-tech-insights-backend-module-jsonfc@workspace:plugins/tech-insights-backend-module-jsonfc" @@ -22492,6 +22518,7 @@ __metadata: "@backstage/plugin-sentry": "workspace:^" "@backstage/plugin-shortcuts": "workspace:^" "@backstage/plugin-stack-overflow": "workspace:^" + "@backstage/plugin-stackstorm": "workspace:^" "@backstage/plugin-tech-insights": "workspace:^" "@backstage/plugin-tech-radar": "workspace:^" "@backstage/plugin-techdocs": "workspace:^"