Skip to content

Commit

Permalink
Fixes #14201 by catching and displaying render errors (#14206)
Browse files Browse the repository at this point in the history
* Fixes #14201 by catching and displaying render errors

* fix lint issues
  • Loading branch information
stacey-gammon authored Oct 2, 2017
1 parent bb3c9f3 commit 895343d
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 11 deletions.
43 changes: 33 additions & 10 deletions src/core_plugins/kibana/public/dashboard/panel/dashboard_panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -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.
Expand All @@ -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 });
});
}
}

Expand Down Expand Up @@ -78,6 +89,20 @@ export class DashboardPanel extends React.Component {
}
}

renderEmbeddedContent() {
return (
<div
id="embeddedPanel"
className="panel-content"
ref={panelElement => this.panelElement = panelElement}
/>
);
}

renderEmbeddedError() {
return <PanelError error={this.state.error} />;
}

render() {
const { title } = this.state;
const { dashboardViewMode, isFullScreenMode, isExpanded } = this.props;
Expand All @@ -102,11 +127,9 @@ export class DashboardPanel extends React.Component {
isExpanded={isExpanded}
isViewOnlyMode={isFullScreenMode || dashboardViewMode === DashboardViewMode.VIEW}
/>
<div
id="embeddedPanel"
className="panel-content"
ref={panelElement => this.panelElement = panelElement}
/>

{this.state.error ? this.renderEmbeddedError() : this.renderEmbeddedContent()}

</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: () => {},
Expand All @@ -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 = {}) {
Expand Down Expand Up @@ -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(<DashboardPanel {...props} />);
return new Promise(resolve => {
return process.nextTick(() => {
const panelElements = component.find(PanelError);
expect(panelElements.length).toBe(1);
resolve();
});
});
});

19 changes: 19 additions & 0 deletions src/core_plugins/kibana/public/dashboard/panel/panel_error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';
import PropTypes from 'prop-types';

export function PanelError({ error }) {
return (
<div className="load-error">
<span aria-hidden="true" className="kuiIcon fa-exclamation-triangle"/>
<span>{error}</span>
</div>
);
}

PanelError.PropTypes = {
error: PropTypes.oneOfType([
PropTypes.string,
PropTypes.node
]),
};

0 comments on commit 895343d

Please sign in to comment.