Просмотр исходного кода

make value processing reusable

ryan 7 лет назад
Родитель
Сommit
8cd54c94e9

+ 4 - 88
packages/grafana-ui/src/components/Gauge/Gauge.test.tsx

@@ -2,7 +2,6 @@ import React from 'react';
 import { shallow } from 'enzyme';
 
 import { Gauge, Props } from './Gauge';
-import { ValueMapping, MappingType } from '../../types';
 import { getTheme } from '../../themes';
 
 jest.mock('jquery', () => ({
@@ -12,19 +11,16 @@ jest.mock('jquery', () => ({
 const setup = (propOverrides?: object) => {
   const props: Props = {
     maxValue: 100,
-    valueMappings: [],
     minValue: 0,
-    prefix: '',
     showThresholdMarkers: true,
     showThresholdLabels: false,
-    suffix: '',
     thresholds: [{ index: 0, value: -Infinity, color: '#7EB26D' }],
-    unit: 'none',
-    stat: 'avg',
     height: 300,
     width: 300,
-    value: 25,
-    decimals: 0,
+    value: {
+      text: '25',
+      numeric: 25,
+    },
     theme: getTheme(),
   };
 
@@ -39,38 +35,6 @@ const setup = (propOverrides?: object) => {
   };
 };
 
-describe('Get font color', () => {
-  it('should get first threshold color when only one threshold', () => {
-    const { instance } = setup({ thresholds: [{ index: 0, value: -Infinity, color: '#7EB26D' }] });
-
-    expect(instance.getFontColor(49)).toEqual('#7EB26D');
-  });
-
-  it('should get the threshold color if value is same as a threshold', () => {
-    const { instance } = setup({
-      thresholds: [
-        { index: 2, value: 75, color: '#6ED0E0' },
-        { index: 1, value: 50, color: '#EAB839' },
-        { index: 0, value: -Infinity, color: '#7EB26D' },
-      ],
-    });
-
-    expect(instance.getFontColor(50)).toEqual('#EAB839');
-  });
-
-  it('should get the nearest threshold color between thresholds', () => {
-    const { instance } = setup({
-      thresholds: [
-        { index: 2, value: 75, color: '#6ED0E0' },
-        { index: 1, value: 50, color: '#EAB839' },
-        { index: 0, value: -Infinity, color: '#7EB26D' },
-      ],
-    });
-
-    expect(instance.getFontColor(55)).toEqual('#EAB839');
-  });
-});
-
 describe('Get thresholds formatted', () => {
   it('should return first thresholds color for min and max', () => {
     const { instance } = setup({ thresholds: [{ index: 0, value: -Infinity, color: '#7EB26D' }] });
@@ -98,51 +62,3 @@ describe('Get thresholds formatted', () => {
     ]);
   });
 });
-
-describe('Format value', () => {
-  it('should return if value isNaN', () => {
-    const valueMappings: ValueMapping[] = [];
-    const value = 'N/A';
-    const { instance } = setup({ valueMappings });
-
-    const result = instance.formatValue(value);
-
-    expect(result).toEqual('N/A');
-  });
-
-  it('should return formatted value if there are no value mappings', () => {
-    const valueMappings: ValueMapping[] = [];
-    const value = '6';
-    const { instance } = setup({ valueMappings, decimals: 1 });
-
-    const result = instance.formatValue(value);
-
-    expect(result).toEqual('6.0');
-  });
-
-  it('should return formatted value if there are no matching value mappings', () => {
-    const valueMappings: ValueMapping[] = [
-      { id: 0, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' },
-      { id: 1, operator: '', text: '1-9', type: MappingType.RangeToText, from: '1', to: '9' },
-    ];
-    const value = '10';
-    const { instance } = setup({ valueMappings, decimals: 1 });
-
-    const result = instance.formatValue(value);
-
-    expect(result).toEqual('10.0');
-  });
-
-  it('should return mapped value if there are matching value mappings', () => {
-    const valueMappings: ValueMapping[] = [
-      { id: 0, operator: '', text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' },
-      { id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' },
-    ];
-    const value = '11';
-    const { instance } = setup({ valueMappings, decimals: 1 });
-
-    const result = instance.formatValue(value);
-
-    expect(result).toEqual('1-20');
-  });
-});

+ 10 - 67
packages/grafana-ui/src/components/Gauge/Gauge.tsx

@@ -1,28 +1,20 @@
 import React, { PureComponent } from 'react';
 import $ from 'jquery';
-import { getMappedValue } from '../../utils/valueMappings';
 import { getColorFromHexRgbOrName } from '../../utils/namedColorsPalette';
 import { Themeable, GrafanaThemeType } from '../../types/theme';
-import { ValueMapping, Threshold, BasicGaugeColor } from '../../types/panel';
-import { getValueFormat } from '../../utils/valueFormats/valueFormats';
-
-type TimeSeriesValue = string | number | null;
+import { Threshold, BasicGaugeColor } from '../../types/panel';
+import { DisplayValue } from '../../utils/valueProcessor';
 
 export interface Props extends Themeable {
-  decimals?: number | null;
+  width: number;
   height: number;
-  valueMappings: ValueMapping[];
   maxValue: number;
   minValue: number;
-  prefix: string;
   thresholds: Threshold[];
   showThresholdMarkers: boolean;
   showThresholdLabels: boolean;
-  stat: string;
-  suffix: string;
-  unit: string;
-  width: number;
-  value: number;
+
+  value: DisplayValue;
 }
 
 const FONT_SCALE = 1;
@@ -32,15 +24,10 @@ export class Gauge extends PureComponent<Props> {
 
   static defaultProps = {
     maxValue: 100,
-    valueMappings: [],
     minValue: 0,
-    prefix: '',
     showThresholdMarkers: true,
     showThresholdLabels: false,
-    suffix: '',
     thresholds: [],
-    unit: 'none',
-    stat: 'avg',
     theme: GrafanaThemeType.Dark,
   };
 
@@ -52,49 +39,6 @@ export class Gauge extends PureComponent<Props> {
     this.draw();
   }
 
-  formatValue(value: TimeSeriesValue) {
-    const { decimals, valueMappings, prefix, suffix, unit } = this.props;
-
-    if (isNaN(value as number)) {
-      return value;
-    }
-
-    if (valueMappings.length > 0) {
-      const valueMappedValue = getMappedValue(valueMappings, value);
-      if (valueMappedValue) {
-        return `${prefix && prefix + ' '}${valueMappedValue.text}${suffix && ' ' + suffix}`;
-      }
-    }
-
-    const formatFunc = getValueFormat(unit);
-    const formattedValue = formatFunc(value as number, decimals);
-    const handleNoValueValue = formattedValue || 'no value';
-
-    return `${prefix && prefix + ' '}${handleNoValueValue}${suffix && ' ' + suffix}`;
-  }
-
-  getFontColor(value: TimeSeriesValue) {
-    const { thresholds, theme } = this.props;
-
-    if (thresholds.length === 1) {
-      return getColorFromHexRgbOrName(thresholds[0].color, theme.type);
-    }
-
-    const atThreshold = thresholds.filter(threshold => (value as number) === threshold.value)[0];
-    if (atThreshold) {
-      return getColorFromHexRgbOrName(atThreshold.color, theme.type);
-    }
-
-    const belowThreshold = thresholds.filter(threshold => (value as number) > threshold.value);
-
-    if (belowThreshold.length > 0) {
-      const nearestThreshold = belowThreshold.sort((t1, t2) => t2.value - t1.value)[0];
-      return getColorFromHexRgbOrName(nearestThreshold.color, theme.type);
-    }
-
-    return BasicGaugeColor.Red;
-  }
-
   getFormattedThresholds() {
     const { maxValue, minValue, thresholds, theme } = this.props;
 
@@ -123,15 +67,13 @@ export class Gauge extends PureComponent<Props> {
   draw() {
     const { maxValue, minValue, showThresholdLabels, showThresholdMarkers, width, height, theme, value } = this.props;
 
-    const formattedValue = this.formatValue(value) as string;
     const dimension = Math.min(width, height * 1.3);
     const backgroundColor = theme.type === GrafanaThemeType.Light ? 'rgb(230,230,230)' : theme.colors.dark3;
 
     const gaugeWidthReduceRatio = showThresholdLabels ? 1.5 : 1;
     const gaugeWidth = Math.min(dimension / 6, 60) / gaugeWidthReduceRatio;
     const thresholdMarkersWidth = gaugeWidth / 5;
-    const fontSize =
-      Math.min(dimension / 5, 100) * (formattedValue !== null ? this.getFontScale(formattedValue.length) : 1);
+    const fontSize = Math.min(dimension / 5, 100) * this.getFontScale(value.text.length);
     const thresholdLabelFontSize = fontSize / 2.5;
 
     const options = {
@@ -160,9 +102,9 @@ export class Gauge extends PureComponent<Props> {
             width: thresholdMarkersWidth,
           },
           value: {
-            color: this.getFontColor(value),
+            color: value.color ? value.color : BasicGaugeColor.Red,
             formatter: () => {
-              return formattedValue;
+              return value.text;
             },
             font: { size: fontSize, family: '"Helvetica Neue", Helvetica, Arial, sans-serif' },
           },
@@ -171,7 +113,8 @@ export class Gauge extends PureComponent<Props> {
       },
     };
 
-    const plotSeries = { data: [[0, value]] };
+    const numeric = value.numeric !== null ? value.numeric : 0;
+    const plotSeries = { data: [[0, numeric]] };
 
     try {
       $.plot(this.canvasElement, [plotSeries], options);

+ 107 - 0
packages/grafana-ui/src/utils/valueProcessor.test.ts

@@ -0,0 +1,107 @@
+import { getValueProcessor, getColorFromThreshold } from './valueProcessor';
+import { getTheme } from '../themes/index';
+import { GrafanaThemeType } from '../types/theme';
+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);
+    });
+  });
+});
+
+describe('Get color from threshold', () => {
+  it('should get first threshold color when only one threshold', () => {
+    const thresholds = [{ index: 0, value: -Infinity, color: '#7EB26D' }];
+    expect(getColorFromThreshold(49, thresholds)).toEqual('#7EB26D');
+  });
+
+  it('should get the threshold color if value is same as a threshold', () => {
+    const thresholds = [
+      { index: 2, value: 75, color: '#6ED0E0' },
+      { index: 1, value: 50, color: '#EAB839' },
+      { index: 0, value: -Infinity, color: '#7EB26D' },
+    ];
+    expect(getColorFromThreshold(50, thresholds)).toEqual('#EAB839');
+  });
+
+  it('should get the nearest threshold color between thresholds', () => {
+    const thresholds = [
+      { index: 2, value: 75, color: '#6ED0E0' },
+      { index: 1, value: 50, color: '#EAB839' },
+      { index: 0, value: -Infinity, color: '#7EB26D' },
+    ];
+    expect(getColorFromThreshold(55, thresholds)).toEqual('#EAB839');
+  });
+});
+
+describe('Format value', () => {
+  it('should return if value isNaN', () => {
+    const valueMappings: ValueMapping[] = [];
+    const value = 'N/A';
+    const instance = getValueProcessor({ mappings: valueMappings });
+
+    const result = instance(value);
+
+    expect(result.text).toEqual('N/A');
+  });
+
+  it('should return formatted value if there are no value mappings', () => {
+    const valueMappings: ValueMapping[] = [];
+    const value = '6';
+
+    const instance = getValueProcessor({ mappings: valueMappings, decimals: 1 });
+
+    const result = instance(value);
+
+    expect(result.text).toEqual('6.0');
+  });
+
+  it('should return formatted value if there are no matching value mappings', () => {
+    const valueMappings: ValueMapping[] = [
+      { id: 0, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' },
+      { id: 1, operator: '', text: '1-9', type: MappingType.RangeToText, from: '1', to: '9' },
+    ];
+    const value = '10';
+    const instance = getValueProcessor({ mappings: valueMappings, decimals: 1 });
+
+    const result = instance(value);
+
+    expect(result.text).toEqual('10.0');
+  });
+
+  it('should return mapped value if there are matching value mappings', () => {
+    const valueMappings: ValueMapping[] = [
+      { id: 0, operator: '', text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' },
+      { id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' },
+    ];
+    const value = '11';
+    const instance = getValueProcessor({ mappings: valueMappings, decimals: 1 });
+
+    expect(instance(value).text).toEqual('1-20');
+  });
+});

+ 97 - 0
packages/grafana-ui/src/utils/valueProcessor.ts

@@ -0,0 +1,97 @@
+import { ValueMapping, Threshold } from '../types/panel';
+import _ from 'lodash';
+import { getValueFormat, DecimalCount } from './valueFormats/valueFormats';
+import { getMappedValue } from './valueMappings';
+import { GrafanaTheme, GrafanaThemeType } from '../types/theme';
+import { getColorFromHexRgbOrName } from './namedColorsPalette';
+
+export interface DisplayValue {
+  text: string; // How the value should be displayed
+  numeric?: number; // the value as a number
+  color?: string; // suggested color
+}
+
+export interface DisplayValueOptions {
+  unit?: string;
+  decimals?: DecimalCount;
+  scaledDecimals?: DecimalCount;
+  isUtc?: boolean;
+
+  color?: string;
+  mappings?: ValueMapping[];
+  thresholds?: Threshold[];
+  prefix?: string;
+  suffix?: string;
+
+  noValue?: string;
+  theme?: GrafanaTheme; // Will pick 'dark' if not defined
+}
+
+export type ValueProcessor = (value: any) => DisplayValue;
+
+export function getValueProcessor(options?: DisplayValueOptions): ValueProcessor {
+  if (options && !_.isEmpty(options)) {
+    const formatFunc = getValueFormat(options.unit || 'none');
+    return (value: any) => {
+      const { prefix, suffix, mappings, thresholds, theme } = options;
+      let color = options.color;
+
+      let text = _.toString(value);
+      const numeric = _.toNumber(value);
+
+      if (mappings && mappings.length > 0) {
+        const mappedValue = getMappedValue(mappings, value);
+        if (mappedValue) {
+          text = mappedValue.text;
+          // TODO? convert the mapped value back to a number?
+        }
+      }
+
+      if (_.isNumber(numeric)) {
+        text = formatFunc(numeric, options.decimals, options.scaledDecimals, options.isUtc);
+        if (thresholds && thresholds.length > 0) {
+          color = getColorFromThreshold(numeric, thresholds, theme);
+        }
+      }
+
+      if (!text) {
+        text = options.noValue ? options.noValue : '';
+      }
+      if (prefix) {
+        text = prefix + text;
+      }
+      if (suffix) {
+        text = text + suffix;
+      }
+      return { text, numeric, color };
+    };
+  }
+  return toStringProcessor;
+}
+
+function toStringProcessor(value: any): DisplayValue {
+  return { text: _.toString(value), numeric: _.toNumber(value) };
+}
+
+export function getColorFromThreshold(value: number, thresholds: Threshold[], theme?: GrafanaTheme): string {
+  const themeType = theme ? theme.type : GrafanaThemeType.Dark;
+
+  if (thresholds.length === 1) {
+    return getColorFromHexRgbOrName(thresholds[0].color, themeType);
+  }
+
+  const atThreshold = thresholds.filter(threshold => value === threshold.value)[0];
+  if (atThreshold) {
+    return getColorFromHexRgbOrName(atThreshold.color, themeType);
+  }
+
+  const belowThreshold = thresholds.filter(threshold => value > threshold.value);
+
+  if (belowThreshold.length > 0) {
+    const nearestThreshold = belowThreshold.sort((t1, t2) => t2.value - t1.value)[0];
+    return getColorFromHexRgbOrName(nearestThreshold.color, themeType);
+  }
+
+  // Use the first threshold as the default color
+  return getColorFromHexRgbOrName(thresholds[0].color, themeType);
+}

+ 64 - 0
public/app/plugins/panel/gauge/DisplayValueEditor.tsx

@@ -0,0 +1,64 @@
+// Libraries
+import React, { PureComponent } from 'react';
+
+// Components
+import { FormField, FormLabel, PanelOptionsGroup, UnitPicker } from '@grafana/ui';
+
+// Types
+import { DisplayValueOptions } from '@grafana/ui/src/utils/valueProcessor';
+
+const labelWidth = 6;
+
+export interface Props {
+  options: DisplayValueOptions;
+  onChange: (options: DisplayValueOptions) => void;
+}
+
+export class DisplayValueEditor extends PureComponent<Props> {
+  onUnitChange = unit => this.props.onChange({ ...this.props.options, unit: unit.value });
+
+  onDecimalChange = event => {
+    if (!isNaN(event.target.value)) {
+      this.props.onChange({
+        ...this.props.options,
+        decimals: parseInt(event.target.value, 10),
+      });
+    } else {
+      this.props.onChange({
+        ...this.props.options,
+        decimals: null,
+      });
+    }
+  };
+
+  onPrefixChange = event => this.props.onChange({ ...this.props.options, prefix: event.target.value });
+  onSuffixChange = event => this.props.onChange({ ...this.props.options, suffix: event.target.value });
+
+  render() {
+    const { unit, decimals, prefix, suffix } = this.props.options;
+
+    let decimalsString = '';
+    if (Number.isFinite(decimals)) {
+      decimalsString = decimals.toString();
+    }
+
+    return (
+      <PanelOptionsGroup title="Display Value">
+        <div className="gf-form">
+          <FormLabel width={labelWidth}>Unit</FormLabel>
+          <UnitPicker defaultValue={unit} onChange={this.onUnitChange} />
+        </div>
+        <FormField
+          label="Decimals"
+          labelWidth={labelWidth}
+          placeholder="auto"
+          onChange={this.onDecimalChange}
+          value={decimalsString}
+          type="number"
+        />
+        <FormField label="Prefix" labelWidth={labelWidth} onChange={this.onPrefixChange} value={prefix || ''} />
+        <FormField label="Suffix" labelWidth={labelWidth} onChange={this.onSuffixChange} value={suffix || ''} />
+      </PanelOptionsGroup>
+    );
+  }
+}

+ 27 - 15
public/app/plugins/panel/gauge/GaugePanel.tsx

@@ -9,30 +9,50 @@ import { Gauge } from '@grafana/ui';
 
 // Types
 import { GaugeOptions } from './types';
-import { PanelProps, NullValueMode, TimeSeriesValue } from '@grafana/ui/src/types';
+import { PanelProps, NullValueMode, BasicGaugeColor } from '@grafana/ui/src/types';
+import { DisplayValue, getValueProcessor } from '@grafana/ui/src/utils/valueProcessor';
 
 interface Props extends PanelProps<GaugeOptions> {}
 interface State {
-  value: TimeSeriesValue;
+  value: DisplayValue;
 }
 
 export class GaugePanel extends Component<Props, State> {
   constructor(props: Props) {
     super(props);
+
+    if (props.options.valueOptions) {
+      console.warn('TODO!! how do we best migration options?');
+    }
+
     this.state = {
-      value: this.findValue(props),
+      value: this.findDisplayValue(props),
     };
   }
 
   componentDidUpdate(prevProps: Props) {
     if (this.props.panelData !== prevProps.panelData) {
-      this.setState({ value: this.findValue(this.props) });
+      this.setState({ value: this.findDisplayValue(this.props) });
     }
   }
 
+  findDisplayValue(props: Props): DisplayValue {
+    const { replaceVariables, options } = this.props;
+    const { displayOptions } = options;
+
+    const prefix = replaceVariables(displayOptions.prefix);
+    const suffix = replaceVariables(displayOptions.suffix);
+    return getValueProcessor({
+      color: BasicGaugeColor.Red, // The default color
+      ...displayOptions,
+      prefix,
+      suffix,
+      // ??? theme:getTheme(GrafanaThemeType.Dark), !! how do I get it here???
+    })(this.findValue(props));
+  }
+
   findValue(props: Props): number | null {
     const { panelData, options } = props;
-    const { valueOptions } = options;
 
     if (panelData.timeSeries) {
       const vmSeries = processTimeSeries({
@@ -41,7 +61,7 @@ export class GaugePanel extends Component<Props, State> {
       });
 
       if (vmSeries[0]) {
-        return vmSeries[0].stats[valueOptions.stat];
+        return vmSeries[0].stats[options.stat];
       }
     } else if (panelData.tableData) {
       return panelData.tableData.rows[0].find(prop => prop > 0);
@@ -50,12 +70,9 @@ export class GaugePanel extends Component<Props, State> {
   }
 
   render() {
-    const { width, height, replaceVariables, options } = this.props;
-    const { valueOptions } = options;
+    const { width, height, options } = this.props;
     const { value } = this.state;
 
-    const prefix = replaceVariables(valueOptions.prefix);
-    const suffix = replaceVariables(valueOptions.suffix);
     return (
       <ThemeContext.Consumer>
         {theme => (
@@ -63,12 +80,7 @@ export class GaugePanel extends Component<Props, State> {
             value={value}
             width={width}
             height={height}
-            prefix={prefix}
-            suffix={suffix}
-            unit={valueOptions.unit}
-            decimals={valueOptions.decimals}
             thresholds={options.thresholds}
-            valueMappings={options.valueMappings}
             showThresholdLabels={options.showThresholdLabels}
             showThresholdMarkers={options.showThresholdMarkers}
             minValue={options.minValue}

+ 12 - 1
public/app/plugins/panel/gauge/GaugePanelEditor.tsx

@@ -11,6 +11,8 @@ import {
 import { SingleStatValueEditor } from 'app/plugins/panel/gauge/SingleStatValueEditor';
 import { GaugeOptionsBox } from './GaugeOptionsBox';
 import { GaugeOptions, SingleStatValueOptions } from './types';
+import { DisplayValueEditor } from './DisplayValueEditor';
+import { DisplayValueOptions } from '@grafana/ui/src/utils/valueProcessor';
 
 export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOptions>> {
   onThresholdsChanged = (thresholds: Threshold[]) =>
@@ -31,13 +33,22 @@ export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOption
       valueOptions,
     });
 
+  onDisplayOptionsChanged = (displayOptions: DisplayValueOptions) =>
+    this.props.onOptionsChange({
+      ...this.props.options,
+      displayOptions,
+    });
+
   render() {
     const { onOptionsChange, options } = this.props;
 
     return (
       <>
         <PanelOptionsGrid>
-          <SingleStatValueEditor onChange={this.onValueOptionsChanged} options={options.valueOptions} />
+          {/* This just sets the 'stats', that should be moved to somethign more general */}
+          <SingleStatValueEditor onChange={onOptionsChange} options={options} />
+
+          <DisplayValueEditor onChange={this.onDisplayOptionsChanged} options={options.displayOptions} />
           <GaugeOptionsBox onOptionsChange={onOptionsChange} options={options} />
           <ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={options.thresholds} />
         </PanelOptionsGrid>

+ 6 - 43
public/app/plugins/panel/gauge/SingleStatValueEditor.tsx

@@ -2,10 +2,10 @@
 import React, { PureComponent } from 'react';
 
 // Components
-import { FormField, FormLabel, PanelOptionsGroup, Select, UnitPicker } from '@grafana/ui';
+import { FormLabel, PanelOptionsGroup, Select } from '@grafana/ui';
 
 // Types
-import { SingleStatValueOptions } from './types';
+import { GaugeOptions } from './types';
 
 const statOptions = [
   { value: 'min', label: 'Min' },
@@ -24,41 +24,18 @@ const statOptions = [
 const labelWidth = 6;
 
 export interface Props {
-  options: SingleStatValueOptions;
-  onChange: (valueOptions: SingleStatValueOptions) => void;
+  options: GaugeOptions;
+  onChange: (options: GaugeOptions) => void;
 }
 
 export class SingleStatValueEditor extends PureComponent<Props> {
-  onUnitChange = unit => this.props.onChange({ ...this.props.options, unit: unit.value });
   onStatChange = stat => this.props.onChange({ ...this.props.options, stat: stat.value });
 
-  onDecimalChange = event => {
-    if (!isNaN(event.target.value)) {
-      this.props.onChange({
-        ...this.props.options,
-        decimals: parseInt(event.target.value, 10),
-      });
-    } else {
-      this.props.onChange({
-        ...this.props.options,
-        decimals: null,
-      });
-    }
-  };
-
-  onPrefixChange = event => this.props.onChange({ ...this.props.options, prefix: event.target.value });
-  onSuffixChange = event => this.props.onChange({ ...this.props.options, suffix: event.target.value });
-
   render() {
-    const { stat, unit, decimals, prefix, suffix } = this.props.options;
-
-    let decimalsString = '';
-    if (Number.isFinite(decimals)) {
-      decimalsString = decimals.toString();
-    }
+    const { stat } = this.props.options;
 
     return (
-      <PanelOptionsGroup title="Value">
+      <PanelOptionsGroup title="Show Value">
         <div className="gf-form">
           <FormLabel width={labelWidth}>Stat</FormLabel>
           <Select
@@ -68,20 +45,6 @@ export class SingleStatValueEditor extends PureComponent<Props> {
             value={statOptions.find(option => option.value === stat)}
           />
         </div>
-        <div className="gf-form">
-          <FormLabel width={labelWidth}>Unit</FormLabel>
-          <UnitPicker defaultValue={unit} onChange={this.onUnitChange} />
-        </div>
-        <FormField
-          label="Decimals"
-          labelWidth={labelWidth}
-          placeholder="auto"
-          onChange={this.onDecimalChange}
-          value={decimalsString}
-          type="number"
-        />
-        <FormField label="Prefix" labelWidth={labelWidth} onChange={this.onPrefixChange} value={prefix || ''} />
-        <FormField label="Suffix" labelWidth={labelWidth} onChange={this.onSuffixChange} value={suffix || ''} />
       </PanelOptionsGroup>
     );
   }

+ 15 - 7
public/app/plugins/panel/gauge/types.ts

@@ -1,15 +1,22 @@
 import { Threshold, ValueMapping } from '@grafana/ui';
+import { DisplayValueOptions } from '@grafana/ui/src/utils/valueProcessor';
 
 export interface GaugeOptions {
-  valueMappings: ValueMapping[];
   maxValue: number;
   minValue: number;
   showThresholdLabels: boolean;
   showThresholdMarkers: boolean;
-  thresholds: Threshold[];
-  valueOptions: SingleStatValueOptions;
+
+  stat: string;
+  displayOptions: DisplayValueOptions;
+
+  // TODO: migrate to DisplayValueOptions
+  thresholds?: Threshold[];
+  valueMappings?: ValueMapping[];
+  valueOptions?: SingleStatValueOptions;
 }
 
+/** Deprecated -- migrate to  */
 export interface SingleStatValueOptions {
   unit: string;
   suffix: string;
@@ -23,13 +30,14 @@ export const defaults: GaugeOptions = {
   maxValue: 100,
   showThresholdMarkers: true,
   showThresholdLabels: false,
-  valueOptions: {
+
+  stat: 'avg',
+  displayOptions: {
     prefix: '',
     suffix: '',
     decimals: null,
-    stat: 'avg',
     unit: 'none',
+    mappings: [],
+    thresholds: [],
   },
-  valueMappings: [],
-  thresholds: [],
 };