Browse Source

SingleStat: add a gauge migration call to action button in the editor (#18604)

Ryan McKinley 6 years ago
parent
commit
21948e80e0

+ 3 - 3
packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.test.ts

@@ -1,6 +1,6 @@
-import { sharedSingleStatMigrationCheck } from './SingleStatBaseOptions';
+import { sharedSingleStatMigrationHandler } from './SingleStatBaseOptions';
 
 
-describe('sharedSingleStatMigrationCheck', () => {
+describe('sharedSingleStatMigrationHandler', () => {
   it('from old valueOptions model without pluginVersion', () => {
   it('from old valueOptions model without pluginVersion', () => {
     const panel = {
     const panel = {
       options: {
       options: {
@@ -34,6 +34,6 @@ describe('sharedSingleStatMigrationCheck', () => {
       type: 'bargauge',
       type: 'bargauge',
     };
     };
 
 
-    expect(sharedSingleStatMigrationCheck(panel as any)).toMatchSnapshot();
+    expect(sharedSingleStatMigrationHandler(panel as any)).toMatchSnapshot();
   });
   });
 });
 });

+ 113 - 6
packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts

@@ -3,7 +3,15 @@ import omit from 'lodash/omit';
 
 
 import { VizOrientation, PanelModel } from '../../types/panel';
 import { VizOrientation, PanelModel } from '../../types/panel';
 import { FieldDisplayOptions } from '../../utils/fieldDisplay';
 import { FieldDisplayOptions } from '../../utils/fieldDisplay';
-import { fieldReducers, Threshold, sortThresholds } from '@grafana/data';
+import {
+  fieldReducers,
+  Threshold,
+  sortThresholds,
+  FieldConfig,
+  ReducerID,
+  ValueMapping,
+  MappingType,
+} from '@grafana/data';
 
 
 export interface SingleStatBaseOptions {
 export interface SingleStatBaseOptions {
   fieldOptions: FieldDisplayOptions;
   fieldOptions: FieldDisplayOptions;
@@ -12,23 +20,82 @@ export interface SingleStatBaseOptions {
 
 
 const optionsToKeep = ['fieldOptions', 'orientation'];
 const optionsToKeep = ['fieldOptions', 'orientation'];
 
 
-export const sharedSingleStatOptionsCheck = (
+export function sharedSingleStatPanelChangedHandler(
   options: Partial<SingleStatBaseOptions> | any,
   options: Partial<SingleStatBaseOptions> | any,
   prevPluginId: string,
   prevPluginId: string,
   prevOptions: any
   prevOptions: any
-) => {
+) {
+  // Migrating from angular singlestat
+  if (prevPluginId === 'singlestat' && prevOptions.angular) {
+    const panel = prevOptions.angular;
+    const reducer = fieldReducers.getIfExists(panel.valueName);
+    const options = {
+      fieldOptions: {
+        defaults: {} as FieldConfig,
+        override: {} as FieldConfig,
+        calcs: [reducer ? reducer.id : ReducerID.mean],
+      },
+      orientation: VizOrientation.Horizontal,
+    };
+
+    const defaults = options.fieldOptions.defaults;
+    if (panel.format) {
+      defaults.unit = panel.format;
+    }
+    if (panel.nullPointMode) {
+      defaults.nullValueMode = panel.nullPointMode;
+    }
+    if (panel.nullText) {
+      defaults.noValue = panel.nullText;
+    }
+    if (panel.decimals || panel.decimals === 0) {
+      defaults.decimals = panel.decimals;
+    }
+
+    // Convert thresholds and color values
+    if (panel.thresholds && panel.colors) {
+      const levels = panel.thresholds.split(',').map((strVale: string) => {
+        return Number(strVale.trim());
+      });
+
+      // One more color than threshold
+      const thresholds: Threshold[] = [];
+      for (const color of panel.colors) {
+        const idx = thresholds.length - 1;
+        if (idx >= 0) {
+          thresholds.push({ value: levels[idx], color });
+        } else {
+          thresholds.push({ value: -Infinity, color });
+        }
+      }
+      defaults.thresholds = thresholds;
+    }
+
+    // Convert value mappings
+    const mappings = convertOldAngulrValueMapping(panel);
+    if (mappings && mappings.length) {
+      defaults.mappings = mappings;
+    }
+
+    if (panel.gauge) {
+      defaults.min = panel.gauge.minValue;
+      defaults.max = panel.gauge.maxValue;
+    }
+    return options;
+  }
+
   for (const k of optionsToKeep) {
   for (const k of optionsToKeep) {
     if (prevOptions.hasOwnProperty(k)) {
     if (prevOptions.hasOwnProperty(k)) {
       options[k] = cloneDeep(prevOptions[k]);
       options[k] = cloneDeep(prevOptions[k]);
     }
     }
   }
   }
   return options;
   return options;
-};
+}
 
 
-export function sharedSingleStatMigrationCheck(panel: PanelModel<SingleStatBaseOptions>) {
+export function sharedSingleStatMigrationHandler(panel: PanelModel<SingleStatBaseOptions>): SingleStatBaseOptions {
   if (!panel.options) {
   if (!panel.options) {
     // This happens on the first load or when migrating from angular
     // This happens on the first load or when migrating from angular
-    return {};
+    return {} as any;
   }
   }
 
 
   const previousVersion = parseFloat(panel.pluginVersion || '6.1');
   const previousVersion = parseFloat(panel.pluginVersion || '6.1');
@@ -121,3 +188,43 @@ export function migrateOldThresholds(thresholds?: any[]): Threshold[] | undefine
   copy[0].value = -Infinity;
   copy[0].value = -Infinity;
   return copy;
   return copy;
 }
 }
+
+/**
+ * Convert the angular single stat mapping to new react style
+ */
+function convertOldAngulrValueMapping(panel: any): ValueMapping[] {
+  const mappings: ValueMapping[] = [];
+
+  // Guess the right type based on options
+  let mappingType = panel.mappingType;
+  if (!panel.mappingType) {
+    if (panel.valueMaps && panel.valueMaps.length) {
+      mappingType = 1;
+    } else if (panel.rangeMaps && panel.rangeMaps.length) {
+      mappingType = 2;
+    }
+  }
+
+  // check value to text mappings if its enabled
+  if (mappingType === 1) {
+    for (let i = 0; i < panel.valueMaps.length; i++) {
+      const map = panel.valueMaps[i];
+      mappings.push({
+        ...map,
+        id: i, // used for order
+        type: MappingType.ValueToText,
+      });
+    }
+  } else if (mappingType === 2) {
+    for (let i = 0; i < panel.rangeMaps.length; i++) {
+      const map = panel.rangeMaps[i];
+      mappings.push({
+        ...map,
+        id: i, // used for order
+        type: MappingType.RangeToText,
+      });
+    }
+  }
+
+  return mappings;
+}

+ 1 - 1
packages/grafana-ui/src/components/SingleStatShared/__snapshots__/SingleStatBaseOptions.test.ts.snap

@@ -1,6 +1,6 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 
-exports[`sharedSingleStatMigrationCheck from old valueOptions model without pluginVersion 1`] = `
+exports[`sharedSingleStatMigrationHandler from old valueOptions model without pluginVersion 1`] = `
 Object {
 Object {
   "fieldOptions": Object {
   "fieldOptions": Object {
     "calcs": Array [
     "calcs": Array [

+ 2 - 2
packages/grafana-ui/src/components/SingleStatShared/index.ts

@@ -3,6 +3,6 @@ export { FieldPropertiesEditor } from './FieldPropertiesEditor';
 
 
 export {
 export {
   SingleStatBaseOptions,
   SingleStatBaseOptions,
-  sharedSingleStatOptionsCheck,
-  sharedSingleStatMigrationCheck,
+  sharedSingleStatPanelChangedHandler,
+  sharedSingleStatMigrationHandler,
 } from './SingleStatBaseOptions';
 } from './SingleStatBaseOptions';

+ 2 - 2
public/app/features/dashboard/dashgrid/DashboardPanel.tsx

@@ -61,7 +61,7 @@ export class DashboardPanel extends PureComponent<Props, State> {
     return <AddPanelWidget panel={this.props.panel} dashboard={this.props.dashboard} />;
     return <AddPanelWidget panel={this.props.panel} dashboard={this.props.dashboard} />;
   }
   }
 
 
-  onPluginTypeChanged = (plugin: PanelPluginMeta) => {
+  onPluginTypeChange = (plugin: PanelPluginMeta) => {
     this.loadPlugin(plugin.id);
     this.loadPlugin(plugin.id);
   };
   };
 
 
@@ -211,7 +211,7 @@ export class DashboardPanel extends PureComponent<Props, State> {
             plugin={plugin}
             plugin={plugin}
             dashboard={dashboard}
             dashboard={dashboard}
             angularPanel={angularPanel}
             angularPanel={angularPanel}
-            onTypeChanged={this.onPluginTypeChanged}
+            onPluginTypeChange={this.onPluginTypeChange}
           />
           />
         )}
         )}
       </div>
       </div>

+ 3 - 3
public/app/features/dashboard/panel_editor/PanelEditor.tsx

@@ -20,7 +20,7 @@ interface PanelEditorProps {
   dashboard: DashboardModel;
   dashboard: DashboardModel;
   plugin: PanelPlugin;
   plugin: PanelPlugin;
   angularPanel?: AngularComponent;
   angularPanel?: AngularComponent;
-  onTypeChanged: (newType: PanelPluginMeta) => void;
+  onPluginTypeChange: (newType: PanelPluginMeta) => void;
 }
 }
 
 
 interface PanelEditorTab {
 interface PanelEditorTab {
@@ -70,7 +70,7 @@ export class PanelEditor extends PureComponent<PanelEditorProps> {
   };
   };
 
 
   renderCurrentTab(activeTab: string) {
   renderCurrentTab(activeTab: string) {
-    const { panel, dashboard, onTypeChanged, plugin, angularPanel } = this.props;
+    const { panel, dashboard, onPluginTypeChange, plugin, angularPanel } = this.props;
 
 
     switch (activeTab) {
     switch (activeTab) {
       case 'advanced':
       case 'advanced':
@@ -85,7 +85,7 @@ export class PanelEditor extends PureComponent<PanelEditorProps> {
             panel={panel}
             panel={panel}
             dashboard={dashboard}
             dashboard={dashboard}
             plugin={plugin}
             plugin={plugin}
-            onTypeChanged={onTypeChanged}
+            onPluginTypeChange={onPluginTypeChange}
             angularPanel={angularPanel}
             angularPanel={angularPanel}
           />
           />
         );
         );

+ 7 - 5
public/app/features/dashboard/panel_editor/VisualizationTab.tsx

@@ -19,13 +19,14 @@ import { DashboardModel } from '../state';
 import { VizPickerSearch } from './VizPickerSearch';
 import { VizPickerSearch } from './VizPickerSearch';
 import PluginStateinfo from 'app/features/plugins/PluginStateInfo';
 import PluginStateinfo from 'app/features/plugins/PluginStateInfo';
 import { PanelPlugin, PanelPluginMeta } from '@grafana/ui';
 import { PanelPlugin, PanelPluginMeta } from '@grafana/ui';
+import { PanelCtrl } from 'app/plugins/sdk';
 
 
 interface Props {
 interface Props {
   panel: PanelModel;
   panel: PanelModel;
   dashboard: DashboardModel;
   dashboard: DashboardModel;
   plugin: PanelPlugin;
   plugin: PanelPlugin;
   angularPanel?: AngularComponent;
   angularPanel?: AngularComponent;
-  onTypeChanged: (newType: PanelPluginMeta) => void;
+  onPluginTypeChange: (newType: PanelPluginMeta) => void;
   updateLocation: typeof updateLocation;
   updateLocation: typeof updateLocation;
   urlOpenVizPicker: boolean;
   urlOpenVizPicker: boolean;
 }
 }
@@ -104,8 +105,9 @@ export class VisualizationTab extends PureComponent<Props, State> {
       return;
       return;
     }
     }
 
 
-    const panelCtrl = scope.$$childHead.ctrl;
+    const panelCtrl: PanelCtrl = scope.$$childHead.ctrl;
     panelCtrl.initEditMode();
     panelCtrl.initEditMode();
+    panelCtrl.onPluginTypeChange = this.onPluginTypeChange;
 
 
     let template = '';
     let template = '';
     for (let i = 0; i < panelCtrl.editorTabs.length; i++) {
     for (let i = 0; i < panelCtrl.editorTabs.length; i++) {
@@ -197,11 +199,11 @@ export class VisualizationTab extends PureComponent<Props, State> {
     }
     }
   };
   };
 
 
-  onTypeChanged = (plugin: PanelPluginMeta) => {
+  onPluginTypeChange = (plugin: PanelPluginMeta) => {
     if (plugin.id === this.props.plugin.meta.id) {
     if (plugin.id === this.props.plugin.meta.id) {
       this.setState({ isVizPickerOpen: false });
       this.setState({ isVizPickerOpen: false });
     } else {
     } else {
-      this.props.onTypeChanged(plugin);
+      this.props.onPluginTypeChange(plugin);
     }
     }
   };
   };
 
 
@@ -235,7 +237,7 @@ export class VisualizationTab extends PureComponent<Props, State> {
           <FadeIn in={isVizPickerOpen} duration={200} unmountOnExit={true} onExited={this.clearQuery}>
           <FadeIn in={isVizPickerOpen} duration={200} unmountOnExit={true} onExited={this.clearQuery}>
             <VizTypePicker
             <VizTypePicker
               current={meta}
               current={meta}
-              onTypeChanged={this.onTypeChanged}
+              onTypeChange={this.onPluginTypeChange}
               searchQuery={searchQuery}
               searchQuery={searchQuery}
               onClose={this.onCloseVizPicker}
               onClose={this.onCloseVizPicker}
             />
             />

+ 3 - 8
public/app/features/dashboard/panel_editor/VizTypePicker.tsx

@@ -6,7 +6,7 @@ import { PanelPluginMeta, EmptySearchResult } from '@grafana/ui';
 
 
 export interface Props {
 export interface Props {
   current: PanelPluginMeta;
   current: PanelPluginMeta;
-  onTypeChanged: (newType: PanelPluginMeta) => void;
+  onTypeChange: (newType: PanelPluginMeta) => void;
   searchQuery: string;
   searchQuery: string;
   onClose: () => void;
   onClose: () => void;
 }
 }
@@ -34,16 +34,11 @@ export class VizTypePicker extends PureComponent<Props> {
   }
   }
 
 
   renderVizPlugin = (plugin: PanelPluginMeta, index: number) => {
   renderVizPlugin = (plugin: PanelPluginMeta, index: number) => {
-    const { onTypeChanged } = this.props;
+    const { onTypeChange } = this.props;
     const isCurrent = plugin.id === this.props.current.id;
     const isCurrent = plugin.id === this.props.current.id;
 
 
     return (
     return (
-      <VizTypePickerPlugin
-        key={plugin.id}
-        isCurrent={isCurrent}
-        plugin={plugin}
-        onClick={() => onTypeChanged(plugin)}
-      />
+      <VizTypePickerPlugin key={plugin.id} isCurrent={isCurrent} plugin={plugin} onClick={() => onTypeChange(plugin)} />
     );
     );
   };
   };
 
 

+ 1 - 1
public/app/features/dashboard/state/PanelModel.test.ts

@@ -165,7 +165,7 @@ describe('PanelModel', () => {
       it('should call react onPanelTypeChanged', () => {
       it('should call react onPanelTypeChanged', () => {
         expect(onPanelTypeChanged.mock.calls.length).toBe(1);
         expect(onPanelTypeChanged.mock.calls.length).toBe(1);
         expect(onPanelTypeChanged.mock.calls[0][1]).toBe('table');
         expect(onPanelTypeChanged.mock.calls[0][1]).toBe('table');
-        expect(onPanelTypeChanged.mock.calls[0][2].fieldOptions).toBeDefined();
+        expect(onPanelTypeChanged.mock.calls[0][2].angular).toBeDefined();
       });
       });
 
 
       it('getQueryRunner() should return same instance after changing to another react panel', () => {
       it('getQueryRunner() should return same instance after changing to another react panel', () => {

+ 15 - 6
public/app/features/dashboard/state/PanelModel.ts

@@ -262,9 +262,10 @@ export class PanelModel {
     const pluginId = newPlugin.meta.id;
     const pluginId = newPlugin.meta.id;
     const oldOptions: any = this.getOptionsToRemember();
     const oldOptions: any = this.getOptionsToRemember();
     const oldPluginId = this.type;
     const oldPluginId = this.type;
+    const wasAngular = !!this.plugin.angularPanelCtrl;
 
 
     // for angular panels we must remove all events and let angular panels do some cleanup
     // for angular panels we must remove all events and let angular panels do some cleanup
-    if (this.plugin.angularPanelCtrl) {
+    if (wasAngular) {
       this.destroy();
       this.destroy();
     }
     }
 
 
@@ -280,17 +281,25 @@ export class PanelModel {
     this.cachedPluginOptions[oldPluginId] = oldOptions;
     this.cachedPluginOptions[oldPluginId] = oldOptions;
     this.restorePanelOptions(pluginId);
     this.restorePanelOptions(pluginId);
 
 
-    // switch
-    this.type = pluginId;
-    this.plugin = newPlugin;
-    this.applyPluginOptionDefaults(newPlugin);
     // Let panel plugins inspect options from previous panel and keep any that it can use
     // Let panel plugins inspect options from previous panel and keep any that it can use
     if (newPlugin.onPanelTypeChanged) {
     if (newPlugin.onPanelTypeChanged) {
+      let old: any = {};
+
+      if (wasAngular) {
+        old = { angular: oldOptions };
+      } else if (oldOptions && oldOptions.options) {
+        old = oldOptions.options;
+      }
+
       this.options = this.options || {};
       this.options = this.options || {};
-      const old = oldOptions && oldOptions.options ? oldOptions.options : {};
       Object.assign(this.options, newPlugin.onPanelTypeChanged(this.options, oldPluginId, old));
       Object.assign(this.options, newPlugin.onPanelTypeChanged(this.options, oldPluginId, old));
     }
     }
 
 
+    // switch
+    this.type = pluginId;
+    this.plugin = newPlugin;
+    this.applyPluginOptionDefaults(newPlugin);
+
     if (newPlugin.onPanelMigration) {
     if (newPlugin.onPanelMigration) {
       this.pluginVersion = getPluginVersion(newPlugin);
       this.pluginVersion = getPluginVersion(newPlugin);
     }
     }

+ 4 - 0
public/app/features/panel/panel_ctrl.ts

@@ -19,6 +19,7 @@ import { GRID_COLUMN_COUNT } from 'app/core/constants';
 import { auto } from 'angular';
 import { auto } from 'angular';
 import { TemplateSrv } from '../templating/template_srv';
 import { TemplateSrv } from '../templating/template_srv';
 import { LinkSrv } from './panellinks/link_srv';
 import { LinkSrv } from './panellinks/link_srv';
+import { PanelPluginMeta } from '@grafana/ui/src/types/panel';
 
 
 export class PanelCtrl {
 export class PanelCtrl {
   panel: any;
   panel: any;
@@ -281,4 +282,7 @@ export class PanelCtrl {
     html += '</div>';
     html += '</div>';
     return html;
     return html;
   }
   }
+
+  // overriden from react
+  onPluginTypeChange = (plugin: PanelPluginMeta) => {};
 }
 }

+ 2 - 2
public/app/plugins/panel/bargauge/BarGaugeMigrations.test.ts

@@ -1,5 +1,5 @@
 import { PanelModel } from '@grafana/ui';
 import { PanelModel } from '@grafana/ui';
-import { barGaugePanelMigrationCheck } from './BarGaugeMigrations';
+import { barGaugePanelMigrationHandler } from './BarGaugeMigrations';
 
 
 describe('BarGauge Panel Migrations', () => {
 describe('BarGauge Panel Migrations', () => {
   it('from 6.2', () => {
   it('from 6.2', () => {
@@ -45,6 +45,6 @@ describe('BarGauge Panel Migrations', () => {
       type: 'bargauge',
       type: 'bargauge',
     } as PanelModel;
     } as PanelModel;
 
 
-    expect(barGaugePanelMigrationCheck(panel)).toMatchSnapshot();
+    expect(barGaugePanelMigrationHandler(panel)).toMatchSnapshot();
   });
   });
 });
 });

+ 3 - 4
public/app/plugins/panel/bargauge/BarGaugeMigrations.ts

@@ -1,7 +1,6 @@
-import { PanelModel } from '@grafana/ui';
-import { sharedSingleStatMigrationCheck } from '@grafana/ui/src/components/SingleStatShared/SingleStatBaseOptions';
+import { PanelModel, sharedSingleStatMigrationHandler } from '@grafana/ui';
 import { BarGaugeOptions } from './types';
 import { BarGaugeOptions } from './types';
 
 
-export const barGaugePanelMigrationCheck = (panel: PanelModel<BarGaugeOptions>): Partial<BarGaugeOptions> => {
-  return sharedSingleStatMigrationCheck(panel);
+export const barGaugePanelMigrationHandler = (panel: PanelModel<BarGaugeOptions>): Partial<BarGaugeOptions> => {
+  return sharedSingleStatMigrationHandler(panel);
 };
 };

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

@@ -1,11 +1,11 @@
-import { PanelPlugin, sharedSingleStatOptionsCheck } from '@grafana/ui';
+import { PanelPlugin, sharedSingleStatPanelChangedHandler } from '@grafana/ui';
 import { BarGaugePanel } from './BarGaugePanel';
 import { BarGaugePanel } from './BarGaugePanel';
 import { BarGaugePanelEditor } from './BarGaugePanelEditor';
 import { BarGaugePanelEditor } from './BarGaugePanelEditor';
 import { BarGaugeOptions, defaults } from './types';
 import { BarGaugeOptions, defaults } from './types';
-import { barGaugePanelMigrationCheck } from './BarGaugeMigrations';
+import { barGaugePanelMigrationHandler } from './BarGaugeMigrations';
 
 
 export const plugin = new PanelPlugin<BarGaugeOptions>(BarGaugePanel)
 export const plugin = new PanelPlugin<BarGaugeOptions>(BarGaugePanel)
   .setDefaults(defaults)
   .setDefaults(defaults)
   .setEditor(BarGaugePanelEditor)
   .setEditor(BarGaugePanelEditor)
-  .setPanelChangeHandler(sharedSingleStatOptionsCheck)
-  .setMigrationHandler(barGaugePanelMigrationCheck);
+  .setPanelChangeHandler(sharedSingleStatPanelChangedHandler)
+  .setMigrationHandler(barGaugePanelMigrationHandler);

+ 26 - 2
public/app/plugins/panel/gauge/GaugeMigrations.test.ts

@@ -1,5 +1,5 @@
 import { PanelModel } from '@grafana/ui';
 import { PanelModel } from '@grafana/ui';
-import { gaugePanelMigrationCheck } from './GaugeMigrations';
+import { gaugePanelMigrationHandler, gaugePanelChangedHandler } from './GaugeMigrations';
 
 
 describe('Gauge Panel Migrations', () => {
 describe('Gauge Panel Migrations', () => {
   it('from 6.1.1', () => {
   it('from 6.1.1', () => {
@@ -77,6 +77,30 @@ describe('Gauge Panel Migrations', () => {
       type: 'gauge',
       type: 'gauge',
     } as PanelModel;
     } as PanelModel;
 
 
-    expect(gaugePanelMigrationCheck(panel)).toMatchSnapshot();
+    expect(gaugePanelMigrationHandler(panel)).toMatchSnapshot();
+  });
+
+  it('change from angular singlestat to gauge', () => {
+    const old: any = {
+      angular: {
+        format: 'ms',
+        decimals: 7,
+        gauge: {
+          maxValue: 150,
+          minValue: -10,
+          show: true,
+          thresholdLabels: true,
+          thresholdMarkers: true,
+        },
+      },
+    };
+
+    const newOptions = gaugePanelChangedHandler({} as any, 'singlestat', old);
+    expect(newOptions.fieldOptions.defaults.unit).toBe('ms');
+    expect(newOptions.fieldOptions.defaults.min).toBe(-10);
+    expect(newOptions.fieldOptions.defaults.max).toBe(150);
+    expect(newOptions.fieldOptions.defaults.decimals).toBe(7);
+    expect(newOptions.showThresholdMarkers).toBe(true);
+    expect(newOptions.showThresholdLabels).toBe(true);
   });
   });
 });
 });

+ 24 - 4
public/app/plugins/panel/gauge/GaugeMigrations.ts

@@ -1,7 +1,27 @@
-import { PanelModel } from '@grafana/ui';
+import { PanelModel, sharedSingleStatPanelChangedHandler, sharedSingleStatMigrationHandler } from '@grafana/ui';
 import { GaugeOptions } from './types';
 import { GaugeOptions } from './types';
-import { sharedSingleStatMigrationCheck } from '@grafana/ui/src/components/SingleStatShared/SingleStatBaseOptions';
 
 
-export const gaugePanelMigrationCheck = (panel: PanelModel<GaugeOptions>): Partial<GaugeOptions> => {
-  return sharedSingleStatMigrationCheck(panel);
+// This is called when the panel first loads
+export const gaugePanelMigrationHandler = (panel: PanelModel<GaugeOptions>): Partial<GaugeOptions> => {
+  return sharedSingleStatMigrationHandler(panel);
+};
+
+// This is called when the panel changes from another panel
+export const gaugePanelChangedHandler = (
+  options: Partial<GaugeOptions> | any,
+  prevPluginId: string,
+  prevOptions: any
+) => {
+  // This handles most config changes
+  const opts = sharedSingleStatPanelChangedHandler(options, prevPluginId, prevOptions) as GaugeOptions;
+
+  // Changing from angular singlestat
+  if (prevPluginId === 'singlestat' && prevOptions.angular) {
+    const gauge = prevOptions.angular.gauge;
+    if (gauge) {
+      opts.showThresholdMarkers = gauge.thresholdMarkers;
+      opts.showThresholdLabels = gauge.thresholdLabels;
+    }
+  }
+  return opts;
 };
 };

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

@@ -1,10 +1,11 @@
-import { PanelPlugin, sharedSingleStatMigrationCheck, sharedSingleStatOptionsCheck } from '@grafana/ui';
+import { PanelPlugin } from '@grafana/ui';
 import { GaugePanelEditor } from './GaugePanelEditor';
 import { GaugePanelEditor } from './GaugePanelEditor';
 import { GaugePanel } from './GaugePanel';
 import { GaugePanel } from './GaugePanel';
 import { GaugeOptions, defaults } from './types';
 import { GaugeOptions, defaults } from './types';
+import { gaugePanelMigrationHandler, gaugePanelChangedHandler } from './GaugeMigrations';
 
 
 export const plugin = new PanelPlugin<GaugeOptions>(GaugePanel)
 export const plugin = new PanelPlugin<GaugeOptions>(GaugePanel)
   .setDefaults(defaults)
   .setDefaults(defaults)
   .setEditor(GaugePanelEditor)
   .setEditor(GaugePanelEditor)
-  .setPanelChangeHandler(sharedSingleStatOptionsCheck)
-  .setMigrationHandler(sharedSingleStatMigrationCheck);
+  .setPanelChangeHandler(gaugePanelChangedHandler)
+  .setMigrationHandler(gaugePanelMigrationHandler);

+ 37 - 0
public/app/plugins/panel/singlestat/editor.html

@@ -1,4 +1,41 @@
 <div class="editor-row">
 <div class="editor-row">
+
+  <div class="grafana-info-box" ng-if="ctrl.panel.gauge.show">
+    <h5>Gauge Migration</h5>
+    <p>
+      Gauge visualizations within the Singlestat panel are deprecated.  Please
+      migrate this panel to use the Gauge panel
+    
+      <div class="gf-form-button-row">
+        <button class="btn btn-primary" ng-click="ctrl.migrateToGaugePanel(true)">
+          Migrate to Gauge Panel
+        </button>
+        <button class="btn btn-inverse" ng-click="ctrl.migrateToGaugePanel(false)">
+          Show as single stat
+        </button>
+      </div>
+    
+      <br/>
+      
+      <div ng-if="ctrl.panel.sparkline.show">
+        <b>NOTE:</b> Sparklines are not supported in the gauge panel
+      </div>
+
+      <div ng-if="ctrl.panel.prefix">
+        <b>NOTE:</b> Prefix will not be show in the gauge panel
+      </div>
+
+      <div ng-if="ctrl.panel.postfix">
+        <b>NOTE:</b> Postfix will not be show in the gauge panel
+      </div>
+
+      <div ng-if="ctrl.panel.links && ctrl.panel.links.length">
+        <b>NOTE:</b> Links will be in the upper left corner, rather than anywhere on the gauge
+      </div>
+    </p>
+  </div>
+    
+
   <div class="section gf-form-group">
   <div class="section gf-form-group">
     <h5 class="section-heading">Value</h5>
     <h5 class="section-heading">Value</h5>
 
 

+ 9 - 0
public/app/plugins/panel/singlestat/module.ts

@@ -113,6 +113,15 @@ class SingleStatCtrl extends MetricsPanelCtrl {
     this.unitFormats = kbn.getUnitFormats();
     this.unitFormats = kbn.getUnitFormats();
   }
   }
 
 
+  migrateToGaugePanel(migrate: boolean) {
+    if (migrate) {
+      this.onPluginTypeChange(config.panels['gauge']);
+    } else {
+      this.panel.gauge.show = false;
+      this.render();
+    }
+  }
+
   setUnitFormat(subItem: { value: any }) {
   setUnitFormat(subItem: { value: any }) {
     this.panel.format = subItem.value;
     this.panel.format = subItem.value;
     this.refresh();
     this.refresh();

+ 3 - 3
public/app/plugins/panel/singlestat2/module.tsx

@@ -1,4 +1,4 @@
-import { PanelPlugin, sharedSingleStatMigrationCheck, sharedSingleStatOptionsCheck } from '@grafana/ui';
+import { PanelPlugin, sharedSingleStatMigrationHandler, sharedSingleStatPanelChangedHandler } from '@grafana/ui';
 import { SingleStatOptions, defaults } from './types';
 import { SingleStatOptions, defaults } from './types';
 import { SingleStatPanel } from './SingleStatPanel';
 import { SingleStatPanel } from './SingleStatPanel';
 import { SingleStatEditor } from './SingleStatEditor';
 import { SingleStatEditor } from './SingleStatEditor';
@@ -6,5 +6,5 @@ import { SingleStatEditor } from './SingleStatEditor';
 export const plugin = new PanelPlugin<SingleStatOptions>(SingleStatPanel)
 export const plugin = new PanelPlugin<SingleStatOptions>(SingleStatPanel)
   .setDefaults(defaults)
   .setDefaults(defaults)
   .setEditor(SingleStatEditor)
   .setEditor(SingleStatEditor)
-  .setPanelChangeHandler(sharedSingleStatOptionsCheck)
-  .setMigrationHandler(sharedSingleStatMigrationCheck);
+  .setPanelChangeHandler(sharedSingleStatPanelChangedHandler)
+  .setMigrationHandler(sharedSingleStatMigrationHandler);