From 67e44a0023b8a30f1d9019fa398ea5545b41a5f1 Mon Sep 17 00:00:00 2001 From: pamelin Date: Thu, 2 Feb 2023 16:38:01 +0000 Subject: [PATCH 01/24] feat: Add StackStorm plugin Signed-off-by: pamelin --- app-config.yaml | 8 + microsite/data/plugins/stackstorm.yaml | 13 ++ packages/app/package.json | 1 + packages/app/src/App.tsx | 2 + plugins/stackstorm/.eslintrc.js | 1 + plugins/stackstorm/README.md | 60 +++++++ plugins/stackstorm/config.d.ts | 38 ++++ plugins/stackstorm/dev/index.tsx | 27 +++ plugins/stackstorm/package.json | 65 +++++++ .../stackstorm/src/api/StackStormClient.ts | 78 +++++++++ plugins/stackstorm/src/api/index.ts | 24 +++ plugins/stackstorm/src/api/types.ts | 59 +++++++ .../components/ActionsList/ActionsList.tsx | 161 +++++++++++++++++ .../src/components/ActionsList/index.ts | 16 ++ .../ExecutionsTable/ExecutionPanel.tsx | 165 ++++++++++++++++++ .../ExecutionsTable/ExecutionsTable.tsx | 130 ++++++++++++++ .../src/components/ExecutionsTable/Status.tsx | 57 ++++++ .../src/components/ExecutionsTable/index.ts | 16 ++ .../src/components/PacksTable/PacksTable.tsx | 59 +++++++ .../src/components/PacksTable/index.ts | 16 ++ .../StackStormHome/StackStormHome.test.tsx | 38 ++++ .../StackStormHome/StackStormHome.tsx | 68 ++++++++ .../src/components/StackStormHome/index.ts | 16 ++ plugins/stackstorm/src/index.ts | 16 ++ plugins/stackstorm/src/plugin.test.ts | 22 +++ plugins/stackstorm/src/plugin.ts | 55 ++++++ plugins/stackstorm/src/routes.ts | 20 +++ plugins/stackstorm/src/setupTests.ts | 17 ++ yarn.lock | 27 +++ 29 files changed, 1275 insertions(+) create mode 100644 microsite/data/plugins/stackstorm.yaml create mode 100644 plugins/stackstorm/.eslintrc.js create mode 100644 plugins/stackstorm/README.md create mode 100644 plugins/stackstorm/config.d.ts create mode 100644 plugins/stackstorm/dev/index.tsx create mode 100644 plugins/stackstorm/package.json create mode 100644 plugins/stackstorm/src/api/StackStormClient.ts create mode 100644 plugins/stackstorm/src/api/index.ts create mode 100644 plugins/stackstorm/src/api/types.ts create mode 100644 plugins/stackstorm/src/components/ActionsList/ActionsList.tsx create mode 100644 plugins/stackstorm/src/components/ActionsList/index.ts create mode 100644 plugins/stackstorm/src/components/ExecutionsTable/ExecutionPanel.tsx create mode 100644 plugins/stackstorm/src/components/ExecutionsTable/ExecutionsTable.tsx create mode 100644 plugins/stackstorm/src/components/ExecutionsTable/Status.tsx create mode 100644 plugins/stackstorm/src/components/ExecutionsTable/index.ts create mode 100644 plugins/stackstorm/src/components/PacksTable/PacksTable.tsx create mode 100644 plugins/stackstorm/src/components/PacksTable/index.ts create mode 100644 plugins/stackstorm/src/components/StackStormHome/StackStormHome.test.tsx create mode 100644 plugins/stackstorm/src/components/StackStormHome/StackStormHome.tsx create mode 100644 plugins/stackstorm/src/components/StackStormHome/index.ts create mode 100644 plugins/stackstorm/src/index.ts create mode 100644 plugins/stackstorm/src/plugin.test.ts create mode 100644 plugins/stackstorm/src/plugin.ts create mode 100644 plugins/stackstorm/src/routes.ts create mode 100644 plugins/stackstorm/src/setupTests.ts 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:^" From f3a15259e912bf054a26fcf14d3221a2311c157b Mon Sep 17 00:00:00 2001 From: pamelin Date: Fri, 3 Feb 2023 11:12:47 +0000 Subject: [PATCH 02/24] fix: Add changeset Signed-off-by: pamelin --- .changeset/quick-tables-listen.md | 5 +++++ plugins/stackstorm/package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/quick-tables-listen.md diff --git a/.changeset/quick-tables-listen.md b/.changeset/quick-tables-listen.md new file mode 100644 index 0000000000..cb2e4eb064 --- /dev/null +++ b/.changeset/quick-tables-listen.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-stackstorm': minor +--- + +Add StackStorm plugin to Backstage which is interfacing with StackStorm API. It allows users to view workflow executions, packs and actions. For installation and configuration instyructions please view [README.md](https://github.com/backstage/backstage/tree/master/plugins/stackstorm/README.md) diff --git a/plugins/stackstorm/package.json b/plugins/stackstorm/package.json index 921cb07c29..03e65d21c5 100644 --- a/plugins/stackstorm/package.json +++ b/plugins/stackstorm/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-stackstorm", "description": "A Backstage plugin that integrates towards StackStorm", - "version": "0.1.0", + "version": "0.0.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", From 68bac7113f490e91c58605608ed27afef7d29b6a Mon Sep 17 00:00:00 2001 From: pamelin Date: Fri, 3 Feb 2023 11:38:14 +0000 Subject: [PATCH 03/24] fix: Format app-config.yaml Signed-off-by: pamelin --- app-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app-config.yaml b/app-config.yaml index 17bc057bac..743ca31c4b 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -450,4 +450,4 @@ permission: enabled: true stackstorm: - webUrl: 'https://your.stackstorm.webui.com' + webUrl: https://your.stackstorm.webui.com From 46649661a6b077b7f7b9384b25767962a0b6fe7f Mon Sep 17 00:00:00 2001 From: pamelin Date: Fri, 3 Feb 2023 11:43:39 +0000 Subject: [PATCH 04/24] fix: Typo in the changeset Signed-off-by: pamelin --- .changeset/quick-tables-listen.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/quick-tables-listen.md b/.changeset/quick-tables-listen.md index cb2e4eb064..fab9ef7969 100644 --- a/.changeset/quick-tables-listen.md +++ b/.changeset/quick-tables-listen.md @@ -2,4 +2,4 @@ '@backstage/plugin-stackstorm': minor --- -Add StackStorm plugin to Backstage which is interfacing with StackStorm API. It allows users to view workflow executions, packs and actions. For installation and configuration instyructions please view [README.md](https://github.com/backstage/backstage/tree/master/plugins/stackstorm/README.md) +Add StackStorm plugin to Backstage which is interfacing with StackStorm API. It allows users to view workflow executions, packs and actions. For installation and configuration instructions please view [README.md](https://github.com/backstage/backstage/tree/master/plugins/stackstorm/README.md) From 718811bafb19bf94a071bd933e8ba0b550c44ceb Mon Sep 17 00:00:00 2001 From: pamelin Date: Fri, 3 Feb 2023 14:54:54 +0000 Subject: [PATCH 05/24] fix: Use app-config.yaml from master Signed-off-by: pamelin --- app-config.yaml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index 743ca31c4b..dd72051189 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -118,11 +118,6 @@ 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 @@ -448,6 +443,3 @@ gocd: permission: enabled: true - -stackstorm: - webUrl: https://your.stackstorm.webui.com From 221ad6e88777e927beebeea4dae58aae3c4e8405 Mon Sep 17 00:00:00 2001 From: pamelin Date: Fri, 3 Feb 2023 15:22:05 +0000 Subject: [PATCH 06/24] fix: add api-report.md for stackstorm api Signed-off-by: pamelin --- plugins/stackstorm/api-report.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 plugins/stackstorm/api-report.md diff --git a/plugins/stackstorm/api-report.md b/plugins/stackstorm/api-report.md new file mode 100644 index 0000000000..2a28388766 --- /dev/null +++ b/plugins/stackstorm/api-report.md @@ -0,0 +1,26 @@ +## API Report File for "@backstage/plugin-stackstorm" + +> 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 { RouteRef } from '@backstage/core-plugin-api'; + +// Warning: (ae-missing-release-tag) "StackstormPage" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const StackstormPage: () => JSX.Element; + +// Warning: (ae-missing-release-tag) "stackstormPlugin" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const stackstormPlugin: BackstagePlugin< + { + root: RouteRef; + }, + {}, + {} +>; + +// (No @packageDocumentation comment for this package) +``` From 7f4ce79b077c5e4905faf83b6cf0416a901c7e18 Mon Sep 17 00:00:00 2001 From: pamelin Date: Fri, 3 Feb 2023 15:39:25 +0000 Subject: [PATCH 07/24] fix: add api-report.md for stackstorm api Signed-off-by: pamelin --- plugins/stackstorm/api-report.md | 12 ++++-------- plugins/stackstorm/src/index.ts | 7 +++++++ plugins/stackstorm/src/plugin.ts | 11 +++++++++++ 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/plugins/stackstorm/api-report.md b/plugins/stackstorm/api-report.md index 2a28388766..3e3d79fe27 100644 --- a/plugins/stackstorm/api-report.md +++ b/plugins/stackstorm/api-report.md @@ -3,17 +3,15 @@ > 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 { RouteRef } from '@backstage/core-plugin-api'; -// Warning: (ae-missing-release-tag) "StackstormPage" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export const StackstormPage: () => JSX.Element; -// Warning: (ae-missing-release-tag) "stackstormPlugin" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export const stackstormPlugin: BackstagePlugin< { root: RouteRef; @@ -21,6 +19,4 @@ export const stackstormPlugin: BackstagePlugin< {}, {} >; - -// (No @packageDocumentation comment for this package) ``` diff --git a/plugins/stackstorm/src/index.ts b/plugins/stackstorm/src/index.ts index aff9e2d78a..b64ae207e4 100644 --- a/plugins/stackstorm/src/index.ts +++ b/plugins/stackstorm/src/index.ts @@ -13,4 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +/** + * A Backstage plugin that integrates towards StackStorm + * + * @packageDocumentation + */ + export { stackstormPlugin, StackstormPage } from './plugin'; diff --git a/plugins/stackstorm/src/plugin.ts b/plugins/stackstorm/src/plugin.ts index 012c78372b..74df4a0b66 100644 --- a/plugins/stackstorm/src/plugin.ts +++ b/plugins/stackstorm/src/plugin.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { createApiFactory, createPlugin, @@ -24,6 +25,11 @@ import { stackStormApiRef, StackStormClient } from './api'; import { rootRouteRef } from './routes'; +/** + * The Backstage plugin that holds stackstorm specific components + * + * @public + */ export const stackstormPlugin = createPlugin({ id: 'stackstorm', apis: [ @@ -45,6 +51,11 @@ export const stackstormPlugin = createPlugin({ }, }); +/** + * A component to display a stackstorm home page + * + * @public + */ export const StackstormPage = stackstormPlugin.provide( createRoutableExtension({ name: 'StackstormPage', From 3d743aeb3491bc287772ab8e1c90b5ff65828d62 Mon Sep 17 00:00:00 2001 From: pamelin Date: Mon, 6 Feb 2023 11:48:20 +0000 Subject: [PATCH 08/24] fix: add stackstorm client tests Signed-off-by: pamelin --- app-config.yaml | 8 + plugins/stackstorm/config.d.ts | 13 -- .../src/api/StackStormClient.test.ts | 214 ++++++++++++++++++ plugins/stackstorm/src/api/types.ts | 6 +- 4 files changed, 225 insertions(+), 16 deletions(-) create mode 100644 plugins/stackstorm/src/api/StackStormClient.test.ts diff --git a/app-config.yaml b/app-config.yaml index dd72051189..c97adec5ef 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 @@ -441,5 +446,8 @@ apacheAirflow: gocd: baseUrl: https://your.gocd.instance.com +stackstorm: + webUrl: https://your.stackstorm.webui.instance.com + permission: enabled: true diff --git a/plugins/stackstorm/config.d.ts b/plugins/stackstorm/config.d.ts index cf7a2e2253..64276503fc 100644 --- a/plugins/stackstorm/config.d.ts +++ b/plugins/stackstorm/config.d.ts @@ -15,19 +15,6 @@ */ 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 diff --git a/plugins/stackstorm/src/api/StackStormClient.test.ts b/plugins/stackstorm/src/api/StackStormClient.test.ts new file mode 100644 index 0000000000..24400e3174 --- /dev/null +++ b/plugins/stackstorm/src/api/StackStormClient.test.ts @@ -0,0 +1,214 @@ +/* + * 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 { UrlPatternDiscovery } from '@backstage/core-app-api'; +import { setupRequestMockHandlers } from '@backstage/test-utils'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; +import { StackStormClient } from './StackStormClient'; +import { Action, Execution, Pack } from './types'; + +const server = setupServer(); + +const executions: Execution[] = [ + { + id: '63dcac3e18ba00e09e7bb3b6', + action: { + name: 'post_message', + ref: 'chatops.post_message', + description: 'Post a message to stream for chatops', + pack: 'chatops', + runner_type: 'announcement', + id: '62fe101b11935b6aaff4ff92', + }, + status: 'succeeded', + start_timestamp: new Date().toISOString(), + end_timestamp: new Date().toISOString(), + result: {}, + parameters: {}, + elapsed_seconds: 2.2, + log: [], + }, + { + id: '63dcac3e18ba00e09e7bb3b6', + action: { + name: 'post_result', + ref: 'chatops.post_result', + description: 'Post an execution result to stream for chatops', + pack: 'chatops', + runner_type: 'orquesta', + id: '62fe101b11935b6aaff4ff93', + }, + status: 'succeeded', + start_timestamp: new Date().toISOString(), + end_timestamp: new Date().toISOString(), + result: {}, + parameters: {}, + elapsed_seconds: 18.5, + log: [], + }, + { + id: '63dcac3c9e0b4fe98f46becc', + action: { + name: 'run', + ref: 'shell.run', + description: 'Run shell script', + pack: 'shell', + runner_type: 'shell', + id: '63736caac3d8557c4d61883a', + }, + status: 'failed', + start_timestamp: new Date().toISOString(), + end_timestamp: new Date().toISOString(), + result: {}, + parameters: {}, + elapsed_seconds: 5.0, + log: [], + }, +]; + +const executionWithDetails: Execution = { + id: '63dcac3e18ba00e09e7bb3b6', + action: { + name: 'post_message', + ref: 'chatops.post_message', + description: 'Post a message to stream for chatops', + pack: 'chatops', + runner_type: 'announcement', + id: '62fe101b11935b6aaff4ff92', + }, + status: 'succeeded', + start_timestamp: new Date().toISOString(), + end_timestamp: new Date().toISOString(), + result: { + output: { + result: 'hello', + }, + }, + parameters: { + var: 'val', + }, + elapsed_seconds: 2.2, + log: [ + { status: 'requested', timestamp: new Date().toISOString() }, + { status: 'succeeded', timestamp: new Date().toISOString() }, + ], +}; + +const packs: Pack[] = [ + { + ref: 'chatops', + description: 'ChatOps integration pack', + version: '3.7.0', + }, + { + ref: 'core', + description: 'Basic core actions.', + version: '3.7.0', + }, +]; + +const actions: Action[] = [ + { + id: '62fe101b11935b6aaff4ff96', + name: 'announcement', + ref: 'core.announcement', + pack: 'core', + description: + 'Action that broadcasts the announcement to all stream consumers.', + runner_type: 'announcement', + }, + { + id: '62fe101b11935b6aaff4ff97', + name: 'echo', + ref: 'core.echo', + pack: 'core', + description: + 'Action that executes the Linux echo command on the localhost.', + runner_type: 'local-shell-cmd', + }, +]; + +const identityApiMock = () => ({ + signOut: jest.fn(), + getProfileInfo: jest.fn(), + getBackstageIdentity: jest.fn(), + getCredentials: jest.fn().mockResolvedValue({ token: undefined }), +}); + +describe('StackStormClient', () => { + setupRequestMockHandlers(server); + + const mockBaseUrl = 'http://backstage:9191/api/proxy'; + const discoveryApi = UrlPatternDiscovery.compile(mockBaseUrl); + let client: StackStormClient; + + const setupHandlers = () => { + server.use( + rest.get(`${mockBaseUrl}/stackstorm/executions`, (req, res, ctx) => { + const limit = req.url.searchParams.get('limit') + ? Number(req.url.searchParams.get('limit')) + : executions.length; + const offset = req.url.searchParams.get('offset') + ? Number(req.url.searchParams.get('offset')) + : 0; + return res(ctx.json(executions.slice(offset, offset + limit))); + }), + rest.get( + `${mockBaseUrl}/stackstorm/executions/63dcac3e18ba00e09e7bb3b6`, + (_req, res, ctx) => { + return res(ctx.json(executionWithDetails)); + }, + ), + rest.get(`${mockBaseUrl}/stackstorm/packs`, (_req, res, ctx) => { + return res(ctx.json(packs)); + }), + rest.get(`${mockBaseUrl}/stackstorm/actions`, (req, res, ctx) => { + const name = req.url.searchParams.get('pack'); + return res(ctx.json(name === 'core' ? actions : [])); + }), + ); + }; + + beforeEach(() => { + setupHandlers(); + client = new StackStormClient({ + discoveryApi: discoveryApi, + identityApi: identityApiMock(), + }); + }); + + it('get executions should return executions with emulated pagination', async () => { + const got = await client.getExecutions(2, 1); + expect(got.length).toEqual(2); + expect(got).toMatchObject(executions.slice(1, 3)); + }); + + it('get execution should return one execution', async () => { + const got = await client.getExecution('63dcac3e18ba00e09e7bb3b6'); + expect(got).toMatchObject(executionWithDetails); + }); + + it('get packs should return list of all packs', async () => { + const got = await client.getPacks(); + expect(got.length).toEqual(packs.length); + expect(got).toMatchObject(packs); + }); + + it('get actions should return list of actions', async () => { + const got = await client.getActions('core'); + expect(got).toMatchObject(actions); + }); +}); diff --git a/plugins/stackstorm/src/api/types.ts b/plugins/stackstorm/src/api/types.ts index 8a3036d3ca..13c92fb055 100644 --- a/plugins/stackstorm/src/api/types.ts +++ b/plugins/stackstorm/src/api/types.ts @@ -23,8 +23,8 @@ export type Execution = { id: string; action: Action; status: string; - start_timestamp: Date; - end_timestamp: Date; + start_timestamp: string; + end_timestamp: string; result: object; parameters: object; elapsed_seconds: number; @@ -33,7 +33,7 @@ export type Execution = { export type ExecutionLog = { status: string; - timestamp: Date; + timestamp: string; }; export type Action = { From 4584a4ba2f1503dea680a8b446dbb449b243db0c Mon Sep 17 00:00:00 2001 From: pamelin Date: Mon, 6 Feb 2023 16:01:21 +0000 Subject: [PATCH 09/24] fix: add PacksTable tests Signed-off-by: pamelin --- .../components/PacksTable/PacksTable.test.tsx | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 plugins/stackstorm/src/components/PacksTable/PacksTable.test.tsx diff --git a/plugins/stackstorm/src/components/PacksTable/PacksTable.test.tsx b/plugins/stackstorm/src/components/PacksTable/PacksTable.test.tsx new file mode 100644 index 0000000000..e0f922d211 --- /dev/null +++ b/plugins/stackstorm/src/components/PacksTable/PacksTable.test.tsx @@ -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 { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import React from 'react'; +import { Pack, StackStormApi, stackStormApiRef } from '../../api'; +import { PacksTable } from './PacksTable'; + +const packs: Pack[] = [ + { + ref: 'chatops', + description: 'ChatOps integration pack', + version: '3.7.0', + }, + { + ref: 'core', + description: 'Basic core actions.', + version: '3.7.1', + }, +]; + +describe('PacksTable', () => { + const mockApi: jest.Mocked = { + getExecutions: jest.fn().mockResolvedValue([]), + getExecution: jest.fn().mockResolvedValue({}), + getPacks: jest.fn().mockResolvedValue(packs), + getActions: jest.fn().mockResolvedValue([]), + } as any; + + it('should render all packs', async () => { + const { getByText } = await renderInTestApp( + + + , + ); + + packs.forEach(p => { + expect(getByText(p.ref)).toBeInTheDocument(); + expect(getByText(p.description)).toBeInTheDocument(); + expect(getByText(p.version)).toBeInTheDocument(); + }); + }); +}); From 7ce480edcfd9996f4653888d9d202e0a684f78eb Mon Sep 17 00:00:00 2001 From: pamelin Date: Mon, 6 Feb 2023 16:54:32 +0000 Subject: [PATCH 10/24] fix: add ActionsList tests Signed-off-by: pamelin --- .../ActionsList/ActionsList.test.tsx | 88 +++++++++++++++++++ .../components/ActionsList/ActionsList.tsx | 2 + 2 files changed, 90 insertions(+) create mode 100644 plugins/stackstorm/src/components/ActionsList/ActionsList.test.tsx diff --git a/plugins/stackstorm/src/components/ActionsList/ActionsList.test.tsx b/plugins/stackstorm/src/components/ActionsList/ActionsList.test.tsx new file mode 100644 index 0000000000..9f57d76ef3 --- /dev/null +++ b/plugins/stackstorm/src/components/ActionsList/ActionsList.test.tsx @@ -0,0 +1,88 @@ +/* + * 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 { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import React from 'react'; +import { Action, Pack, StackStormApi, stackStormApiRef } from '../../api'; +import { ActionsList, PackListItem } from './ActionsList'; + +const packs: Pack[] = [ + { + ref: 'chatops', + description: 'ChatOps integration pack', + version: '3.7.0', + }, + { + ref: 'core', + description: 'Basic core actions.', + version: '3.7.1', + }, +]; + +const actions: Action[] = [ + { + id: '62fe101b11935b6aaff4ff96', + name: 'announcement', + ref: 'core.announcement', + pack: 'core', + description: + 'Action that broadcasts the announcement to all stream consumers.', + runner_type: 'broadcast', + }, + { + id: '62fe101b11935b6aaff4ff97', + name: 'echo', + ref: 'core.echo', + pack: 'core', + description: + 'Action that executes the Linux echo command on the localhost.', + runner_type: 'local-shell-cmd', + }, +]; + +describe('ActionsList', () => { + const mockApi: jest.Mocked = { + getExecutions: jest.fn().mockResolvedValue([]), + getExecution: jest.fn().mockResolvedValue({}), + getPacks: jest.fn().mockResolvedValue(packs), + getActions: jest.fn().mockResolvedValue(actions), + } as any; + + it('should render all packs', async () => { + const { getByText } = await renderInTestApp( + + + , + ); + + packs.forEach(p => { + expect(getByText(p.ref)).toBeInTheDocument(); + expect(getByText(p.description)).toBeInTheDocument(); + }); + }); + + it('should render all pack actions', async () => { + const { getByText } = await renderInTestApp( + + {}} /> + , + ); + + actions.forEach(a => { + expect(getByText(a.name)).toBeInTheDocument(); + expect(getByText(a.runner_type)).toBeInTheDocument(); + }); + }); +}); diff --git a/plugins/stackstorm/src/components/ActionsList/ActionsList.tsx b/plugins/stackstorm/src/components/ActionsList/ActionsList.tsx index 014e8c31a3..47e328716f 100644 --- a/plugins/stackstorm/src/components/ActionsList/ActionsList.tsx +++ b/plugins/stackstorm/src/components/ActionsList/ActionsList.tsx @@ -73,6 +73,7 @@ export const ActionItems = ({ pack }: ActionItemsProps) => { {(value || []).map(a => { return ( @@ -150,6 +151,7 @@ export const ActionsList = () => { {(value || []).map(p => { return ( Date: Tue, 7 Feb 2023 14:47:46 +0000 Subject: [PATCH 11/24] fix: add ExecutionsTable and ExecutionPanel tests Signed-off-by: pamelin --- .../stackstorm/src/components/ActionsList/ActionsList.test.tsx | 2 -- .../stackstorm/src/components/PacksTable/PacksTable.test.tsx | 3 --- 2 files changed, 5 deletions(-) diff --git a/plugins/stackstorm/src/components/ActionsList/ActionsList.test.tsx b/plugins/stackstorm/src/components/ActionsList/ActionsList.test.tsx index 9f57d76ef3..b59d2f4132 100644 --- a/plugins/stackstorm/src/components/ActionsList/ActionsList.test.tsx +++ b/plugins/stackstorm/src/components/ActionsList/ActionsList.test.tsx @@ -54,8 +54,6 @@ const actions: Action[] = [ describe('ActionsList', () => { const mockApi: jest.Mocked = { - getExecutions: jest.fn().mockResolvedValue([]), - getExecution: jest.fn().mockResolvedValue({}), getPacks: jest.fn().mockResolvedValue(packs), getActions: jest.fn().mockResolvedValue(actions), } as any; diff --git a/plugins/stackstorm/src/components/PacksTable/PacksTable.test.tsx b/plugins/stackstorm/src/components/PacksTable/PacksTable.test.tsx index e0f922d211..b45d460068 100644 --- a/plugins/stackstorm/src/components/PacksTable/PacksTable.test.tsx +++ b/plugins/stackstorm/src/components/PacksTable/PacksTable.test.tsx @@ -33,10 +33,7 @@ const packs: Pack[] = [ describe('PacksTable', () => { const mockApi: jest.Mocked = { - getExecutions: jest.fn().mockResolvedValue([]), - getExecution: jest.fn().mockResolvedValue({}), getPacks: jest.fn().mockResolvedValue(packs), - getActions: jest.fn().mockResolvedValue([]), } as any; it('should render all packs', async () => { From 93f55b5ca08adecf94afbfbaefd6af5d55817491 Mon Sep 17 00:00:00 2001 From: pamelin Date: Thu, 9 Feb 2023 15:24:02 +0000 Subject: [PATCH 12/24] fix: add missing files for ExecutionsTable and ExecutionPanel tests Signed-off-by: pamelin --- .../ExecutionsTable/ExecutionPanel.test.tsx | 79 +++++++++++++ .../ExecutionsTable/ExecutionsTable.test.tsx | 109 ++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100644 plugins/stackstorm/src/components/ExecutionsTable/ExecutionPanel.test.tsx create mode 100644 plugins/stackstorm/src/components/ExecutionsTable/ExecutionsTable.test.tsx diff --git a/plugins/stackstorm/src/components/ExecutionsTable/ExecutionPanel.test.tsx b/plugins/stackstorm/src/components/ExecutionsTable/ExecutionPanel.test.tsx new file mode 100644 index 0000000000..51adbe0371 --- /dev/null +++ b/plugins/stackstorm/src/components/ExecutionsTable/ExecutionPanel.test.tsx @@ -0,0 +1,79 @@ +/* + * 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 { configApiRef } from '@backstage/core-plugin-api'; +import { + MockConfigApi, + renderInTestApp, + TestApiProvider, +} from '@backstage/test-utils'; +import React from 'react'; +import { Execution, StackStormApi, stackStormApiRef } from '../../api'; +import { ExecutionPanel } from './ExecutionPanel'; + +const execution: Execution = { + id: '63dcac3e18ba00e09e7bb3b6', + action: { + name: 'post_message', + ref: 'chatops.post_message', + description: 'Post a message to stream for chatops', + pack: 'chatops', + runner_type: 'announcement', + id: '62fe101b11935b6aaff4ff92', + }, + status: 'succeeded', + start_timestamp: new Date().toISOString(), + end_timestamp: new Date().toISOString(), + result: { + output: { + result: 'hello world!', + }, + }, + parameters: { + var: 'my funky partameter', + }, + elapsed_seconds: 2.2, + log: [ + { status: 'requested', timestamp: new Date().toISOString() }, + { status: 'succeeded', timestamp: new Date().toISOString() }, + ], +}; + +describe('ExecutionPanel', () => { + const mockApi: jest.Mocked = { + getExecution: jest.fn().mockResolvedValue(execution), + } as any; + + it('should render execution details', async () => { + const { getByText } = await renderInTestApp( + + + , + ); + + expect(getByText(execution.id)).toBeInTheDocument(); + expect(getByText(execution.status)).toBeInTheDocument(); + expect(getByText('"hello world!"')).toBeInTheDocument(); + expect(getByText('"my funky partameter"')).toBeInTheDocument(); + }); +}); diff --git a/plugins/stackstorm/src/components/ExecutionsTable/ExecutionsTable.test.tsx b/plugins/stackstorm/src/components/ExecutionsTable/ExecutionsTable.test.tsx new file mode 100644 index 0000000000..dc2bfa4db8 --- /dev/null +++ b/plugins/stackstorm/src/components/ExecutionsTable/ExecutionsTable.test.tsx @@ -0,0 +1,109 @@ +/* + * 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 { configApiRef } from '@backstage/core-plugin-api'; +import { + MockConfigApi, + renderInTestApp, + TestApiProvider, +} from '@backstage/test-utils'; +import React from 'react'; +import { Execution, StackStormApi, stackStormApiRef } from '../../api'; +import { ExecutionsTable } from './ExecutionsTable'; + +const executions: Execution[] = [ + { + id: '63dcac3e18ba00e09e7bb3b6', + action: { + name: 'post_message', + ref: 'chatops.post_message', + description: 'Post a message to stream for chatops', + pack: 'chatops', + runner_type: 'announcement', + id: '62fe101b11935b6aaff4ff92', + }, + status: 'processing', + start_timestamp: new Date().toISOString(), + end_timestamp: new Date().toISOString(), + result: {}, + parameters: {}, + elapsed_seconds: 2.2, + log: [], + }, + { + id: '62fe101b11935b6aaff4ff93', + action: { + name: 'post_result', + ref: 'chatops.post_result', + description: 'Post an execution result to stream for chatops', + pack: 'chatops', + runner_type: 'orquesta', + id: '62fe101b11935b6aaff4ff93', + }, + status: 'succeeded', + start_timestamp: new Date().toISOString(), + end_timestamp: new Date().toISOString(), + result: {}, + parameters: {}, + elapsed_seconds: 18.5, + log: [], + }, + { + id: '63dcac3c9e0b4fe98f46becc', + action: { + name: 'run', + ref: 'shell.run', + description: 'Run shell script', + pack: 'shell', + runner_type: 'shell', + id: '63736caac3d8557c4d61883a', + }, + status: 'failed', + start_timestamp: new Date().toISOString(), + end_timestamp: new Date().toISOString(), + result: {}, + parameters: {}, + elapsed_seconds: 5.0, + log: [], + }, +]; + +describe('ExecutionsTable', () => { + const mockApi: jest.Mocked = { + getExecutions: jest.fn().mockResolvedValue(executions), + } as any; + + it('should render all executions', async () => { + const { getByText } = await renderInTestApp( + + + , + ); + + executions.forEach(e => { + expect(getByText(e.id)).toBeInTheDocument(); + expect(getByText(e.action.ref)).toBeInTheDocument(); + expect(getByText(e.status)).toBeInTheDocument(); + }); + }); +}); From de6fcb0822387da055d45e1f0ecd9c394734b4e5 Mon Sep 17 00:00:00 2001 From: pamelin Date: Thu, 16 Feb 2023 09:42:03 +0000 Subject: [PATCH 13/24] fix: add a comment to installation instructions to README.md Signed-off-by: pamelin --- plugins/stackstorm/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/stackstorm/README.md b/plugins/stackstorm/README.md index 1671e6150f..0d5caeaca2 100644 --- a/plugins/stackstorm/README.md +++ b/plugins/stackstorm/README.md @@ -16,6 +16,7 @@ One of the quickest ways is [running StackStorm with Docker](https://docs.stacks 1. Install the plugin with `yarn` in the root of your Backstage directory ```bash +# From your Backstage root directory yarn --cwd packages/app add @backstage/plugin-stackstorm ``` From b6a04cb4b37bc1723b5d6d58dde49a4089b4ecfd Mon Sep 17 00:00:00 2001 From: pamelin Date: Thu, 16 Feb 2023 09:44:49 +0000 Subject: [PATCH 14/24] fix: remove unnecessary Grid on the StackStormHome Signed-off-by: pamelin --- .../StackStormHome/StackStormHome.tsx | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/plugins/stackstorm/src/components/StackStormHome/StackStormHome.tsx b/plugins/stackstorm/src/components/StackStormHome/StackStormHome.tsx index a059505c2b..d3065ff2e5 100644 --- a/plugins/stackstorm/src/components/StackStormHome/StackStormHome.tsx +++ b/plugins/stackstorm/src/components/StackStormHome/StackStormHome.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ import React from 'react'; -import { Grid, IconButton } from '@material-ui/core'; +import { IconButton } from '@material-ui/core'; import { Header, Page, @@ -38,29 +38,17 @@ export const StackStormHome = () => ( - - - - - + - - - - - + - - - - - + From 5b85cc1595ddf6ba4dcd9e60d21f6f5569d61fc7 Mon Sep 17 00:00:00 2001 From: pamelin Date: Thu, 16 Feb 2023 10:03:18 +0000 Subject: [PATCH 15/24] fix: update case of StackStrom to Stackstorm or stackstorm where necessary Signed-off-by: pamelin --- plugins/stackstorm/src/api/StackStormClient.test.ts | 8 ++++---- plugins/stackstorm/src/api/StackStormClient.ts | 4 ++-- plugins/stackstorm/src/api/index.ts | 6 +++--- plugins/stackstorm/src/api/types.ts | 4 ++-- .../src/components/ActionsList/ActionsList.test.tsx | 8 ++++---- .../src/components/ActionsList/ActionsList.tsx | 6 +++--- .../ExecutionsTable/ExecutionPanel.test.tsx | 6 +++--- .../components/ExecutionsTable/ExecutionPanel.tsx | 4 ++-- .../ExecutionsTable/ExecutionsTable.test.tsx | 6 +++--- .../components/ExecutionsTable/ExecutionsTable.tsx | 4 ++-- .../src/components/PacksTable/PacksTable.test.tsx | 6 +++--- .../src/components/PacksTable/PacksTable.tsx | 4 ++-- .../StackStormHome/StackStormHome.test.tsx | 12 ++++++------ .../src/components/StackStormHome/StackStormHome.tsx | 2 +- .../src/components/StackStormHome/index.ts | 2 +- plugins/stackstorm/src/plugin.ts | 9 ++++----- 16 files changed, 45 insertions(+), 46 deletions(-) diff --git a/plugins/stackstorm/src/api/StackStormClient.test.ts b/plugins/stackstorm/src/api/StackStormClient.test.ts index 24400e3174..4a3e4cb79b 100644 --- a/plugins/stackstorm/src/api/StackStormClient.test.ts +++ b/plugins/stackstorm/src/api/StackStormClient.test.ts @@ -17,7 +17,7 @@ import { UrlPatternDiscovery } from '@backstage/core-app-api'; import { setupRequestMockHandlers } from '@backstage/test-utils'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; -import { StackStormClient } from './StackStormClient'; +import { StackstormClient } from './StackstormClient'; import { Action, Execution, Pack } from './types'; const server = setupServer(); @@ -148,12 +148,12 @@ const identityApiMock = () => ({ getCredentials: jest.fn().mockResolvedValue({ token: undefined }), }); -describe('StackStormClient', () => { +describe('StackstormClient', () => { setupRequestMockHandlers(server); const mockBaseUrl = 'http://backstage:9191/api/proxy'; const discoveryApi = UrlPatternDiscovery.compile(mockBaseUrl); - let client: StackStormClient; + let client: StackstormClient; const setupHandlers = () => { server.use( @@ -184,7 +184,7 @@ describe('StackStormClient', () => { beforeEach(() => { setupHandlers(); - client = new StackStormClient({ + client = new StackstormClient({ discoveryApi: discoveryApi, identityApi: identityApiMock(), }); diff --git a/plugins/stackstorm/src/api/StackStormClient.ts b/plugins/stackstorm/src/api/StackStormClient.ts index c93901ce43..5fa862dc5a 100644 --- a/plugins/stackstorm/src/api/StackStormClient.ts +++ b/plugins/stackstorm/src/api/StackStormClient.ts @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Action, Execution, Pack, StackStormApi } from './types'; +import { Action, Execution, Pack, StackstormApi } from './types'; import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api'; -export class StackStormClient implements StackStormApi { +export class StackstormClient implements StackstormApi { private readonly discoveryApi: DiscoveryApi; private readonly identityApi: IdentityApi; diff --git a/plugins/stackstorm/src/api/index.ts b/plugins/stackstorm/src/api/index.ts index 2291fb1f73..0ce652e27b 100644 --- a/plugins/stackstorm/src/api/index.ts +++ b/plugins/stackstorm/src/api/index.ts @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { stackStormApiRef } from './types'; +export { stackstormApiRef } from './types'; export type { Action, Execution, ExecutionLog, Pack, - StackStormApi, + StackstormApi, } from './types'; -export { StackStormClient } from './StackStormClient'; +export { StackstormClient } from './StackstormClient'; diff --git a/plugins/stackstorm/src/api/types.ts b/plugins/stackstorm/src/api/types.ts index 13c92fb055..401f79c9b6 100644 --- a/plugins/stackstorm/src/api/types.ts +++ b/plugins/stackstorm/src/api/types.ts @@ -15,7 +15,7 @@ */ import { createApiRef } from '@backstage/core-plugin-api'; -export const stackStormApiRef = createApiRef({ +export const stackstormApiRef = createApiRef({ id: 'plugin.stackstorm.service', }); @@ -51,7 +51,7 @@ export type Pack = { version: string; }; -export interface StackStormApi { +export interface StackstormApi { getExecutions(limit: number, offset: number): Promise; getExecution(id: string): Promise; getPacks(): Promise; diff --git a/plugins/stackstorm/src/components/ActionsList/ActionsList.test.tsx b/plugins/stackstorm/src/components/ActionsList/ActionsList.test.tsx index b59d2f4132..5876860c54 100644 --- a/plugins/stackstorm/src/components/ActionsList/ActionsList.test.tsx +++ b/plugins/stackstorm/src/components/ActionsList/ActionsList.test.tsx @@ -15,7 +15,7 @@ */ import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import React from 'react'; -import { Action, Pack, StackStormApi, stackStormApiRef } from '../../api'; +import { Action, Pack, StackstormApi, stackstormApiRef } from '../../api'; import { ActionsList, PackListItem } from './ActionsList'; const packs: Pack[] = [ @@ -53,14 +53,14 @@ const actions: Action[] = [ ]; describe('ActionsList', () => { - const mockApi: jest.Mocked = { + const mockApi: jest.Mocked = { getPacks: jest.fn().mockResolvedValue(packs), getActions: jest.fn().mockResolvedValue(actions), } as any; it('should render all packs', async () => { const { getByText } = await renderInTestApp( - + , ); @@ -73,7 +73,7 @@ describe('ActionsList', () => { it('should render all pack actions', async () => { const { getByText } = await renderInTestApp( - + {}} /> , ); diff --git a/plugins/stackstorm/src/components/ActionsList/ActionsList.tsx b/plugins/stackstorm/src/components/ActionsList/ActionsList.tsx index 47e328716f..e792a4f6d2 100644 --- a/plugins/stackstorm/src/components/ActionsList/ActionsList.tsx +++ b/plugins/stackstorm/src/components/ActionsList/ActionsList.tsx @@ -29,7 +29,7 @@ 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'; +import { Action, Pack, stackstormApiRef } from '../../api'; const useStyles = makeStyles(theme => ({ root: { @@ -54,7 +54,7 @@ type ActionItemsProps = { export const ActionItems = ({ pack }: ActionItemsProps) => { const classes = useStyles(); - const st2 = useApi(stackStormApiRef); + const st2 = useApi(stackstormApiRef); const config = useApi(configApiRef); const { value, loading, error } = useAsync(async (): Promise => { @@ -120,7 +120,7 @@ export const PackListItem = ({ pack, opened, onClick }: PackListItemProps) => { }; export const ActionsList = () => { - const st2 = useApi(stackStormApiRef); + const st2 = useApi(stackstormApiRef); const classes = useStyles(); const [expanded, setExpanded] = useState([]); diff --git a/plugins/stackstorm/src/components/ExecutionsTable/ExecutionPanel.test.tsx b/plugins/stackstorm/src/components/ExecutionsTable/ExecutionPanel.test.tsx index 51adbe0371..442151ebc1 100644 --- a/plugins/stackstorm/src/components/ExecutionsTable/ExecutionPanel.test.tsx +++ b/plugins/stackstorm/src/components/ExecutionsTable/ExecutionPanel.test.tsx @@ -20,7 +20,7 @@ import { TestApiProvider, } from '@backstage/test-utils'; import React from 'react'; -import { Execution, StackStormApi, stackStormApiRef } from '../../api'; +import { Execution, StackstormApi, stackstormApiRef } from '../../api'; import { ExecutionPanel } from './ExecutionPanel'; const execution: Execution = { @@ -52,7 +52,7 @@ const execution: Execution = { }; describe('ExecutionPanel', () => { - const mockApi: jest.Mocked = { + const mockApi: jest.Mocked = { getExecution: jest.fn().mockResolvedValue(execution), } as any; @@ -60,7 +60,7 @@ describe('ExecutionPanel', () => { const { getByText } = await renderInTestApp( { }; export const ExecutionPanel = ({ id }: { id: string }) => { - const st2 = useApi(stackStormApiRef); + const st2 = useApi(stackstormApiRef); const { value, loading, error } = useAsync(async (): Promise => { const data = await st2.getExecution(id); diff --git a/plugins/stackstorm/src/components/ExecutionsTable/ExecutionsTable.test.tsx b/plugins/stackstorm/src/components/ExecutionsTable/ExecutionsTable.test.tsx index dc2bfa4db8..b53d3a2f5f 100644 --- a/plugins/stackstorm/src/components/ExecutionsTable/ExecutionsTable.test.tsx +++ b/plugins/stackstorm/src/components/ExecutionsTable/ExecutionsTable.test.tsx @@ -20,7 +20,7 @@ import { TestApiProvider, } from '@backstage/test-utils'; import React from 'react'; -import { Execution, StackStormApi, stackStormApiRef } from '../../api'; +import { Execution, StackstormApi, stackstormApiRef } from '../../api'; import { ExecutionsTable } from './ExecutionsTable'; const executions: Execution[] = [ @@ -81,7 +81,7 @@ const executions: Execution[] = [ ]; describe('ExecutionsTable', () => { - const mockApi: jest.Mocked = { + const mockApi: jest.Mocked = { getExecutions: jest.fn().mockResolvedValue(executions), } as any; @@ -89,7 +89,7 @@ describe('ExecutionsTable', () => { const { getByText } = await renderInTestApp( { - const st2 = useApi(stackStormApiRef); + const st2 = useApi(stackstormApiRef); const errorApi = useApi(errorApiRef); const [page, setPage] = useState(0); const [rowsPerPage, setRowsPerPage] = useState(10); diff --git a/plugins/stackstorm/src/components/PacksTable/PacksTable.test.tsx b/plugins/stackstorm/src/components/PacksTable/PacksTable.test.tsx index b45d460068..9de17e2f14 100644 --- a/plugins/stackstorm/src/components/PacksTable/PacksTable.test.tsx +++ b/plugins/stackstorm/src/components/PacksTable/PacksTable.test.tsx @@ -15,7 +15,7 @@ */ import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import React from 'react'; -import { Pack, StackStormApi, stackStormApiRef } from '../../api'; +import { Pack, StackstormApi, stackstormApiRef } from '../../api'; import { PacksTable } from './PacksTable'; const packs: Pack[] = [ @@ -32,13 +32,13 @@ const packs: Pack[] = [ ]; describe('PacksTable', () => { - const mockApi: jest.Mocked = { + const mockApi: jest.Mocked = { getPacks: jest.fn().mockResolvedValue(packs), } as any; it('should render all packs', async () => { const { getByText } = await renderInTestApp( - + , ); diff --git a/plugins/stackstorm/src/components/PacksTable/PacksTable.tsx b/plugins/stackstorm/src/components/PacksTable/PacksTable.tsx index b150aeec4d..ebb5e8a14e 100644 --- a/plugins/stackstorm/src/components/PacksTable/PacksTable.tsx +++ b/plugins/stackstorm/src/components/PacksTable/PacksTable.tsx @@ -18,7 +18,7 @@ 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'; +import { Pack, stackstormApiRef } from '../../api'; type DenseTableProps = { packs: Pack[]; @@ -42,7 +42,7 @@ export const DenseTable = ({ packs }: DenseTableProps) => { }; export const PacksTable = () => { - const st2 = useApi(stackStormApiRef); + const st2 = useApi(stackstormApiRef); const { value, loading, error } = useAsync(async (): Promise => { const data = await st2.getPacks(); diff --git a/plugins/stackstorm/src/components/StackStormHome/StackStormHome.test.tsx b/plugins/stackstorm/src/components/StackStormHome/StackStormHome.test.tsx index 6249fa71e8..897cec61c1 100644 --- a/plugins/stackstorm/src/components/StackStormHome/StackStormHome.test.tsx +++ b/plugins/stackstorm/src/components/StackStormHome/StackStormHome.test.tsx @@ -14,13 +14,13 @@ * limitations under the License. */ import React from 'react'; -import { StackStormHome } from './StackStormHome'; +import { StackstormHome } from './StackstormHome'; import { screen } from '@testing-library/react'; import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; -import { StackStormApi, stackStormApiRef } from '../../api'; +import { StackstormApi, stackstormApiRef } from '../../api'; -describe('StackStormHome', () => { - const mockApi: jest.Mocked = { +describe('StackstormHome', () => { + const mockApi: jest.Mocked = { getExecutions: jest.fn().mockResolvedValue([]), getExecution: jest.fn().mockResolvedValue({}), getPacks: jest.fn().mockResolvedValue([]), @@ -29,8 +29,8 @@ describe('StackStormHome', () => { 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 index d3065ff2e5..d96d45e82d 100644 --- a/plugins/stackstorm/src/components/StackStormHome/StackStormHome.tsx +++ b/plugins/stackstorm/src/components/StackStormHome/StackStormHome.tsx @@ -27,7 +27,7 @@ import { ExecutionsTable } from '../ExecutionsTable'; import { PacksTable } from '../PacksTable/PacksTable'; import { ActionsList } from '../ActionsList'; -export const StackStormHome = () => ( +export const StackstormHome = () => (
diff --git a/plugins/stackstorm/src/components/StackStormHome/index.ts b/plugins/stackstorm/src/components/StackStormHome/index.ts index 27c6db48ab..2063d21f34 100644 --- a/plugins/stackstorm/src/components/StackStormHome/index.ts +++ b/plugins/stackstorm/src/components/StackStormHome/index.ts @@ -13,4 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { StackStormHome } from './StackStormHome'; +export { StackstormHome } from './StackstormHome'; diff --git a/plugins/stackstorm/src/plugin.ts b/plugins/stackstorm/src/plugin.ts index 74df4a0b66..6f36e4fc7f 100644 --- a/plugins/stackstorm/src/plugin.ts +++ b/plugins/stackstorm/src/plugin.ts @@ -21,8 +21,7 @@ import { discoveryApiRef, identityApiRef, } from '@backstage/core-plugin-api'; -import { stackStormApiRef, StackStormClient } from './api'; - +import { stackstormApiRef, StackstormClient } from './api'; import { rootRouteRef } from './routes'; /** @@ -34,13 +33,13 @@ export const stackstormPlugin = createPlugin({ id: 'stackstorm', apis: [ createApiFactory({ - api: stackStormApiRef, + api: stackstormApiRef, deps: { discoveryApi: discoveryApiRef, identityApi: identityApiRef, }, factory: ({ discoveryApi, identityApi }) => - new StackStormClient({ + new StackstormClient({ discoveryApi, identityApi, }), @@ -60,7 +59,7 @@ export const StackstormPage = stackstormPlugin.provide( createRoutableExtension({ name: 'StackstormPage', component: () => - import('./components/StackStormHome').then(m => m.StackStormHome), + import('./components/StackstormHome').then(m => m.StackstormHome), mountPoint: rootRouteRef, }), ); From 83e83873912619b218452ef7260aed673e4d8752 Mon Sep 17 00:00:00 2001 From: pamelin Date: Thu, 16 Feb 2023 10:25:29 +0000 Subject: [PATCH 16/24] fix: use FetchApi instead of IdentityApi Signed-off-by: pamelin --- .../src/api/StackStormClient.test.ts | 11 ++----- .../stackstorm/src/api/StackStormClient.ts | 32 +++++++++++++------ plugins/stackstorm/src/plugin.ts | 8 ++--- 3 files changed, 28 insertions(+), 23 deletions(-) diff --git a/plugins/stackstorm/src/api/StackStormClient.test.ts b/plugins/stackstorm/src/api/StackStormClient.test.ts index 4a3e4cb79b..7166c8297b 100644 --- a/plugins/stackstorm/src/api/StackStormClient.test.ts +++ b/plugins/stackstorm/src/api/StackStormClient.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { UrlPatternDiscovery } from '@backstage/core-app-api'; -import { setupRequestMockHandlers } from '@backstage/test-utils'; +import { MockFetchApi, setupRequestMockHandlers } from '@backstage/test-utils'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; import { StackstormClient } from './StackstormClient'; @@ -141,13 +141,6 @@ const actions: Action[] = [ }, ]; -const identityApiMock = () => ({ - signOut: jest.fn(), - getProfileInfo: jest.fn(), - getBackstageIdentity: jest.fn(), - getCredentials: jest.fn().mockResolvedValue({ token: undefined }), -}); - describe('StackstormClient', () => { setupRequestMockHandlers(server); @@ -186,7 +179,7 @@ describe('StackstormClient', () => { setupHandlers(); client = new StackstormClient({ discoveryApi: discoveryApi, - identityApi: identityApiMock(), + fetchApi: new MockFetchApi(), }); }); diff --git a/plugins/stackstorm/src/api/StackStormClient.ts b/plugins/stackstorm/src/api/StackStormClient.ts index 5fa862dc5a..7048ad1796 100644 --- a/plugins/stackstorm/src/api/StackStormClient.ts +++ b/plugins/stackstorm/src/api/StackStormClient.ts @@ -14,36 +14,48 @@ * limitations under the License. */ import { Action, Execution, Pack, StackstormApi } from './types'; -import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api'; +import { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api'; export class StackstormClient implements StackstormApi { private readonly discoveryApi: DiscoveryApi; - private readonly identityApi: IdentityApi; + private readonly fetchApi: FetchApi; constructor({ discoveryApi, - identityApi, + fetchApi, }: { discoveryApi: DiscoveryApi; - identityApi: IdentityApi; + fetchApi: FetchApi; }) { this.discoveryApi = discoveryApi; - this.identityApi = identityApi; + this.fetchApi = fetchApi; } 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', + const response = await this.fetchApi.fetch(`${apiUrl}${input}`, { 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(); + return (await response.json()) as T; + } + + async addProject(bazaarProject: any): Promise { + const baseUrl = await this.discoveryApi.getBaseUrl('bazaar'); + + return await this.fetchApi + .fetch(`${baseUrl}/projects`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(bazaarProject), + }) + .then(resp => resp.json()); } async getExecutions(limit?: number, offset?: number): Promise { diff --git a/plugins/stackstorm/src/plugin.ts b/plugins/stackstorm/src/plugin.ts index 6f36e4fc7f..8e903c69e8 100644 --- a/plugins/stackstorm/src/plugin.ts +++ b/plugins/stackstorm/src/plugin.ts @@ -19,7 +19,7 @@ import { createPlugin, createRoutableExtension, discoveryApiRef, - identityApiRef, + fetchApiRef, } from '@backstage/core-plugin-api'; import { stackstormApiRef, StackstormClient } from './api'; import { rootRouteRef } from './routes'; @@ -36,12 +36,12 @@ export const stackstormPlugin = createPlugin({ api: stackstormApiRef, deps: { discoveryApi: discoveryApiRef, - identityApi: identityApiRef, + fetchApi: fetchApiRef, }, - factory: ({ discoveryApi, identityApi }) => + factory: ({ discoveryApi, fetchApi }) => new StackstormClient({ discoveryApi, - identityApi, + fetchApi, }), }), ], From 9c53ca52eca9a87d658878c9ce7b07bf97f6ed79 Mon Sep 17 00:00:00 2001 From: pamelin Date: Thu, 16 Feb 2023 11:47:09 +0000 Subject: [PATCH 17/24] fix: move webUrl construction into the st2 client Signed-off-by: pamelin --- plugins/stackstorm/config.d.ts | 4 +- .../src/api/StackStormClient.test.ts | 37 ++++++++++++++----- .../stackstorm/src/api/StackStormClient.ts | 30 ++++++++++++++- plugins/stackstorm/src/api/types.ts | 2 + .../components/ActionsList/ActionsList.tsx | 10 +---- .../ExecutionsTable/ExecutionPanel.test.tsx | 22 ++++------- .../ExecutionsTable/ExecutionPanel.tsx | 12 +++--- .../ExecutionsTable/ExecutionsTable.test.tsx | 15 +++----- .../ExecutionsTable/ExecutionsTable.tsx | 12 ++---- plugins/stackstorm/src/plugin.ts | 6 ++- 10 files changed, 89 insertions(+), 61 deletions(-) diff --git a/plugins/stackstorm/config.d.ts b/plugins/stackstorm/config.d.ts index 64276503fc..b85d682eb0 100644 --- a/plugins/stackstorm/config.d.ts +++ b/plugins/stackstorm/config.d.ts @@ -14,12 +14,12 @@ * limitations under the License. */ export interface Config { - stackstorm?: { + stackstorm: { /** * StackStorm Web UI url * Used in links to StackStorm web UI * @visibility frontend */ - webUrl?: string; + webUrl: string; }; } diff --git a/plugins/stackstorm/src/api/StackStormClient.test.ts b/plugins/stackstorm/src/api/StackStormClient.test.ts index 7166c8297b..9757bde2af 100644 --- a/plugins/stackstorm/src/api/StackStormClient.test.ts +++ b/plugins/stackstorm/src/api/StackStormClient.test.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { UrlPatternDiscovery } from '@backstage/core-app-api'; +import { ConfigReader, UrlPatternDiscovery } from '@backstage/core-app-api'; import { MockFetchApi, setupRequestMockHandlers } from '@backstage/test-utils'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; @@ -177,31 +177,50 @@ describe('StackstormClient', () => { beforeEach(() => { setupHandlers(); - client = new StackstormClient({ - discoveryApi: discoveryApi, - fetchApi: new MockFetchApi(), - }); + client = StackstormClient.fromConfig( + new ConfigReader({ + stackstorm: { + webUrl: 'http://stackstorm.example.com:8080', + }, + }), + { + discoveryApi: discoveryApi, + fetchApi: new MockFetchApi(), + }, + ); }); - it('get executions should return executions with emulated pagination', async () => { + it('getExecutions should return executions with emulated pagination', async () => { const got = await client.getExecutions(2, 1); expect(got.length).toEqual(2); expect(got).toMatchObject(executions.slice(1, 3)); }); - it('get execution should return one execution', async () => { + it('getExecution should return one execution', async () => { const got = await client.getExecution('63dcac3e18ba00e09e7bb3b6'); expect(got).toMatchObject(executionWithDetails); }); - it('get packs should return list of all packs', async () => { + it('getPacks should return list of all packs', async () => { const got = await client.getPacks(); expect(got.length).toEqual(packs.length); expect(got).toMatchObject(packs); }); - it('get actions should return list of actions', async () => { + it('getActions should return list of actions', async () => { const got = await client.getActions('core'); expect(got).toMatchObject(actions); }); + + it('getExecutionHistoryUrl should return webUrl for executions', async () => { + const got = client.getExecutionHistoryUrl('123abc'); + expect(got).toEqual('http://stackstorm.example.com:8080/?#/history/123abc'); + }); + + it('getActionUrl should return webUrl for action', async () => { + const got = client.getActionUrl('core.shell'); + expect(got).toEqual( + 'http://stackstorm.example.com:8080/?#/actions/core.shell', + ); + }); }); diff --git a/plugins/stackstorm/src/api/StackStormClient.ts b/plugins/stackstorm/src/api/StackStormClient.ts index 7048ad1796..692bfbe4a6 100644 --- a/plugins/stackstorm/src/api/StackStormClient.ts +++ b/plugins/stackstorm/src/api/StackStormClient.ts @@ -14,21 +14,39 @@ * limitations under the License. */ import { Action, Execution, Pack, StackstormApi } from './types'; -import { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api'; +import { ConfigApi, DiscoveryApi, FetchApi } from '@backstage/core-plugin-api'; export class StackstormClient implements StackstormApi { private readonly discoveryApi: DiscoveryApi; private readonly fetchApi: FetchApi; + private readonly webUrl: string; - constructor({ + private constructor({ discoveryApi, fetchApi, + webUrl, }: { discoveryApi: DiscoveryApi; fetchApi: FetchApi; + webUrl: string; }) { this.discoveryApi = discoveryApi; this.fetchApi = fetchApi; + this.webUrl = webUrl; + } + + static fromConfig( + config: ConfigApi, + dependencies: { + discoveryApi: DiscoveryApi; + fetchApi: FetchApi; + }, + ): StackstormClient { + return new StackstormClient({ + discoveryApi: dependencies.discoveryApi, + fetchApi: dependencies.fetchApi, + webUrl: config.getString('stackstorm.webUrl'), + }); } private async get(input: string): Promise { @@ -87,4 +105,12 @@ export class StackstormClient implements StackstormApi { const path = `/actions?${new URLSearchParams(params)}`; return this.get(path); } + + getExecutionHistoryUrl(id: string): string { + return `${this.webUrl}/?#/history/${id}`; + } + + getActionUrl(ref: string): string { + return `${this.webUrl}/?#/actions/${ref}`; + } } diff --git a/plugins/stackstorm/src/api/types.ts b/plugins/stackstorm/src/api/types.ts index 401f79c9b6..6ebf3d67a6 100644 --- a/plugins/stackstorm/src/api/types.ts +++ b/plugins/stackstorm/src/api/types.ts @@ -56,4 +56,6 @@ export interface StackstormApi { getExecution(id: string): Promise; getPacks(): Promise; getActions(pack: string): Promise; + getExecutionHistoryUrl(id: string): string; + getActionUrl(ref: string): string; } diff --git a/plugins/stackstorm/src/components/ActionsList/ActionsList.tsx b/plugins/stackstorm/src/components/ActionsList/ActionsList.tsx index e792a4f6d2..96262d7e6b 100644 --- a/plugins/stackstorm/src/components/ActionsList/ActionsList.tsx +++ b/plugins/stackstorm/src/components/ActionsList/ActionsList.tsx @@ -16,7 +16,7 @@ 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 { useApi } from '@backstage/core-plugin-api'; import { List, ListItemText, @@ -55,7 +55,6 @@ type ActionItemsProps = { 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); @@ -76,12 +75,7 @@ export const ActionItems = ({ pack }: ActionItemsProps) => { key={a.ref} button className={classes.nested} - onClick={() => - window.open( - `${config.getString('stackstorm.webUrl')}/?#/actions/${a.ref}`, - '_blank', - ) - } + onClick={() => window.open(st2.getActionUrl(a.ref), '_blank')} > {a.runner_type} diff --git a/plugins/stackstorm/src/components/ExecutionsTable/ExecutionPanel.test.tsx b/plugins/stackstorm/src/components/ExecutionsTable/ExecutionPanel.test.tsx index 442151ebc1..53930d339f 100644 --- a/plugins/stackstorm/src/components/ExecutionsTable/ExecutionPanel.test.tsx +++ b/plugins/stackstorm/src/components/ExecutionsTable/ExecutionPanel.test.tsx @@ -13,12 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { configApiRef } from '@backstage/core-plugin-api'; -import { - MockConfigApi, - renderInTestApp, - TestApiProvider, -} from '@backstage/test-utils'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import React from 'react'; import { Execution, StackstormApi, stackstormApiRef } from '../../api'; import { ExecutionPanel } from './ExecutionPanel'; @@ -54,19 +49,16 @@ const execution: Execution = { describe('ExecutionPanel', () => { const mockApi: jest.Mocked = { getExecution: jest.fn().mockResolvedValue(execution), + getExecutionHistoryUrl: jest + .fn() + .mockResolvedValue( + 'http://stackstorm.example.com:8080/?#/history/63dcac3e18ba00e09e7bb3b6', + ), } as any; it('should render execution details', async () => { const { getByText } = await renderInTestApp( - + , ); diff --git a/plugins/stackstorm/src/components/ExecutionsTable/ExecutionPanel.tsx b/plugins/stackstorm/src/components/ExecutionsTable/ExecutionPanel.tsx index 78b0f0dd5a..47f301e910 100644 --- a/plugins/stackstorm/src/components/ExecutionsTable/ExecutionPanel.tsx +++ b/plugins/stackstorm/src/components/ExecutionsTable/ExecutionPanel.tsx @@ -15,7 +15,7 @@ */ import React from 'react'; import { CodeSnippet, Progress } from '@backstage/core-components'; -import { configApiRef, useApi } from '@backstage/core-plugin-api'; +import { useApi } from '@backstage/core-plugin-api'; import { Execution, stackstormApiRef } from '../../api'; import useAsync from 'react-use/lib/useAsync'; import { Alert } from '@material-ui/lab'; @@ -65,11 +65,9 @@ const TRow = withStyles(theme => ({ }))(TableRow); const ExecutionCard = ({ e }: { e: Execution }) => { - const config = useApi(configApiRef); + const st2 = useApi(stackstormApiRef); const classes = useStyles(); - const webUrl = `${config.getString('stackstorm.webUrl')}/?#/history/${e.id}`; - return ( @@ -139,7 +137,11 @@ const ExecutionCard = ({ e }: { e: Execution }) => { /> - diff --git a/plugins/stackstorm/src/components/ExecutionsTable/ExecutionsTable.test.tsx b/plugins/stackstorm/src/components/ExecutionsTable/ExecutionsTable.test.tsx index b53d3a2f5f..1890bfa10b 100644 --- a/plugins/stackstorm/src/components/ExecutionsTable/ExecutionsTable.test.tsx +++ b/plugins/stackstorm/src/components/ExecutionsTable/ExecutionsTable.test.tsx @@ -83,19 +83,16 @@ const executions: Execution[] = [ describe('ExecutionsTable', () => { const mockApi: jest.Mocked = { getExecutions: jest.fn().mockResolvedValue(executions), + getExecutionHistoryUrl: jest + .fn() + .mockResolvedValue( + 'http://stackstorm.example.com:8080/?#/history/123abc', + ), } as any; it('should render all executions', async () => { const { getByText } = await renderInTestApp( - + , ); diff --git a/plugins/stackstorm/src/components/ExecutionsTable/ExecutionsTable.tsx b/plugins/stackstorm/src/components/ExecutionsTable/ExecutionsTable.tsx index 289c896678..1816f1db1f 100644 --- a/plugins/stackstorm/src/components/ExecutionsTable/ExecutionsTable.tsx +++ b/plugins/stackstorm/src/components/ExecutionsTable/ExecutionsTable.tsx @@ -15,7 +15,7 @@ */ import React, { useEffect, useState } from 'react'; import { Link, Table, TableColumn } from '@backstage/core-components'; -import { useApi, configApiRef, errorApiRef } from '@backstage/core-plugin-api'; +import { useApi, errorApiRef } from '@backstage/core-plugin-api'; import { Execution, stackstormApiRef } from '../../api'; import { Status } from './Status'; import { ExecutionPanel } from './ExecutionPanel'; @@ -37,7 +37,7 @@ export const DenseTable = ({ onPageChange, onRowsPerPageChange, }: DenseTableProps) => { - const config = useApi(configApiRef); + const st2 = useApi(stackstormApiRef); const columns: TableColumn[] = [ { @@ -54,13 +54,7 @@ export const DenseTable = ({ { title: 'Execution ID', field: 'id', - render: e => ( - - {e.id} - - ), + render: e => {e.id}, }, ]; diff --git a/plugins/stackstorm/src/plugin.ts b/plugins/stackstorm/src/plugin.ts index 8e903c69e8..be4b9c065c 100644 --- a/plugins/stackstorm/src/plugin.ts +++ b/plugins/stackstorm/src/plugin.ts @@ -15,6 +15,7 @@ */ import { + configApiRef, createApiFactory, createPlugin, createRoutableExtension, @@ -35,11 +36,12 @@ export const stackstormPlugin = createPlugin({ createApiFactory({ api: stackstormApiRef, deps: { + configApi: configApiRef, discoveryApi: discoveryApiRef, fetchApi: fetchApiRef, }, - factory: ({ discoveryApi, fetchApi }) => - new StackstormClient({ + factory: ({ configApi, discoveryApi, fetchApi }) => + StackstormClient.fromConfig(configApi, { discoveryApi, fetchApi, }), From 89209ca8daf0eec65fd14fc5c6d5c3366e3d926c Mon Sep 17 00:00:00 2001 From: pamelin Date: Thu, 16 Feb 2023 16:18:05 +0000 Subject: [PATCH 18/24] fix: refactor error handling to use ResponseErrorPanel Signed-off-by: pamelin --- plugins/stackstorm/package.json | 1 + .../stackstorm/src/api/StackStormClient.ts | 20 ++-------- .../components/ActionsList/ActionsList.tsx | 7 ++-- .../ExecutionsTable/ExecutionPanel.tsx | 9 +++-- .../ExecutionsTable/ExecutionsTable.tsx | 40 +++++++++---------- .../src/components/PacksTable/PacksTable.tsx | 10 +++-- yarn.lock | 1 + 7 files changed, 39 insertions(+), 49 deletions(-) diff --git a/plugins/stackstorm/package.json b/plugins/stackstorm/package.json index 03e65d21c5..c5b266782e 100644 --- a/plugins/stackstorm/package.json +++ b/plugins/stackstorm/package.json @@ -36,6 +36,7 @@ "dependencies": { "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", + "@backstage/errors": "workspace:^", "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", diff --git a/plugins/stackstorm/src/api/StackStormClient.ts b/plugins/stackstorm/src/api/StackStormClient.ts index 692bfbe4a6..1eb81c63e9 100644 --- a/plugins/stackstorm/src/api/StackStormClient.ts +++ b/plugins/stackstorm/src/api/StackStormClient.ts @@ -15,6 +15,7 @@ */ import { Action, Execution, Pack, StackstormApi } from './types'; import { ConfigApi, DiscoveryApi, FetchApi } from '@backstage/core-plugin-api'; +import { ResponseError } from '@backstage/errors'; export class StackstormClient implements StackstormApi { private readonly discoveryApi: DiscoveryApi; @@ -57,28 +58,13 @@ export class StackstormClient implements StackstormApi { }, }); - if (!response.ok) throw new Error(`Unable to get data: ${response.status}`); + if (!response.ok) throw await ResponseError.fromResponse(response); return (await response.json()) as T; } - async addProject(bazaarProject: any): Promise { - const baseUrl = await this.discoveryApi.getBaseUrl('bazaar'); - - return await this.fetchApi - .fetch(`${baseUrl}/projects`, { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify(bazaarProject), - }) - .then(resp => resp.json()); - } - async getExecutions(limit?: number, offset?: number): Promise { const params = { - limit: limit?.toString() || '25', + limit: limit?.toString() || '10', offset: offset?.toString() || '0', include_attributes: 'id,status,start_timestamp,action.ref,action.name,rule.ref', diff --git a/plugins/stackstorm/src/components/ActionsList/ActionsList.tsx b/plugins/stackstorm/src/components/ActionsList/ActionsList.tsx index 96262d7e6b..35006baef0 100644 --- a/plugins/stackstorm/src/components/ActionsList/ActionsList.tsx +++ b/plugins/stackstorm/src/components/ActionsList/ActionsList.tsx @@ -15,7 +15,7 @@ */ import React, { useState } from 'react'; import useAsync from 'react-use/lib/useAsync'; -import { Progress } from '@backstage/core-components'; +import { Progress, ResponseErrorPanel } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; import { List, @@ -28,7 +28,6 @@ import { 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 => ({ @@ -64,7 +63,7 @@ export const ActionItems = ({ pack }: ActionItemsProps) => { if (loading) { return ; } else if (error) { - return {error.message}; + return ; } return ( @@ -133,7 +132,7 @@ export const ActionsList = () => { if (loading) { return ; } else if (error) { - return {error.message}; + return ; } return ( diff --git a/plugins/stackstorm/src/components/ExecutionsTable/ExecutionPanel.tsx b/plugins/stackstorm/src/components/ExecutionsTable/ExecutionPanel.tsx index 47f301e910..8a55c31246 100644 --- a/plugins/stackstorm/src/components/ExecutionsTable/ExecutionPanel.tsx +++ b/plugins/stackstorm/src/components/ExecutionsTable/ExecutionPanel.tsx @@ -14,11 +14,14 @@ * limitations under the License. */ import React from 'react'; -import { CodeSnippet, Progress } from '@backstage/core-components'; +import { + CodeSnippet, + Progress, + ResponseErrorPanel, +} from '@backstage/core-components'; import { 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, @@ -160,7 +163,7 @@ export const ExecutionPanel = ({ id }: { id: string }) => { if (loading) { return ; } else if (error) { - return {error.message}; + return ; } return ; diff --git a/plugins/stackstorm/src/components/ExecutionsTable/ExecutionsTable.tsx b/plugins/stackstorm/src/components/ExecutionsTable/ExecutionsTable.tsx index 1816f1db1f..f034cb64e8 100644 --- a/plugins/stackstorm/src/components/ExecutionsTable/ExecutionsTable.tsx +++ b/plugins/stackstorm/src/components/ExecutionsTable/ExecutionsTable.tsx @@ -13,12 +13,18 @@ * 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, errorApiRef } from '@backstage/core-plugin-api'; +import React, { useState } from 'react'; +import { + Link, + ResponseErrorPanel, + Table, + TableColumn, +} from '@backstage/core-components'; +import { useApi } from '@backstage/core-plugin-api'; import { Execution, stackstormApiRef } from '../../api'; import { Status } from './Status'; import { ExecutionPanel } from './ExecutionPanel'; +import useAsync from 'react-use/lib/useAsync'; type DenseTableProps = { executions: Execution[]; @@ -89,34 +95,24 @@ export const DenseTable = ({ 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]); + const { value, loading, error } = useAsync(async (): Promise => { + const data = await st2.getExecutions(rowsPerPage, page * rowsPerPage); + return data; + }, [page, rowsPerPage, st2]); + + if (error) { + return ; + } return ( diff --git a/plugins/stackstorm/src/components/PacksTable/PacksTable.tsx b/plugins/stackstorm/src/components/PacksTable/PacksTable.tsx index ebb5e8a14e..2167200e8e 100644 --- a/plugins/stackstorm/src/components/PacksTable/PacksTable.tsx +++ b/plugins/stackstorm/src/components/PacksTable/PacksTable.tsx @@ -14,8 +14,12 @@ * limitations under the License. */ import React from 'react'; -import { Progress, Table, TableColumn } from '@backstage/core-components'; -import { Alert } from '@material-ui/lab'; +import { + Progress, + ResponseErrorPanel, + Table, + TableColumn, +} from '@backstage/core-components'; import useAsync from 'react-use/lib/useAsync'; import { useApi } from '@backstage/core-plugin-api'; import { Pack, stackstormApiRef } from '../../api'; @@ -52,7 +56,7 @@ export const PacksTable = () => { if (loading) { return ; } else if (error) { - return {error.message}; + return ; } return ; diff --git a/yarn.lock b/yarn.lock index 5c8cdcad98..71eb638d93 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7964,6 +7964,7 @@ __metadata: "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" + "@backstage/errors": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/theme": "workspace:^" "@material-ui/core": ^4.12.2 From 52b453aadbd7832f8f66d07d9adb296d99967b2d Mon Sep 17 00:00:00 2001 From: pamelin Date: Thu, 16 Feb 2023 16:59:00 +0000 Subject: [PATCH 19/24] fix: use core Link component for action links Signed-off-by: pamelin --- .../src/components/ActionsList/ActionsList.test.tsx | 5 +++++ .../src/components/ActionsList/ActionsList.tsx | 9 ++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/plugins/stackstorm/src/components/ActionsList/ActionsList.test.tsx b/plugins/stackstorm/src/components/ActionsList/ActionsList.test.tsx index 5876860c54..2a65db427a 100644 --- a/plugins/stackstorm/src/components/ActionsList/ActionsList.test.tsx +++ b/plugins/stackstorm/src/components/ActionsList/ActionsList.test.tsx @@ -56,6 +56,11 @@ describe('ActionsList', () => { const mockApi: jest.Mocked = { getPacks: jest.fn().mockResolvedValue(packs), getActions: jest.fn().mockResolvedValue(actions), + getActionUrl: jest + .fn() + .mockResolvedValue( + 'http://stackstorm.example.com:8080/?#/actions/core.action', + ), } as any; it('should render all packs', async () => { diff --git a/plugins/stackstorm/src/components/ActionsList/ActionsList.tsx b/plugins/stackstorm/src/components/ActionsList/ActionsList.tsx index 35006baef0..1080f67373 100644 --- a/plugins/stackstorm/src/components/ActionsList/ActionsList.tsx +++ b/plugins/stackstorm/src/components/ActionsList/ActionsList.tsx @@ -15,7 +15,7 @@ */ import React, { useState } from 'react'; import useAsync from 'react-use/lib/useAsync'; -import { Progress, ResponseErrorPanel } from '@backstage/core-components'; +import { Link, Progress, ResponseErrorPanel } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; import { List, @@ -72,9 +72,12 @@ export const ActionItems = ({ pack }: ActionItemsProps) => { return ( window.open(st2.getActionUrl(a.ref), '_blank')} + underline="none" + color="inherit" + button > {a.runner_type} From 689a04a446132802fdaf8b3e50fe5de17c76e175 Mon Sep 17 00:00:00 2001 From: pamelin Date: Thu, 16 Feb 2023 17:05:20 +0000 Subject: [PATCH 20/24] fix: use encodeURIComponent in StackstormClient Signed-off-by: pamelin --- plugins/stackstorm/src/api/StackStormClient.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/stackstorm/src/api/StackStormClient.ts b/plugins/stackstorm/src/api/StackStormClient.ts index 1eb81c63e9..495384e8a5 100644 --- a/plugins/stackstorm/src/api/StackStormClient.ts +++ b/plugins/stackstorm/src/api/StackStormClient.ts @@ -75,7 +75,7 @@ export class StackstormClient implements StackstormApi { } async getExecution(id: string): Promise { - const path = `/executions/${id}`; + const path = `/executions/${encodeURIComponent(id)}`; return this.get(path); } @@ -93,10 +93,10 @@ export class StackstormClient implements StackstormApi { } getExecutionHistoryUrl(id: string): string { - return `${this.webUrl}/?#/history/${id}`; + return `${this.webUrl}/?#/history/${encodeURIComponent(id)}`; } getActionUrl(ref: string): string { - return `${this.webUrl}/?#/actions/${ref}`; + return `${this.webUrl}/?#/actions/${encodeURIComponent(ref)}`; } } From a1aacf3d27b0d3717e0c685f646892005aeac888 Mon Sep 17 00:00:00 2001 From: pamelin Date: Thu, 16 Feb 2023 17:25:10 +0000 Subject: [PATCH 21/24] fix: rename files Signed-off-by: pamelin --- .../api/{StackStormClient.test.ts => StackstormClient.test.ts} | 0 .../src/api/{StackStormClient.ts => StackstormClient.ts} | 0 .../StackstormHome.test.tsx} | 0 .../StackStormHome.tsx => StackstormHome/StackstormHome.tsx} | 0 .../src/components/{StackStormHome => StackstormHome}/index.ts | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename plugins/stackstorm/src/api/{StackStormClient.test.ts => StackstormClient.test.ts} (100%) rename plugins/stackstorm/src/api/{StackStormClient.ts => StackstormClient.ts} (100%) rename plugins/stackstorm/src/components/{StackStormHome/StackStormHome.test.tsx => StackstormHome/StackstormHome.test.tsx} (100%) rename plugins/stackstorm/src/components/{StackStormHome/StackStormHome.tsx => StackstormHome/StackstormHome.tsx} (100%) rename plugins/stackstorm/src/components/{StackStormHome => StackstormHome}/index.ts (100%) diff --git a/plugins/stackstorm/src/api/StackStormClient.test.ts b/plugins/stackstorm/src/api/StackstormClient.test.ts similarity index 100% rename from plugins/stackstorm/src/api/StackStormClient.test.ts rename to plugins/stackstorm/src/api/StackstormClient.test.ts diff --git a/plugins/stackstorm/src/api/StackStormClient.ts b/plugins/stackstorm/src/api/StackstormClient.ts similarity index 100% rename from plugins/stackstorm/src/api/StackStormClient.ts rename to plugins/stackstorm/src/api/StackstormClient.ts diff --git a/plugins/stackstorm/src/components/StackStormHome/StackStormHome.test.tsx b/plugins/stackstorm/src/components/StackstormHome/StackstormHome.test.tsx similarity index 100% rename from plugins/stackstorm/src/components/StackStormHome/StackStormHome.test.tsx rename to plugins/stackstorm/src/components/StackstormHome/StackstormHome.test.tsx diff --git a/plugins/stackstorm/src/components/StackStormHome/StackStormHome.tsx b/plugins/stackstorm/src/components/StackstormHome/StackstormHome.tsx similarity index 100% rename from plugins/stackstorm/src/components/StackStormHome/StackStormHome.tsx rename to plugins/stackstorm/src/components/StackstormHome/StackstormHome.tsx diff --git a/plugins/stackstorm/src/components/StackStormHome/index.ts b/plugins/stackstorm/src/components/StackstormHome/index.ts similarity index 100% rename from plugins/stackstorm/src/components/StackStormHome/index.ts rename to plugins/stackstorm/src/components/StackstormHome/index.ts From 25394d153f46639ec4e03ba98d681666bef185da Mon Sep 17 00:00:00 2001 From: pamelin Date: Thu, 16 Feb 2023 18:09:32 +0000 Subject: [PATCH 22/24] fix: remove unused imports Signed-off-by: pamelin --- .../components/ExecutionsTable/ExecutionsTable.test.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/plugins/stackstorm/src/components/ExecutionsTable/ExecutionsTable.test.tsx b/plugins/stackstorm/src/components/ExecutionsTable/ExecutionsTable.test.tsx index 1890bfa10b..6727f3cc56 100644 --- a/plugins/stackstorm/src/components/ExecutionsTable/ExecutionsTable.test.tsx +++ b/plugins/stackstorm/src/components/ExecutionsTable/ExecutionsTable.test.tsx @@ -13,12 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { configApiRef } from '@backstage/core-plugin-api'; -import { - MockConfigApi, - renderInTestApp, - TestApiProvider, -} from '@backstage/test-utils'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import React from 'react'; import { Execution, StackstormApi, stackstormApiRef } from '../../api'; import { ExecutionsTable } from './ExecutionsTable'; From 76c4fcd44785110dce4ca8ea59b6e245393c303d Mon Sep 17 00:00:00 2001 From: pamelin Date: Thu, 16 Feb 2023 18:18:43 +0000 Subject: [PATCH 23/24] fix: add st2 icon Signed-off-by: pamelin --- microsite/data/plugins/stackstorm.yaml | 1 + microsite/static/img/stackstorm.png | Bin 0 -> 3936 bytes 2 files changed, 1 insertion(+) create mode 100644 microsite/static/img/stackstorm.png diff --git a/microsite/data/plugins/stackstorm.yaml b/microsite/data/plugins/stackstorm.yaml index 282117d1fe..2524ca538f 100644 --- a/microsite/data/plugins/stackstorm.yaml +++ b/microsite/data/plugins/stackstorm.yaml @@ -5,6 +5,7 @@ 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 +iconUrl: img/stackstorm.png npmPackageName: '@backstage/plugin-stackstorm' tags: - stackstorm diff --git a/microsite/static/img/stackstorm.png b/microsite/static/img/stackstorm.png new file mode 100644 index 0000000000000000000000000000000000000000..f017906e073f53022e412210f4c1852eea1f4197 GIT binary patch literal 3936 zcmaJ^c|4SB*q#_=-&$m!##WXYhQW+|43WmZ?-Rp}ZKg38l4NX+N~EH!WeGL1WXYDK zvc^%NGPDp!!ekeHqjNgn_s2Qk`}@7`^E~%+U)O!z%Rlc0TkGQjyb`rGm zAAqz+oBth)eKG*!DHIYC3XO`2f<$RTh~z-11_FVAs>7f#m>SzdEj&7mf~Bg3h0FbB zK;y#w$OICFKnw%zGGb2=BPa%7w$i^+2qpbP8y5a|nb-w`Qn4hc21I=~rQePi%>VBi z8v2iSIK>|Kzk2_bINULsgoE1S!if=NKX&5+;u5{V(dt7wZSQi$Pr zA_-(_`m1Un4Azejwrl%^!C;VudZT_EFC|eA4x4HkP*}qNf3EJKMGrH`< zKf{j;V^2JpJ=&@{m017)-=HPh#F6^;y$7G86YkK?Kob4Z@gj_HjB)4?_S<%Y4ZuGOawuFgZzo}TCKI)e3Oc*Y4FgQ~)S`Az4j2yrQe z4N~5*#Ik8E`Gzz@3a7QuddYib{C=e;i;pYmBUsvaa|58)$LB(6sp9hNaLSJ{_+Tq& zFg@({osN+U1N)n?DpWB>%_)-5K}7JB-<%c5LP1G@Av4H-IeBRaE{th7>bls5(B>!~r(CXMhfDiYFJ4UO5Vp`P_Aq{Ml*3o$hormqG^&OQ0{VzIzFGx*f1r{U zzr$=AoXD9SR#@m-tMg}c4d~Az`F|*ZEYy~3INoMy zsFzN?kX3dy=`d716M=wuRhXT;C}n6rB0@83c~jPZE1#xzPAE}WqNRvNOZln50>@WRGPk*K)-oq<1u4_vMzRWlC^OGyFbh?2%UwLGbu_*+p%6`5N zGSjTM6eE$73g9SxiJGjt5&I~1b9S$P)j$!y97tKYrZ)P+^CN@Hc8WhwqkP5sb$EG( z2Jr?~zQ zLLDw?r*$>9uw)FsXN7!zAwt!Q60o<=L_l?=E?a-%iaVJ8aBuy%81}L5;)Xxt&|u_|uJcjw-7_3yX@19H{3xec#;@u<;VJK3j5yyM&V<6U;o} zs)O60boWt3&+QD$TIx5Ec78qyeHX=}cz{o6z*^PH$lBo}y z5ZT%hyw-V0%PKvC~}q}bOK))8T)a-#UuisvPBTK83@ z(-7ZlCB0M0LwI~TxvtVX`*7A2A!ul+hmmr41a$8S1PN?-c=&BcIsJ>0{q@xJ^yh8c zTXtR!w8_FkZB9_kpL0!~^LC2eiGA{QpDH5oJ zG1gd2=2x2s+VtDKxd?)5Tda6rfaXh4rhFauCEm<;M#jSUd|vrnsV(|ZdHW9xMClAH zfFk-Oskj)O3-b2OD=R2H@j9(G>}BSkp^xh8nqttp+%EQ0W|C4VmTf6qrqVf+E3veW zc=7V#3qtqS1193~VJk6bgDpm#p#6E7Q;fTR@WiZNt5)rJgc-QaA#w7c*3$liFKw)4 z(o$2IEuZ|WU8#6s?npanP2M=d{StUL{@pZmaQ&OymE22o$l62{w8o|N@{1ST-cDq} z!S~2ZK4|iW61OTP$y;l`;Ej(g`k6`O&-=G~uN>j=MA>Ak=hGhRp{_@0_AxSpQacuX z_J3aCF2%3Z9s>LJ=@&qPC%l!SlIBxJZrxv~0-?^Iy9J6W6xW$i#3W#+S@jQ}QskzM zFFN;bBhP7D4hVBx}#0O|&S-)jqu4b*wR{i$+d=w%=$wD|5c zVJyRH_}zVez|T9?LVbKXnRhMrc$j3}+?EA88&M&RSR+=(kDsrJpVuj{0q;wX5$#iF zIc+*IkNK1$k*Vl`+?7FvQWKWrl~<}?=3){Cej5AM_;4TkS^?yUS4K(C<-)dDy?KqU zUYAZE>!w>_-kx_jvdSAjm%=H9IX8E-yA{~H{U`SrisKWYesaC*%dH3TsE^CDn)H!D zg>8w_GG_8&g1q-)E$YPc$V%6^+`VphuD}KM@uzkQm6W(H`YT3$dtcJWXLo_y`v(y( zx*z0~sZi##$RXL(DImiElQG&0*453fx);(t(G$3@SCk1YO$r0PW_~pLm}GB#*&t7B zo#D*08q6`si}LjaDRqrEw?FA-T|KN9phZD0$ob{DX(;nEK5=>KZtQ`M9=ry6$LQJH z7a6`V_8nf*!ymi0z1C9%57F*RV-?Z`GNL<%O%a3r0p+}gQ3vW66+Q_tAq&RnVwZ{7TCz~r7OIrPOKDyziWg7N`4{t|t za#?)}e?BHiV7obj_ibx=@=}8P{pl9c=NY?VhYHoB>*hnpHA%>%r$&XB7u2j~BwG&m zR=7J6;dKqxv{4P9!Vdk?B{R*Eo-aOBk%iWRvfRl2ZQp@W{_#YAixo0I^QgCP@V=WO z<%tQU^HP%n+@&&O0VUFUkso5GqKoB~hWVG;rqH+4X($Wke)OZwK8ETrcnwDLrX5|HeB1v59R_ef=Sfv&!_#TWQNm)8D~Xvv0H+ z=R~b53`V)unE|W%x1G(PYEPM`i&dVX^k%}8yY|&BtVtP?msO=PGXZ|-kLv2`7M0Ko z7BXprSA+`;8MX2Sp(p!+##+e^u8DTmLRYsuq-&r3 Date: Fri, 17 Feb 2023 09:40:25 +0000 Subject: [PATCH 24/24] fix: update stackstorm config Signed-off-by: pamelin --- plugins/stackstorm/config.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stackstorm/config.d.ts b/plugins/stackstorm/config.d.ts index b85d682eb0..bf7b56823a 100644 --- a/plugins/stackstorm/config.d.ts +++ b/plugins/stackstorm/config.d.ts @@ -14,7 +14,7 @@ * limitations under the License. */ export interface Config { - stackstorm: { + stackstorm?: { /** * StackStorm Web UI url * Used in links to StackStorm web UI