mirror of https://github.com/grafana/grafana
SafeDynamicImport: Updates so that it does not act as an ErrorBoundary (#20170)
* SafeDynamicImport: Fixes so that it shows different messages depending on error * Refactor: Fixes type error * Refactor: Adds grafana constant to error message * Refactor: Renames components and adds exports * Refactor: Uses react-loader instead * Refactor: Updates after PR comments * Tests: Adds tests for loadComponentHandlerpull/20199/head
parent
9117fab43a
commit
54602f16a8
@ -0,0 +1,28 @@ |
|||||||
|
import React, { FunctionComponent } from 'react'; |
||||||
|
import { ErrorBoundaryApi } from './ErrorBoundary'; |
||||||
|
import { stylesFactory } from '../../themes'; |
||||||
|
import { css } from 'emotion'; |
||||||
|
|
||||||
|
const getStyles = stylesFactory(() => { |
||||||
|
return css` |
||||||
|
width: 500px; |
||||||
|
margin: 64px auto; |
||||||
|
`;
|
||||||
|
}); |
||||||
|
|
||||||
|
export interface Props extends ErrorBoundaryApi { |
||||||
|
title: string; |
||||||
|
} |
||||||
|
|
||||||
|
export const ErrorWithStack: FunctionComponent<Props> = ({ error, errorInfo, title }) => ( |
||||||
|
<div className={getStyles()}> |
||||||
|
<h2>{title}</h2> |
||||||
|
<details style={{ whiteSpace: 'pre-wrap' }}> |
||||||
|
{error && error.toString()} |
||||||
|
<br /> |
||||||
|
{errorInfo && errorInfo.componentStack} |
||||||
|
</details> |
||||||
|
</div> |
||||||
|
); |
||||||
|
|
||||||
|
ErrorWithStack.displayName = 'ErrorWithStack'; |
||||||
@ -0,0 +1,35 @@ |
|||||||
|
import React, { FunctionComponent } from 'react'; |
||||||
|
import { Button, stylesFactory } from '@grafana/ui'; |
||||||
|
import { css } from 'emotion'; |
||||||
|
|
||||||
|
const getStyles = stylesFactory(() => { |
||||||
|
return css` |
||||||
|
width: 508px; |
||||||
|
margin: 128px auto; |
||||||
|
`;
|
||||||
|
}); |
||||||
|
|
||||||
|
interface Props { |
||||||
|
error: Error | null; |
||||||
|
} |
||||||
|
|
||||||
|
export const ErrorLoadingChunk: FunctionComponent<Props> = ({ error }) => ( |
||||||
|
<div className={getStyles()}> |
||||||
|
<h2>Unable to find application file</h2> |
||||||
|
<br /> |
||||||
|
<h2 className="page-heading">Grafana has likely been updated. Please try reloading the page.</h2> |
||||||
|
<br /> |
||||||
|
<div className="gf-form-group"> |
||||||
|
<Button size="md" variant="secondary" icon="fa fa-repeat" onClick={() => window.location.reload()}> |
||||||
|
Reload |
||||||
|
</Button> |
||||||
|
</div> |
||||||
|
<details style={{ whiteSpace: 'pre-wrap' }}> |
||||||
|
{error && error.message ? error.message : 'Unexpected error occurred'} |
||||||
|
<br /> |
||||||
|
{error && error.stack ? error.stack : null} |
||||||
|
</details> |
||||||
|
</div> |
||||||
|
); |
||||||
|
|
||||||
|
ErrorLoadingChunk.displayName = 'ErrorLoadingChunk'; |
||||||
@ -0,0 +1,10 @@ |
|||||||
|
import React, { FunctionComponent } from 'react'; |
||||||
|
import { LoadingPlaceholder } from '@grafana/ui'; |
||||||
|
|
||||||
|
export const LoadingChunkPlaceHolder: FunctionComponent = React.memo(() => ( |
||||||
|
<div className="preloader"> |
||||||
|
<LoadingPlaceholder text={'Loading...'} /> |
||||||
|
</div> |
||||||
|
)); |
||||||
|
|
||||||
|
LoadingChunkPlaceHolder.displayName = 'LoadingChunkPlaceHolder'; |
||||||
@ -0,0 +1,36 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { loadComponentHandler } from './SafeDynamicImport'; |
||||||
|
import { ErrorLoadingChunk } from './ErrorLoadingChunk'; |
||||||
|
import { LoadingChunkPlaceHolder } from './LoadingChunkPlaceHolder'; |
||||||
|
|
||||||
|
describe('loadComponentHandler', () => { |
||||||
|
describe('when there is no error and pastDelay is false', () => { |
||||||
|
it('then it should return null', () => { |
||||||
|
const error: Error = null; |
||||||
|
const pastDelay = false; |
||||||
|
const element = loadComponentHandler({ error, pastDelay }); |
||||||
|
|
||||||
|
expect(element).toBe(null); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('when there is an error', () => { |
||||||
|
it('then it should return ErrorLoadingChunk', () => { |
||||||
|
const error: Error = new Error('Some chunk failed to load'); |
||||||
|
const pastDelay = false; |
||||||
|
const element = loadComponentHandler({ error, pastDelay }); |
||||||
|
|
||||||
|
expect(element).toEqual(<ErrorLoadingChunk error={error} />); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('when loading is taking more then default delay of 200ms', () => { |
||||||
|
it('then it should return LoadingChunkPlaceHolder', () => { |
||||||
|
const error: Error = null; |
||||||
|
const pastDelay = true; |
||||||
|
const element = loadComponentHandler({ error, pastDelay }); |
||||||
|
|
||||||
|
expect(element).toEqual(<LoadingChunkPlaceHolder />); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
@ -0,0 +1,27 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import Loadable from 'react-loadable'; |
||||||
|
import { LoadingChunkPlaceHolder } from './LoadingChunkPlaceHolder'; |
||||||
|
import { ErrorLoadingChunk } from './ErrorLoadingChunk'; |
||||||
|
|
||||||
|
export const loadComponentHandler = (props: { error: Error; pastDelay: boolean }) => { |
||||||
|
const { error, pastDelay } = props; |
||||||
|
|
||||||
|
if (error) { |
||||||
|
return <ErrorLoadingChunk error={error} />; |
||||||
|
} |
||||||
|
|
||||||
|
if (pastDelay) { |
||||||
|
return <LoadingChunkPlaceHolder />; |
||||||
|
} |
||||||
|
|
||||||
|
return null; |
||||||
|
}; |
||||||
|
|
||||||
|
export const SafeDynamicImport = (importStatement: Promise<any>) => ({ ...props }) => { |
||||||
|
const LoadableComponent = Loadable({ |
||||||
|
loader: () => importStatement, |
||||||
|
loading: loadComponentHandler, |
||||||
|
}); |
||||||
|
|
||||||
|
return <LoadableComponent {...props} />; |
||||||
|
}; |
||||||
@ -1,52 +0,0 @@ |
|||||||
import React, { lazy, Suspense, FunctionComponent } from 'react'; |
|
||||||
import { cx, css } from 'emotion'; |
|
||||||
import { LoadingPlaceholder, ErrorBoundary, Button } from '@grafana/ui'; |
|
||||||
|
|
||||||
export const LoadingChunkPlaceHolder: FunctionComponent = () => ( |
|
||||||
<div className={cx('preloader')}> |
|
||||||
<LoadingPlaceholder text={'Loading...'} /> |
|
||||||
</div> |
|
||||||
); |
|
||||||
|
|
||||||
function getAlertPageStyle() { |
|
||||||
return css` |
|
||||||
width: 508px; |
|
||||||
margin: 128px auto; |
|
||||||
`;
|
|
||||||
} |
|
||||||
|
|
||||||
export const SafeDynamicImport = (importStatement: Promise<any>) => ({ ...props }) => { |
|
||||||
const LazyComponent = lazy(() => importStatement); |
|
||||||
return ( |
|
||||||
<ErrorBoundary> |
|
||||||
{({ error, errorInfo }) => { |
|
||||||
if (!errorInfo) { |
|
||||||
return ( |
|
||||||
<Suspense fallback={<LoadingChunkPlaceHolder />}> |
|
||||||
<LazyComponent {...props} /> |
|
||||||
</Suspense> |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
return ( |
|
||||||
<div className={getAlertPageStyle()}> |
|
||||||
<h2>Unable to find application file</h2> |
|
||||||
<br /> |
|
||||||
<h2 className="page-heading">Grafana has likely been updated. Please try reloading the page.</h2> |
|
||||||
<br /> |
|
||||||
<div className="gf-form-group"> |
|
||||||
<Button size={'md'} variant={'secondary'} icon="fa fa-repeat" onClick={() => window.location.reload()}> |
|
||||||
Reload |
|
||||||
</Button> |
|
||||||
</div> |
|
||||||
<details style={{ whiteSpace: 'pre-wrap' }}> |
|
||||||
{error && error.toString()} |
|
||||||
<br /> |
|
||||||
{errorInfo.componentStack} |
|
||||||
</details> |
|
||||||
</div> |
|
||||||
); |
|
||||||
}} |
|
||||||
</ErrorBoundary> |
|
||||||
); |
|
||||||
}; |
|
||||||
Loading…
Reference in new issue