From dff78561bca94935e3207e6e1c848a3d9b2d5abd Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Tue, 20 Apr 2021 02:05:50 -0700 Subject: [PATCH] PanelData: include structure revision info and only apply overrides once (#33098) * calculate schemaRevision in panel query runner * schema > structureRev * calculate revision after apply field overrides * this.state * don't apply field overrides if the shape is already the same * check schema revision * update snapshots --- .../src/dataframe/frameComparisons.ts | 7 ++- packages/grafana-data/src/types/panel.ts | 6 ++ .../src/components/GraphNG/GraphNG.tsx | 8 +-- .../PanelEditor/usePanelLatestData.ts | 16 ++--- .../features/query/state/PanelQueryRunner.ts | 61 ++++++++++++++++--- .../app/features/query/state/QueryRunner.ts | 21 ++++++- public/app/plugins/panel/debug/DebugPanel.tsx | 21 ++----- .../panel/timeseries/TimeSeriesPanel.tsx | 1 + .../plugins/panel/xychart/XYChartPanel.tsx | 1 + 9 files changed, 99 insertions(+), 43 deletions(-) diff --git a/packages/grafana-data/src/dataframe/frameComparisons.ts b/packages/grafana-data/src/dataframe/frameComparisons.ts index c9f7dd502b9..9b252191213 100644 --- a/packages/grafana-data/src/dataframe/frameComparisons.ts +++ b/packages/grafana-data/src/dataframe/frameComparisons.ts @@ -15,7 +15,7 @@ import { DataFrame } from '../types/dataFrame'; * * @beta */ -export function compareDataFrameStructures(a: DataFrame, b: DataFrame): boolean { +export function compareDataFrameStructures(a: DataFrame, b: DataFrame, skipConfig?: boolean): boolean { if (a === b) { return true; } @@ -32,6 +32,11 @@ export function compareDataFrameStructures(a: DataFrame, b: DataFrame): boolean return false; } + // Do not check the config fields + if (skipConfig) { + continue; + } + const cfgA = fA.config as any; const cfgB = fB.config as any; diff --git a/packages/grafana-data/src/types/panel.ts b/packages/grafana-data/src/types/panel.ts index 7de98b5c1a7..9db57f751b1 100644 --- a/packages/grafana-data/src/types/panel.ts +++ b/packages/grafana-data/src/types/panel.ts @@ -29,6 +29,12 @@ export interface PanelData { /** Contains data frames with field overrides applied */ series: DataFrame[]; + /** + * This is a key that will change when the DataFrame[] structure changes. + * The revision is a useful way to know if only data has changed or data+structure + */ + structureRev?: number; + /** A list of annotation items */ annotations?: DataFrame[]; diff --git a/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx b/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx index ed005c1b484..b9d28225db0 100755 --- a/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx +++ b/packages/grafana-ui/src/components/GraphNG/GraphNG.tsx @@ -1,8 +1,6 @@ import React from 'react'; import { AlignedData } from 'uplot'; import { - compareArrayValues, - compareDataFrameStructures, DataFrame, DataFrameFieldIndex, FieldMatcherID, @@ -32,6 +30,7 @@ export interface GraphNGProps extends Themeable { width: number; height: number; data: DataFrame[]; + structureRev?: number; // a number that will change when the data[] structure changes timeRange: TimeRange; legend: VizLegendOptions; timeZone: TimeZone; @@ -119,7 +118,7 @@ class UnthemedGraphNG extends React.Component { } componentDidUpdate(prevProps: GraphNGProps) { - const { data, theme } = this.props; + const { data, theme, structureRev } = this.props; const { alignedDataFrame } = this.state; let shouldConfigUpdate = false; let stateUpdate = {} as GraphNGState; @@ -133,8 +132,7 @@ class UnthemedGraphNG extends React.Component { return; } - const hasStructureChanged = !compareArrayValues(data, prevProps.data, compareDataFrameStructures); - + const hasStructureChanged = structureRev !== prevProps.structureRev || !structureRev; if (shouldConfigUpdate || hasStructureChanged) { const builder = preparePlotConfigBuilder(alignedDataFrame, theme, this.getTimeRange, this.getTimeZone); stateUpdate = { ...stateUpdate, config: builder }; diff --git a/public/app/features/dashboard/components/PanelEditor/usePanelLatestData.ts b/public/app/features/dashboard/components/PanelEditor/usePanelLatestData.ts index 7f738a4ca93..625642f43a5 100644 --- a/public/app/features/dashboard/components/PanelEditor/usePanelLatestData.ts +++ b/public/app/features/dashboard/components/PanelEditor/usePanelLatestData.ts @@ -1,11 +1,4 @@ -import { - compareArrayValues, - compareDataFrameStructures, - DataFrame, - DataQueryError, - LoadingState, - PanelData, -} from '@grafana/data'; +import { DataQueryError, LoadingState, PanelData } from '@grafana/data'; import { useEffect, useRef, useState } from 'react'; import { PanelModel } from '../../state'; import { Unsubscribable } from 'rxjs'; @@ -30,7 +23,7 @@ export const usePanelLatestData = ( const [latestData, setLatestData] = useState(); useEffect(() => { - let last: DataFrame[] = []; + let lastRev = -1; let lastUpdate = 0; querySubscription.current = panel @@ -39,8 +32,7 @@ export const usePanelLatestData = ( .subscribe({ next: (data) => { if (checkSchema) { - const sameStructure = compareArrayValues(last, data.series, compareDataFrameStructures); - if (sameStructure) { + if (lastRev === data.structureRev) { const now = Date.now(); const elapsed = now - lastUpdate; if (elapsed < 10000) { @@ -48,7 +40,7 @@ export const usePanelLatestData = ( } lastUpdate = now; } - last = data.series; + lastRev = data.structureRev ?? -1; } setLatestData(data); }, diff --git a/public/app/features/query/state/PanelQueryRunner.ts b/public/app/features/query/state/PanelQueryRunner.ts index 210a51f282d..c15eb8f908f 100644 --- a/public/app/features/query/state/PanelQueryRunner.ts +++ b/public/app/features/query/state/PanelQueryRunner.ts @@ -12,8 +12,11 @@ import { isSharedDashboardQuery, runSharedRequest } from '../../../plugins/datas // Types import { applyFieldOverrides, + compareArrayValues, + compareDataFrameStructures, CoreApp, DataConfigSource, + DataFrame, DataQuery, DataQueryRequest, DataSourceApi, @@ -72,30 +75,74 @@ export class PanelQueryRunner { */ getData(options: GetDataOptions): Observable { const { withFieldConfig, withTransforms } = options; + let structureRev = 1; + let lastData: DataFrame[] = []; + let processedCount = 0; + let lastConfigRev = -1; + const fastCompare = (a: DataFrame, b: DataFrame) => { + return compareDataFrameStructures(a, b, true); + }; return this.subject.pipe( this.getTransformationsStream(withTransforms), map((data: PanelData) => { let processedData = data; + let sameStructure = false; - if (withFieldConfig) { + if (withFieldConfig && data.series?.length) { // Apply field defaults and overrides - const fieldConfig = this.dataConfigSource.getFieldOverrideOptions(); - const timeZone = data.request?.timezone ?? 'browser'; + let fieldConfig = this.dataConfigSource.getFieldOverrideOptions(); + let processFields = fieldConfig != null; - if (fieldConfig) { + // If the shape is the same, we can skip field overrides + if ( + processFields && + processedCount > 0 && + lastData.length && + lastConfigRev === this.dataConfigSource.configRev + ) { + const sameTypes = compareArrayValues(lastData, processedData.series, fastCompare); + if (sameTypes) { + // Keep the previous field config settings + processedData = { + ...processedData, + series: lastData.map((frame, frameIndex) => ({ + ...frame, + fields: frame.fields.map((field, fieldIndex) => ({ + ...field, + values: data.series[frameIndex].fields[fieldIndex].values, + })), + })), + }; + processFields = false; + sameStructure = true; + } + } + + if (processFields) { + lastConfigRev = this.dataConfigSource.configRev!; + processedCount++; // results with data processedData = { ...processedData, series: applyFieldOverrides({ - timeZone: timeZone, + timeZone: data.request?.timezone ?? 'browser', data: processedData.series, - ...fieldConfig, + ...fieldConfig!, }), }; } } - return processedData; + if (!sameStructure) { + sameStructure = compareArrayValues(lastData, processedData.series, compareDataFrameStructures); + } + if (!sameStructure) { + structureRev++; + } + + lastData = processedData.series; + + return { ...processedData, structureRev }; }) ); } diff --git a/public/app/features/query/state/QueryRunner.ts b/public/app/features/query/state/QueryRunner.ts index 179420d930a..9d36f4ca906 100644 --- a/public/app/features/query/state/QueryRunner.ts +++ b/public/app/features/query/state/QueryRunner.ts @@ -8,6 +8,8 @@ import { QueryRunnerOptions, QueryRunner as QueryRunnerSrv, LoadingState, + compareArrayValues, + compareDataFrameStructures, } from '@grafana/data'; import { getTemplateSrv } from '@grafana/runtime'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; @@ -99,7 +101,24 @@ export class QueryRunner implements QueryRunnerSrv { this.subscription = runRequest(ds, request).subscribe({ next: (data) => { - this.lastResult = preProcessPanelData(data, this.lastResult); + const results = preProcessPanelData(data, this.lastResult); + + // Indicate if the structure has changed since the last query + let structureRev = 1; + if (this.lastResult?.structureRev && this.lastResult.series) { + structureRev = this.lastResult.structureRev; + const sameStructure = compareArrayValues( + results.series, + this.lastResult.series, + compareDataFrameStructures + ); + if (!sameStructure) { + structureRev++; + } + } + results.structureRev = structureRev; + this.lastResult = results; + // Store preprocessed query results for applying overrides later on in the pipeline this.subject.next(this.lastResult); }, diff --git a/public/app/plugins/panel/debug/DebugPanel.tsx b/public/app/plugins/panel/debug/DebugPanel.tsx index d32033af57f..a5fdb66dcf6 100644 --- a/public/app/plugins/panel/debug/DebugPanel.tsx +++ b/public/app/plugins/panel/debug/DebugPanel.tsx @@ -1,13 +1,5 @@ import React, { Component } from 'react'; -import { - compareArrayValues, - compareDataFrameStructures, - fieldReducers, - getFieldDisplayName, - getFrameDisplayName, - PanelProps, - ReducerID, -} from '@grafana/data'; +import { fieldReducers, getFieldDisplayName, getFrameDisplayName, PanelProps, ReducerID } from '@grafana/data'; import { DebugPanelOptions, UpdateCounters, UpdateConfig } from './types'; import { IconButton } from '@grafana/ui'; @@ -30,13 +22,8 @@ export class DebugPanel extends Component { this.counters.dataChanged++; if (options.counters?.schemaChanged) { - const oldSeries = prevProps.data?.series; - const series = data.series; - if (series && oldSeries) { - const sameStructure = compareArrayValues(series, oldSeries, compareDataFrameStructures); - if (!sameStructure) { - this.counters.schemaChanged++; - } + if (data.structureRev !== prevProps.data.structureRev) { + this.counters.schemaChanged++; } } } @@ -49,7 +36,7 @@ export class DebugPanel extends Component { dataChanged: 0, schemaChanged: 0, }; - this.forceUpdate(); + this.setState(this.state); // force update }; render() { diff --git a/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx b/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx index 47e66216d00..1da1f4063f8 100644 --- a/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx +++ b/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx @@ -52,6 +52,7 @@ export const TimeSeriesPanel: React.FC = ({ return ( = ({ return (