Merge remote-tracking branch 'origin/develop' into 14409/threshold-ux-changes

This commit is contained in:
Peter Holmberg
2018-12-13 11:19:16 +01:00
47 changed files with 1331 additions and 775 deletions

View File

@@ -8,7 +8,7 @@
</div>
<div class="gf-form-inline">
<div class="gf-form max-width-26">
<div class="gf-form">
<label class="gf-form-label width-8">Legend format</label>
<input type="text" class="gf-form-input" ng-model="ctrl.target.legendFormat" spellcheck='false' placeholder="legend format"
data-min-length=0 data-items=1000 ng-model-onblur ng-change="ctrl.refreshMetricData()">
@@ -58,4 +58,4 @@
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
</query-editor-row>
</query-editor-row>

View File

@@ -0,0 +1,155 @@
import React, { PureComponent } from 'react';
import { Label } from 'app/core/components/Label/Label';
import SimplePicker from 'app/core/components/Picker/SimplePicker';
import { MappingType, RangeMap, ValueMap } from 'app/types';
interface Props {
mapping: ValueMap | RangeMap;
updateMapping: (mapping) => void;
removeMapping: () => void;
}
interface State {
from: string;
id: number;
operator: string;
text: string;
to: string;
type: MappingType;
value: string;
}
const mappingOptions = [
{ value: MappingType.ValueToText, label: 'Value' },
{ value: MappingType.RangeToText, label: 'Range' },
];
export default class MappingRow extends PureComponent<Props, State> {
constructor(props) {
super(props);
this.state = {
...props.mapping,
};
}
onMappingValueChange = event => {
this.setState({ value: event.target.value });
};
onMappingFromChange = event => {
this.setState({ from: event.target.value });
};
onMappingToChange = event => {
this.setState({ to: event.target.value });
};
onMappingTextChange = event => {
this.setState({ text: event.target.value });
};
onMappingTypeChange = mappingType => {
this.setState({ type: mappingType });
};
updateMapping = () => {
this.props.updateMapping({ ...this.state });
};
renderRow() {
const { from, text, to, type, value } = this.state;
if (type === MappingType.RangeToText) {
return (
<div className="gf-form">
<div className="gf-form-inline mapping-row-input">
<Label width={4}>From</Label>
<div>
<input
className="gf-form-input"
value={from}
onBlur={this.updateMapping}
onChange={this.onMappingFromChange}
/>
</div>
</div>
<div className="gf-form-inline mapping-row-input">
<Label width={4}>To</Label>
<div>
<input
className="gf-form-input"
value={to}
onBlur={this.updateMapping}
onChange={this.onMappingToChange}
/>
</div>
</div>
<div className="gf-form-inline mapping-row-input">
<Label width={4}>Text</Label>
<div>
<input
className="gf-form-input"
value={text}
onBlur={this.updateMapping}
onChange={this.onMappingTextChange}
/>
</div>
</div>
</div>
);
}
return (
<div className="gf-form">
<div className="gf-form-inline mapping-row-input">
<Label width={4}>Value</Label>
<div>
<input
className="gf-form-input"
onBlur={this.updateMapping}
onChange={this.onMappingValueChange}
value={value}
/>
</div>
</div>
<div className="gf-form-inline mapping-row-input">
<Label width={4}>Text</Label>
<div>
<input
className="gf-form-input"
onBlur={this.updateMapping}
value={text}
onChange={this.onMappingTextChange}
/>
</div>
</div>
</div>
);
}
render() {
const { type } = this.state;
return (
<div className="mapping-row">
<div className="gf-form-inline mapping-row-type">
<Label width={5}>Type</Label>
<SimplePicker
placeholder="Choose type"
options={mappingOptions}
value={mappingOptions.find(o => o.value === type)}
getOptionLabel={i => i.label}
getOptionValue={i => i.value}
onSelected={type => this.onMappingTypeChange(type.value)}
width={7}
/>
</div>
<div>{this.renderRow()}</div>
<div onClick={this.props.removeMapping} className="threshold-row-remove">
<i className="fa fa-times" />
</div>
</div>
);
}
}

