diff --git a/packages/grafana-data/src/types/data.ts b/packages/grafana-data/src/types/data.ts index 9d05768b679..fe7bd106369 100644 --- a/packages/grafana-data/src/types/data.ts +++ b/packages/grafana-data/src/types/data.ts @@ -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 { diff --git a/packages/grafana-data/src/types/threshold.ts b/packages/grafana-data/src/types/threshold.ts index 741ecd940bf..fac4352a0b0 100644 --- a/packages/grafana-data/src/types/threshold.ts +++ b/packages/grafana-data/src/types/threshold.ts @@ -1,5 +1,4 @@ export interface Threshold { - index: number; value: number; color: string; } diff --git a/packages/grafana-data/src/utils/thresholds.ts b/packages/grafana-data/src/utils/thresholds.ts index 2fdca67194a..20068893078 100644 --- a/packages/grafana-data/src/utils/thresholds.ts +++ b/packages/grafana-data/src/utils/thresholds.ts @@ -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; + }); } diff --git a/packages/grafana-ui/src/components/BarGauge/BarGauge.story.tsx b/packages/grafana-ui/src/components/BarGauge/BarGauge.story.tsx index 8e8f479691b..930709364e8 100644 --- a/packages/grafana-ui/src/components/BarGauge/BarGauge.story.tsx +++ b/packages/grafana-ui/src/components/BarGauge/BarGauge.story.tsx @@ -49,9 +49,9 @@ function addBarGaugeStory(name: string, overrides: Partial) { 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 }, ], }; diff --git a/packages/grafana-ui/src/components/BarGauge/BarGauge.test.tsx b/packages/grafana-ui/src/components/BarGauge/BarGauge.test.tsx index 0dea5e2c9b5..cd7254bc5e1 100644 --- a/packages/grafana-ui/src/components/BarGauge/BarGauge.test.tsx +++ b/packages/grafana-ui/src/components/BarGauge/BarGauge.test.tsx @@ -25,11 +25,7 @@ function getProps(propOverrides?: Partial): 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: { diff --git a/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx b/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx index de397d599e4..a0194af56ef 100644 --- a/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx +++ b/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx @@ -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 { 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); diff --git a/packages/grafana-ui/src/components/Gauge/Gauge.test.tsx b/packages/grafana-ui/src/components/Gauge/Gauge.test.tsx index c6a49eb5b55..bce40c3f246 100644 --- a/packages/grafana-ui/src/components/Gauge/Gauge.test.tsx +++ b/packages/grafana-ui/src/components/Gauge/Gauge.test.tsx @@ -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' }, ], }); diff --git a/packages/grafana-ui/src/components/Gauge/Gauge.tsx b/packages/grafana-ui/src/components/Gauge/Gauge.tsx index fa836fe29a7..3952c6ef54a 100644 --- a/packages/grafana-ui/src/components/Gauge/Gauge.tsx +++ b/packages/grafana-ui/src/components/Gauge/Gauge.tsx @@ -43,12 +43,12 @@ export class Gauge extends PureComponent { 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) }, diff --git a/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts b/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts index 047dd68ffcc..d5a64d5a0e1 100644 --- a/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts +++ b/packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts @@ -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 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 { + return { + // Drops 'index' + value: t.value === null ? -Infinity : t.value, + color: t.color, + }; + }); + sortThresholds(copy); + copy[0].value = -Infinity; + return copy; +} diff --git a/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.test.tsx b/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.test.tsx index db494053d6e..73a49f0072c 100644 --- a/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.test.tsx +++ b/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.test.tsx @@ -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) => { @@ -20,6 +20,10 @@ const setup = (propOverrides?: Partial) => { }; }; +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; - 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' }, ]); }); }); diff --git a/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx b/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx index 905b23a7547..8a381ea4f60 100644 --- a/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx +++ b/packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx @@ -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 { 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, 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, 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 { }; 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 (
@@ -175,12 +173,11 @@ export class ThresholdsEditor extends PureComponent {
)} - {threshold.index === 0 && ( + {!isFinite(threshold.value) ? (
- )} - {threshold.index > 0 && ( + ) : ( <>
{ onChange={(event: ChangeEvent) => this.onChangeThresholdValue(event, threshold)} value={threshold.value} onBlur={this.onBlur} - readOnly={threshold.index === 0} />
this.onRemoveThreshold(threshold)}> @@ -212,13 +208,10 @@ export class ThresholdsEditor extends PureComponent { {thresholds .slice(0) .reverse() - .map((threshold, index) => { + .map(threshold => { return ( -
-
this.onAddThreshold(threshold.index + 1)} - > +
+
this.onAddThresholdAfter(threshold)}>
{ ); } } + +export function threshodsWithoutKey(thresholds: ThresholdWithKey[]): Threshold[] { + return thresholds.map(t => { + const { key, ...rest } = t; + return rest; // everything except key + }); +} diff --git a/packages/grafana-ui/src/components/ThresholdsEditor/__snapshots__/ThresholdsEditor.test.tsx.snap b/packages/grafana-ui/src/components/ThresholdsEditor/__snapshots__/ThresholdsEditor.test.tsx.snap index 50e45051386..9db0753f147 100644 --- a/packages/grafana-ui/src/components/ThresholdsEditor/__snapshots__/ThresholdsEditor.test.tsx.snap +++ b/packages/grafana-ui/src/components/ThresholdsEditor/__snapshots__/ThresholdsEditor.test.tsx.snap @@ -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`] = ` >
{ 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'); }); diff --git a/packages/grafana-ui/src/utils/displayValue.ts b/packages/grafana-ui/src/utils/displayValue.ts index 57a2125a2a7..2f55531b6a4 100644 --- a/packages/grafana-ui/src/utils/displayValue.ts +++ b/packages/grafana-ui/src/utils/displayValue.ts @@ -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; - 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); diff --git a/packages/grafana-ui/src/utils/fieldDisplay.test.ts b/packages/grafana-ui/src/utils/fieldDisplay.test.ts index 458a97be2ad..648834a1174 100644 --- a/packages/grafana-ui/src/utils/fieldDisplay.test.ts +++ b/packages/grafana-ui/src/utils/fieldDisplay.test.ts @@ -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); + }); }); diff --git a/packages/grafana-ui/src/utils/fieldDisplay.ts b/packages/grafana-ui/src/utils/fieldDisplay.ts index 0ba354625f9..4dd59f8c978 100644 --- a/packages/grafana-ui/src/utils/fieldDisplay.ts +++ b/packages/grafana-ui/src/utils/fieldDisplay.ts @@ -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; // Use these values unless otherwise stated override: Partial; // 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 { diff --git a/public/app/features/dashboard/state/PanelModel.test.ts b/public/app/features/dashboard/state/PanelModel.test.ts index 3dcb5f5cb06..cad0be87e66 100644 --- a/public/app/features/dashboard/state/PanelModel.test.ts +++ b/public/app/features/dashboard/state/PanelModel.test.ts @@ -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', () => { diff --git a/public/app/features/dashboard/state/PanelModel.ts b/public/app/features/dashboard/state/PanelModel.ts index bf6099a6449..78018d3f64c 100644 --- a/public/app/features/dashboard/state/PanelModel.ts +++ b/public/app/features/dashboard/state/PanelModel.ts @@ -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; } diff --git a/public/app/plugins/panel/bargauge/BarGaugeMigrations.test.ts b/public/app/plugins/panel/bargauge/BarGaugeMigrations.test.ts new file mode 100644 index 00000000000..5d354f13706 --- /dev/null +++ b/public/app/plugins/panel/bargauge/BarGaugeMigrations.test.ts @@ -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(); + }); +}); diff --git a/public/app/plugins/panel/bargauge/BarGaugeMigrations.ts b/public/app/plugins/panel/bargauge/BarGaugeMigrations.ts new file mode 100644 index 00000000000..d85796ee85e --- /dev/null +++ b/public/app/plugins/panel/bargauge/BarGaugeMigrations.ts @@ -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): Partial => { + 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); +}; diff --git a/public/app/plugins/panel/bargauge/BarGaugePanel.tsx b/public/app/plugins/panel/bargauge/BarGaugePanel.tsx index b0df78da0f2..87a8e735a89 100644 --- a/public/app/plugins/panel/bargauge/BarGaugePanel.tsx +++ b/public/app/plugins/panel/bargauge/BarGaugePanel.tsx @@ -14,7 +14,6 @@ import { PanelProps } from '@grafana/ui'; export class BarGaugePanel extends PureComponent> { 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> { width={width} height={height} orientation={options.orientation} - thresholds={fieldOptions.thresholds} + thresholds={field.thresholds} theme={config.theme} itemSpacing={this.getItemSpacing()} displayMode={options.displayMode} diff --git a/public/app/plugins/panel/bargauge/BarGaugePanelEditor.tsx b/public/app/plugins/panel/bargauge/BarGaugePanelEditor.tsx index 37dd077f461..dedf8fdb7e9 100644 --- a/public/app/plugins/panel/bargauge/BarGaugePanelEditor.tsx +++ b/public/app/plugins/panel/bargauge/BarGaugePanelEditor.tsx @@ -19,17 +19,21 @@ import { Threshold, ValueMapping } from '@grafana/data'; import { BarGaugeOptions, orientationOptions, displayModes } from './types'; export class BarGaugePanelEditor extends PureComponent> { - 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 - + - + - + ); } diff --git a/public/app/plugins/panel/bargauge/__snapshots__/BarGaugeMigrations.test.ts.snap b/public/app/plugins/panel/bargauge/__snapshots__/BarGaugeMigrations.test.ts.snap new file mode 100644 index 00000000000..9d366d61544 --- /dev/null +++ b/public/app/plugins/panel/bargauge/__snapshots__/BarGaugeMigrations.test.ts.snap @@ -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", +} +`; diff --git a/public/app/plugins/panel/bargauge/module.tsx b/public/app/plugins/panel/bargauge/module.tsx index 26ab6720609..423aef5fab4 100644 --- a/public/app/plugins/panel/bargauge/module.tsx +++ b/public/app/plugins/panel/bargauge/module.tsx @@ -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(BarGaugePanel) .setDefaults(defaults) .setEditor(BarGaugePanelEditor) - .setPanelChangeHandler(sharedSingleStatOptionsCheck); + .setPanelChangeHandler(sharedSingleStatOptionsCheck) + .setMigrationHandler(barGaugePanelMigrationCheck); diff --git a/public/app/plugins/panel/gauge/GaugeMigrations.ts b/public/app/plugins/panel/gauge/GaugeMigrations.ts index 961c1e07c71..730b7f60b42 100644 --- a/public/app/plugins/panel/gauge/GaugeMigrations.ts +++ b/public/app/plugins/panel/gauge/GaugeMigrations.ts @@ -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): Partial => { @@ -10,7 +13,8 @@ export const gaugePanelMigrationCheck = (panel: PanelModel): 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): 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 diff --git a/public/app/plugins/panel/gauge/GaugePanel.tsx b/public/app/plugins/panel/gauge/GaugePanel.tsx index b08b49f01bf..1e1c02c3755 100644 --- a/public/app/plugins/panel/gauge/GaugePanel.tsx +++ b/public/app/plugins/panel/gauge/GaugePanel.tsx @@ -14,7 +14,6 @@ import { PanelProps, VizRepeater } from '@grafana/ui'; export class GaugePanel extends PureComponent> { 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> { value={display} width={width} height={height} - thresholds={fieldOptions.thresholds} + thresholds={field.thresholds} showThresholdLabels={options.showThresholdLabels} showThresholdMarkers={options.showThresholdMarkers} minValue={field.min} diff --git a/public/app/plugins/panel/gauge/GaugePanelEditor.tsx b/public/app/plugins/panel/gauge/GaugePanelEditor.tsx index 616ca78014a..e1dd0481d15 100644 --- a/public/app/plugins/panel/gauge/GaugePanelEditor.tsx +++ b/public/app/plugins/panel/gauge/GaugePanelEditor.tsx @@ -27,17 +27,21 @@ export class GaugePanelEditor extends PureComponent - 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 @@ -80,13 +85,13 @@ export class GaugePanelEditor extends PureComponent - + - + - + ); } diff --git a/public/app/plugins/panel/gauge/__snapshots__/GaugeMigrations.test.ts.snap b/public/app/plugins/panel/gauge/__snapshots__/GaugeMigrations.test.ts.snap index 195ff674274..093914fc7a0 100644 --- a/public/app/plugins/panel/gauge/__snapshots__/GaugeMigrations.test.ts.snap +++ b/public/app/plugins/panel/gauge/__snapshots__/GaugeMigrations.test.ts.snap @@ -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, diff --git a/public/app/plugins/panel/piechart/PieChartPanelEditor.tsx b/public/app/plugins/panel/piechart/PieChartPanelEditor.tsx index a3c94a0a49e..6379199ae7a 100644 --- a/public/app/plugins/panel/piechart/PieChartPanelEditor.tsx +++ b/public/app/plugins/panel/piechart/PieChartPanelEditor.tsx @@ -14,11 +14,13 @@ import { PieChartOptionsBox } from './PieChartOptionsBox'; import { PieChartOptions } from './types'; export class PieChartPanelEditor extends PureComponent> { - 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 @@ -45,13 +48,13 @@ export class PieChartPanelEditor extends PureComponent - + - + ); } diff --git a/public/app/plugins/panel/singlestat2/SingleStatEditor.tsx b/public/app/plugins/panel/singlestat2/SingleStatEditor.tsx index 001a36ab40e..6a940f07338 100644 --- a/public/app/plugins/panel/singlestat2/SingleStatEditor.tsx +++ b/public/app/plugins/panel/singlestat2/SingleStatEditor.tsx @@ -18,17 +18,21 @@ import { FontSizeEditor } from './FontSizeEditor'; import { SparklineEditor } from './SparklineEditor'; export class SingleStatEditor extends PureComponent> { - 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 @@ -61,17 +66,17 @@ export class SingleStatEditor extends PureComponent - + - + - + ); } diff --git a/public/app/plugins/panel/singlestat2/types.ts b/public/app/plugins/panel/singlestat2/types.ts index c86d5976c83..761fc56e6ec 100644 --- a/public/app/plugins/panel/singlestat2/types.ts +++ b/public/app/plugins/panel/singlestat2/types.ts @@ -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 = {