Commit 514818f1 by Ryan McKinley Committed by GitHub

Panel Plugins: pass query request/response to react panel plugins (#16577)

* pass query request/response to panel plugins

* rename finishTime to endTime

* move QueryResponseData to a sub variable

* rename to PanelData

* make data not optional

* make data not optional

* missing optional
parent 7b63913d
...@@ -234,6 +234,14 @@ export interface DataQueryOptions<TQuery extends DataQuery = DataQuery> { ...@@ -234,6 +234,14 @@ export interface DataQueryOptions<TQuery extends DataQuery = DataQuery> {
scopedVars: ScopedVars; scopedVars: ScopedVars;
} }
/**
* Timestamps when the query starts and stops
*/
export interface DataRequestInfo extends DataQueryOptions {
startTime: number;
endTime?: number;
}
export interface QueryFix { export interface QueryFix {
type: string; type: string;
label: string; label: string;
......
import { ComponentClass } from 'react'; import { ComponentClass } from 'react';
import { LoadingState, SeriesData } from './data'; import { LoadingState, SeriesData } from './data';
import { TimeRange } from './time'; 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 type InterpolateFunction = (value: string, scopedVars?: ScopedVars, format?: string | Function) => string;
export interface PanelData {
state: LoadingState;
series: SeriesData[];
request?: DataRequestInfo;
error?: DataQueryError;
}
export interface PanelProps<T = any> { export interface PanelProps<T = any> {
data?: SeriesData[]; data: PanelData;
// TODO: annotation?: PanelData;
timeRange: TimeRange; timeRange: TimeRange;
loading: LoadingState;
options: T; options: T;
renderCounter: number; renderCounter: number;
width: number; width: number;
......
...@@ -7,7 +7,6 @@ import { DatasourceSrv, getDatasourceSrv } from 'app/features/plugins/datasource ...@@ -7,7 +7,6 @@ import { DatasourceSrv, getDatasourceSrv } from 'app/features/plugins/datasource
import kbn from 'app/core/utils/kbn'; import kbn from 'app/core/utils/kbn';
// Types // Types
import { import {
DataQueryOptions,
DataQueryError, DataQueryError,
LoadingState, LoadingState,
SeriesData, SeriesData,
...@@ -16,11 +15,12 @@ import { ...@@ -16,11 +15,12 @@ import {
toSeriesData, toSeriesData,
guessFieldTypes, guessFieldTypes,
DataQuery, DataQuery,
PanelData,
DataRequestInfo,
} from '@grafana/ui'; } from '@grafana/ui';
interface RenderProps { interface RenderProps {
loading: LoadingState; data: PanelData;
data: SeriesData[];
} }
export interface Props { export interface Props {
...@@ -42,8 +42,7 @@ export interface Props { ...@@ -42,8 +42,7 @@ export interface Props {
export interface State { export interface State {
isFirstLoad: boolean; isFirstLoad: boolean;
loading: LoadingState; data: PanelData;
data?: SeriesData[];
} }
/** /**
...@@ -78,8 +77,11 @@ export class DataPanel extends Component<Props, State> { ...@@ -78,8 +77,11 @@ export class DataPanel extends Component<Props, State> {
super(props); super(props);
this.state = { this.state = {
loading: LoadingState.NotStarted,
isFirstLoad: true, isFirstLoad: true,
data: {
state: LoadingState.NotStarted,
series: [],
},
}; };
} }
...@@ -123,11 +125,21 @@ export class DataPanel extends Component<Props, State> { ...@@ -123,11 +125,21 @@ export class DataPanel extends Component<Props, State> {
} }
if (!queries.length) { if (!queries.length) {
this.setState({ loading: LoadingState.Done }); this.setState({
data: {
state: LoadingState.Done,
series: [],
},
});
return; return;
} }
this.setState({ loading: LoadingState.Loading }); this.setState({
data: {
...this.state.data,
loading: LoadingState.Loading,
},
});
try { try {
const ds = await this.dataSourceSrv.get(datasource, scopedVars); const ds = await this.dataSourceSrv.get(datasource, scopedVars);
...@@ -142,7 +154,7 @@ export class DataPanel extends Component<Props, State> { ...@@ -142,7 +154,7 @@ export class DataPanel extends Component<Props, State> {
__interval_ms: { text: intervalRes.intervalMs.toString(), value: intervalRes.intervalMs }, __interval_ms: { text: intervalRes.intervalMs.toString(), value: intervalRes.intervalMs },
}); });
const queryOptions: DataQueryOptions = { const request: DataRequestInfo = {
timezone: 'browser', timezone: 'browser',
panelId: panelId, panelId: panelId,
dashboardId: dashboardId, dashboardId: dashboardId,
...@@ -154,24 +166,29 @@ export class DataPanel extends Component<Props, State> { ...@@ -154,24 +166,29 @@ export class DataPanel extends Component<Props, State> {
maxDataPoints: maxDataPoints || widthPixels, maxDataPoints: maxDataPoints || widthPixels,
scopedVars: scopedVarsWithInterval, scopedVars: scopedVarsWithInterval,
cacheTimeout: null, cacheTimeout: null,
startTime: Date.now(),
}; };
const resp = await ds.query(queryOptions); const resp = await ds.query(request);
request.endTime = Date.now();
if (this.isUnmounted) { if (this.isUnmounted) {
return; return;
} }
// Make sure the data is SeriesData[] // Make sure the data is SeriesData[]
const data = getProcessedSeriesData(resp.data); const series = getProcessedSeriesData(resp.data);
if (onDataResponse) { if (onDataResponse) {
onDataResponse(data); onDataResponse(series);
} }
this.setState({ this.setState({
data,
loading: LoadingState.Done,
isFirstLoad: false, isFirstLoad: false,
data: {
state: LoadingState.Done,
series,
request,
},
}); });
} catch (err) { } catch (err) {
console.log('DataPanel error', err); console.log('DataPanel error', err);
...@@ -189,16 +206,24 @@ export class DataPanel extends Component<Props, State> { ...@@ -189,16 +206,24 @@ export class DataPanel extends Component<Props, State> {
} }
onError(message, err); onError(message, err);
this.setState({ isFirstLoad: false, loading: LoadingState.Error });
this.setState({
isFirstLoad: false,
data: {
...this.state.data,
loading: LoadingState.Error,
},
});
} }
}; };
render() { render() {
const { queries } = this.props; 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 // 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(); return this.renderLoadingState();
} }
...@@ -212,8 +237,8 @@ export class DataPanel extends Component<Props, State> { ...@@ -212,8 +237,8 @@ export class DataPanel extends Component<Props, State> {
return ( return (
<> <>
{loading === LoadingState.Loading && this.renderLoadingState()} {state === LoadingState.Loading && this.renderLoadingState()}
{this.props.children({ loading, data })} {this.props.children({ data })}
</> </>
); );
} }
......
...@@ -19,7 +19,7 @@ import config from 'app/core/config'; ...@@ -19,7 +19,7 @@ import config from 'app/core/config';
// Types // Types
import { DashboardModel, PanelModel } from '../state'; import { DashboardModel, PanelModel } from '../state';
import { PanelPlugin } from 'app/types'; 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 { ScopedVars } from '@grafana/ui';
import templateSrv from 'app/features/templating/template_srv'; import templateSrv from 'app/features/templating/template_srv';
...@@ -152,24 +152,26 @@ export class PanelChrome extends PureComponent<Props, State> { ...@@ -152,24 +152,26 @@ export class PanelChrome extends PureComponent<Props, State> {
} }
get getDataForPanel() { 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 { panel, plugin } = this.props;
const { timeRange, renderCounter } = this.state; const { timeRange, renderCounter } = this.state;
const PanelComponent = plugin.reactPlugin.panel; const PanelComponent = plugin.reactPlugin.panel;
// This is only done to increase a counter that is used by backend // This is only done to increase a counter that is used by backend
// image rendering (phantomjs/headless chrome) to know when to capture image // image rendering (phantomjs/headless chrome) to know when to capture image
if (loading === LoadingState.Done) { if (data.state === LoadingState.Done) {
profiler.renderingCompleted(panel.id); profiler.renderingCompleted(panel.id);
} }
return ( return (
<div className="panel-content"> <div className="panel-content">
<PanelComponent <PanelComponent
loading={loading}
data={data} data={data}
timeRange={timeRange} timeRange={timeRange}
options={panel.getOptions(plugin.reactPlugin.defaults)} options={panel.getOptions(plugin.reactPlugin.defaults)}
...@@ -201,12 +203,12 @@ export class PanelChrome extends PureComponent<Props, State> { ...@@ -201,12 +203,12 @@ export class PanelChrome extends PureComponent<Props, State> {
onDataResponse={this.onDataResponse} onDataResponse={this.onDataResponse}
onError={this.onDataError} onError={this.onDataError}
> >
{({ loading, data }) => { {({ data }) => {
return this.renderPanelPlugin(loading, data, width, height); return this.renderPanelPlugin(data, width, height);
}} }}
</DataPanel> </DataPanel>
) : ( ) : (
this.renderPanelPlugin(LoadingState.Done, this.getDataForPanel, width, height) this.renderPanelPlugin(this.getDataForPanel, width, height)
)} )}
</> </>
); );
......
...@@ -30,13 +30,12 @@ export class BarGaugePanel extends PureComponent<PanelProps<BarGaugeOptions>> { ...@@ -30,13 +30,12 @@ export class BarGaugePanel extends PureComponent<PanelProps<BarGaugeOptions>> {
}; };
getValues = (): DisplayValue[] => { getValues = (): DisplayValue[] => {
const { data, options, replaceVariables } = this.props;
return getSingleStatDisplayValues({ return getSingleStatDisplayValues({
valueMappings: this.props.options.valueMappings, ...options,
thresholds: this.props.options.thresholds, replaceVariables,
valueOptions: this.props.options.valueOptions,
data: this.props.data,
theme: config.theme, theme: config.theme,
replaceVariables: this.props.replaceVariables, data: data.series,
}); });
}; };
...@@ -50,6 +49,7 @@ export class BarGaugePanel extends PureComponent<PanelProps<BarGaugeOptions>> { ...@@ -50,6 +49,7 @@ export class BarGaugePanel extends PureComponent<PanelProps<BarGaugeOptions>> {
render() { render() {
const { height, width, options, data, renderCounter } = this.props; const { height, width, options, data, renderCounter } = this.props;
return ( return (
<VizRepeater <VizRepeater
source={data} source={data}
......
...@@ -31,13 +31,12 @@ export class GaugePanel extends PureComponent<PanelProps<GaugeOptions>> { ...@@ -31,13 +31,12 @@ export class GaugePanel extends PureComponent<PanelProps<GaugeOptions>> {
}; };
getValues = (): DisplayValue[] => { getValues = (): DisplayValue[] => {
const { data, options, replaceVariables } = this.props;
return getSingleStatDisplayValues({ return getSingleStatDisplayValues({
valueMappings: this.props.options.valueMappings, ...options,
thresholds: this.props.options.thresholds, replaceVariables,
valueOptions: this.props.options.valueOptions,
data: this.props.data,
theme: config.theme, theme: config.theme,
replaceVariables: this.props.replaceVariables, data: data.series,
}); });
}; };
......
...@@ -14,33 +14,31 @@ export class GraphPanel extends PureComponent<Props> { ...@@ -14,33 +14,31 @@ export class GraphPanel extends PureComponent<Props> {
const { showLines, showBars, showPoints } = this.props.options; const { showLines, showBars, showPoints } = this.props.options;
const graphs: GraphSeriesXY[] = []; const graphs: GraphSeriesXY[] = [];
if (data) { for (const series of data.series) {
for (const series of data) { const timeColumn = getFirstTimeField(series);
const timeColumn = getFirstTimeField(series); if (timeColumn < 0) {
if (timeColumn < 0) { continue;
continue; }
}
for (let i = 0; i < series.fields.length; i++) { for (let i = 0; i < series.fields.length; i++) {
const field = series.fields[i]; const field = series.fields[i];
// Show all numeric columns // Show all numeric columns
if (field.type === FieldType.number) { if (field.type === FieldType.number) {
// Use external calculator just to make sure it works :) // Use external calculator just to make sure it works :)
const points = getFlotPairs({ const points = getFlotPairs({
series, series,
xIndex: timeColumn, xIndex: timeColumn,
yIndex: i, yIndex: i,
nullValueMode: NullValueMode.Null, nullValueMode: NullValueMode.Null,
}); });
if (points.length > 0) { if (points.length > 0) {
graphs.push({ graphs.push({
label: field.name, label: field.name,
data: points, data: points,
color: colors[graphs.length % colors.length], color: colors[graphs.length % colors.length],
}); });
}
} }
} }
} }
......
...@@ -21,7 +21,7 @@ export class PieChartPanel extends PureComponent<Props> { ...@@ -21,7 +21,7 @@ export class PieChartPanel extends PureComponent<Props> {
valueMappings: options.valueMappings, valueMappings: options.valueMappings,
thresholds: options.thresholds, thresholds: options.thresholds,
valueOptions: options.valueOptions, valueOptions: options.valueOptions,
data: data, data: data.series,
theme: config.theme, theme: config.theme,
replaceVariables: replaceVariables, replaceVariables: replaceVariables,
}); });
......
...@@ -50,8 +50,7 @@ export class SingleStatPanel extends PureComponent<PanelProps<SingleStatOptions> ...@@ -50,8 +50,7 @@ export class SingleStatPanel extends PureComponent<PanelProps<SingleStatOptions>
const { stat } = valueOptions; const { stat } = valueOptions;
const values: SingleStatDisplay[] = []; const values: SingleStatDisplay[] = [];
for (const series of data.series) {
for (const series of data) {
const timeColumn = sparkline.show ? getFirstTimeField(series) : -1; const timeColumn = sparkline.show ? getFirstTimeField(series) : -1;
for (let i = 0; i < series.fields.length; i++) { for (let i = 0; i < series.fields.length; i++) {
...@@ -122,11 +121,17 @@ export class SingleStatPanel extends PureComponent<PanelProps<SingleStatOptions> ...@@ -122,11 +121,17 @@ export class SingleStatPanel extends PureComponent<PanelProps<SingleStatOptions>
} }
} }
// Don't show a title if there is only one item if (values.length === 0) {
if (values.length === 1) { 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; values[0].value.title = null;
} }
return values; return values;
}; };
......
...@@ -16,13 +16,13 @@ export class TablePanel extends Component<Props> { ...@@ -16,13 +16,13 @@ export class TablePanel extends Component<Props> {
render() { render() {
const { data, options } = this.props; const { data, options } = this.props;
if (data.length < 1) { if (data.series.length < 1) {
return <div>No Table Data...</div>; return <div>No Table Data...</div>;
} }
return ( return (
<ThemeContext.Consumer> <ThemeContext.Consumer>
{theme => <Table {...this.props} {...options} theme={theme} data={data[0]} />} {theme => <Table {...this.props} {...options} theme={theme} data={data.series[0]} />}
</ThemeContext.Consumer> </ThemeContext.Consumer>
); );
} }
......
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