mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
b3ce655b67
commit
dff78561bc
@ -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;
|
||||
|
||||
|
@ -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[];
|
||||
|
||||
|
@ -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 };
|
||||
|
@ -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);
|
||||
},
|
||||
|
@ -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 };
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -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);
|
||||
},
|
||||
|
@ -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() {
|
||||
|
@ -52,6 +52,7 @@ export const TimeSeriesPanel: React.FC<TimeSeriesPanelProps> = ({
|
||||
return (
|
||||
<GraphNG
|
||||
data={data.series}
|
||||
structureRev={data.structureRev}
|
||||
timeRange={timeRange}
|
||||
timeZone={timeZone}
|
||||
width={width}
|
||||
|
@ -53,6 +53,7 @@ export const XYChartPanel: React.FC<XYChartPanelProps> = ({
|
||||
return (
|
||||
<GraphNG
|
||||
data={frames}
|
||||
structureRev={data.structureRev}
|
||||
fields={dims.fields}
|
||||
timeRange={timeRange}
|
||||
timeZone={timeZone}
|
||||
|
Loading…
Reference in New Issue
Block a user