mirror of
https://github.com/grafana/grafana.git
synced 2025-09-24 19:23:51 +08:00
Using an id to identify mappings
This commit is contained in:
@ -41,7 +41,7 @@ export default class ToggleButtonGroup extends PureComponent<ToggleButtonGroupPr
|
|||||||
<div className="gf-form">
|
<div className="gf-form">
|
||||||
<div className={`toggle-button-group ${stackedButtons ? 'stacked' : ''}`}>
|
<div className={`toggle-button-group ${stackedButtons ? 'stacked' : ''}`}>
|
||||||
{label && <label className={labelClassName}>{label}</label>}
|
{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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -54,9 +54,17 @@ interface ToggleButtonProps {
|
|||||||
value: any;
|
value: any;
|
||||||
className?: string;
|
className?: string;
|
||||||
children: ReactNode;
|
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 => {
|
const handleChange = event => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
if (onChange) {
|
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 (
|
return (
|
||||||
<button className={btnClassName} onClick={handleChange}>
|
<button className={btnClassName} onClick={handleChange}>
|
||||||
<span>{children}</span>
|
<span>{children}</span>
|
||||||
|
@ -53,12 +53,6 @@ export default class MappingRow extends PureComponent<Props, State> {
|
|||||||
this.setState({ mapping: updatedMapping });
|
this.setState({ mapping: updatedMapping });
|
||||||
};
|
};
|
||||||
|
|
||||||
updateMapping = () => {
|
|
||||||
const { mapping } = this.state;
|
|
||||||
|
|
||||||
this.props.updateMapping(mapping);
|
|
||||||
};
|
|
||||||
|
|
||||||
onMappingTypeChange = mappingType => {
|
onMappingTypeChange = mappingType => {
|
||||||
const { mapping } = this.state;
|
const { mapping } = this.state;
|
||||||
|
|
||||||
@ -66,6 +60,12 @@ export default class MappingRow extends PureComponent<Props, State> {
|
|||||||
this.setState({ mapping: updatedMapping });
|
this.setState({ mapping: updatedMapping });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
updateMapping = () => {
|
||||||
|
const { mapping } = this.state;
|
||||||
|
|
||||||
|
this.props.updateMapping(mapping);
|
||||||
|
};
|
||||||
|
|
||||||
renderRow() {
|
renderRow() {
|
||||||
const { mapping } = this.state;
|
const { mapping } = this.state;
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ export default class MappingRow extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="gf-form">
|
<div className="gf-form">
|
||||||
<div className="gf-form-inline">
|
<div className="gf-form-inline mapping-row-input">
|
||||||
<Label width={4}>From</Label>
|
<Label width={4}>From</Label>
|
||||||
<div>
|
<div>
|
||||||
<input
|
<input
|
||||||
@ -85,7 +85,7 @@ export default class MappingRow extends PureComponent<Props, State> {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="gf-form-inline">
|
<div className="gf-form-inline mapping-row-input">
|
||||||
<Label width={4}>To</Label>
|
<Label width={4}>To</Label>
|
||||||
<div>
|
<div>
|
||||||
<input
|
<input
|
||||||
@ -96,7 +96,7 @@ export default class MappingRow extends PureComponent<Props, State> {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="gf-form-inline">
|
<div className="gf-form-inline mapping-row-input">
|
||||||
<Label width={4}>Text</Label>
|
<Label width={4}>Text</Label>
|
||||||
<div>
|
<div>
|
||||||
<input
|
<input
|
||||||
@ -115,7 +115,7 @@ export default class MappingRow extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="gf-form">
|
<div className="gf-form">
|
||||||
<div className="gf-form-inline">
|
<div className="gf-form-inline mapping-row-input">
|
||||||
<Label width={4}>Value</Label>
|
<Label width={4}>Value</Label>
|
||||||
<div>
|
<div>
|
||||||
<input
|
<input
|
||||||
@ -126,7 +126,7 @@ export default class MappingRow extends PureComponent<Props, State> {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="gf-form-inline">
|
<div className="gf-form-inline mapping-row-input">
|
||||||
<Label width={4}>Text</Label>
|
<Label width={4}>Text</Label>
|
||||||
<div>
|
<div>
|
||||||
<input
|
<input
|
||||||
@ -151,7 +151,7 @@ export default class MappingRow extends PureComponent<Props, State> {
|
|||||||
onChange={mappingType => this.onMappingTypeChange(mappingType)}
|
onChange={mappingType => this.onMappingTypeChange(mappingType)}
|
||||||
value={mapping.type}
|
value={mapping.type}
|
||||||
stackedButtons={true}
|
stackedButtons={true}
|
||||||
render={({ selectedValue, onChange }) => {
|
render={({ selectedValue, onChange, stackedButtons }) => {
|
||||||
return [
|
return [
|
||||||
<ToggleButton
|
<ToggleButton
|
||||||
className="btn-small"
|
className="btn-small"
|
||||||
@ -159,6 +159,7 @@ export default class MappingRow extends PureComponent<Props, State> {
|
|||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
selected={selectedValue === MappingType.ValueToText}
|
selected={selectedValue === MappingType.ValueToText}
|
||||||
value={MappingType.ValueToText}
|
value={MappingType.ValueToText}
|
||||||
|
stackedButtons={stackedButtons}
|
||||||
>
|
>
|
||||||
Value
|
Value
|
||||||
</ToggleButton>,
|
</ToggleButton>,
|
||||||
@ -168,6 +169,7 @@ export default class MappingRow extends PureComponent<Props, State> {
|
|||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
selected={selectedValue === MappingType.RangeToText}
|
selected={selectedValue === MappingType.RangeToText}
|
||||||
value={MappingType.RangeToText}
|
value={MappingType.RangeToText}
|
||||||
|
stackedButtons={stackedButtons}
|
||||||
>
|
>
|
||||||
Range
|
Range
|
||||||
</ToggleButton>,
|
</ToggleButton>,
|
||||||
|
@ -2,27 +2,18 @@ import React, { PureComponent } from 'react';
|
|||||||
import classNames from 'classnames/bind';
|
import classNames from 'classnames/bind';
|
||||||
import { ColorPicker } from 'app/core/components/colorpicker/ColorPicker';
|
import { ColorPicker } from 'app/core/components/colorpicker/ColorPicker';
|
||||||
import { OptionModuleProps } from './module';
|
import { OptionModuleProps } from './module';
|
||||||
import { Threshold } from 'app/types';
|
import { BasicGaugeColor, Threshold } from 'app/types';
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
thresholds: Threshold[];
|
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> {
|
export default class Thresholds extends PureComponent<OptionModuleProps, State> {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
thresholds: this.props.options.thresholds || [
|
thresholds: props.options.thresholds,
|
||||||
{ index: 0, label: 'Min', value: 0, canRemove: false, color: BasicGaugeColor.Green },
|
|
||||||
{ index: 1, label: 'Max', value: 100, canRemove: false },
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
54
public/app/plugins/panel/gauge/ValueMappings.test.tsx
Normal file
54
public/app/plugins/panel/gauge/ValueMappings.test.tsx
Normal file
@ -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);
|
||||||
|
});
|
||||||
|
});
|
@ -5,35 +5,60 @@ import { MappingType, RangeMap, ValueMap } from 'app/types';
|
|||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
mappings: Array<ValueMap | RangeMap>;
|
mappings: Array<ValueMap | RangeMap>;
|
||||||
|
nextIdToAdd: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ValueMappings extends PureComponent<OptionModuleProps, State> {
|
export default class ValueMappings extends PureComponent<OptionModuleProps, State> {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
const mappings = props.options.mappings;
|
||||||
|
|
||||||
this.state = {
|
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 = () =>
|
addMapping = () =>
|
||||||
this.setState(prevState => ({
|
this.setState(prevState => ({
|
||||||
mappings: [
|
mappings: [
|
||||||
...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 =>
|
onRemoveMapping = id => {
|
||||||
this.setState(prevState => ({
|
this.setState(
|
||||||
mappings: prevState.mappings.filter((m, i) => i !== index),
|
prevState => ({
|
||||||
}));
|
mappings: prevState.mappings.filter(m => {
|
||||||
|
return m.id !== id;
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
() => {
|
||||||
|
this.props.onChange({ ...this.props.options, mappings: this.state.mappings });
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
updateGauge = mapping => {
|
updateGauge = mapping => {
|
||||||
this.setState(
|
this.setState(
|
||||||
prevState => ({
|
prevState => ({
|
||||||
mappings: prevState.mappings.map(m => {
|
mappings: prevState.mappings.map(m => {
|
||||||
if (m === mapping) {
|
if (m.id === mapping.id) {
|
||||||
return { ...mapping };
|
return { ...mapping };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,16 +79,14 @@ export default class ValueMappings extends PureComponent<OptionModuleProps, Stat
|
|||||||
<h5 className="page-heading">Value mappings</h5>
|
<h5 className="page-heading">Value mappings</h5>
|
||||||
<div>
|
<div>
|
||||||
{mappings.length > 0 &&
|
{mappings.length > 0 &&
|
||||||
mappings.map((mapping, index) => {
|
mappings.map((mapping, index) => (
|
||||||
return (
|
|
||||||
<MappingRow
|
<MappingRow
|
||||||
key={index}
|
key={`${mapping.text}-${index}`}
|
||||||
mapping={mapping}
|
mapping={mapping}
|
||||||
updateMapping={this.updateGauge}
|
updateMapping={this.updateGauge}
|
||||||
removeMapping={() => this.onRemoveMapping(index)}
|
removeMapping={() => this.onRemoveMapping(mapping.id)}
|
||||||
/>
|
/>
|
||||||
);
|
))}
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="add-mapping-row" onClick={this.addMapping}>
|
<div className="add-mapping-row" onClick={this.addMapping}>
|
||||||
<div className="add-mapping-row-icon">
|
<div className="add-mapping-row-icon">
|
||||||
|
@ -1,11 +1,19 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import Gauge from 'app/viz/Gauge';
|
import Gauge from 'app/viz/Gauge';
|
||||||
import { NullValueMode, PanelOptionsProps, PanelProps, RangeMap, Threshold, ValueMap } from 'app/types';
|
|
||||||
import { getTimeSeriesVMs } from 'app/viz/state/timeSeries';
|
import { getTimeSeriesVMs } from 'app/viz/state/timeSeries';
|
||||||
import ValueOptions from './ValueOptions';
|
import ValueOptions from './ValueOptions';
|
||||||
import GaugeOptions from './GaugeOptions';
|
import GaugeOptions from './GaugeOptions';
|
||||||
import Thresholds from './Thresholds';
|
import Thresholds from './Thresholds';
|
||||||
import ValueMappings from './ValueMappings';
|
import ValueMappings from './ValueMappings';
|
||||||
|
import {
|
||||||
|
BasicGaugeColor,
|
||||||
|
NullValueMode,
|
||||||
|
PanelOptionsProps,
|
||||||
|
PanelProps,
|
||||||
|
RangeMap,
|
||||||
|
Threshold,
|
||||||
|
ValueMap,
|
||||||
|
} from 'app/types';
|
||||||
|
|
||||||
export interface OptionsProps {
|
export interface OptionsProps {
|
||||||
decimals: number;
|
decimals: number;
|
||||||
@ -16,8 +24,7 @@ export interface OptionsProps {
|
|||||||
suffix: string;
|
suffix: string;
|
||||||
unit: string;
|
unit: string;
|
||||||
thresholds: Threshold[];
|
thresholds: Threshold[];
|
||||||
mappings: Array<ValueMap | RangeMap>;
|
mappings: Array<RangeMap | ValueMap>;
|
||||||
mappingType: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OptionModuleProps {
|
export interface OptionModuleProps {
|
||||||
@ -33,8 +40,14 @@ export const defaultProps = {
|
|||||||
showThresholdMarkers: true,
|
showThresholdMarkers: true,
|
||||||
showThresholdLabels: false,
|
showThresholdLabels: false,
|
||||||
suffix: '',
|
suffix: '',
|
||||||
valueMaps: [],
|
decimals: 0,
|
||||||
rangeMaps: [],
|
stat: '',
|
||||||
|
unit: '',
|
||||||
|
mappings: [],
|
||||||
|
thresholds: [
|
||||||
|
{ index: 0, label: 'Min', value: 0, canRemove: false, color: BasicGaugeColor.Green },
|
||||||
|
{ index: 1, label: 'Max', value: 100, canRemove: false },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ import {
|
|||||||
DataQueryOptions,
|
DataQueryOptions,
|
||||||
IntervalValues,
|
IntervalValues,
|
||||||
} from './series';
|
} 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 { PluginDashboard, PluginMeta, Plugin, PanelPlugin, PluginsState } from './plugins';
|
||||||
import { Organization, OrganizationState } from './organization';
|
import { Organization, OrganizationState } from './organization';
|
||||||
import {
|
import {
|
||||||
@ -97,6 +97,7 @@ export {
|
|||||||
RangeMap,
|
RangeMap,
|
||||||
IntervalValues,
|
IntervalValues,
|
||||||
MappingType,
|
MappingType,
|
||||||
|
BasicGaugeColor,
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface StoreState {
|
export interface StoreState {
|
||||||
|
@ -42,8 +42,15 @@ export enum MappingType {
|
|||||||
RangeToText = 2,
|
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 {
|
interface BaseMap {
|
||||||
op: string;
|
id: number;
|
||||||
|
operator: string;
|
||||||
text: string;
|
text: string;
|
||||||
type: MappingType;
|
type: MappingType;
|
||||||
}
|
}
|
||||||
|
@ -37,5 +37,9 @@
|
|||||||
border-radius: 0 $border-radius $border-radius 0;
|
border-radius: 0 $border-radius $border-radius 0;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.stacked {
|
||||||
|
border-radius: $border-radius;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,11 +8,17 @@
|
|||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mapping-row-input {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.add-mapping-row {
|
.add-mapping-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 37px;
|
height: 37px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
width: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-mapping-row-icon {
|
.add-mapping-row-icon {
|
||||||
@ -27,4 +33,6 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 5px 8px;
|
padding: 5px 8px;
|
||||||
|
background-color: $input-label-bg;
|
||||||
|
width: calc(100% - 36px);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user