Move error api to more generic alert api (#605)

This commit is contained in:
Wesley Yee
2020-04-22 14:14:56 -04:00
committed by GitHub
parent ab86d08e16
commit efb789a4fe
11 changed files with 170 additions and 48 deletions
+3 -3
View File
@@ -25,9 +25,9 @@ import { createApp } from '@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 AlertDisplay from './components/AlertDisplay';
import * as plugins from './plugins';
import apis, { errorDialogForwarder } from './apis';
import apis, { alertApiForwarder } from './apis';
import { ThemeContextType, ThemeContext, useThemeType } from './ThemeContext';
const useStyles = makeStyles(theme => ({
@@ -86,7 +86,7 @@ const App: FC<{}> = () => {
<ThemeContext.Provider value={themeContext}>
<ThemeProvider theme={backstageTheme as Theme}>
<CssBaseline>
<ErrorDisplay forwarder={errorDialogForwarder} />
<AlertDisplay forwarder={alertApiForwarder} />
<Router>
<Root>
<AppComponent />
+7 -4
View File
@@ -17,7 +17,10 @@
import {
ApiHolder,
ApiRegistry,
alertApiRef,
errorApiRef,
AlertApiForwarder,
ErrorApiForwarder,
featureFlagsApiRef,
FeatureFlags,
} from '@backstage/core';
@@ -26,13 +29,13 @@ import {
LighthouseRestApi,
} from '@backstage/plugin-lighthouse';
import { ErrorDisplayForwarder } from './components/ErrorDisplay/ErrorDisplay';
const builder = ApiRegistry.builder();
export const errorDialogForwarder = new ErrorDisplayForwarder();
export const alertApiForwarder = new AlertApiForwarder();
builder.add(alertApiRef, alertApiForwarder);
builder.add(errorApiRef, errorDialogForwarder);
export const errorApiForwarder = new ErrorApiForwarder(alertApiForwarder);
builder.add(errorApiRef, errorApiForwarder);
builder.add(featureFlagsApiRef, new FeatureFlags());
@@ -19,59 +19,36 @@ import PropTypes from 'prop-types';
import { Snackbar, IconButton } from '@material-ui/core';
import CloseIcon from '@material-ui/icons/Close';
import { Alert } from '@material-ui/lab';
import { ErrorApi, ErrorContext } from '@backstage/core';
type SubscriberFunc = (error: Error) => void;
type Unsubscribe = () => void;
// TODO: figure out where to put implementations of APIs, both inside apps
// but also in core/separate package.
export class ErrorDisplayForwarder implements ErrorApi {
private readonly subscribers = new Set<SubscriberFunc>();
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);
};
}
}
import { AlertApiForwarder, AlertMessage } from '@backstage/core';
type Props = {
forwarder: ErrorDisplayForwarder;
forwarder: AlertApiForwarder;
};
// TODO: improve on this and promote to a shared component for use by all apps.
const ErrorDisplay: FC<Props> = ({ forwarder }) => {
const [errors, setErrors] = useState<Array<Error>>([]);
const AlertDisplay: FC<Props> = ({ forwarder }) => {
const [messages, setMessages] = useState<Array<AlertMessage>>([]);
useEffect(() => {
return forwarder.subscribe(error => setErrors(errs => errs.concat(error)));
return forwarder.subscribe((message: AlertMessage) =>
setMessages(msgs => msgs.concat(message)),
);
}, [forwarder]);
if (errors.length === 0) {
if (messages.length === 0) {
return null;
}
const [firstError] = errors;
const [firstMessage] = messages;
const handleClose = () => {
setErrors(errs => errs.filter(err => err !== firstError));
setMessages(msgs => msgs.filter(msg => msg !== firstMessage));
};
return (
<Snackbar
open
message={firstError.toString()}
message={firstMessage.message.toString()}
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
>
<Alert
@@ -85,16 +62,16 @@ const ErrorDisplay: FC<Props> = ({ forwarder }) => {
<CloseIcon />
</IconButton>
}
severity="error"
severity={firstMessage.severity}
>
{firstError.toString()}
{firstMessage.message.toString()}
</Alert>
</Snackbar>
);
};
ErrorDisplay.propTypes = {
forwarder: PropTypes.instanceOf(ErrorDisplayForwarder).isRequired,
AlertDisplay.propTypes = {
forwarder: PropTypes.instanceOf(AlertApiForwarder).isRequired,
};
export default ErrorDisplay;
export default AlertDisplay;
@@ -14,4 +14,4 @@
* limitations under the License.
*/
export { default } from './ErrorDisplay';
export { default } from './AlertDisplay';
@@ -0,0 +1,38 @@
/*
* 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 ApiRef from '../ApiRef';
export type AlertMessage = {
message: string;
// Severity will default to success since that is what material ui defaults the value to.
severity?: 'success' | 'info' | 'warning' | 'error';
};
/**
* The alert API is used to report alerts to the app, and display them to the user.
*/
export type AlertApi = {
/**
* Post an alert for handling by the application.
*/
post(alert: AlertMessage);
};
export const alertApiRef = new ApiRef<AlertApi>({
id: 'core.alert',
description: 'Used to report alerts and forward them to the app',
});
@@ -20,5 +20,6 @@
//
// If you think some API definition is missing, please open an Issue or send a PR!
export * from './alert';
export * from './error';
export * from './featureFlags';
@@ -0,0 +1,35 @@
/*
* 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 { AlertApi, AlertMessage } from '../../../';
type SubscriberFunc = (message: AlertMessage) => void;
type Unsubscribe = () => void;
export class AlertApiForwarder implements AlertApi {
private readonly subscribers = new Set<SubscriberFunc>();
post(alert: AlertMessage) {
this.subscribers.forEach(subscriber => subscriber(alert));
}
subscribe(func: SubscriberFunc): Unsubscribe {
this.subscribers.add(func);
return () => {
this.subscribers.delete(func);
};
}
}
@@ -0,0 +1,45 @@
/*
* 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 { ErrorApi, ErrorContext, AlertApi } from '../../../';
type SubscriberFunc = (error: Error) => void;
type Unsubscribe = () => void;
export class ErrorApiForwarder implements ErrorApi {
private readonly subscribers = new Set<SubscriberFunc>();
private alertApi: AlertApi;
constructor(alertApi: AlertApi) {
this.alertApi = alertApi;
}
post(error: Error, context?: ErrorContext) {
if (context?.hidden) {
return;
}
this.alertApi.post({ message: error.message, severity: 'error' });
this.subscribers.forEach(subscriber => subscriber(error));
}
subscribe(func: SubscriberFunc): Unsubscribe {
this.subscribers.add(func);
return () => {
this.subscribers.delete(func);
};
}
}
@@ -0,0 +1,22 @@
/*
* 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.
*/
// This folder contains implementations for all core APIs.
//
// Plugins should rely on these APIs for functionality as much as possible.
export * from './AlertApiForwarder';
export * from './ErrorApiForwarder';
+1
View File
@@ -20,3 +20,4 @@ export { default as ApiTestRegistry } from './ApiTestRegistry';
export { default as ApiRef } from './ApiRef';
export * from './types';
export * from './definitions';
export * from './implementations';
+1 -1
View File
@@ -17382,7 +17382,7 @@ request@^2.85.0, request@^2.87.0, request@^2.88.0:
tunnel-agent "^0.6.0"
uuid "^3.3.2"
request@cypress-io/request#b5af0d1fa47eec97ba980cde90a13e69a2afcd16:
"request@github:cypress-io/request#b5af0d1fa47eec97ba980cde90a13e69a2afcd16":
version "2.88.1"
resolved "https://codeload.github.com/cypress-io/request/tar.gz/b5af0d1fa47eec97ba980cde90a13e69a2afcd16"
dependencies: