From ede2d548496cde1a279f91b622918f1a859039b2 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Mon, 1 Apr 2019 22:22:52 -0700 Subject: [PATCH] Refactor: React Panels to only use SeriesData[] (#16306) * only use SeriesData[] in react panels * update target * Refactor: Added refId filtering for queryResponse and queryError --- packages/grafana-ui/src/types/data.ts | 17 +++++-- packages/grafana-ui/src/types/datasource.ts | 1 + packages/grafana-ui/src/types/plugin.ts | 12 ++++- .../grafana-ui/src/utils/processSeriesData.ts | 8 +++ .../dashboard/dashgrid/DashboardPanel.tsx | 12 ++++- .../features/dashboard/dashgrid/DataPanel.tsx | 14 ++---- .../dashboard/dashgrid/PanelChrome.tsx | 21 ++++++-- .../dashboard/panel_editor/QueryEditorRow.tsx | 49 +++++++++++++++---- 8 files changed, 105 insertions(+), 29 deletions(-) diff --git a/packages/grafana-ui/src/types/data.ts b/packages/grafana-ui/src/types/data.ts index ed1f3ab37a0..a251398b981 100644 --- a/packages/grafana-ui/src/types/data.ts +++ b/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; diff --git a/packages/grafana-ui/src/types/datasource.ts b/packages/grafana-ui/src/types/datasource.ts index 15011de9836..23fa22937ab 100644 --- a/packages/grafana-ui/src/types/datasource.ts +++ b/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 { diff --git a/packages/grafana-ui/src/types/plugin.ts b/packages/grafana-ui/src/types/plugin.ts index ae59322f596..211fbad418e 100644 --- a/packages/grafana-ui/src/types/plugin.ts +++ b/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 { /** @@ -52,6 +60,8 @@ export interface QueryEditorProps void; onChange: (value: TQuery) => void; + queryResponse?: SeriesData[]; + queryError?: DataQueryError; } export enum DatasourceStatus { diff --git a/packages/grafana-ui/src/utils/processSeriesData.ts b/packages/grafana-ui/src/utils/processSeriesData.ts index d573947a860..1e78eee3da0 100644 --- a/packages/grafana-ui/src/utils/processSeriesData.ts +++ b/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, diff --git a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx index ba9988fbca9..4fe90c7bcc5 100644 --- a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx @@ -128,10 +128,18 @@ export class DashboardPanel extends PureComponent { }; renderReactPanel() { - const { dashboard, panel, isFullscreen } = this.props; + const { dashboard, panel, isFullscreen, isEditing } = this.props; const { plugin } = this.state; - return ; + return ( + + ); } renderAngularPanel() { diff --git a/public/app/features/dashboard/dashgrid/DataPanel.tsx b/public/app/features/dashboard/dashgrid/DataPanel.tsx index 520a39094aa..62be96ed40c 100644 --- a/public/app/features/dashboard/dashgrid/DataPanel.tsx +++ b/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 { this.state = { loading: LoadingState.NotStarted, - response: { - data: [], - }, isFirstLoad: true, }; } @@ -160,14 +155,15 @@ export class DataPanel extends Component { 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) { diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index bcf91627a47..3b92754538d 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/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 { 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) => { diff --git a/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx b/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx index 704edb1f062..02fdd1af818 100644 --- a/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx +++ b/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 { @@ -43,6 +45,8 @@ export class QueryEditorRow extends PureComponent { isCollapsed: false, loadedDataSourceValue: undefined, hasTextEditMode: false, + queryError: null, + queryResponse: null, }; componentDidMount() { @@ -50,19 +54,36 @@ export class QueryEditorRow extends PureComponent { 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) { // for some reason this needs to be done in next tick @@ -70,11 +91,12 @@ export class QueryEditorRow extends PureComponent { } }; - onPanelDataReceived = () => { - // 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); + // 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 }); } }; @@ -152,7 +174,7 @@ export class QueryEditorRow extends PureComponent { renderPluginEditor() { const { query, onChange } = this.props; - const { datasource } = this.state; + const { datasource, queryResponse, queryError } = this.state; if (datasource.pluginExports.QueryCtrl) { return
(this.element = element)} />; @@ -160,7 +182,16 @@ export class QueryEditorRow extends PureComponent { if (datasource.pluginExports.QueryEditor) { const QueryEditor = datasource.pluginExports.QueryEditor; - return ; + return ( + + ); } return
Data source plugin does not export any Query Editor component
;