From a2dad6157a0e77dbdae2f6c7440b55d6a40e3864 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Mon, 11 Feb 2019 16:44:09 +0100 Subject: [PATCH 01/10] hard move --- .../features/dashboard/dashgrid/DataPanel.tsx | 23 ++----- .../dashboard/dashgrid/PanelChrome.tsx | 62 ++++++++++++++----- 2 files changed, 49 insertions(+), 36 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/DataPanel.tsx b/public/app/features/dashboard/dashgrid/DataPanel.tsx index 2183548000b..5b0b8588ad0 100644 --- a/public/app/features/dashboard/dashgrid/DataPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DataPanel.tsx @@ -2,7 +2,6 @@ import React, { Component } from 'react'; import { Tooltip } from '@grafana/ui'; -import ErrorBoundary from 'app/core/components/ErrorBoundary/ErrorBoundary'; // Services import { DatasourceSrv, getDatasourceSrv } from 'app/features/plugins/datasource_srv'; // Utils @@ -18,8 +17,6 @@ import { TimeSeries, } from '@grafana/ui'; -const DEFAULT_PLUGIN_ERROR = 'Error in plugin'; - interface RenderProps { loading: LoadingState; panelData: PanelData; @@ -203,22 +200,10 @@ export class DataPanel extends Component { return ( <> {this.renderLoadingStates()} - - {({ error, errorInfo }) => { - if (errorInfo) { - this.onError(error.message || DEFAULT_PLUGIN_ERROR); - return null; - } - return ( - <> - {this.props.children({ - loading, - panelData, - })} - - ); - }} - + {this.props.children({ + loading, + panelData, + })} ); } diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index b02d9479dcc..1f69fb81d30 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -8,6 +8,7 @@ import { getTimeSrv, TimeSrv } from '../services/TimeSrv'; // Components import { PanelHeader } from './PanelHeader/PanelHeader'; import { DataPanel } from './DataPanel'; +import ErrorBoundary from '../../../core/components/ErrorBoundary/ErrorBoundary'; // Utils import { applyPanelTimeOverrides } from 'app/features/dashboard/utils/panel'; @@ -23,6 +24,8 @@ import variables from 'sass/_variables.scss'; import templateSrv from 'app/features/templating/template_srv'; import { DataQueryResponse } from '@grafana/ui/src'; +const DEFAULT_PLUGIN_ERROR = 'Error in plugin'; + export interface Props { panel: PanelModel; dashboard: DashboardModel; @@ -34,6 +37,9 @@ export interface State { renderCounter: number; timeInfo?: string; timeRange?: TimeRange; + loading: LoadingState; + isFirstLoad: boolean; + errorMessage: string; } export class PanelChrome extends PureComponent { @@ -43,8 +49,11 @@ export class PanelChrome extends PureComponent { super(props); this.state = { + loading: LoadingState.NotStarted, refreshCounter: 0, renderCounter: 0, + isFirstLoad: false, + errorMessage: '', }; } @@ -94,6 +103,16 @@ export class PanelChrome extends PureComponent { return !this.props.dashboard.otherPanelInFullscreen(this.props.panel); } + onError = (errorMessage: string) => { + if (this.state.loading !== LoadingState.Error || this.state.errorMessage !== errorMessage) { + this.setState({ + loading: LoadingState.Error, + isFirstLoad: false, + errorMessage: errorMessage, + }); + } + }; + renderPanel(loading, panelData, width, height): JSX.Element { const { panel, plugin } = this.props; const { timeRange, renderCounter } = this.state; @@ -145,23 +164,32 @@ export class PanelChrome extends PureComponent { scopedVars={panel.scopedVars} links={panel.links} /> - {panel.snapshotData ? ( - this.renderPanel(false, panel.snapshotData, width, height) - ) : ( - - {({ loading, panelData }) => { - return this.renderPanel(loading, panelData, width, height); - }} - - )} + + {({ error, errorInfo }) => { + if (errorInfo) { + this.onError(error.message || DEFAULT_PLUGIN_ERROR); + return null; + } + + return panel.snapshotData ? ( + this.renderPanel(false, panel.snapshotData, width, height) + ) : ( + + {({ loading, panelData }) => { + return this.renderPanel(loading, panelData, width, height); + }} + + ); + }} + ); }} From 7d0edb285d89cf474df82bfe8a64ab4df5f11605 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Tue, 12 Feb 2019 10:53:05 +0100 Subject: [PATCH 02/10] Revert "hard move" This reverts commit a2dad6157a0e77dbdae2f6c7440b55d6a40e3864. --- .../features/dashboard/dashgrid/DataPanel.tsx | 23 +++++-- .../dashboard/dashgrid/PanelChrome.tsx | 62 +++++-------------- 2 files changed, 36 insertions(+), 49 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/DataPanel.tsx b/public/app/features/dashboard/dashgrid/DataPanel.tsx index 5b0b8588ad0..2183548000b 100644 --- a/public/app/features/dashboard/dashgrid/DataPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DataPanel.tsx @@ -2,6 +2,7 @@ import React, { Component } from 'react'; import { Tooltip } from '@grafana/ui'; +import ErrorBoundary from 'app/core/components/ErrorBoundary/ErrorBoundary'; // Services import { DatasourceSrv, getDatasourceSrv } from 'app/features/plugins/datasource_srv'; // Utils @@ -17,6 +18,8 @@ import { TimeSeries, } from '@grafana/ui'; +const DEFAULT_PLUGIN_ERROR = 'Error in plugin'; + interface RenderProps { loading: LoadingState; panelData: PanelData; @@ -200,10 +203,22 @@ export class DataPanel extends Component { return ( <> {this.renderLoadingStates()} - {this.props.children({ - loading, - panelData, - })} + + {({ error, errorInfo }) => { + if (errorInfo) { + this.onError(error.message || DEFAULT_PLUGIN_ERROR); + return null; + } + return ( + <> + {this.props.children({ + loading, + panelData, + })} + + ); + }} + ); } diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index 1f69fb81d30..b02d9479dcc 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -8,7 +8,6 @@ import { getTimeSrv, TimeSrv } from '../services/TimeSrv'; // Components import { PanelHeader } from './PanelHeader/PanelHeader'; import { DataPanel } from './DataPanel'; -import ErrorBoundary from '../../../core/components/ErrorBoundary/ErrorBoundary'; // Utils import { applyPanelTimeOverrides } from 'app/features/dashboard/utils/panel'; @@ -24,8 +23,6 @@ import variables from 'sass/_variables.scss'; import templateSrv from 'app/features/templating/template_srv'; import { DataQueryResponse } from '@grafana/ui/src'; -const DEFAULT_PLUGIN_ERROR = 'Error in plugin'; - export interface Props { panel: PanelModel; dashboard: DashboardModel; @@ -37,9 +34,6 @@ export interface State { renderCounter: number; timeInfo?: string; timeRange?: TimeRange; - loading: LoadingState; - isFirstLoad: boolean; - errorMessage: string; } export class PanelChrome extends PureComponent { @@ -49,11 +43,8 @@ export class PanelChrome extends PureComponent { super(props); this.state = { - loading: LoadingState.NotStarted, refreshCounter: 0, renderCounter: 0, - isFirstLoad: false, - errorMessage: '', }; } @@ -103,16 +94,6 @@ export class PanelChrome extends PureComponent { return !this.props.dashboard.otherPanelInFullscreen(this.props.panel); } - onError = (errorMessage: string) => { - if (this.state.loading !== LoadingState.Error || this.state.errorMessage !== errorMessage) { - this.setState({ - loading: LoadingState.Error, - isFirstLoad: false, - errorMessage: errorMessage, - }); - } - }; - renderPanel(loading, panelData, width, height): JSX.Element { const { panel, plugin } = this.props; const { timeRange, renderCounter } = this.state; @@ -164,32 +145,23 @@ export class PanelChrome extends PureComponent { scopedVars={panel.scopedVars} links={panel.links} /> - - {({ error, errorInfo }) => { - if (errorInfo) { - this.onError(error.message || DEFAULT_PLUGIN_ERROR); - return null; - } - - return panel.snapshotData ? ( - this.renderPanel(false, panel.snapshotData, width, height) - ) : ( - - {({ loading, panelData }) => { - return this.renderPanel(loading, panelData, width, height); - }} - - ); - }} - + {panel.snapshotData ? ( + this.renderPanel(false, panel.snapshotData, width, height) + ) : ( + + {({ loading, panelData }) => { + return this.renderPanel(loading, panelData, width, height); + }} + + )} ); }} From 530a370379d3d4ea8c88178acfc8fbca0dc7285c Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Tue, 12 Feb 2019 16:06:02 +0100 Subject: [PATCH 03/10] bubble error from datapanel to panelchrome --- .../features/dashboard/dashgrid/DataPanel.tsx | 76 ++++--------------- .../dashboard/dashgrid/PanelChrome.tsx | 76 ++++++++++++------- 2 files changed, 64 insertions(+), 88 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/DataPanel.tsx b/public/app/features/dashboard/dashgrid/DataPanel.tsx index 2183548000b..84fb4a3a961 100644 --- a/public/app/features/dashboard/dashgrid/DataPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DataPanel.tsx @@ -1,8 +1,6 @@ // Library import React, { Component } from 'react'; -import { Tooltip } from '@grafana/ui'; -import ErrorBoundary from 'app/core/components/ErrorBoundary/ErrorBoundary'; // Services import { DatasourceSrv, getDatasourceSrv } from 'app/features/plugins/datasource_srv'; // Utils @@ -18,8 +16,6 @@ import { TimeSeries, } from '@grafana/ui'; -const DEFAULT_PLUGIN_ERROR = 'Error in plugin'; - interface RenderProps { loading: LoadingState; panelData: PanelData; @@ -38,12 +34,12 @@ export interface Props { maxDataPoints?: number; children: (r: RenderProps) => JSX.Element; onDataResponse?: (data: DataQueryResponse) => void; + onError?: (errorMessage: string) => void; } export interface State { isFirstLoad: boolean; loading: LoadingState; - errorMessage: string; response: DataQueryResponse; } @@ -62,7 +58,6 @@ export class DataPanel extends Component { this.state = { loading: LoadingState.NotStarted, - errorMessage: '', response: { data: [], }, @@ -112,7 +107,7 @@ export class DataPanel extends Component { return; } - this.setState({ loading: LoadingState.Loading, errorMessage: '' }); + this.setState({ loading: LoadingState.Loading }); try { const ds = await this.dataSourceSrv.get(datasource); @@ -152,19 +147,13 @@ export class DataPanel extends Component { }); } catch (err) { console.log('Loading error', err); - this.onError('Request Error'); + this.setState({ isFirstLoad: false }); + this.props.onError('Request Error'); } }; - onError = (errorMessage: string) => { - if (this.state.loading !== LoadingState.Error || this.state.errorMessage !== errorMessage) { - this.setState({ - loading: LoadingState.Error, - isFirstLoad: false, - errorMessage: errorMessage, - }); - } - }; + // error som callback eller renderprop? + // ta bort error, bubbla till panelchrome getPanelData = () => { const { response } = this.state; @@ -189,7 +178,11 @@ export class DataPanel extends Component { const panelData = this.getPanelData(); if (isFirstLoad && loading === LoadingState.Loading) { - return this.renderLoadingStates(); + return ( +
+ +
+ ); } if (!queries.length) { @@ -200,48 +193,9 @@ export class DataPanel extends Component { ); } - return ( - <> - {this.renderLoadingStates()} - - {({ error, errorInfo }) => { - if (errorInfo) { - this.onError(error.message || DEFAULT_PLUGIN_ERROR); - return null; - } - return ( - <> - {this.props.children({ - loading, - panelData, - })} - - ); - }} - - - ); - } - - private renderLoadingStates(): JSX.Element { - const { loading, errorMessage } = this.state; - if (loading === LoadingState.Loading) { - return ( -
- -
- ); - } else if (loading === LoadingState.Error) { - return ( - -
- - -
-
- ); - } - - return null; + return this.props.children({ + loading, + panelData, + }); } } diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index 3be9c361a83..f72fa9e95c7 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -8,6 +8,7 @@ import { getTimeSrv, TimeSrv } from '../services/TimeSrv'; // Components import { PanelHeader } from './PanelHeader/PanelHeader'; import { DataPanel } from './DataPanel'; +import ErrorBoundary from '../../../core/components/ErrorBoundary/ErrorBoundary'; // Utils import { applyPanelTimeOverrides, snapshotDataToPanelData } from 'app/features/dashboard/utils/panel'; @@ -17,11 +18,12 @@ import { profiler } from 'app/core/profiler'; // Types import { DashboardModel, PanelModel } from '../state'; import { PanelPlugin } from 'app/types'; -import { TimeRange, LoadingState, PanelData } from '@grafana/ui'; +import { DataQueryResponse, TimeRange, LoadingState, PanelData } from '@grafana/ui'; import variables from 'sass/_variables.scss'; import templateSrv from 'app/features/templating/template_srv'; -import { DataQueryResponse } from '@grafana/ui/src'; + +const DEFAULT_PLUGIN_ERROR = 'Error in plugin'; export interface Props { panel: PanelModel; @@ -34,6 +36,8 @@ export interface State { renderCounter: number; timeInfo?: string; timeRange?: TimeRange; + loading: LoadingState; + errorMessage: string; } export class PanelChrome extends PureComponent { @@ -45,6 +49,8 @@ export class PanelChrome extends PureComponent { this.state = { refreshCounter: 0, renderCounter: 0, + loading: LoadingState.NotStarted, + errorMessage: '', }; } @@ -127,33 +133,41 @@ export class PanelChrome extends PureComponent { const { datasource, targets } = panel; return ( <> - {panel.snapshotData && panel.snapshotData.length > 0 ? ( - this.renderPanelPlugin(LoadingState.Done, snapshotDataToPanelData(panel), width, height) - ) : ( - <> - {plugin.noQueries ? - this.renderPanelPlugin(LoadingState.Done, null, width, height) - : ( - - {({ loading, panelData }) => { - return this.renderPanelPlugin(loading, panelData, width, height); - }} - - )} - - )} + {panel.snapshotData && panel.snapshotData.length > 0 ? ( + this.renderPanelPlugin(LoadingState.Done, snapshotDataToPanelData(panel), width, height) + ) : ( + <> + {plugin.noQueries ? ( + this.renderPanelPlugin(LoadingState.Done, null, width, height) + ) : ( + + {({ loading, panelData }) => { + return this.renderPanelPlugin(loading, panelData, width, height); + }} + + )} + + )} ); - } + }; + onError = (errorMessage: string) => { + if (this.state.loading !== LoadingState.Error || this.state.errorMessage !== errorMessage) { + this.setState({ + loading: LoadingState.Error, + errorMessage: errorMessage, + }); + } + }; render() { const { dashboard, panel } = this.props; @@ -179,7 +193,15 @@ export class PanelChrome extends PureComponent { scopedVars={panel.scopedVars} links={panel.links} /> - {this.renderHelper(width, height)} + + {({ error, errorInfo }) => { + if (errorInfo) { + this.onError(error.message || DEFAULT_PLUGIN_ERROR); + return null; + } + return this.renderHelper(width, height); + }} + ); }} From 9f88a22b4c9d4a7250ecb86ea3ed286ca78a8f78 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Tue, 12 Feb 2019 16:14:26 +0100 Subject: [PATCH 04/10] remove comments --- public/app/features/dashboard/dashgrid/DataPanel.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/DataPanel.tsx b/public/app/features/dashboard/dashgrid/DataPanel.tsx index 075a4d1c864..eeb9fd2f0f1 100644 --- a/public/app/features/dashboard/dashgrid/DataPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DataPanel.tsx @@ -151,9 +151,6 @@ export class DataPanel extends Component { } }; - // error som callback eller renderprop? - // ta bort error, bubbla till panelchrome - getPanelData = () => { const { response } = this.state; From 1c5118748a1af0b0edee537dacdbd4be62726ca4 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Tue, 12 Feb 2019 17:01:07 +0100 Subject: [PATCH 05/10] implement show error in panelcorner --- .../features/dashboard/dashgrid/DataPanel.tsx | 3 +- .../dashboard/dashgrid/PanelChrome.tsx | 19 ++++---- .../dashgrid/PanelHeader/PanelHeader.tsx | 4 +- .../PanelHeader/PanelHeaderCorner.tsx | 47 ++++++++++--------- 4 files changed, 40 insertions(+), 33 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/DataPanel.tsx b/public/app/features/dashboard/dashgrid/DataPanel.tsx index eeb9fd2f0f1..80c49b15494 100644 --- a/public/app/features/dashboard/dashgrid/DataPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DataPanel.tsx @@ -34,7 +34,6 @@ export interface Props { maxDataPoints?: number; children: (r: RenderProps) => JSX.Element; onDataResponse?: (data: DataQueryResponse) => void; - onError?: (errorMessage: string) => void; } export interface State { @@ -147,7 +146,7 @@ export class DataPanel extends Component { } catch (err) { console.log('Loading error', err); this.setState({ isFirstLoad: false }); - this.props.onError('Request Error'); + throw new Error('Request Error'); } }; diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index aeb2122be34..d61e5266790 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -119,6 +119,15 @@ export class PanelChrome extends PureComponent { return this.hasPanelSnapshot ? snapshotDataToPanelData(panel) : null; } + onError = (errorMessage: string) => { + if (this.state.loading !== LoadingState.Error || this.state.errorMessage !== errorMessage) { + this.setState({ + loading: LoadingState.Error, + errorMessage: errorMessage, + }); + } + }; + renderPanelPlugin(loading: LoadingState, panelData: PanelData, width: number, height: number): JSX.Element { const { panel, plugin } = this.props; const { timeRange, renderCounter } = this.state; @@ -174,15 +183,6 @@ export class PanelChrome extends PureComponent { ); }; - onError = (errorMessage: string) => { - if (this.state.loading !== LoadingState.Error || this.state.errorMessage !== errorMessage) { - this.setState({ - loading: LoadingState.Error, - errorMessage: errorMessage, - }); - } - }; - render() { const { dashboard, panel } = this.props; const { timeInfo } = this.state; @@ -206,6 +206,7 @@ export class PanelChrome extends PureComponent { description={panel.description} scopedVars={panel.scopedVars} links={panel.links} + error={this.state.errorMessage} /> {({ error, errorInfo }) => { diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx index ebc89673387..26c9e754892 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeader.tsx @@ -18,6 +18,7 @@ export interface Props { description?: string; scopedVars?: string; links?: []; + error?: string; } interface ClickCoordinates { @@ -71,7 +72,7 @@ export class PanelHeader extends Component { const isFullscreen = false; const isLoading = false; const panelHeaderClass = classNames({ 'panel-header': true, 'grid-drag-handle': !isFullscreen }); - const { panel, dashboard, timeInfo, scopedVars } = this.props; + const { panel, dashboard, timeInfo, scopedVars, error } = this.props; const title = templateSrv.replaceWithText(panel.title, scopedVars); return ( @@ -82,6 +83,7 @@ export class PanelHeader extends Component { description={panel.description} scopedVars={panel.scopedVars} links={panel.links} + error={error} />
{isLoading && ( diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx index 159c9d92914..ad686713afc 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx @@ -18,13 +18,17 @@ interface Props { description?: string; scopedVars?: string; links?: []; + error?: string; } export class PanelHeaderCorner extends Component { timeSrv: TimeSrv = getTimeSrv(); getInfoMode = () => { - const { panel } = this.props; + const { panel, error } = this.props; + if (error) { + return InfoModes.Error; + } if (!!panel.description) { return InfoModes.Info; } @@ -42,7 +46,7 @@ export class PanelHeaderCorner extends Component { const interpolatedMarkdown = templateSrv.replace(markdown, panel.scopedVars); const remarkableInterpolatedMarkdown = new Remarkable().render(interpolatedMarkdown); - const html = ( + return (
{panel.links && @@ -62,10 +66,19 @@ export class PanelHeaderCorner extends Component { )}
); - - return html; }; + renderCornerType(infoMode: InfoModes, content: string | JSX.Element) { + return ( + +
+ + +
+
+ ); + } + render() { const infoMode: InfoModes | undefined = this.getInfoMode(); @@ -73,23 +86,15 @@ export class PanelHeaderCorner extends Component { return null; } - return ( - <> - {infoMode === InfoModes.Info || infoMode === InfoModes.Links ? ( - -
- - -
-
- ) : null} - - ); + if (infoMode === InfoModes.Error) { + return this.renderCornerType(infoMode, this.props.error); + } + + if (infoMode === InfoModes.Info) { + return this.renderCornerType(infoMode, this.getInfoContent()); + } + + return null; } } From 56f7cd96021ef55cf5d70a79f2f08b8fd0fa582a Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Wed, 13 Feb 2019 11:18:08 +0100 Subject: [PATCH 06/10] fixed issue with updatePopperPosition --- packages/grafana-ui/src/components/Tooltip/Popper.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/grafana-ui/src/components/Tooltip/Popper.tsx b/packages/grafana-ui/src/components/Tooltip/Popper.tsx index 93a896d97ed..90cfc2619e8 100644 --- a/packages/grafana-ui/src/components/Tooltip/Popper.tsx +++ b/packages/grafana-ui/src/components/Tooltip/Popper.tsx @@ -50,7 +50,7 @@ class Popper extends PureComponent { // TODO: move modifiers config to popper controller modifiers={{ preventOverflow: { enabled: true, boundariesElement: 'window' } }} > - {({ ref, style, placement, arrowProps, scheduleUpdate }) => { + {({ ref, style, placement, arrowProps }) => { return (
{ className={`${wrapperClassName}`} >
- {typeof content === 'string' - ? content - : React.cloneElement(content, { - updatePopperPosition: scheduleUpdate, - })} + {typeof content === 'string' ? content : React.cloneElement(content)} {renderArrow && renderArrow({ arrowProps, From aad558ce9cd36ad4dc962180fb47930d9520d647 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Wed, 13 Feb 2019 15:54:10 +0100 Subject: [PATCH 07/10] using error callback from datapanel instead --- .../grafana-ui/src/components/Tooltip/Popper.tsx | 12 ++++++++++-- public/app/features/dashboard/dashgrid/DataPanel.tsx | 7 +++++-- .../app/features/dashboard/dashgrid/PanelChrome.tsx | 5 +++-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/packages/grafana-ui/src/components/Tooltip/Popper.tsx b/packages/grafana-ui/src/components/Tooltip/Popper.tsx index 90cfc2619e8..cf4b9cdd653 100644 --- a/packages/grafana-ui/src/components/Tooltip/Popper.tsx +++ b/packages/grafana-ui/src/components/Tooltip/Popper.tsx @@ -35,8 +35,16 @@ interface Props extends React.HTMLAttributes { class Popper extends PureComponent { render() { - const { show, placement, onMouseEnter, onMouseLeave, className, wrapperClassName, renderArrow } = this.props; - const { content } = this.props; + const { + content, + show, + placement, + onMouseEnter, + onMouseLeave, + className, + wrapperClassName, + renderArrow, + } = this.props; return ( diff --git a/public/app/features/dashboard/dashgrid/DataPanel.tsx b/public/app/features/dashboard/dashgrid/DataPanel.tsx index 80c49b15494..75c931ec14e 100644 --- a/public/app/features/dashboard/dashgrid/DataPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DataPanel.tsx @@ -34,6 +34,7 @@ export interface Props { maxDataPoints?: number; children: (r: RenderProps) => JSX.Element; onDataResponse?: (data: DataQueryResponse) => void; + onError: (errorMessage: string) => void; } export interface State { @@ -94,6 +95,7 @@ export class DataPanel extends Component { widthPixels, maxDataPoints, onDataResponse, + onError, } = this.props; if (!isVisible) { @@ -146,7 +148,9 @@ export class DataPanel extends Component { } catch (err) { console.log('Loading error', err); this.setState({ isFirstLoad: false }); - throw new Error('Request Error'); + onError(`Query error + status: ${err.status} + message: ${err.statusText}`); } }; @@ -169,7 +173,6 @@ export class DataPanel extends Component { render() { const { queries } = this.props; const { loading, isFirstLoad } = this.state; - const panelData = this.getPanelData(); if (isFirstLoad && loading === LoadingState.Loading) { diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index d61e5266790..44028f59518 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -171,6 +171,7 @@ export class PanelChrome extends PureComponent { widthPixels={width} refreshCounter={refreshCounter} onDataResponse={this.onDataResponse} + onError={this.onError} > {({ loading, panelData }) => { return this.renderPanelPlugin(loading, panelData, width, height); @@ -185,7 +186,7 @@ export class PanelChrome extends PureComponent { render() { const { dashboard, panel } = this.props; - const { timeInfo } = this.state; + const { errorMessage, timeInfo } = this.state; const { transparent } = panel; const containerClassNames = `panel-container panel-container--absolute ${transparent ? 'panel-transparent' : ''}`; @@ -206,7 +207,7 @@ export class PanelChrome extends PureComponent { description={panel.description} scopedVars={panel.scopedVars} links={panel.links} - error={this.state.errorMessage} + error={errorMessage} /> {({ error, errorInfo }) => { From 2bc26a01f98cd77e5dc8159ce841cea93a317b03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 14 Feb 2019 15:18:55 +0100 Subject: [PATCH 08/10] Fixes to error handling and clearing, also publishing of legacy events so old query editors work with react panels fully --- packages/grafana-ui/src/types/datasource.ts | 10 +++ public/app/core/services/AngularLoader.ts | 4 +- .../features/dashboard/dashgrid/DataPanel.tsx | 20 +++-- .../dashboard/dashgrid/PanelChrome.tsx | 46 ++++++++---- .../PanelHeader/PanelHeaderCorner.tsx | 19 ++--- .../dashboard/panel_editor/QueryEditorRow.tsx | 75 ++++++++++++------- 6 files changed, 114 insertions(+), 60 deletions(-) diff --git a/packages/grafana-ui/src/types/datasource.ts b/packages/grafana-ui/src/types/datasource.ts index e34cf25dc01..a34f39b59c6 100644 --- a/packages/grafana-ui/src/types/datasource.ts +++ b/packages/grafana-ui/src/types/datasource.ts @@ -29,6 +29,16 @@ export interface DataQuery { datasource?: string | null; } +export interface DataQueryError { + data?: { + message?: string; + error?: string; + }; + message?: string; + status?: string; + statusText?: string; +} + export interface DataQueryOptions { timezone: string; range: TimeRange; diff --git a/public/app/core/services/AngularLoader.ts b/public/app/core/services/AngularLoader.ts index 54dd9a35767..d9b78e66cba 100644 --- a/public/app/core/services/AngularLoader.ts +++ b/public/app/core/services/AngularLoader.ts @@ -27,7 +27,9 @@ export class AngularLoader { compiledElem.remove(); }, digest: () => { - scope.$digest(); + if (!scope.$$phase) { + scope.$digest(); + } }, getScope: () => { return scope; diff --git a/public/app/features/dashboard/dashgrid/DataPanel.tsx b/public/app/features/dashboard/dashgrid/DataPanel.tsx index 75c931ec14e..c8d1947f892 100644 --- a/public/app/features/dashboard/dashgrid/DataPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DataPanel.tsx @@ -9,6 +9,7 @@ import kbn from 'app/core/utils/kbn'; import { DataQueryOptions, DataQueryResponse, + DataQueryError, LoadingState, PanelData, TableData, @@ -34,7 +35,7 @@ export interface Props { maxDataPoints?: number; children: (r: RenderProps) => JSX.Element; onDataResponse?: (data: DataQueryResponse) => void; - onError: (errorMessage: string) => void; + onError: (message: string, error: DataQueryError) => void; } export interface State { @@ -146,11 +147,20 @@ export class DataPanel extends Component { isFirstLoad: false, }); } catch (err) { - console.log('Loading error', err); + console.log('DataPanel error', err); + + let message = 'Query error'; + + if (err.message) { + message = err.message; + } else if (err.data && err.data.message) { + message = err.data.message; + } else if (err.data && err.data.error) { + message = err.data.error; + } + + onError(message, err); this.setState({ isFirstLoad: false }); - onError(`Query error - status: ${err.status} - message: ${err.statusText}`); } }; diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index db5a6c62483..5a993293946 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -18,7 +18,7 @@ import { profiler } from 'app/core/profiler'; // Types import { DashboardModel, PanelModel } from '../state'; import { PanelPlugin } from 'app/types'; -import { DataQueryResponse, TimeRange, LoadingState, PanelData } from '@grafana/ui'; +import { DataQueryResponse, TimeRange, LoadingState, PanelData, DataQueryError } from '@grafana/ui'; import variables from 'sass/_variables.scss'; import templateSrv from 'app/features/templating/template_srv'; @@ -36,8 +36,7 @@ export interface State { renderCounter: number; timeInfo?: string; timeRange?: TimeRange; - loading: LoadingState; - errorMessage: string; + errorMessage: string | null; } export class PanelChrome extends PureComponent { @@ -49,8 +48,7 @@ export class PanelChrome extends PureComponent { this.state = { refreshCounter: 0, renderCounter: 0, - loading: LoadingState.NotStarted, - errorMessage: '', + errorMessage: null, }; } @@ -94,8 +92,33 @@ export class PanelChrome extends PureComponent { if (this.props.dashboard.isSnapshot()) { this.props.panel.snapshotData = dataQueryResponse.data; } + // clear error state (if any) + this.clearErrorState(); + + // This event is used by old query editors and panel editor options + this.props.panel.events.emit('data-received', dataQueryResponse.data); }; + onDataError = (message: string, error: DataQueryError) => { + if (this.state.errorMessage !== message) { + this.setState({ errorMessage: message }); + } + // this event is used by old query editors + this.props.panel.events.emit('data-error', error); + }; + + onPanelError = (message: string) => { + if (this.state.errorMessage !== message) { + this.setState({ errorMessage: message }); + } + }; + + clearErrorState() { + if (this.state.errorMessage) { + this.setState({ errorMessage: null }); + } + } + get isVisible() { return !this.props.dashboard.otherPanelInFullscreen(this.props.panel); } @@ -113,15 +136,6 @@ export class PanelChrome extends PureComponent { return this.hasPanelSnapshot ? snapshotDataToPanelData(this.props.panel) : null; } - onError = (errorMessage: string) => { - if (this.state.loading !== LoadingState.Error || this.state.errorMessage !== errorMessage) { - this.setState({ - loading: LoadingState.Error, - errorMessage: errorMessage, - }); - } - }; - renderPanelPlugin(loading: LoadingState, panelData: PanelData, width: number, height: number): JSX.Element { const { panel, plugin } = this.props; const { timeRange, renderCounter } = this.state; @@ -165,7 +179,7 @@ export class PanelChrome extends PureComponent { widthPixels={width} refreshCounter={refreshCounter} onDataResponse={this.onDataResponse} - onError={this.onError} + onError={this.onDataError} > {({ loading, panelData }) => { return this.renderPanelPlugin(loading, panelData, width, height); @@ -206,7 +220,7 @@ export class PanelChrome extends PureComponent { {({ error, errorInfo }) => { if (errorInfo) { - this.onError(error.message || DEFAULT_PLUGIN_ERROR); + this.onPanelError(error.message || DEFAULT_PLUGIN_ERROR); return null; } return this.renderPanelBody(width, height); diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx index ad686713afc..ef2fb69358d 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx @@ -6,7 +6,7 @@ import templateSrv from 'app/features/templating/template_srv'; import { LinkSrv } from 'app/features/panel/panellinks/link_srv'; import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv'; -enum InfoModes { +enum InfoMode { Error = 'Error', Info = 'Info', Links = 'Links', @@ -27,13 +27,13 @@ export class PanelHeaderCorner extends Component { getInfoMode = () => { const { panel, error } = this.props; if (error) { - return InfoModes.Error; + return InfoMode.Error; } if (!!panel.description) { - return InfoModes.Info; + return InfoMode.Info; } if (panel.links && panel.links.length) { - return InfoModes.Links; + return InfoMode.Links; } return undefined; @@ -68,9 +68,10 @@ export class PanelHeaderCorner extends Component { ); }; - renderCornerType(infoMode: InfoModes, content: string | JSX.Element) { + renderCornerType(infoMode: InfoMode, content: string | JSX.Element) { + const theme = infoMode === InfoMode.Error ? 'error' : 'info'; return ( - +
@@ -80,17 +81,17 @@ export class PanelHeaderCorner extends Component { } render() { - const infoMode: InfoModes | undefined = this.getInfoMode(); + const infoMode: InfoMode | undefined = this.getInfoMode(); if (!infoMode) { return null; } - if (infoMode === InfoModes.Error) { + if (infoMode === InfoMode.Error) { return this.renderCornerType(infoMode, this.props.error); } - if (infoMode === InfoModes.Info) { + if (infoMode === InfoMode.Info) { return this.renderCornerType(infoMode, this.getInfoContent()); } diff --git a/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx b/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx index cf89175a5ee..0b8d2c39908 100644 --- a/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx +++ b/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx @@ -28,28 +28,57 @@ interface State { loadedDataSourceValue: string | null | undefined; datasource: DataSourceApi | null; isCollapsed: boolean; - angularScope: AngularQueryComponentScope | null; + hasTextEditMode: boolean; } export class QueryEditorRow extends PureComponent { element: HTMLElement | null = null; + angularScope: AngularQueryComponentScope | null; angularQueryEditor: AngularComponent | null = null; state: State = { datasource: null, isCollapsed: false, - angularScope: null, loadedDataSourceValue: undefined, + hasTextEditMode: false, }; componentDidMount() { this.loadDatasource(); this.props.panel.events.on('refresh', this.onPanelRefresh); + this.props.panel.events.on('data-error', this.onPanelDataError); + this.props.panel.events.on('data-received', this.onPanelDataReceived); } + componentWillUnmount() { + this.props.panel.events.off('refresh', this.onPanelRefresh); + this.props.panel.events.off('data-error', this.onPanelDataError); + this.props.panel.events.off('data-received', this.onPanelDataReceived); + + if (this.angularQueryEditor) { + this.angularQueryEditor.destroy(); + } + } + + onPanelDataError = () => { + // Some query controllers listen to data error events and need a digest + if (this.angularQueryEditor) { + // for some reason this needs to be done in next tick + setTimeout(this.angularQueryEditor.digest); + } + }; + + onPanelDataReceived = () => { + // Some query controllers listen to data error events and need a digest + if (this.angularQueryEditor) { + // for some reason this needs to be done in next tick + setTimeout(this.angularQueryEditor.digest); + } + }; + onPanelRefresh = () => { - if (this.state.angularScope) { - this.state.angularScope.range = getTimeSrv().timeRange(); + if (this.angularScope) { + this.angularScope.range = getTimeSrv().timeRange(); } }; @@ -73,7 +102,11 @@ export class QueryEditorRow extends PureComponent { const dataSourceSrv = getDatasourceSrv(); const datasource = await dataSourceSrv.get(query.datasource || panel.datasource); - this.setState({ datasource, loadedDataSourceValue: this.props.dataSourceValue }); + this.setState({ + datasource, + loadedDataSourceValue: this.props.dataSourceValue, + hasTextEditMode: false, + }); } componentDidUpdate() { @@ -98,21 +131,14 @@ export class QueryEditorRow extends PureComponent { const scopeProps = { ctrl: this.getAngularQueryComponentScope() }; this.angularQueryEditor = loader.load(this.element, scopeProps, template); + this.angularScope = scopeProps.ctrl; // give angular time to compile setTimeout(() => { - this.setState({ angularScope: scopeProps.ctrl }); + this.setState({ hasTextEditMode: !!this.angularScope.toggleEditorMode }); }, 10); } - componentWillUnmount() { - this.props.panel.events.off('refresh', this.onPanelRefresh); - - if (this.angularQueryEditor) { - this.angularQueryEditor.destroy(); - } - } - onToggleCollapse = () => { this.setState({ isCollapsed: !this.state.isCollapsed }); }; @@ -138,10 +164,8 @@ export class QueryEditorRow extends PureComponent { } onToggleEditMode = () => { - const { angularScope } = this.state; - - if (angularScope && angularScope.toggleEditorMode) { - angularScope.toggleEditorMode(); + if (this.angularScope && this.angularScope.toggleEditorMode) { + this.angularScope.toggleEditorMode(); this.angularQueryEditor.digest(); } @@ -150,11 +174,6 @@ export class QueryEditorRow extends PureComponent { } }; - get hasTextEditMode() { - const { angularScope } = this.state; - return angularScope && angularScope.toggleEditorMode; - } - onRemoveQuery = () => { this.props.onRemoveQuery(this.props.query); }; @@ -171,10 +190,8 @@ export class QueryEditorRow extends PureComponent { }; renderCollapsedText(): string | null { - const { angularScope } = this.state; - - if (angularScope && angularScope.getCollapsedText) { - return angularScope.getCollapsedText(); + if (this.angularScope && this.angularScope.getCollapsedText) { + return this.angularScope.getCollapsedText(); } return null; @@ -182,7 +199,7 @@ export class QueryEditorRow extends PureComponent { render() { const { query, inMixedMode } = this.props; - const { datasource, isCollapsed } = this.state; + const { datasource, isCollapsed, hasTextEditMode } = this.state; const isDisabled = query.hide; const bodyClasses = classNames('query-editor-row__body gf-form-query', { @@ -212,7 +229,7 @@ export class QueryEditorRow extends PureComponent { {isCollapsed &&
{this.renderCollapsedText()}
}
- {this.hasTextEditMode && ( + {hasTextEditMode && (