From 15e314f40c378aa65c4d83055760f49fb56af539 Mon Sep 17 00:00:00 2001 From: egorkluch Date: Thu, 15 Jun 2023 10:26:24 +0700 Subject: [PATCH] feat(main): Add renderErrorView prop for custom render and handle errors --- src/components/ChartKit.tsx | 6 +- .../ErrorBoundary/ErrorBoundary.tsx | 27 ++++++- src/components/ErrorView/ErrorView.tsx | 14 ---- .../__stories__/components/ChartStory.tsx | 14 +++- .../custom-error-render.stories.tsx | 76 ++++++++++++++++++ .../highcharts/mocks/custom-error-render.ts | 80 +++++++++++++++++++ src/types/index.ts | 4 +- src/utils/getErrorMessage.ts | 7 ++ 8 files changed, 208 insertions(+), 20 deletions(-) delete mode 100644 src/components/ErrorView/ErrorView.tsx create mode 100644 src/plugins/highcharts/__stories__/custom-error-render/custom-error-render.stories.tsx create mode 100644 src/plugins/highcharts/mocks/custom-error-render.ts create mode 100644 src/utils/getErrorMessage.ts diff --git a/src/components/ChartKit.tsx b/src/components/ChartKit.tsx index a402ab48..c2595bbf 100644 --- a/src/components/ChartKit.tsx +++ b/src/components/ChartKit.tsx @@ -74,7 +74,11 @@ const ChartKitComponentWithErrorBoundary = React.forwardRef< }, []); return ( - + ); diff --git a/src/components/ErrorBoundary/ErrorBoundary.tsx b/src/components/ErrorBoundary/ErrorBoundary.tsx index 6065e968..14dbc432 100644 --- a/src/components/ErrorBoundary/ErrorBoundary.tsx +++ b/src/components/ErrorBoundary/ErrorBoundary.tsx @@ -1,17 +1,28 @@ import React from 'react'; import type {ChartKitError} from '../../libs'; import type {ChartKitOnError} from '../../types'; -import {ErrorView} from '../ErrorView/ErrorView'; +import {getErrorMessage} from '../../utils/getErrorMessage'; type Props = { onError?: ChartKitOnError; resetError?(resetError: () => void): void; + renderErrorView?: ErrorBoundaryRenderErrorView; }; type State = { error?: ChartKitError | Error; }; +export type ErrorBoundaryRenderErrorViewOpts = { + message: string; + error: ChartKitError | Error; + resetError: () => void; +}; + +export type ErrorBoundaryRenderErrorView = ( + opts: ErrorBoundaryRenderErrorViewOpts, +) => React.ReactNode; + export class ErrorBoundary extends React.Component { static getDerivedStateFromError(error: Error) { return {error}; @@ -37,7 +48,19 @@ export class ErrorBoundary extends React.Component { const {error} = this.state; if (error) { - return ; + const message = getErrorMessage(error); + + if (this.props.renderErrorView) { + return this.props.renderErrorView({ + error, + message, + resetError: () => { + this.setState({error: undefined}); + }, + }); + } + + return
{message}
; } return this.props.children; diff --git a/src/components/ErrorView/ErrorView.tsx b/src/components/ErrorView/ErrorView.tsx deleted file mode 100644 index 37f62bd7..00000000 --- a/src/components/ErrorView/ErrorView.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import {i18n} from '../../i18n'; -import type {ChartKitError} from '../../libs'; - -type Props = { - error: ChartKitError | Error; -}; - -export const ErrorView = ({error}: Props) => { - const code = 'code' in error && error.code; - const message = error.message || code || i18n('error', 'label_unknown-error'); - - return
{message}
; -}; diff --git a/src/plugins/highcharts/__stories__/components/ChartStory.tsx b/src/plugins/highcharts/__stories__/components/ChartStory.tsx index 838284ab..b7a1ebc3 100644 --- a/src/plugins/highcharts/__stories__/components/ChartStory.tsx +++ b/src/plugins/highcharts/__stories__/components/ChartStory.tsx @@ -6,6 +6,7 @@ import {HighchartsPlugin} from '../../index'; import holidays from '../../mocks/holidays'; import {ChartKit} from '../../../../components/ChartKit'; import {HighchartsWidgetData} from '../../types'; +import {ErrorBoundaryRenderErrorView} from '../../../../components/ErrorBoundary/ErrorBoundary'; const DEFAULT_STORY_HEIGHT = '300px'; const DEFAULT_STORY_WIDTH = '100%'; @@ -13,9 +14,11 @@ const DEFAULT_STORY_WIDTH = '100%'; export type ChartStoryProps = { data: HighchartsWidgetData; + withoutPlugin?: boolean; visible?: boolean; height?: string; width?: string; + renderErrorView?: ErrorBoundaryRenderErrorView; }; export const ChartStory: React.FC = (props: ChartStoryProps) => { const {height, width, data} = props; @@ -25,7 +28,9 @@ export const ChartStory: React.FC = (props: ChartStoryProps) => const chartKitRef = React.useRef(); if (!initRef.current) { - settings.set({plugins: [HighchartsPlugin], extra: {holidays}}); + if (!props.withoutPlugin) { + settings.set({plugins: [HighchartsPlugin], extra: {holidays}}); + } initRef.current = true; } @@ -40,7 +45,12 @@ export const ChartStory: React.FC = (props: ChartStoryProps) => width: width || DEFAULT_STORY_WIDTH, }} > - + ); }; diff --git a/src/plugins/highcharts/__stories__/custom-error-render/custom-error-render.stories.tsx b/src/plugins/highcharts/__stories__/custom-error-render/custom-error-render.stories.tsx new file mode 100644 index 00000000..633564dc --- /dev/null +++ b/src/plugins/highcharts/__stories__/custom-error-render/custom-error-render.stories.tsx @@ -0,0 +1,76 @@ +import React from 'react'; +import {Meta, Story} from '@storybook/react'; +import {ChartKit} from '../../../../components/ChartKit'; +import {ChartStory} from '../components/ChartStory'; +import {Button} from '@gravity-ui/uikit'; +import {ErrorBoundaryRenderErrorView} from '../../../../components/ErrorBoundary/ErrorBoundary'; +import {CHARTKIT_ERROR_CODE, settings} from '../../../../libs'; +import {HighchartsPlugin} from '../../index'; +import holidays from '../../mocks/holidays'; +import {noData, filledData} from '../../mocks/custom-error-render'; + +export default { + title: 'Plugins/Highcharts/CustomErrorRender', + component: ChartKit, +} as Meta; + +const Template: Story = () => { + const [data, setData] = React.useState(noData); + + const renderErrorView: ErrorBoundaryRenderErrorView = React.useCallback( + ({error, message, resetError}) => { + function renderFixButton() { + if (!('code' in error)) { + return null; + } + + switch (error.code) { + case CHARTKIT_ERROR_CODE.UNKNOWN_PLUGIN: + return ( + + ); + case CHARTKIT_ERROR_CODE.NO_DATA: + return ( + + ); + default: + return null; + } + } + + return ( +
+

{message}

+ {renderFixButton()} +
+ ); + }, + [], + ); + + return ( +
+ +
+ ); +}; + +export const CustomErrorRender = Template.bind({}); diff --git a/src/plugins/highcharts/mocks/custom-error-render.ts b/src/plugins/highcharts/mocks/custom-error-render.ts new file mode 100644 index 00000000..1e4185c0 --- /dev/null +++ b/src/plugins/highcharts/mocks/custom-error-render.ts @@ -0,0 +1,80 @@ +import type {HighchartsWidgetData} from '../types'; +import _ from 'lodash'; + +const baseData: Omit = { + config: { + hideHolidays: false, + normalizeDiv: false, + normalizeSub: false, + }, + libraryConfig: { + chart: { + type: 'arearange', + }, + title: { + text: 'Temperature variation by day', + }, + xAxis: { + type: 'datetime', + }, + tooltip: { + valueSuffix: '°C', + }, + }, +}; + +export const noData: HighchartsWidgetData = { + ...baseData, + data: { + graphs: [ + { + name: 'Temperatures', + data: [], + }, + ], + }, +}; + +export const filledData: HighchartsWidgetData = { + ...baseData, + data: { + graphs: [ + { + name: 'Temperatures', + data: [ + [1246406400000, 10.4, 17], + [1246492800000, 10.3, 28.6], + [1246579200000, 14.8, 18.4], + [1246665600000, 11.5, 25.8], + [1246752000000, 11.1, 24.4], + [1246838400000, 17.7, 19.6], + [1246924800000, 15.1, 18.1], + [1247011200000, 15.1, 27.2], + [1247097600000, 17, 17.5], + [1247184000000, 12.6, 18.5], + [1247270400000, 12.2, 26], + [1247356800000, 15.9, 22.9], + [1247443200000, 17.1, 18.1], + [1247529600000, 13.3, 24.2], + [1247616000000, 17, 28.1], + [1247702400000, 16.2, 22.6], + [1247788800000, 10.6, 19], + [1247875200000, 11.3, 19.7], + [1247961600000, 14.1, 24.6], + [1248048000000, 14.2, 22.5], + [1248134400000, 14.1, 28.5], + [1248220800000, 14, 27], + [1248307200000, 10.2, 20.6], + [1248393600000, 13.1, 29.9], + [1248480000000, 13.7, 21.1], + [1248566400000, 15, 28.6], + [1248652800000, 12, 17.5], + [1248739200000, 17.8, 24.4], + [1248825600000, 11.7, 25.9], + [1248912000000, 13.6, 25.6], + [1248998400000, 17.3, 22.2], + ], + }, + ], + }, +}; diff --git a/src/types/index.ts b/src/types/index.ts index 0e928bdc..e290f4c2 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,6 +1,7 @@ import React from 'react'; import type {ChartKitWidget} from './widget'; +import type {ErrorBoundaryRenderErrorView} from '../components/ErrorBoundary/ErrorBoundary'; export type {ChartKitHolidays} from './misc'; @@ -34,6 +35,7 @@ export type ChartKitOnError = (data: {error: any}) => void; export type ChartKitProps = { type: T; data: ChartKitWidget[T]['data']; + id?: string; isMobile?: boolean; onLoad?: (data?: ChartKitOnLoadData) => void; @@ -47,7 +49,7 @@ export type ChartKitProps = { * @param data */ onChartLoad?: (data: ChartKitOnChartLoad) => void; - + renderErrorView?: ErrorBoundaryRenderErrorView; onError?: ChartKitOnError; } & {[key in keyof Omit]: ChartKitWidget[T][key]}; diff --git a/src/utils/getErrorMessage.ts b/src/utils/getErrorMessage.ts new file mode 100644 index 00000000..f1ec644c --- /dev/null +++ b/src/utils/getErrorMessage.ts @@ -0,0 +1,7 @@ +import type {ChartKitError} from '../libs'; +import {i18n} from '../i18n'; + +export function getErrorMessage(error: ChartKitError | Error) { + const code = 'code' in error && error.code; + return (error.message || code || i18n('error', 'label_unknown-error')).toString(); +}