|
|
@@ -1,5 +1,9 @@
|
|
|
// Library
|
|
|
import React, { Component } from 'react';
|
|
|
+import { Tooltip } from '@grafana/ui';
|
|
|
+import { Themes } from '@grafana/ui/src/components/Tooltip/Popper';
|
|
|
+
|
|
|
+import ErrorBoundary from 'app/core/components/ErrorBoundary/ErrorBoundary';
|
|
|
|
|
|
// Services
|
|
|
import { getDatasourceSrv, DatasourceSrv } from 'app/features/plugins/datasource_srv';
|
|
|
@@ -11,6 +15,8 @@ import kbn from 'app/core/utils/kbn';
|
|
|
import { DataQueryOptions, DataQueryResponse } from 'app/types';
|
|
|
import { TimeRange, TimeSeries, LoadingState } from '@grafana/ui';
|
|
|
|
|
|
+const DEFAULT_PLUGIN_ERROR = 'Error in plugin';
|
|
|
+
|
|
|
interface RenderProps {
|
|
|
loading: LoadingState;
|
|
|
timeSeries: TimeSeries[];
|
|
|
@@ -33,6 +39,7 @@ export interface Props {
|
|
|
export interface State {
|
|
|
isFirstLoad: boolean;
|
|
|
loading: LoadingState;
|
|
|
+ errorMessage: string;
|
|
|
response: DataQueryResponse;
|
|
|
}
|
|
|
|
|
|
@@ -51,6 +58,7 @@ export class DataPanel extends Component<Props, State> {
|
|
|
|
|
|
this.state = {
|
|
|
loading: LoadingState.NotStarted,
|
|
|
+ errorMessage: '',
|
|
|
response: {
|
|
|
data: [],
|
|
|
},
|
|
|
@@ -90,7 +98,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,7 +136,17 @@ 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,
|
|
|
+ });
|
|
|
}
|
|
|
};
|
|
|
|
|
|
@@ -139,7 +157,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 +170,44 @@ 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} placement="bottom-start" theme={Themes.Error}>
|
|
|
+ <div className="panel-info-corner panel-info-corner--error">
|
|
|
+ <i className="fa" />
|
|
|
+ <span className="panel-info-corner-inner" />
|
|
|
+ </div>
|
|
|
+ </Tooltip>
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
return null;
|