diff --git a/.eslintrc.js b/.eslintrc.js index 784397af6e..aab3d82e69 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -223,6 +223,9 @@ module.exports = { 'react/sort-comp': 0, 'react/jsx-one-expression-per-line': 0, 'react/jsx-curly-newline': 0, + 'react/jsx-indent-props': 0, + 'react/jsx-max-props-per-line': 0, + 'react/jsx-first-prop-new-line': 0, 'react/jsx-indent': 0, // Too restrictive: https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/destructuring-assignment.md 'react/destructuring-assignment': 0, diff --git a/src/chart_types/xy_chart/domains/y_domain.ts b/src/chart_types/xy_chart/domains/y_domain.ts index d08df5f7e9..265edd8fac 100644 --- a/src/chart_types/xy_chart/domains/y_domain.ts +++ b/src/chart_types/xy_chart/domains/y_domain.ts @@ -17,6 +17,7 @@ * under the License. */ +import { GracefulError } from '../../../components/error_boundary/errors'; import { ScaleContinuousType } from '../../../scales'; import { ScaleType } from '../../../scales/constants'; import { identity } from '../../../utils/commons'; @@ -141,7 +142,7 @@ function mergeYDomainForGroup( domain = [newCustomDomain.min, newCustomDomain.max]; } else if (newCustomDomain && isLowerBound(newCustomDomain)) { if (newCustomDomain.min > computedDomainMax) { - throw new Error(`custom yDomain for ${groupId} is invalid, custom min is greater than computed max`); + throw new GracefulError(`custom yDomain for ${groupId} is invalid, custom min is greater than computed max`); } domain = [newCustomDomain.min, computedDomainMax]; diff --git a/src/components/chart.tsx b/src/components/chart.tsx index b2e0902400..e00dea5752 100644 --- a/src/components/chart.tsx +++ b/src/components/chart.tsx @@ -36,6 +36,7 @@ import { ChartBackground } from './chart_background'; import { ChartContainer } from './chart_container'; import { ChartResizer } from './chart_resizer'; import { ChartStatus } from './chart_status'; +import { ErrorBoundary } from './error_boundary'; import { Legend } from './legend/legend'; interface ChartProps { @@ -179,10 +180,12 @@ export class Chart extends React.Component { - {this.props.children} -
- -
+ + {this.props.children} +
+ +
+
); diff --git a/src/components/error_boundary/error_boundary.tsx b/src/components/error_boundary/error_boundary.tsx new file mode 100644 index 0000000000..878a959fd3 --- /dev/null +++ b/src/components/error_boundary/error_boundary.tsx @@ -0,0 +1,63 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { Component, ReactNode } from 'react'; + +import { isGracefulError } from './errors'; + +type ErrorBoundaryProps = { + children: ReactNode; +}; + +interface ErrorBoundaryState { + hasError: boolean; +} + +/** + * Error Boundary to catch and handle custom errors + * @internal + */ +export class ErrorBoundary extends Component { + hasError = false; + + componentDidUpdate() { + if (this.hasError) { + this.hasError = false; + } + } + + componentDidCatch(error: Error) { + if (isGracefulError(error)) { + this.hasError = true; + this.forceUpdate(); + } + } + + render() { + if (this.hasError) { + return ( +
+

No data to display

+
+ ); + } + + return this.props.children; + } +} diff --git a/src/components/error_boundary/errors.ts b/src/components/error_boundary/errors.ts new file mode 100644 index 0000000000..02221896e5 --- /dev/null +++ b/src/components/error_boundary/errors.ts @@ -0,0 +1,37 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { $Values } from 'utility-types'; + +export const ErrorType = Object.freeze({ + Graceful: 'graceful' as const, +}); +/** @public */ +export type ErrorType = $Values; + +/** + * Error to used to gracefully render empty chart + */ +export class GracefulError extends Error { + type = ErrorType.Graceful; +} + +export function isGracefulError(error: Error): error is GracefulError { + return (error as GracefulError)?.type === ErrorType.Graceful; +} diff --git a/src/components/error_boundary/index.tsx b/src/components/error_boundary/index.tsx new file mode 100644 index 0000000000..c0ac8af0c0 --- /dev/null +++ b/src/components/error_boundary/index.tsx @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* @internal */ +export { ErrorBoundary } from './error_boundary'; +/* @internal */ +export * from './errors';