diff --git a/packages/grafana-ui/src/components/Gauge/Gauge.tsx b/packages/grafana-ui/src/components/Gauge/Gauge.tsx index 460547a4d7e..7be0600292f 100644 --- a/packages/grafana-ui/src/components/Gauge/Gauge.tsx +++ b/packages/grafana-ui/src/components/Gauge/Gauge.tsx @@ -113,8 +113,7 @@ export class Gauge extends PureComponent { }, }; - const numeric = value.numeric !== null ? value.numeric : 0; - const plotSeries = { data: [[0, numeric]] }; + const plotSeries = { data: [[0, value.numeric]] }; // May be NaN try { $.plot(this.canvasElement, [plotSeries], options); diff --git a/packages/grafana-ui/src/utils/valueProcessor.test.ts b/packages/grafana-ui/src/utils/valueProcessor.test.ts index 76c18f9e93c..22ecf604dc6 100644 --- a/packages/grafana-ui/src/utils/valueProcessor.test.ts +++ b/packages/grafana-ui/src/utils/valueProcessor.test.ts @@ -1,36 +1,62 @@ -import { getValueProcessor, getColorFromThreshold } from './valueProcessor'; -import { getTheme } from '../themes/index'; -import { GrafanaThemeType } from '../types/theme'; +import { getValueProcessor, getColorFromThreshold, ValueProcessor, DisplayValue } from './valueProcessor'; import { MappingType, ValueMapping } from '../types/panel'; -describe('Process values', () => { - const basicConversions = [ - { value: null, text: '' }, - { value: undefined, text: '' }, - { value: 1.23, text: '1.23' }, - { value: 1, text: '1' }, - { value: 'hello', text: 'hello' }, - { value: {}, text: '[object Object]' }, - { value: [], text: '' }, - { value: [1, 2, 3], text: '1,2,3' }, - { value: ['a', 'b', 'c'], text: 'a,b,c' }, - ]; - - it('should return return a string for any input value', () => { - const processor = getValueProcessor(); - basicConversions.forEach(item => { - expect(processor(item.value).text).toBe(item.text); - }); - }); - - it('should add a suffix to any value', () => { - const processor = getValueProcessor({ - prefix: 'xxx', - theme: getTheme(GrafanaThemeType.Dark), - }); - basicConversions.forEach(item => { - expect(processor(item.value).text).toBe('xxx' + item.text); - }); +function assertSame(input: any, processor: ValueProcessor, match: DisplayValue) { + const value = processor(input); + expect(value.text).toEqual(match.text); + if (match.hasOwnProperty('numeric')) { + expect(value.numeric).toEqual(match.numeric); + } +} + +describe('Process simple display values', () => { + const processor = getValueProcessor(); + + it('support null', () => { + assertSame(null, processor, { text: '', numeric: NaN }); + }); + + it('support undefined', () => { + assertSame(undefined, processor, { text: '', numeric: NaN }); + }); + + it('support NaN', () => { + assertSame(NaN, processor, { text: 'NaN', numeric: NaN }); + }); + it('Simple Float', () => { + assertSame(1.23456, processor, { text: '1.23456', numeric: 1.23456 }); + }); + + it('Integer', () => { + assertSame(3, processor, { text: '3', numeric: 3 }); + }); + + it('Text', () => { + assertSame('3', processor, { text: '3', numeric: 3 }); + }); + + it('Simple String', () => { + assertSame('hello', processor, { text: 'hello', numeric: NaN }); + }); + + it('empty array', () => { + assertSame([], processor, { text: '', numeric: NaN }); + }); + it('array of text', () => { + assertSame(['a', 'b', 'c'], processor, { text: 'a,b,c', numeric: NaN }); + }); + it('array of numbers', () => { + assertSame([1, 2, 3], processor, { text: '1,2,3', numeric: NaN }); + }); + it('empty object', () => { + assertSame({}, processor, { text: '[object Object]', numeric: NaN }); + }); + + it('boolean true', () => { + assertSame(true, processor, { text: 'true', numeric: 1 }); + }); + it('boolean false', () => { + assertSame(false, processor, { text: 'false', numeric: 0 }); }); }); diff --git a/packages/grafana-ui/src/utils/valueProcessor.ts b/packages/grafana-ui/src/utils/valueProcessor.ts index 243904c269c..7bd5e3477ac 100644 --- a/packages/grafana-ui/src/utils/valueProcessor.ts +++ b/packages/grafana-ui/src/utils/valueProcessor.ts @@ -7,7 +7,7 @@ import { getColorFromHexRgbOrName } from './namedColorsPalette'; export interface DisplayValue { text: string; // How the value should be displayed - numeric?: number; // the value as a number + numeric: number; // Use isNaN to check if it actually is a number color?: string; // suggested color } @@ -37,18 +37,25 @@ export function getValueProcessor(options?: DisplayValueOptions): ValueProcessor let color = options.color; let text = _.toString(value); - const numeric = _.toNumber(value); + let numeric = toNumber(value); + let shouldFormat = true; if (mappings && mappings.length > 0) { const mappedValue = getMappedValue(mappings, value); if (mappedValue) { text = mappedValue.text; - // TODO? convert the mapped value back to a number? + const v = toNumber(text); + if (!isNaN(v)) { + numeric = v; + } + shouldFormat = false; } } - if (_.isNumber(numeric)) { - text = formatFunc(numeric, options.decimals, options.scaledDecimals, options.isUtc); + if (!isNaN(numeric)) { + if (shouldFormat) { + text = formatFunc(numeric, options.decimals, options.scaledDecimals, options.isUtc); + } if (thresholds && thresholds.length > 0) { color = getColorFromThreshold(numeric, thresholds, theme); } @@ -69,8 +76,22 @@ export function getValueProcessor(options?: DisplayValueOptions): ValueProcessor return toStringProcessor; } +/** Will return any value as a number or NaN */ +function toNumber(value: any): number { + if (typeof value === 'number') { + return value; + } + if (value === null || value === undefined || Array.isArray(value)) { + return NaN; // lodash calls them 0 + } + if (typeof value === 'boolean') { + return value ? 1 : 0; + } + return _.toNumber(value); +} + function toStringProcessor(value: any): DisplayValue { - return { text: _.toString(value), numeric: _.toNumber(value) }; + return { text: _.toString(value), numeric: toNumber(value) }; } export function getColorFromThreshold(value: number, thresholds: Threshold[], theme?: GrafanaTheme): string {