mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
@@ -98,6 +98,7 @@ export class Graph extends PureComponent<GraphProps> {
|
||||
$.plot(this.element, timeSeries, flotOptions);
|
||||
} catch (err) {
|
||||
console.log('Graph rendering error', err, flotOptions, timeSeries);
|
||||
throw new Error('Error rendering panel');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
44
public/app/core/components/ErrorBoundary/ErrorBoundary.tsx
Normal file
44
public/app/core/components/ErrorBoundary/ErrorBoundary.tsx
Normal file
@@ -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<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';
|
||||
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<Props> {
|
||||
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 (
|
||||
<Manager>
|
||||
<Reference>
|
||||
@@ -53,7 +61,7 @@ class Popper extends PureComponent<Props> {
|
||||
data-placement={placement}
|
||||
className="popper"
|
||||
>
|
||||
<div className="popper__background">
|
||||
<div className={popperBackgroundClassName}>
|
||||
{renderContent(content)}
|
||||
<div ref={arrowProps.ref} data-placement={placement} className="popper__arrow" />
|
||||
</div>
|
||||
|
||||
@@ -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 (
|
||||
<WrappedComponent
|
||||
{...this.props}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// Library
|
||||
import React, { Component } from 'react';
|
||||
import Tooltip from 'app/core/components/Tooltip/Tooltip';
|
||||
import ErrorBoundary from 'app/core/components/ErrorBoundary/ErrorBoundary';
|
||||
|
||||
// Services
|
||||
import { getDatasourceSrv, DatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
@@ -10,6 +12,9 @@ import kbn from 'app/core/utils/kbn';
|
||||
// Types
|
||||
import { DataQueryOptions, DataQueryResponse } from 'app/types';
|
||||
import { TimeRange, TimeSeries, LoadingState } from '@grafana/ui';
|
||||
import { Themes } from 'app/core/components/Tooltip/Popper';
|
||||
|
||||
const DEFAULT_PLUGIN_ERROR = 'Error in plugin';
|
||||
|
||||
interface RenderProps {
|
||||
loading: LoadingState;
|
||||
@@ -33,6 +38,7 @@ export interface Props {
|
||||
export interface State {
|
||||
isFirstLoad: boolean;
|
||||
loading: LoadingState;
|
||||
errorMessage: string;
|
||||
response: DataQueryResponse;
|
||||
}
|
||||
|
||||
@@ -51,6 +57,7 @@ export class DataPanel extends Component<Props, State> {
|
||||
|
||||
this.state = {
|
||||
loading: LoadingState.NotStarted,
|
||||
errorMessage: '',
|
||||
response: {
|
||||
data: [],
|
||||
},
|
||||
@@ -90,7 +97,7 @@ export class DataPanel extends Component<Props, State> {
|
||||
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<Props, State> {
|
||||
});
|
||||
} 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<Props, State> {
|
||||
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<Props, State> {
|
||||
|
||||
return (
|
||||
<>
|
||||
{this.renderLoadingSpinner()}
|
||||
{this.props.children({
|
||||
timeSeries,
|
||||
loading,
|
||||
})}
|
||||
{this.renderLoadingStates()}
|
||||
<ErrorBoundary>
|
||||
{({error, errorInfo}) => {
|
||||
if (errorInfo) {
|
||||
this.onError(error.message || DEFAULT_PLUGIN_ERROR);
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{this.props.children({
|
||||
timeSeries,
|
||||
loading,
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</ErrorBoundary>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
private renderLoadingSpinner(): JSX.Element {
|
||||
const { loading } = this.state;
|
||||
|
||||
private renderLoadingStates(): JSX.Element {
|
||||
const { loading, errorMessage } = this.state;
|
||||
if (loading === LoadingState.Loading) {
|
||||
return (
|
||||
<div className="panel-loading">
|
||||
<i className="fa fa-spinner fa-spin" />
|
||||
</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;
|
||||
|
||||
@@ -87,7 +87,6 @@ export class PanelChrome extends PureComponent<Props, State> {
|
||||
const { datasource, targets, transparent } = panel;
|
||||
const PanelComponent = plugin.exports.Panel;
|
||||
const containerClassNames = `panel-container panel-container--absolute ${transparent ? 'panel-transparent' : ''}`;
|
||||
|
||||
return (
|
||||
<AutoSizer>
|
||||
{({ width, height }) => {
|
||||
|
||||
@@ -10,10 +10,6 @@ import { Options } from './types';
|
||||
interface Props extends PanelProps<Options> {}
|
||||
|
||||
export class GraphPanel extends PureComponent<Props> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { timeSeries, timeRange, width, height } = this.props;
|
||||
const { showLines, showBars, showPoints } = this.props.options;
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user