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