Refactor error panel

Signed-off-by: Juan Lulkin <jmaiz@spotify.com>
This commit is contained in:
Juan Lulkin
2021-05-11 14:39:30 +02:00
committed by Fredrik Adelöw
parent 467fe2200c
commit 6ce1caa854
5 changed files with 178 additions and 139 deletions
@@ -14,11 +14,9 @@
* limitations under the License.
*/
// TODO align this design with the backend errors
import React from 'react';
import { AppComponents } from '../..';
import { ResponseErrorPanel } from '../ResponseErrorPanel';
import { ErrorPanel } from '../ErrorPanel';
import { Button } from '@material-ui/core';
export const ErrorBoundaryFallback: AppComponents['ErrorBoundaryFallback'] = ({
@@ -27,15 +25,14 @@ export const ErrorBoundaryFallback: AppComponents['ErrorBoundaryFallback'] = ({
plugin,
}) => {
return (
<ResponseErrorPanel
<ErrorPanel
title={`Error in ${plugin?.getId()}`}
defaultExpanded
error={error}
actions={
<Button variant="outlined" onClick={resetError}>
Retry
</Button>
}
/>
>
<Button variant="outlined" onClick={resetError}>
Retry
</Button>
</ErrorPanel>
);
};
@@ -0,0 +1,114 @@
/*
* Copyright 2021 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { List, ListItem, ListItemText, makeStyles } from '@material-ui/core';
import React, { PropsWithChildren } from 'react';
import { CopyTextButton } from '../CopyTextButton';
import { WarningPanel } from '../WarningPanel';
const useStyles = makeStyles(theme => ({
text: {
fontFamily: 'monospace',
whiteSpace: 'pre',
overflowX: 'auto',
marginRight: theme.spacing(2),
},
divider: {
margin: theme.spacing(2),
},
}));
type ErrorListProps = {
error: string;
message: string;
request?: string;
stack?: string;
json?: string;
};
const ErrorList = ({
error,
message,
stack,
children,
}: PropsWithChildren<ErrorListProps>) => {
const classes = useStyles();
return (
<List dense>
<ListItem alignItems="flex-start">
<ListItemText
classes={{ secondary: classes.text }}
primary="Error"
secondary={error}
/>
<CopyTextButton text={error} />
</ListItem>
<ListItem alignItems="flex-start">
<ListItemText
classes={{ secondary: classes.text }}
primary="Message"
secondary={message}
/>
<CopyTextButton text={message} />
</ListItem>
{stack && (
<ListItem alignItems="flex-start">
<ListItemText
classes={{ secondary: classes.text }}
primary="Stack Trace"
secondary={stack}
/>
<CopyTextButton text={stack} />
</ListItem>
)}
{children}
</List>
);
};
export type ErrorPanelProps = {
error: Error;
defaultExpanded?: boolean;
title?: string;
};
/**
* Renders a warning panel as the effect of an error.
*/
export const ErrorPanel = ({
title,
error,
defaultExpanded,
children,
}: PropsWithChildren<ErrorPanelProps>) => {
return (
<WarningPanel
title={title ?? error.message}
defaultExpanded={defaultExpanded}
>
<ErrorList
error={error.name}
message={error.message}
stack={error.stack}
children={children}
/>
</WarningPanel>
);
};
@@ -0,0 +1,18 @@
/*
* Copyright 2021 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { ErrorPanel } from './ErrorPanel';
export type { ErrorPanelProps } from './ErrorPanel';
@@ -15,17 +15,11 @@
*/
import { ResponseError } from '@backstage/errors';
import {
Divider,
List,
ListItem,
ListItemText,
makeStyles,
} from '@material-ui/core';
import { Divider, ListItem, ListItemText, makeStyles } from '@material-ui/core';
import React from 'react';
import { CodeSnippet } from '../CodeSnippet';
import { CopyTextButton } from '../CopyTextButton';
import { WarningPanel } from '../WarningPanel';
import { ErrorPanel, ErrorPanelProps } from '../ErrorPanel';
const useStyles = makeStyles(theme => ({
text: {
@@ -39,107 +33,25 @@ const useStyles = makeStyles(theme => ({
},
}));
type ResponseErrorListProps = {
error: string;
message: string;
request?: string;
stack?: string;
json?: string;
actions?: React.ReactNode;
};
const ResponseErrorList = ({
error,
request,
message,
stack,
json,
actions,
}: ResponseErrorListProps) => {
const classes = useStyles();
return (
<List dense>
<ListItem alignItems="flex-start">
<ListItemText
classes={{ secondary: classes.text }}
primary="Error"
secondary={error}
/>
<CopyTextButton text={error} />
</ListItem>
<ListItem alignItems="flex-start">
<ListItemText
classes={{ secondary: classes.text }}
primary="Message"
secondary={message}
/>
<CopyTextButton text={message} />
</ListItem>
{request && (
<ListItem alignItems="flex-start">
<ListItemText
classes={{ secondary: classes.text }}
primary="Request"
secondary={request}
/>
<CopyTextButton text={request} />
</ListItem>
)}
{stack && (
<ListItem alignItems="flex-start">
<ListItemText
classes={{ secondary: classes.text }}
primary="Stack Trace"
secondary={stack}
/>
<CopyTextButton text={stack} />
</ListItem>
)}
{json && (
<>
<Divider component="li" className={classes.divider} />
<ListItem alignItems="flex-start">
<ListItemText
classes={{ secondary: classes.text }}
primary="Full Error as JSON"
secondary={<CodeSnippet language="json" text={json} />}
/>
<CopyTextButton text={json} />
</ListItem>
</>
)}
{actions && (
<>
<Divider component="li" className={classes.divider} />
<ListItem alignItems="flex-start">{actions}</ListItem>
</>
)}
</List>
);
};
type Props = {
error: Error;
defaultExpanded?: boolean;
title?: string;
actions?: React.ReactNode;
};
/**
* Renders details about a failed server request.
* Renders a warning panel as the effect of a failed server request.
*
* Has special treatment for ResponseError errors, to display rich
* server-provided information about what happened.
*/
export const ResponseErrorDetails = ({ error, actions }: Props) => {
export const ResponseErrorPanel = ({
title,
error,
defaultExpanded,
}: ErrorPanelProps) => {
const classes = useStyles();
if (error.name !== 'ResponseError') {
return (
<ResponseErrorList
error={error.name}
message={error.message}
stack={error.stack}
actions={actions}
<ErrorPanel
title={title ?? error.message}
defaultExpanded={defaultExpanded}
error={error}
/>
);
}
@@ -154,34 +66,32 @@ export const ResponseErrorDetails = ({ error, actions }: Props) => {
const jsonString = JSON.stringify(data, undefined, 2);
return (
<ResponseErrorList
error={errorString}
message={messageString}
request={requestString}
stack={stackString}
json={jsonString}
/>
);
};
/**
* Renders a warning panel as the effect of a failed server request.
*
* Has special treatment for ResponseError errors, to display rich
* server-provided information about what happened.
*/
export const ResponseErrorPanel = ({
title,
error,
defaultExpanded,
actions,
}: Props) => {
return (
<WarningPanel
<ErrorPanel
title={title ?? error.message}
defaultExpanded={defaultExpanded}
error={{ name: errorString, message: messageString, stack: stackString }}
>
<ResponseErrorDetails error={error} actions={actions} />
</WarningPanel>
{requestString && (
<ListItem alignItems="flex-start">
<ListItemText
classes={{ secondary: classes.text }}
primary="Request"
secondary={request}
/>
<CopyTextButton text={requestString} />
</ListItem>
)}
<>
<Divider component="li" className={classes.divider} />
<ListItem alignItems="flex-start">
<ListItemText
classes={{ secondary: classes.text }}
primary="Full Error as JSON"
secondary={<CodeSnippet language="json" text={jsonString} />}
/>
<CopyTextButton text={jsonString} />
</ListItem>
</>
</ErrorPanel>
);
};
@@ -14,4 +14,4 @@
* limitations under the License.
*/
export { ResponseErrorDetails, ResponseErrorPanel } from './ResponseErrorPanel';
export { ResponseErrorPanel } from './ResponseErrorPanel';