diff --git a/packages/grafana-ui/src/types/datasource.ts b/packages/grafana-ui/src/types/datasource.ts index c5dcc3c3c01..c71f45fdf77 100644 --- a/packages/grafana-ui/src/types/datasource.ts +++ b/packages/grafana-ui/src/types/datasource.ts @@ -234,6 +234,14 @@ export interface DataQueryOptions { scopedVars: ScopedVars; } +/** + * Timestamps when the query starts and stops + */ +export interface DataRequestInfo extends DataQueryOptions { + startTime: number; + endTime?: number; +} + export interface QueryFix { type: string; label: string; diff --git a/packages/grafana-ui/src/types/panel.ts b/packages/grafana-ui/src/types/panel.ts index cd990dcaa61..81cc653ee43 100644 --- a/packages/grafana-ui/src/types/panel.ts +++ b/packages/grafana-ui/src/types/panel.ts @@ -1,14 +1,22 @@ import { ComponentClass } from 'react'; import { LoadingState, SeriesData } from './data'; import { TimeRange } from './time'; -import { ScopedVars } from './datasource'; +import { ScopedVars, DataRequestInfo, DataQueryError } from './datasource'; export type InterpolateFunction = (value: string, scopedVars?: ScopedVars, format?: string | Function) => string; +export interface PanelData { + state: LoadingState; + series: SeriesData[]; + request?: DataRequestInfo; + error?: DataQueryError; +} + export interface PanelProps { - data?: SeriesData[]; + data: PanelData; + // TODO: annotation?: PanelData; + timeRange: TimeRange; - loading: LoadingState; options: T; renderCounter: number; width: number; diff --git a/public/app/features/dashboard/dashgrid/DataPanel.tsx b/public/app/features/dashboard/dashgrid/DataPanel.tsx index fa52da377ad..a68a2aea9a0 100644 --- a/public/app/features/dashboard/dashgrid/DataPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DataPanel.tsx @@ -7,7 +7,6 @@ import { DatasourceSrv, getDatasourceSrv } from 'app/features/plugins/datasource import kbn from 'app/core/utils/kbn'; // Types import { - DataQueryOptions, DataQueryError, LoadingState, SeriesData, @@ -16,11 +15,12 @@ import { toSeriesData, guessFieldTypes, DataQuery, + PanelData, + DataRequestInfo, } from '@grafana/ui'; interface RenderProps { - loading: LoadingState; - data: SeriesData[]; + data: PanelData; } export interface Props { @@ -42,8 +42,7 @@ export interface Props { export interface State { isFirstLoad: boolean; - loading: LoadingState; - data?: SeriesData[]; + data: PanelData; } /** @@ -78,8 +77,11 @@ export class DataPanel extends Component { super(props); this.state = { - loading: LoadingState.NotStarted, isFirstLoad: true, + data: { + state: LoadingState.NotStarted, + series: [], + }, }; } @@ -123,11 +125,21 @@ export class DataPanel extends Component { } if (!queries.length) { - this.setState({ loading: LoadingState.Done }); + this.setState({ + data: { + state: LoadingState.Done, + series: [], + }, + }); return; } - this.setState({ loading: LoadingState.Loading }); + this.setState({ + data: { + ...this.state.data, + loading: LoadingState.Loading, + }, + }); try { const ds = await this.dataSourceSrv.get(datasource, scopedVars); @@ -142,7 +154,7 @@ export class DataPanel extends Component { __interval_ms: { text: intervalRes.intervalMs.toString(), value: intervalRes.intervalMs }, }); - const queryOptions: DataQueryOptions = { + const request: DataRequestInfo = { timezone: 'browser', panelId: panelId, dashboardId: dashboardId, @@ -154,24 +166,29 @@ export class DataPanel extends Component { maxDataPoints: maxDataPoints || widthPixels, scopedVars: scopedVarsWithInterval, cacheTimeout: null, + startTime: Date.now(), }; - const resp = await ds.query(queryOptions); + const resp = await ds.query(request); + request.endTime = Date.now(); if (this.isUnmounted) { return; } // Make sure the data is SeriesData[] - const data = getProcessedSeriesData(resp.data); + const series = getProcessedSeriesData(resp.data); if (onDataResponse) { - onDataResponse(data); + onDataResponse(series); } this.setState({ - data, - loading: LoadingState.Done, isFirstLoad: false, + data: { + state: LoadingState.Done, + series, + request, + }, }); } catch (err) { console.log('DataPanel error', err); @@ -189,16 +206,24 @@ export class DataPanel extends Component { } onError(message, err); - this.setState({ isFirstLoad: false, loading: LoadingState.Error }); + + this.setState({ + isFirstLoad: false, + data: { + ...this.state.data, + loading: LoadingState.Error, + }, + }); } }; render() { const { queries } = this.props; - const { loading, isFirstLoad, data } = this.state; + const { isFirstLoad, data } = this.state; + const { state } = data; // do not render component until we have first data - if (isFirstLoad && (loading === LoadingState.Loading || loading === LoadingState.NotStarted)) { + if (isFirstLoad && (state === LoadingState.Loading || state === LoadingState.NotStarted)) { return this.renderLoadingState(); } @@ -212,8 +237,8 @@ export class DataPanel extends Component { return ( <> - {loading === LoadingState.Loading && this.renderLoadingState()} - {this.props.children({ loading, data })} + {state === LoadingState.Loading && this.renderLoadingState()} + {this.props.children({ data })} ); } diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index 0d1ccbbda7a..67cd8be2a1d 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -19,7 +19,7 @@ import config from 'app/core/config'; // Types import { DashboardModel, PanelModel } from '../state'; import { PanelPlugin } from 'app/types'; -import { TimeRange, LoadingState, DataQueryError, SeriesData, toLegacyResponseData } from '@grafana/ui'; +import { TimeRange, LoadingState, DataQueryError, SeriesData, toLegacyResponseData, PanelData } from '@grafana/ui'; import { ScopedVars } from '@grafana/ui'; import templateSrv from 'app/features/templating/template_srv'; @@ -152,24 +152,26 @@ export class PanelChrome extends PureComponent { } get getDataForPanel() { - return this.hasPanelSnapshot ? getProcessedSeriesData(this.props.panel.snapshotData) : null; + return { + state: LoadingState.Done, + series: this.hasPanelSnapshot ? getProcessedSeriesData(this.props.panel.snapshotData) : [], + }; } - renderPanelPlugin(loading: LoadingState, data: SeriesData[], width: number, height: number): JSX.Element { + renderPanelPlugin(data: PanelData, width: number, height: number): JSX.Element { const { panel, plugin } = this.props; const { timeRange, renderCounter } = this.state; const PanelComponent = plugin.reactPlugin.panel; // This is only done to increase a counter that is used by backend // image rendering (phantomjs/headless chrome) to know when to capture image - if (loading === LoadingState.Done) { + if (data.state === LoadingState.Done) { profiler.renderingCompleted(panel.id); } return (
{ onDataResponse={this.onDataResponse} onError={this.onDataError} > - {({ loading, data }) => { - return this.renderPanelPlugin(loading, data, width, height); + {({ data }) => { + return this.renderPanelPlugin(data, width, height); }} ) : ( - this.renderPanelPlugin(LoadingState.Done, this.getDataForPanel, width, height) + this.renderPanelPlugin(this.getDataForPanel, width, height) )} ); diff --git a/public/app/plugins/panel/bargauge/BarGaugePanel.tsx b/public/app/plugins/panel/bargauge/BarGaugePanel.tsx index b76e391c870..7498378e425 100644 --- a/public/app/plugins/panel/bargauge/BarGaugePanel.tsx +++ b/public/app/plugins/panel/bargauge/BarGaugePanel.tsx @@ -30,13 +30,12 @@ export class BarGaugePanel extends PureComponent> { }; getValues = (): DisplayValue[] => { + const { data, options, replaceVariables } = this.props; return getSingleStatDisplayValues({ - valueMappings: this.props.options.valueMappings, - thresholds: this.props.options.thresholds, - valueOptions: this.props.options.valueOptions, - data: this.props.data, + ...options, + replaceVariables, theme: config.theme, - replaceVariables: this.props.replaceVariables, + data: data.series, }); }; @@ -50,6 +49,7 @@ export class BarGaugePanel extends PureComponent> { render() { const { height, width, options, data, renderCounter } = this.props; + return ( > { }; getValues = (): DisplayValue[] => { + const { data, options, replaceVariables } = this.props; return getSingleStatDisplayValues({ - valueMappings: this.props.options.valueMappings, - thresholds: this.props.options.thresholds, - valueOptions: this.props.options.valueOptions, - data: this.props.data, + ...options, + replaceVariables, theme: config.theme, - replaceVariables: this.props.replaceVariables, + data: data.series, }); }; diff --git a/public/app/plugins/panel/graph2/GraphPanel.tsx b/public/app/plugins/panel/graph2/GraphPanel.tsx index a6cb543b643..a6821323b27 100644 --- a/public/app/plugins/panel/graph2/GraphPanel.tsx +++ b/public/app/plugins/panel/graph2/GraphPanel.tsx @@ -14,33 +14,31 @@ export class GraphPanel extends PureComponent { const { showLines, showBars, showPoints } = this.props.options; const graphs: GraphSeriesXY[] = []; - if (data) { - for (const series of data) { - const timeColumn = getFirstTimeField(series); - if (timeColumn < 0) { - continue; - } + for (const series of data.series) { + const timeColumn = getFirstTimeField(series); + if (timeColumn < 0) { + continue; + } - for (let i = 0; i < series.fields.length; i++) { - const field = series.fields[i]; + for (let i = 0; i < series.fields.length; i++) { + const field = series.fields[i]; - // Show all numeric columns - if (field.type === FieldType.number) { - // Use external calculator just to make sure it works :) - const points = getFlotPairs({ - series, - xIndex: timeColumn, - yIndex: i, - nullValueMode: NullValueMode.Null, + // Show all numeric columns + if (field.type === FieldType.number) { + // Use external calculator just to make sure it works :) + const points = getFlotPairs({ + series, + xIndex: timeColumn, + yIndex: i, + nullValueMode: NullValueMode.Null, + }); + + if (points.length > 0) { + graphs.push({ + label: field.name, + data: points, + color: colors[graphs.length % colors.length], }); - - if (points.length > 0) { - graphs.push({ - label: field.name, - data: points, - color: colors[graphs.length % colors.length], - }); - } } } } diff --git a/public/app/plugins/panel/piechart/PieChartPanel.tsx b/public/app/plugins/panel/piechart/PieChartPanel.tsx index fae799abc58..0cf4e2de674 100644 --- a/public/app/plugins/panel/piechart/PieChartPanel.tsx +++ b/public/app/plugins/panel/piechart/PieChartPanel.tsx @@ -21,7 +21,7 @@ export class PieChartPanel extends PureComponent { valueMappings: options.valueMappings, thresholds: options.thresholds, valueOptions: options.valueOptions, - data: data, + data: data.series, theme: config.theme, replaceVariables: replaceVariables, }); diff --git a/public/app/plugins/panel/singlestat2/SingleStatPanel.tsx b/public/app/plugins/panel/singlestat2/SingleStatPanel.tsx index 920eef3580b..fe5bc4d96f0 100644 --- a/public/app/plugins/panel/singlestat2/SingleStatPanel.tsx +++ b/public/app/plugins/panel/singlestat2/SingleStatPanel.tsx @@ -50,8 +50,7 @@ export class SingleStatPanel extends PureComponent const { stat } = valueOptions; const values: SingleStatDisplay[] = []; - - for (const series of data) { + for (const series of data.series) { const timeColumn = sparkline.show ? getFirstTimeField(series) : -1; for (let i = 0; i < series.fields.length; i++) { @@ -122,11 +121,17 @@ export class SingleStatPanel extends PureComponent } } - // Don't show a title if there is only one item - if (values.length === 1) { + if (values.length === 0) { + values.push({ + value: { + numeric: 0, + text: 'No data', + }, + }); + } else if (values.length === 1) { + // Don't show title for single item values[0].value.title = null; } - return values; }; diff --git a/public/app/plugins/panel/table2/TablePanel.tsx b/public/app/plugins/panel/table2/TablePanel.tsx index 70b652a8d2c..863fc9ef703 100644 --- a/public/app/plugins/panel/table2/TablePanel.tsx +++ b/public/app/plugins/panel/table2/TablePanel.tsx @@ -16,13 +16,13 @@ export class TablePanel extends Component { render() { const { data, options } = this.props; - if (data.length < 1) { + if (data.series.length < 1) { return
No Table Data...
; } return ( - {theme => } + {theme =>
} ); }