diff --git a/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel.js b/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel.js index 76c6b7a494cd3..1243bdf2ac90c 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel.js +++ b/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel.js @@ -4,6 +4,7 @@ import classNames from 'classnames'; import { DashboardViewMode } from '../dashboard_view_mode'; import { PanelHeader } from './panel_header'; +import { PanelError } from './panel_error'; export class DashboardPanel extends React.Component { @@ -15,13 +16,18 @@ export class DashboardPanel extends React.Component { this._isMounted = false; } - async componentDidMount() { + componentDidMount() { this._isMounted = true; const { getEmbeddableHandler, panel, getContainerApi } = this.props; this.containerApi = getContainerApi(); this.embeddableHandler = getEmbeddableHandler(panel.type); + if (!this.embeddableHandler) { + /* eslint-disable react/no-did-mount-set-state */ + this.setState({ error: `Invalid panel type ${panel.type}` }); + } + // TODO: use redux instead of the isMounted anti-pattern to handle the case when the component is unmounted // before the async calls above return. We can then get rid of the eslint disable line. Without redux, there is // not a better option, since you aren't supposed to run async calls inside of componentWillMount. @@ -37,10 +43,15 @@ export class DashboardPanel extends React.Component { }); if (this._isMounted) { - this.destroyEmbeddable = await this.embeddableHandler.render( - this.panelElement, - panel, - this.containerApi); + this.embeddableHandler.render( + this.panelElement, + panel, + this.containerApi) + .then(destroyEmbeddable => this.destroyEmbeddable = destroyEmbeddable) + .catch(error => { + const message = error.message || JSON.stringify(error); + this.setState({ error: message }); + }); } } @@ -78,6 +89,20 @@ export class DashboardPanel extends React.Component { } } + renderEmbeddedContent() { + return ( +
this.panelElement = panelElement} + /> + ); + } + + renderEmbeddedError() { + return ; + } + render() { const { title } = this.state; const { dashboardViewMode, isFullScreenMode, isExpanded } = this.props; @@ -102,11 +127,9 @@ export class DashboardPanel extends React.Component { isExpanded={isExpanded} isViewOnlyMode={isFullScreenMode || dashboardViewMode === DashboardViewMode.VIEW} /> -
this.panelElement = panelElement} - /> + + {this.state.error ? this.renderEmbeddedError() : this.renderEmbeddedContent()} +
); diff --git a/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel.test.js b/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel.test.js index 3cc6b99b3a0e6..26d56fa50d5d0 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel.test.js +++ b/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel.test.js @@ -3,6 +3,7 @@ import _ from 'lodash'; import { mount } from 'enzyme'; import { DashboardViewMode } from '../dashboard_view_mode'; import { DashboardPanel } from './dashboard_panel'; +import { PanelError } from '../panel/panel_error'; const containerApiMock = { addFilter: () => {}, @@ -15,7 +16,7 @@ const containerApiMock = { const embeddableHandlerMock = { getEditPath: () => Promise.resolve('editPath'), getTitleFor: () => Promise.resolve('title'), - render: jest.fn() + render: jest.fn(() => Promise.resolve(() => {})) }; function getProps(props = {}) { @@ -45,3 +46,24 @@ test('DashboardPanel matches snapshot', () => { test('and calls render', () => { expect(embeddableHandlerMock.render.mock.calls.length).toBe(1); }); + +test('renders an error message when an error is thrown', () => { + const props = getProps({ + getEmbeddableHandler: () => { + return { + getEditPath: () => Promise.resolve('editPath'), + getTitleFor: () => Promise.resolve('title'), + render: () => Promise.reject(new Error({ message: 'simulated error' })) + }; + } + }); + const component = mount(); + return new Promise(resolve => { + return process.nextTick(() => { + const panelElements = component.find(PanelError); + expect(panelElements.length).toBe(1); + resolve(); + }); + }); +}); + diff --git a/src/core_plugins/kibana/public/dashboard/panel/panel_error.js b/src/core_plugins/kibana/public/dashboard/panel/panel_error.js new file mode 100644 index 0000000000000..6c247691496ff --- /dev/null +++ b/src/core_plugins/kibana/public/dashboard/panel/panel_error.js @@ -0,0 +1,19 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +export function PanelError({ error }) { + return ( +
+
+ ); +} + +PanelError.PropTypes = { + error: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.node + ]), +}; +