From bdaa6031d3f7f5c8ec0dd93252529b8dc3e3660b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 19 Mar 2020 18:16:47 +0100 Subject: [PATCH] app: more elaborate error display --- packages/app/package.json | 1 + packages/app/src/App.tsx | 4 +- packages/app/src/apis.ts | 10 +-- .../components/ErrorDisplay/ErrorDisplay.tsx | 84 +++++++++++++++++++ .../app/src/components/ErrorDisplay/index.ts | 17 ++++ 5 files changed, 107 insertions(+), 9 deletions(-) create mode 100644 packages/app/src/components/ErrorDisplay/ErrorDisplay.tsx create mode 100644 packages/app/src/components/ErrorDisplay/index.ts diff --git a/packages/app/package.json b/packages/app/package.json index 90ff7fa0b9..4f581599a5 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -5,6 +5,7 @@ "dependencies": { "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", + "@material-ui/lab": "4.0.0-alpha.45", "@spotify-backstage/cli": "^0.1.0", "@spotify-backstage/core": "^0.1.0", "@spotify-backstage/plugin-home-page": "^0.1.0", diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 9bb5eec4e3..91b588041c 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -19,8 +19,9 @@ import { BackstageTheme, createApp } from '@spotify-backstage/core'; import React, { FC } from 'react'; import { BrowserRouter as Router } from 'react-router-dom'; import Root from './components/Root'; +import ErrorDisplay from './components/ErrorDisplay'; import * as plugins from './plugins'; -import apis from './apis'; +import apis, { errorDialogForwarder } from './apis'; const useStyles = makeStyles(theme => ({ '@global': { @@ -50,6 +51,7 @@ const App: FC<{}> = () => { return ( + diff --git a/packages/app/src/apis.ts b/packages/app/src/apis.ts index cf8a94d9fe..6e2de63795 100644 --- a/packages/app/src/apis.ts +++ b/packages/app/src/apis.ts @@ -15,17 +15,11 @@ */ import { ApiHolder, ApiRegistry, errorApiRef } from '@spotify-backstage/core'; +import { ErrorDisplayForwarder } from './components/ErrorDisplay/ErrorDisplay'; const builder = ApiRegistry.builder(); -class ErrorDialogForwarder { - post(error: Error) { - // eslint-disable-next-line no-console - console.error(`Received error, ${error}`); - } -} - -export const errorDialogForwarder = new ErrorDialogForwarder(); +export const errorDialogForwarder = new ErrorDisplayForwarder(); builder.add(errorApiRef, errorDialogForwarder); diff --git a/packages/app/src/components/ErrorDisplay/ErrorDisplay.tsx b/packages/app/src/components/ErrorDisplay/ErrorDisplay.tsx new file mode 100644 index 0000000000..0a67191b41 --- /dev/null +++ b/packages/app/src/components/ErrorDisplay/ErrorDisplay.tsx @@ -0,0 +1,84 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { FC, useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; +import { Snackbar } from '@material-ui/core'; +import { Alert } from '@material-ui/lab'; +import { ErrorApi, ErrorContext } from '@spotify-backstage/core'; + +type SubscriberFunc = (error: Error) => void; +type Unsubscribe = () => void; + +export class ErrorDisplayForwarder implements ErrorApi { + private readonly subscribers = new Set(); + + post(error: Error, context?: ErrorContext) { + if (context?.hidden) { + return; + } + + this.subscribers.forEach(subscriber => subscriber(error)); + } + + subscribe(func: SubscriberFunc): Unsubscribe { + this.subscribers.add(func); + + return () => { + this.subscribers.delete(func); + }; + } +} + +type Props = { + forwarder: ErrorDisplayForwarder; +}; + +const ErrorDisplay: FC = ({ forwarder }) => { + const [errors, setErrors] = useState>([]); + + useEffect(() => { + return forwarder.subscribe(error => setErrors(errs => errs.concat(error))); + }, [forwarder]); + + if (errors.length === 0) { + return null; + } + + const [firstError] = errors; + + const handleClose = () => { + setErrors(errs => errs.filter(err => err !== firstError)); + }; + + return ( + + + {firstError.toString()} + + + ); +}; + +ErrorDisplay.propTypes = { + forwarder: PropTypes.instanceOf(ErrorDisplayForwarder).isRequired, +}; + +export default ErrorDisplay; diff --git a/packages/app/src/components/ErrorDisplay/index.ts b/packages/app/src/components/ErrorDisplay/index.ts new file mode 100644 index 0000000000..05e414061b --- /dev/null +++ b/packages/app/src/components/ErrorDisplay/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { default } from './ErrorDisplay';