MinMax: keep global min/main in field state (#29406)

This commit is contained in:
Ryan McKinley 2020-11-29 09:22:16 -08:00 committed by GitHub
parent ea64dda1e3
commit 097dcc456a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 65 additions and 53 deletions

View File

@ -77,7 +77,6 @@ export interface GetFieldDisplayValuesOptions {
replaceVariables: InterpolateFunction; replaceVariables: InterpolateFunction;
sparkline?: boolean; // Calculate the sparkline sparkline?: boolean; // Calculate the sparkline
theme: GrafanaTheme; theme: GrafanaTheme;
autoMinMax?: boolean;
timeZone?: TimeZone; timeZone?: TimeZone;
} }
@ -122,7 +121,11 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi
continue; continue;
} }
const config = field.config; // already set by the prepare task let config = field.config; // already set by the prepare task
if (field.state?.range) {
// Us the global min/max values
config = { ...config, ...field.state?.range };
}
const displayName = field.config.displayName ?? defaultDisplayName; const displayName = field.config.displayName ?? defaultDisplayName;
const display = const display =

View File

@ -293,16 +293,15 @@ describe('applyFieldOverrides', () => {
replaceVariables: (undefined as any) as InterpolateFunction, replaceVariables: (undefined as any) as InterpolateFunction,
getDataSourceSettingsByUid: undefined as any, getDataSourceSettingsByUid: undefined as any,
theme: getTestTheme(), theme: getTestTheme(),
autoMinMax: true,
})[0]; })[0];
const valueColumn = data.fields[1]; const valueColumn = data.fields[1];
const config = valueColumn.config; const range = valueColumn.state!.range!;
// Keep max from the original setting // Keep max from the original setting
expect(config.max).toEqual(0); expect(range.max).toEqual(0);
// Don't Automatically pick the min value // Don't Automatically pick the min value
expect(config.min).toEqual(-20); expect(range.min).toEqual(-20);
}); });
it('getLinks should use applied field config', () => { it('getLinks should use applied field config', () => {
@ -317,7 +316,6 @@ describe('applyFieldOverrides', () => {
}) as InterpolateFunction, }) as InterpolateFunction,
getDataSourceSettingsByUid: undefined as any, getDataSourceSettingsByUid: undefined as any,
theme: getTestTheme(), theme: getTestTheme(),
autoMinMax: true,
fieldConfigRegistry: customFieldRegistry, fieldConfigRegistry: customFieldRegistry,
})[0]; })[0];

View File

