feat(frontend-plugin-api): improve extension boundary

Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
Camila Belo
2023-10-12 22:08:03 +02:00
parent 0eaa698bb5
commit ed71a018b1
4 changed files with 106 additions and 3 deletions
+3 -1
View File
@@ -24,6 +24,7 @@
},
"devDependencies": {
"@backstage/cli": "workspace:^",
"@backstage/frontend-app-api": "workspace:^",
"@backstage/test-utils": "workspace:^",
"@testing-library/jest-dom": "^6.0.0",
"@testing-library/react": "^14.0.0",
@@ -37,10 +38,11 @@
"react-router-dom": "6.0.0-beta.0 || ^6.3.0"
},
"dependencies": {
"@backstage/core-components": "workspace:^",
"@backstage/core-plugin-api": "workspace:^",
"@backstage/frontend-app-api": "workspace:^",
"@backstage/types": "workspace:^",
"@backstage/version-bridge": "workspace:^",
"@material-ui/core": "^4.12.4",
"@types/react": "^16.13.1 || ^17.0.0",
"lodash": "^4.17.21",
"zod": "^3.21.4",
@@ -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 React, { Component, PropsWithChildren } from 'react';
import { Button } from '@material-ui/core';
import { BackstagePlugin } from '../wiring';
import { ErrorPanel } from '@backstage/core-components';
type DefaultErrorBoundaryFallbackProps = PropsWithChildren<{
plugin?: BackstagePlugin;
error: Error;
resetError: () => void;
}>;
const DefaultErrorBoundaryFallback = ({
plugin,
error,
resetError,
}: DefaultErrorBoundaryFallbackProps) => {
const title = `Error in ${plugin?.id}`;
return (
<ErrorPanel title={title} error={error} defaultExpanded>
<Button variant="outlined" onClick={resetError}>
Retry
</Button>
</ErrorPanel>
);
};
type ErrorBoundaryProps = PropsWithChildren<{ plugin?: BackstagePlugin }>;
type ErrorBoundaryState = { error?: Error };
/** @internal */
export class ErrorBoundary extends Component<
ErrorBoundaryProps,
ErrorBoundaryState
> {
static getDerivedStateFromError(error: Error) {
return { error };
}
state: ErrorBoundaryState = { error: undefined };
handleErrorReset = () => {
this.setState({ error: undefined });
};
render() {
const { error } = this.state;
const { plugin, children } = this.props;
if (error) {
// TODO: use a configurable error boundary fallback
return (
<DefaultErrorBoundaryFallback
plugin={plugin}
error={error}
resetError={this.handleErrorReset}
/>
);
}
return children;
}
}
@@ -15,15 +15,35 @@
*/
import React, { ReactNode } from 'react';
import { AnalyticsContext } from '@backstage/core-plugin-api';
import { BackstagePlugin } from '../wiring';
import { RouteRef } from '../routing';
import { ErrorBoundary } from './ErrorBoundary';
import { toInternalRouteRef } from '../routing/RouteRef';
/** @public */
export interface ExtensionBoundaryProps {
children: ReactNode;
id: string;
source?: BackstagePlugin;
routeRef?: RouteRef;
children: ReactNode;
}
/** @public */
export function ExtensionBoundary(props: ExtensionBoundaryProps) {
return <>{props.children}</>;
const { id, source, routeRef, children } = props;
const attributes = {
extension: id,
pluginId: source?.id,
routeRef: routeRef
? toInternalRouteRef(routeRef).getDescription()
: undefined,
};
return (
<ErrorBoundary plugin={source}>
<AnalyticsContext attributes={attributes}>{children}</AnalyticsContext>
</ErrorBoundary>
);
}