ソースを参照

Refactor: React Panels to only use SeriesData[] (#16306)

* only use SeriesData[] in react panels
* update target
* Refactor: Added refId filtering for queryResponse and queryError
Ryan McKinley 6 年 前
コミット
ede2d54849

+ 14 - 3
packages/grafana-ui/src/types/data.ts

@@ -13,6 +13,17 @@ export enum FieldType {
   other = 'other', // Object, Array, etc
 }
 
+export interface QueryResultBase {
+  /**
+   * Matches the query target refId
+   */
+  refId?: string;
+  /**
+   * Used by some backend datasources to communicate back info about the execution (generated sql, timing)
+   */
+  meta?: any;
+}
+
 export interface Field {
   name: string; // The column name
   type?: FieldType;
@@ -25,7 +36,7 @@ export interface Labels {
   [key: string]: string;
 }
 
-export interface SeriesData {
+export interface SeriesData extends QueryResultBase {
   name?: string;
   fields: Field[];
   rows: any[][];
@@ -38,7 +49,7 @@ export interface Column {
   unit?: string;
 }
 
-export interface TableData {
+export interface TableData extends QueryResultBase {
   columns: Column[];
   rows: any[][];
 }
@@ -47,7 +58,7 @@ export type TimeSeriesValue = number | null;
 
 export type TimeSeriesPoints = TimeSeriesValue[][];
 
-export interface TimeSeries {
+export interface TimeSeries extends QueryResultBase {
   target: string;
   datapoints: TimeSeriesPoints;
   unit?: string;

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

@@ -44,6 +44,7 @@ export interface DataQueryError {
   message?: string;
   status?: string;
   statusText?: string;
+  refId?: string;
 }
 
 export interface ScopedVar {

+ 11 - 1
packages/grafana-ui/src/types/plugin.ts

@@ -1,6 +1,14 @@
 import { ComponentClass } from 'react';
 import { ReactPanelPlugin } from './panel';
-import { DataQueryOptions, DataQuery, DataQueryResponse, QueryHint, QueryFixAction } from './datasource';
+import {
+  DataQueryOptions,
+  DataQuery,
+  DataQueryResponse,
+  QueryHint,
+  QueryFixAction,
+  DataQueryError,
+} from './datasource';
+import { SeriesData } from './data';
 
 export interface DataSourceApi<TQuery extends DataQuery = DataQuery> {
   /**
@@ -52,6 +60,8 @@ export interface QueryEditorProps<DSType extends DataSourceApi, TQuery extends D
   query: TQuery;
   onRunQuery: () => void;
   onChange: (value: TQuery) => void;
+  queryResponse?: SeriesData[];
+  queryError?: DataQueryError;
 }
 
 export enum DatasourceStatus {

+ 8 - 0
packages/grafana-ui/src/utils/processSeriesData.ts

@@ -17,6 +17,8 @@ function convertTableToSeriesData(table: TableData): SeriesData {
       return f;
     }),
     rows: table.rows,
+    refId: table.refId,
+    meta: table.meta,
   };
 }
 
@@ -36,6 +38,8 @@ function convertTimeSeriesToSeriesData(timeSeries: TimeSeries): SeriesData {
     ],
     rows: timeSeries.datapoints,
     labels: timeSeries.tags,
+    refId: timeSeries.refId,
+    meta: timeSeries.meta,
   };
 }
 
@@ -168,6 +172,8 @@ export const toLegacyResponseData = (series: SeriesData): TimeSeries | TableData
         target: fields[0].name || series.name,
         datapoints: rows,
         unit: fields[0].unit,
+        refId: series.refId,
+        meta: series.meta,
       } as TimeSeries;
     }
   }
@@ -178,6 +184,8 @@ export const toLegacyResponseData = (series: SeriesData): TimeSeries | TableData
         text: f.name,
         filterable: f.filterable,
         unit: f.unit,
+        refId: series.refId,
+        meta: series.meta,
       };
     }),
     rows,

+ 10 - 2
public/app/features/dashboard/dashgrid/DashboardPanel.tsx

@@ -128,10 +128,18 @@ export class DashboardPanel extends PureComponent<Props, State> {
   };
 
   renderReactPanel() {
-    const { dashboard, panel, isFullscreen } = this.props;
+    const { dashboard, panel, isFullscreen, isEditing } = this.props;
     const { plugin } = this.state;
 
-    return <PanelChrome plugin={plugin} panel={panel} dashboard={dashboard} isFullscreen={isFullscreen} />;
+    return (
+      <PanelChrome
+        plugin={plugin}
+        panel={panel}
+        dashboard={dashboard}
+        isFullscreen={isFullscreen}
+        isEditing={isEditing}
+      />
+    );
   }
 
   renderAngularPanel() {

+ 5 - 9
public/app/features/dashboard/dashgrid/DataPanel.tsx

@@ -8,7 +8,6 @@ import kbn from 'app/core/utils/kbn';
 // Types
 import {
   DataQueryOptions,
-  DataQueryResponse,
   DataQueryError,
   LoadingState,
   SeriesData,
@@ -36,14 +35,13 @@ export interface Props {
   maxDataPoints?: number;
   scopedVars?: ScopedVars;
   children: (r: RenderProps) => JSX.Element;
-  onDataResponse?: (data: DataQueryResponse) => void;
+  onDataResponse?: (data?: SeriesData[]) => void;
   onError: (message: string, error: DataQueryError) => void;
 }
 
 export interface State {
   isFirstLoad: boolean;
   loading: LoadingState;
-  response: DataQueryResponse;
   data?: SeriesData[];
 }
 
@@ -80,9 +78,6 @@ export class DataPanel extends Component<Props, State> {
 
     this.state = {
       loading: LoadingState.NotStarted,
-      response: {
-        data: [],
-      },
       isFirstLoad: true,
     };
   }
@@ -160,14 +155,15 @@ export class DataPanel extends Component<Props, State> {
         return;
       }
 
+      // Make sure the data is SeriesData[]
+      const data = getProcessedSeriesData(resp.data);
       if (onDataResponse) {
-        onDataResponse(resp);
+        onDataResponse(data);
       }
 
       this.setState({
+        data,
         loading: LoadingState.Done,
-        response: resp,
-        data: getProcessedSeriesData(resp.data),
         isFirstLoad: false,
       });
     } catch (err) {

+ 16 - 5
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 { DataQueryResponse, TimeRange, LoadingState, DataQueryError, SeriesData } from '@grafana/ui';
+import { TimeRange, LoadingState, DataQueryError, SeriesData, toLegacyResponseData } from '@grafana/ui';
 import { ScopedVars } from '@grafana/ui';
 
 import templateSrv from 'app/features/templating/template_srv';
@@ -33,6 +33,7 @@ export interface Props {
   dashboard: DashboardModel;
   plugin: PanelPlugin;
   isFullscreen: boolean;
+  isEditing: boolean;
 }
 
 export interface State {
@@ -96,15 +97,25 @@ export class PanelChrome extends PureComponent<Props, State> {
     return templateSrv.replace(value, vars, format);
   };
 
-  onDataResponse = (dataQueryResponse: DataQueryResponse) => {
+  onDataResponse = (data?: SeriesData[]) => {
     if (this.props.dashboard.isSnapshot()) {
-      this.props.panel.snapshotData = dataQueryResponse.data;
+      this.props.panel.snapshotData = data;
     }
     // clear error state (if any)
     this.clearErrorState();
 
-    // This event is used by old query editors and panel editor options
-    this.props.panel.events.emit('data-received', dataQueryResponse.data);
+    if (this.props.isEditing) {
+      const events = this.props.panel.events;
+      if (!data) {
+        data = [];
+      }
+
+      // Angular query editors expect TimeSeries|TableData
+      events.emit('data-received', data.map(v => toLegacyResponseData(v)));
+
+      // Notify react query editors
+      events.emit('series-data-received', data);
+    }
   };
 
   onDataError = (message: string, error: DataQueryError) => {

+ 35 - 4
public/app/features/dashboard/panel_editor/QueryEditorRow.tsx

@@ -11,7 +11,7 @@ import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
 
 // Types
 import { PanelModel } from '../state/PanelModel';
-import { DataQuery, DataSourceApi, TimeRange } from '@grafana/ui';
+import { DataQuery, DataSourceApi, TimeRange, DataQueryError, SeriesData } from '@grafana/ui';
 import { DashboardModel } from '../state/DashboardModel';
 
 interface Props {
@@ -31,6 +31,8 @@ interface State {
   datasource: DataSourceApi | null;
   isCollapsed: boolean;
   hasTextEditMode: boolean;
+  queryError: DataQueryError | null;
+  queryResponse: SeriesData[] | null;
 }
 
 export class QueryEditorRow extends PureComponent<Props, State> {
@@ -43,6 +45,8 @@ export class QueryEditorRow extends PureComponent<Props, State> {
     isCollapsed: false,
     loadedDataSourceValue: undefined,
     hasTextEditMode: false,
+    queryError: null,
+    queryResponse: null,
   };
 
   componentDidMount() {
@@ -50,26 +54,35 @@ export class QueryEditorRow extends PureComponent<Props, State> {
     this.props.panel.events.on('refresh', this.onPanelRefresh);
     this.props.panel.events.on('data-error', this.onPanelDataError);
     this.props.panel.events.on('data-received', this.onPanelDataReceived);
+    this.props.panel.events.on('series-data-received', this.onSeriesDataReceived);
   }
 
   componentWillUnmount() {
     this.props.panel.events.off('refresh', this.onPanelRefresh);
     this.props.panel.events.off('data-error', this.onPanelDataError);
     this.props.panel.events.off('data-received', this.onPanelDataReceived);
+    this.props.panel.events.off('series-data-received', this.onSeriesDataReceived);
 
     if (this.angularQueryEditor) {
       this.angularQueryEditor.destroy();
     }
   }
 
-  onPanelDataError = () => {
+  onPanelDataError = (error: DataQueryError) => {
     // Some query controllers listen to data error events and need a digest
     if (this.angularQueryEditor) {
       // for some reason this needs to be done in next tick
       setTimeout(this.angularQueryEditor.digest);
+      return;
+    }
+
+    // if error relates to this query store it in state and pass it on to query editor
+    if (error.refId === this.props.query.refId) {
+      this.setState({ queryError: error });
     }
   };
 
+  // Only used by angular plugins
   onPanelDataReceived = () => {
     // Some query controllers listen to data error events and need a digest
     if (this.angularQueryEditor) {
@@ -78,6 +91,15 @@ export class QueryEditorRow extends PureComponent<Props, State> {
     }
   };
 
+  // Only used by the React Query Editors
+  onSeriesDataReceived = (data: SeriesData[]) => {
+    if (!this.angularQueryEditor) {
+      // only pass series related to this query to query editor
+      const filterByRefId = data.filter(series => series.refId === this.props.query.refId);
+      this.setState({ queryResponse: filterByRefId, queryError: null });
+    }
+  };
+
   onPanelRefresh = () => {
     if (this.angularScope) {
       this.angularScope.range = getTimeSrv().timeRange();
@@ -152,7 +174,7 @@ export class QueryEditorRow extends PureComponent<Props, State> {
 
   renderPluginEditor() {
     const { query, onChange } = this.props;
-    const { datasource } = this.state;
+    const { datasource, queryResponse, queryError } = this.state;
 
     if (datasource.pluginExports.QueryCtrl) {
       return <div ref={element => (this.element = element)} />;
@@ -160,7 +182,16 @@ export class QueryEditorRow extends PureComponent<Props, State> {
 
     if (datasource.pluginExports.QueryEditor) {
       const QueryEditor = datasource.pluginExports.QueryEditor;
-      return <QueryEditor query={query} datasource={datasource} onChange={onChange} onRunQuery={this.onRunQuery} />;
+      return (
+        <QueryEditor
+          query={query}
+          datasource={datasource}
+          onChange={onChange}
+          onRunQuery={this.onRunQuery}
+          queryResponse={queryResponse}
+          queryError={queryError}
+        />
+      );
     }
 
     return <div>Data source plugin does not export any Query Editor component</div>;