mirror of
https://github.com/grafana/grafana.git
synced 2025-02-20 11:48:34 -06:00
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:
parent
7b63913dc1
commit
514818f16d
@ -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;
|
||||||
|
@ -1,14 +1,22 @@
|
|||||||
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
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
|
|
||||||
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> {
|
|||||||
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> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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> {
|
|||||||
__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> {
|
|||||||
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> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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> {
|
|||||||
|
|
||||||
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';
|
|||||||
// 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> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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> {
|
|||||||
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>> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
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>> {
|
|||||||
|
|
||||||
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>> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
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> {
|
|||||||
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) {
|
||||||
|
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],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>
|
|||||||
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>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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> {
|
|||||||
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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user