mirror of
https://github.com/grafana/grafana.git
synced 2025-02-09 23:16:16 -06:00
Error Handling: support errors and data in a response (#20169)
This commit is contained in:
parent
7a3d1c0e4b
commit
92ab4d80f0
@ -376,6 +376,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
|
||||
|
@ -283,6 +283,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) {
|
||||
|
Loading…
Reference in New Issue
Block a user