Commit a9808ef5 by Torkel Ödegaard Committed by GitHub

Merge pull request #14763 from grafana/14528-panel-errors

Panel errors
parents 023e4961 12a3edd6
...@@ -98,6 +98,7 @@ export class Graph extends PureComponent<GraphProps> { ...@@ -98,6 +98,7 @@ export class Graph extends PureComponent<GraphProps> {
$.plot(this.element, timeSeries, flotOptions); $.plot(this.element, timeSeries, flotOptions);
} catch (err) { } catch (err) {
console.log('Graph rendering error', err, flotOptions, timeSeries); console.log('Graph rendering error', err, flotOptions, timeSeries);
throw new Error('Error rendering panel');
} }
} }
......
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<Props, State> {
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;
...@@ -3,6 +3,11 @@ import Portal from 'app/core/components/Portal/Portal'; ...@@ -3,6 +3,11 @@ import Portal from 'app/core/components/Portal/Portal';
import { Manager, Popper as ReactPopper, Reference } from 'react-popper'; import { Manager, Popper as ReactPopper, Reference } from 'react-popper';
import Transition from 'react-transition-group/Transition'; import Transition from 'react-transition-group/Transition';
export enum Themes {
Default = 'popper__background--default',
Error = 'popper__background--error',
}
const defaultTransitionStyles = { const defaultTransitionStyles = {
transition: 'opacity 200ms linear', transition: 'opacity 200ms linear',
opacity: 0, opacity: 0,
...@@ -21,13 +26,16 @@ interface Props { ...@@ -21,13 +26,16 @@ interface Props {
placement?: any; placement?: any;
content: string | ((props: any) => JSX.Element); content: string | ((props: any) => JSX.Element);
refClassName?: string; refClassName?: string;
theme?: Themes;
} }
class Popper extends PureComponent<Props> { class Popper extends PureComponent<Props> {
render() { render() {
const { children, renderContent, show, placement, refClassName } = this.props; const { children, renderContent, show, placement, refClassName, theme } = this.props;
const { content } = this.props; const { content } = this.props;
const popperBackgroundClassName = 'popper__background' + (theme ? ' ' + theme : '');
return ( return (
<Manager> <Manager>
<Reference> <Reference>
...@@ -53,7 +61,7 @@ class Popper extends PureComponent<Props> { ...@@ -53,7 +61,7 @@ class Popper extends PureComponent<Props> {
data-placement={placement} data-placement={placement}
className="popper" className="popper"
> >
<div className="popper__background"> <div className={popperBackgroundClassName}>
{renderContent(content)} {renderContent(content)}
<div ref={arrowProps.ref} data-placement={placement} className="popper__arrow" /> <div ref={arrowProps.ref} data-placement={placement} className="popper__arrow" />
</div> </div>
......
import React from 'react'; import React from 'react';
import { Themes } from './Popper';
export interface UsingPopperProps { export interface UsingPopperProps {
showPopper: (prevState: object) => void; showPopper: (prevState: object) => void;
hidePopper: (prevState: object) => void; hidePopper: (prevState: object) => void;
...@@ -9,6 +9,7 @@ export interface UsingPopperProps { ...@@ -9,6 +9,7 @@ export interface UsingPopperProps {
content: string | ((props: any) => JSX.Element); content: string | ((props: any) => JSX.Element);
className?: string; className?: string;
refClassName?: string; refClassName?: string;
theme?: Themes;
} }
interface Props { interface Props {
...@@ -16,6 +17,7 @@ interface Props { ...@@ -16,6 +17,7 @@ interface Props {
className?: string; className?: string;
refClassName?: string; refClassName?: string;
content: string | ((props: any) => JSX.Element); content: string | ((props: any) => JSX.Element);
theme?: Themes;
} }
interface State { interface State {
...@@ -71,7 +73,6 @@ export default function withPopper(WrappedComponent) { ...@@ -71,7 +73,6 @@ export default function withPopper(WrappedComponent) {
render() { render() {
const { show, placement } = this.state; const { show, placement } = this.state;
const className = this.props.className || ''; const className = this.props.className || '';
return ( return (
<WrappedComponent <WrappedComponent
{...this.props} {...this.props}
......
// Library // Library
import React, { Component } from 'react'; import React, { Component } from 'react';
import Tooltip from 'app/core/components/Tooltip/Tooltip';
import ErrorBoundary from 'app/core/components/ErrorBoundary/ErrorBoundary';
// Services // Services
import { getDatasourceSrv, DatasourceSrv } from 'app/features/plugins/datasource_srv'; import { getDatasourceSrv, DatasourceSrv } from 'app/features/plugins/datasource_srv';
...@@ -10,6 +12,9 @@ import kbn from 'app/core/utils/kbn'; ...@@ -10,6 +12,9 @@ import kbn from 'app/core/utils/kbn';
// Types // Types
import { DataQueryOptions, DataQueryResponse } from 'app/types'; import { DataQueryOptions, DataQueryResponse } from 'app/types';
import { TimeRange, TimeSeries, LoadingState } from '@grafana/ui'; import { TimeRange, TimeSeries, LoadingState } from '@grafana/ui';
import { Themes } from 'app/core/components/Tooltip/Popper';
const DEFAULT_PLUGIN_ERROR = 'Error in plugin';
interface RenderProps { interface RenderProps {
loading: LoadingState; loading: LoadingState;
...@@ -33,6 +38,7 @@ export interface Props { ...@@ -33,6 +38,7 @@ export interface Props {
export interface State { export interface State {
isFirstLoad: boolean; isFirstLoad: boolean;
loading: LoadingState; loading: LoadingState;
errorMessage: string;
response: DataQueryResponse; response: DataQueryResponse;
} }
...@@ -51,6 +57,7 @@ export class DataPanel extends Component<Props, State> { ...@@ -51,6 +57,7 @@ export class DataPanel extends Component<Props, State> {
this.state = { this.state = {
loading: LoadingState.NotStarted, loading: LoadingState.NotStarted,
errorMessage: '',
response: { response: {
data: [], data: [],
}, },
...@@ -90,7 +97,7 @@ export class DataPanel extends Component<Props, State> { ...@@ -90,7 +97,7 @@ export class DataPanel extends Component<Props, State> {
return; return;
} }
this.setState({ loading: LoadingState.Loading }); this.setState({ loading: LoadingState.Loading, errorMessage: '' });
try { try {
const ds = await this.dataSourceSrv.get(datasource); const ds = await this.dataSourceSrv.get(datasource);
...@@ -128,10 +135,20 @@ export class DataPanel extends Component<Props, State> { ...@@ -128,10 +135,20 @@ export class DataPanel extends Component<Props, State> {
}); });
} catch (err) { } catch (err) {
console.log('Loading error', 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() { render() {
const { queries } = this.props; const { queries } = this.props;
const { response, loading, isFirstLoad } = this.state; const { response, loading, isFirstLoad } = this.state;
...@@ -139,7 +156,7 @@ export class DataPanel extends Component<Props, State> { ...@@ -139,7 +156,7 @@ export class DataPanel extends Component<Props, State> {
const timeSeries = response.data; const timeSeries = response.data;
if (isFirstLoad && loading === LoadingState.Loading) { if (isFirstLoad && loading === LoadingState.Loading) {
return this.renderLoadingSpinner(); return this.renderLoadingStates();
} }
if (!queries.length) { if (!queries.length) {
...@@ -152,24 +169,48 @@ export class DataPanel extends Component<Props, State> { ...@@ -152,24 +169,48 @@ export class DataPanel extends Component<Props, State> {
return ( return (
<> <>
{this.renderLoadingSpinner()} {this.renderLoadingStates()}
{this.props.children({ <ErrorBoundary>
timeSeries, {({error, errorInfo}) => {
loading, if (errorInfo) {
})} this.onError(error.message || DEFAULT_PLUGIN_ERROR);
return null;
}
return (
<>
{this.props.children({
timeSeries,
loading,
})}
</>
);
}}
</ErrorBoundary>
</> </>
); );
} }
private renderLoadingSpinner(): JSX.Element { private renderLoadingStates(): JSX.Element {
const { loading } = this.state; const { loading, errorMessage } = this.state;
if (loading === LoadingState.Loading) { if (loading === LoadingState.Loading) {
return ( return (
<div className="panel-loading"> <div className="panel-loading">
<i className="fa fa-spinner fa-spin" /> <i className="fa fa-spinner fa-spin" />
</div> </div>
); );
} else if (loading === LoadingState.Error) {
return (
<Tooltip
content={errorMessage}
className="popper__manager--block"
refClassName={`panel-info-corner panel-info-corner--error`}
placement="bottom-start"
theme={Themes.Error}
>
<i className="fa" />
<span className="panel-info-corner-inner" />
</Tooltip>
);
} }
return null; return null;
......
...@@ -87,7 +87,6 @@ export class PanelChrome extends PureComponent<Props, State> { ...@@ -87,7 +87,6 @@ export class PanelChrome extends PureComponent<Props, State> {
const { datasource, targets, transparent } = panel; const { datasource, targets, transparent } = panel;
const PanelComponent = plugin.exports.Panel; const PanelComponent = plugin.exports.Panel;
const containerClassNames = `panel-container panel-container--absolute ${transparent ? 'panel-transparent' : ''}`; const containerClassNames = `panel-container panel-container--absolute ${transparent ? 'panel-transparent' : ''}`;
return ( return (
<AutoSizer> <AutoSizer>
{({ width, height }) => { {({ width, height }) => {
......
...@@ -10,10 +10,6 @@ import { Options } from './types'; ...@@ -10,10 +10,6 @@ import { Options } from './types';
interface Props extends PanelProps<Options> {} interface Props extends PanelProps<Options> {}
export class GraphPanel extends PureComponent<Props> { export class GraphPanel extends PureComponent<Props> {
constructor(props) {
super(props);
}
render() { render() {
const { timeSeries, timeRange, width, height } = this.props; const { timeSeries, timeRange, width, height } = this.props;
const { showLines, showBars, showPoints } = this.props.options; const { showLines, showBars, showPoints } = this.props.options;
......
...@@ -103,6 +103,7 @@ $panel-bg: #212124; ...@@ -103,6 +103,7 @@ $panel-bg: #212124;
$panel-border-color: $dark-1; $panel-border-color: $dark-1;
$panel-border: solid 1px $panel-border-color; $panel-border: solid 1px $panel-border-color;
$panel-header-hover-bg: $dark-4; $panel-header-hover-bg: $dark-4;
$panel-corner: $panel-bg;
// page header // page header
$page-header-bg: linear-gradient(90deg, #292a2d, black); $page-header-bg: linear-gradient(90deg, #292a2d, black);
...@@ -302,12 +303,14 @@ $popover-error-bg: $btn-danger-bg; ...@@ -302,12 +303,14 @@ $popover-error-bg: $btn-danger-bg;
// Tooltips and popovers // Tooltips and popovers
// ------------------------- // -------------------------
$tooltipColor: $popover-help-color; $tooltipColor: $popover-help-color;
$tooltipBackground: $popover-help-bg;
$tooltipArrowWidth: 5px; $tooltipArrowWidth: 5px;
$tooltipArrowColor: $tooltipBackground;
$tooltipLinkColor: $link-color; $tooltipLinkColor: $link-color;
$graph-tooltip-bg: $dark-1; $graph-tooltip-bg: $dark-1;
$tooltipBackground: $popover-help-bg;
$tooltipArrowColor: $tooltipBackground;
$tooltipBackgroundError: $brand-danger;
// images // images
$checkboxImageUrl: '../img/checkbox.png'; $checkboxImageUrl: '../img/checkbox.png';
......
...@@ -102,6 +102,7 @@ $panel-bg: $white; ...@@ -102,6 +102,7 @@ $panel-bg: $white;
$panel-border-color: $gray-5; $panel-border-color: $gray-5;
$panel-border: solid 1px $panel-border-color; $panel-border: solid 1px $panel-border-color;
$panel-header-hover-bg: $gray-6; $panel-header-hover-bg: $gray-6;
$panel-corner: $gray-4;
// Page header // Page header
$page-header-bg: linear-gradient(90deg, $white, $gray-7); $page-header-bg: linear-gradient(90deg, $white, $gray-7);
...@@ -307,12 +308,14 @@ $popover-error-bg: $btn-danger-bg; ...@@ -307,12 +308,14 @@ $popover-error-bg: $btn-danger-bg;
// Tooltips and popovers // Tooltips and popovers
// ------------------------- // -------------------------
$tooltipColor: $popover-help-color; $tooltipColor: $popover-help-color;
$tooltipBackground: $popover-help-bg;
$tooltipArrowWidth: 5px; $tooltipArrowWidth: 5px;
$tooltipArrowColor: $tooltipBackground;
$tooltipLinkColor: lighten($popover-help-color, 5%); $tooltipLinkColor: lighten($popover-help-color, 5%);
$graph-tooltip-bg: $gray-5; $graph-tooltip-bg: $gray-5;
$tooltipBackground: $popover-help-bg;
$tooltipArrowColor: $tooltipBackground; // Used by Angular tooltip
$tooltipBackgroundError: $brand-danger;
// images // images
$checkboxImageUrl: '../img/checkbox_white.png'; $checkboxImageUrl: '../img/checkbox_white.png';
......
...@@ -8,7 +8,22 @@ $popper-margin-from-ref: 5px; ...@@ -8,7 +8,22 @@ $popper-margin-from-ref: 5px;
text-align: center; 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; width: 0;
height: 0; height: 0;
border-style: solid; border-style: solid;
...@@ -16,17 +31,10 @@ $popper-margin-from-ref: 5px; ...@@ -16,17 +31,10 @@ $popper-margin-from-ref: 5px;
margin: 0px; margin: 0px;
} }
.popper .popper__arrow { .popper__arrow {
border-color: $tooltipBackground; 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 // Top
.popper[data-placement^='top'] { .popper[data-placement^='top'] {
padding-bottom: $popper-margin-from-ref; padding-bottom: $popper-margin-from-ref;
......
...@@ -214,7 +214,7 @@ div.flot-text { ...@@ -214,7 +214,7 @@ div.flot-text {
&--info { &--info {
display: block; display: block;
@include panel-corner-color(lighten($panel-bg, 4%)); @include panel-corner-color(lighten($panel-corner, 4%));
.fa:before { .fa:before {
content: '\f129'; content: '\f129';
} }
...@@ -222,7 +222,7 @@ div.flot-text { ...@@ -222,7 +222,7 @@ div.flot-text {
&--links { &--links {
display: block; display: block;
@include panel-corner-color(lighten($panel-bg, 4%)); @include panel-corner-color(lighten($panel-corner, 4%));
.fa { .fa {
left: 4px; left: 4px;
} }
...@@ -233,7 +233,7 @@ div.flot-text { ...@@ -233,7 +233,7 @@ div.flot-text {
&--error { &--error {
display: block; display: block;
color: $text-color; color: $white;
@include panel-corner-color($popover-error-bg); @include panel-corner-color($popover-error-bg);
.fa:before { .fa:before {
content: '\f12a'; content: '\f12a';
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment