瀏覽代碼

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
Ryan McKinley 6 年之前
父節點
當前提交
514818f16d

+ 8 - 0
packages/grafana-ui/src/types/datasource.ts

@@ -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;

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

+ 44 - 19
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<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 })}
       </>
     );
   }

+ 10 - 8
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<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)
         )}
       </>
     );

+ 5 - 5
public/app/plugins/panel/bargauge/BarGaugePanel.tsx

@@ -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}

+ 4 - 5
public/app/plugins/panel/gauge/GaugePanel.tsx

@@ -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,
     });
   };
 

+ 22 - 24
public/app/plugins/panel/graph2/GraphPanel.tsx

@@ -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],
+            });
           }
         }
       }

+ 1 - 1
public/app/plugins/panel/piechart/PieChartPanel.tsx

@@ -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,
     });

+ 10 - 5
public/app/plugins/panel/singlestat2/SingleStatPanel.tsx

@@ -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;
   };
 

+ 2 - 2
public/app/plugins/panel/table2/TablePanel.tsx

@@ -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>
     );
   }