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
This commit is contained in:
Ryan McKinley 2019-04-16 13:23:34 -07:00 committed by GitHub
parent 7b63913dc1
commit 514818f16d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 117 additions and 72 deletions

View File

@ -234,6 +234,14 @@ export interface DataQueryOptions<TQuery extends DataQuery = DataQuery> {
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;

View File

@ -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<T = any> {
data?: SeriesData[];
data: PanelData;
// TODO: annotation?: PanelData;
timeRange: TimeRange;
loading: LoadingState;
options: T;
renderCounter: number;
width: number;

View File

@ -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<Props, State> {
super(props);
this.state = {
loading: LoadingState.NotStarted,
isFirstLoad: true,
data: {
state: LoadingState.NotStarted,
series: [],
},
};
}
@ -123,11 +125,21 @@ export class DataPanel extends Component<Props, State> {
}
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<Props, State> {
__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<Props, State> {
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<Props, State> {
}
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<Props, State> {
return (
<>
{loading === LoadingState.Loading && this.renderLoadingState()}
{this.props.children({ loading, data })}
{state === LoadingState.Loading && this.renderLoadingState()}
{this.props.children({ data })}
</>
);
}

View File

@ -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<Props, State> {
}
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 (
<div className="panel-content">
<PanelComponent
loading={loading}
data={data}
timeRange={timeRange}
options={panel.getOptions(plugin.reactPlugin.defaults)}
@ -201,12 +203,12 @@ export class PanelChrome extends PureComponent<Props, State> {
onDataResponse={this.onDataResponse}
onError={this.onDataError}
>
{({ loading, data }) => {
return this.renderPanelPlugin(loading, data, width, height);
{({ data }) => {
return this.renderPanelPlugin(data, width, height);
}}
</DataPanel>
) : (
this.renderPanelPlugin(LoadingState.Done, this.getDataForPanel, width, height)
this.renderPanelPlugin(this.getDataForPanel, width, height)
)}
</>
);

View File

@ -30,13 +30,12 @@ export class BarGaugePanel extends PureComponent<PanelProps<BarGaugeOptions>> {
};
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<PanelProps<BarGaugeOptions>> {
render() {
const { height, width, options, data, renderCounter } = this.props;
return (
<VizRepeater
source={data}

View File

@ -31,13 +31,12 @@ export class GaugePanel extends PureComponent<PanelProps<GaugeOptions>> {
};
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,
});
};

View File

@ -14,33 +14,31 @@ export class GraphPanel extends PureComponent<Props> {
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],
});
}
}
}
}

View File

@ -21,7 +21,7 @@ export class PieChartPanel extends PureComponent<Props> {
valueMappings: options.valueMappings,
thresholds: options.thresholds,
valueOptions: options.valueOptions,
data: data,
data: data.series,
theme: config.theme,
replaceVariables: replaceVariables,
});

View File

@ -50,8 +50,7 @@ export class SingleStatPanel extends PureComponent<PanelProps<SingleStatOptions>
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<PanelProps<SingleStatOptions>
}
}
// 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;
};

View File

@ -16,13 +16,13 @@ export class TablePanel extends Component<Props> {
render() {
const { data, options } = this.props;
if (data.length < 1) {
if (data.series.length < 1) {
return <div>No Table Data...</div>;
}
return (
<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>
);
}