diff --git a/packages/grafana-data/src/types/datasource.ts b/packages/grafana-data/src/types/datasource.ts index dac78035e7a..2529a3fdd2a 100644 --- a/packages/grafana-data/src/types/datasource.ts +++ b/packages/grafana-data/src/types/datasource.ts @@ -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 diff --git a/pkg/tsdb/testdatasource/scenarios.go b/pkg/tsdb/testdatasource/scenarios.go index 8b5ed2ba81e..26be77cede9 100644 --- a/pkg/tsdb/testdatasource/scenarios.go +++ b/pkg/tsdb/testdatasource/scenarios.go @@ -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", diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx index 507b603f267..f0e6b6d8b27 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx @@ -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 { ); }; - 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 ( -
+
@@ -88,7 +96,7 @@ export class PanelHeaderCorner extends Component { } 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) { diff --git a/public/app/features/dashboard/state/runRequest.ts b/public/app/features/dashboard/state/runRequest.ts index 85f8f54f67f..f8fb4d93f32 100644 --- a/public/app/features/dashboard/state/runRequest.ts +++ b/public/app/features/dashboard/state/runRequest.ts @@ -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, }; diff --git a/public/app/features/panel/metrics_panel_ctrl.ts b/public/app/features/panel/metrics_panel_ctrl.ts index 75d9e17f478..f0e705124e6 100644 --- a/public/app/features/panel/metrics_panel_ctrl.ts +++ b/public/app/features/panel/metrics_panel_ctrl.ts @@ -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 diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index f4c3bdd6879..6a84964709b 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -31,7 +31,6 @@ export class PanelCtrl { $injector: auto.IInjectorService; $location: any; $timeout: any; - inspector: any; editModeInitiated: boolean; height: any; containerHeight: any; diff --git a/public/app/features/panel/panel_directive.ts b/public/app/features/panel/panel_directive.ts index c5bb50b7054..56a85e8a983 100644 --- a/public/app/features/panel/panel_directive.ts +++ b/public/app/features/panel/panel_directive.ts @@ -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(); diff --git a/public/app/plugins/datasource/testdata/datasource.ts b/public/app/plugins/datasource/testdata/datasource.ts index 3dae81781a5..62e797b5491 100644 --- a/public/app/plugins/datasource/testdata/datasource.ts +++ b/public/app/plugins/datasource/testdata/datasource.ts @@ -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 { 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 { 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) { diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index 67aceb031da..78688205c57 100644 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -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) {