View File

@@ -1,13 +1,19 @@
import React from 'react';
import { shallow } from 'enzyme';
import Thresholds, { BasicGaugeColor } from './Thresholds';
import { OptionsProps } from './module';
import Thresholds from './Thresholds';
import { defaultProps, OptionsProps } from './module';
import { PanelOptionsProps } from '../../../types';
const setup = (propOverrides?: object) => {
const props: PanelOptionsProps<OptionsProps> = {
onChange: jest.fn(),
options: {} as OptionsProps,
options: {
...defaultProps.options,
thresholds: [
{ index: 0, label: 'Min', value: 0, canRemove: false, color: 'rgba(50, 172, 45, 0.97)' },
{ index: 1, label: 'Max', value: 100, canRemove: false },
],
},
};
Object.assign(props, propOverrides);
@@ -15,12 +21,6 @@ const setup = (propOverrides?: object) => {
return shallow(<Thresholds {...props} />).instance() as Thresholds;
};
const thresholds = [
{ index: 0, label: 'Min', value: 0, canRemove: false, color: BasicGaugeColor.Green },
{ index: 1, label: '', value: 50, canRemove: true, color: 'rgba(237, 129, 40, 0.89)' },
{ index: 2, label: 'Max', value: 100, canRemove: false, color: BasicGaugeColor.Red },
];
describe('Add threshold', () => {
it('should add threshold between min and max', () => {
const instance = setup();
@@ -28,24 +28,31 @@ describe('Add threshold', () => {
instance.onAddThreshold(1);
expect(instance.state.thresholds).toEqual([
{ index: 0, label: 'Min', value: 0, canRemove: false, color: BasicGaugeColor.Green },
{ index: 1, label: '', value: 50, canRemove: true, color: 'rgba(131, 123, 52, 0.99)' },
{ index: 2, label: 'Max', value: 100, canRemove: false, color: BasicGaugeColor.Red },
{ index: 0, label: 'Min', value: 0, canRemove: false, color: 'rgba(50, 172, 45, 0.97)' },
{ index: 1, label: '', value: 50, canRemove: true, color: 'rgba(237, 129, 40, 0.89)' },
{ index: 2, label: 'Max', value: 100, canRemove: false },
]);
});
it('should add threshold between min and added threshold', () => {
const instance = setup({
options: { thresholds: thresholds },
options: {
...defaultProps.options,
thresholds: [
{ index: 0, label: 'Min', value: 0, canRemove: false, color: 'rgba(50, 172, 45, 0.97)' },
{ index: 1, label: '', value: 50, canRemove: true, color: 'rgba(237, 129, 40, 0.89)' },
{ index: 2, label: 'Max', value: 100, canRemove: false },
],
},
});
instance.onAddThreshold(1);
expect(instance.state.thresholds).toEqual([
{ index: 0, label: 'Min', value: 0, canRemove: false, color: BasicGaugeColor.Green },
{ index: 1, label: '', value: 25, canRemove: true, color: 'rgba(144, 151, 43, 0.93)' },
{ index: 0, label: 'Min', value: 0, canRemove: false, color: 'rgba(50, 172, 45, 0.97)' },
{ index: 1, label: '', value: 25, canRemove: true, color: 'rgba(237, 129, 40, 0.89)' },
{ index: 2, label: '', value: 50, canRemove: true, color: 'rgba(237, 129, 40, 0.89)' },
{ index: 3, label: 'Max', value: 100, canRemove: false, color: BasicGaugeColor.Red },
{ index: 3, label: 'Max', value: 100, canRemove: false },
]);
});
});

View File

@@ -3,27 +3,18 @@ import classNames from 'classnames/bind';
import tinycolor from 'tinycolor2';
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[];
}
export 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, color: BasicGaugeColor.Red },
],
thresholds: props.options.thresholds,
};
}

View File

