Skip to content

Commit

Permalink
Merge pull request #1897 from nextstrain/james/panel-error-boundaries
Browse files Browse the repository at this point in the history
Error boundaries for panels
  • Loading branch information
jameshadfield authored Nov 13, 2024
2 parents eafae7f + d101449 commit 8886bdd
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 14 deletions.
80 changes: 80 additions & 0 deletions src/components/errorBoundaries/panelErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React from "react";
import styled from "styled-components";

const Background = styled.div`
background: repeating-linear-gradient(
45deg,
transparent,
transparent 20px,
#f4d2ff 20px,
#f4d2ff 21px
);
margin: 10px 10px;
padding: 10px 10px;
border: 1px solid #f4d2ff;
display: inline-block;
position: relative;
`;

interface Props {
width: number;
height: number;
name: string;
}
interface State {
hasError: boolean;
errorMessage: string;
}

class PanelErrorBoundary extends React.Component<Props, State> {
constructor(props) {
super(props);
this.state = { hasError: false, errorMessage: ''};
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return {
hasError: true,
errorMessage: error instanceof Error ? error.message : "Unknown error (thrown value was not an instance of Error)",
};
}
override componentDidCatch(error, info) {
// You can also log the error to an error reporting service
console.error(error);
console.error(info);
}

override render() {
if (!this.state.hasError) return this.props.children;

/**
* We could add something like
* <button onClick={() => {this.setState({hasError: false})}}>You can try clicking here to re-render the panel</button>
* but it risks rendering an incorrect state so I'd prefer not to
*/

return (
<Background style={{width: this.props.width, height: this.props.height}}>
<h1 style={{fontSize: '3rem'}}>{`Error! Something's gone wrong within the ${this.props.name} panel`}</h1>

<p style={{fontSize: '1.8rem'}}>
{`Error message: "${this.state.errorMessage}"`}
</p>

<p style={{fontSize: '1.8rem'}}>
{'Please consider making a bug report either on '}
<a href="https://github.com/nextstrain/auspice/issues/new" target="_blank" rel="noreferrer noopener">GitHub</a>
{' or via '}
<a href="mailto:[email protected]" target="_blank" rel="noreferrer noopener">email</a>.
(The more information you can include the better - things such as steps to reproduce the bug, your browser version, the version of Auspice etc are incredibly helpful.)
</p>
<p style={{fontSize: '1.8rem'}}>
In the meantime you could try refreshing the page which may fix things in the short term.
</p>

</Background>
);
}
}

export default PanelErrorBoundary;
51 changes: 37 additions & 14 deletions src/components/main/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import ErrorBoundary from "../../util/errorBoundary";
import Spinner, { PanelSpinner } from "../framework/spinner";
import MainDisplayMarkdown from "../narrative/MainDisplayMarkdown";
import MobileNarrativeDisplay from "../narrative/MobileNarrativeDisplay";
import PanelErrorBoundary from "../errorBoundaries/panelErrorBoundary";

const Entropy = lazy(() => import("../entropy"));
const Frequencies = lazy(() => import("../frequencies"));
Expand Down Expand Up @@ -155,11 +156,17 @@ class Main extends React.Component {
}
{this.props.displayNarrative || this.props.showOnlyPanels ? null : <Info width={calcUsableWidth(availableWidth, 1)} />}
{this.props.panelsToDisplay.includes("tree") ?
<Tree
<PanelErrorBoundary
width={this.inGrid() ? grid.width : full.width}
height={this.inGrid() ? grid.height : full.height}
key={keyName}
/> :
name="tree"
>
<Tree
width={this.inGrid() ? grid.width : full.width}
height={this.inGrid() ? grid.height : full.height}
key={keyName}
/>
</PanelErrorBoundary> :
null
}
{this.props.panelsToDisplay.includes("measurements") ?
Expand All @@ -172,34 +179,50 @@ class Main extends React.Component {
/>
}
>
<Measurements
<PanelErrorBoundary
width={this.inGrid() ? grid.width : full.width}
height={this.inGrid() ? grid.height : full.height}
key={keyName+"_measurements"}
showLegend={this.shouldShowMeasurementsLegend()}
/>
name="measurements"
>
<Measurements
width={this.inGrid() ? grid.width : full.width}
height={this.inGrid() ? grid.height : full.height}
key={keyName+"_measurements"}
showLegend={this.shouldShowMeasurementsLegend()}
/>
</PanelErrorBoundary>
</Suspense> :
null
}
{this.props.panelsToDisplay.includes("map") ?
<Map
<PanelErrorBoundary
width={this.shouldMapBeInGrid() ? grid.width : full.width}
height={this.shouldMapBeInGrid() ? grid.height : full.height}
key={keyName+"_map"}
justGotNewDatasetRenderNewMap={false}
legend={this.shouldShowMapLegend()}
/> :
name="map"
>
<Map
width={this.shouldMapBeInGrid() ? grid.width : full.width}
height={this.shouldMapBeInGrid() ? grid.height : full.height}
key={keyName+"_map"}
justGotNewDatasetRenderNewMap={false}
legend={this.shouldShowMapLegend()}
/>
</PanelErrorBoundary> :
null
}
{this.props.panelsToDisplay.includes("entropy") ?
(<Suspense fallback={null}>
<Entropy width={chartEntropy.width} height={chartEntropy.height} key={keyName+"_entropy"}/>
<PanelErrorBoundary width={chartEntropy.width} height={chartEntropy.height} name="entropy">
<Entropy width={chartEntropy.width} height={chartEntropy.height} key={keyName+"_entropy"}/>
</PanelErrorBoundary>
</Suspense>) :
null
}
{this.props.panelsToDisplay.includes("frequencies") && this.props.frequenciesLoaded ?
(<Suspense fallback={null}>
<Frequencies width={chartFrequencies.width} height={chartFrequencies.height} key={keyName+"_frequencies"}/>
<PanelErrorBoundary width={chartFrequencies.width} height={chartFrequencies.height} name="frequencies">
<Frequencies width={chartFrequencies.width} height={chartFrequencies.height} key={keyName+"_frequencies"}/>
</PanelErrorBoundary>
</Suspense>) :
null
}
Expand Down

0 comments on commit 8886bdd

Please sign in to comment.