Skip to content
This repository has been archived by the owner on Dec 8, 2022. It is now read-only.

Commit

Permalink
Merge pull request #368 from Bayer-Group/error-boundary
Browse files Browse the repository at this point in the history
Added ErrorBoundary to Map and context wrapped components
  • Loading branch information
gabe647 authored Dec 7, 2021
2 parents af1f005 + 17172d5 commit 7e967d3
Show file tree
Hide file tree
Showing 17 changed files with 290 additions and 84 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@bayer/ol-kit",
"version": "1.18.1",
"version": "1.19.0",
"license": "BSD",
"description": "Mapping components & utils built with openlayers + react",
"keywords": [
Expand Down
18 changes: 9 additions & 9 deletions src/Draw/__snapshots__/DrawContainer.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ exports[`<DrawContainer /> should render a basic prebuilt DrawContainer componen
class=\\"sc-gtssRu fBppYk\\"
href=\\"https://ol-kit.com/\\"
target=\\"_blank\\"
[33mtitle[39m=[32m\\"Powered by ol-kit v1.18.1\\"[39m
[33mtitle[39m=[32m\\"Powered by ol-kit v1.19.0\\"[39m
>
<svg
viewBox=\\"0 0 204.76 236.44\\"
Expand Down Expand Up @@ -50,18 +50,18 @@ exports[`<DrawContainer /> should render a basic prebuilt DrawContainer componen
</div>
</div>
<div
[33mclass[39m=[32m\\"sc-gKAblj qioHn\\"[39m
[33mclass[39m=[32m\\"sc-kEqYlL iNtNNs\\"[39m
>
<div>
<div
[33mclass[39m=[32m\\"sc-pNWxx eghRuN\\"[39m
[33mclass[39m=[32m\\"sc-dIsAE cezkQS\\"[39m
>
<div
[33mclass[39m=[32m\\"sc-jrsJCI gBTtMU\\"[39m
[33mclass[39m=[32m\\"sc-bqGHjH dDEWGd\\"[39m
id=\\"_POINT_LABELS_ENABLED\\"
>
<label
[33mclass[39m=[32m\\"sc-kEqYlL dlkeav\\"[39m
[33mclass[39m=[32m\\"sc-ksluoS blZtgg\\"[39m
for=\\"_POINT_LABELS_ENABLED\\"
>
Coordinate Labels
Expand Down Expand Up @@ -106,10 +106,10 @@ exports[`<DrawContainer /> should render a basic prebuilt DrawContainer componen
style=\\"width: 100%;\\"
>
<div
[33mclass[39m=[32m\\"sc-pNWxx eghRuN\\"[39m
[33mclass[39m=[32m\\"sc-dIsAE cezkQS\\"[39m
>
<div
[33mclass[39m=[32m\\"sc-jrsJCI gBTtMU\\"[39m
[33mclass[39m=[32m\\"sc-bqGHjH dDEWGd\\"[39m
id=\\"_DISTANCE_LABEL_ENABLED\\"
>
<span
Expand Down Expand Up @@ -142,7 +142,7 @@ exports[`<DrawContainer /> should render a basic prebuilt DrawContainer componen
</span>
</div>
<div
[33mclass[39m=[32m\\"sc-iqAbSa eMfxro\\"[39m
[33mclass[39m=[32m\\"sc-hBMVcZ cksXlh\\"[39m
>
<span>
Distance: 
Expand All @@ -156,5 +156,5 @@ exports[`<DrawContainer /> should render a basic prebuilt DrawContainer componen
>
<div
aria-haspopup=\\"listbox\\"
[33mclass[39m=[32m\\"MuiSelect-root WithStyles(ForwardRef(Select))-root-42 WithStyles(ForwardRef(Select))-root-43 MuiSelect-select MuiSelect-selectMenu MuiI..."
[33mclass[39m=[32m\\"MuiSelect-root WithStyles(ForwardRef(Select))-root-42 WithStyles(ForwardRef(Select))-root-43 MuiSelect-select MuiSelect-selectMenu Mui..."
`;
79 changes: 79 additions & 0 deletions src/ErrorBoundary/ErrorBoundary.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Button, Container, FloatingBackground, Header, Message } from './styled'
import MappyConcerned from './mappy_concerned.svg'
import MappyDead from './mappy_dead.svg'

/**
* React ErrorBoundary component
* @component
*/
class ErrorBoundary extends React.Component {
constructor (props) {
super(props)

this.state = {
abandonAllHope: false,
attemptedReset: false,
hasError: false
}
}

componentDidCatch () {
const { attemptedReset } = this.state

if (attemptedReset) {
// don't try to reset state after attemptedReset
this.setState({ abandonAllHope: true })
}
}

componentDidUpdate (prevProps, prevState) {
const { abandonAllHope, attemptedReset, hasError } = this.state

// reset the attemptedReset bool whenever successful reset occurs so next error can also be reset instead of abandoned
if (prevState.hasError && !prevState.attemptedReset && !abandonAllHope && attemptedReset && !hasError) {
this.setState({ attemptedReset: false })
}
}

static getDerivedStateFromError (error) { // eslint-disable-line handle-callback-err
return { hasError: true }
}

render () {
const { abandonAllHope, hasError } = this.state
const { floating } = this.props

return hasError
? (
<FloatingBackground floating={floating}>
<Container floating={floating}>
<Header>Something went wrong!</Header>
{!abandonAllHope
? <MappyConcerned />
: <MappyDead />}
<Message>{!abandonAllHope
? 'Have another go?'
: 'This component is beyond recovery...'}
</Message>
{!abandonAllHope
? <Button onClick={() => this.setState({ attemptedReset: true, hasError: false })}>Try Again</Button>
: null}
</Container>
</FloatingBackground>
) : this.props.children
}
}

ErrorBoundary.propTypes = {
/** pass components as children of ErrorBoundary which are rendered if there is no error */
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node
]).isRequired,
/** display error boundary with a modal-like background */
floating: PropTypes.bool
}

export default ErrorBoundary
54 changes: 54 additions & 0 deletions src/ErrorBoundary/ErrorBoundary.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from 'react'
import { render, waitFor } from '@testing-library/react'
import { Map } from 'Map'
import { MultiMapManager, FlexMap, FullScreenFlex } from 'MultiMapManager'

const Bomb = () => {
throw new Error('Blew up during render')
}

describe('ErrorBoundary', () => {
it('displays mappy on Map render errors', async () => {
const { container, getByText } = render(<Map><Bomb /></Map>)

// wait for async child render
await waitFor(() => {}, { container })

expect(getByText('Something went wrong!')).toBeInTheDocument()
})

it.skip('displays mappy on MultiMap render errors', async () => {
const mapKeys = [
'map0',
'map1',
'map2',
'map3'
]

const { container, getByText } = render(
<MultiMapManager groups={[['map0', 'map1'], ['map2', 'map3']]}>
<FullScreenFlex>
{mapKeys.map((key, i, array) => {
return (
<FlexMap
key={key}
index={i}
total={array.length}
numberOfRows={2}
numberOfColumns={2}>
<Map id={key} isMultiMap>
{ i % 2 === 0 && <Bomb />}
</Map>
</FlexMap>
)
})}
</FullScreenFlex>
</MultiMapManager>
)

// wait for async child render
await waitFor(() => {}, { container })

expect(getByText('Something went wrong!')).toBeInTheDocument()
})
})
1 change: 1 addition & 0 deletions src/ErrorBoundary/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as ErrorBoundary } from './ErrorBoundary'
1 change: 1 addition & 0 deletions src/ErrorBoundary/mappy_concerned.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/ErrorBoundary/mappy_dead.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
54 changes: 54 additions & 0 deletions src/ErrorBoundary/styled.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import styled from 'styled-components'

export const Button = styled.button`
border: 1px solid #353535;
border-radius: 2px;
padding: 2px 6px;
background: white;
margin-top: 20px;
font-size: 12px;
color: #353535;
`

export const Container = styled.div`
width: 100%;
height: 100%;
color: #ca2525;
text-align: center;
${props => props.floating ? `
position: absolute;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
margin: auto;
background: white;
width: 250px;
height: 250px;
border-radius: 4px;`
: null
}
`

export const FloatingBackground = styled.div`
${props => props.floating ? `
background: #000000ab;
top: 0;
left: 0;
bottom: 0;
right: 0;
position: absolute;`
: null
}
`

export const Header = styled.h1`
font-size: 20px;
padding-top: 20px;
`

export const Message = styled.em`
display: block;
color: #353535;
font-size: 12px;
`
2 changes: 1 addition & 1 deletion src/LayerStyler/_Selector/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ class Selector extends Component {
}

return (
<Container style={styles} {...this.props}>
<Container style={styles} selected={selected} disabled={disabled}>
<Group>
<TextInput
autoFocus={false /* autoFocus would open the menu on mount which blocks some of the ui */}
Expand Down
9 changes: 6 additions & 3 deletions src/MultiMapManager/MultiMapManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types'
import en from 'locales/en'
import { syncViewEvents } from './utils'
import { ErrorBoundary } from 'ErrorBoundary'

// context is only created when <MultiMapManager> is implemented (see constructor)
export let MultiMapContext = null
Expand Down Expand Up @@ -102,9 +103,11 @@ class MultiMapManager extends React.Component {
const adoptedChildren = childModifier(this.props.children)

return (
<MultiMapContext.Provider value={this.getContextValue()}>
{adoptedChildren}
</MultiMapContext.Provider>
<ErrorBoundary floating={true}>
<MultiMapContext.Provider value={this.getContextValue()}>
{adoptedChildren}
</MultiMapContext.Provider>
</ErrorBoundary>
)
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/Project/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { loadBasemapLayer } from 'Basemaps'
import { transform } from 'ol/proj'
import { centerAndZoom } from 'Map'
import ugh from 'ugh'
import { version } from '../../package.json'

import packageJson from '../../package.json'
const { version } = packageJson
const readOpts = { dataProjection: 'EPSG:4326', featureProjection: 'EPSG:3857' }

/**
Expand Down
9 changes: 6 additions & 3 deletions src/Provider/Provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types'
import en from 'locales/en'
import ugh from 'ugh'
import { ErrorBoundary } from 'ErrorBoundary'

// context is only created when <Provider> is implemented (see constructor)
export let ProviderContext = null
Expand Down Expand Up @@ -68,9 +69,11 @@ class Provider extends React.Component {

render () {
return (
<ProviderContext.Provider value={this.getContextValue()}>
{this.props.children}
</ProviderContext.Provider>
<ErrorBoundary floating={true}>
<ProviderContext.Provider value={this.getContextValue()}>
{this.props.children}
</ProviderContext.Provider>
</ErrorBoundary>
)
}
}
Expand Down
Loading

0 comments on commit 7e967d3

Please sign in to comment.