diff --git a/libs/shared/lib/components/errorBoundary/ErrorBoundary.tsx b/libs/shared/lib/components/errorBoundary/ErrorBoundary.tsx new file mode 100644 index 0000000000000000000000000000000000000000..14908635c37f4ed4522542e57c74b22eff82992b --- /dev/null +++ b/libs/shared/lib/components/errorBoundary/ErrorBoundary.tsx @@ -0,0 +1,48 @@ +import React, { Component, ErrorInfo, ReactNode } from 'react'; +import { Button } from '../buttons'; + +interface ErrorBoundaryProps { + children: ReactNode; + fallback?: ReactNode; + onError?: (error: Error, errorInfo: ErrorInfo) => void; +} + +interface ErrorBoundaryState { + hasError: boolean; +} + +class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> { + constructor(props: ErrorBoundaryProps) { + super(props); + this.state = { hasError: false }; + } + + static getDerivedStateFromError(): ErrorBoundaryState { + return { hasError: true }; + } + + componentDidCatch(error: Error, errorInfo: ErrorInfo) { + if (this.props.onError) { + this.props.onError(error, errorInfo); + } + } + + handleRetry() { + this.setState({ hasError: false }); + } + + render() { + if (this.state.hasError) { + return ( + <div> + {this.props.fallback || <div>Something went wrong. Please try again later.</div>} + <Button label="Retry now" variant="outline" variantType="primary" onClick={this.handleRetry} /> + </div> + ); + } + + return this.props.children; + } +} + +export { ErrorBoundary }; diff --git a/libs/shared/lib/components/errorBoundary/index.ts b/libs/shared/lib/components/errorBoundary/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..43a371efcb52aa53fa73cf07a189a098fa4c1143 --- /dev/null +++ b/libs/shared/lib/components/errorBoundary/index.ts @@ -0,0 +1 @@ +export * from './ErrorBoundary'; diff --git a/libs/shared/lib/vis/components/VisualizationPanel.tsx b/libs/shared/lib/vis/components/VisualizationPanel.tsx index 1b6eba6ea99846b060ee2fb974b2360c45fa1008..b39ab2f80b3f0f0e7fb2a4da996ba180263c9672 100644 --- a/libs/shared/lib/vis/components/VisualizationPanel.tsx +++ b/libs/shared/lib/vis/components/VisualizationPanel.tsx @@ -15,6 +15,8 @@ import { Recommender, NoData, Querying } from '../views'; import { resultSetFocus, resultSetSelection, unSelect } from '../../data-access/store/interactionSlice'; import { updateVisualization, addVisualization } from '../../data-access/store/visualizationSlice'; import { VisualizationPropTypes, VISComponentType } from '../common'; +import { ErrorBoundary } from '../../components/errorBoundary'; +import { addError } from '../../data-access/store/configSlice'; type PromiseFunc = () => Promise<{ default: VISComponentType<any> }>; export const Visualizations: Record<string, PromiseFunc> = { @@ -83,6 +85,7 @@ export const VisualizationPanel = ({ fullSize }: { fullSize: () => void }) => { } }; const exportImage = viz?.exportImage || (() => {}); + return ( <div className="relative pt-7 vis-panel h-full w-full flex flex-col border bg-light" @@ -98,23 +101,31 @@ export const VisualizationPanel = ({ fullSize }: { fullSize: () => void }) => { ) : ( <div className="w-full h-full flex"> <Suspense fallback={<div>Loading...</div>}> - {!!viz && - activeVisualizationIndex !== -1 && - openVisualizationArray?.[activeVisualizationIndex] && - viz.id === openVisualizationArray[activeVisualizationIndex].id && - graphMetadata && ( - <viz.component - data={graphQueryResult} - schema={schema} - ml={ml} - settings={openVisualizationArray[activeVisualizationIndex]} - dispatch={dispatch} - handleSelect={handleSelect} - graphMetadata={graphMetadata} - updateSettings={updateSettings} - handleHover={() => {}} - /> - )} + <ErrorBoundary + fallback={<div>Something went wrong</div>} + onError={() => { + dispatch(addError('Something went wrong while trying to load the visualization')); + setViz(undefined); + }} + > + {!!viz && + activeVisualizationIndex !== -1 && + openVisualizationArray?.[activeVisualizationIndex] && + viz.id === openVisualizationArray[activeVisualizationIndex].id && + graphMetadata && ( + <viz.component + data={graphQueryResult} + schema={schema} + ml={ml} + settings={openVisualizationArray[activeVisualizationIndex]} + dispatch={dispatch} + handleSelect={handleSelect} + graphMetadata={graphMetadata} + updateSettings={updateSettings} + handleHover={() => {}} + /> + )} + </ErrorBoundary> </Suspense> </div> )}