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
This commit is contained in:
Ryan McKinley 2021-04-20 02:05:50 -07:00 committed by GitHub
parent b3ce655b67
commit dff78561bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 99 additions and 43 deletions

View File

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

View File

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

View File

@ -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<GraphNGProps, GraphNGState> {
}
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<GraphNGProps, GraphNGState> {
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 };

View File

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

View File

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

View File

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

View File

@ -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<Props> {
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<Props> {
dataChanged: 0,
schemaChanged: 0,
};
this.forceUpdate();
this.setState(this.state); // force update
};
render() {

View File

@ -52,6 +52,7 @@ export const TimeSeriesPanel: React.FC<TimeSeriesPanelProps> = ({
return (
<GraphNG
data={data.series}
structureRev={data.structureRev}
timeRange={timeRange}
timeZone={timeZone}
width={width}

View File

@ -53,6 +53,7 @@ export const XYChartPanel: React.FC<XYChartPanelProps> = ({
return (
<GraphNG
data={frames}
structureRev={data.structureRev}
fields={dims.fields}
timeRange={timeRange}
timeZone={timeZone}