mirror of
https://github.com/grafana/grafana.git
synced 2024-11-27 11:20:27 -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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps when the query starts and stops
|
||||
*/
|
||||
export interface DataRequestInfo extends DataQueryOptions {
|
||||
startTime: number;
|
||||
endTime?: number;
|
||||
}
|
||||
|
||||
export interface QueryFix {
|
||||
type: string;
|
||||
label: string;
|
||||
|
@ -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;
|
||||
|
@ -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 })}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -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)
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
@ -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}
|
||||
|
@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -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],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
});
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user