@@ -0,0 +1,73 @@
import React from 'react';
import { shallow } from 'enzyme';
import ValueMappings from './ValueMappings';
import { defaultProps, OptionModuleProps } from './module';
import { MappingType } from 'app/types';
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} />);
const instance = wrapper.instance() as ValueMappings;
return {
instance,
wrapper,
};
};
describe('Render', () => {
it('should render component', () => {
const { wrapper } = setup();
expect(wrapper).toMatchSnapshot();
});
});
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);
});
it('should default to 1', () => {
const { instance } = setup({ options: { ...defaultProps.options } });
expect(instance.state.nextIdToAdd).toEqual(1);
});
});

View File

@@ -0,0 +1,100 @@
import React, { PureComponent } from 'react';
import MappingRow from './MappingRow';
import { OptionModuleProps } from './module';
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: mappings || [],
nextIdToAdd: mappings.length > 0 ? 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,
{
id: prevState.nextIdToAdd,
operator: '',
value: '',
text: '',
type: MappingType.ValueToText,
from: '',
to: '',
},
],
nextIdToAdd: prevState.nextIdToAdd + 1,
}));
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.id === mapping.id) {
return { ...mapping };
}
return m;
}),
}),
() => {
this.props.onChange({ ...this.props.options, mappings: this.state.mappings });
}
);
};
render() {
const { mappings } = this.state;
return (
<div className="section gf-form-group">
<h5 className="page-heading">Value mappings</h5>
<div>
{mappings.length > 0 &&
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">
<i className="fa fa-plus" />
</div>
<div className="add-mapping-row-label">Add mapping</div>
</div>
</div>
);
}
}

View File