@ -13,6 +13,7 @@ import {
GrafanaTheme, GrafanaTheme,
InterpolateFunction, InterpolateFunction,
LinkModel, LinkModel,
NumericRange,
ScopedVars, ScopedVars,
TimeZone, TimeZone,
ValueLinkConfig, ValueLinkConfig,
@ -40,12 +41,7 @@ interface OverrideProps {
properties: DynamicConfigValue[]; properties: DynamicConfigValue[];
} }
interface GlobalMinMax { export function findNumericFieldMinMax(data: DataFrame[]): NumericRange {
min?: number | null;
max?: number | null;
}
export function findNumericFieldMinMax(data: DataFrame[]): GlobalMinMax {
let min: number | null = null; let min: number | null = null;
let max: number | null = null; let max: number | null = null;
@ -69,7 +65,7 @@ export function findNumericFieldMinMax(data: DataFrame[]): GlobalMinMax {
} }
} }
return { min, max }; return { min, max, delta: (max ?? 0) - (min ?? 0) };
} }
/** /**
@ -88,7 +84,7 @@ export function applyFieldOverrides(options: ApplyFieldOverrideOptions): DataFra
const fieldConfigRegistry = options.fieldConfigRegistry ?? standardFieldConfigEditorRegistry; const fieldConfigRegistry = options.fieldConfigRegistry ?? standardFieldConfigEditorRegistry;
let seriesIndex = 0; let seriesIndex = 0;
let range: GlobalMinMax | undefined = undefined; let globalRange: NumericRange | undefined = undefined;
// Prepare the Matchers // Prepare the Matchers
const override: OverrideProps[] = []; const override: OverrideProps[] = [];
@ -178,18 +174,14 @@ export function applyFieldOverrides(options: ApplyFieldOverrideOptions): DataFra
} }
// Set the Min/Max value automatically // Set the Min/Max value automatically
if (options.autoMinMax && field.type === FieldType.number) { let range: NumericRange | undefined = undefined;
if (!isNumber(config.min) || !isNumber(config.max)) { if (field.type === FieldType.number) {
if (!range) { if (!globalRange && (!isNumber(config.min) || !isNumber(config.max))) {
range = findNumericFieldMinMax(options.data!); // Global value globalRange = findNumericFieldMinMax(options.data!);
}
if (!isNumber(config.min)) {
config.min = range.min;
}
if (!isNumber(config.max)) {
config.max = range.max;
}
} }
const min = config.min ?? globalRange!.min;
const max = config.max ?? globalRange!.max;
range = { min, max, delta: max! - min! };
} }
// Some color modes needs series index to assign field color so we count // Some color modes needs series index to assign field color so we count
@ -207,6 +199,7 @@ export function applyFieldOverrides(options: ApplyFieldOverrideOptions): DataFra
...field.state, ...field.state,
displayName: null, displayName: null,
seriesIndex, seriesIndex,
range,
}, },
}; };

View File

@ -32,7 +32,6 @@ describe('getFieldDisplayValuesProxy', () => {
getDataSourceSettingsByUid: (val: string) => ({} as any), getDataSourceSettingsByUid: (val: string) => ({} as any),
timeZone: 'utc', timeZone: 'utc',
theme: getTestTheme(), theme: getTestTheme(),
autoMinMax: true,
})[0]; })[0];
it('should define all display functions', () => { it('should define all display functions', () => {

View File

@ -1,6 +1,6 @@
import { isNumber } from 'lodash'; import { isNumber } from 'lodash';
import { reduceField, ReducerID } from '../transformations/fieldReducer'; import { reduceField, ReducerID } from '../transformations/fieldReducer';
import { Field, FieldType, GrafanaTheme, Threshold } from '../types'; import { Field, FieldConfig, FieldType, GrafanaTheme, NumericRange, Threshold } from '../types';
import { getFieldColorModeForField } from './fieldColor'; import { getFieldColorModeForField } from './fieldColor';
import { getActiveThresholdForValue } from './thresholds'; import { getActiveThresholdForValue } from './thresholds';
@ -15,7 +15,7 @@ export type ScaleCalculator = (value: number) => ScaledValue;
export function getScaleCalculator(field: Field, theme: GrafanaTheme): ScaleCalculator { export function getScaleCalculator(field: Field, theme: GrafanaTheme): ScaleCalculator {
const mode = getFieldColorModeForField(field); const mode = getFieldColorModeForField(field);
const getColor = mode.getCalculator(field, theme); const getColor = mode.getCalculator(field, theme);
const info = getMinMaxAndDelta(field); const info = field.state?.range ?? getMinMaxAndDelta(field);
return (value: number) => { return (value: number) => {
let percent = 0; let percent = 0;
@ -34,13 +34,7 @@ export function getScaleCalculator(field: Field, theme: GrafanaTheme): ScaleCalc
}; };
} }
interface FieldMinMaxInfo { function getMinMaxAndDelta(field: Field): NumericRange {
min?: number | null;
max?: number | null;
delta: number;
}
function getMinMaxAndDelta(field: Field): FieldMinMaxInfo {
if (field.type !== FieldType.number) { if (field.type !== FieldType.number) {
return { min: 0, max: 100, delta: 100 }; return { min: 0, max: 100, delta: 100 };
} }
@ -70,3 +64,15 @@ function getMinMaxAndDelta(field: Field): FieldMinMaxInfo {
delta: max! - min!, delta: max! - min!,
}; };
} }
export function getFieldConfigWithMinMax(field: Field, local?: boolean): FieldConfig {
const { config } = field;
let { min, max } = config;
if (isNumber(min) && !isNumber(max)) {
return config; // noop
}
if (local || !field.state?.range) {
return { ...config, ...getMinMaxAndDelta(field) };
}
return { ...config, ...field.state.range };
}

View File

@ -126,6 +126,13 @@ export interface FieldState {
*/ */
calcs?: FieldCalcs; calcs?: FieldCalcs;
/**
* The numeric range for values in this field. This value will respect the min/max
* set in field config, or when set to `auto` this will have the min/max for all data
* in the response
*/
range?: NumericRange;
/** /**
* Appropriate values for templating * Appropriate values for templating
*/ */
@ -138,6 +145,12 @@ export interface FieldState {
seriesIndex?: number; seriesIndex?: number;
} }
export interface NumericRange {
min?: number | null;
max?: number | null;
delta: number;
}
export interface DataFrame extends QueryResultBase { export interface DataFrame extends QueryResultBase {
name?: string; name?: string;
fields: Field[]; // All fields of equal length fields: Field[]; // All fields of equal length

View File

@ -117,7 +117,6 @@ export interface ApplyFieldOverrideOptions {
getDataSourceSettingsByUid: (uid: string) => DataSourceInstanceSettings | undefined; getDataSourceSettingsByUid: (uid: string) => DataSourceInstanceSettings | undefined;
theme: GrafanaTheme; theme: GrafanaTheme;
timeZone?: TimeZone; timeZone?: TimeZone;
autoMinMax?: boolean;
fieldConfigRegistry?: FieldConfigOptionsRegistry; fieldConfigRegistry?: FieldConfigOptionsRegistry;
} }

View File

@ -2,6 +2,7 @@ import React, { FC } from 'react';
import { ThresholdsConfig, ThresholdsMode, VizOrientation } from '@grafana/data'; import { ThresholdsConfig, ThresholdsMode, VizOrientation } from '@grafana/data';
import { BarGauge, BarGaugeDisplayMode } from '../BarGauge/BarGauge'; import { BarGauge, BarGaugeDisplayMode } from '../BarGauge/BarGauge';
import { TableCellProps, TableCellDisplayMode } from './types'; import { TableCellProps, TableCellDisplayMode } from './types';
import { getFieldConfigWithMinMax } from '@grafana/data/src/field/scale';
const defaultScale: ThresholdsConfig = { const defaultScale: ThresholdsConfig = {
mode: ThresholdsMode.Absolute, mode: ThresholdsMode.Absolute,
@ -20,7 +21,7 @@ const defaultScale: ThresholdsConfig = {
export const BarGaugeCell: FC<TableCellProps> = props => { export const BarGaugeCell: FC<TableCellProps> = props => {
const { field, column, tableStyles, cell, cellProps } = props; const { field, column, tableStyles, cell, cellProps } = props;
let { config } = field; let config = getFieldConfigWithMinMax(field, false);
if (!config.thresholds) { if (!config.thresholds) {
config = { config = {
...config, ...config,

View File

@ -22,12 +22,10 @@ describe('UPlotConfigBuilder', () => {
"axes": Array [], "axes": Array [],
"scales": Object { "scales": Object {
"scale-x": Object { "scale-x": Object {
"range": undefined,
"time": true, "time": true,
}, },
"scale-y": Object { "scale-y": Object {
"range": undefined, "range": [Function],
"time": false,
}, },
}, },
"series": Array [ "series": Array [

View File

@ -1,5 +1,4 @@
import isNumber from 'lodash/isNumber'; import uPlot, { Scale } from 'uplot';
import { Scale } from 'uplot';
import { PlotConfigBuilder } from '../types'; import { PlotConfigBuilder } from '../types';
export interface ScaleProps { export interface ScaleProps {
@ -11,12 +10,21 @@ export interface ScaleProps {
export class UPlotScaleBuilder extends PlotConfigBuilder<ScaleProps, Scale> { export class UPlotScaleBuilder extends PlotConfigBuilder<ScaleProps, Scale> {
getConfig() { getConfig() {
const { isTime, scaleKey, min, max } = this.props; const { isTime, scaleKey } = this.props;
const range = isNumber(min) && isNumber(max) ? [min, max] : undefined; if (isTime) {
return {
[scaleKey]: {
time: true, // TODO? this should be based on the query range, not the data
},
};
}
return { return {
[scaleKey]: { [scaleKey]: {
time: !!isTime, range: (u: uPlot, dataMin: number, dataMax: number) => {
range, const { min, max } = this.props;
const [smin, smax] = uPlot.rangeNum(min ?? dataMin, max ?? dataMax, 0.1 as any, true);
return [min ?? smin, max ?? smax];
},
}, },
}; };
} }

View File

@ -16,7 +16,6 @@ export function loadSnapshotData(panel: PanelModel, dashboard: DashboardModel):
defaults: {}, defaults: {},
overrides: [], overrides: [],
}, },
autoMinMax: true,
replaceVariables: panel.replaceVariables, replaceVariables: panel.replaceVariables,
getDataSourceSettingsByUid: getDatasourceSrv().getDataSourceSettingsByUid.bind(getDatasourceSrv()), getDataSourceSettingsByUid: getDatasourceSrv().getDataSourceSettingsByUid.bind(getDatasourceSrv()),
fieldConfigRegistry: panel.plugin!.fieldConfigRegistry, fieldConfigRegistry: panel.plugin!.fieldConfigRegistry,

View File

@ -93,7 +93,6 @@ describe('getFieldLinksSupplier', () => {
getDataSourceSettingsByUid: (val: string) => ({} as any), getDataSourceSettingsByUid: (val: string) => ({} as any),
timeZone: 'utc', timeZone: 'utc',
theme: getTheme(), theme: getTheme(),
autoMinMax: true,
})[0]; })[0];
const rowIndex = 0; const rowIndex = 0;

View File

@ -89,7 +89,6 @@ export class PanelQueryRunner {
...processedData, ...processedData,
series: applyFieldOverrides({ series: applyFieldOverrides({
timeZone: timeZone, timeZone: timeZone,
autoMinMax: true,
data: processedData.series, data: processedData.series,
...fieldConfig, ...fieldConfig,
}), }),

View File

@ -75,7 +75,6 @@ export class BarGaugePanel extends PureComponent<PanelProps<BarGaugeOptions>> {
replaceVariables, replaceVariables,
theme: config.theme, theme: config.theme,
data: data.series, data: data.series,
autoMinMax: true,
timeZone, timeZone,
}); });
}; };

View File

@ -57,7 +57,6 @@ export class GaugePanel extends PureComponent<PanelProps<GaugeOptions>> {
replaceVariables, replaceVariables,
theme: config.theme, theme: config.theme,
data: data.series, data: data.series,
autoMinMax: true,
timeZone, timeZone,
}); });
}; };

View File

@ -103,7 +103,6 @@ export class StatPanel extends PureComponent<PanelProps<StatPanelOptions>> {
theme: config.theme, theme: config.theme,
data: data.series, data: data.series,
sparkline: options.graphMode !== BigValueGraphMode.None, sparkline: options.graphMode !== BigValueGraphMode.None,
autoMinMax: true,
timeZone, timeZone,
}); });
}; };