[Input controls] Replace react-select with EuiComboBox (#17452)
* replace react-select with EuiComboBox * remove constructor for ListControl component * get working with portal version * remove overflow visible from input_control_vis since its no longer needed * convert index pattern select to EuiComboBox * replace react-select with EuiComboBox for field select * group fields by type * remove esvm * remove on-foxus box around input cursor * fix jest tests * remove broken jest test * fix functional tests * review changes * remove componentWillMount from field_select * update snapshot changed from rebasing and getting new EUI version * use combo box clear and close buttons for clearing and closing * jsdoc syntax fix
This commit is contained in:
parent
147d208e2d
commit
e3841ea7ea
|
@ -4,17 +4,18 @@ exports[`parentCandidates 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<IndexPatternSelect
|
<IndexPatternSelect
|
||||||
controlIndex={0}
|
controlIndex={0}
|
||||||
|
getIndexPattern={[Function]}
|
||||||
getIndexPatterns={[Function]}
|
getIndexPatterns={[Function]}
|
||||||
|
indexPatternId="indexPattern1"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
value="indexPattern1"
|
|
||||||
/>
|
/>
|
||||||
<FieldSelect
|
<FieldSelect
|
||||||
controlIndex={0}
|
controlIndex={0}
|
||||||
|
fieldName="keywordField"
|
||||||
filterField={[Function]}
|
filterField={[Function]}
|
||||||
getIndexPattern={[Function]}
|
getIndexPattern={[Function]}
|
||||||
indexPatternId="indexPattern1"
|
indexPatternId="indexPattern1"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
value="keywordField"
|
|
||||||
/>
|
/>
|
||||||
<EuiFormRow
|
<EuiFormRow
|
||||||
describedByIds={Array []}
|
describedByIds={Array []}
|
||||||
|
@ -83,17 +84,18 @@ exports[`renders ListControlEditor 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<IndexPatternSelect
|
<IndexPatternSelect
|
||||||
controlIndex={0}
|
controlIndex={0}
|
||||||
|
getIndexPattern={[Function]}
|
||||||
getIndexPatterns={[Function]}
|
getIndexPatterns={[Function]}
|
||||||
|
indexPatternId="indexPattern1"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
value="indexPattern1"
|
|
||||||
/>
|
/>
|
||||||
<FieldSelect
|
<FieldSelect
|
||||||
controlIndex={0}
|
controlIndex={0}
|
||||||
|
fieldName="keywordField"
|
||||||
filterField={[Function]}
|
filterField={[Function]}
|
||||||
getIndexPattern={[Function]}
|
getIndexPattern={[Function]}
|
||||||
indexPatternId="indexPattern1"
|
indexPatternId="indexPattern1"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
value="keywordField"
|
|
||||||
/>
|
/>
|
||||||
<EuiFormRow
|
<EuiFormRow
|
||||||
describedByIds={Array []}
|
describedByIds={Array []}
|
||||||
|
|
|
@ -4,17 +4,18 @@ exports[`renders RangeControlEditor 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<IndexPatternSelect
|
<IndexPatternSelect
|
||||||
controlIndex={0}
|
controlIndex={0}
|
||||||
|
getIndexPattern={[Function]}
|
||||||
getIndexPatterns={[Function]}
|
getIndexPatterns={[Function]}
|
||||||
|
indexPatternId="indexPattern1"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
value="indexPattern1"
|
|
||||||
/>
|
/>
|
||||||
<FieldSelect
|
<FieldSelect
|
||||||
controlIndex={0}
|
controlIndex={0}
|
||||||
|
fieldName="numberField"
|
||||||
filterField={[Function]}
|
filterField={[Function]}
|
||||||
getIndexPattern={[Function]}
|
getIndexPattern={[Function]}
|
||||||
indexPatternId="indexPattern1"
|
indexPatternId="indexPattern1"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
value="numberField"
|
|
||||||
/>
|
/>
|
||||||
<EuiFormRow
|
<EuiFormRow
|
||||||
describedByIds={Array []}
|
describedByIds={Array []}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
export const getIndexPatternMock = () => {
|
||||||
|
return Promise.resolve({
|
||||||
|
id: 'mockIndexPattern',
|
||||||
|
title: 'mockIndexPattern',
|
||||||
|
fields: [
|
||||||
|
{ name: 'keywordField', type: 'string', aggregatable: true },
|
||||||
|
{ name: 'textField', type: 'string', aggregatable: false },
|
||||||
|
{ name: 'numberField', type: 'number', aggregatable: true }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,16 @@
|
||||||
|
export const getIndexPatternsMock = () => {
|
||||||
|
return Promise.resolve([
|
||||||
|
{
|
||||||
|
id: 'indexPattern1',
|
||||||
|
attributes: {
|
||||||
|
title: 'indexPattern1'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'indexPattern2',
|
||||||
|
attributes: {
|
||||||
|
title: 'indexPattern2'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
};
|
|
@ -48,16 +48,16 @@ export class ControlsTab extends Component {
|
||||||
this.setVisParam('controls', setControl(this.props.scope.vis.params.controls, controlIndex, updatedControl));
|
this.setVisParam('controls', setControl(this.props.scope.vis.params.controls, controlIndex, updatedControl));
|
||||||
}
|
}
|
||||||
|
|
||||||
handleIndexPatternChange = (controlIndex, evt) => {
|
handleIndexPatternChange = (controlIndex, indexPatternId) => {
|
||||||
const updatedControl = this.props.scope.vis.params.controls[controlIndex];
|
const updatedControl = this.props.scope.vis.params.controls[controlIndex];
|
||||||
updatedControl.indexPattern = evt.value;
|
updatedControl.indexPattern = indexPatternId;
|
||||||
updatedControl.fieldName = '';
|
updatedControl.fieldName = '';
|
||||||
this.setVisParam('controls', setControl(this.props.scope.vis.params.controls, controlIndex, updatedControl));
|
this.setVisParam('controls', setControl(this.props.scope.vis.params.controls, controlIndex, updatedControl));
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFieldNameChange = (controlIndex, evt) => {
|
handleFieldNameChange = (controlIndex, fieldName) => {
|
||||||
const updatedControl = this.props.scope.vis.params.controls[controlIndex];
|
const updatedControl = this.props.scope.vis.params.controls[controlIndex];
|
||||||
updatedControl.fieldName = evt.value;
|
updatedControl.fieldName = fieldName;
|
||||||
this.setVisParam('controls', setControl(this.props.scope.vis.params.controls, controlIndex, updatedControl));
|
this.setVisParam('controls', setControl(this.props.scope.vis.params.controls, controlIndex, updatedControl));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
import { mount, shallow } from 'enzyme';
|
import { mount, shallow } from 'enzyme';
|
||||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||||
|
import { getIndexPatternMock } from './__tests__/get_index_pattern_mock';
|
||||||
import {
|
import {
|
||||||
ControlsTab,
|
ControlsTab,
|
||||||
} from './controls_tab';
|
} from './controls_tab';
|
||||||
|
@ -21,14 +22,7 @@ const savedObjectsClientMock = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const indexPatternsMock = {
|
const indexPatternsMock = {
|
||||||
get: () => {
|
get: getIndexPatternMock
|
||||||
return Promise.resolve({
|
|
||||||
fields: [
|
|
||||||
{ name: 'keywordField', type: 'string', aggregatable: true },
|
|
||||||
{ name: 'numberField', type: 'number', aggregatable: true }
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
const scopeMock = {
|
const scopeMock = {
|
||||||
vis: {
|
vis: {
|
||||||
|
|
|
@ -1,60 +1,102 @@
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import Select from 'react-select';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
EuiFormRow,
|
EuiFormRow,
|
||||||
|
EuiComboBox,
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
|
|
||||||
export class FieldSelect extends Component {
|
export class FieldSelect extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
// not storing activeIndexPatternId in react state
|
this._hasUnmounted = false;
|
||||||
// 1) does not effect rendering
|
|
||||||
// 2) requires synchronous modification to avoid race condition
|
|
||||||
this.activeIndexPatternId = props.indexPatternId;
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
fields: []
|
isLoading: false,
|
||||||
|
fields: [],
|
||||||
|
indexPatternId: props.indexPatternId,
|
||||||
};
|
};
|
||||||
this.filterField = _.get(props, 'filterField', () => { return true; });
|
this.filterField = _.get(props, 'filterField', () => { return true; });
|
||||||
this.loadFields(props.indexPatternId);
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this._hasUnmounted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.loadFields(this.state.indexPatternId);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
if (this.props.indexPatternId !== nextProps.indexPatternId) {
|
if (this.props.indexPatternId !== nextProps.indexPatternId) {
|
||||||
this.activeIndexPatternId = nextProps.indexPatternId;
|
|
||||||
this.setState({ fields: [] });
|
|
||||||
this.loadFields(nextProps.indexPatternId);
|
this.loadFields(nextProps.indexPatternId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadFields(indexPatternId) {
|
loadFields = (indexPatternId) => {
|
||||||
|
this.setState({
|
||||||
|
isLoading: true,
|
||||||
|
fields: [],
|
||||||
|
indexPatternId
|
||||||
|
}, this.debouncedLoad.bind(null, indexPatternId));
|
||||||
|
}
|
||||||
|
|
||||||
|
debouncedLoad = _.debounce(async (indexPatternId) => {
|
||||||
if (!indexPatternId || indexPatternId.length === 0) {
|
if (!indexPatternId || indexPatternId.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const indexPattern = await this.props.getIndexPattern(indexPatternId);
|
const indexPattern = await this.props.getIndexPattern(indexPatternId);
|
||||||
|
|
||||||
// props.indexPatternId may be updated before getIndexPattern returns
|
if (this._hasUnmounted) {
|
||||||
// ignore response when fetched index pattern does not match active index pattern
|
|
||||||
if (indexPattern.id !== this.activeIndexPatternId) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fields = indexPattern.fields
|
// props.indexPatternId may be updated before getIndexPattern returns
|
||||||
|
// ignore response when fetched index pattern does not match active index pattern
|
||||||
|
if (indexPattern.id !== this.state.indexPatternId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldsByTypeMap = new Map();
|
||||||
|
const fields = [];
|
||||||
|
indexPattern.fields
|
||||||
.filter(this.filterField)
|
.filter(this.filterField)
|
||||||
.sort((a, b) => {
|
.forEach(field => {
|
||||||
if (a.name < b.name) return -1;
|
if (fieldsByTypeMap.has(field.type)) {
|
||||||
if (a.name > b.name) return 1;
|
const fieldsList = fieldsByTypeMap.get(field.type);
|
||||||
return 0;
|
fieldsList.push(field.name);
|
||||||
})
|
fieldsByTypeMap.set(field.type, fieldsList);
|
||||||
.map(function (field) {
|
} else {
|
||||||
return { label: field.name, value: field.name };
|
fieldsByTypeMap.set(field.type, [field.name]);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
this.setState({ fields: fields });
|
|
||||||
|
fieldsByTypeMap.forEach((fieldsList, fieldType) => {
|
||||||
|
fields.push({
|
||||||
|
label: fieldType,
|
||||||
|
options: fieldsList.sort().map(fieldName => {
|
||||||
|
return { value: fieldName, label: fieldName };
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
fields.sort((a, b) => {
|
||||||
|
if (a.label < b.label) return -1;
|
||||||
|
if (a.label > b.label) return 1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
isLoading: false,
|
||||||
|
fields: fields
|
||||||
|
});
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
onChange = (selectedOptions) => {
|
||||||
|
this.props.onChange(_.get(selectedOptions, '0.value'));
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -63,19 +105,25 @@ export class FieldSelect extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectId = `fieldSelect-${this.props.controlIndex}`;
|
const selectId = `fieldSelect-${this.props.controlIndex}`;
|
||||||
|
|
||||||
|
const selectedOptions = [];
|
||||||
|
if (this.props.fieldName) {
|
||||||
|
selectedOptions.push({ value: this.props.fieldName, label: this.props.fieldName });
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EuiFormRow
|
<EuiFormRow
|
||||||
id={selectId}
|
id={selectId}
|
||||||
label="Field"
|
label="Field"
|
||||||
>
|
>
|
||||||
<Select
|
<EuiComboBox
|
||||||
className="field-react-select"
|
|
||||||
placeholder="Select field..."
|
placeholder="Select field..."
|
||||||
value={this.props.value}
|
singleSelection={true}
|
||||||
|
isLoading={this.state.isLoading}
|
||||||
options={this.state.fields}
|
options={this.state.fields}
|
||||||
onChange={this.props.onChange}
|
selectedOptions={selectedOptions}
|
||||||
resetValue={''}
|
onChange={this.onChange}
|
||||||
inputProps={{ id: selectId }}
|
data-test-subj={selectId}
|
||||||
/>
|
/>
|
||||||
</EuiFormRow>
|
</EuiFormRow>
|
||||||
);
|
);
|
||||||
|
@ -86,7 +134,7 @@ FieldSelect.propTypes = {
|
||||||
getIndexPattern: PropTypes.func.isRequired,
|
getIndexPattern: PropTypes.func.isRequired,
|
||||||
indexPatternId: PropTypes.string,
|
indexPatternId: PropTypes.string,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
value: PropTypes.string,
|
fieldName: PropTypes.string,
|
||||||
filterField: PropTypes.func,
|
filterField: PropTypes.func,
|
||||||
controlIndex: PropTypes.number.isRequired,
|
controlIndex: PropTypes.number.isRequired,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,45 +1,123 @@
|
||||||
|
import _ from 'lodash';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import Select from 'react-select';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
EuiFormRow,
|
EuiFormRow,
|
||||||
|
EuiComboBox,
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
|
|
||||||
export class IndexPatternSelect extends Component {
|
export class IndexPatternSelect extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.loadOptions = this.loadOptions.bind(this);
|
this.state = {
|
||||||
|
isLoading: false,
|
||||||
|
options: [],
|
||||||
|
selectedIndexPattern: undefined,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
loadOptions(input, callback) {
|
componentWillMount() {
|
||||||
this.props.getIndexPatterns(input).then((indexPatternSavedObjects) => {
|
this._isMounted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this._isMounted = false;
|
||||||
|
this.debouncedFetch.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.fetchOptions();
|
||||||
|
this.fetchSelectedIndexPattern(this.props.indexPatternId);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
if (nextProps.indexPatternId !== this.props.indexPatternId) {
|
||||||
|
this.fetchSelectedIndexPattern(nextProps.indexPatternId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchSelectedIndexPattern = async (indexPatternId) => {
|
||||||
|
if (!indexPatternId) {
|
||||||
|
this.setState({
|
||||||
|
selectedIndexPattern: undefined
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const indexPattern = await this.props.getIndexPattern(indexPatternId);
|
||||||
|
|
||||||
|
if (!this._isMounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!indexPattern) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
selectedIndexPattern: {
|
||||||
|
value: indexPattern.id,
|
||||||
|
label: indexPattern.title,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
debouncedFetch = _.debounce(async (searchValue) => {
|
||||||
|
const indexPatternSavedObjects = await this.props.getIndexPatterns(searchValue);
|
||||||
|
|
||||||
|
if (!this._isMounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need this check to handle the case where search results come back in a different
|
||||||
|
// order than they were sent out. Only load results for the most recent search.
|
||||||
|
if (searchValue === this.state.searchValue) {
|
||||||
const options = indexPatternSavedObjects.map((indexPatternSavedObject) => {
|
const options = indexPatternSavedObjects.map((indexPatternSavedObject) => {
|
||||||
return {
|
return {
|
||||||
label: indexPatternSavedObject.attributes.title,
|
label: indexPatternSavedObject.attributes.title,
|
||||||
value: indexPatternSavedObject.id
|
value: indexPatternSavedObject.id
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
callback(null, { options: options });
|
this.setState({
|
||||||
});
|
isLoading: false,
|
||||||
|
options,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
fetchOptions = (searchValue = '') => {
|
||||||
|
this.setState({
|
||||||
|
isLoading: true,
|
||||||
|
searchValue
|
||||||
|
}, this.debouncedFetch.bind(null, searchValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange = (selectedOptions) => {
|
||||||
|
this.props.onChange(_.get(selectedOptions, '0.value'));
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const selectId = `indexPatternSelect-${this.props.controlIndex}`;
|
const selectId = `indexPatternSelect-${this.props.controlIndex}`;
|
||||||
|
const selectedOptions = [];
|
||||||
|
if (this.state.selectedIndexPattern) {
|
||||||
|
selectedOptions.push(this.state.selectedIndexPattern);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<EuiFormRow
|
<EuiFormRow
|
||||||
id={selectId}
|
id={selectId}
|
||||||
label="Index Pattern"
|
label="Index Pattern"
|
||||||
>
|
>
|
||||||
<Select.Async
|
<EuiComboBox
|
||||||
className="index-pattern-react-select"
|
|
||||||
placeholder="Select index pattern..."
|
placeholder="Select index pattern..."
|
||||||
value={this.props.value}
|
singleSelection={true}
|
||||||
loadOptions={this.loadOptions}
|
isLoading={this.state.isLoading}
|
||||||
onChange={this.props.onChange}
|
onSearchChange={this.fetchOptions}
|
||||||
resetValue={''}
|
options={this.state.options}
|
||||||
inputProps={{ id: selectId }}
|
selectedOptions={selectedOptions}
|
||||||
|
onChange={this.onChange}
|
||||||
|
data-test-subj={selectId}
|
||||||
/>
|
/>
|
||||||
</EuiFormRow>
|
</EuiFormRow>
|
||||||
);
|
);
|
||||||
|
@ -48,7 +126,8 @@ export class IndexPatternSelect extends Component {
|
||||||
|
|
||||||
IndexPatternSelect.propTypes = {
|
IndexPatternSelect.propTypes = {
|
||||||
getIndexPatterns: PropTypes.func.isRequired,
|
getIndexPatterns: PropTypes.func.isRequired,
|
||||||
|
getIndexPattern: PropTypes.func.isRequired,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
value: PropTypes.string,
|
indexPatternId: PropTypes.string,
|
||||||
controlIndex: PropTypes.number.isRequired,
|
controlIndex: PropTypes.number.isRequired,
|
||||||
};
|
};
|
||||||
|
|
|
@ -52,14 +52,15 @@ export function ListControlEditor(props) {
|
||||||
<div>
|
<div>
|
||||||
|
|
||||||
<IndexPatternSelect
|
<IndexPatternSelect
|
||||||
value={props.controlParams.indexPattern}
|
indexPatternId={props.controlParams.indexPattern}
|
||||||
onChange={props.handleIndexPatternChange}
|
onChange={props.handleIndexPatternChange}
|
||||||
getIndexPatterns={props.getIndexPatterns}
|
getIndexPatterns={props.getIndexPatterns}
|
||||||
|
getIndexPattern={props.getIndexPattern}
|
||||||
controlIndex={props.controlIndex}
|
controlIndex={props.controlIndex}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldSelect
|
<FieldSelect
|
||||||
value={props.controlParams.fieldName}
|
fieldName={props.controlParams.fieldName}
|
||||||
indexPatternId={props.controlParams.indexPattern}
|
indexPatternId={props.controlParams.indexPattern}
|
||||||
filterField={filterField}
|
filterField={filterField}
|
||||||
onChange={props.handleFieldNameChange}
|
onChange={props.handleFieldNameChange}
|
||||||
|
|
|
@ -2,36 +2,13 @@ import React from 'react';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
import { mount, shallow } from 'enzyme';
|
import { mount, shallow } from 'enzyme';
|
||||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||||
|
import { getIndexPatternMock } from './__tests__/get_index_pattern_mock';
|
||||||
|
import { getIndexPatternsMock } from './__tests__/get_index_patterns_mock';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ListControlEditor,
|
ListControlEditor,
|
||||||
} from './list_control_editor';
|
} from './list_control_editor';
|
||||||
|
|
||||||
const getIndexPatterns = () => {
|
|
||||||
return Promise.resolve([
|
|
||||||
{
|
|
||||||
id: 'indexPattern1',
|
|
||||||
attributes: {
|
|
||||||
title: 'indexPattern1'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'indexPattern2',
|
|
||||||
attributes: {
|
|
||||||
title: 'indexPattern2'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
const getIndexPattern = () => {
|
|
||||||
return Promise.resolve({
|
|
||||||
fields: [
|
|
||||||
{ name: 'keywordField', type: 'string', aggregatable: true },
|
|
||||||
{ name: 'textField', type: 'string', aggregatable: false },
|
|
||||||
{ name: 'numberField', type: 'number', aggregatable: true }
|
|
||||||
]
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const controlParams = {
|
const controlParams = {
|
||||||
id: '1',
|
id: '1',
|
||||||
indexPattern: 'indexPattern1',
|
indexPattern: 'indexPattern1',
|
||||||
|
@ -58,8 +35,8 @@ beforeEach(() => {
|
||||||
|
|
||||||
test('renders ListControlEditor', () => {
|
test('renders ListControlEditor', () => {
|
||||||
const component = shallow(<ListControlEditor
|
const component = shallow(<ListControlEditor
|
||||||
getIndexPatterns={getIndexPatterns}
|
getIndexPatterns={getIndexPatternsMock}
|
||||||
getIndexPattern={getIndexPattern}
|
getIndexPattern={getIndexPatternMock}
|
||||||
controlIndex={0}
|
controlIndex={0}
|
||||||
controlParams={controlParams}
|
controlParams={controlParams}
|
||||||
handleFieldNameChange={handleFieldNameChange}
|
handleFieldNameChange={handleFieldNameChange}
|
||||||
|
@ -78,8 +55,8 @@ test('parentCandidates', () => {
|
||||||
{ value: '2', text: 'fieldB' }
|
{ value: '2', text: 'fieldB' }
|
||||||
];
|
];
|
||||||
const component = shallow(<ListControlEditor
|
const component = shallow(<ListControlEditor
|
||||||
getIndexPatterns={getIndexPatterns}
|
getIndexPatterns={getIndexPatternsMock}
|
||||||
getIndexPattern={getIndexPattern}
|
getIndexPattern={getIndexPatternMock}
|
||||||
controlIndex={0}
|
controlIndex={0}
|
||||||
controlParams={controlParams}
|
controlParams={controlParams}
|
||||||
handleFieldNameChange={handleFieldNameChange}
|
handleFieldNameChange={handleFieldNameChange}
|
||||||
|
@ -94,8 +71,8 @@ test('parentCandidates', () => {
|
||||||
|
|
||||||
test('handleCheckboxOptionChange - multiselect', () => {
|
test('handleCheckboxOptionChange - multiselect', () => {
|
||||||
const component = mount(<ListControlEditor
|
const component = mount(<ListControlEditor
|
||||||
getIndexPatterns={getIndexPatterns}
|
getIndexPatterns={getIndexPatternsMock}
|
||||||
getIndexPattern={getIndexPattern}
|
getIndexPattern={getIndexPatternMock}
|
||||||
controlIndex={0}
|
controlIndex={0}
|
||||||
controlParams={controlParams}
|
controlParams={controlParams}
|
||||||
handleFieldNameChange={handleFieldNameChange}
|
handleFieldNameChange={handleFieldNameChange}
|
||||||
|
@ -126,8 +103,8 @@ test('handleCheckboxOptionChange - multiselect', () => {
|
||||||
|
|
||||||
test('handleNumberOptionChange - size', () => {
|
test('handleNumberOptionChange - size', () => {
|
||||||
const component = mount(<ListControlEditor
|
const component = mount(<ListControlEditor
|
||||||
getIndexPatterns={getIndexPatterns}
|
getIndexPatterns={getIndexPatternsMock}
|
||||||
getIndexPattern={getIndexPattern}
|
getIndexPattern={getIndexPatternMock}
|
||||||
controlIndex={0}
|
controlIndex={0}
|
||||||
controlParams={controlParams}
|
controlParams={controlParams}
|
||||||
handleFieldNameChange={handleFieldNameChange}
|
handleFieldNameChange={handleFieldNameChange}
|
||||||
|
|
|
@ -25,14 +25,15 @@ export function RangeControlEditor(props) {
|
||||||
<div>
|
<div>
|
||||||
|
|
||||||
<IndexPatternSelect
|
<IndexPatternSelect
|
||||||
value={props.controlParams.indexPattern}
|
indexPatternId={props.controlParams.indexPattern}
|
||||||
onChange={props.handleIndexPatternChange}
|
onChange={props.handleIndexPatternChange}
|
||||||
getIndexPatterns={props.getIndexPatterns}
|
getIndexPatterns={props.getIndexPatterns}
|
||||||
|
getIndexPattern={props.getIndexPattern}
|
||||||
controlIndex={props.controlIndex}
|
controlIndex={props.controlIndex}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldSelect
|
<FieldSelect
|
||||||
value={props.controlParams.fieldName}
|
fieldName={props.controlParams.fieldName}
|
||||||
indexPatternId={props.controlParams.indexPattern}
|
indexPatternId={props.controlParams.indexPattern}
|
||||||
filterField={filterField}
|
filterField={filterField}
|
||||||
onChange={props.handleFieldNameChange}
|
onChange={props.handleFieldNameChange}
|
||||||
|
|
|
@ -2,36 +2,13 @@ import React from 'react';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
import { mount, shallow } from 'enzyme';
|
import { mount, shallow } from 'enzyme';
|
||||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||||
|
import { getIndexPatternMock } from './__tests__/get_index_pattern_mock';
|
||||||
|
import { getIndexPatternsMock } from './__tests__/get_index_patterns_mock';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
RangeControlEditor,
|
RangeControlEditor,
|
||||||
} from './range_control_editor';
|
} from './range_control_editor';
|
||||||
|
|
||||||
const getIndexPatterns = () => {
|
|
||||||
return Promise.resolve([
|
|
||||||
{
|
|
||||||
id: 'indexPattern1',
|
|
||||||
attributes: {
|
|
||||||
title: 'indexPattern1'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'indexPattern2',
|
|
||||||
attributes: {
|
|
||||||
title: 'indexPattern2'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
const getIndexPattern = () => {
|
|
||||||
return Promise.resolve({
|
|
||||||
fields: [
|
|
||||||
{ name: 'keywordField', type: 'string', aggregatable: true },
|
|
||||||
{ name: 'textField', type: 'string', aggregatable: false },
|
|
||||||
{ name: 'numberField', type: 'number', aggregatable: true }
|
|
||||||
]
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const controlParams = {
|
const controlParams = {
|
||||||
id: '1',
|
id: '1',
|
||||||
indexPattern: 'indexPattern1',
|
indexPattern: 'indexPattern1',
|
||||||
|
@ -55,8 +32,8 @@ beforeEach(() => {
|
||||||
|
|
||||||
test('renders RangeControlEditor', () => {
|
test('renders RangeControlEditor', () => {
|
||||||
const component = shallow(<RangeControlEditor
|
const component = shallow(<RangeControlEditor
|
||||||
getIndexPatterns={getIndexPatterns}
|
getIndexPatterns={getIndexPatternsMock}
|
||||||
getIndexPattern={getIndexPattern}
|
getIndexPattern={getIndexPatternMock}
|
||||||
controlIndex={0}
|
controlIndex={0}
|
||||||
controlParams={controlParams}
|
controlParams={controlParams}
|
||||||
handleFieldNameChange={handleFieldNameChange}
|
handleFieldNameChange={handleFieldNameChange}
|
||||||
|
@ -68,8 +45,8 @@ test('renders RangeControlEditor', () => {
|
||||||
|
|
||||||
test('handleNumberOptionChange - step', () => {
|
test('handleNumberOptionChange - step', () => {
|
||||||
const component = mount(<RangeControlEditor
|
const component = mount(<RangeControlEditor
|
||||||
getIndexPatterns={getIndexPatterns}
|
getIndexPatterns={getIndexPatternsMock}
|
||||||
getIndexPattern={getIndexPattern}
|
getIndexPattern={getIndexPatternMock}
|
||||||
controlIndex={0}
|
controlIndex={0}
|
||||||
controlParams={controlParams}
|
controlParams={controlParams}
|
||||||
handleFieldNameChange={handleFieldNameChange}
|
handleFieldNameChange={handleFieldNameChange}
|
||||||
|
@ -95,8 +72,8 @@ test('handleNumberOptionChange - step', () => {
|
||||||
|
|
||||||
test('handleNumberOptionChange - decimalPlaces', () => {
|
test('handleNumberOptionChange - decimalPlaces', () => {
|
||||||
const component = mount(<RangeControlEditor
|
const component = mount(<RangeControlEditor
|
||||||
getIndexPatterns={getIndexPatterns}
|
getIndexPatterns={getIndexPatternsMock}
|
||||||
getIndexPattern={getIndexPattern}
|
getIndexPattern={getIndexPatternMock}
|
||||||
controlIndex={0}
|
controlIndex={0}
|
||||||
controlParams={controlParams}
|
controlParams={controlParams}
|
||||||
handleFieldNameChange={handleFieldNameChange}
|
handleFieldNameChange={handleFieldNameChange}
|
||||||
|
|
|
@ -25,31 +25,24 @@ exports[`Apply and Cancel change btns enabled when there are changes 1`] = `
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ListControl
|
<ListControl
|
||||||
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}
|
controlIndex={0}
|
||||||
|
disableMsg={null}
|
||||||
|
id="mock-list-control"
|
||||||
|
label="list control"
|
||||||
|
multiselect={true}
|
||||||
|
options={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"label": "choice1",
|
||||||
|
"value": "choice1",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"label": "choice2",
|
||||||
|
"value": "choice2",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
selectedOptions={Array []}
|
||||||
stageFilter={[Function]}
|
stageFilter={[Function]}
|
||||||
/>
|
/>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
|
@ -158,31 +151,24 @@ exports[`Clear btns enabled when there are values 1`] = `
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ListControl
|
<ListControl
|
||||||
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}
|
controlIndex={0}
|
||||||
|
disableMsg={null}
|
||||||
|
id="mock-list-control"
|
||||||
|
label="list control"
|
||||||
|
multiselect={true}
|
||||||
|
options={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"label": "choice1",
|
||||||
|
"value": "choice1",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"label": "choice2",
|
||||||
|
"value": "choice2",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
selectedOptions={Array []}
|
||||||
stageFilter={[Function]}
|
stageFilter={[Function]}
|
||||||
/>
|
/>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
|
@ -291,31 +277,24 @@ exports[`Renders list control 1`] = `
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ListControl
|
<ListControl
|
||||||
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}
|
controlIndex={0}
|
||||||
|
disableMsg={null}
|
||||||
|
id="mock-list-control"
|
||||||
|
label="list control"
|
||||||
|
multiselect={true}
|
||||||
|
options={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"label": "choice1",
|
||||||
|
"value": "choice1",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"label": "choice2",
|
||||||
|
"value": "choice2",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
selectedOptions={Array []}
|
||||||
stageFilter={[Function]}
|
stageFilter={[Function]}
|
||||||
/>
|
/>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
|
|
|
@ -2,98 +2,31 @@
|
||||||
|
|
||||||
exports[`renders ListControl 1`] = `
|
exports[`renders ListControl 1`] = `
|
||||||
<FormRow
|
<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}
|
controlIndex={0}
|
||||||
id="mock-list-control"
|
id="mock-list-control"
|
||||||
label="list control"
|
label="list control"
|
||||||
>
|
>
|
||||||
<Select
|
<EuiComboBox
|
||||||
arrowRenderer={[Function]}
|
data-test-subj="listControlSelect0"
|
||||||
autosize={true}
|
isClearable={true}
|
||||||
backspaceRemoves={true}
|
|
||||||
backspaceToRemoveMessage="Press backspace to remove {label}"
|
|
||||||
className="list-control-react-select"
|
|
||||||
clearAllText="Clear all"
|
|
||||||
clearRenderer={[Function]}
|
|
||||||
clearValueText="Clear value"
|
|
||||||
clearable={true}
|
|
||||||
closeOnSelect={true}
|
|
||||||
deleteRemoves={true}
|
|
||||||
delimiter=","
|
|
||||||
disabled={false}
|
|
||||||
escapeClearsValue={true}
|
|
||||||
filterOptions={[Function]}
|
|
||||||
ignoreAccents={true}
|
|
||||||
ignoreCase={true}
|
|
||||||
inputProps={
|
|
||||||
Object {
|
|
||||||
"id": "mock-list-control",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isLoading={false}
|
|
||||||
joinValues={false}
|
|
||||||
labelKey="label"
|
|
||||||
matchPos="any"
|
|
||||||
matchProp="any"
|
|
||||||
menuBuffer={0}
|
|
||||||
menuRenderer={[Function]}
|
|
||||||
multi={true}
|
|
||||||
noResultsText="No results found"
|
|
||||||
onBlurResetsInput={true}
|
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
onCloseResetsInput={true}
|
|
||||||
onSelectResetsInput={true}
|
|
||||||
openOnClick={true}
|
|
||||||
optionComponent={[Function]}
|
|
||||||
options={
|
options={
|
||||||
Array [
|
Array [
|
||||||
Object {
|
Object {
|
||||||
|
"data-test-subj": "option_choice1",
|
||||||
"label": "choice1",
|
"label": "choice1",
|
||||||
"value": "choice1",
|
"value": "choice1",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
|
"data-test-subj": "option_choice2",
|
||||||
"label": "choice2",
|
"label": "choice2",
|
||||||
"value": "choice2",
|
"value": "choice2",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
pageSize={5}
|
|
||||||
placeholder="Select..."
|
placeholder="Select..."
|
||||||
removeSelected={true}
|
selectedOptions={Array []}
|
||||||
required={false}
|
singleSelection={false}
|
||||||
rtl={false}
|
|
||||||
scrollMenuIntoView={true}
|
|
||||||
searchable={true}
|
|
||||||
simpleValue={true}
|
|
||||||
tabSelectsValue={true}
|
|
||||||
trimFilter={true}
|
|
||||||
value=""
|
|
||||||
valueComponent={[Function]}
|
|
||||||
valueKey="value"
|
|
||||||
valueRenderer={[Function]}
|
|
||||||
/>
|
/>
|
||||||
</FormRow>
|
</FormRow>
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -2,26 +2,8 @@
|
||||||
|
|
||||||
exports[`renders RangeControl 1`] = `
|
exports[`renders RangeControl 1`] = `
|
||||||
<FormRow
|
<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}
|
controlIndex={0}
|
||||||
|
disableMsg={null}
|
||||||
id="mock-range-control"
|
id="mock-range-control"
|
||||||
label="range control"
|
label="range control"
|
||||||
>
|
>
|
||||||
|
|
|
@ -8,9 +8,9 @@ import {
|
||||||
|
|
||||||
export function FormRow(props) {
|
export function FormRow(props) {
|
||||||
let control = props.children;
|
let control = props.children;
|
||||||
if (!props.control.isEnabled()) {
|
if (props.disableMsg) {
|
||||||
control = (
|
control = (
|
||||||
<EuiToolTip placement="top" content={props.control.disabledReason}>
|
<EuiToolTip placement="top" content={props.disableMsg}>
|
||||||
{control}
|
{control}
|
||||||
</EuiToolTip>
|
</EuiToolTip>
|
||||||
);
|
);
|
||||||
|
@ -32,5 +32,5 @@ FormRow.propTypes = {
|
||||||
id: PropTypes.string.isRequired,
|
id: PropTypes.string.isRequired,
|
||||||
children: PropTypes.node.isRequired,
|
children: PropTypes.node.isRequired,
|
||||||
controlIndex: PropTypes.number.isRequired,
|
controlIndex: PropTypes.number.isRequired,
|
||||||
control: PropTypes.object.isRequired,
|
disableMsg: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,15 +6,10 @@ import {
|
||||||
} from './form_row';
|
} from './form_row';
|
||||||
|
|
||||||
test('renders enabled control', () => {
|
test('renders enabled control', () => {
|
||||||
const enabledControl = {
|
|
||||||
id: 'mock-enabled-control',
|
|
||||||
isEnabled: () => { return true; },
|
|
||||||
};
|
|
||||||
const component = shallow(
|
const component = shallow(
|
||||||
<FormRow
|
<FormRow
|
||||||
label="test control"
|
label="test control"
|
||||||
id="controlId"
|
id="controlId"
|
||||||
control={enabledControl}
|
|
||||||
controlIndex={0}
|
controlIndex={0}
|
||||||
>
|
>
|
||||||
<div>My Control</div>
|
<div>My Control</div>
|
||||||
|
@ -24,16 +19,11 @@ test('renders enabled control', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('renders disabled control with tooltip', () => {
|
test('renders disabled control with tooltip', () => {
|
||||||
const disabledControl = {
|
|
||||||
id: 'mock-disabled-control',
|
|
||||||
isEnabled: () => { return false; },
|
|
||||||
disabledReason: 'I am disabled for testing purposes'
|
|
||||||
};
|
|
||||||
const component = shallow(
|
const component = shallow(
|
||||||
<FormRow
|
<FormRow
|
||||||
label="test control"
|
label="test control"
|
||||||
id="controlId"
|
id="controlId"
|
||||||
control={disabledControl}
|
disableMsg="I am disabled for testing purposes"
|
||||||
controlIndex={0}
|
controlIndex={0}
|
||||||
>
|
>
|
||||||
<div>My Control</div>
|
<div>My Control</div>
|
||||||
|
|
|
@ -38,7 +38,12 @@ export class InputControlVis extends Component {
|
||||||
case 'list':
|
case 'list':
|
||||||
controlComponent = (
|
controlComponent = (
|
||||||
<ListControl
|
<ListControl
|
||||||
control={control}
|
id={control.id}
|
||||||
|
label={control.label}
|
||||||
|
options={control.selectOptions}
|
||||||
|
selectedOptions={control.value}
|
||||||
|
disableMsg={control.isEnabled() ? null : control.disabledReason}
|
||||||
|
multiselect={control.options.multiselect}
|
||||||
controlIndex={index}
|
controlIndex={index}
|
||||||
stageFilter={this.props.stageFilter}
|
stageFilter={this.props.stageFilter}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -16,8 +16,7 @@ const mockListControl = {
|
||||||
},
|
},
|
||||||
type: 'list',
|
type: 'list',
|
||||||
label: 'list control',
|
label: 'list control',
|
||||||
value: '',
|
value: [],
|
||||||
getMultiSelectDelimiter: () => { return ','; },
|
|
||||||
selectOptions: [
|
selectOptions: [
|
||||||
{ label: 'choice1', value: 'choice1' },
|
{ label: 'choice1', value: 'choice1' },
|
||||||
{ label: 'choice2', value: 'choice2' }
|
{ label: 'choice2', value: 'choice2' }
|
||||||
|
@ -159,28 +158,3 @@ test('resetControls', () => {
|
||||||
sinon.assert.notCalled(submitFilters);
|
sinon.assert.notCalled(submitFilters);
|
||||||
sinon.assert.notCalled(stageFilter);
|
sinon.assert.notCalled(stageFilter);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('stageFilter list control', () => {
|
|
||||||
const component = mount(<InputControlVis
|
|
||||||
stageFilter={stageFilter}
|
|
||||||
submitFilters={submitFilters}
|
|
||||||
resetControls={resetControls}
|
|
||||||
clearControls={clearControls}
|
|
||||||
controls={[mockListControl]}
|
|
||||||
updateFiltersOnChange={updateFiltersOnChange}
|
|
||||||
hasChanges={() => { return true; }}
|
|
||||||
hasValues={() => { return true; }}
|
|
||||||
/>);
|
|
||||||
const reactSelectInput = component.find(`#${mockListControl.id}`).hostNodes();
|
|
||||||
reactSelectInput.simulate('change', { target: { value: 'choice1' } });
|
|
||||||
reactSelectInput.simulate('keyDown', { keyCode: 9, key: 'Tab' });
|
|
||||||
sinon.assert.notCalled(clearControls);
|
|
||||||
sinon.assert.notCalled(submitFilters);
|
|
||||||
sinon.assert.notCalled(resetControls);
|
|
||||||
const expectedControlIndex = 0;
|
|
||||||
const expectedControlValue = 'choice1';
|
|
||||||
sinon.assert.calledWith(stageFilter,
|
|
||||||
expectedControlIndex,
|
|
||||||
expectedControlValue
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,57 +1,44 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import Select from 'react-select';
|
|
||||||
import { FormRow } from './form_row';
|
import { FormRow } from './form_row';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
EuiFieldText,
|
EuiFieldText,
|
||||||
|
EuiComboBox,
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
|
|
||||||
export class ListControl extends Component {
|
export class ListControl extends Component {
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.handleOnChange = this.handleOnChange.bind(this);
|
handleOnChange = (selectedOptions) => {
|
||||||
this.truncate = this.truncate.bind(this);
|
this.props.stageFilter(this.props.controlIndex, selectedOptions);
|
||||||
}
|
|
||||||
|
|
||||||
handleOnChange(evt) {
|
|
||||||
let newValue = '';
|
|
||||||
if (evt) {
|
|
||||||
newValue = evt;
|
|
||||||
}
|
|
||||||
this.props.stageFilter(this.props.controlIndex, newValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
truncate(selected) {
|
|
||||||
if (selected.label.length <= 24) {
|
|
||||||
return selected.label;
|
|
||||||
}
|
|
||||||
return `${selected.label.substring(0, 23)}...`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderControl() {
|
renderControl() {
|
||||||
if (!this.props.control.isEnabled()) {
|
if (this.props.disableMsg) {
|
||||||
// react-select clobbers the tooltip, so just returning a disabled input instead
|
|
||||||
return (
|
return (
|
||||||
<EuiFieldText
|
<EuiFieldText
|
||||||
|
placeholder="Select..."
|
||||||
disabled={true}
|
disabled={true}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const options = this.props.options.map(option => {
|
||||||
|
return {
|
||||||
|
label: option.label,
|
||||||
|
value: option.value,
|
||||||
|
['data-test-subj']: `option_${option.value.replace(' ', '_')}`
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<EuiComboBox
|
||||||
className="list-control-react-select"
|
|
||||||
placeholder="Select..."
|
placeholder="Select..."
|
||||||
multi={this.props.control.options.multiselect}
|
options={options}
|
||||||
simpleValue={true}
|
selectedOptions={this.props.selectedOptions}
|
||||||
delimiter={this.props.control.getMultiSelectDelimiter()}
|
|
||||||
value={this.props.control.value}
|
|
||||||
options={this.props.control.selectOptions}
|
|
||||||
onChange={this.handleOnChange}
|
onChange={this.handleOnChange}
|
||||||
valueRenderer={this.truncate}
|
singleSelection={!this.props.multiselect}
|
||||||
inputProps={{ id: this.props.control.id }}
|
data-test-subj={`listControlSelect${this.props.controlIndex}`}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -59,10 +46,10 @@ export class ListControl extends Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<FormRow
|
<FormRow
|
||||||
id={this.props.control.id}
|
id={this.props.id}
|
||||||
label={this.props.control.label}
|
label={this.props.label}
|
||||||
controlIndex={this.props.controlIndex}
|
controlIndex={this.props.controlIndex}
|
||||||
control={this.props.control}
|
disableMsg={this.props.disableMsg}
|
||||||
>
|
>
|
||||||
{this.renderControl()}
|
{this.renderControl()}
|
||||||
</FormRow>
|
</FormRow>
|
||||||
|
@ -70,8 +57,18 @@ export class ListControl extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const comboBoxOptionShape = PropTypes.shape({
|
||||||
|
label: PropTypes.string.isRequired,
|
||||||
|
value: PropTypes.string.isRequired,
|
||||||
|
});
|
||||||
|
|
||||||
ListControl.propTypes = {
|
ListControl.propTypes = {
|
||||||
control: PropTypes.object.isRequired,
|
id: PropTypes.string.isRequired,
|
||||||
|
label: PropTypes.string.isRequired,
|
||||||
|
selectedOptions: PropTypes.arrayOf(comboBoxOptionShape).isRequired,
|
||||||
|
options: PropTypes.arrayOf(comboBoxOptionShape).isRequired,
|
||||||
|
disableMsg: PropTypes.string,
|
||||||
|
multiselect: PropTypes.bool.isRequired,
|
||||||
controlIndex: PropTypes.number.isRequired,
|
controlIndex: PropTypes.number.isRequired,
|
||||||
stageFilter: PropTypes.func.isRequired
|
stageFilter: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,22 +6,11 @@ import {
|
||||||
ListControl,
|
ListControl,
|
||||||
} from './list_control';
|
} from './list_control';
|
||||||
|
|
||||||
const control = {
|
const options = [
|
||||||
id: 'mock-list-control',
|
{ label: 'choice1', value: 'choice1' },
|
||||||
isEnabled: () => { return true; },
|
{ label: 'choice2', value: 'choice2' }
|
||||||
options: {
|
];
|
||||||
type: 'terms',
|
|
||||||
multiselect: true
|
|
||||||
},
|
|
||||||
type: 'list',
|
|
||||||
label: 'list control',
|
|
||||||
getMultiSelectDelimiter: () => { return ','; },
|
|
||||||
value: '',
|
|
||||||
selectOptions: [
|
|
||||||
{ label: 'choice1', value: 'choice1' },
|
|
||||||
{ label: 'choice2', value: 'choice2' }
|
|
||||||
]
|
|
||||||
};
|
|
||||||
let stageFilter;
|
let stageFilter;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -30,7 +19,11 @@ beforeEach(() => {
|
||||||
|
|
||||||
test('renders ListControl', () => {
|
test('renders ListControl', () => {
|
||||||
const component = shallow(<ListControl
|
const component = shallow(<ListControl
|
||||||
control={control}
|
id="mock-list-control"
|
||||||
|
label="list control"
|
||||||
|
options={options}
|
||||||
|
selectedOptions={[]}
|
||||||
|
multiselect={true}
|
||||||
controlIndex={0}
|
controlIndex={0}
|
||||||
stageFilter={stageFilter}
|
stageFilter={stageFilter}
|
||||||
/>);
|
/>);
|
||||||
|
|
|
@ -159,7 +159,7 @@ export class RangeControl extends Component {
|
||||||
id={this.props.control.id}
|
id={this.props.control.id}
|
||||||
label={this.props.control.label}
|
label={this.props.control.label}
|
||||||
controlIndex={this.props.controlIndex}
|
controlIndex={this.props.controlIndex}
|
||||||
control={this.props.control}
|
disableMsg={this.props.control.isEnabled() ? null : this.props.control.disabledReason}
|
||||||
>
|
>
|
||||||
{this.renderControl()}
|
{this.renderControl()}
|
||||||
</FormRow>
|
</FormRow>
|
||||||
|
|
|
@ -3,25 +3,23 @@ import { FilterManager } from './filter_manager.js';
|
||||||
import { buildPhraseFilter } from 'ui/filter_manager/lib/phrase';
|
import { buildPhraseFilter } from 'ui/filter_manager/lib/phrase';
|
||||||
import { buildPhrasesFilter } from 'ui/filter_manager/lib/phrases';
|
import { buildPhrasesFilter } from 'ui/filter_manager/lib/phrases';
|
||||||
|
|
||||||
const EMPTY_VALUE = '';
|
|
||||||
|
|
||||||
export class PhraseFilterManager extends FilterManager {
|
export class PhraseFilterManager extends FilterManager {
|
||||||
constructor(controlId, fieldName, indexPattern, queryFilter, delimiter) {
|
constructor(controlId, fieldName, indexPattern, queryFilter) {
|
||||||
super(controlId, fieldName, indexPattern, queryFilter, EMPTY_VALUE);
|
super(controlId, fieldName, indexPattern, queryFilter, []);
|
||||||
|
|
||||||
this.delimiter = delimiter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert phrases into filter
|
* Convert phrases into filter
|
||||||
*
|
*
|
||||||
* @param {string} react-select value (delimiter-separated string of values)
|
* @param {array}
|
||||||
* @return {object} query filter
|
* @return {object} query filter
|
||||||
* single phrase: match query
|
* single phrase: match query
|
||||||
* multiple phrases: bool query with should containing list of match_phrase queries
|
* multiple phrases: bool query with should containing list of match_phrase queries
|
||||||
*/
|
*/
|
||||||
createFilter(value) {
|
createFilter(selectedOptions) {
|
||||||
const phrases = value.split(this.delimiter);
|
const phrases = selectedOptions.map(phrase => {
|
||||||
|
return phrase.value;
|
||||||
|
});
|
||||||
let newFilter;
|
let newFilter;
|
||||||
if (phrases.length === 1) {
|
if (phrases.length === 1) {
|
||||||
newFilter = buildPhraseFilter(
|
newFilter = buildPhraseFilter(
|
||||||
|
@ -43,14 +41,25 @@ export class PhraseFilterManager extends FilterManager {
|
||||||
if (kbnFilters.length === 0) {
|
if (kbnFilters.length === 0) {
|
||||||
return this.getUnsetValue();
|
return this.getUnsetValue();
|
||||||
} else {
|
} else {
|
||||||
const values = kbnFilters
|
return kbnFilters
|
||||||
.map((kbnFilter) => {
|
.map((kbnFilter) => {
|
||||||
return this._getValueFromFilter(kbnFilter);
|
return this._getValueFromFilter(kbnFilter);
|
||||||
|
})
|
||||||
|
.reduce((accumulator, currentValue) => {
|
||||||
|
return accumulator.concat(currentValue);
|
||||||
|
}, [])
|
||||||
|
.map(value => {
|
||||||
|
return { value, label: value };
|
||||||
});
|
});
|
||||||
return values.join(this.delimiter);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract filtering value from kibana filters
|
||||||
|
*
|
||||||
|
* @param {object} kbnFilter
|
||||||
|
* @return {Array.<string>} array of values pulled from filter
|
||||||
|
*/
|
||||||
_getValueFromFilter(kbnFilter) {
|
_getValueFromFilter(kbnFilter) {
|
||||||
// bool filter - multiple phrase filters
|
// bool filter - multiple phrase filters
|
||||||
if (_.has(kbnFilter, 'query.bool.should')) {
|
if (_.has(kbnFilter, 'query.bool.should')) {
|
||||||
|
@ -63,8 +72,7 @@ export class PhraseFilterManager extends FilterManager {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
})
|
});
|
||||||
.join(this.delimiter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// scripted field filter
|
// scripted field filter
|
||||||
|
|
|
@ -24,11 +24,11 @@ describe('PhraseFilterManager', function () {
|
||||||
const queryFilterMock = {};
|
const queryFilterMock = {};
|
||||||
let filterManager;
|
let filterManager;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
filterManager = new PhraseFilterManager(controlId, 'field1', indexPatternMock, queryFilterMock, '|');
|
filterManager = new PhraseFilterManager(controlId, 'field1', indexPatternMock, queryFilterMock);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should create match phrase filter from single value', function () {
|
test('should create match phrase filter from single value', function () {
|
||||||
const newFilter = filterManager.createFilter('ios');
|
const newFilter = filterManager.createFilter([{ value: 'ios' }]);
|
||||||
expect(newFilter).to.have.property('meta');
|
expect(newFilter).to.have.property('meta');
|
||||||
expect(newFilter.meta.index).to.be(indexPatternId);
|
expect(newFilter.meta.index).to.be(indexPatternId);
|
||||||
expect(newFilter.meta.controlledBy).to.be(controlId);
|
expect(newFilter.meta.controlledBy).to.be(controlId);
|
||||||
|
@ -37,7 +37,7 @@ describe('PhraseFilterManager', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should create bool filter from multiple values', function () {
|
test('should create bool filter from multiple values', function () {
|
||||||
const newFilter = filterManager.createFilter('ios|win xp');
|
const newFilter = filterManager.createFilter([{ value: 'ios' }, { value: 'win xp' }]);
|
||||||
expect(newFilter).to.have.property('meta');
|
expect(newFilter).to.have.property('meta');
|
||||||
expect(newFilter.meta.index).to.be(indexPatternId);
|
expect(newFilter.meta.index).to.be(indexPatternId);
|
||||||
expect(newFilter.meta.controlledBy).to.be(controlId);
|
expect(newFilter.meta.controlledBy).to.be(controlId);
|
||||||
|
@ -67,7 +67,7 @@ describe('PhraseFilterManager', function () {
|
||||||
this.mockFilters = mockFilters;
|
this.mockFilters = mockFilters;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
filterManager = new MockFindFiltersPhraseFilterManager(controlId, 'field1', indexPatternMock, queryFilterMock, '|');
|
filterManager = new MockFindFiltersPhraseFilterManager(controlId, 'field1', indexPatternMock, queryFilterMock);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should extract value from match phrase filter', function () {
|
test('should extract value from match phrase filter', function () {
|
||||||
|
@ -83,7 +83,33 @@ describe('PhraseFilterManager', function () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
expect(filterManager.getValueFromFilterBar()).to.be('ios');
|
expect(filterManager.getValueFromFilterBar()).to.eql([{ value: 'ios', label: 'ios' }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should extract value from multiple filters', function () {
|
||||||
|
filterManager.setMockFilters([
|
||||||
|
{
|
||||||
|
query: {
|
||||||
|
match: {
|
||||||
|
field1: {
|
||||||
|
query: 'ios',
|
||||||
|
type: 'phrase'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
query: {
|
||||||
|
match: {
|
||||||
|
field1: {
|
||||||
|
query: 'win xp',
|
||||||
|
type: 'phrase'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expect(filterManager.getValueFromFilterBar()).to.eql([{ value: 'ios', label: 'ios' }, { value: 'win xp', label: 'win xp' }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should extract value from bool filter', function () {
|
test('should extract value from bool filter', function () {
|
||||||
|
@ -107,7 +133,7 @@ describe('PhraseFilterManager', function () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
expect(filterManager.getValueFromFilterBar()).to.be('ios|win xp');
|
expect(filterManager.getValueFromFilterBar()).to.eql([{ value: 'ios', label: 'ios' }, { value: 'win xp', label: 'win xp' }]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -32,14 +32,8 @@ const termsAgg = (field, size, direction) => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const listControlDelimiter = '$$kbn_delimiter$$';
|
|
||||||
|
|
||||||
class ListControl extends Control {
|
class ListControl extends Control {
|
||||||
|
|
||||||
getMultiSelectDelimiter() {
|
|
||||||
return this.filterManager.delimiter;
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetch() {
|
async fetch() {
|
||||||
let ancestorFilters;
|
let ancestorFilters;
|
||||||
if (this.hasAncestors()) {
|
if (this.hasAncestors()) {
|
||||||
|
@ -99,7 +93,7 @@ export async function listControlFactory(controlParams, kbnApi, useTimeFilter) {
|
||||||
|
|
||||||
return new ListControl(
|
return new ListControl(
|
||||||
controlParams,
|
controlParams,
|
||||||
new PhraseFilterManager(controlParams.id, controlParams.fieldName, indexPattern, kbnApi.queryFilter, listControlDelimiter),
|
new PhraseFilterManager(controlParams.id, controlParams.fieldName, indexPattern, kbnApi.queryFilter),
|
||||||
kbnApi,
|
kbnApi,
|
||||||
useTimeFilter
|
useTimeFilter
|
||||||
);
|
);
|
||||||
|
|
|
@ -32,11 +32,3 @@
|
||||||
left: 0% !important;
|
left: 0% !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
visualization.input_control_vis {
|
|
||||||
overflow: visible;
|
|
||||||
|
|
||||||
.vis-container {
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -705,4 +705,10 @@ style-compile {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.euiComboBox {
|
||||||
|
input:focus {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@import "~dragula/dist/dragula.css";
|
@import "~dragula/dist/dragula.css";
|
||||||
|
|
|
@ -16,9 +16,9 @@ export default function ({ getService, getPageObjects }) {
|
||||||
await PageObjects.header.setAbsoluteRange('2017-01-01', '2017-01-02');
|
await PageObjects.header.setAbsoluteRange('2017-01-01', '2017-01-02');
|
||||||
await PageObjects.visualize.clickVisEditorTab('controls');
|
await PageObjects.visualize.clickVisEditorTab('controls');
|
||||||
await PageObjects.visualize.addInputControl();
|
await PageObjects.visualize.addInputControl();
|
||||||
await PageObjects.visualize.setReactSelect('.index-pattern-react-select', 'logstash');
|
await PageObjects.visualize.setComboBox('indexPatternSelect-0', 'logstash');
|
||||||
await PageObjects.common.sleep(1000); // give time for index-pattern to be fetched
|
await PageObjects.common.sleep(1000); // give time for index-pattern to be fetched
|
||||||
await PageObjects.visualize.setReactSelect('.field-react-select', FIELD_NAME);
|
await PageObjects.visualize.setComboBox('fieldSelect-0', FIELD_NAME);
|
||||||
await PageObjects.visualize.clickGo();
|
await PageObjects.visualize.clickGo();
|
||||||
|
|
||||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||||
|
@ -34,7 +34,7 @@ export default function ({ getService, getPageObjects }) {
|
||||||
describe('updateFiltersOnChange is false', () => {
|
describe('updateFiltersOnChange is false', () => {
|
||||||
|
|
||||||
it('should contain dropdown with terms aggregation results as options', async () => {
|
it('should contain dropdown with terms aggregation results as options', async () => {
|
||||||
const menu = await PageObjects.visualize.getReactSelectOptions('inputControl0');
|
const menu = await PageObjects.visualize.getComboBoxOptions('listControlSelect0');
|
||||||
expect(menu.trim().split('\n').join()).to.equal('ios,osx,win 7,win 8,win xp');
|
expect(menu.trim().split('\n').join()).to.equal('ios,osx,win 7,win 8,win xp');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -48,10 +48,10 @@ export default function ({ getService, getPageObjects }) {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should stage filter when item selected but not create filter pill', async () => {
|
it('should stage filter when item selected but not create filter pill', async () => {
|
||||||
await PageObjects.visualize.setReactSelect('.list-control-react-select', 'ios');
|
await PageObjects.visualize.setComboBox('listControlSelect0', 'ios');
|
||||||
|
|
||||||
const dropdownValue = await PageObjects.visualize.getReactSelectValue('.list-control-react-select');
|
const selectedOptions = await PageObjects.visualize.getComboBoxSelectedOptions('listControlSelect0');
|
||||||
expect(dropdownValue.trim()).to.equal('ios');
|
expect(selectedOptions[0].trim()).to.equal('ios');
|
||||||
|
|
||||||
const hasFilter = await filterBar.hasFilter(FIELD_NAME, 'ios');
|
const hasFilter = await filterBar.hasFilter(FIELD_NAME, 'ios');
|
||||||
expect(hasFilter).to.equal(false);
|
expect(hasFilter).to.equal(false);
|
||||||
|
@ -65,8 +65,8 @@ export default function ({ getService, getPageObjects }) {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should replace existing filter pill(s) when new item is selected', async () => {
|
it('should replace existing filter pill(s) when new item is selected', async () => {
|
||||||
await PageObjects.visualize.clearReactSelect('.list-control-react-select');
|
await PageObjects.visualize.clearComboBox('listControlSelect0');
|
||||||
await PageObjects.visualize.setReactSelect('.list-control-react-select', 'osx');
|
await PageObjects.visualize.setComboBox('listControlSelect0', 'osx');
|
||||||
await testSubjects.click('inputControlSubmitBtn');
|
await testSubjects.click('inputControlSubmitBtn');
|
||||||
|
|
||||||
const hasOldFilter = await filterBar.hasFilter(FIELD_NAME, 'ios');
|
const hasOldFilter = await filterBar.hasFilter(FIELD_NAME, 'ios');
|
||||||
|
@ -79,18 +79,18 @@ export default function ({ getService, getPageObjects }) {
|
||||||
await filterBar.removeFilter(FIELD_NAME);
|
await filterBar.removeFilter(FIELD_NAME);
|
||||||
await PageObjects.common.sleep(500); // give time for filter to be removed and event handlers to fire
|
await PageObjects.common.sleep(500); // give time for filter to be removed and event handlers to fire
|
||||||
|
|
||||||
const hasValue = await PageObjects.visualize.doesReactSelectHaveValue('.list-control-react-select');
|
const hasValue = await PageObjects.visualize.doesComboBoxHaveSelectedOptions('listControlSelect0');
|
||||||
expect(hasValue).to.equal(false);
|
expect(hasValue).to.equal(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should clear form when Clear button is clicked but not remove filter pill', async () => {
|
it('should clear form when Clear button is clicked but not remove filter pill', async () => {
|
||||||
await PageObjects.visualize.setReactSelect('.list-control-react-select', 'ios');
|
await PageObjects.visualize.setComboBox('listControlSelect0', 'ios');
|
||||||
await testSubjects.click('inputControlSubmitBtn');
|
await testSubjects.click('inputControlSubmitBtn');
|
||||||
const hasFilterBeforeClearBtnClicked = await filterBar.hasFilter(FIELD_NAME, 'ios');
|
const hasFilterBeforeClearBtnClicked = await filterBar.hasFilter(FIELD_NAME, 'ios');
|
||||||
expect(hasFilterBeforeClearBtnClicked).to.equal(true);
|
expect(hasFilterBeforeClearBtnClicked).to.equal(true);
|
||||||
|
|
||||||
await testSubjects.click('inputControlClearBtn');
|
await testSubjects.click('inputControlClearBtn');
|
||||||
const hasValue = await PageObjects.visualize.doesReactSelectHaveValue('.list-control-react-select');
|
const hasValue = await PageObjects.visualize.doesComboBoxHaveSelectedOptions('listControlSelect0');
|
||||||
expect(hasValue).to.equal(false);
|
expect(hasValue).to.equal(false);
|
||||||
|
|
||||||
const hasFilterAfterClearBtnClicked = await filterBar.hasFilter(FIELD_NAME, 'ios');
|
const hasFilterAfterClearBtnClicked = await filterBar.hasFilter(FIELD_NAME, 'ios');
|
||||||
|
@ -131,10 +131,10 @@ export default function ({ getService, getPageObjects }) {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add filter pill when item selected', async () => {
|
it('should add filter pill when item selected', async () => {
|
||||||
await PageObjects.visualize.setReactSelect('.list-control-react-select', 'ios');
|
await PageObjects.visualize.setComboBox('listControlSelect0', 'ios');
|
||||||
|
|
||||||
const dropdownValue = await PageObjects.visualize.getReactSelectValue('.list-control-react-select');
|
const selectedOptions = await PageObjects.visualize.getComboBoxSelectedOptions('listControlSelect0');
|
||||||
expect(dropdownValue.trim()).to.equal('ios');
|
expect(selectedOptions[0].trim()).to.equal('ios');
|
||||||
|
|
||||||
const hasFilter = await filterBar.hasFilter(FIELD_NAME, 'ios');
|
const hasFilter = await filterBar.hasFilter(FIELD_NAME, 'ios');
|
||||||
expect(hasFilter).to.equal(true);
|
expect(hasFilter).to.equal(true);
|
||||||
|
@ -159,7 +159,7 @@ export default function ({ getService, getPageObjects }) {
|
||||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||||
|
|
||||||
// Expect control to have values for selected time filter
|
// Expect control to have values for selected time filter
|
||||||
const menu = await PageObjects.visualize.getReactSelectOptions('inputControl0');
|
const menu = await PageObjects.visualize.getComboBoxOptions('listControlSelect0');
|
||||||
expect(menu.trim().split('\n').join()).to.equal('osx,win 7,win 8,win xp');
|
expect(menu.trim().split('\n').join()).to.equal('osx,win 7,win 8,win xp');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -172,14 +172,14 @@ export default function ({ getService, getPageObjects }) {
|
||||||
await PageObjects.visualize.clickVisEditorTab('controls');
|
await PageObjects.visualize.clickVisEditorTab('controls');
|
||||||
|
|
||||||
await PageObjects.visualize.addInputControl();
|
await PageObjects.visualize.addInputControl();
|
||||||
await PageObjects.visualize.setReactSelect('#indexPatternSelect-0-row', 'logstash');
|
await PageObjects.visualize.setComboBox('indexPatternSelect-0', 'logstash');
|
||||||
await PageObjects.common.sleep(1000); // give time for index-pattern to be fetched
|
await PageObjects.common.sleep(1000); // give time for index-pattern to be fetched
|
||||||
await PageObjects.visualize.setReactSelect('#fieldSelect-0-row', 'geo.src');
|
await PageObjects.visualize.setComboBox('fieldSelect-0', 'geo.src');
|
||||||
|
|
||||||
await PageObjects.visualize.addInputControl();
|
await PageObjects.visualize.addInputControl();
|
||||||
await PageObjects.visualize.setReactSelect('#indexPatternSelect-1-row', 'logstash');
|
await PageObjects.visualize.setComboBox('indexPatternSelect-1', 'logstash');
|
||||||
await PageObjects.common.sleep(1000); // give time for index-pattern to be fetched
|
await PageObjects.common.sleep(1000); // give time for index-pattern to be fetched
|
||||||
await PageObjects.visualize.setReactSelect('#fieldSelect-1-row', 'clientip');
|
await PageObjects.visualize.setComboBox('fieldSelect-1', 'clientip');
|
||||||
await PageObjects.visualize.setSelectByOptionText('parentSelect-1', 'geo.src');
|
await PageObjects.visualize.setSelectByOptionText('parentSelect-1', 'geo.src');
|
||||||
|
|
||||||
await PageObjects.visualize.clickGo();
|
await PageObjects.visualize.clickGo();
|
||||||
|
@ -187,7 +187,7 @@ export default function ({ getService, getPageObjects }) {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should disable child control when parent control is not set', async () => {
|
it('should disable child control when parent control is not set', async () => {
|
||||||
const parentControlMenu = await PageObjects.visualize.getReactSelectOptions('inputControl0');
|
const parentControlMenu = await PageObjects.visualize.getComboBoxOptions('listControlSelect0');
|
||||||
expect(parentControlMenu.trim().split('\n').join()).to.equal('BR,CN,ID,IN,US');
|
expect(parentControlMenu.trim().split('\n').join()).to.equal('BR,CN,ID,IN,US');
|
||||||
|
|
||||||
const childControlInput = await find.byCssSelector('[data-test-subj="inputControl1"] input');
|
const childControlInput = await find.byCssSelector('[data-test-subj="inputControl1"] input');
|
||||||
|
@ -196,14 +196,14 @@ export default function ({ getService, getPageObjects }) {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should filter child control options by parent control value', async () => {
|
it('should filter child control options by parent control value', async () => {
|
||||||
await PageObjects.visualize.setReactSelect('[data-test-subj="inputControl0"]', 'BR');
|
await PageObjects.visualize.setComboBox('listControlSelect0', 'BR');
|
||||||
|
|
||||||
const childControlMenu = await PageObjects.visualize.getReactSelectOptions('inputControl1');
|
const childControlMenu = await PageObjects.visualize.getComboBoxOptions('listControlSelect1');
|
||||||
expect(childControlMenu.trim().split('\n').join()).to.equal('14.61.182.136,3.174.21.181,6.183.121.70,71.241.97.89,9.69.255.135');
|
expect(childControlMenu.trim().split('\n').join()).to.equal('14.61.182.136,3.174.21.181,6.183.121.70,71.241.97.89,9.69.255.135');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create a seperate filter pill for parent control and child control', async () => {
|
it('should create a seperate filter pill for parent control and child control', async () => {
|
||||||
await PageObjects.visualize.setReactSelect('[data-test-subj="inputControl1"]', '14.61.182.136');
|
await PageObjects.visualize.setComboBox('listControlSelect1', '14.61.182.136');
|
||||||
|
|
||||||
await testSubjects.click('inputControlSubmitBtn');
|
await testSubjects.click('inputControlSubmitBtn');
|
||||||
|
|
||||||
|
@ -215,7 +215,7 @@ export default function ({ getService, getPageObjects }) {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should clear child control dropdown when parent control value is removed', async () => {
|
it('should clear child control dropdown when parent control value is removed', async () => {
|
||||||
await PageObjects.visualize.clearReactSelect('[data-test-subj="inputControl0"]');
|
await PageObjects.visualize.clearComboBox('listControlSelect0');
|
||||||
await PageObjects.common.sleep(500); // give time for filter to be removed and event handlers to fire
|
await PageObjects.common.sleep(500); // give time for filter to be removed and event handlers to fire
|
||||||
|
|
||||||
const childControlInput = await find.byCssSelector('[data-test-subj="inputControl1"] input');
|
const childControlInput = await find.byCssSelector('[data-test-subj="inputControl1"] input');
|
||||||
|
@ -229,7 +229,7 @@ export default function ({ getService, getPageObjects }) {
|
||||||
await filterBar.removeFilter('geo.src');
|
await filterBar.removeFilter('geo.src');
|
||||||
await PageObjects.common.sleep(500); // give time for filter to be removed and event handlers to fire
|
await PageObjects.common.sleep(500); // give time for filter to be removed and event handlers to fire
|
||||||
|
|
||||||
const hasValue = await PageObjects.visualize.doesReactSelectHaveValue('[data-test-subj="inputControl1"]');
|
const hasValue = await PageObjects.visualize.doesComboBoxHaveSelectedOptions('listControlSelect0');
|
||||||
expect(hasValue).to.equal(false);
|
expect(hasValue).to.equal(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -194,41 +194,57 @@ export function VisualizePageProvider({ getService, getPageObjects }) {
|
||||||
await input.type(timeString);
|
await input.type(timeString);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setReactSelect(className, value) {
|
async setComboBox(comboBoxSelector, value) {
|
||||||
const input = await find.byCssSelector(className + ' * input', 0);
|
const comboBox = await testSubjects.find(comboBoxSelector);
|
||||||
|
const input = await comboBox.findByTagName('input');
|
||||||
await input.clearValue();
|
await input.clearValue();
|
||||||
await input.type(value);
|
await input.type(value);
|
||||||
await find.clickByCssSelector('.Select-option');
|
await find.clickByCssSelector('.euiComboBoxOption');
|
||||||
const stillOpen = await find.existsByCssSelector('.Select-menu-outer', 0);
|
await this.closeComboBoxOptionsList(comboBox);
|
||||||
if (stillOpen) {
|
await remote.pressKeys('\uE004');
|
||||||
await find.clickByCssSelector(className + ' * .Select-arrow-zone');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async clearReactSelect(className) {
|
async getComboBoxOptions(comboBoxSelector) {
|
||||||
await find.clickByCssSelector(className + ' * .Select-clear-zone');
|
await testSubjects.click(comboBoxSelector);
|
||||||
}
|
|
||||||
|
|
||||||
async getReactSelectOptions(containerSelector) {
|
|
||||||
await testSubjects.click(containerSelector);
|
|
||||||
const menu = await retry.try(
|
const menu = await retry.try(
|
||||||
async () => find.byCssSelector('.Select-menu-outer'));
|
async () => await testSubjects.find('comboBoxOptionsList'));
|
||||||
return await menu.getVisibleText();
|
const optionsText = await menu.getVisibleText();
|
||||||
|
const comboBox = await testSubjects.find(comboBoxSelector);
|
||||||
|
await this.closeComboBoxOptionsList(comboBox);
|
||||||
|
return optionsText;
|
||||||
}
|
}
|
||||||
|
|
||||||
async doesReactSelectHaveValue(className) {
|
async doesComboBoxHaveSelectedOptions(comboBoxSelector) {
|
||||||
return await find.existsByCssSelector(className + ' * .Select-value-label', 0);
|
const comboBox = await testSubjects.find(comboBoxSelector);
|
||||||
|
const selectedOptions = await comboBox.findAllByClassName('euiComboBoxPill');
|
||||||
|
return selectedOptions > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getReactSelectValue(className) {
|
async getComboBoxSelectedOptions(comboBoxSelector) {
|
||||||
const hasValue = await this.doesReactSelectHaveValue(className);
|
const comboBox = await testSubjects.find(comboBoxSelector);
|
||||||
if (!hasValue) {
|
const selectedOptions = await comboBox.findAllByClassName('euiComboBoxPill');
|
||||||
return '';
|
if (selectedOptions.length === 0) {
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const valueElement = await retry.try(
|
const getOptionValuePromises = selectedOptions.map(async (optionElement) => {
|
||||||
async () => find.byCssSelector(className + ' * .Select-value-label'));
|
return await optionElement.getVisibleText();
|
||||||
return await valueElement.getVisibleText();
|
});
|
||||||
|
return await Promise.all(getOptionValuePromises);
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearComboBox(comboBoxSelector) {
|
||||||
|
const comboBox = await testSubjects.find(comboBoxSelector);
|
||||||
|
const clearBtn = await comboBox.findByCssSelector('button.euiFormControlLayout__clear');
|
||||||
|
await clearBtn.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async closeComboBoxOptionsList(comboBoxElement) {
|
||||||
|
const isOptionsListOpen = await testSubjects.exists('comboBoxOptionsList');
|
||||||
|
if (isOptionsListOpen) {
|
||||||
|
const closeBtn = await comboBoxElement.findByCssSelector('button.euiFormControlLayout__icon');
|
||||||
|
await closeBtn.click();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async addInputControl() {
|
async addInputControl() {
|
||||||
|
|
Loading…
Reference in a new issue