@@ -0,0 +1,61 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Render should render component 1`] = `
<div
className="section gf-form-group"
>
<h5
className="page-heading"
>
Value mappings
</h5>
<div>
<MappingRow
key="Ok-0"
mapping={
Object {
"id": 1,
"operator": "",
"text": "Ok",
"type": 1,
"value": "20",
}
}
removeMapping={[Function]}
updateMapping={[Function]}
/>
<MappingRow
key="Meh-1"
mapping={
Object {
"from": "21",
"id": 2,
"operator": "",
"text": "Meh",
"to": "30",
"type": 2,
}
}
removeMapping={[Function]}
updateMapping={[Function]}
/>
</div>
<div
className="add-mapping-row"
onClick={[Function]}
>
<div
className="add-mapping-row-icon"
>
<i
className="fa fa-plus"
/>
</div>
<div
className="add-mapping-row-label"
>
Add mapping
</div>
</div>
</div>
`;

View File

@@ -1,10 +1,19 @@
import React, { PureComponent } from 'react';
import Gauge from 'app/viz/Gauge';
import { NullValueMode, PanelOptionsProps, PanelProps, Threshold } 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;
@@ -15,6 +24,7 @@ export interface OptionsProps {
suffix: string;
unit: string;
thresholds: Threshold[];
mappings: Array<RangeMap | ValueMap>;
}
export interface OptionModuleProps {
@@ -30,6 +40,14 @@ export const defaultProps = {
showThresholdMarkers: true,
showThresholdLabels: false,
suffix: '',
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 },
],
},
};
@@ -52,11 +70,17 @@ class Options extends PureComponent<PanelOptionsProps<OptionsProps>> {
static defaultProps = defaultProps;
render() {
const { onChange, options } = this.props;
return (
<div>
<ValueOptions onChange={this.props.onChange} options={this.props.options} />
<GaugeOptions onChange={this.props.onChange} options={this.props.options} />
<Thresholds onChange={this.props.onChange} options={this.props.options} />
<div className="form-section">
<ValueOptions onChange={onChange} options={options} />
<GaugeOptions onChange={onChange} options={options} />
<Thresholds onChange={onChange} options={options} />
</div>
<div className="form-section">
<ValueMappings onChange={onChange} options={options} />
</div>
</div>
);
}

View File

@@ -1,200 +1,190 @@
<div class="edit-tab-with-sidemenu">
<aside class="edit-sidemenu-aside">
<ul class="edit-sidemenu">
<li ng-repeat="style in editor.panel.styles" ng-class="{active: editor.activeStyleIndex === $index}">
<a ng-click="editor.activeStyleIndex = $index">{{style.pattern || 'New rule'}}</a>
</li>
<li>
<a class="pointer" ng-click="editor.addColumnStyle()">
<i class="fa fa-plus"></i>&nbsp;Add
</a>
</li>
</ul>
</aside>
<div class="edit-tab-content" ng-repeat="style in editor.panel.styles" ng-if="editor.activeStyleIndex === $index">
<div class="section gf-form-group">
<h5 class="section-heading">Options</h5>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label width-13">Apply to columns named</label>
<input type="text" placeholder="Name or regex" class="gf-form-input width-13" ng-model="style.pattern" bs-tooltip="'Specify regex using /my.*regex/ syntax'"
<div class="edit-tab-content" ng-repeat="style in editor.panel.styles">
<p class="column-styles-heading">{{style.pattern || 'New rule'}}</p>
<div class="section gf-form-group">
<h5 class="section-heading">Options</h5>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label width-13">Apply to columns named</label>
<input type="text" placeholder="Name or regex" class="gf-form-input width-13" ng-model="style.pattern" bs-tooltip="'Specify regex using /my.*regex/ syntax'"
bs-typeahead="editor.getColumnNames" ng-blur="editor.render()" data-min-length=0 data-items=100 ng-model-onblur
data-placement="right">
</div>
</div>
<div class="gf-form" ng-if="style.type !== 'hidden'">
<label class="gf-form-label width-13">Column Header</label>
<input type="text" class="gf-form-input width-13" ng-model="style.alias" ng-change="editor.render()" ng-model-onblur placeholder="Override header label">
</div>
<gf-form-switch class="gf-form" label-class="width-13" label="Render value as link" checked="style.link" change="editor.render()"></gf-form-switch>
</div>
<div class="gf-form" ng-if="style.type !== 'hidden'">
<label class="gf-form-label width-13">Column Header</label>
<input type="text" class="gf-form-input width-13" ng-model="style.alias" ng-change="editor.render()" ng-model-onblur placeholder="Override header label">
</div>
<gf-form-switch class="gf-form" label-class="width-13" label="Render value as link" checked="style.link" change="editor.render()"></gf-form-switch>
</div>
<div class="section gf-form-group">
<h5 class="section-heading">Type</h5>
<div class="section gf-form-group">
<h5 class="section-heading">Type</h5>
<div class="gf-form">
<label class="gf-form-label width-11">Type</label>
<div class="gf-form-select-wrapper width-16">
<select class="gf-form-input" ng-model="style.type" ng-options="c.value as c.text for c in editor.columnTypes" ng-change="editor.render()"></select>
</div>
<div class="gf-form">
<label class="gf-form-label width-11">Type</label>
<div class="gf-form-select-wrapper width-16">
<select class="gf-form-input" ng-model="style.type" ng-options="c.value as c.text for c in editor.columnTypes" ng-change="editor.render()"></select>
</div>
<div class="gf-form" ng-if="style.type === 'date'">
<label class="gf-form-label width-11">Date Format</label>
<gf-form-dropdown model="style.dateFormat" css-class="gf-form-input width-16" lookup-text="true"
</div>
<div class="gf-form" ng-if="style.type === 'date'">
<label class="gf-form-label width-11">Date Format</label>
<gf-form-dropdown model="style.dateFormat" css-class="gf-form-input width-16" lookup-text="true"
get-options="editor.dateFormats" on-change="editor.render()" allow-custom="true">
</gf-form-dropdown>
</div>
</gf-form-dropdown>
</div>
<div ng-if="style.type === 'string'">
<gf-form-switch class="gf-form" label-class="width-11" ng-if="style.type === 'string'" label="Sanitize HTML" checked="style.sanitize"
<div ng-if="style.type === 'string'">
<gf-form-switch class="gf-form" label-class="width-11" ng-if="style.type === 'string'" label="Sanitize HTML" checked="style.sanitize"
change="editor.render()"></gf-form-switch>
</div>
<div ng-if="style.type === 'string'">
<gf-form-switch class="gf-form" label-class="width-11" ng-if="style.type === 'string'" label="Preserve Formatting" checked="style.preserveFormat"
</div>
<div ng-if="style.type === 'string'">
<gf-form-switch class="gf-form" label-class="width-11" ng-if="style.type === 'string'" label="Preserve Formatting" checked="style.preserveFormat"
change="editor.render()"></gf-form-switch>
</div>
</div>
<div ng-if="style.type === 'number'">
<div class="gf-form">
<label class="gf-form-label width-11">Unit</label>
<div class="gf-form-dropdown-typeahead width-16" ng-model="style.unit" dropdown-typeahead2="editor.unitFormats" dropdown-typeahead-on-select="editor.setUnitFormat(style, $subItem)"></div>
</div>
<div class="gf-form">
<label class="gf-form-label width-11">Decimals</label>
<input type="number" class="gf-form-input width-4" data-placement="right" ng-model="style.decimals" ng-change="editor.render()"
<div ng-if="style.type === 'number'">
<div class="gf-form">
<label class="gf-form-label width-11">Unit</label>
<div class="gf-form-dropdown-typeahead width-16" ng-model="style.unit" dropdown-typeahead2="editor.unitFormats" dropdown-typeahead-on-select="editor.setUnitFormat(style, $subItem)"></div>
</div>
<div class="gf-form">
<label class="gf-form-label width-11">Decimals</label>
<input type="number" class="gf-form-input width-4" data-placement="right" ng-model="style.decimals" ng-change="editor.render()"
ng-model-onblur>
</div>
</div>
</div>
</div>
<div class="section gf-form-group" ng-if="style.type === 'string'">
<h5 class="section-heading">Value Mappings</h5>
<div class="editor-row">
<div class="gf-form-group">
<div class="gf-form">
<span class="gf-form-label">
Type
</span>
<div class="gf-form-select-wrapper">
<select class="gf-form-input" ng-model="style.mappingType"
<div class="section gf-form-group" ng-if="style.type === 'string'">
<h5 class="section-heading">Value Mappings</h5>
<div class="editor-row">
<div class="gf-form-group">
<div class="gf-form">
<span class="gf-form-label">
Type
</span>
<div class="gf-form-select-wrapper">
<select class="gf-form-input" ng-model="style.mappingType"
ng-options="c.value as c.text for c in editor.mappingTypes" ng-change="editor.render()"></select>
</div>
</div>
<div class="gf-form-group" ng-if="style.mappingType==1">
<div class="gf-form" ng-repeat="map in style.valueMaps">
<span class="gf-form-label">
<i class="fa fa-remove pointer" ng-click="editor.removeValueMap(style, $index)"></i>
</span>
<input type="text" class="gf-form-input max-width-6" ng-model="map.value" placeholder="Value" ng-blur="editor.render()">
<label class="gf-form-label">
<i class="fa fa-arrow-right"></i>
</label>
<input type="text" class="gf-form-input max-width-8" ng-model="map.text" placeholder="Text" ng-blur="editor.render()">
</div>
<div class="gf-form">
<label class="gf-form-label">
<a class="pointer" ng-click="editor.addValueMap(style)"><i class="fa fa-plus"></i></a>
</label>
</div>
</div>
<div class="gf-form-group" ng-if="style.mappingType==2">
<div class="gf-form" ng-repeat="rangeMap in style.rangeMaps">
<span class="gf-form-label">
<i class="fa fa-remove pointer" ng-click="editor.removeRangeMap(style, $index)"></i>
</span>
<span class="gf-form-label">From</span>
<input type="text" ng-model="rangeMap.from" class="gf-form-input max-width-6" ng-blur="editor.render()">
<span class="gf-form-label">To</span>
<input type="text" ng-model="rangeMap.to" class="gf-form-input max-width-6" ng-blur="editor.render()">
<span class="gf-form-label">Text</span>
<input type="text" ng-model="rangeMap.text" class="gf-form-input max-width-8" ng-blur="editor.render()">
</div>
<div class="gf-form">
<label class="gf-form-label">
<a class="pointer" ng-click="editor.addRangeMap(style)"><i class="fa fa-plus"></i></a>
</label>
</div>
</div>
</div>
</div>
</div>
<div class="section gf-form-group" ng-if="['number', 'string'].indexOf(style.type) !== -1">
<h5 class="section-heading">Thresholds</h5>
<div class="gf-form">
<label class="gf-form-label width-8">Thresholds
<tip>Comma separated values</tip>
</label>
<input type="text" class="gf-form-input width-10" ng-model="style.thresholds" placeholder="50,80" ng-blur="editor.render()"
array-join>
</div>
<div class="gf-form">
<label class="gf-form-label width-8">Color Mode</label>
<div class="gf-form-select-wrapper width-10">
<select class="gf-form-input" ng-model="style.colorMode" ng-options="c.value as c.text for c in editor.colorModes" ng-change="editor.render()"></select>
</div>
</div>
<div class="gf-form">
<label class="gf-form-label width-8">Colors</label>
<span class="gf-form-label">
<color-picker color="style.colors[0]" onChange="editor.onColorChange($index, 0)"></color-picker>
</span>
<span class="gf-form-label">
<color-picker color="style.colors[1]" onChange="editor.onColorChange($index, 1)"></color-picker>
</span>
<span class="gf-form-label">
<color-picker color="style.colors[2]" onChange="editor.onColorChange($index, 2)"></color-picker>
</span>
<div class="gf-form-label">
<a class="pointer" ng-click="editor.invertColorOrder($index)">Invert</a>
</div>
</div>
</div>
<div class="section gf-form-group" ng-if="style.link">
<h5 class="section-heading">Link</h5>
<div class="gf-form">
<label class="gf-form-label width-9">
Url
<info-popover mode="right-normal">
<p>Specify an URL (relative or absolute)</p>
<span>
Use special variables to specify cell values:
<br>
<em>${__cell}</em> refers to current cell value
<br>
<em>${__cell_n}</em> refers to Nth column value in current row. Column indexes are started from 0. For instance,
<em>${__cell_1}</em> refers to second column's value.
<br>
<em>${__cell:raw}</em> syntax. By default values are URI encoded. If the value is a complete URL you can disable all encoding using
<div class="gf-form-group" ng-if="style.mappingType==1">
<div class="gf-form" ng-repeat="map in style.valueMaps">
<span class="gf-form-label">
<i class="fa fa-remove pointer" ng-click="editor.removeValueMap(style, $index)"></i>
</span>
</info-popover>
</label>
<input type="text" class="gf-form-input width-29" ng-model="style.linkUrl" ng-blur="editor.render()" ng-model-onblur data-placement="right">
<input type="text" class="gf-form-input max-width-6" ng-model="map.value" placeholder="Value" ng-blur="editor.render()">
<label class="gf-form-label">
<i class="fa fa-arrow-right"></i>
</label>
<input type="text" class="gf-form-input max-width-8" ng-model="map.text" placeholder="Text" ng-blur="editor.render()">
</div>
<div class="gf-form">
<label class="gf-form-label">
<a class="pointer" ng-click="editor.addValueMap(style)"><i class="fa fa-plus"></i></a>
</label>
</div>
</div>
<div class="gf-form-group" ng-if="style.mappingType==2">
<div class="gf-form" ng-repeat="rangeMap in style.rangeMaps">
<span class="gf-form-label">
<i class="fa fa-remove pointer" ng-click="editor.removeRangeMap(style, $index)"></i>
</span>
<span class="gf-form-label">From</span>
<input type="text" ng-model="rangeMap.from" class="gf-form-input max-width-6" ng-blur="editor.render()">
<span class="gf-form-label">To</span>
<input type="text" ng-model="rangeMap.to" class="gf-form-input max-width-6" ng-blur="editor.render()">
<span class="gf-form-label">Text</span>
<input type="text" ng-model="rangeMap.text" class="gf-form-input max-width-8" ng-blur="editor.render()">
</div>
<div class="gf-form">
<label class="gf-form-label">
<a class="pointer" ng-click="editor.addRangeMap(style)"><i class="fa fa-plus"></i></a>
</label>
</div>
</div>
</div>
<div class="gf-form">
<label class="gf-form-label width-9">
Tooltip
<info-popover mode="right-normal">
<p>Specify text for link tooltip.</p>
<span>
</div>
</div>
<div class="section gf-form-group" ng-if="['number', 'string'].indexOf(style.type) !== -1">
<h5 class="section-heading">Thresholds</h5>
<div class="gf-form">
<label class="gf-form-label width-8">Thresholds
<tip>Comma separated values</tip>
</label>
<input type="text" class="gf-form-input width-10" ng-model="style.thresholds" placeholder="50,80" ng-blur="editor.render()"
array-join>
</div>
<div class="gf-form">
<label class="gf-form-label width-8">Color Mode</label>
<div class="gf-form-select-wrapper width-10">
<select class="gf-form-input" ng-model="style.colorMode" ng-options="c.value as c.text for c in editor.colorModes" ng-change="editor.render()"></select>
</div>
</div>
<div class="gf-form">
<label class="gf-form-label width-8">Colors</label>
<span class="gf-form-label">
<color-picker color="style.colors[0]" onChange="editor.onColorChange($index, 0)"></color-picker>
</span>
<span class="gf-form-label">
<color-picker color="style.colors[1]" onChange="editor.onColorChange($index, 1)"></color-picker>
</span>
<span class="gf-form-label">
<color-picker color="style.colors[2]" onChange="editor.onColorChange($index, 2)"></color-picker>
</span>
<div class="gf-form-label">
<a class="pointer" ng-click="editor.invertColorOrder($index)">Invert</a>
</div>
</div>
</div>
<div class="section gf-form-group" ng-if="style.link">
<h5 class="section-heading">Link</h5>
<div class="gf-form">
<label class="gf-form-label width-9">
Url
<info-popover mode="right-normal">
<p>Specify an URL (relative or absolute)</p>
<span>
Use special variables to specify cell values:
<br>
<em>${__cell}</em> refers to current cell value
<br>
<em>${__cell_n}</em> refers to Nth column value in current row. Column indexes are started from 0. For instance,
<em>${__cell_1}</em> refers to second column's value.
<br>
<em>${__cell:raw}</em> syntax. By default values are URI encoded. If the value is a complete URL you can disable all encoding using
</span>
</info-popover>
</label>
<input type="text" class="gf-form-input width-29" ng-model="style.linkUrl" ng-blur="editor.render()" ng-model-onblur data-placement="right">
</div>
<div class="gf-form">
<label class="gf-form-label width-9">
Tooltip
<info-popover mode="right-normal">
<p>Specify text for link tooltip.</p>
<span>
This title appears when user hovers pointer over the cell with link. Use the same variables as for URL.
</span>
</info-popover></label>
<input type="text" class="gf-form-input width-29" ng-model="style.linkTooltip" ng-blur="editor.render()" ng-model-onblur
</span>
</info-popover>
</label>
<input type="text" class="gf-form-input width-29" ng-model="style.linkTooltip" ng-blur="editor.render()" ng-model-onblur
data-placement="right">
</div>
<gf-form-switch class="gf-form" label-class="width-9" label="Open in new tab" checked="style.linkTargetBlank"></gf-form-switch>
</div>
<gf-form-switch class="gf-form" label-class="width-9" label="Open in new tab" checked="style.linkTargetBlank"></gf-form-switch>
</div>
<div class="clearfix"></div>
<div class="clearfix"></div>
<div class="gf-form-group">
<button class="btn btn-danger btn-small" ng-click="editor.removeColumnStyle(style)">
<i class="fa fa-trash"></i> Remove Rule
</button>
<br />
<br />
</div>
</div>
<div class="gf-form-button-row">
<button class="btn btn-inverse" ng-click="editor.addColumnStyle()">
<i class="fa fa-plus"></i>&nbsp;Add column style
</button>
</div>