Files
grafana/public/app/features/plugins/components/PluginErrorBoundary.tsx
Levente Balogh 77f84e494d Plugin Extensions: Add error boundaries (#107515)
* feat(grafana-data): expose PluginContext

This is aimed to be used in the `PluginErrorBoundary` (which is a class component, and cannot use the hook.)

* feat(PluginErrorBoundary): add an error boundary for plugins

* feat(ExtensionsErrorBoundary): add an error boundary for extensions)

* feat(Extensions/Utils): wrap components with error boundaries

* feat(Plugins): wrap root plugin page with an error boundary

* fix: Fallback component should always be visible for onClick() modals

* review: use object arguments instead of positional ones for `renderWithPluginContext()`

* review: update `wrapWithPluginContext()` to receive args as an object

* refactor(AppChromeExtensionPoint): remove the error boundary

We have an error boundary on the extensions-framework level now

* refactor(ExtensionSidebar): remove the ErrorBoundary from the extensions

This is handled on the extensions-framework level now.

* test(ExtensionSidebar): add tests

* chore: translation extraction

* chore: prettier formatting

* fix(PluginErrorBoundary): remove unnecessary type casting
2025-07-07 14:51:04 +02:00

50 lines
1.4 KiB
TypeScript

import * as React from 'react';
import { PluginContext } from '@grafana/data';
interface PluginErrorBoundaryProps {
children: React.ReactNode;
fallback?: React.ComponentType<{ error: Error | null; errorInfo: React.ErrorInfo | null }>;
onError?: (error: Error, info: React.ErrorInfo) => void;
}
interface PluginErrorBoundaryState {
hasError: boolean;
error: Error | null;
errorInfo: React.ErrorInfo | null;
}
export class PluginErrorBoundary extends React.Component<PluginErrorBoundaryProps, PluginErrorBoundaryState> {
static contextType = PluginContext;
declare context: React.ContextType<typeof PluginContext>;
constructor(props: PluginErrorBoundaryProps) {
super(props);
this.state = { hasError: false, error: null, errorInfo: null };
}
static getDerivedStateFromError(error: Error): PluginErrorBoundaryState {
return { hasError: true, error: error, errorInfo: null };
}
componentDidCatch(error: Error, info: React.ErrorInfo) {
if (this.props.onError) {
this.props.onError(error, info);
} else {
console.error(`Plugin "${this.context?.meta.id}" failed to load:`, error, info);
}
this.setState({ error, errorInfo: info });
}
render() {
const Fallback = this.props.fallback;
if (this.state.hasError) {
return Fallback ? <Fallback error={this.state.error} errorInfo={this.state.errorInfo} /> : null;
}
return this.props.children;
}
}