Commit 92ab4d80 by Ryan McKinley Committed by GitHub

Error Handling: support errors and data in a response (#20169)

parent 7a3d1c0e
......@@ -377,6 +377,11 @@ export interface DataQueryResponse {
key?: string;
/**
* Optionally include error info along with the response data
*/
error?: DataQueryError;
/**
* Use this to control which state the response should have
* Defaults to LoadingState.Done if state is not defined
*/
......
......@@ -284,6 +284,26 @@ func init() {
})
registerScenario(&Scenario{
Id: "random_walk_with_error",
Name: "Random Walk (with error)",
Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
queryRes := getRandomWalk(query, context)
queryRes.ErrorString = "This is an error. It can include URLs http://grafana.com/"
return queryRes
},
})
registerScenario(&Scenario{
Id: "server_error_500",
Name: "Server Error (500)",
Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
panic("Test Data Panic!")
},
})
registerScenario(&Scenario{
Id: "logs",
Name: "Logs",
......
......@@ -6,6 +6,7 @@ import { Tooltip, PopoverContent } from '@grafana/ui';
import { PanelModel } from 'app/features/dashboard/state/PanelModel';
import templateSrv from 'app/features/templating/template_srv';
import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { getLocationSrv } from '@grafana/runtime';
enum InfoMode {
Error = 'Error',
......@@ -68,11 +69,18 @@ export class PanelHeaderCorner extends Component<Props> {
);
};
renderCornerType(infoMode: InfoMode, content: PopoverContent) {
/**
* Open the Panel Inspector when we click on an error
*/
onClickError = () => {
getLocationSrv().update({ partial: true, query: { inspect: this.props.panel.id } });
};
renderCornerType(infoMode: InfoMode, content: PopoverContent, onClick?: () => void) {
const theme = infoMode === InfoMode.Error ? 'error' : 'info';
return (
<Tooltip content={content} placement="top-start" theme={theme}>
<div className={`panel-info-corner panel-info-corner--${infoMode.toLowerCase()}`}>
<div className={`panel-info-corner panel-info-corner--${infoMode.toLowerCase()}`} onClick={onClick}>
<i className="fa" />
<span className="panel-info-corner-inner" />
</div>
......@@ -88,7 +96,7 @@ export class PanelHeaderCorner extends Component<Props> {
}
if (infoMode === InfoMode.Error) {
return this.renderCornerType(infoMode, this.props.error);
return this.renderCornerType(infoMode, this.props.error, this.onClickError);
}
if (infoMode === InfoMode.Info || infoMode === InfoMode.Links) {
......
......@@ -38,6 +38,9 @@ export function processResponsePacket(packet: DataQueryResponse, state: RunningQ
packets[packet.key || 'A'] = packet;
let loadingState = packet.state || LoadingState.Done;
let error: DataQueryError | undefined = undefined;
// Update the time range
const range = { ...request.range };
const timeRange = isString(range.raw.from)
......@@ -50,13 +53,18 @@ export function processResponsePacket(packet: DataQueryResponse, state: RunningQ
const combinedData = flatten(
lodashMap(packets, (packet: DataQueryResponse) => {
if (packet.error) {
loadingState = LoadingState.Error;
error = packet.error;
}
return packet.data;
})
);
const panelData = {
state: packet.state || LoadingState.Done,
state: loadingState,
series: combinedData,
error,
request,
timeRange,
};
......
......@@ -108,18 +108,15 @@ class MetricsPanelCtrl extends PanelCtrl {
this.loading = false;
this.error = err.message || 'Request Error';
this.inspector = { error: err };
if (err.data) {
if (err.data.message) {
this.error = err.data.message;
}
if (err.data.error) {
} else if (err.data.error) {
this.error = err.data.error;
}
}
console.log('Panel data error:', err);
return this.$timeout(() => {
this.events.emit(PanelEvents.dataError, err);
});
......@@ -131,7 +128,10 @@ class MetricsPanelCtrl extends PanelCtrl {
if (data.state === LoadingState.Error) {
this.loading = false;
this.processDataError(data.error);
return;
if (!data.series) {
// keep current data if the response is empty
return;
}
}
// Ignore data in loading state
......
......@@ -31,7 +31,6 @@ export class PanelCtrl {
$injector: auto.IInjectorService;
$location: any;
$timeout: any;
inspector: any;
editModeInitiated: boolean;
height: any;
containerHeight: any;
......
......@@ -5,6 +5,7 @@ import Drop from 'tether-drop';
// @ts-ignore
import baron from 'baron';
import { PanelEvents } from '@grafana/data';
import { getLocationSrv } from '@grafana/runtime';
const module = angular.module('grafana.directives');
......@@ -67,6 +68,12 @@ module.directive('grafanaPanel', ($rootScope, $document, $timeout) => {
}
}
function infoCornerClicked() {
if (ctrl.error) {
getLocationSrv().update({ partial: true, query: { inspect: ctrl.panel.id } });
}
}
// set initial transparency
if (ctrl.panel.transparent) {
transparentLastState = true;
......@@ -200,6 +207,8 @@ module.directive('grafanaPanel', ($rootScope, $document, $timeout) => {
elem.on('mouseenter', mouseEnter);
elem.on('mouseleave', mouseLeave);
cornerInfoElem.on('click', infoCornerClicked);
scope.$on('$destroy', () => {
elem.off();
cornerInfoElem.off();
......
......@@ -6,6 +6,7 @@ import {
MetricFindValue,
TableData,
TimeSeries,
DataQueryError,
} from '@grafana/data';
import { Scenario, TestDataQuery } from './types';
import { getBackendSrv } from 'app/core/services/backend_srv';
......@@ -67,6 +68,7 @@ export class TestDataDataSource extends DataSourceApi<TestDataQuery> {
processQueryResult(queries: any, res: any): DataQueryResponse {
const data: TestData[] = [];
let error: DataQueryError | undefined = undefined;
for (const query of queries) {
const results = res.data.results[query.refId];
......@@ -81,9 +83,15 @@ export class TestDataDataSource extends DataSourceApi<TestDataQuery> {
for (const series of results.series || []) {
data.push({ target: series.name, datapoints: series.points, refId: query.refId, tags: series.tags });
}
if (results.error) {
error = {
message: results.error,
};
}
}
return { data };
return { data, error };
}
annotationQuery(options: any) {
......
......@@ -480,13 +480,11 @@ class GraphElement {
this.plot = $.plot(this.elem, this.sortedSeries, options);
if (this.ctrl.renderError) {
delete this.ctrl.error;
delete this.ctrl.inspector;
}
} catch (e) {
console.log('flotcharts error', e);
this.ctrl.error = e.message || 'Render Error';
this.ctrl.renderError = true;
this.ctrl.inspector = { error: e };
}
if (incrementRenderCounter) {
......
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