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
|
* @beta
|
||||||
*/
|
*/
|
||||||
export function compareDataFrameStructures(a: DataFrame, b: DataFrame): boolean {
|
export function compareDataFrameStructures(a: DataFrame, b: DataFrame, skipConfig?: boolean): boolean {
|
||||||
if (a === b) {
|
if (a === b) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -32,6 +32,11 @@ export function compareDataFrameStructures(a: DataFrame, b: DataFrame): boolean
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Do not check the config fields
|
||||||
|
if (skipConfig) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const cfgA = fA.config as any;
|
const cfgA = fA.config as any;
|
||||||
const cfgB = fB.config as any;
|
const cfgB = fB.config as any;
|
||||||
|
|
||||||
|
@ -29,6 +29,12 @@ export interface PanelData {
|
|||||||
/** Contains data frames with field overrides applied */
|
/** Contains data frames with field overrides applied */
|
||||||
series: DataFrame[];
|
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 */
|
/** A list of annotation items */
|
||||||
annotations?: DataFrame[];
|
annotations?: DataFrame[];
|
||||||
|
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { AlignedData } from 'uplot';
|
import { AlignedData } from 'uplot';
|
||||||
import {
|
import {
|
||||||
compareArrayValues,
|
|
||||||
compareDataFrameStructures,
|
|
||||||
DataFrame,
|
DataFrame,
|
||||||
DataFrameFieldIndex,
|
DataFrameFieldIndex,
|
||||||
FieldMatcherID,
|
FieldMatcherID,
|
||||||
@ -32,6 +30,7 @@ export interface GraphNGProps extends Themeable {
|
|||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
data: DataFrame[];
|
data: DataFrame[];
|
||||||
|
structureRev?: number; // a number that will change when the data[] structure changes
|
||||||
timeRange: TimeRange;
|
timeRange: TimeRange;
|
||||||
legend: VizLegendOptions;
|
legend: VizLegendOptions;
|
||||||
timeZone: TimeZone;
|
timeZone: TimeZone;
|
||||||
@ -119,7 +118,7 @@ class UnthemedGraphNG extends React.Component<GraphNGProps, GraphNGState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: GraphNGProps) {
|
componentDidUpdate(prevProps: GraphNGProps) {
|
||||||
const { data, theme } = this.props;
|
const { data, theme, structureRev } = this.props;
|
||||||
const { alignedDataFrame } = this.state;
|
const { alignedDataFrame } = this.state;
|
||||||
let shouldConfigUpdate = false;
|
let shouldConfigUpdate = false;
|
||||||
let stateUpdate = {} as GraphNGState;
|
let stateUpdate = {} as GraphNGState;
|
||||||
@ -133,8 +132,7 @@ class UnthemedGraphNG extends React.Component<GraphNGProps, GraphNGState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasStructureChanged = !compareArrayValues(data, prevProps.data, compareDataFrameStructures);
|
const hasStructureChanged = structureRev !== prevProps.structureRev || !structureRev;
|
||||||
|
|
||||||
if (shouldConfigUpdate || hasStructureChanged) {
|
if (shouldConfigUpdate || hasStructureChanged) {
|
||||||
const builder = preparePlotConfigBuilder(alignedDataFrame, theme, this.getTimeRange, this.getTimeZone);
|
const builder = preparePlotConfigBuilder(alignedDataFrame, theme, this.getTimeRange, this.getTimeZone);
|
||||||
stateUpdate = { ...stateUpdate, config: builder };
|
stateUpdate = { ...stateUpdate, config: builder };
|
||||||
|
@ -1,11 +1,4 @@
|
|||||||
import {
|
import { DataQueryError, LoadingState, PanelData } from '@grafana/data';
|
||||||
compareArrayValues,
|
|
||||||
compareDataFrameStructures,
|
|
||||||
DataFrame,
|
|
||||||
DataQueryError,
|
|
||||||
LoadingState,
|
|
||||||
PanelData,
|
|
||||||
} from '@grafana/data';
|
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { PanelModel } from '../../state';
|
import { PanelModel } from '../../state';
|
||||||
import { Unsubscribable } from 'rxjs';
|
import { Unsubscribable } from 'rxjs';
|
||||||
@ -30,7 +23,7 @@ export const usePanelLatestData = (
|
|||||||
const [latestData, setLatestData] = useState<PanelData>();
|
const [latestData, setLatestData] = useState<PanelData>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let last: DataFrame[] = [];
|
let lastRev = -1;
|
||||||
let lastUpdate = 0;
|
let lastUpdate = 0;
|
||||||
|
|
||||||
querySubscription.current = panel
|
querySubscription.current = panel
|
||||||
@ -39,8 +32,7 @@ export const usePanelLatestData = (
|
|||||||
.subscribe({
|
.subscribe({
|
||||||
next: (data) => {
|
next: (data) => {
|
||||||
if (checkSchema) {
|
if (checkSchema) {
|
||||||
const sameStructure = compareArrayValues(last, data.series, compareDataFrameStructures);
|
if (lastRev === data.structureRev) {
|
||||||
if (sameStructure) {
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const elapsed = now - lastUpdate;
|
const elapsed = now - lastUpdate;
|
||||||
if (elapsed < 10000) {
|
if (elapsed < 10000) {
|
||||||
@ -48,7 +40,7 @@ export const usePanelLatestData = (
|
|||||||
}
|
}
|
||||||
lastUpdate = now;
|
lastUpdate = now;
|
||||||
}
|
}
|
||||||
last = data.series;
|
lastRev = data.structureRev ?? -1;
|
||||||
}
|
}
|
||||||
setLatestData(data);
|
setLatestData(data);
|
||||||
},
|
},
|
||||||
|
@ -12,8 +12,11 @@ import { isSharedDashboardQuery, runSharedRequest } from '../../../plugins/datas
|
|||||||
// Types
|
// Types
|
||||||
import {
|
import {
|
||||||
applyFieldOverrides,
|
applyFieldOverrides,
|
||||||
|
compareArrayValues,
|
||||||
|
compareDataFrameStructures,
|
||||||
CoreApp,
|
CoreApp,
|
||||||
DataConfigSource,
|
DataConfigSource,
|
||||||
|
DataFrame,
|
||||||
DataQuery,
|
DataQuery,
|
||||||
DataQueryRequest,
|
DataQueryRequest,
|
||||||
DataSourceApi,
|
DataSourceApi,
|
||||||
@ -72,30 +75,74 @@ export class PanelQueryRunner {
|
|||||||
*/
|
*/
|
||||||
getData(options: GetDataOptions): Observable<PanelData> {
|
getData(options: GetDataOptions): Observable<PanelData> {
|
||||||
const { withFieldConfig, withTransforms } = options;
|
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(
|
return this.subject.pipe(
|
||||||
this.getTransformationsStream(withTransforms),
|
this.getTransformationsStream(withTransforms),
|
||||||
map((data: PanelData) => {
|
map((data: PanelData) => {
|
||||||
let processedData = data;
|
let processedData = data;
|
||||||
|
let sameStructure = false;
|
||||||
|
|
||||||
if (withFieldConfig) {
|
if (withFieldConfig && data.series?.length) {
|
||||||
// Apply field defaults and overrides
|
// Apply field defaults and overrides
|
||||||
const fieldConfig = this.dataConfigSource.getFieldOverrideOptions();
|
let fieldConfig = this.dataConfigSource.getFieldOverrideOptions();
|
||||||
const timeZone = data.request?.timezone ?? 'browser';
|
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 = {
|
||||||
...processedData,
|
...processedData,
|
||||||
series: applyFieldOverrides({
|
series: applyFieldOverrides({
|
||||||
timeZone: timeZone,
|
timeZone: data.request?.timezone ?? 'browser',
|
||||||
data: processedData.series,
|
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,
|
QueryRunnerOptions,
|
||||||
QueryRunner as QueryRunnerSrv,
|
QueryRunner as QueryRunnerSrv,
|
||||||
LoadingState,
|
LoadingState,
|
||||||
|
compareArrayValues,
|
||||||
|
compareDataFrameStructures,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { getTemplateSrv } from '@grafana/runtime';
|
import { getTemplateSrv } from '@grafana/runtime';
|
||||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||||
@ -99,7 +101,24 @@ export class QueryRunner implements QueryRunnerSrv {
|
|||||||
|
|
||||||
this.subscription = runRequest(ds, request).subscribe({
|
this.subscription = runRequest(ds, request).subscribe({
|
||||||
next: (data) => {
|
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
|
// Store preprocessed query results for applying overrides later on in the pipeline
|
||||||
this.subject.next(this.lastResult);
|
this.subject.next(this.lastResult);
|
||||||
},
|
},
|
||||||
|
@ -1,13 +1,5 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import {
|
import { fieldReducers, getFieldDisplayName, getFrameDisplayName, PanelProps, ReducerID } from '@grafana/data';
|
||||||
compareArrayValues,
|
|
||||||
compareDataFrameStructures,
|
|
||||||
fieldReducers,
|
|
||||||
getFieldDisplayName,
|
|
||||||
getFrameDisplayName,
|
|
||||||
PanelProps,
|
|
||||||
ReducerID,
|
|
||||||
} from '@grafana/data';
|
|
||||||
|
|
||||||
import { DebugPanelOptions, UpdateCounters, UpdateConfig } from './types';
|
import { DebugPanelOptions, UpdateCounters, UpdateConfig } from './types';
|
||||||
import { IconButton } from '@grafana/ui';
|
import { IconButton } from '@grafana/ui';
|
||||||
@ -30,13 +22,8 @@ export class DebugPanel extends Component<Props> {
|
|||||||
this.counters.dataChanged++;
|
this.counters.dataChanged++;
|
||||||
|
|
||||||
if (options.counters?.schemaChanged) {
|
if (options.counters?.schemaChanged) {
|
||||||
const oldSeries = prevProps.data?.series;
|
if (data.structureRev !== prevProps.data.structureRev) {
|
||||||
const series = data.series;
|
this.counters.schemaChanged++;
|
||||||
if (series && oldSeries) {
|
|
||||||
const sameStructure = compareArrayValues(series, oldSeries, compareDataFrameStructures);
|
|
||||||
if (!sameStructure) {
|
|
||||||
this.counters.schemaChanged++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -49,7 +36,7 @@ export class DebugPanel extends Component<Props> {
|
|||||||
dataChanged: 0,
|
dataChanged: 0,
|
||||||
schemaChanged: 0,
|
schemaChanged: 0,
|
||||||
};
|
};
|
||||||
this.forceUpdate();
|
this.setState(this.state); // force update
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -52,6 +52,7 @@ export const TimeSeriesPanel: React.FC<TimeSeriesPanelProps> = ({
|
|||||||
return (
|
return (
|
||||||
<GraphNG
|
<GraphNG
|
||||||
data={data.series}
|
data={data.series}
|
||||||
|
structureRev={data.structureRev}
|
||||||
timeRange={timeRange}
|
timeRange={timeRange}
|
||||||
timeZone={timeZone}
|
timeZone={timeZone}
|
||||||
width={width}
|
width={width}
|
||||||
|
@ -53,6 +53,7 @@ export const XYChartPanel: React.FC<XYChartPanelProps> = ({
|
|||||||
return (
|
return (
|
||||||
<GraphNG
|
<GraphNG
|
||||||
data={frames}
|
data={frames}
|
||||||
|
structureRev={data.structureRev}
|
||||||
fields={dims.fields}
|
fields={dims.fields}
|
||||||
timeRange={timeRange}
|
timeRange={timeRange}
|
||||||
timeZone={timeZone}
|
timeZone={timeZone}
|
||||||
|
Loading…
Reference in New Issue
Block a user