diff --git a/.changeset/rude-pumpkins-hang.md b/.changeset/rude-pumpkins-hang.md new file mode 100644 index 0000000000..9a95b0e455 --- /dev/null +++ b/.changeset/rude-pumpkins-hang.md @@ -0,0 +1,32 @@ +--- +'@backstage/core-components': patch +--- + +Make `ErrorBoundary` display more helpful information about the error that +occurred. + +The `slackChannel` (optional) prop can now be passed as an object on the form +`{ name: string; href?: string; }` in addition to the old string form. If you +are using the error boundary like + +```tsx + + + +``` + +you may like to migrate it to + +```tsx +const support = { + name: '#support', + href: 'https://slack.com/channels/your-channel', +}; + + + + +``` + +Also deprecated the prop `slackChannel` on `TabbedCard` and `InfoCard`, while +adding the prop `errorBoundaryProps` to replace it. diff --git a/packages/core-components/src/components/WarningPanel/WarningPanel.tsx b/packages/core-components/src/components/WarningPanel/WarningPanel.tsx index c90874fba0..37ae5b2c62 100644 --- a/packages/core-components/src/components/WarningPanel/WarningPanel.tsx +++ b/packages/core-components/src/components/WarningPanel/WarningPanel.tsx @@ -107,6 +107,7 @@ export const WarningPanel = (props: Props) => { } diff --git a/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.test.tsx b/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.test.tsx index 1dbeeca8c6..f8adb81a2a 100644 --- a/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.test.tsx +++ b/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.test.tsx @@ -17,7 +17,13 @@ import React from 'react'; import { ErrorBoundary } from './ErrorBoundary'; -import { renderInTestApp, withLogCollector } from '@backstage/test-utils'; +import { + MockErrorApi, + renderInTestApp, + withLogCollector, +} from '@backstage/test-utils'; +import { ApiProvider, ApiRegistry } from '@backstage/core-app-api'; +import { errorApiRef } from '@backstage/core-plugin-api'; type BombProps = { shouldThrow?: boolean; @@ -35,28 +41,33 @@ const Bomb = ({ shouldThrow }: BombProps) => { describe('', () => { it('should render error boundary with and without error', async () => { const { error } = await withLogCollector(['error'], async () => { + const apis = ApiRegistry.with(errorApiRef, new MockErrorApi()); const { rerender, queryByRole, getByRole, getByText, } = await renderInTestApp( - - - , + + + + + , ); expect(queryByRole('alert')).not.toBeInTheDocument(); expect(getByText(/working component/i)).toBeInTheDocument(); rerender( - - - , + + + + + , ); expect(getByRole('alert')).toBeInTheDocument(); - expect(getByText(/something went wrong here/i)).toBeInTheDocument(); + expect(getByText(/something went wrong/i)).toBeInTheDocument(); }); expect(error).toEqual([ diff --git a/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.tsx b/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.tsx index 4134808311..b8dfd7ff3a 100644 --- a/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.tsx +++ b/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.tsx @@ -15,9 +15,16 @@ */ import React, { ComponentClass, Component, ErrorInfo } from 'react'; +import { Button } from '../../components/Button'; +import { ErrorPanel } from '../../components/ErrorPanel'; -type Props = { - slackChannel?: string; +type SlackChannel = { + name: string; + href?: string; +}; + +export type ErrorBoundaryProps = { + slackChannel?: string | SlackChannel; onError?: (error: Error, errorInfo: string) => null; }; @@ -26,28 +33,30 @@ type State = { errorInfo?: ErrorInfo; }; -type EProps = { - error?: Error; - slackChannel?: string; - children?: React.ReactNode; -}; +const SlackLink = (props: { slackChannel?: string | SlackChannel }) => { + const { slackChannel } = props; + + if (!slackChannel) { + return null; + } else if (typeof slackChannel === 'string') { + return <>Please contact {slackChannel} for help.; + } else if (!slackChannel.href) { + return <>Please contact {slackChannel.name} for help.; + } -const Error = ({ slackChannel }: EProps) => { return ( -
- Something went wrong here.{' '} - {slackChannel && <>Please contact {slackChannel} for help.} -
+ ); }; export const ErrorBoundary: ComponentClass< - Props, + ErrorBoundaryProps, State -> = class ErrorBoundary extends Component { - constructor(props: Props) { +> = class ErrorBoundary extends Component { + constructor(props: ErrorBoundaryProps) { super(props); - this.state = { error: undefined, errorInfo: undefined, @@ -61,13 +70,17 @@ export const ErrorBoundary: ComponentClass< } render() { - const { slackChannel } = this.props; - const { error, errorInfo } = this.state; + const { slackChannel, children } = this.props; + const { error } = this.state; - if (!errorInfo) { - return this.props.children; + if (!error) { + return children; } - return ; + return ( + + + + ); } }; diff --git a/packages/core-components/src/layout/ErrorBoundary/index.ts b/packages/core-components/src/layout/ErrorBoundary/index.ts index 47c1b2bace..a0640bd5ef 100644 --- a/packages/core-components/src/layout/ErrorBoundary/index.ts +++ b/packages/core-components/src/layout/ErrorBoundary/index.ts @@ -14,4 +14,5 @@ * limitations under the License. */ +export type { ErrorBoundaryProps } from './ErrorBoundary'; export { ErrorBoundary } from './ErrorBoundary'; diff --git a/packages/core-components/src/layout/InfoCard/InfoCard.tsx b/packages/core-components/src/layout/InfoCard/InfoCard.tsx index 9a64bfa6f3..763038388b 100644 --- a/packages/core-components/src/layout/InfoCard/InfoCard.tsx +++ b/packages/core-components/src/layout/InfoCard/InfoCard.tsx @@ -26,7 +26,7 @@ import { makeStyles, } from '@material-ui/core'; import classNames from 'classnames'; -import { ErrorBoundary } from '../ErrorBoundary'; +import { ErrorBoundary, ErrorBoundaryProps } from '../ErrorBoundary'; import { BottomLink, BottomLinkProps } from '../BottomLink'; const useStyles = makeStyles(theme => ({ @@ -95,8 +95,8 @@ export type InfoCardVariants = 'flex' | 'fullHeight' | 'gridItem'; * You can custom style an InfoCard with the 'style' (outer container) and 'cardStyle' (inner container) * styles. * - * The InfoCard serves as an error boundary. As a result, if you provide a 'slackChannel' property this - * specifies the channel to display in the error component that is displayed if an error occurs + * The InfoCard serves as an error boundary. As a result, if you provide an 'errorBoundaryProps' property this + * specifies the extra information to display in the error component that is displayed if an error occurs * in any descendent components. * * By default the InfoCard has no custom layout of its children, but is treated as a block element. A @@ -112,7 +112,9 @@ type Props = { subheader?: ReactNode; divider?: boolean; deepLink?: BottomLinkProps; + /** @deprecated Use errorBoundaryProps instead */ slackChannel?: string; + errorBoundaryProps?: ErrorBoundaryProps; variant?: InfoCardVariants; style?: object; cardStyle?: object; @@ -133,7 +135,8 @@ export const InfoCard = ({ subheader, divider = true, deepLink, - slackChannel = '#backstage', + slackChannel, + errorBoundaryProps, variant, children, headerStyle, @@ -169,9 +172,12 @@ export const InfoCard = ({ }); } + const errProps: ErrorBoundaryProps = + errorBoundaryProps || (slackChannel ? { slackChannel } : {}); + return ( - + {title && ( ({ root: { @@ -52,7 +52,9 @@ const BoldHeader = withStyles(theme => ({ }))(CardHeader); type Props = { + /** @deprecated Use errorBoundaryProps instead */ slackChannel?: string; + errorBoundaryProps?: ErrorBoundaryProps; children?: ReactElement[]; onChange?: (event: React.ChangeEvent<{}>, value: number | string) => void; title?: string; @@ -61,7 +63,8 @@ type Props = { }; const TabbedCard = ({ - slackChannel = '#backstage', + slackChannel, + errorBoundaryProps, children, title, deepLink, @@ -87,9 +90,12 @@ const TabbedCard = ({ }); } + const errProps: ErrorBoundaryProps = + errorBoundaryProps || (slackChannel ? { slackChannel } : {}); + return ( - + {title && }