mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
* disable range control when no min and max * use message text provided by gchaps * use pui-react-tooltip instead of waiting for UI-framework tooltip * disable list control when no terms returned * fix jest tests and add test case disabled controls * fix pui tooltip styling * set disable to true since its inside if check for same value * update jest snapshot
This commit is contained in:
parent
a287eef7d4
commit
ef0cdaad10
16 changed files with 347 additions and 94 deletions
|
@ -0,0 +1,60 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders disabled control with tooltip 1`] = `
|
||||
<div
|
||||
className="kuiVerticalRhythm"
|
||||
data-test-subj="inputControl0"
|
||||
>
|
||||
<label
|
||||
className="kuiLabel kuiVerticalRhythmSmall"
|
||||
htmlFor="controlId"
|
||||
>
|
||||
test control
|
||||
</label>
|
||||
<div
|
||||
className="kuiVerticalRhythmSmall"
|
||||
>
|
||||
<OverlayTrigger
|
||||
display={false}
|
||||
isSticky={false}
|
||||
overlay={
|
||||
<Tooltip
|
||||
className="inputControlDisabledTooltip"
|
||||
isSticky={false}
|
||||
size="auto"
|
||||
visible={true}
|
||||
/>
|
||||
}
|
||||
pin={true}
|
||||
placement="top"
|
||||
theme="dark"
|
||||
trigger="hover"
|
||||
>
|
||||
<div>
|
||||
My Control
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders enabled control 1`] = `
|
||||
<div
|
||||
className="kuiVerticalRhythm"
|
||||
data-test-subj="inputControl0"
|
||||
>
|
||||
<label
|
||||
className="kuiLabel kuiVerticalRhythmSmall"
|
||||
htmlFor="controlId"
|
||||
>
|
||||
test control
|
||||
</label>
|
||||
<div
|
||||
className="kuiVerticalRhythmSmall"
|
||||
>
|
||||
<div>
|
||||
My Control
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -24,6 +24,7 @@ exports[`Apply and Cancel change btns enabled when there are changes 1`] = `
|
|||
Object {
|
||||
"getMultiSelectDelimiter": [Function],
|
||||
"id": "mock-list-control",
|
||||
"isEnabled": [Function],
|
||||
"label": "list control",
|
||||
"options": Object {
|
||||
"multiselect": true,
|
||||
|
@ -119,6 +120,7 @@ exports[`Clear btns enabled when there are values 1`] = `
|
|||
Object {
|
||||
"getMultiSelectDelimiter": [Function],
|
||||
"id": "mock-list-control",
|
||||
"isEnabled": [Function],
|
||||
"label": "list control",
|
||||
"options": Object {
|
||||
"multiselect": true,
|
||||
|
@ -214,6 +216,7 @@ exports[`Renders list control 1`] = `
|
|||
Object {
|
||||
"getMultiSelectDelimiter": [Function],
|
||||
"id": "mock-list-control",
|
||||
"isEnabled": [Function],
|
||||
"label": "list control",
|
||||
"options": Object {
|
||||
"multiselect": true,
|
||||
|
@ -308,6 +311,7 @@ exports[`Renders range control 1`] = `
|
|||
control={
|
||||
Object {
|
||||
"id": "mock-range-control",
|
||||
"isEnabled": [Function],
|
||||
"label": "ragne control",
|
||||
"max": 100,
|
||||
"min": 0,
|
||||
|
|
|
@ -1,7 +1,31 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders ListControl 1`] = `
|
||||
<Component
|
||||
<FormRow
|
||||
control={
|
||||
Object {
|
||||
"getMultiSelectDelimiter": [Function],
|
||||
"id": "mock-list-control",
|
||||
"isEnabled": [Function],
|
||||
"label": "list control",
|
||||
"options": Object {
|
||||
"multiselect": true,
|
||||
"type": "terms",
|
||||
},
|
||||
"selectOptions": Array [
|
||||
Object {
|
||||
"label": "choice1",
|
||||
"value": "choice1",
|
||||
},
|
||||
Object {
|
||||
"label": "choice2",
|
||||
"value": "choice2",
|
||||
},
|
||||
],
|
||||
"type": "list",
|
||||
"value": "",
|
||||
}
|
||||
}
|
||||
controlIndex={0}
|
||||
id="mock-list-control"
|
||||
label="list control"
|
||||
|
@ -66,5 +90,5 @@ exports[`renders ListControl 1`] = `
|
|||
valueKey="value"
|
||||
valueRenderer={[Function]}
|
||||
/>
|
||||
</Component>
|
||||
</FormRow>
|
||||
`;
|
||||
|
|
|
@ -1,65 +1,88 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders RangeControl 1`] = `
|
||||
<Component
|
||||
<FormRow
|
||||
control={
|
||||
Object {
|
||||
"hasValue": [Function],
|
||||
"id": "mock-range-control",
|
||||
"isEnabled": [Function],
|
||||
"label": "range control",
|
||||
"max": 100,
|
||||
"min": 0,
|
||||
"options": Object {
|
||||
"decimalPlaces": 0,
|
||||
"step": 1,
|
||||
},
|
||||
"type": "range",
|
||||
"value": Object {
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
controlIndex={0}
|
||||
id="mock-range-control"
|
||||
label="range control"
|
||||
>
|
||||
<input
|
||||
className="kuiTextInput"
|
||||
id="mock-range-control_min"
|
||||
max={100}
|
||||
min={0}
|
||||
name="min"
|
||||
onChange={[Function]}
|
||||
type="number"
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
className="inputRangeContainer"
|
||||
>
|
||||
<InputRange
|
||||
ariaLabelledby="mock-range-control"
|
||||
classNames={
|
||||
Object {
|
||||
"activeTrack": "input-range__track input-range__track--active",
|
||||
"disabledInputRange": "input-range input-range--disabled",
|
||||
"inputRange": "input-range",
|
||||
"labelContainer": "input-range__label-container",
|
||||
"maxLabel": "input-range__label input-range__label--max",
|
||||
"minLabel": "input-range__label input-range__label--min",
|
||||
"slider": "input-range__slider",
|
||||
"sliderContainer": "input-range__slider-container",
|
||||
"track": "input-range__track input-range__track--background",
|
||||
"valueLabel": "input-range__label input-range__label--value",
|
||||
}
|
||||
}
|
||||
<div>
|
||||
<input
|
||||
className="kuiTextInput"
|
||||
disabled={false}
|
||||
draggableTrack={true}
|
||||
formatLabel={[Function]}
|
||||
maxValue={100}
|
||||
minValue={0}
|
||||
id="mock-range-control_min"
|
||||
max={100}
|
||||
min={0}
|
||||
name="min"
|
||||
onChange={[Function]}
|
||||
onChangeComplete={[Function]}
|
||||
step={1}
|
||||
value={
|
||||
Object {
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
type="number"
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
className="inputRangeContainer"
|
||||
>
|
||||
<InputRange
|
||||
ariaLabelledby="mock-range-control"
|
||||
classNames={
|
||||
Object {
|
||||
"activeTrack": "input-range__track input-range__track--active",
|
||||
"disabledInputRange": "input-range input-range--disabled",
|
||||
"inputRange": "input-range",
|
||||
"labelContainer": "input-range__label-container",
|
||||
"maxLabel": "input-range__label input-range__label--max",
|
||||
"minLabel": "input-range__label input-range__label--min",
|
||||
"slider": "input-range__slider",
|
||||
"sliderContainer": "input-range__slider-container",
|
||||
"track": "input-range__track input-range__track--background",
|
||||
"valueLabel": "input-range__label input-range__label--value",
|
||||
}
|
||||
}
|
||||
}
|
||||
disabled={false}
|
||||
draggableTrack={true}
|
||||
formatLabel={[Function]}
|
||||
maxValue={100}
|
||||
minValue={0}
|
||||
onChange={[Function]}
|
||||
onChangeComplete={[Function]}
|
||||
step={1}
|
||||
value={
|
||||
Object {
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
className="kuiTextInput"
|
||||
disabled={false}
|
||||
id="mock-range-control_max"
|
||||
max={100}
|
||||
min={0}
|
||||
name="max"
|
||||
onChange={[Function]}
|
||||
type="number"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
className="kuiTextInput"
|
||||
id="mock-range-control_max"
|
||||
max={100}
|
||||
min={0}
|
||||
name="max"
|
||||
onChange={[Function]}
|
||||
type="number"
|
||||
value=""
|
||||
/>
|
||||
</Component>
|
||||
</FormRow>
|
||||
`;
|
||||
|
|
|
@ -1,23 +1,40 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { Tooltip } from 'pui-react-tooltip';
|
||||
import { OverlayTrigger } from 'pui-react-overlay-trigger';
|
||||
|
||||
export const FormRow = (props) => (
|
||||
<div
|
||||
className="kuiVerticalRhythm"
|
||||
data-test-subj={'inputControl' + props.controlIndex}
|
||||
>
|
||||
<label className="kuiLabel kuiVerticalRhythmSmall" htmlFor={props.id}>
|
||||
{props.label}
|
||||
</label>
|
||||
<div className="kuiVerticalRhythmSmall">
|
||||
{props.children}
|
||||
export function FormRow(props) {
|
||||
let control = props.children;
|
||||
if (!props.control.isEnabled()) {
|
||||
const tooltip = (
|
||||
<Tooltip className="inputControlDisabledTooltip" >{props.control.disabledReason}</Tooltip>
|
||||
);
|
||||
control = (
|
||||
<OverlayTrigger placement="top" overlay={tooltip}>
|
||||
{control}
|
||||
</OverlayTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="kuiVerticalRhythm"
|
||||
data-test-subj={'inputControl' + props.controlIndex}
|
||||
>
|
||||
<label className="kuiLabel kuiVerticalRhythmSmall" htmlFor={props.id}>
|
||||
{props.label}
|
||||
</label>
|
||||
<div className="kuiVerticalRhythmSmall">
|
||||
{control}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
FormRow.propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
id: PropTypes.string.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
controlIndex: PropTypes.number.isRequired
|
||||
controlIndex: PropTypes.number.isRequired,
|
||||
control: PropTypes.object.isRequired,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import {
|
||||
FormRow,
|
||||
} from './form_row';
|
||||
|
||||
test('renders enabled control', () => {
|
||||
const enabledControl = {
|
||||
id: 'mock-enabled-control',
|
||||
isEnabled: () => { return true; },
|
||||
};
|
||||
const component = shallow(
|
||||
<FormRow
|
||||
label="test control"
|
||||
id="controlId"
|
||||
control={enabledControl}
|
||||
controlIndex={0}
|
||||
>
|
||||
<div>My Control</div>
|
||||
</FormRow>
|
||||
);
|
||||
expect(component).toMatchSnapshot(); // eslint-disable-line
|
||||
});
|
||||
|
||||
test('renders disabled control with tooltip', () => {
|
||||
const disabledControl = {
|
||||
id: 'mock-disabled-control',
|
||||
isEnabled: () => { return false; },
|
||||
};
|
||||
const component = shallow(
|
||||
<FormRow
|
||||
label="test control"
|
||||
id="controlId"
|
||||
control={disabledControl}
|
||||
controlIndex={0}
|
||||
>
|
||||
<div>My Control</div>
|
||||
</FormRow>
|
||||
);
|
||||
expect(component).toMatchSnapshot(); // eslint-disable-line
|
||||
});
|
|
@ -9,6 +9,7 @@ import {
|
|||
|
||||
const mockListControl = {
|
||||
id: 'mock-list-control',
|
||||
isEnabled: () => { return true; },
|
||||
options: {
|
||||
type: 'terms',
|
||||
multiselect: true
|
||||
|
@ -24,6 +25,7 @@ const mockListControl = {
|
|||
};
|
||||
const mockRangeControl = {
|
||||
id: 'mock-range-control',
|
||||
isEnabled: () => { return true; },
|
||||
options: {
|
||||
decimalPlaces: 0,
|
||||
step: 1
|
||||
|
|
|
@ -26,25 +26,43 @@ export class ListControl extends Component {
|
|||
return `${selected.label.substring(0, 23)}...`;
|
||||
}
|
||||
|
||||
renderControl() {
|
||||
if (!this.props.control.isEnabled()) {
|
||||
// react-select clobbers the tooltip, so just returning a disabled input instead
|
||||
return (
|
||||
<input
|
||||
disabled={true}
|
||||
className="kuiTextInput"
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Select
|
||||
className="list-control-react-select"
|
||||
placeholder="Select..."
|
||||
multi={this.props.control.options.multiselect}
|
||||
simpleValue={true}
|
||||
delimiter={this.props.control.getMultiSelectDelimiter()}
|
||||
value={this.props.control.value}
|
||||
options={this.props.control.selectOptions}
|
||||
onChange={this.handleOnChange}
|
||||
valueRenderer={this.truncate}
|
||||
inputProps={{ id: this.props.control.id }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<FormRow
|
||||
id={this.props.control.id}
|
||||
label={this.props.control.label}
|
||||
controlIndex={this.props.controlIndex}
|
||||
control={this.props.control}
|
||||
>
|
||||
<Select
|
||||
className="list-control-react-select"
|
||||
placeholder="Select..."
|
||||
multi={this.props.control.options.multiselect}
|
||||
simpleValue={true}
|
||||
delimiter={this.props.control.getMultiSelectDelimiter()}
|
||||
value={this.props.control.value}
|
||||
options={this.props.control.selectOptions}
|
||||
onChange={this.handleOnChange}
|
||||
valueRenderer={this.truncate}
|
||||
inputProps={{ id: this.props.control.id }}
|
||||
/>
|
||||
{this.renderControl()}
|
||||
</FormRow>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
|
||||
const control = {
|
||||
id: 'mock-list-control',
|
||||
isEnabled: () => { return true; },
|
||||
options: {
|
||||
type: 'terms',
|
||||
multiselect: true
|
||||
|
|
|
@ -79,15 +79,12 @@ export class RangeControl extends Component {
|
|||
return formatedValue;
|
||||
}
|
||||
|
||||
render() {
|
||||
renderControl() {
|
||||
return (
|
||||
<FormRow
|
||||
id={this.props.control.id}
|
||||
label={this.props.control.label}
|
||||
controlIndex={this.props.controlIndex}
|
||||
>
|
||||
<div>
|
||||
<input
|
||||
id={`${this.props.control.id}_min`}
|
||||
disabled={!this.props.control.isEnabled()}
|
||||
name="min"
|
||||
type="number"
|
||||
className="kuiTextInput"
|
||||
|
@ -98,6 +95,7 @@ export class RangeControl extends Component {
|
|||
/>
|
||||
<div className="inputRangeContainer">
|
||||
<InputRange
|
||||
disabled={!this.props.control.isEnabled()}
|
||||
maxValue={this.props.control.max}
|
||||
minValue={this.props.control.min}
|
||||
step={this.props.control.options.step}
|
||||
|
@ -111,6 +109,7 @@ export class RangeControl extends Component {
|
|||
</div>
|
||||
<input
|
||||
id={`${this.props.control.id}_max`}
|
||||
disabled={!this.props.control.isEnabled()}
|
||||
name="max"
|
||||
type="number"
|
||||
className="kuiTextInput"
|
||||
|
@ -119,6 +118,19 @@ export class RangeControl extends Component {
|
|||
max={this.props.control.max}
|
||||
onChange={this.handleInputChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<FormRow
|
||||
id={this.props.control.id}
|
||||
label={this.props.control.label}
|
||||
controlIndex={this.props.controlIndex}
|
||||
control={this.props.control}
|
||||
>
|
||||
{this.renderControl()}
|
||||
</FormRow>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
|
||||
const control = {
|
||||
id: 'mock-range-control',
|
||||
isEnabled: () => { return true; },
|
||||
options: {
|
||||
decimalPlaces: 0,
|
||||
step: 1
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
import _ from 'lodash';
|
||||
|
||||
export function noValuesDisableMsg(fieldName, indexPatternName) {
|
||||
return `Filtering occurs on the "${fieldName}" field,
|
||||
which doesn't exist on any documents in the "${indexPatternName}" index pattern.
|
||||
Choose a different field or index documents that contain values for this field.`;
|
||||
}
|
||||
|
||||
export class Control {
|
||||
constructor(controlParams, filterManager) {
|
||||
this.id = controlParams.id;
|
||||
|
@ -7,10 +13,20 @@ export class Control {
|
|||
this.type = controlParams.type;
|
||||
this.label = controlParams.label ? controlParams.label : controlParams.fieldName;
|
||||
this.filterManager = filterManager;
|
||||
this.enable = true;
|
||||
// restore state from kibana filter context
|
||||
this.reset();
|
||||
}
|
||||
|
||||
isEnabled() {
|
||||
return this.enable;
|
||||
}
|
||||
|
||||
disable(reason) {
|
||||
this.enable = false;
|
||||
this.disabledReason = reason;
|
||||
}
|
||||
|
||||
set(newValue) {
|
||||
this.value = newValue;
|
||||
this._hasChanged = true;
|
||||
|
|
|
@ -10,6 +10,10 @@ export class FilterManager {
|
|||
this.unsetValue = unsetValue;
|
||||
}
|
||||
|
||||
getIndexPattern() {
|
||||
return this.indexPattern;
|
||||
}
|
||||
|
||||
createFilter() {
|
||||
throw new Error('Must implement createFilter.');
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import _ from 'lodash';
|
||||
import { Control } from './control';
|
||||
import {
|
||||
Control,
|
||||
noValuesDisableMsg
|
||||
} from './control';
|
||||
import { PhraseFilterManager } from './filter_manager/phrase_filter_manager';
|
||||
|
||||
const termsAgg = (field, size, direction) => {
|
||||
|
@ -58,12 +61,17 @@ export async function listControlFactory(controlParams, kbnApi) {
|
|||
'desc'));
|
||||
|
||||
const resp = await searchSource.fetch();
|
||||
const termsSelectOptions = _.get(resp, 'aggregations.termsAgg.buckets', []).map((bucket) => {
|
||||
return { label: bucket.key.toString(), value: bucket.key.toString() };
|
||||
});
|
||||
|
||||
return new ListControl(
|
||||
const listControl = new ListControl(
|
||||
controlParams,
|
||||
new PhraseFilterManager(controlParams.id, controlParams.fieldName, indexPattern, kbnApi.queryFilter, listControlDelimiter),
|
||||
_.get(resp, 'aggregations.termsAgg.buckets', []).map((bucket) => {
|
||||
return { label: bucket.key.toString(), value: bucket.key.toString() };
|
||||
})
|
||||
termsSelectOptions
|
||||
);
|
||||
if (termsSelectOptions.length === 0) {
|
||||
listControl.disable(noValuesDisableMsg(controlParams.fieldName, indexPattern.title));
|
||||
}
|
||||
return listControl;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import _ from 'lodash';
|
||||
import { Control } from './control';
|
||||
import {
|
||||
Control,
|
||||
noValuesDisableMsg
|
||||
} from './control';
|
||||
import { RangeFilterManager } from './filter_manager/range_filter_manager';
|
||||
|
||||
const minMaxAgg = (field) => {
|
||||
|
@ -40,13 +43,23 @@ export async function rangeControlFactory(controlParams, kbnApi) {
|
|||
|
||||
const resp = await searchSource.fetch();
|
||||
|
||||
const min = _.get(resp, 'aggregations.minAgg.value');
|
||||
const max = _.get(resp, 'aggregations.maxAgg.value');
|
||||
let minMaxReturnedFromAggregation = true;
|
||||
let min = _.get(resp, 'aggregations.minAgg.value');
|
||||
let max = _.get(resp, 'aggregations.maxAgg.value');
|
||||
if (min === null || max === null) {
|
||||
min = 0;
|
||||
max = 1;
|
||||
minMaxReturnedFromAggregation = false;
|
||||
}
|
||||
const emptyValue = { min: min, max: min };
|
||||
return new RangeControl(
|
||||
const rangeControl = new RangeControl(
|
||||
controlParams,
|
||||
new RangeFilterManager(controlParams.id, controlParams.fieldName, indexPattern, kbnApi.queryFilter, emptyValue),
|
||||
min,
|
||||
max
|
||||
);
|
||||
if (!minMaxReturnedFromAggregation) {
|
||||
rangeControl.disable(noValuesDisableMsg(controlParams.fieldName, indexPattern.title));
|
||||
}
|
||||
return rangeControl;
|
||||
}
|
||||
|
|
|
@ -44,4 +44,12 @@ visualization.input_control_vis {
|
|||
}
|
||||
}
|
||||
|
||||
.inputControlDisabledTooltip {
|
||||
width: 250px;
|
||||
|
||||
.tooltip-content {
|
||||
white-space: normal !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue