Bladeren bron

revert most options sharing

ryan 6 jaren geleden
bovenliggende
commit
985f057ab3

+ 0 - 6
packages/grafana-ui/src/utils/singlestat.ts

@@ -1,17 +1,11 @@
 import { PanelData, NullValueMode, SingleStatValueInfo } from '../types';
 import { processTimeSeries } from './processTimeSeries';
-import { DisplayValueOptions } from './displayValue';
 
 export interface SingleStatProcessingOptions {
   panelData: PanelData;
   stat: string;
 }
 
-export interface SingleStatOptions {
-  stat: string;
-  display: DisplayValueOptions;
-}
-
 //
 // This is a temporary thing, waiting for a better data model and maybe unification between time series & table data
 //

+ 2 - 0
public/app/features/plugins/built_in_plugins.ts

@@ -25,6 +25,7 @@ import * as heatmapPanel from 'app/plugins/panel/heatmap/module';
 import * as tablePanel from 'app/plugins/panel/table/module';
 import * as table2Panel from 'app/plugins/panel/table2/module';
 import * as singlestatPanel from 'app/plugins/panel/singlestat/module';
+import * as singlestatPanel2 from 'app/plugins/panel/singlestat2/module';
 import * as gettingStartedPanel from 'app/plugins/panel/gettingstarted/module';
 import * as gaugePanel from 'app/plugins/panel/gauge/module';
 import * as barGaugePanel from 'app/plugins/panel/bargauge/module';
@@ -57,6 +58,7 @@ const builtInPlugins = {
   'app/plugins/panel/table/module': tablePanel,
   'app/plugins/panel/table2/module': table2Panel,
   'app/plugins/panel/singlestat/module': singlestatPanel,
+  'app/plugins/panel/singlestat2/module': singlestatPanel2,
   'app/plugins/panel/gettingstarted/module': gettingStartedPanel,
   'app/plugins/panel/gauge/module': gaugePanel,
   'app/plugins/panel/bargauge/module': barGaugePanel,

+ 4 - 10
public/app/plugins/panel/bargauge/BarGaugePanel.tsx

@@ -2,7 +2,7 @@
 import React from 'react';
 
 // Services & Utils
-import { DisplayValue, VizOrientation } from '@grafana/ui';
+import { DisplayValue } from '@grafana/ui';
 import { config } from 'app/core/config';
 
 // Components
@@ -10,17 +10,11 @@ import { BarGauge } from '@grafana/ui';
 
 // Types
 import { BarGaugeOptions } from './types';
-import { SingleStatPanel } from '../gauge/SingleStatPanel';
-
-export class BarGaugePanel extends SingleStatPanel<BarGaugeOptions> {
-  getOrientation(): VizOrientation {
-    const { options } = this.props;
-    return options.orientation;
-  }
+import { SingleStatBase } from '../singlestat2/SingleStatBase';
 
+export class BarGaugePanel extends SingleStatBase<BarGaugeOptions> {
   renderStat(value: DisplayValue, width: number, height: number) {
     const { options } = this.props;
-    const { display } = options;
 
     return (
       <BarGauge
@@ -28,7 +22,7 @@ export class BarGaugePanel extends SingleStatPanel<BarGaugeOptions> {
         width={width}
         height={height}
         orientation={options.orientation}
-        thresholds={display.thresholds}
+        thresholds={options.thresholds}
         theme={config.theme}
       />
     );

+ 19 - 30
public/app/plugins/panel/bargauge/BarGaugePanelEditor.tsx

@@ -2,38 +2,31 @@
 import React, { PureComponent } from 'react';
 
 // Components
-import { SingleStatValueEditor } from 'app/plugins/panel/gauge/SingleStatValueEditor';
-import {
-  PanelOptionsGrid,
-  PanelOptionsGroup,
-  FormField,
-  DisplayValueOptions,
-  ThresholdsEditor,
-  Threshold,
-} from '@grafana/ui';
+import { ThresholdsEditor, ValueMappingsEditor, PanelOptionsGrid, PanelOptionsGroup, FormField } from '@grafana/ui';
 
 // Types
-import { FormLabel, PanelEditorProps, Select, ValueMappingsEditor, ValueMapping } from '@grafana/ui';
+import { FormLabel, PanelEditorProps, Threshold, Select, ValueMapping } from '@grafana/ui';
 import { BarGaugeOptions, orientationOptions } from './types';
-import { DisplayValueEditor } from '../gauge/DisplayValueEditor';
+import { SingleStatValueEditor } from '../singlestat2/SingleStatValueEditor';
+import { SingleStatValueOptions } from '../singlestat2/types';
 
 export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGaugeOptions>> {
-  onDisplayOptionsChanged = (displayOptions: DisplayValueOptions) =>
+  onThresholdsChanged = (thresholds: Threshold[]) =>
     this.props.onOptionsChange({
       ...this.props.options,
-      display: displayOptions,
-    });
-
-  onThresholdsChanged = (thresholds: Threshold[]) =>
-    this.onDisplayOptionsChanged({
-      ...this.props.options.display,
       thresholds,
     });
 
   onValueMappingsChanged = (valueMappings: ValueMapping[]) =>
-    this.onDisplayOptionsChanged({
-      ...this.props.options.display,
-      mappings: valueMappings,
+    this.props.onOptionsChange({
+      ...this.props.options,
+      valueMappings,
+    });
+
+  onValueOptionsChanged = (valueOptions: SingleStatValueOptions) =>
+    this.props.onOptionsChange({
+      ...this.props.options,
+      valueOptions,
     });
 
   onMinValueChange = ({ target }) => this.props.onOptionsChange({ ...this.props.options, minValue: target.value });
@@ -41,17 +34,12 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge
   onOrientationChange = ({ value }) => this.props.onOptionsChange({ ...this.props.options, orientation: value });
 
   render() {
-    const { onOptionsChange, options } = this.props;
-    const { display } = options;
+    const { options } = this.props;
 
     return (
       <>
         <PanelOptionsGrid>
-          {/* This just sets the 'stats', that should be moved to somethign more general */}
-          <SingleStatValueEditor onChange={onOptionsChange} options={options} />
-
-          <DisplayValueEditor onChange={this.onDisplayOptionsChanged} options={display} />
-
+          <SingleStatValueEditor onChange={this.onValueOptionsChanged} options={options.valueOptions} />
           <PanelOptionsGroup title="Gauge">
             <FormField label="Min value" labelWidth={8} onChange={this.onMinValueChange} value={options.minValue} />
             <FormField label="Max value" labelWidth={8} onChange={this.onMaxValueChange} value={options.maxValue} />
@@ -66,9 +54,10 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge
               />
             </div>
           </PanelOptionsGroup>
-          <ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={display.thresholds} />
-          <ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={display.mappings} />
+          <ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={options.thresholds} />
         </PanelOptionsGrid>
+
+        <ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={options.valueMappings} />
       </>
     );
   }

+ 2 - 2
public/app/plugins/panel/bargauge/module.tsx

@@ -3,10 +3,10 @@ import { ReactPanelPlugin } from '@grafana/ui';
 import { BarGaugePanel } from './BarGaugePanel';
 import { BarGaugePanelEditor } from './BarGaugePanelEditor';
 import { BarGaugeOptions, defaults } from './types';
-import { gaugePanelTypeChangedHook } from '../gauge/module';
+import { singleStatOptionsCheck } from '../singlestat2/module';
 
 export const reactPanel = new ReactPanelPlugin<BarGaugeOptions>(BarGaugePanel);
 
 reactPanel.setEditor(BarGaugePanelEditor);
 reactPanel.setDefaults(defaults);
-reactPanel.setPanelTypeChangedHook(gaugePanelTypeChangedHook);
+reactPanel.setPanelTypeChangedHook(singleStatOptionsCheck);

+ 18 - 7
public/app/plugins/panel/bargauge/types.ts

@@ -1,17 +1,28 @@
-import { SelectOptionItem, VizOrientation } from '@grafana/ui';
+import { VizOrientation, SelectOptionItem } from '@grafana/ui';
 
-import { GaugeOptions, defaults as gaugeDefaults } from '../gauge/types';
-
-export interface BarGaugeOptions extends GaugeOptions {
-  orientation: VizOrientation;
-}
+import { SingleStatBaseOptions } from '../singlestat2/types';
 
 export const orientationOptions: SelectOptionItem[] = [
   { value: VizOrientation.Horizontal, label: 'Horizontal' },
   { value: VizOrientation.Vertical, label: 'Vertical' },
 ];
 
+export interface BarGaugeOptions extends SingleStatBaseOptions {
+  minValue: number;
+  maxValue: number;
+}
+
 export const defaults: BarGaugeOptions = {
-  ...gaugeDefaults,
+  minValue: 0,
+  maxValue: 100,
   orientation: VizOrientation.Horizontal,
+  valueOptions: {
+    unit: 'none',
+    stat: 'avg',
+    prefix: '',
+    suffix: '',
+    decimals: null,
+  },
+  thresholds: [{ index: 0, value: -Infinity, color: 'green' }, { index: 1, value: 80, color: 'red' }],
+  valueMappings: [],
 };

+ 3 - 4
public/app/plugins/panel/gauge/GaugePanel.tsx

@@ -10,19 +10,18 @@ import { Gauge } from '@grafana/ui';
 // Types
 import { GaugeOptions } from './types';
 import { DisplayValue } from '@grafana/ui/src/utils/displayValue';
-import { SingleStatPanel } from './SingleStatPanel';
+import { SingleStatBase } from '../singlestat2/SingleStatBase';
 
-export class GaugePanel extends SingleStatPanel<GaugeOptions> {
+export class GaugePanel extends SingleStatBase<GaugeOptions> {
   renderStat(value: DisplayValue, width: number, height: number) {
     const { options } = this.props;
-    const { display } = options;
 
     return (
       <Gauge
         value={value}
         width={width}
         height={height}
-        thresholds={display.thresholds}
+        thresholds={options.thresholds}
         showThresholdLabels={options.showThresholdLabels}
         showThresholdMarkers={options.showThresholdMarkers}
         minValue={options.minValue}

+ 15 - 19
public/app/plugins/panel/gauge/GaugePanelEditor.tsx

@@ -9,46 +9,42 @@ import {
   ValueMapping,
 } from '@grafana/ui';
 
-import { SingleStatValueEditor } from 'app/plugins/panel/gauge/SingleStatValueEditor';
 import { GaugeOptionsBox } from './GaugeOptionsBox';
 import { GaugeOptions } from './types';
-import { DisplayValueEditor } from './DisplayValueEditor';
-import { DisplayValueOptions } from '@grafana/ui';
+import { SingleStatValueEditor } from '../singlestat2/SingleStatValueEditor';
+import { SingleStatValueOptions } from '../singlestat2/types';
 
 export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOptions>> {
-  onDisplayOptionsChanged = (displayOptions: DisplayValueOptions) =>
+  onThresholdsChanged = (thresholds: Threshold[]) =>
     this.props.onOptionsChange({
       ...this.props.options,
-      display: displayOptions,
-    });
-
-  onThresholdsChanged = (thresholds: Threshold[]) =>
-    this.onDisplayOptionsChanged({
-      ...this.props.options.display,
       thresholds,
     });
 
   onValueMappingsChanged = (valueMappings: ValueMapping[]) =>
-    this.onDisplayOptionsChanged({
-      ...this.props.options.display,
-      mappings: valueMappings,
+    this.props.onOptionsChange({
+      ...this.props.options,
+      valueMappings,
+    });
+
+  onValueOptionsChanged = (valueOptions: SingleStatValueOptions) =>
+    this.props.onOptionsChange({
+      ...this.props.options,
+      valueOptions,
     });
 
   render() {
     const { onOptionsChange, options } = this.props;
-    const { display } = options;
 
     return (
       <>
         <PanelOptionsGrid>
-          {/* This just sets the 'stats', that should be moved to somethign more general */}
-          <SingleStatValueEditor onChange={onOptionsChange} options={options} />
-          <DisplayValueEditor onChange={this.onDisplayOptionsChanged} options={display} />
+          <SingleStatValueEditor onChange={this.onValueOptionsChanged} options={options.valueOptions} />
           <GaugeOptionsBox onOptionsChange={onOptionsChange} options={options} />
-          <ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={display.thresholds} />
+          <ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={options.thresholds} />
         </PanelOptionsGrid>
 
-        <ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={display.mappings} />
+        <ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={options.valueMappings} />
       </>
     );
   }

+ 0 - 51
public/app/plugins/panel/gauge/SingleStatValueEditor.tsx

@@ -1,51 +0,0 @@
-// Libraries
-import React, { PureComponent } from 'react';
-
-// Components
-import { FormLabel, PanelOptionsGroup, Select } from '@grafana/ui';
-
-// Types
-import { GaugeOptions } from './types';
-
-const statOptions = [
-  { value: 'min', label: 'Min' },
-  { value: 'max', label: 'Max' },
-  { value: 'avg', label: 'Average' },
-  { value: 'current', label: 'Current' },
-  { value: 'total', label: 'Total' },
-  { value: 'name', label: 'Name' },
-  { value: 'first', label: 'First' },
-  { value: 'delta', label: 'Delta' },
-  { value: 'diff', label: 'Difference' },
-  { value: 'range', label: 'Range' },
-  { value: 'last_time', label: 'Time of last point' },
-];
-
-const labelWidth = 6;
-
-export interface Props {
-  options: GaugeOptions;
-  onChange: (options: GaugeOptions) => void;
-}
-
-export class SingleStatValueEditor extends PureComponent<Props> {
-  onStatChange = stat => this.props.onChange({ ...this.props.options, stat: stat.value });
-
-  render() {
-    const { stat } = this.props.options;
-
-    return (
-      <PanelOptionsGroup title="Show Value">
-        <div className="gf-form">
-          <FormLabel width={labelWidth}>Stat</FormLabel>
-          <Select
-            width={12}
-            options={statOptions}
-            onChange={this.onStatChange}
-            value={statOptions.find(option => option.value === stat)}
-          />
-        </div>
-      </PanelOptionsGroup>
-    );
-  }
-}

+ 0 - 31
public/app/plugins/panel/gauge/__snapshots__/module.test.ts.snap

@@ -1,31 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Gauge Module migrations should migrate from 6.0 settings to 6.1 1`] = `
-Object {
-  "display": Object {
-    "decimals": 4,
-    "mappings": Array [],
-    "prefix": "a",
-    "stat": "avg",
-    "suffix": "z",
-    "thresholds": Array [
-      Object {
-        "color": "green",
-        "index": 0,
-        "value": -Infinity,
-      },
-      Object {
-        "color": "red",
-        "index": 1,
-        "value": 80,
-      },
-    ],
-    "unit": "ms",
-  },
-  "maxValue": 60,
-  "minValue": 50,
-  "showThresholdLabels": false,
-  "showThresholdMarkers": true,
-  "stat": "avg",
-}
-`;

+ 0 - 27
public/app/plugins/panel/gauge/module.test.ts

@@ -1,27 +0,0 @@
-import { gaugePanelTypeChangedHook } from './module';
-
-describe('Gauge Module', () => {
-  describe('migrations', () => {
-    it('should migrate from 6.0 settings to 6.1', () => {
-      const v60 = {
-        minValue: 50,
-        maxValue: 60,
-        showThresholdMarkers: true,
-        showThresholdLabels: false,
-        valueOptions: {
-          prefix: 'a',
-          suffix: 'z',
-          decimals: 4,
-          stat: 'avg',
-          unit: 'ms',
-        },
-        valueMappings: [],
-        thresholds: [{ index: 0, value: -Infinity, color: 'green' }, { index: 1, value: 80, color: 'red' }],
-      };
-
-      const after = gaugePanelTypeChangedHook(v60);
-      expect((after.stat = 'avg'));
-      expect(after).toMatchSnapshot();
-    });
-  });
-});

+ 3 - 49
public/app/plugins/panel/gauge/module.tsx

@@ -1,58 +1,12 @@
-import { ReactPanelPlugin, DisplayValueOptions } from '@grafana/ui';
-import cloneDeep from 'lodash/cloneDeep';
+import { ReactPanelPlugin } from '@grafana/ui';
 
 import { GaugePanelEditor } from './GaugePanelEditor';
 import { GaugePanel } from './GaugePanel';
 import { GaugeOptions, defaults } from './types';
+import { singleStatOptionsCheck } from '../singlestat2/module';
 
 export const reactPanel = new ReactPanelPlugin<GaugeOptions>(GaugePanel);
 
-// Bar Gauge uses the same handler
-
-const optionsToCheck = ['display', 'stat', 'maxValue', 'maxValue'];
-
-export const gaugePanelTypeChangedHook = (options: Partial<GaugeOptions>, prevPluginId?: string, prevOptions?: any) => {
-  // TODO! migrate to new settings format
-  //
-  // thresholds?: Threshold[];
-  // valueMappings?: ValueMapping[];
-  // valueOptions?: SingleStatValueOptions;
-  //
-  // if (props.options.valueOptions) {
-  //   console.warn('TODO!! how do we best migration options?');
-  // }
-
-  // 6.0 -> 6.1, settings were stored on the root, now moved to display
-  if (!options.display && !prevOptions && options.hasOwnProperty('thresholds')) {
-    console.log('Migrating old gauge settings format', options);
-    const migrate = options as any;
-    const display = (migrate.valueOptions || {}) as DisplayValueOptions;
-
-    display.thresholds = migrate.thresholds;
-    display.mappings = migrate.valueMappings;
-    if (migrate.valueMappings) {
-      options.stat = migrate.valueMappings.stat;
-      delete migrate.valueMappings.stat;
-    }
-
-    delete migrate.valueOptions;
-    delete migrate.thresholds;
-    delete migrate.valueMappings;
-
-    options.display = display;
-  }
-
-  if (prevOptions) {
-    optionsToCheck.forEach(v => {
-      if (prevOptions.hasOwnProperty(v)) {
-        options[v] = cloneDeep(prevOptions.display);
-      }
-    });
-  }
-
-  return options;
-};
-
 reactPanel.setEditor(GaugePanelEditor);
 reactPanel.setDefaults(defaults);
-reactPanel.setPanelTypeChangedHook(gaugePanelTypeChangedHook);
+reactPanel.setPanelTypeChangedHook(singleStatOptionsCheck);

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

@@ -1,6 +1,7 @@
-import { SingleStatOptions } from '@grafana/ui';
+import { SingleStatBaseOptions } from '../singlestat2/types';
+import { VizOrientation } from '@grafana/ui';
 
-export interface GaugeOptions extends SingleStatOptions {
+export interface GaugeOptions extends SingleStatBaseOptions {
   maxValue: number;
   minValue: number;
   showThresholdLabels: boolean;
@@ -12,14 +13,14 @@ export const defaults: GaugeOptions = {
   maxValue: 100,
   showThresholdMarkers: true,
   showThresholdLabels: false,
-
-  stat: 'avg',
-  display: {
+  valueOptions: {
     prefix: '',
     suffix: '',
     decimals: null,
+    stat: 'avg',
     unit: 'none',
-    mappings: [],
-    thresholds: [{ index: 0, value: -Infinity, color: 'green' }, { index: 1, value: 80, color: 'red' }],
   },
+  valueMappings: [],
+  thresholds: [{ index: 0, value: -Infinity, color: 'green' }, { index: 1, value: 80, color: 'red' }],
+  orientation: VizOrientation.Auto,
 };

+ 9 - 0
public/app/plugins/panel/singlestat2/README.md

@@ -0,0 +1,9 @@
+# Singlestat Panel -  Native Plugin
+
+The Singlestat Panel is **included** with Grafana.
+
+The Singlestat Panel allows you to show the one main summary stat of a SINGLE series. It reduces the series into a single number (by looking at the max, min, average, or sum of values in the series). Singlestat also provides thresholds to color the stat or the Panel background. It can also translate the single number into a text value, and show a sparkline summary of the series.
+
+Read more about it here:
+
+[http://docs.grafana.org/reference/singlestat/](http://docs.grafana.org/reference/singlestat/)

+ 16 - 24
public/app/plugins/panel/gauge/SingleStatPanel.tsx → public/app/plugins/panel/singlestat2/SingleStatBase.tsx

@@ -1,21 +1,16 @@
-// Libraries
 import React, { PureComponent } from 'react';
-
-// Services & Utils
-import { processSingleStatPanelData, SingleStatOptions, DisplayValue, PanelProps, VizOrientation } from '@grafana/ui';
+import { processSingleStatPanelData, DisplayValue, PanelProps } from '@grafana/ui';
 import { config } from 'app/core/config';
-
-// Components
 import { VizRepeater, getDisplayProcessor } from '@grafana/ui';
+import { SingleStatBaseOptions } from './types';
 
-interface State {
+export interface State {
   values: DisplayValue[];
 }
 
-export class SingleStatPanel<T extends SingleStatOptions> extends PureComponent<PanelProps<T>, State> {
+export class SingleStatBase<T extends SingleStatBaseOptions> extends PureComponent<PanelProps<T>, State> {
   constructor(props: PanelProps<T>) {
     super(props);
-
     this.state = {
       values: this.findDisplayValues(props),
     };
@@ -29,18 +24,20 @@ export class SingleStatPanel<T extends SingleStatOptions> extends PureComponent<
 
   findDisplayValues(props: PanelProps<T>): DisplayValue[] {
     const { panelData, replaceVariables, options } = this.props;
-    const { display } = options;
-
+    const { valueOptions, valueMappings } = options;
     const processor = getDisplayProcessor({
-      ...display,
-      prefix: replaceVariables(display.prefix),
-      suffix: replaceVariables(display.suffix),
+      unit: valueOptions.unit,
+      decimals: valueOptions.decimals,
+      mappings: valueMappings,
+      thresholds: options.thresholds,
+
+      prefix: replaceVariables(valueOptions.prefix),
+      suffix: replaceVariables(valueOptions.suffix),
       theme: config.theme,
     });
-
     return processSingleStatPanelData({
       panelData: panelData,
-      stat: options.stat,
+      stat: valueOptions.stat,
     }).map(stat => processor(stat.value));
   }
 
@@ -51,17 +48,12 @@ export class SingleStatPanel<T extends SingleStatOptions> extends PureComponent<
     return <div style={{ width, height, border: '1px solid red' }}>{value.text}</div>;
   }
 
-  // Or we could add this to single stat props?
-  getOrientation(): VizOrientation {
-    return VizOrientation.Auto;
-  }
-
   render() {
-    const { height, width } = this.props;
+    const { height, width, options } = this.props;
+    const { orientation } = options;
     const { values } = this.state;
-
     return (
-      <VizRepeater height={height} width={width} values={values} orientation={this.getOrientation()}>
+      <VizRepeater height={height} width={width} values={values} orientation={orientation}>
         {({ vizHeight, vizWidth, value }) => this.renderStat(value, vizWidth, vizHeight)}
       </VizRepeater>
     );

+ 48 - 0
public/app/plugins/panel/singlestat2/SingleStatEditor.tsx

@@ -0,0 +1,48 @@
+// Libraries
+import React, { PureComponent } from 'react';
+import {
+  PanelEditorProps,
+  ThresholdsEditor,
+  Threshold,
+  PanelOptionsGrid,
+  ValueMappingsEditor,
+  ValueMapping,
+} from '@grafana/ui';
+
+import { SingleStatOptions, SingleStatValueOptions } from './types';
+import { SingleStatValueEditor } from './SingleStatValueEditor';
+
+export class SingleStatEditor extends PureComponent<PanelEditorProps<SingleStatOptions>> {
+  onThresholdsChanged = (thresholds: Threshold[]) =>
+    this.props.onOptionsChange({
+      ...this.props.options,
+      thresholds,
+    });
+
+  onValueMappingsChanged = (valueMappings: ValueMapping[]) =>
+    this.props.onOptionsChange({
+      ...this.props.options,
+      valueMappings,
+    });
+
+  onValueOptionsChanged = (valueOptions: SingleStatValueOptions) =>
+    this.props.onOptionsChange({
+      ...this.props.options,
+      valueOptions,
+    });
+
+  render() {
+    const { options } = this.props;
+
+    return (
+      <>
+        <PanelOptionsGrid>
+          <SingleStatValueEditor onChange={this.onValueOptionsChanged} options={options.valueOptions} />
+          <ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={options.thresholds} />
+        </PanelOptionsGrid>
+
+        <ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={options.valueMappings} />
+      </>
+    );
+  }
+}

+ 17 - 0
public/app/plugins/panel/singlestat2/SingleStatPanel.tsx

@@ -0,0 +1,17 @@
+// Libraries
+import React from 'react';
+
+// Types
+import { SingleStatOptions } from './types';
+import { DisplayValue } from '@grafana/ui/src/utils/displayValue';
+import { SingleStatBase } from './SingleStatBase';
+
+export class SingleStatPanel extends SingleStatBase<SingleStatOptions> {
+  renderStat(value: DisplayValue, width: number, height: number) {
+    return (
+      <div style={{ width, height }}>
+        <b>{value.text}</b>
+      </div>
+    );
+  }
+}

+ 31 - 7
public/app/plugins/panel/gauge/DisplayValueEditor.tsx → public/app/plugins/panel/singlestat2/SingleStatValueEditor.tsx

@@ -2,20 +2,35 @@
 import React, { PureComponent } from 'react';
 
 // Components
-import { FormField, FormLabel, PanelOptionsGroup, UnitPicker } from '@grafana/ui';
+import { FormField, FormLabel, PanelOptionsGroup, Select, UnitPicker } from '@grafana/ui';
 
 // Types
-import { DisplayValueOptions } from '@grafana/ui';
+import { SingleStatValueOptions } from './types';
+
+const statOptions = [
+  { value: 'min', label: 'Min' },
+  { value: 'max', label: 'Max' },
+  { value: 'avg', label: 'Average' },
+  { value: 'current', label: 'Current' },
+  { value: 'total', label: 'Total' },
+  { value: 'name', label: 'Name' },
+  { value: 'first', label: 'First' },
+  { value: 'delta', label: 'Delta' },
+  { value: 'diff', label: 'Difference' },
+  { value: 'range', label: 'Range' },
+  { value: 'last_time', label: 'Time of last point' },
+];
 
 const labelWidth = 6;
 
 export interface Props {
-  options: DisplayValueOptions;
-  onChange: (options: DisplayValueOptions) => void;
+  options: SingleStatValueOptions;
+  onChange: (valueOptions: SingleStatValueOptions) => void;
 }
 
-export class DisplayValueEditor extends PureComponent<Props> {
+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)) {
@@ -35,7 +50,7 @@ export class DisplayValueEditor extends PureComponent<Props> {
   onSuffixChange = event => this.props.onChange({ ...this.props.options, suffix: event.target.value });
 
   render() {
-    const { unit, decimals, prefix, suffix } = this.props.options;
+    const { stat, unit, decimals, prefix, suffix } = this.props.options;
 
     let decimalsString = '';
     if (Number.isFinite(decimals)) {
@@ -43,7 +58,16 @@ export class DisplayValueEditor extends PureComponent<Props> {
     }
 
     return (
-      <PanelOptionsGroup title="Display Value">
+      <PanelOptionsGroup title="Value">
+        <div className="gf-form">
+          <FormLabel width={labelWidth}>Stat</FormLabel>
+          <Select
+            width={12}
+            options={statOptions}
+            onChange={this.onStatChange}
+            value={statOptions.find(option => option.value === stat)}
+          />
+        </div>
         <div className="gf-form">
           <FormLabel width={labelWidth}>Unit</FormLabel>
           <UnitPicker defaultValue={unit} onChange={this.onUnitChange} />

+ 83 - 0
public/app/plugins/panel/singlestat2/img/icn-singlestat-panel.svg

@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="100px" height="100px" viewBox="0 0 100 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
+<style type="text/css">
+	.st0{opacity:0.26;fill:url(#SVGID_1_);}
+	.st1{fill:url(#SVGID_2_);}
+	.st2{fill:url(#SVGID_3_);}
+	.st3{fill:url(#SVGID_4_);}
+	.st4{fill:url(#SVGID_5_);}
+	.st5{fill:none;stroke:url(#SVGID_6_);stroke-miterlimit:10;}
+</style>
+<g>
+	<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="50" y1="65.6698" x2="50" y2="93.5681">
+		<stop  offset="0" style="stop-color:#FFF23A"/>
+		<stop  offset="4.010540e-02" style="stop-color:#FEE62D"/>
+		<stop  offset="0.1171" style="stop-color:#FED41A"/>
+		<stop  offset="0.1964" style="stop-color:#FDC90F"/>
+		<stop  offset="0.2809" style="stop-color:#FDC60B"/>
+		<stop  offset="0.6685" style="stop-color:#F28F3F"/>
+		<stop  offset="0.8876" style="stop-color:#ED693C"/>
+		<stop  offset="1" style="stop-color:#E83E39"/>
+	</linearGradient>
+	<path class="st0" d="M97.6,83.8H2.4c-1.3,0-2.4-1.1-2.4-2.4v-1.8l17-1l19.2-4.3l16.3-1.6l16.5,0l15.8-4.7l15.1-3v16.3
+		C100,82.8,98.9,83.8,97.6,83.8z"/>
+	<g>
+		<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="19.098" y1="76.0776" x2="19.098" y2="27.8027">
+			<stop  offset="0" style="stop-color:#FFF23A"/>
+			<stop  offset="4.010540e-02" style="stop-color:#FEE62D"/>
+			<stop  offset="0.1171" style="stop-color:#FED41A"/>
+			<stop  offset="0.1964" style="stop-color:#FDC90F"/>
+			<stop  offset="0.2809" style="stop-color:#FDC60B"/>
+			<stop  offset="0.6685" style="stop-color:#F28F3F"/>
+			<stop  offset="0.8876" style="stop-color:#ED693C"/>
+			<stop  offset="1" style="stop-color:#E83E39"/>
+		</linearGradient>
+		<path class="st1" d="M19.6,64.3V38.9l-5.2,3.9l-3.5-6l9.4-6.9h6.8v34.4H19.6z"/>
+		<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="42.412" y1="76.0776" x2="42.412" y2="27.8027">
+			<stop  offset="0" style="stop-color:#FFF23A"/>
+			<stop  offset="4.010540e-02" style="stop-color:#FEE62D"/>
+			<stop  offset="0.1171" style="stop-color:#FED41A"/>
+			<stop  offset="0.1964" style="stop-color:#FDC90F"/>
+			<stop  offset="0.2809" style="stop-color:#FDC60B"/>
+			<stop  offset="0.6685" style="stop-color:#F28F3F"/>
+			<stop  offset="0.8876" style="stop-color:#ED693C"/>
+			<stop  offset="1" style="stop-color:#E83E39"/>
+		</linearGradient>
+		<path class="st2" d="M53.1,39.4c0,1.1-0.1,2.2-0.4,3.2c-0.3,1-0.7,1.9-1.2,2.8c-0.5,0.9-1,1.7-1.7,2.5c-0.6,0.8-1.2,1.6-1.9,2.3
+			l-6.4,7.4h11.1v6.7H32.3v-6.9l10.5-12c0.8-1,1.5-2,2-3c0.5-1,0.7-2,0.7-2.9c0-1-0.2-1.9-0.7-2.6c-0.5-0.7-1.2-1.1-2.2-1.1
+			c-0.9,0-1.7,0.4-2.3,1.1c-0.6,0.8-1,1.9-1.1,3.3l-7.3-0.7c0.4-3.5,1.6-6.1,3.6-7.9c2-1.7,4.5-2.6,7.4-2.6c1.6,0,3,0.2,4.3,0.7
+			c1.3,0.5,2.3,1.2,3.2,2c0.9,0.9,1.6,1.9,2.1,3.2C52.8,36.4,53.1,37.8,53.1,39.4z"/>
+		<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="60.3739" y1="76.0776" x2="60.3739" y2="27.8027">
+			<stop  offset="0" style="stop-color:#FFF23A"/>
+			<stop  offset="4.010540e-02" style="stop-color:#FEE62D"/>
+			<stop  offset="0.1171" style="stop-color:#FED41A"/>
+			<stop  offset="0.1964" style="stop-color:#FDC90F"/>
+			<stop  offset="0.2809" style="stop-color:#FDC60B"/>
+			<stop  offset="0.6685" style="stop-color:#F28F3F"/>
+			<stop  offset="0.8876" style="stop-color:#ED693C"/>
+			<stop  offset="1" style="stop-color:#E83E39"/>
+		</linearGradient>
+		<path class="st3" d="M64.5,60.4c0,1.2-0.4,2.3-1.2,3.1c-0.8,0.8-1.8,1.3-3,1.3c-1.2,0-2.2-0.4-3-1.3c-0.8-0.8-1.1-1.9-1.1-3.1
+			c0-1.2,0.4-2.2,1.1-3.1c0.8-0.9,1.8-1.3,3-1.3c1.2,0,2.2,0.4,3,1.3C64.1,58.1,64.5,59.2,64.5,60.4z"/>
+		<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="77.5234" y1="76.0776" x2="77.5234" y2="27.8027">
+			<stop  offset="0" style="stop-color:#FFF23A"/>
+			<stop  offset="4.010540e-02" style="stop-color:#FEE62D"/>
+			<stop  offset="0.1171" style="stop-color:#FED41A"/>
+			<stop  offset="0.1964" style="stop-color:#FDC90F"/>
+			<stop  offset="0.2809" style="stop-color:#FDC60B"/>
+			<stop  offset="0.6685" style="stop-color:#F28F3F"/>
+			<stop  offset="0.8876" style="stop-color:#ED693C"/>
+			<stop  offset="1" style="stop-color:#E83E39"/>
+		</linearGradient>
+		<path class="st4" d="M85.5,57.4v6.9h-6.9v-6.9H66v-6.6l10.1-20.9h9.4V51H89v6.4H85.5z M78.8,37.5L78.8,37.5l-6,13.5h6V37.5z"/>
+	</g>
+	<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="-2.852199e-02" y1="72.3985" x2="100.0976" y2="72.3985">
+		<stop  offset="0" style="stop-color:#F28F3F"/>
+		<stop  offset="1" style="stop-color:#F28F3F"/>
+	</linearGradient>
+	<polyline class="st5" points="0,79.7 17,78.7 36.2,74.4 52.5,72.8 69,72.9 84.9,68.1 100,65.1 	"/>
+</g>
+</svg>

+ 29 - 0
public/app/plugins/panel/singlestat2/module.tsx

@@ -0,0 +1,29 @@
+import { ReactPanelPlugin } from '@grafana/ui';
+import { SingleStatOptions, defaults } from './types';
+import { SingleStatPanel } from './SingleStatPanel';
+import cloneDeep from 'lodash/cloneDeep';
+import { SingleStatEditor } from './SingleStatEditor';
+
+export const reactPanel = new ReactPanelPlugin<SingleStatOptions>(SingleStatPanel);
+
+const optionsToKeep = ['valueOptions', 'stat', 'maxValue', 'maxValue', 'thresholds', 'valueMappings'];
+
+export const singleStatOptionsCheck = (
+  options: Partial<SingleStatOptions>,
+  prevPluginId?: string,
+  prevOptions?: any
+) => {
+  if (prevOptions) {
+    optionsToKeep.forEach(v => {
+      if (prevOptions.hasOwnProperty(v)) {
+        options[v] = cloneDeep(prevOptions.display);
+      }
+    });
+  }
+
+  return options;
+};
+
+reactPanel.setEditor(SingleStatEditor);
+reactPanel.setDefaults(defaults);
+reactPanel.setPanelTypeChangedHook(singleStatOptionsCheck);

+ 20 - 0
public/app/plugins/panel/singlestat2/plugin.json

@@ -0,0 +1,20 @@
+{
+  "type": "panel",
+  "name": "Singlestat (react)",
+  "id": "singlestat2",
+  "state": "alpha",
+
+  "dataFormats": ["time_series", "table"],
+
+  "info": {
+    "description": "Singlestat Panel for Grafana",
+    "author": {
+      "name": "Grafana Project",
+      "url": "https://grafana.com"
+    },
+    "logos": {
+      "small": "img/icn-singlestat-panel.svg",
+      "large": "img/icn-singlestat-panel.svg"
+    }
+  }
+}

+ 33 - 0
public/app/plugins/panel/singlestat2/types.ts

@@ -0,0 +1,33 @@
+import { VizOrientation, ValueMapping, Threshold } from '@grafana/ui';
+
+export interface SingleStatBaseOptions {
+  valueMappings: ValueMapping[];
+  thresholds: Threshold[];
+  valueOptions: SingleStatValueOptions;
+  orientation: VizOrientation;
+}
+
+export interface SingleStatValueOptions {
+  unit: string;
+  suffix: string;
+  stat: string;
+  prefix: string;
+  decimals?: number | null;
+}
+
+export interface SingleStatOptions extends SingleStatBaseOptions {
+  // TODO, fill in with options from angular
+}
+
+export const defaults: SingleStatOptions = {
+  valueOptions: {
+    prefix: '',
+    suffix: '',
+    decimals: null,
+    stat: 'avg',
+    unit: 'none',
+  },
+  valueMappings: [],
+  thresholds: [{ index: 0, value: -Infinity, color: 'green' }, { index: 1, value: 80, color: 'red' }],
+  orientation: VizOrientation.Auto,
+};