mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
FieldDisplay: move threshold and mapping to Field (#17043)
This commit is contained in:
parent
acc678c309
commit
14caa6a068
@ -1,3 +1,6 @@
|
||||
import { Threshold } from './threshold';
|
||||
import { ValueMapping } from './valueMapping';
|
||||
|
||||
export enum LoadingState {
|
||||
NotStarted = 'NotStarted',
|
||||
Loading = 'Loading',
|
||||
@ -49,6 +52,12 @@ export interface Field {
|
||||
decimals?: number | null; // Significant digits (for display)
|
||||
min?: number | null;
|
||||
max?: number | null;
|
||||
|
||||
// Convert input values into a display value
|
||||
mappings?: ValueMapping[];
|
||||
|
||||
// Must be sorted by 'value', first value is always -Infinity
|
||||
thresholds?: Threshold[];
|
||||
}
|
||||
|
||||
export interface Labels {
|
||||
|
@ -1,5 +1,4 @@
|
||||
export interface Threshold {
|
||||
index: number;
|
||||
value: number;
|
||||
color: string;
|
||||
}
|
||||
|
@ -1,23 +1,22 @@
|
||||
import { Threshold } from '../types';
|
||||
|
||||
export function getThresholdForValue(
|
||||
thresholds: Threshold[],
|
||||
value: number | null | string | undefined
|
||||
): Threshold | null {
|
||||
if (thresholds.length === 1) {
|
||||
return thresholds[0];
|
||||
export function getActiveThreshold(value: number, thresholds: Threshold[]): Threshold {
|
||||
let active = thresholds[0];
|
||||
for (const threshold of thresholds) {
|
||||
if (value >= threshold.value) {
|
||||
active = threshold;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const atThreshold = thresholds.filter(threshold => (value as number) === threshold.value)[0];
|
||||
if (atThreshold) {
|
||||
return atThreshold;
|
||||
}
|
||||
|
||||
const belowThreshold = thresholds.filter(threshold => (value as number) > threshold.value);
|
||||
if (belowThreshold.length > 0) {
|
||||
const nearestThreshold = belowThreshold.sort((t1: Threshold, t2: Threshold) => t2.value - t1.value)[0];
|
||||
return nearestThreshold;
|
||||
}
|
||||
|
||||
return null;
|
||||
return active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the thresholds
|
||||
*/
|
||||
export function sortThresholds(thresholds: Threshold[]) {
|
||||
return thresholds.sort((t1, t2) => {
|
||||
return t1.value - t2.value;
|
||||
});
|
||||
}
|
||||
|
@ -49,9 +49,9 @@ function addBarGaugeStory(name: string, overrides: Partial<Props>) {
|
||||
orientation: VizOrientation.Vertical,
|
||||
displayMode: 'basic',
|
||||
thresholds: [
|
||||
{ index: 0, value: -Infinity, color: 'green' },
|
||||
{ index: 1, value: threshold1Value, color: threshold1Color },
|
||||
{ index: 1, value: threshold2Value, color: threshold2Color },
|
||||
{ value: -Infinity, color: 'green' },
|
||||
{ value: threshold1Value, color: threshold1Color },
|
||||
{ value: threshold2Value, color: threshold2Color },
|
||||
],
|
||||
};
|
||||
|
||||
|
@ -25,11 +25,7 @@ function getProps(propOverrides?: Partial<Props>): Props {
|
||||
maxValue: 100,
|
||||
minValue: 0,
|
||||
displayMode: 'basic',
|
||||
thresholds: [
|
||||
{ index: 0, value: -Infinity, color: 'green' },
|
||||
{ index: 1, value: 70, color: 'orange' },
|
||||
{ index: 2, value: 90, color: 'red' },
|
||||
],
|
||||
thresholds: [{ value: -Infinity, color: 'green' }, { value: 70, color: 'orange' }, { value: 90, color: 'red' }],
|
||||
height: 300,
|
||||
width: 300,
|
||||
value: {
|
||||
|
@ -7,7 +7,7 @@ import { getColorFromHexRgbOrName } from '../../utils';
|
||||
|
||||
// Types
|
||||
import { DisplayValue, Themeable, VizOrientation } from '../../types';
|
||||
import { Threshold, TimeSeriesValue, getThresholdForValue } from '@grafana/data';
|
||||
import { Threshold, TimeSeriesValue, getActiveThreshold } from '@grafana/data';
|
||||
|
||||
const MIN_VALUE_HEIGHT = 18;
|
||||
const MAX_VALUE_HEIGHT = 50;
|
||||
@ -87,8 +87,14 @@ export class BarGauge extends PureComponent<Props> {
|
||||
|
||||
getCellColor(positionValue: TimeSeriesValue): CellColors {
|
||||
const { thresholds, theme, value } = this.props;
|
||||
const activeThreshold = getThresholdForValue(thresholds, positionValue);
|
||||
if (positionValue === null) {
|
||||
return {
|
||||
background: 'gray',
|
||||
border: 'gray',
|
||||
};
|
||||
}
|
||||
|
||||
const activeThreshold = getActiveThreshold(positionValue, thresholds);
|
||||
if (activeThreshold !== null) {
|
||||
const color = getColorFromHexRgbOrName(activeThreshold.color, theme.type);
|
||||
|
||||
@ -474,7 +480,7 @@ export function getBarGradient(props: Props, maxSize: number): string {
|
||||
export function getValueColor(props: Props): string {
|
||||
const { thresholds, theme, value } = props;
|
||||
|
||||
const activeThreshold = getThresholdForValue(thresholds, value.numeric);
|
||||
const activeThreshold = getActiveThreshold(value.numeric, thresholds);
|
||||
|
||||
if (activeThreshold !== null) {
|
||||
return getColorFromHexRgbOrName(activeThreshold.color, theme.type);
|
||||
|
@ -14,7 +14,7 @@ const setup = (propOverrides?: object) => {
|
||||
minValue: 0,
|
||||
showThresholdMarkers: true,
|
||||
showThresholdLabels: false,
|
||||
thresholds: [{ index: 0, value: -Infinity, color: '#7EB26D' }],
|
||||
thresholds: [{ value: -Infinity, color: '#7EB26D' }],
|
||||
height: 300,
|
||||
width: 300,
|
||||
value: {
|
||||
@ -48,9 +48,9 @@ describe('Get thresholds formatted', () => {
|
||||
it('should get the correct formatted values when thresholds are added', () => {
|
||||
const { instance } = setup({
|
||||
thresholds: [
|
||||
{ index: 0, value: -Infinity, color: '#7EB26D' },
|
||||
{ index: 1, value: 50, color: '#EAB839' },
|
||||
{ index: 2, value: 75, color: '#6ED0E0' },
|
||||
{ value: -Infinity, color: '#7EB26D' },
|
||||
{ value: 50, color: '#EAB839' },
|
||||
{ value: 75, color: '#6ED0E0' },
|
||||
],
|
||||
});
|
||||
|
||||
|
@ -43,12 +43,12 @@ export class Gauge extends PureComponent<Props> {
|
||||
const lastThreshold = thresholds[thresholds.length - 1];
|
||||
|
||||
return [
|
||||
...thresholds.map(threshold => {
|
||||
if (threshold.index === 0) {
|
||||
...thresholds.map((threshold, index) => {
|
||||
if (index === 0) {
|
||||
return { value: minValue, color: getColorFromHexRgbOrName(threshold.color, theme.type) };
|
||||
}
|
||||
|
||||
const previousThreshold = thresholds[threshold.index - 1];
|
||||
const previousThreshold = thresholds[index - 1];
|
||||
return { value: threshold.value, color: getColorFromHexRgbOrName(previousThreshold.color, theme.type) };
|
||||
}),
|
||||
{ value: maxValue, color: getColorFromHexRgbOrName(lastThreshold.color, theme.type) },
|
||||
|
@ -3,7 +3,7 @@ import omit from 'lodash/omit';
|
||||
|
||||
import { VizOrientation, PanelModel } from '../../types/panel';
|
||||
import { FieldDisplayOptions } from '../../utils/fieldDisplay';
|
||||
import { Field, getFieldReducers } from '@grafana/data';
|
||||
import { Field, getFieldReducers, Threshold, sortThresholds } from '@grafana/data';
|
||||
|
||||
export interface SingleStatBaseOptions {
|
||||
fieldOptions: FieldDisplayOptions;
|
||||
@ -39,18 +39,16 @@ export const sharedSingleStatMigrationCheck = (panel: PanelModel<SingleStatBaseO
|
||||
const { valueOptions } = old;
|
||||
|
||||
const fieldOptions = (old.fieldOptions = {} as FieldDisplayOptions);
|
||||
fieldOptions.mappings = old.valueMappings;
|
||||
fieldOptions.thresholds = old.thresholds;
|
||||
|
||||
const field = (fieldOptions.defaults = {} as Field);
|
||||
if (valueOptions) {
|
||||
field.unit = valueOptions.unit;
|
||||
field.decimals = valueOptions.decimals;
|
||||
field.mappings = old.valueMappings;
|
||||
field.thresholds = migrateOldThresholds(old.thresholds);
|
||||
field.unit = valueOptions.unit;
|
||||
field.decimals = valueOptions.decimals;
|
||||
|
||||
// Make sure the stats have a valid name
|
||||
if (valueOptions.stat) {
|
||||
fieldOptions.calcs = getFieldReducers([valueOptions.stat]).map(s => s.id);
|
||||
}
|
||||
// Make sure the stats have a valid name
|
||||
if (valueOptions.stat) {
|
||||
fieldOptions.calcs = getFieldReducers([valueOptions.stat]).map(s => s.id);
|
||||
}
|
||||
|
||||
field.min = old.minValue;
|
||||
@ -58,7 +56,33 @@ export const sharedSingleStatMigrationCheck = (panel: PanelModel<SingleStatBaseO
|
||||
|
||||
// remove old props
|
||||
return omit(old, 'valueMappings', 'thresholds', 'valueOptions', 'minValue', 'maxValue');
|
||||
} else if (old.fieldOptions) {
|
||||
// Move mappins & thresholds to field defautls (6.4+)
|
||||
const { mappings, thresholds, ...fieldOptions } = old.fieldOptions;
|
||||
fieldOptions.defaults = {
|
||||
mappings,
|
||||
thresholds: migrateOldThresholds(thresholds),
|
||||
...fieldOptions.defaults,
|
||||
};
|
||||
old.fieldOptions = fieldOptions;
|
||||
return old;
|
||||
}
|
||||
|
||||
return panel.options;
|
||||
};
|
||||
|
||||
export function migrateOldThresholds(thresholds?: any[]): Threshold[] | undefined {
|
||||
if (!thresholds || !thresholds.length) {
|
||||
return undefined;
|
||||
}
|
||||
const copy = thresholds.map(t => {
|
||||
return {
|
||||
// Drops 'index'
|
||||
value: t.value === null ? -Infinity : t.value,
|
||||
color: t.color,
|
||||
};
|
||||
});
|
||||
sortThresholds(copy);
|
||||
copy[0].value = -Infinity;
|
||||
return copy;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { ChangeEvent } from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { ThresholdsEditor, Props } from './ThresholdsEditor';
|
||||
import { ThresholdsEditor, Props, threshodsWithoutKey } from './ThresholdsEditor';
|
||||
import { colors } from '../../utils';
|
||||
|
||||
const setup = (propOverrides?: Partial<Props>) => {
|
||||
@ -20,6 +20,10 @@ const setup = (propOverrides?: Partial<Props>) => {
|
||||
};
|
||||
};
|
||||
|
||||
function getCurrentThresholds(editor: ThresholdsEditor) {
|
||||
return threshodsWithoutKey(editor.state.thresholds);
|
||||
}
|
||||
|
||||
describe('Render', () => {
|
||||
it('should render with base threshold', () => {
|
||||
const { wrapper } = setup();
|
||||
@ -32,60 +36,55 @@ describe('Initialization', () => {
|
||||
it('should add a base threshold if missing', () => {
|
||||
const { instance } = setup();
|
||||
|
||||
expect(instance.state.thresholds).toEqual([{ index: 0, value: -Infinity, color: colors[0] }]);
|
||||
expect(getCurrentThresholds(instance)).toEqual([{ value: -Infinity, color: colors[0] }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Add threshold', () => {
|
||||
it('should not add threshold at index 0', () => {
|
||||
const { instance } = setup();
|
||||
|
||||
instance.onAddThreshold(0);
|
||||
|
||||
expect(instance.state.thresholds).toEqual([{ index: 0, value: -Infinity, color: colors[0] }]);
|
||||
});
|
||||
|
||||
it('should add threshold', () => {
|
||||
const { instance } = setup();
|
||||
|
||||
instance.onAddThreshold(1);
|
||||
instance.onAddThresholdAfter(instance.state.thresholds[0]);
|
||||
|
||||
expect(instance.state.thresholds).toEqual([
|
||||
{ index: 0, value: -Infinity, color: colors[0] },
|
||||
{ index: 1, value: 50, color: colors[2] },
|
||||
expect(getCurrentThresholds(instance)).toEqual([
|
||||
{ value: -Infinity, color: colors[0] }, // 0
|
||||
{ value: 50, color: colors[2] }, // 1
|
||||
]);
|
||||
});
|
||||
|
||||
it('should add another threshold above a first', () => {
|
||||
const { instance } = setup({
|
||||
thresholds: [{ index: 0, value: -Infinity, color: colors[0] }, { index: 1, value: 50, color: colors[2] }],
|
||||
thresholds: [
|
||||
{ value: -Infinity, color: colors[0] }, // 0
|
||||
{ value: 50, color: colors[2] }, // 1
|
||||
],
|
||||
});
|
||||
|
||||
instance.onAddThreshold(2);
|
||||
instance.onAddThresholdAfter(instance.state.thresholds[1]);
|
||||
|
||||
expect(instance.state.thresholds).toEqual([
|
||||
{ index: 0, value: -Infinity, color: colors[0] },
|
||||
{ index: 1, value: 50, color: colors[2] },
|
||||
{ index: 2, value: 75, color: colors[3] },
|
||||
expect(getCurrentThresholds(instance)).toEqual([
|
||||
{ value: -Infinity, color: colors[0] }, // 0
|
||||
{ value: 50, color: colors[2] }, // 1
|
||||
{ value: 75, color: colors[3] }, // 2
|
||||
]);
|
||||
});
|
||||
|
||||
it('should add another threshold between first and second index', () => {
|
||||
const { instance } = setup({
|
||||
thresholds: [
|
||||
{ index: 0, value: -Infinity, color: colors[0] },
|
||||
{ index: 1, value: 50, color: colors[2] },
|
||||
{ index: 2, value: 75, color: colors[3] },
|
||||
{ value: -Infinity, color: colors[0] },
|
||||
{ value: 50, color: colors[2] },
|
||||
{ value: 75, color: colors[3] },
|
||||
],
|
||||
});
|
||||
|
||||
instance.onAddThreshold(2);
|
||||
instance.onAddThresholdAfter(instance.state.thresholds[1]);
|
||||
|
||||
expect(instance.state.thresholds).toEqual([
|
||||
{ index: 0, value: -Infinity, color: colors[0] },
|
||||
{ index: 1, value: 50, color: colors[2] },
|
||||
{ index: 2, value: 62.5, color: colors[4] },
|
||||
{ index: 3, value: 75, color: colors[3] },
|
||||
expect(getCurrentThresholds(instance)).toEqual([
|
||||
{ value: -Infinity, color: colors[0] },
|
||||
{ value: 50, color: colors[2] },
|
||||
{ value: 62.5, color: colors[4] },
|
||||
{ value: 75, color: colors[3] },
|
||||
]);
|
||||
});
|
||||
});
|
||||
@ -93,30 +92,30 @@ describe('Add threshold', () => {
|
||||
describe('Remove threshold', () => {
|
||||
it('should not remove threshold at index 0', () => {
|
||||
const thresholds = [
|
||||
{ index: 0, value: -Infinity, color: '#7EB26D' },
|
||||
{ index: 1, value: 50, color: '#EAB839' },
|
||||
{ index: 2, value: 75, color: '#6ED0E0' },
|
||||
{ value: -Infinity, color: '#7EB26D' },
|
||||
{ value: 50, color: '#EAB839' },
|
||||
{ value: 75, color: '#6ED0E0' },
|
||||
];
|
||||
const { instance } = setup({ thresholds });
|
||||
|
||||
instance.onRemoveThreshold(thresholds[0]);
|
||||
instance.onRemoveThreshold(instance.state.thresholds[0]);
|
||||
|
||||
expect(instance.state.thresholds).toEqual(thresholds);
|
||||
expect(getCurrentThresholds(instance)).toEqual(thresholds);
|
||||
});
|
||||
|
||||
it('should remove threshold', () => {
|
||||
const thresholds = [
|
||||
{ index: 0, value: -Infinity, color: '#7EB26D' },
|
||||
{ index: 1, value: 50, color: '#EAB839' },
|
||||
{ index: 2, value: 75, color: '#6ED0E0' },
|
||||
{ value: -Infinity, color: '#7EB26D' },
|
||||
{ value: 50, color: '#EAB839' },
|
||||
{ value: 75, color: '#6ED0E0' },
|
||||
];
|
||||
const { instance } = setup({ thresholds });
|
||||
|
||||
instance.onRemoveThreshold(thresholds[1]);
|
||||
instance.onRemoveThreshold(instance.state.thresholds[1]);
|
||||
|
||||
expect(instance.state.thresholds).toEqual([
|
||||
{ index: 0, value: -Infinity, color: '#7EB26D' },
|
||||
{ index: 1, value: 75, color: '#6ED0E0' },
|
||||
expect(getCurrentThresholds(instance)).toEqual([
|
||||
{ value: -Infinity, color: '#7EB26D' },
|
||||
{ value: 75, color: '#6ED0E0' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
@ -124,25 +123,25 @@ describe('Remove threshold', () => {
|
||||
describe('change threshold value', () => {
|
||||
it('should not change threshold at index 0', () => {
|
||||
const thresholds = [
|
||||
{ index: 0, value: -Infinity, color: '#7EB26D' },
|
||||
{ index: 1, value: 50, color: '#EAB839' },
|
||||
{ index: 2, value: 75, color: '#6ED0E0' },
|
||||
{ value: -Infinity, color: '#7EB26D' },
|
||||
{ value: 50, color: '#EAB839' },
|
||||
{ value: 75, color: '#6ED0E0' },
|
||||
];
|
||||
const { instance } = setup({ thresholds });
|
||||
|
||||
const mockEvent = ({ target: { value: '12' } } as any) as ChangeEvent<HTMLInputElement>;
|
||||
|
||||
instance.onChangeThresholdValue(mockEvent, thresholds[0]);
|
||||
instance.onChangeThresholdValue(mockEvent, instance.state.thresholds[0]);
|
||||
|
||||
expect(instance.state.thresholds).toEqual(thresholds);
|
||||
expect(getCurrentThresholds(instance)).toEqual(thresholds);
|
||||
});
|
||||
|
||||
it('should update value', () => {
|
||||
const { instance } = setup();
|
||||
const thresholds = [
|
||||
{ index: 0, value: -Infinity, color: '#7EB26D' },
|
||||
{ index: 1, value: 50, color: '#EAB839' },
|
||||
{ index: 2, value: 75, color: '#6ED0E0' },
|
||||
{ value: -Infinity, color: '#7EB26D', key: 1 },
|
||||
{ value: 50, color: '#EAB839', key: 2 },
|
||||
{ value: 75, color: '#6ED0E0', key: 3 },
|
||||
];
|
||||
|
||||
instance.state = {
|
||||
@ -153,10 +152,10 @@ describe('change threshold value', () => {
|
||||
|
||||
instance.onChangeThresholdValue(mockEvent, thresholds[1]);
|
||||
|
||||
expect(instance.state.thresholds).toEqual([
|
||||
{ index: 0, value: -Infinity, color: '#7EB26D' },
|
||||
{ index: 1, value: 78, color: '#EAB839' },
|
||||
{ index: 2, value: 75, color: '#6ED0E0' },
|
||||
expect(getCurrentThresholds(instance)).toEqual([
|
||||
{ value: -Infinity, color: '#7EB26D' },
|
||||
{ value: 78, color: '#EAB839' },
|
||||
{ value: 75, color: '#6ED0E0' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
@ -165,9 +164,9 @@ describe('on blur threshold value', () => {
|
||||
it('should resort rows and update indexes', () => {
|
||||
const { instance } = setup();
|
||||
const thresholds = [
|
||||
{ index: 0, value: -Infinity, color: '#7EB26D' },
|
||||
{ index: 1, value: 78, color: '#EAB839' },
|
||||
{ index: 2, value: 75, color: '#6ED0E0' },
|
||||
{ value: -Infinity, color: '#7EB26D', key: 1 },
|
||||
{ value: 78, color: '#EAB839', key: 2 },
|
||||
{ value: 75, color: '#6ED0E0', key: 3 },
|
||||
];
|
||||
|
||||
instance.setState({
|
||||
@ -176,10 +175,10 @@ describe('on blur threshold value', () => {
|
||||
|
||||
instance.onBlur();
|
||||
|
||||
expect(instance.state.thresholds).toEqual([
|
||||
{ index: 0, value: -Infinity, color: '#7EB26D' },
|
||||
{ index: 1, value: 75, color: '#6ED0E0' },
|
||||
{ index: 2, value: 78, color: '#EAB839' },
|
||||
expect(getCurrentThresholds(instance)).toEqual([
|
||||
{ value: -Infinity, color: '#7EB26D' },
|
||||
{ value: 75, color: '#6ED0E0' },
|
||||
{ value: 78, color: '#EAB839' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { PureComponent, ChangeEvent } from 'react';
|
||||
import { Threshold } from '@grafana/data';
|
||||
import { Threshold, sortThresholds } from '@grafana/data';
|
||||
import { colors } from '../../utils';
|
||||
import { ThemeContext } from '../../themes';
|
||||
import { getColorFromHexRgbOrName } from '../../utils';
|
||||
@ -13,115 +13,121 @@ export interface Props {
|
||||
}
|
||||
|
||||
interface State {
|
||||
thresholds: Threshold[];
|
||||
thresholds: ThresholdWithKey[];
|
||||
}
|
||||
|
||||
interface ThresholdWithKey extends Threshold {
|
||||
key: number;
|
||||
}
|
||||
|
||||
let counter = 100;
|
||||
|
||||
export class ThresholdsEditor extends PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
const addDefaultThreshold = this.props.thresholds.length === 0;
|
||||
const thresholds: Threshold[] = addDefaultThreshold
|
||||
? [{ index: 0, value: -Infinity, color: colors[0] }]
|
||||
: props.thresholds;
|
||||
const thresholds = props.thresholds
|
||||
? props.thresholds.map(t => {
|
||||
return {
|
||||
color: t.color,
|
||||
value: t.value === null ? -Infinity : t.value,
|
||||
key: counter++,
|
||||
};
|
||||
})
|
||||
: ([] as ThresholdWithKey[]);
|
||||
|
||||
let needsCallback = false;
|
||||
if (!thresholds.length) {
|
||||
thresholds.push({ value: -Infinity, color: colors[0], key: counter++ });
|
||||
needsCallback = true;
|
||||
} else {
|
||||
// First value is always base
|
||||
thresholds[0].value = -Infinity;
|
||||
}
|
||||
|
||||
// Update the state
|
||||
this.state = { thresholds };
|
||||
|
||||
if (addDefaultThreshold) {
|
||||
if (needsCallback) {
|
||||
this.onChange();
|
||||
}
|
||||
}
|
||||
|
||||
onAddThreshold = (index: number) => {
|
||||
onAddThresholdAfter = (threshold: ThresholdWithKey) => {
|
||||
const { thresholds } = this.state;
|
||||
|
||||
const maxValue = 100;
|
||||
const minValue = 0;
|
||||
|
||||
if (index === 0) {
|
||||
return;
|
||||
let prev: ThresholdWithKey | undefined = undefined;
|
||||
let next: ThresholdWithKey | undefined = undefined;
|
||||
for (const t of thresholds) {
|
||||
if (prev && prev.key === threshold.key) {
|
||||
next = t;
|
||||
break;
|
||||
}
|
||||
prev = t;
|
||||
}
|
||||
|
||||
const newThresholds = thresholds.map(threshold => {
|
||||
if (threshold.index >= index) {
|
||||
const index = threshold.index + 1;
|
||||
threshold = { ...threshold, index };
|
||||
}
|
||||
return threshold;
|
||||
});
|
||||
const prevValue = prev && isFinite(prev.value) ? prev.value : minValue;
|
||||
const nextValue = next && isFinite(next.value) ? next.value : maxValue;
|
||||
|
||||
// Setting value to a value between the previous thresholds
|
||||
const beforeThreshold = newThresholds.filter(t => t.index === index - 1 && t.index !== 0)[0];
|
||||
const afterThreshold = newThresholds.filter(t => t.index === index + 1 && t.index !== 0)[0];
|
||||
const beforeThresholdValue = beforeThreshold !== undefined ? beforeThreshold.value : minValue;
|
||||
const afterThresholdValue = afterThreshold !== undefined ? afterThreshold.value : maxValue;
|
||||
const value = afterThresholdValue - (afterThresholdValue - beforeThresholdValue) / 2;
|
||||
|
||||
// Set a color
|
||||
const color = colors.filter(c => !newThresholds.some(t => t.color === c))[1];
|
||||
const color = colors.filter(c => !thresholds.some(t => t.color === c))[1];
|
||||
const add = {
|
||||
value: prevValue + (nextValue - prevValue) / 2.0,
|
||||
color: color,
|
||||
key: counter++,
|
||||
};
|
||||
const newThresholds = [...thresholds, add];
|
||||
sortThresholds(newThresholds);
|
||||
|
||||
this.setState(
|
||||
{
|
||||
thresholds: this.sortThresholds([
|
||||
...newThresholds,
|
||||
{
|
||||
color,
|
||||
index,
|
||||
value: value as number,
|
||||
},
|
||||
]),
|
||||
thresholds: newThresholds,
|
||||
},
|
||||
() => this.onChange()
|
||||
);
|
||||
};
|
||||
|
||||
onRemoveThreshold = (threshold: Threshold) => {
|
||||
if (threshold.index === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(
|
||||
prevState => {
|
||||
const newThresholds = prevState.thresholds.map(t => {
|
||||
if (t.index > threshold.index) {
|
||||
const index = t.index - 1;
|
||||
t = { ...t, index };
|
||||
}
|
||||
return t;
|
||||
});
|
||||
|
||||
return {
|
||||
thresholds: newThresholds.filter(t => t !== threshold),
|
||||
};
|
||||
},
|
||||
() => this.onChange()
|
||||
);
|
||||
};
|
||||
|
||||
onChangeThresholdValue = (event: ChangeEvent<HTMLInputElement>, threshold: Threshold) => {
|
||||
if (threshold.index === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
onRemoveThreshold = (threshold: ThresholdWithKey) => {
|
||||
const { thresholds } = this.state;
|
||||
if (!thresholds.length) {
|
||||
return;
|
||||
}
|
||||
// Don't remove index 0
|
||||
if (threshold.key === thresholds[0].key) {
|
||||
return;
|
||||
}
|
||||
this.setState(
|
||||
{
|
||||
thresholds: thresholds.filter(t => t.key !== threshold.key),
|
||||
},
|
||||
() => this.onChange()
|
||||
);
|
||||
};
|
||||
|
||||
onChangeThresholdValue = (event: ChangeEvent<HTMLInputElement>, threshold: ThresholdWithKey) => {
|
||||
const cleanValue = event.target.value.replace(/,/g, '.');
|
||||
const parsedValue = parseFloat(cleanValue);
|
||||
const value = isNaN(parsedValue) ? '' : parsedValue;
|
||||
|
||||
const newThresholds = thresholds.map(t => {
|
||||
if (t === threshold && t.index !== 0) {
|
||||
const thresholds = this.state.thresholds.map(t => {
|
||||
if (t.key === threshold.key) {
|
||||
t = { ...t, value: value as number };
|
||||
}
|
||||
|
||||
return t;
|
||||
});
|
||||
|
||||
this.setState({ thresholds: newThresholds });
|
||||
if (thresholds.length) {
|
||||
thresholds[0].value = -Infinity;
|
||||
}
|
||||
this.setState({ thresholds });
|
||||
};
|
||||
|
||||
onChangeThresholdColor = (threshold: Threshold, color: string) => {
|
||||
onChangeThresholdColor = (threshold: ThresholdWithKey, color: string) => {
|
||||
const { thresholds } = this.state;
|
||||
|
||||
const newThresholds = thresholds.map(t => {
|
||||
if (t === threshold) {
|
||||
if (t.key === threshold.key) {
|
||||
t = { ...t, color: color };
|
||||
}
|
||||
|
||||
@ -137,30 +143,22 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
onBlur = () => {
|
||||
this.setState(prevState => {
|
||||
const sortThresholds = this.sortThresholds([...prevState.thresholds]);
|
||||
let index = 0;
|
||||
sortThresholds.forEach(t => {
|
||||
t.index = index++;
|
||||
});
|
||||
|
||||
return { thresholds: sortThresholds };
|
||||
});
|
||||
|
||||
this.onChange();
|
||||
const thresholds = [...this.state.thresholds];
|
||||
sortThresholds(thresholds);
|
||||
this.setState(
|
||||
{
|
||||
thresholds,
|
||||
},
|
||||
() => this.onChange()
|
||||
);
|
||||
};
|
||||
|
||||
onChange = () => {
|
||||
this.props.onChange(this.state.thresholds);
|
||||
const { thresholds } = this.state;
|
||||
this.props.onChange(threshodsWithoutKey(thresholds));
|
||||
};
|
||||
|
||||
sortThresholds = (thresholds: Threshold[]) => {
|
||||
return thresholds.sort((t1, t2) => {
|
||||
return t1.value - t2.value;
|
||||
});
|
||||
};
|
||||
|
||||
renderInput = (threshold: Threshold) => {
|
||||
renderInput = (threshold: ThresholdWithKey) => {
|
||||
return (
|
||||
<div className="thresholds-row-input-inner">
|
||||
<span className="thresholds-row-input-inner-arrow" />
|
||||
@ -175,12 +173,11 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{threshold.index === 0 && (
|
||||
{!isFinite(threshold.value) ? (
|
||||
<div className="thresholds-row-input-inner-value">
|
||||
<Input type="text" value="Base" readOnly />
|
||||
</div>
|
||||
)}
|
||||
{threshold.index > 0 && (
|
||||
) : (
|
||||
<>
|
||||
<div className="thresholds-row-input-inner-value">
|
||||
<Input
|
||||
@ -189,7 +186,6 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => this.onChangeThresholdValue(event, threshold)}
|
||||
value={threshold.value}
|
||||
onBlur={this.onBlur}
|
||||
readOnly={threshold.index === 0}
|
||||
/>
|
||||
</div>
|
||||
<div className="thresholds-row-input-inner-remove" onClick={() => this.onRemoveThreshold(threshold)}>
|
||||
@ -212,13 +208,10 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
|
||||
{thresholds
|
||||
.slice(0)
|
||||
.reverse()
|
||||
.map((threshold, index) => {
|
||||
.map(threshold => {
|
||||
return (
|
||||
<div className="thresholds-row" key={`${threshold.index}-${index}`}>
|
||||
<div
|
||||
className="thresholds-row-add-button"
|
||||
onClick={() => this.onAddThreshold(threshold.index + 1)}
|
||||
>
|
||||
<div className="thresholds-row" key={`${threshold.key}`}>
|
||||
<div className="thresholds-row-add-button" onClick={() => this.onAddThresholdAfter(threshold)}>
|
||||
<i className="fa fa-plus" />
|
||||
</div>
|
||||
<div
|
||||
@ -237,3 +230,10 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function threshodsWithoutKey(thresholds: ThresholdWithKey[]): Threshold[] {
|
||||
return thresholds.map(t => {
|
||||
const { key, ...rest } = t;
|
||||
return rest; // everything except key
|
||||
});
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ exports[`Render should render with base threshold 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"color": "#7EB26D",
|
||||
"index": 0,
|
||||
"value": -Infinity,
|
||||
},
|
||||
],
|
||||
@ -48,7 +47,7 @@ exports[`Render should render with base threshold 1`] = `
|
||||
>
|
||||
<div
|
||||
className="thresholds-row"
|
||||
key="0-0"
|
||||
key="100"
|
||||
>
|
||||
<div
|
||||
className="thresholds-row-add-button"
|
||||
|
@ -103,7 +103,7 @@ describe('Format value', () => {
|
||||
it('should return if value isNaN', () => {
|
||||
const valueMappings: ValueMapping[] = [];
|
||||
const value = 'N/A';
|
||||
const instance = getDisplayProcessor({ mappings: valueMappings });
|
||||
const instance = getDisplayProcessor({ field: { mappings: valueMappings } });
|
||||
|
||||
const result = instance(value);
|
||||
|
||||
@ -114,7 +114,7 @@ describe('Format value', () => {
|
||||
const valueMappings: ValueMapping[] = [];
|
||||
const value = '6';
|
||||
|
||||
const instance = getDisplayProcessor({ mappings: valueMappings, field: { decimals: 1 } });
|
||||
const instance = getDisplayProcessor({ field: { decimals: 1, mappings: valueMappings } });
|
||||
|
||||
const result = instance(value);
|
||||
|
||||
@ -127,7 +127,7 @@ describe('Format value', () => {
|
||||
{ id: 1, operator: '', text: '1-9', type: MappingType.RangeToText, from: '1', to: '9' },
|
||||
];
|
||||
const value = '10';
|
||||
const instance = getDisplayProcessor({ mappings: valueMappings, field: { decimals: 1 } });
|
||||
const instance = getDisplayProcessor({ field: { decimals: 1, mappings: valueMappings } });
|
||||
|
||||
const result = instance(value);
|
||||
|
||||
@ -160,7 +160,7 @@ describe('Format value', () => {
|
||||
{ id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' },
|
||||
];
|
||||
const value = '11';
|
||||
const instance = getDisplayProcessor({ mappings: valueMappings, field: { decimals: 1 } });
|
||||
const instance = getDisplayProcessor({ field: { decimals: 1, mappings: valueMappings } });
|
||||
|
||||
expect(instance(value).text).toEqual('1-20');
|
||||
});
|
||||
|
@ -7,16 +7,13 @@ import { getColorFromHexRgbOrName } from './namedColorsPalette';
|
||||
|
||||
// Types
|
||||
import { DecimalInfo, DisplayValue, GrafanaTheme, GrafanaThemeType, DecimalCount } from '../types';
|
||||
import { DateTime, dateTime, Threshold, ValueMapping, getMappedValue, Field } from '@grafana/data';
|
||||
import { DateTime, dateTime, Threshold, getMappedValue, Field } from '@grafana/data';
|
||||
|
||||
export type DisplayProcessor = (value: any) => DisplayValue;
|
||||
|
||||
export interface DisplayValueOptions {
|
||||
field?: Partial<Field>;
|
||||
|
||||
mappings?: ValueMapping[];
|
||||
thresholds?: Threshold[];
|
||||
|
||||
// Alternative to empty string
|
||||
noValue?: string;
|
||||
|
||||
@ -31,7 +28,8 @@ export function getDisplayProcessor(options?: DisplayValueOptions): DisplayProce
|
||||
const formatFunc = getValueFormat(field.unit || 'none');
|
||||
|
||||
return (value: any) => {
|
||||
const { mappings, thresholds, theme } = options;
|
||||
const { theme } = options;
|
||||
const { mappings, thresholds } = field;
|
||||
let color;
|
||||
|
||||
let text = _.toString(value);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { getFieldProperties, getFieldDisplayValues, GetFieldDisplayValuesOptions } from './fieldDisplay';
|
||||
import { FieldType, ReducerID } from '@grafana/data';
|
||||
import { FieldType, ReducerID, Threshold } from '@grafana/data';
|
||||
import { GrafanaThemeType } from '../types/theme';
|
||||
import { getTheme } from '../themes/index';
|
||||
|
||||
@ -55,8 +55,6 @@ describe('FieldDisplay', () => {
|
||||
},
|
||||
fieldOptions: {
|
||||
calcs: [],
|
||||
mappings: [],
|
||||
thresholds: [],
|
||||
override: {},
|
||||
defaults: {},
|
||||
},
|
||||
@ -68,8 +66,6 @@ describe('FieldDisplay', () => {
|
||||
...options,
|
||||
fieldOptions: {
|
||||
calcs: [ReducerID.first],
|
||||
mappings: [],
|
||||
thresholds: [],
|
||||
override: {},
|
||||
defaults: {
|
||||
title: '$__cell_0 * $__field_name * $__series_name',
|
||||
@ -88,8 +84,6 @@ describe('FieldDisplay', () => {
|
||||
...options,
|
||||
fieldOptions: {
|
||||
calcs: [ReducerID.last],
|
||||
mappings: [],
|
||||
thresholds: [],
|
||||
override: {},
|
||||
defaults: {},
|
||||
},
|
||||
@ -104,8 +98,6 @@ describe('FieldDisplay', () => {
|
||||
values: true, //
|
||||
limit: 1000,
|
||||
calcs: [],
|
||||
mappings: [],
|
||||
thresholds: [],
|
||||
override: {},
|
||||
defaults: {},
|
||||
},
|
||||
@ -120,12 +112,27 @@ describe('FieldDisplay', () => {
|
||||
values: true, //
|
||||
limit: 2,
|
||||
calcs: [],
|
||||
mappings: [],
|
||||
thresholds: [],
|
||||
override: {},
|
||||
defaults: {},
|
||||
},
|
||||
});
|
||||
expect(display.map(v => v.display.numeric)).toEqual([1, 3]); // First 2 are from the first field
|
||||
});
|
||||
|
||||
it('should restore -Infinity value for base threshold', () => {
|
||||
const field = getFieldProperties({
|
||||
thresholds: [
|
||||
({
|
||||
color: '#73BF69',
|
||||
value: null,
|
||||
} as unknown) as Threshold,
|
||||
{
|
||||
color: '#F2495C',
|
||||
value: 50,
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(field.thresholds!.length).toEqual(2);
|
||||
expect(field.thresholds![0].value).toBe(-Infinity);
|
||||
});
|
||||
});
|
||||
|
@ -4,16 +4,7 @@ import toString from 'lodash/toString';
|
||||
import { DisplayValue, GrafanaTheme, InterpolateFunction, ScopedVars, GraphSeriesValue } from '../types/index';
|
||||
import { getDisplayProcessor } from './displayValue';
|
||||
import { getFlotPairs } from './flotPairs';
|
||||
import {
|
||||
ValueMapping,
|
||||
Threshold,
|
||||
ReducerID,
|
||||
reduceField,
|
||||
FieldType,
|
||||
NullValueMode,
|
||||
DataFrame,
|
||||
Field,
|
||||
} from '@grafana/data';
|
||||
import { ReducerID, reduceField, FieldType, NullValueMode, DataFrame, Field } from '@grafana/data';
|
||||
|
||||
export interface FieldDisplayOptions {
|
||||
values?: boolean; // If true show each row value
|
||||
@ -22,10 +13,6 @@ export interface FieldDisplayOptions {
|
||||
|
||||
defaults: Partial<Field>; // Use these values unless otherwise stated
|
||||
override: Partial<Field>; // Set these values regardless of the source
|
||||
|
||||
// Could these be data driven also?
|
||||
thresholds: Threshold[];
|
||||
mappings: ValueMapping[];
|
||||
}
|
||||
|
||||
export const VAR_SERIES_NAME = '__series_name';
|
||||
@ -127,8 +114,6 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi
|
||||
|
||||
const display = getDisplayProcessor({
|
||||
field,
|
||||
mappings: fieldOptions.mappings,
|
||||
thresholds: fieldOptions.thresholds,
|
||||
theme: options.theme,
|
||||
});
|
||||
|
||||
@ -263,6 +248,11 @@ export function getFieldProperties(...props: PartialField[]): Field {
|
||||
field = applyFieldProperties(field, props[i]);
|
||||
}
|
||||
|
||||
// First value is always -Infinity
|
||||
if (field.thresholds && field.thresholds.length) {
|
||||
field.thresholds[0].value = -Infinity;
|
||||
}
|
||||
|
||||
// Verify that max > min
|
||||
if (field.hasOwnProperty('min') && field.hasOwnProperty('max') && field.min! > field.max!) {
|
||||
return {
|
||||
|
@ -98,21 +98,6 @@ describe('PanelModel', () => {
|
||||
expect(saveModel.events).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should restore -Infinity value for base threshold', () => {
|
||||
expect(model.options.fieldOptions.thresholds).toEqual([
|
||||
{
|
||||
color: '#F2495C',
|
||||
index: 1,
|
||||
value: 50,
|
||||
},
|
||||
{
|
||||
color: '#73BF69',
|
||||
index: 0,
|
||||
value: -Infinity,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
describe('when changing panel type', () => {
|
||||
const newPanelPluginDefaults = {
|
||||
showThresholdLabels: false,
|
||||
@ -180,7 +165,7 @@ describe('PanelModel', () => {
|
||||
it('should call react onPanelTypeChanged', () => {
|
||||
expect(onPanelTypeChanged.mock.calls.length).toBe(1);
|
||||
expect(onPanelTypeChanged.mock.calls[0][1]).toBe('table');
|
||||
expect(onPanelTypeChanged.mock.calls[0][2].fieldOptions.thresholds).toBeDefined();
|
||||
expect(onPanelTypeChanged.mock.calls[0][2].fieldOptions).toBeDefined();
|
||||
});
|
||||
|
||||
it('getQueryRunner() should return same instance after changing to another react panel', () => {
|
||||
|
@ -136,7 +136,6 @@ export class PanelModel {
|
||||
|
||||
// queries must have refId
|
||||
this.ensureQueryIds();
|
||||
this.restoreInfintyForThresholds();
|
||||
}
|
||||
|
||||
ensureQueryIds() {
|
||||
@ -149,16 +148,6 @@ export class PanelModel {
|
||||
}
|
||||
}
|
||||
|
||||
restoreInfintyForThresholds() {
|
||||
if (this.options && this.options.fieldOptions) {
|
||||
for (const threshold of this.options.fieldOptions.thresholds) {
|
||||
if (threshold.value === null) {
|
||||
threshold.value = -Infinity;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getOptions() {
|
||||
return this.options;
|
||||
}
|
||||
|
61
public/app/plugins/panel/bargauge/BarGaugeMigrations.test.ts
Normal file
61
public/app/plugins/panel/bargauge/BarGaugeMigrations.test.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { PanelModel } from '@grafana/ui';
|
||||
import { barGaugePanelMigrationCheck } from './BarGaugeMigrations';
|
||||
|
||||
describe('BarGauge Panel Migrations', () => {
|
||||
it('from 6.2', () => {
|
||||
const panel = {
|
||||
id: 7,
|
||||
links: [],
|
||||
options: {
|
||||
displayMode: 'lcd',
|
||||
fieldOptions: {
|
||||
calcs: ['mean'],
|
||||
defaults: {
|
||||
decimals: null,
|
||||
max: -22,
|
||||
min: 33,
|
||||
unit: 'watt',
|
||||
},
|
||||
mappings: [],
|
||||
override: {},
|
||||
thresholds: [
|
||||
{
|
||||
color: 'green',
|
||||
index: 0,
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
color: 'orange',
|
||||
index: 1,
|
||||
value: 40,
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
index: 2,
|
||||
value: 80,
|
||||
},
|
||||
],
|
||||
values: false,
|
||||
},
|
||||
orientation: 'vertical',
|
||||
},
|
||||
pluginVersion: '6.2.0',
|
||||
targets: [
|
||||
{
|
||||
refId: 'A',
|
||||
scenarioId: 'random_walk',
|
||||
},
|
||||
{
|
||||
refId: 'B',
|
||||
scenarioId: 'random_walk',
|
||||
},
|
||||
],
|
||||
timeFrom: null,
|
||||
timeShift: null,
|
||||
title: 'Usage',
|
||||
type: 'bargauge',
|
||||
} as PanelModel;
|
||||
|
||||
expect(barGaugePanelMigrationCheck(panel)).toMatchSnapshot();
|
||||
});
|
||||
});
|
36
public/app/plugins/panel/bargauge/BarGaugeMigrations.ts
Normal file
36
public/app/plugins/panel/bargauge/BarGaugeMigrations.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { PanelModel } from '@grafana/ui';
|
||||
import {
|
||||
sharedSingleStatMigrationCheck,
|
||||
migrateOldThresholds,
|
||||
} from '@grafana/ui/src/components/SingleStatShared/SingleStatBaseOptions';
|
||||
import { BarGaugeOptions } from './types';
|
||||
|
||||
export const barGaugePanelMigrationCheck = (panel: PanelModel<BarGaugeOptions>): Partial<BarGaugeOptions> => {
|
||||
if (!panel.options) {
|
||||
// This happens on the first load or when migrating from angular
|
||||
return {};
|
||||
}
|
||||
|
||||
// Move thresholds to field
|
||||
const previousVersion = panel.pluginVersion || '';
|
||||
if (previousVersion.startsWith('6.2') || previousVersion.startsWith('6.3')) {
|
||||
console.log('TRANSFORM', panel.options);
|
||||
const old = panel.options as any;
|
||||
const { fieldOptions } = old;
|
||||
if (fieldOptions) {
|
||||
const { mappings, thresholds, ...rest } = fieldOptions;
|
||||
rest.defaults = {
|
||||
mappings,
|
||||
thresholds: migrateOldThresholds(thresholds),
|
||||
...rest.defaults,
|
||||
};
|
||||
return {
|
||||
...old,
|
||||
fieldOptions: rest,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Default to the standard migration path
|
||||
return sharedSingleStatMigrationCheck(panel);
|
||||
};
|
@ -14,7 +14,6 @@ import { PanelProps } from '@grafana/ui';
|
||||
export class BarGaugePanel extends PureComponent<PanelProps<BarGaugeOptions>> {
|
||||
renderValue = (value: FieldDisplay, width: number, height: number): JSX.Element => {
|
||||
const { options } = this.props;
|
||||
const { fieldOptions } = options;
|
||||
const { field, display } = value;
|
||||
|
||||
return (
|
||||
@ -23,7 +22,7 @@ export class BarGaugePanel extends PureComponent<PanelProps<BarGaugeOptions>> {
|
||||
width={width}
|
||||
height={height}
|
||||
orientation={options.orientation}
|
||||
thresholds={fieldOptions.thresholds}
|
||||
thresholds={field.thresholds}
|
||||
theme={config.theme}
|
||||
itemSpacing={this.getItemSpacing()}
|
||||
displayMode={options.displayMode}
|
||||
|
@ -19,17 +19,21 @@ import { Threshold, ValueMapping } from '@grafana/data';
|
||||
import { BarGaugeOptions, orientationOptions, displayModes } from './types';
|
||||
|
||||
export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGaugeOptions>> {
|
||||
onThresholdsChanged = (thresholds: Threshold[]) =>
|
||||
this.onDisplayOptionsChanged({
|
||||
...this.props.options.fieldOptions,
|
||||
onThresholdsChanged = (thresholds: Threshold[]) => {
|
||||
const current = this.props.options.fieldOptions.defaults;
|
||||
this.onDefaultsChange({
|
||||
...current,
|
||||
thresholds,
|
||||
});
|
||||
};
|
||||
|
||||
onValueMappingsChanged = (mappings: ValueMapping[]) =>
|
||||
this.onDisplayOptionsChanged({
|
||||
...this.props.options.fieldOptions,
|
||||
onValueMappingsChanged = (mappings: ValueMapping[]) => {
|
||||
const current = this.props.options.fieldOptions.defaults;
|
||||
this.onDefaultsChange({
|
||||
...current,
|
||||
mappings,
|
||||
});
|
||||
};
|
||||
|
||||
onDisplayOptionsChanged = (fieldOptions: FieldDisplayOptions) =>
|
||||
this.props.onOptionsChange({
|
||||
@ -50,6 +54,7 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge
|
||||
render() {
|
||||
const { options } = this.props;
|
||||
const { fieldOptions } = options;
|
||||
const { defaults } = fieldOptions;
|
||||
|
||||
const labelWidth = 6;
|
||||
|
||||
@ -80,13 +85,13 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge
|
||||
</div>
|
||||
</PanelOptionsGroup>
|
||||
<PanelOptionsGroup title="Field">
|
||||
<FieldPropertiesEditor showMinMax={true} onChange={this.onDefaultsChange} value={fieldOptions.defaults} />
|
||||
<FieldPropertiesEditor showMinMax={true} onChange={this.onDefaultsChange} value={defaults} />
|
||||
</PanelOptionsGroup>
|
||||
|
||||
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={fieldOptions.thresholds} />
|
||||
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={defaults.thresholds} />
|
||||
</PanelOptionsGrid>
|
||||
|
||||
<ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={fieldOptions.mappings} />
|
||||
<ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,36 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`BarGauge Panel Migrations from 6.2 1`] = `
|
||||
Object {
|
||||
"displayMode": "lcd",
|
||||
"fieldOptions": Object {
|
||||
"calcs": Array [
|
||||
"mean",
|
||||
],
|
||||
"defaults": Object {
|
||||
"decimals": null,
|
||||
"mappings": Array [],
|
||||
"max": -22,
|
||||
"min": 33,
|
||||
"thresholds": Array [
|
||||
Object {
|
||||
"color": "green",
|
||||
"value": -Infinity,
|
||||
},
|
||||
Object {
|
||||
"color": "orange",
|
||||
"value": 40,
|
||||
},
|
||||
Object {
|
||||
"color": "red",
|
||||
"value": 80,
|
||||
},
|
||||
],
|
||||
"unit": "watt",
|
||||
},
|
||||
"override": Object {},
|
||||
"values": false,
|
||||
},
|
||||
"orientation": "vertical",
|
||||
}
|
||||
`;
|
@ -2,8 +2,10 @@ import { PanelPlugin, sharedSingleStatOptionsCheck } from '@grafana/ui';
|
||||
import { BarGaugePanel } from './BarGaugePanel';
|
||||
import { BarGaugePanelEditor } from './BarGaugePanelEditor';
|
||||
import { BarGaugeOptions, defaults } from './types';
|
||||
import { barGaugePanelMigrationCheck } from './BarGaugeMigrations';
|
||||
|
||||
export const plugin = new PanelPlugin<BarGaugeOptions>(BarGaugePanel)
|
||||
.setDefaults(defaults)
|
||||
.setEditor(BarGaugePanelEditor)
|
||||
.setPanelChangeHandler(sharedSingleStatOptionsCheck);
|
||||
.setPanelChangeHandler(sharedSingleStatOptionsCheck)
|
||||
.setMigrationHandler(barGaugePanelMigrationCheck);
|
||||
|
@ -1,7 +1,10 @@
|
||||
import { Field, getFieldReducers } from '@grafana/data';
|
||||
import { PanelModel } from '@grafana/ui';
|
||||
import { GaugeOptions } from './types';
|
||||
import { sharedSingleStatMigrationCheck } from '@grafana/ui/src/components/SingleStatShared/SingleStatBaseOptions';
|
||||
import {
|
||||
sharedSingleStatMigrationCheck,
|
||||
migrateOldThresholds,
|
||||
} from '@grafana/ui/src/components/SingleStatShared/SingleStatBaseOptions';
|
||||
import { FieldDisplayOptions } from '@grafana/ui/src/utils/fieldDisplay';
|
||||
|
||||
export const gaugePanelMigrationCheck = (panel: PanelModel<GaugeOptions>): Partial<GaugeOptions> => {
|
||||
@ -10,7 +13,8 @@ export const gaugePanelMigrationCheck = (panel: PanelModel<GaugeOptions>): Parti
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!panel.pluginVersion || panel.pluginVersion.startsWith('6.1')) {
|
||||
const previousVersion = panel.pluginVersion || '';
|
||||
if (!previousVersion || previousVersion.startsWith('6.1')) {
|
||||
const old = panel.options as any;
|
||||
const { valueOptions } = old;
|
||||
|
||||
@ -20,23 +24,36 @@ export const gaugePanelMigrationCheck = (panel: PanelModel<GaugeOptions>): Parti
|
||||
options.orientation = old.orientation;
|
||||
|
||||
const fieldOptions = (options.fieldOptions = {} as FieldDisplayOptions);
|
||||
fieldOptions.mappings = old.valueMappings;
|
||||
fieldOptions.thresholds = old.thresholds;
|
||||
|
||||
const field = (fieldOptions.defaults = {} as Field);
|
||||
if (valueOptions) {
|
||||
field.unit = valueOptions.unit;
|
||||
field.decimals = valueOptions.decimals;
|
||||
field.mappings = old.valueMappings;
|
||||
field.thresholds = migrateOldThresholds(old.thresholds);
|
||||
field.unit = valueOptions.unit;
|
||||
field.decimals = valueOptions.decimals;
|
||||
|
||||
// Make sure the stats have a valid name
|
||||
if (valueOptions.stat) {
|
||||
fieldOptions.calcs = getFieldReducers([valueOptions.stat]).map(s => s.id);
|
||||
}
|
||||
// Make sure the stats have a valid name
|
||||
if (valueOptions.stat) {
|
||||
fieldOptions.calcs = getFieldReducers([valueOptions.stat]).map(s => s.id);
|
||||
}
|
||||
field.min = old.minValue;
|
||||
field.max = old.maxValue;
|
||||
|
||||
return options;
|
||||
} else if (previousVersion.startsWith('6.2') || previousVersion.startsWith('6.3')) {
|
||||
const old = panel.options as any;
|
||||
const { fieldOptions } = old;
|
||||
if (fieldOptions) {
|
||||
const { mappings, thresholds, ...rest } = fieldOptions;
|
||||
rest.default = {
|
||||
mappings,
|
||||
thresholds: migrateOldThresholds(thresholds),
|
||||
...rest.defaults,
|
||||
};
|
||||
return {
|
||||
...old.options,
|
||||
fieldOptions: rest,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Default to the standard migration path
|
||||
|
@ -14,7 +14,6 @@ import { PanelProps, VizRepeater } from '@grafana/ui';
|
||||
export class GaugePanel extends PureComponent<PanelProps<GaugeOptions>> {
|
||||
renderValue = (value: FieldDisplay, width: number, height: number): JSX.Element => {
|
||||
const { options } = this.props;
|
||||
const { fieldOptions } = options;
|
||||
const { field, display } = value;
|
||||
|
||||
return (
|
||||
@ -22,7 +21,7 @@ export class GaugePanel extends PureComponent<PanelProps<GaugeOptions>> {
|
||||
value={display}
|
||||
width={width}
|
||||
height={height}
|
||||
thresholds={fieldOptions.thresholds}
|
||||
thresholds={field.thresholds}
|
||||
showThresholdLabels={options.showThresholdLabels}
|
||||
showThresholdMarkers={options.showThresholdMarkers}
|
||||
minValue={field.min}
|
||||
|
@ -27,17 +27,21 @@ export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOption
|
||||
showThresholdMarkers: !this.props.options.showThresholdMarkers,
|
||||
});
|
||||
|
||||
onThresholdsChanged = (thresholds: Threshold[]) =>
|
||||
this.onDisplayOptionsChanged({
|
||||
...this.props.options.fieldOptions,
|
||||
onThresholdsChanged = (thresholds: Threshold[]) => {
|
||||
const current = this.props.options.fieldOptions.defaults;
|
||||
this.onDefaultsChange({
|
||||
...current,
|
||||
thresholds,
|
||||
});
|
||||
};
|
||||
|
||||
onValueMappingsChanged = (mappings: ValueMapping[]) =>
|
||||
this.onDisplayOptionsChanged({
|
||||
...this.props.options.fieldOptions,
|
||||
onValueMappingsChanged = (mappings: ValueMapping[]) => {
|
||||
const current = this.props.options.fieldOptions.defaults;
|
||||
this.onDefaultsChange({
|
||||
...current,
|
||||
mappings,
|
||||
});
|
||||
};
|
||||
|
||||
onDisplayOptionsChanged = (fieldOptions: FieldDisplayOptions) =>
|
||||
this.props.onOptionsChange({
|
||||
@ -55,6 +59,7 @@ export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOption
|
||||
render() {
|
||||
const { options } = this.props;
|
||||
const { fieldOptions, showThresholdLabels, showThresholdMarkers } = options;
|
||||
const { defaults } = fieldOptions;
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -80,13 +85,13 @@ export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOption
|
||||
</PanelOptionsGroup>
|
||||
|
||||
<PanelOptionsGroup title="Field">
|
||||
<FieldPropertiesEditor showMinMax={true} onChange={this.onDefaultsChange} value={fieldOptions.defaults} />
|
||||
<FieldPropertiesEditor showMinMax={true} onChange={this.onDefaultsChange} value={defaults} />
|
||||
</PanelOptionsGroup>
|
||||
|
||||
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={fieldOptions.thresholds} />
|
||||
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={defaults.thresholds} />
|
||||
</PanelOptionsGrid>
|
||||
|
||||
<ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={fieldOptions.mappings} />
|
||||
<ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -8,43 +8,39 @@ Object {
|
||||
],
|
||||
"defaults": Object {
|
||||
"decimals": 3,
|
||||
"mappings": Array [
|
||||
Object {
|
||||
"from": "50",
|
||||
"id": 1,
|
||||
"operator": "",
|
||||
"text": "BIG",
|
||||
"to": "1000",
|
||||
"type": 2,
|
||||
"value": "",
|
||||
},
|
||||
],
|
||||
"max": "50",
|
||||
"min": "-50",
|
||||
"thresholds": Array [
|
||||
Object {
|
||||
"color": "green",
|
||||
"value": -Infinity,
|
||||
},
|
||||
Object {
|
||||
"color": "#EAB839",
|
||||
"value": -25,
|
||||
},
|
||||
Object {
|
||||
"color": "#6ED0E0",
|
||||
"value": 0,
|
||||
},
|
||||
Object {
|
||||
"color": "red",
|
||||
"value": 25,
|
||||
},
|
||||
],
|
||||
"unit": "accMS2",
|
||||
},
|
||||
"mappings": Array [
|
||||
Object {
|
||||
"from": "50",
|
||||
"id": 1,
|
||||
"operator": "",
|
||||
"text": "BIG",
|
||||
"to": "1000",
|
||||
"type": 2,
|
||||
"value": "",
|
||||
},
|
||||
],
|
||||
"thresholds": Array [
|
||||
Object {
|
||||
"color": "green",
|
||||
"index": 0,
|
||||
"value": null,
|
||||
},
|
||||
Object {
|
||||
"color": "#EAB839",
|
||||
"index": 1,
|
||||
"value": -25,
|
||||
},
|
||||
Object {
|
||||
"color": "#6ED0E0",
|
||||
"index": 2,
|
||||
"value": 0,
|
||||
},
|
||||
Object {
|
||||
"color": "red",
|
||||
"index": 3,
|
||||
"value": 25,
|
||||
},
|
||||
],
|
||||
},
|
||||
"orientation": "auto",
|
||||
"showThresholdLabels": true,
|
||||
|
@ -14,11 +14,13 @@ import { PieChartOptionsBox } from './PieChartOptionsBox';
|
||||
import { PieChartOptions } from './types';
|
||||
|
||||
export class PieChartPanelEditor extends PureComponent<PanelEditorProps<PieChartOptions>> {
|
||||
onValueMappingsChanged = (mappings: ValueMapping[]) =>
|
||||
this.onDisplayOptionsChanged({
|
||||
...this.props.options.fieldOptions,
|
||||
onValueMappingsChanged = (mappings: ValueMapping[]) => {
|
||||
const current = this.props.options.fieldOptions.defaults;
|
||||
this.onDefaultsChange({
|
||||
...current,
|
||||
mappings,
|
||||
});
|
||||
};
|
||||
|
||||
onDisplayOptionsChanged = (fieldOptions: FieldDisplayOptions) =>
|
||||
this.props.onOptionsChange({
|
||||
@ -36,6 +38,7 @@ export class PieChartPanelEditor extends PureComponent<PanelEditorProps<PieChart
|
||||
render() {
|
||||
const { onOptionsChange, options } = this.props;
|
||||
const { fieldOptions } = options;
|
||||
const { defaults } = fieldOptions;
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -45,13 +48,13 @@ export class PieChartPanelEditor extends PureComponent<PanelEditorProps<PieChart
|
||||
</PanelOptionsGroup>
|
||||
|
||||
<PanelOptionsGroup title="Field (default)">
|
||||
<FieldPropertiesEditor showMinMax={true} onChange={this.onDefaultsChange} value={fieldOptions.defaults} />
|
||||
<FieldPropertiesEditor showMinMax={true} onChange={this.onDefaultsChange} value={defaults} />
|
||||
</PanelOptionsGroup>
|
||||
|
||||
<PieChartOptionsBox onOptionsChange={onOptionsChange} options={options} />
|
||||
</PanelOptionsGrid>
|
||||
|
||||
<ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={fieldOptions.mappings} />
|
||||
<ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -18,17 +18,21 @@ import { FontSizeEditor } from './FontSizeEditor';
|
||||
import { SparklineEditor } from './SparklineEditor';
|
||||
|
||||
export class SingleStatEditor extends PureComponent<PanelEditorProps<SingleStatOptions>> {
|
||||
onThresholdsChanged = (thresholds: Threshold[]) =>
|
||||
this.onDisplayOptionsChanged({
|
||||
...this.props.options.fieldOptions,
|
||||
onThresholdsChanged = (thresholds: Threshold[]) => {
|
||||
const current = this.props.options.fieldOptions.defaults;
|
||||
this.onDefaultsChange({
|
||||
...current,
|
||||
thresholds,
|
||||
});
|
||||
};
|
||||
|
||||
onValueMappingsChanged = (mappings: ValueMapping[]) =>
|
||||
this.onDisplayOptionsChanged({
|
||||
...this.props.options.fieldOptions,
|
||||
onValueMappingsChanged = (mappings: ValueMapping[]) => {
|
||||
const current = this.props.options.fieldOptions.defaults;
|
||||
this.onDefaultsChange({
|
||||
...current,
|
||||
mappings,
|
||||
});
|
||||
};
|
||||
|
||||
onDisplayOptionsChanged = (fieldOptions: FieldDisplayOptions) =>
|
||||
this.props.onOptionsChange({
|
||||
@ -52,6 +56,7 @@ export class SingleStatEditor extends PureComponent<PanelEditorProps<SingleStatO
|
||||
render() {
|
||||
const { options } = this.props;
|
||||
const { fieldOptions } = options;
|
||||
const { defaults } = fieldOptions;
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -61,17 +66,17 @@ export class SingleStatEditor extends PureComponent<PanelEditorProps<SingleStatO
|
||||
</PanelOptionsGroup>
|
||||
|
||||
<PanelOptionsGroup title="Field (default)">
|
||||
<FieldPropertiesEditor showMinMax={true} onChange={this.onDefaultsChange} value={fieldOptions.defaults} />
|
||||
<FieldPropertiesEditor showMinMax={true} onChange={this.onDefaultsChange} value={defaults} />
|
||||
</PanelOptionsGroup>
|
||||
|
||||
<FontSizeEditor options={options} onChange={this.props.onOptionsChange} />
|
||||
<ColoringEditor options={options} onChange={this.props.onOptionsChange} />
|
||||
<SparklineEditor options={options.sparkline} onChange={this.onSparklineChanged} />
|
||||
|
||||
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={fieldOptions.thresholds} />
|
||||
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={defaults.thresholds} />
|
||||
</PanelOptionsGrid>
|
||||
|
||||
<ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={fieldOptions.mappings} />
|
||||
<ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -25,10 +25,15 @@ export interface SingleStatOptions extends SingleStatBaseOptions {
|
||||
export const standardFieldDisplayOptions: FieldDisplayOptions = {
|
||||
values: false,
|
||||
calcs: [ReducerID.mean],
|
||||
defaults: {},
|
||||
defaults: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
thresholds: [
|
||||
{ value: -Infinity, color: 'green' },
|
||||
{ value: 80, color: 'red' }, // 80%
|
||||
],
|
||||
},
|
||||
override: {},
|
||||
mappings: [],
|
||||
thresholds: [{ index: 0, value: -Infinity, color: 'green' }, { index: 1, value: 80, color: 'red' }],
|
||||
};
|
||||
|
||||
export const defaults: SingleStatOptions = {
|
||||
|
Loading…
Reference in New Issue
Block a user