Преглед на файлове

Using an id to identify mappings

Peter Holmberg преди 7 години
родител
ревизия
ddf080dab6

+ 11 - 3
public/app/core/components/ToggleButtonGroup/ToggleButtonGroup.tsx

@@ -41,7 +41,7 @@ export default class ToggleButtonGroup extends PureComponent<ToggleButtonGroupPr
       <div className="gf-form">
         <div className={`toggle-button-group ${stackedButtons ? 'stacked' : ''}`}>
           {label && <label className={labelClassName}>{label}</label>}
-          {this.props.render({ selectedValue, onChange: this.handleToggle.bind(this) })}
+          {this.props.render({ selectedValue, onChange: this.handleToggle.bind(this), stackedButtons: stackedButtons })}
         </div>
       </div>
     );
@@ -54,9 +54,17 @@ interface ToggleButtonProps {
   value: any;
   className?: string;
   children: ReactNode;
+  stackedButtons?: boolean;
 }
 
-export const ToggleButton: SFC<ToggleButtonProps> = ({ children, selected, className = '', value, onChange }) => {
+export const ToggleButton: SFC<ToggleButtonProps> = ({
+  children,
+  selected,
+  className = '',
+  value,
+  onChange,
+  stackedButtons,
+}) => {
   const handleChange = event => {
     event.stopPropagation();
     if (onChange) {
@@ -64,7 +72,7 @@ export const ToggleButton: SFC<ToggleButtonProps> = ({ children, selected, class
     }
   };
 
-  const btnClassName = `btn ${className} ${selected ? 'active' : ''}`;
+  const btnClassName = `btn ${className} ${selected ? 'active' : ''} ${stackedButtons ? 'stacked' : ''}`;
   return (
     <button className={btnClassName} onClick={handleChange}>
       <span>{children}</span>

+ 13 - 11
public/app/plugins/panel/gauge/MappingRow.tsx

@@ -53,17 +53,17 @@ export default class MappingRow extends PureComponent<Props, State> {
     this.setState({ mapping: updatedMapping });
   };
 
-  updateMapping = () => {
+  onMappingTypeChange = mappingType => {
     const { mapping } = this.state;
 
-    this.props.updateMapping(mapping);
+    const updatedMapping = { ...mapping, type: mappingType };
+    this.setState({ mapping: updatedMapping });
   };
 
-  onMappingTypeChange = mappingType => {
+  updateMapping = () => {
     const { mapping } = this.state;
 
-    const updatedMapping = { ...mapping, type: mappingType };
-    this.setState({ mapping: updatedMapping });
+    this.props.updateMapping(mapping);
   };
 
   renderRow() {
@@ -74,7 +74,7 @@ export default class MappingRow extends PureComponent<Props, State> {
 
       return (
         <div className="gf-form">
-          <div className="gf-form-inline">
+          <div className="gf-form-inline mapping-row-input">
             <Label width={4}>From</Label>
             <div>
               <input
@@ -85,7 +85,7 @@ export default class MappingRow extends PureComponent<Props, State> {
               />
             </div>
           </div>
-          <div className="gf-form-inline">
+          <div className="gf-form-inline mapping-row-input">
             <Label width={4}>To</Label>
             <div>
               <input
@@ -96,7 +96,7 @@ export default class MappingRow extends PureComponent<Props, State> {
               />
             </div>
           </div>
-          <div className="gf-form-inline">
+          <div className="gf-form-inline mapping-row-input">
             <Label width={4}>Text</Label>
             <div>
               <input
@@ -115,7 +115,7 @@ export default class MappingRow extends PureComponent<Props, State> {
 
     return (
       <div className="gf-form">
-        <div className="gf-form-inline">
+        <div className="gf-form-inline mapping-row-input">
           <Label width={4}>Value</Label>
           <div>
             <input
@@ -126,7 +126,7 @@ export default class MappingRow extends PureComponent<Props, State> {
             />
           </div>
         </div>
-        <div className="gf-form-inline">
+        <div className="gf-form-inline mapping-row-input">
           <Label width={4}>Text</Label>
           <div>
             <input
@@ -151,7 +151,7 @@ export default class MappingRow extends PureComponent<Props, State> {
             onChange={mappingType => this.onMappingTypeChange(mappingType)}
             value={mapping.type}
             stackedButtons={true}
-            render={({ selectedValue, onChange }) => {
+            render={({ selectedValue, onChange, stackedButtons }) => {
               return [
                 <ToggleButton
                   className="btn-small"
@@ -159,6 +159,7 @@ export default class MappingRow extends PureComponent<Props, State> {
                   onChange={onChange}
                   selected={selectedValue === MappingType.ValueToText}
                   value={MappingType.ValueToText}
+                  stackedButtons={stackedButtons}
                 >
                   Value
                 </ToggleButton>,
@@ -168,6 +169,7 @@ export default class MappingRow extends PureComponent<Props, State> {
                   onChange={onChange}
                   selected={selectedValue === MappingType.RangeToText}
                   value={MappingType.RangeToText}
+                  stackedButtons={stackedButtons}
                 >
                   Range
                 </ToggleButton>,

+ 2 - 11
public/app/plugins/panel/gauge/Thresholds.tsx

@@ -2,27 +2,18 @@ import React, { PureComponent } from 'react';
 import classNames from 'classnames/bind';
 import { ColorPicker } from 'app/core/components/colorpicker/ColorPicker';
 import { OptionModuleProps } from './module';
-import { Threshold } from 'app/types';
+import { BasicGaugeColor, Threshold } from 'app/types';
 
 interface State {
   thresholds: Threshold[];
 }
 
-enum BasicGaugeColor {
-  Green = 'rgba(50, 172, 45, 0.97)',
-  Orange = 'rgba(237, 129, 40, 0.89)',
-  Red = 'rgb(212, 74, 58)',
-}
-
 export default class Thresholds extends PureComponent<OptionModuleProps, State> {
   constructor(props) {
     super(props);
 
     this.state = {
-      thresholds: this.props.options.thresholds || [
-        { index: 0, label: 'Min', value: 0, canRemove: false, color: BasicGaugeColor.Green },
-        { index: 1, label: 'Max', value: 100, canRemove: false },
-      ],
+      thresholds: props.options.thresholds,
     };
   }
 

+ 54 - 0
public/app/plugins/panel/gauge/ValueMappings.test.tsx

@@ -0,0 +1,54 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+import { defaultProps, OptionModuleProps } from './module';
+import { MappingType } from '../../../types';
+import ValueMappings from './ValueMappings';
+
+const setup = (propOverrides?: object) => {
+  const props: OptionModuleProps = {
+    onChange: jest.fn(),
+    options: {
+      ...defaultProps.options,
+      mappings: [
+        { id: 1, operator: '', type: MappingType.ValueToText, value: '20', text: 'Ok' },
+        { id: 2, operator: '', type: MappingType.RangeToText, from: '21', to: '30', text: 'Meh' },
+      ],
+    },
+  };
+
+  Object.assign(props, propOverrides);
+
+  const wrapper = shallow(<ValueMappings {...props} />);
+
+  return wrapper.instance() as ValueMappings;
+};
+
+describe('On remove mapping', () => {
+  it('Should remove mapping with id 0', () => {
+    const instance = setup();
+    instance.onRemoveMapping(1);
+
+    expect(instance.state.mappings).toEqual([
+      { id: 2, operator: '', type: MappingType.RangeToText, from: '21', to: '30', text: 'Meh' },
+    ]);
+  });
+
+  it('should remove mapping with id 1', () => {
+    const instance = setup();
+    instance.onRemoveMapping(2);
+
+    expect(instance.state.mappings).toEqual([
+      { id: 1, operator: '', type: MappingType.ValueToText, value: '20', text: 'Ok' },
+    ]);
+  });
+});
+
+describe('Next id to add', () => {
+  it('should be 4', () => {
+    const instance = setup();
+
+    instance.addMapping();
+
+    expect(instance.state.nextIdToAdd).toEqual(4);
+  });
+});

+ 40 - 17
public/app/plugins/panel/gauge/ValueMappings.tsx

@@ -5,35 +5,60 @@ import { MappingType, RangeMap, ValueMap } from 'app/types';
 
 interface State {
   mappings: Array<ValueMap | RangeMap>;
+  nextIdToAdd: number;
 }
 
 export default class ValueMappings extends PureComponent<OptionModuleProps, State> {
   constructor(props) {
     super(props);
 
+    const mappings = props.options.mappings;
+
     this.state = {
-      mappings: props.mappings || [],
+      mappings: mappings || [],
+      nextIdToAdd: mappings ? this.getMaxIdFromMappings(mappings) : 1,
     };
   }
 
+  getMaxIdFromMappings(mappings) {
+    return Math.max.apply(null, mappings.map(mapping => mapping.id).map(m => m)) + 1;
+  }
+
   addMapping = () =>
     this.setState(prevState => ({
       mappings: [
         ...prevState.mappings,
-        { op: '', value: '', text: '', type: MappingType.ValueToText, from: '', to: '' },
+        {
+          id: prevState.nextIdToAdd,
+          operator: '',
+          value: '',
+          text: '',
+          type: MappingType.ValueToText,
+          from: '',
+          to: '',
+        },
       ],
+      nextIdToAdd: prevState.nextIdToAdd + 1,
     }));
 
-  onRemoveMapping = index =>
-    this.setState(prevState => ({
-      mappings: prevState.mappings.filter((m, i) => i !== index),
-    }));
+  onRemoveMapping = id => {
+    this.setState(
+      prevState => ({
+        mappings: prevState.mappings.filter(m => {
+          return m.id !== id;
+        }),
+      }),
+      () => {
+        this.props.onChange({ ...this.props.options, mappings: this.state.mappings });
+      }
+    );
+  };
 
   updateGauge = mapping => {
     this.setState(
       prevState => ({
         mappings: prevState.mappings.map(m => {
-          if (m === mapping) {
+          if (m.id === mapping.id) {
             return { ...mapping };
           }
 
@@ -54,16 +79,14 @@ export default class ValueMappings extends PureComponent<OptionModuleProps, Stat
         <h5 className="page-heading">Value mappings</h5>
         <div>
           {mappings.length > 0 &&
-            mappings.map((mapping, index) => {
-              return (
-                <MappingRow
-                  key={index}
-                  mapping={mapping}
-                  updateMapping={this.updateGauge}
-                  removeMapping={() => this.onRemoveMapping(index)}
-                />
-              );
-            })}
+            mappings.map((mapping, index) => (
+              <MappingRow
+                key={`${mapping.text}-${index}`}
+                mapping={mapping}
+                updateMapping={this.updateGauge}
+                removeMapping={() => this.onRemoveMapping(mapping.id)}
+              />
+            ))}
         </div>
         <div className="add-mapping-row" onClick={this.addMapping}>
           <div className="add-mapping-row-icon">

+ 18 - 5
public/app/plugins/panel/gauge/module.tsx

@@ -1,11 +1,19 @@
 import React, { PureComponent } from 'react';
 import Gauge from 'app/viz/Gauge';
-import { NullValueMode, PanelOptionsProps, PanelProps, RangeMap, Threshold, ValueMap } from 'app/types';
 import { getTimeSeriesVMs } from 'app/viz/state/timeSeries';
 import ValueOptions from './ValueOptions';
 import GaugeOptions from './GaugeOptions';
 import Thresholds from './Thresholds';
 import ValueMappings from './ValueMappings';
+import {
+  BasicGaugeColor,
+  NullValueMode,
+  PanelOptionsProps,
+  PanelProps,
+  RangeMap,
+  Threshold,
+  ValueMap,
+} from 'app/types';
 
 export interface OptionsProps {
   decimals: number;
@@ -16,8 +24,7 @@ export interface OptionsProps {
   suffix: string;
   unit: string;
   thresholds: Threshold[];
-  mappings: Array<ValueMap | RangeMap>;
-  mappingType: number;
+  mappings: Array<RangeMap | ValueMap>;
 }
 
 export interface OptionModuleProps {
@@ -33,8 +40,14 @@ export const defaultProps = {
     showThresholdMarkers: true,
     showThresholdLabels: false,
     suffix: '',
-    valueMaps: [],
-    rangeMaps: [],
+    decimals: 0,
+    stat: '',
+    unit: '',
+    mappings: [],
+    thresholds: [
+      { index: 0, label: 'Min', value: 0, canRemove: false, color: BasicGaugeColor.Green },
+      { index: 1, label: 'Max', value: 100, canRemove: false },
+    ],
   },
 };
 

+ 2 - 1
public/app/types/index.ts

@@ -21,7 +21,7 @@ import {
   DataQueryOptions,
   IntervalValues,
 } from './series';
-import { MappingType, PanelProps, PanelOptionsProps, RangeMap, Threshold, ValueMap } from './panel';
+import { BasicGaugeColor, MappingType, PanelProps, PanelOptionsProps, RangeMap, Threshold, ValueMap } from './panel';
 import { PluginDashboard, PluginMeta, Plugin, PanelPlugin, PluginsState } from './plugins';
 import { Organization, OrganizationState } from './organization';
 import {
@@ -97,6 +97,7 @@ export {
   RangeMap,
   IntervalValues,
   MappingType,
+  BasicGaugeColor,
 };
 
 export interface StoreState {

+ 8 - 1
public/app/types/panel.ts

@@ -42,8 +42,15 @@ export enum MappingType {
   RangeToText = 2,
 }
 
+export enum BasicGaugeColor {
+  Green = 'rgba(50, 172, 45, 0.97)',
+  Orange = 'rgba(237, 129, 40, 0.89)',
+  Red = 'rgb(212, 74, 58)',
+}
+
 interface BaseMap {
-  op: string;
+  id: number;
+  operator: string;
   text: string;
   type: MappingType;
 }

+ 4 - 0
public/sass/components/_toggle_button_group.scss

@@ -37,5 +37,9 @@
       border-radius: 0 $border-radius $border-radius 0;
       margin-left: 0;
     }
+
+    &.stacked {
+      border-radius: $border-radius;
+    }
   }
 }

+ 8 - 0
public/sass/components/_value-mappings.scss

@@ -8,11 +8,17 @@
   margin-right: 5px;
 }
 
+.mapping-row-input {
+  margin-right: 5px;
+}
+
 .add-mapping-row {
   display: flex;
   overflow: hidden;
   height: 37px;
   cursor: pointer;
+  border-radius: $border-radius;
+  width: 200px;
 }
 
 .add-mapping-row-icon {
@@ -27,4 +33,6 @@
   align-items: center;
   display: flex;
   padding: 5px 8px;
+  background-color: $input-label-bg;
+  width: calc(100% - 36px);
 }