diff --git a/packages/grafana-ui/src/visualizations/Graph/Graph.tsx b/packages/grafana-ui/src/visualizations/Graph/Graph.tsx index 51afb33802d..ad038cebcda 100644 --- a/packages/grafana-ui/src/visualizations/Graph/Graph.tsx +++ b/packages/grafana-ui/src/visualizations/Graph/Graph.tsx @@ -98,6 +98,7 @@ export class Graph extends PureComponent { $.plot(this.element, timeSeries, flotOptions); } catch (err) { console.log('Graph rendering error', err, flotOptions, timeSeries); + throw new Error('Error rendering panel'); } } diff --git a/public/app/core/components/ErrorBoundary/ErrorBoundary.tsx b/public/app/core/components/ErrorBoundary/ErrorBoundary.tsx new file mode 100644 index 00000000000..188750b0fef --- /dev/null +++ b/public/app/core/components/ErrorBoundary/ErrorBoundary.tsx @@ -0,0 +1,44 @@ +import { Component } from 'react'; + +interface ErrorInfo { + componentStack: string; +} + +interface RenderProps { + error: Error; + errorInfo: ErrorInfo; +} + +interface Props { + children: (r: RenderProps) => JSX.Element; +} + +interface State { + error: Error; + errorInfo: ErrorInfo; +} + +class ErrorBoundary extends Component { + readonly state: State = { + error: null, + errorInfo: null, + }; + + componentDidCatch(error: Error, errorInfo: ErrorInfo) { + this.setState({ + error: error, + errorInfo: errorInfo + }); + } + + render() { + const { children } = this.props; + const { error, errorInfo } = this.state; + return children({ + error, + errorInfo, + }); + } +} + +export default ErrorBoundary; diff --git a/public/app/core/components/Tooltip/Popper.tsx b/public/app/core/components/Tooltip/Popper.tsx index 36cf0fe837e..65ef510ba8f 100644 --- a/public/app/core/components/Tooltip/Popper.tsx +++ b/public/app/core/components/Tooltip/Popper.tsx @@ -3,6 +3,11 @@ import Portal from 'app/core/components/Portal/Portal'; import { Manager, Popper as ReactPopper, Reference } from 'react-popper'; import Transition from 'react-transition-group/Transition'; +export enum Themes { + Default = 'popper__background--default', + Error = 'popper__background--error', +} + const defaultTransitionStyles = { transition: 'opacity 200ms linear', opacity: 0, @@ -21,13 +26,16 @@ interface Props { placement?: any; content: string | ((props: any) => JSX.Element); refClassName?: string; + theme?: Themes; } class Popper extends PureComponent { render() { - const { children, renderContent, show, placement, refClassName } = this.props; + const { children, renderContent, show, placement, refClassName, theme } = this.props; const { content } = this.props; + const popperBackgroundClassName = 'popper__background' + (theme ? ' ' + theme : ''); + return ( @@ -53,7 +61,7 @@ class Popper extends PureComponent { data-placement={placement} className="popper" > -
+
{renderContent(content)}
diff --git a/public/app/core/components/Tooltip/withPopper.tsx b/public/app/core/components/Tooltip/withPopper.tsx index 4ba05937531..3766b78f0f6 100644 --- a/public/app/core/components/Tooltip/withPopper.tsx +++ b/public/app/core/components/Tooltip/withPopper.tsx @@ -1,5 +1,5 @@ import React from 'react'; - +import { Themes } from './Popper'; export interface UsingPopperProps { showPopper: (prevState: object) => void; hidePopper: (prevState: object) => void; @@ -9,6 +9,7 @@ export interface UsingPopperProps { content: string | ((props: any) => JSX.Element); className?: string; refClassName?: string; + theme?: Themes; } interface Props { @@ -16,6 +17,7 @@ interface Props { className?: string; refClassName?: string; content: string | ((props: any) => JSX.Element); + theme?: Themes; } interface State { @@ -71,7 +73,6 @@ export default function withPopper(WrappedComponent) { render() { const { show, placement } = this.state; const className = this.props.className || ''; - return ( { this.state = { loading: LoadingState.NotStarted, + errorMessage: '', response: { data: [], }, @@ -90,7 +97,7 @@ export class DataPanel extends Component { return; } - this.setState({ loading: LoadingState.Loading }); + this.setState({ loading: LoadingState.Loading, errorMessage: '' }); try { const ds = await this.dataSourceSrv.get(datasource); @@ -128,10 +135,20 @@ export class DataPanel extends Component { }); } catch (err) { console.log('Loading error', err); - this.setState({ loading: LoadingState.Error, isFirstLoad: false }); + this.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 + }); + } + } + render() { const { queries } = this.props; const { response, loading, isFirstLoad } = this.state; @@ -139,7 +156,7 @@ export class DataPanel extends Component { const timeSeries = response.data; if (isFirstLoad && loading === LoadingState.Loading) { - return this.renderLoadingSpinner(); + return this.renderLoadingStates(); } if (!queries.length) { @@ -152,24 +169,48 @@ export class DataPanel extends Component { return ( <> - {this.renderLoadingSpinner()} - {this.props.children({ - timeSeries, - loading, - })} + {this.renderLoadingStates()} + + {({error, errorInfo}) => { + if (errorInfo) { + this.onError(error.message || DEFAULT_PLUGIN_ERROR); + return null; + } + return ( + <> + {this.props.children({ + timeSeries, + loading, + })} + + ); + }} + ); } - private renderLoadingSpinner(): JSX.Element { - const { loading } = this.state; - + private renderLoadingStates(): JSX.Element { + const { loading, errorMessage } = this.state; if (loading === LoadingState.Loading) { return (
); + } else if (loading === LoadingState.Error) { + return ( + + + + + ); } return null; diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index 94719dfe6e0..84e11511453 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -87,7 +87,6 @@ export class PanelChrome extends PureComponent { const { datasource, targets, transparent } = panel; const PanelComponent = plugin.exports.Panel; const containerClassNames = `panel-container panel-container--absolute ${transparent ? 'panel-transparent' : ''}`; - return ( {({ width, height }) => { diff --git a/public/app/plugins/panel/graph2/GraphPanel.tsx b/public/app/plugins/panel/graph2/GraphPanel.tsx index a08276e5179..020c33f7d38 100644 --- a/public/app/plugins/panel/graph2/GraphPanel.tsx +++ b/public/app/plugins/panel/graph2/GraphPanel.tsx @@ -10,10 +10,6 @@ import { Options } from './types'; interface Props extends PanelProps {} export class GraphPanel extends PureComponent { - constructor(props) { - super(props); - } - render() { const { timeSeries, timeRange, width, height } = this.props; const { showLines, showBars, showPoints } = this.props.options; diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index 70db51a0fb2..ded17e6ecfe 100644 --- a/public/sass/_variables.dark.scss +++ b/public/sass/_variables.dark.scss @@ -103,6 +103,7 @@ $panel-bg: #212124; $panel-border-color: $dark-1; $panel-border: solid 1px $panel-border-color; $panel-header-hover-bg: $dark-4; +$panel-corner: $panel-bg; // page header $page-header-bg: linear-gradient(90deg, #292a2d, black); @@ -302,12 +303,14 @@ $popover-error-bg: $btn-danger-bg; // Tooltips and popovers // ------------------------- $tooltipColor: $popover-help-color; -$tooltipBackground: $popover-help-bg; $tooltipArrowWidth: 5px; -$tooltipArrowColor: $tooltipBackground; $tooltipLinkColor: $link-color; $graph-tooltip-bg: $dark-1; +$tooltipBackground: $popover-help-bg; +$tooltipArrowColor: $tooltipBackground; +$tooltipBackgroundError: $brand-danger; + // images $checkboxImageUrl: '../img/checkbox.png'; diff --git a/public/sass/_variables.light.scss b/public/sass/_variables.light.scss index 6afd087a849..1e7a2e9cfbe 100644 --- a/public/sass/_variables.light.scss +++ b/public/sass/_variables.light.scss @@ -102,6 +102,7 @@ $panel-bg: $white; $panel-border-color: $gray-5; $panel-border: solid 1px $panel-border-color; $panel-header-hover-bg: $gray-6; +$panel-corner: $gray-4; // Page header $page-header-bg: linear-gradient(90deg, $white, $gray-7); @@ -307,12 +308,14 @@ $popover-error-bg: $btn-danger-bg; // Tooltips and popovers // ------------------------- $tooltipColor: $popover-help-color; -$tooltipBackground: $popover-help-bg; $tooltipArrowWidth: 5px; -$tooltipArrowColor: $tooltipBackground; $tooltipLinkColor: lighten($popover-help-color, 5%); $graph-tooltip-bg: $gray-5; +$tooltipBackground: $popover-help-bg; +$tooltipArrowColor: $tooltipBackground; // Used by Angular tooltip +$tooltipBackgroundError: $brand-danger; + // images $checkboxImageUrl: '../img/checkbox_white.png'; diff --git a/public/sass/components/_popper.scss b/public/sass/components/_popper.scss index d869d52b92f..afa629d4043 100644 --- a/public/sass/components/_popper.scss +++ b/public/sass/components/_popper.scss @@ -8,7 +8,22 @@ $popper-margin-from-ref: 5px; text-align: center; } -.popper .popper__arrow { +.popper__background { + background: $tooltipBackground; + border-radius: $border-radius; + box-shadow: 0 0 2px rgba(0, 0, 0, 0.5); + padding: 10px; + + // Themes + &.popper__background--error { + background: $tooltipBackgroundError; + .popper__arrow { + border-color: $tooltipBackgroundError; + } + } +} + +.popper__arrow { width: 0; height: 0; border-style: solid; @@ -16,17 +31,10 @@ $popper-margin-from-ref: 5px; margin: 0px; } -.popper .popper__arrow { +.popper__arrow { border-color: $tooltipBackground; } -.popper__background { - background: $tooltipBackground; - border-radius: $border-radius; - box-shadow: 0 0 2px rgba(0, 0, 0, 0.5); - padding: 10px; -} - // Top .popper[data-placement^='top'] { padding-bottom: $popper-margin-from-ref; diff --git a/public/sass/pages/_dashboard.scss b/public/sass/pages/_dashboard.scss index 589012bff3f..a0ff9fd877c 100644 --- a/public/sass/pages/_dashboard.scss +++ b/public/sass/pages/_dashboard.scss @@ -214,7 +214,7 @@ div.flot-text { &--info { display: block; - @include panel-corner-color(lighten($panel-bg, 4%)); + @include panel-corner-color(lighten($panel-corner, 4%)); .fa:before { content: '\f129'; } @@ -222,7 +222,7 @@ div.flot-text { &--links { display: block; - @include panel-corner-color(lighten($panel-bg, 4%)); + @include panel-corner-color(lighten($panel-corner, 4%)); .fa { left: 4px; } @@ -233,7 +233,7 @@ div.flot-text { &--error { display: block; - color: $text-color; + color: $white; @include panel-corner-color($popover-error-bg); .fa:before { content: '\f12a';