mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[input controls] update dropdown suggestions when filtered (#18985)
* input controls - re-fetch options list user input * fix jest tests * add functional test case * remove unneeded async and await * use fetchAsRejectablePromise instead of fetch to avoid kill kibana session when fetching agg results * only show options once field is selected * clean up list control editor to only display options when field selected and only display dynamic checkbox when field is string * do not use size when using dynamic options * display disabled toggle when field is not string field, allow searching in middle of word * no tooltip for disabled dyncamic options, just change help text * fix functional test expects since search now includes more than terms that start with
This commit is contained in:
parent
cb5ee01c6a
commit
00f81dc093
13 changed files with 725 additions and 160 deletions
|
@ -1,6 +1,197 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`parentCandidates 1`] = `
|
||||
exports[`renders dynamic options should display disabled dynamic options with tooltip for non-string fields 1`] = `
|
||||
<div>
|
||||
<IndexPatternSelect
|
||||
controlIndex={0}
|
||||
getIndexPattern={[Function]}
|
||||
getIndexPatterns={[Function]}
|
||||
indexPatternId="mockIndexPattern"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
<FieldSelect
|
||||
controlIndex={0}
|
||||
fieldName="numberField"
|
||||
filterField={[Function]}
|
||||
getIndexPattern={[Function]}
|
||||
indexPatternId="mockIndexPattern"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
helpText="Allow multiple selection"
|
||||
id="multiselect-0"
|
||||
key="multiselect"
|
||||
>
|
||||
<EuiSwitch
|
||||
checked={true}
|
||||
data-test-subj="listControlMultiselectInput"
|
||||
label="Multiselect"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
helpText="Only available for \\"string\\" fields"
|
||||
id="dynamicOptions-0"
|
||||
key="dynamicOptions"
|
||||
>
|
||||
<EuiSwitch
|
||||
checked={true}
|
||||
data-test-subj="listControlDynamicOptionsSwitch"
|
||||
disabled={true}
|
||||
label="Dynamic Options"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
helpText="Number of options"
|
||||
id="size-0"
|
||||
key="size"
|
||||
label="Size"
|
||||
>
|
||||
<EuiFieldNumber
|
||||
compressed={false}
|
||||
data-test-subj="listControlSizeInput"
|
||||
fullWidth={false}
|
||||
isLoading={false}
|
||||
min={1}
|
||||
onChange={[Function]}
|
||||
value={5}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders dynamic options should display dynamic options for string fields 1`] = `
|
||||
<div>
|
||||
<IndexPatternSelect
|
||||
controlIndex={0}
|
||||
getIndexPattern={[Function]}
|
||||
getIndexPatterns={[Function]}
|
||||
indexPatternId="mockIndexPattern"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
<FieldSelect
|
||||
controlIndex={0}
|
||||
fieldName="keywordField"
|
||||
filterField={[Function]}
|
||||
getIndexPattern={[Function]}
|
||||
indexPatternId="mockIndexPattern"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
helpText="Allow multiple selection"
|
||||
id="multiselect-0"
|
||||
key="multiselect"
|
||||
>
|
||||
<EuiSwitch
|
||||
checked={true}
|
||||
data-test-subj="listControlMultiselectInput"
|
||||
label="Multiselect"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
helpText="Update options in response to user input"
|
||||
id="dynamicOptions-0"
|
||||
key="dynamicOptions"
|
||||
>
|
||||
<EuiSwitch
|
||||
checked={true}
|
||||
data-test-subj="listControlDynamicOptionsSwitch"
|
||||
disabled={false}
|
||||
label="Dynamic Options"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders dynamic options should display size field when dynamic options is disabled 1`] = `
|
||||
<div>
|
||||
<IndexPatternSelect
|
||||
controlIndex={0}
|
||||
getIndexPattern={[Function]}
|
||||
getIndexPatterns={[Function]}
|
||||
indexPatternId="mockIndexPattern"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
<FieldSelect
|
||||
controlIndex={0}
|
||||
fieldName="keywordField"
|
||||
filterField={[Function]}
|
||||
getIndexPattern={[Function]}
|
||||
indexPatternId="mockIndexPattern"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
helpText="Allow multiple selection"
|
||||
id="multiselect-0"
|
||||
key="multiselect"
|
||||
>
|
||||
<EuiSwitch
|
||||
checked={true}
|
||||
data-test-subj="listControlMultiselectInput"
|
||||
label="Multiselect"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
helpText="Update options in response to user input"
|
||||
id="dynamicOptions-0"
|
||||
key="dynamicOptions"
|
||||
>
|
||||
<EuiSwitch
|
||||
checked={false}
|
||||
data-test-subj="listControlDynamicOptionsSwitch"
|
||||
disabled={false}
|
||||
label="Dynamic Options"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
helpText="Number of options"
|
||||
id="size-0"
|
||||
key="size"
|
||||
label="Size"
|
||||
>
|
||||
<EuiFieldNumber
|
||||
compressed={false}
|
||||
data-test-subj="listControlSizeInput"
|
||||
fullWidth={false}
|
||||
isLoading={false}
|
||||
min={1}
|
||||
onChange={[Function]}
|
||||
value={5}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders should display chaining input when parents are provided 1`] = `
|
||||
<div>
|
||||
<IndexPatternSelect
|
||||
controlIndex={0}
|
||||
|
@ -23,6 +214,7 @@ exports[`parentCandidates 1`] = `
|
|||
hasEmptyLabelSpace={false}
|
||||
helpText="Options are based on the value of parent control. Disabled if parent is not set."
|
||||
id="parentSelect-0"
|
||||
key="parentSelect"
|
||||
label="Parent control"
|
||||
>
|
||||
<EuiSelect
|
||||
|
@ -53,7 +245,9 @@ exports[`parentCandidates 1`] = `
|
|||
describedByIds={Array []}
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
helpText="Allow multiple selection"
|
||||
id="multiselect-0"
|
||||
key="multiselect"
|
||||
>
|
||||
<EuiSwitch
|
||||
checked={true}
|
||||
|
@ -66,7 +260,25 @@ exports[`parentCandidates 1`] = `
|
|||
describedByIds={Array []}
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
helpText="Update options in response to user input"
|
||||
id="dynamicOptions-0"
|
||||
key="dynamicOptions"
|
||||
>
|
||||
<EuiSwitch
|
||||
checked={false}
|
||||
data-test-subj="listControlDynamicOptionsSwitch"
|
||||
disabled={false}
|
||||
label="Dynamic Options"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
helpText="Number of options"
|
||||
id="size-0"
|
||||
key="size"
|
||||
label="Size"
|
||||
>
|
||||
<EuiFieldNumber
|
||||
|
@ -82,52 +294,22 @@ exports[`parentCandidates 1`] = `
|
|||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders ListControlEditor 1`] = `
|
||||
exports[`renders should not display any options until field is selected 1`] = `
|
||||
<div>
|
||||
<IndexPatternSelect
|
||||
controlIndex={0}
|
||||
getIndexPattern={[Function]}
|
||||
getIndexPatterns={[Function]}
|
||||
indexPatternId="indexPattern1"
|
||||
indexPatternId="mockIndexPattern"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
<FieldSelect
|
||||
controlIndex={0}
|
||||
fieldName="keywordField"
|
||||
fieldName=""
|
||||
filterField={[Function]}
|
||||
getIndexPattern={[Function]}
|
||||
indexPatternId="indexPattern1"
|
||||
indexPatternId="mockIndexPattern"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
id="multiselect-0"
|
||||
>
|
||||
<EuiSwitch
|
||||
checked={true}
|
||||
data-test-subj="listControlMultiselectInput"
|
||||
label="Multiselect"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
id="size-0"
|
||||
label="Size"
|
||||
>
|
||||
<EuiFieldNumber
|
||||
compressed={false}
|
||||
data-test-subj="listControlSizeInput"
|
||||
fullWidth={false}
|
||||
isLoading={false}
|
||||
min={1}
|
||||
onChange={[Function]}
|
||||
value={10}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import { IndexPatternSelect } from './index_pattern_select';
|
||||
import { FieldSelect } from './field_select';
|
||||
|
||||
|
@ -33,87 +33,186 @@ function filterField(field) {
|
|||
return field.aggregatable && ['number', 'boolean', 'date', 'ip', 'string'].includes(field.type);
|
||||
}
|
||||
|
||||
export function ListControlEditor(props) {
|
||||
const multiselectId = `multiselect-${props.controlIndex}`;
|
||||
const sizeId = `size-${props.controlIndex}`;
|
||||
const handleMultiselectChange = (evt) => {
|
||||
props.handleCheckboxOptionChange(props.controlIndex, 'multiselect', evt);
|
||||
};
|
||||
const handleSizeChange = (evt) => {
|
||||
props.handleNumberOptionChange(props.controlIndex, 'size', evt);
|
||||
};
|
||||
const handleParentChange = (evt) => {
|
||||
props.handleParentChange(props.controlIndex, evt);
|
||||
export class ListControlEditor extends Component {
|
||||
state = {
|
||||
isLoadingFieldType: true,
|
||||
isStringField: false,
|
||||
prevFieldName: this.props.controlParams.fieldName,
|
||||
};
|
||||
|
||||
let parentSelect;
|
||||
if (props.parentCandidates && props.parentCandidates.length > 0) {
|
||||
const options = [
|
||||
{ value: '', text: '' },
|
||||
...props.parentCandidates,
|
||||
];
|
||||
parentSelect = (
|
||||
<EuiFormRow
|
||||
id={`parentSelect-${props.controlIndex}`}
|
||||
label="Parent control"
|
||||
helpText="Options are based on the value of parent control. Disabled if parent is not set."
|
||||
>
|
||||
<EuiSelect
|
||||
options={options}
|
||||
value={props.controlParams.parent}
|
||||
onChange={handleParentChange}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
componentDidMount() {
|
||||
this._isMounted = true;
|
||||
this.loadIsStringField();
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
componentWillUnmount() {
|
||||
this._isMounted = false;
|
||||
}
|
||||
|
||||
<IndexPatternSelect
|
||||
indexPatternId={props.controlParams.indexPattern}
|
||||
onChange={props.handleIndexPatternChange}
|
||||
getIndexPatterns={props.getIndexPatterns}
|
||||
getIndexPattern={props.getIndexPattern}
|
||||
controlIndex={props.controlIndex}
|
||||
/>
|
||||
static getDerivedStateFromProps = (nextProps, prevState) => {
|
||||
const isNewFieldName = prevState.prevFieldName !== nextProps.controlParams.fieldName;
|
||||
if (!prevState.isLoadingFieldType && isNewFieldName) {
|
||||
return {
|
||||
isLoadingFieldType: true,
|
||||
};
|
||||
}
|
||||
|
||||
<FieldSelect
|
||||
fieldName={props.controlParams.fieldName}
|
||||
indexPatternId={props.controlParams.indexPattern}
|
||||
filterField={filterField}
|
||||
onChange={props.handleFieldNameChange}
|
||||
getIndexPattern={props.getIndexPattern}
|
||||
controlIndex={props.controlIndex}
|
||||
/>
|
||||
return null;
|
||||
}
|
||||
|
||||
{ parentSelect }
|
||||
componentDidUpdate = () => {
|
||||
if (this.state.isLoadingFieldType) {
|
||||
this.loadIsStringField();
|
||||
}
|
||||
}
|
||||
|
||||
loadIsStringField = async () => {
|
||||
if (!this.props.controlParams.indexPattern || !this.props.controlParams.fieldName) {
|
||||
this.setState({ isLoadingFieldType: false });
|
||||
return;
|
||||
}
|
||||
|
||||
let indexPattern;
|
||||
try {
|
||||
indexPattern = await this.props.getIndexPattern(this.props.controlParams.indexPattern);
|
||||
} catch (err) {
|
||||
// index pattern no longer exists
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._isMounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const field = indexPattern.fields.find((field) => {
|
||||
return field.name === this.props.controlParams.fieldName;
|
||||
});
|
||||
if (!field) {
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
isLoadingFieldType: false,
|
||||
isStringField: field.type === 'string'
|
||||
});
|
||||
}
|
||||
|
||||
renderOptions = () => {
|
||||
if (this.state.isLoadingFieldType || !this.props.controlParams.fieldName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const options = [];
|
||||
if (this.props.parentCandidates && this.props.parentCandidates.length > 0) {
|
||||
const parentCandidatesOptions = [
|
||||
{ value: '', text: '' },
|
||||
...this.props.parentCandidates,
|
||||
];
|
||||
options.push(
|
||||
<EuiFormRow
|
||||
id={`parentSelect-${this.props.controlIndex}`}
|
||||
label="Parent control"
|
||||
helpText="Options are based on the value of parent control. Disabled if parent is not set."
|
||||
key="parentSelect"
|
||||
>
|
||||
<EuiSelect
|
||||
options={parentCandidatesOptions}
|
||||
value={this.props.controlParams.parent}
|
||||
onChange={(evt) => {
|
||||
this.props.handleParentChange(this.props.controlIndex, evt);
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
}
|
||||
|
||||
options.push(
|
||||
<EuiFormRow
|
||||
id={multiselectId}
|
||||
id={`multiselect-${this.props.controlIndex}`}
|
||||
key="multiselect"
|
||||
helpText="Allow multiple selection"
|
||||
>
|
||||
<EuiSwitch
|
||||
label="Multiselect"
|
||||
checked={props.controlParams.options.multiselect}
|
||||
onChange={handleMultiselectChange}
|
||||
checked={this.props.controlParams.options.multiselect}
|
||||
onChange={(evt) => {
|
||||
this.props.handleCheckboxOptionChange(this.props.controlIndex, 'multiselect', evt);
|
||||
}}
|
||||
data-test-subj="listControlMultiselectInput"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
|
||||
const dynamicOptionsHelpText = this.state.isStringField
|
||||
? 'Update options in response to user input'
|
||||
: 'Only available for "string" fields';
|
||||
options.push(
|
||||
<EuiFormRow
|
||||
id={sizeId}
|
||||
label="Size"
|
||||
id={`dynamicOptions-${this.props.controlIndex}`}
|
||||
key="dynamicOptions"
|
||||
helpText={dynamicOptionsHelpText}
|
||||
>
|
||||
<EuiFieldNumber
|
||||
min={1}
|
||||
value={props.controlParams.options.size}
|
||||
onChange={handleSizeChange}
|
||||
data-test-subj="listControlSizeInput"
|
||||
<EuiSwitch
|
||||
label="Dynamic Options"
|
||||
checked={this.props.controlParams.options.dynamicOptions}
|
||||
onChange={(evt) => {
|
||||
this.props.handleCheckboxOptionChange(this.props.controlIndex, 'dynamicOptions', evt);
|
||||
}}
|
||||
disabled={this.state.isStringField ? false : true}
|
||||
data-test-subj="listControlDynamicOptionsSwitch"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
|
||||
</div>
|
||||
);
|
||||
// size is not used when dynamic options is set
|
||||
if (!this.props.controlParams.options.dynamicOptions || !this.state.isStringField) {
|
||||
options.push(
|
||||
<EuiFormRow
|
||||
id={`size-${this.props.controlIndex}`}
|
||||
label="Size"
|
||||
key="size"
|
||||
helpText="Number of options"
|
||||
>
|
||||
<EuiFieldNumber
|
||||
min={1}
|
||||
value={this.props.controlParams.options.size}
|
||||
onChange={(evt) => {
|
||||
this.props.handleNumberOptionChange(this.props.controlIndex, 'size', evt);
|
||||
}}
|
||||
data-test-subj="listControlSizeInput"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
|
||||
<IndexPatternSelect
|
||||
indexPatternId={this.props.controlParams.indexPattern}
|
||||
onChange={this.props.handleIndexPatternChange}
|
||||
getIndexPatterns={this.props.getIndexPatterns}
|
||||
getIndexPattern={this.props.getIndexPattern}
|
||||
controlIndex={this.props.controlIndex}
|
||||
/>
|
||||
|
||||
<FieldSelect
|
||||
fieldName={this.props.controlParams.fieldName}
|
||||
indexPatternId={this.props.controlParams.indexPattern}
|
||||
filterField={filterField}
|
||||
onChange={this.props.handleFieldNameChange}
|
||||
getIndexPattern={this.props.getIndexPattern}
|
||||
controlIndex={this.props.controlIndex}
|
||||
/>
|
||||
|
||||
{this.renderOptions()}
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ListControlEditor.propTypes = {
|
||||
|
|
|
@ -37,6 +37,7 @@ const controlParams = {
|
|||
options: {
|
||||
type: 'terms',
|
||||
multiselect: true,
|
||||
dynamicOptions: false,
|
||||
size: 10
|
||||
}
|
||||
};
|
||||
|
@ -52,43 +53,173 @@ beforeEach(() => {
|
|||
handleNumberOptionChange = sinon.spy();
|
||||
});
|
||||
|
||||
test('renders ListControlEditor', () => {
|
||||
const component = shallow(<ListControlEditor
|
||||
getIndexPatterns={getIndexPatternsMock}
|
||||
getIndexPattern={getIndexPatternMock}
|
||||
controlIndex={0}
|
||||
controlParams={controlParams}
|
||||
handleFieldNameChange={handleFieldNameChange}
|
||||
handleIndexPatternChange={handleIndexPatternChange}
|
||||
handleCheckboxOptionChange={handleCheckboxOptionChange}
|
||||
handleNumberOptionChange={handleNumberOptionChange}
|
||||
handleParentChange={() => {}}
|
||||
parentCandidates={[]}
|
||||
/>);
|
||||
expect(component).toMatchSnapshot(); // eslint-disable-line
|
||||
describe('renders', () => {
|
||||
test('should not display any options until field is selected', async () => {
|
||||
const controlParams = {
|
||||
id: '1',
|
||||
indexPattern: 'mockIndexPattern',
|
||||
fieldName: '',
|
||||
type: 'list',
|
||||
options: {
|
||||
type: 'terms',
|
||||
multiselect: true,
|
||||
dynamicOptions: true,
|
||||
size: 5,
|
||||
}
|
||||
};
|
||||
const component = shallow(<ListControlEditor
|
||||
getIndexPatterns={getIndexPatternsMock}
|
||||
getIndexPattern={getIndexPatternMock}
|
||||
controlIndex={0}
|
||||
controlParams={controlParams}
|
||||
handleFieldNameChange={handleFieldNameChange}
|
||||
handleIndexPatternChange={handleIndexPatternChange}
|
||||
handleCheckboxOptionChange={handleCheckboxOptionChange}
|
||||
handleNumberOptionChange={handleNumberOptionChange}
|
||||
handleParentChange={() => {}}
|
||||
parentCandidates={[]}
|
||||
/>);
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
// Ensure the state changes are reflected
|
||||
component.update();
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should display chaining input when parents are provided', async () => {
|
||||
const parentCandidates = [
|
||||
{ value: '1', text: 'fieldA' },
|
||||
{ value: '2', text: 'fieldB' }
|
||||
];
|
||||
const component = shallow(<ListControlEditor
|
||||
getIndexPatterns={getIndexPatternsMock}
|
||||
getIndexPattern={getIndexPatternMock}
|
||||
controlIndex={0}
|
||||
controlParams={controlParams}
|
||||
handleFieldNameChange={handleFieldNameChange}
|
||||
handleIndexPatternChange={handleIndexPatternChange}
|
||||
handleCheckboxOptionChange={handleCheckboxOptionChange}
|
||||
handleNumberOptionChange={handleNumberOptionChange}
|
||||
handleParentChange={() => {}}
|
||||
parentCandidates={parentCandidates}
|
||||
/>);
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
// Ensure the state changes are reflected
|
||||
component.update();
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('dynamic options', () => {
|
||||
test('should display dynamic options for string fields', async () => {
|
||||
const controlParams = {
|
||||
id: '1',
|
||||
indexPattern: 'mockIndexPattern',
|
||||
fieldName: 'keywordField',
|
||||
type: 'list',
|
||||
options: {
|
||||
type: 'terms',
|
||||
multiselect: true,
|
||||
dynamicOptions: true,
|
||||
size: 5,
|
||||
}
|
||||
};
|
||||
const component = shallow(<ListControlEditor
|
||||
getIndexPatterns={getIndexPatternsMock}
|
||||
getIndexPattern={getIndexPatternMock}
|
||||
controlIndex={0}
|
||||
controlParams={controlParams}
|
||||
handleFieldNameChange={handleFieldNameChange}
|
||||
handleIndexPatternChange={handleIndexPatternChange}
|
||||
handleCheckboxOptionChange={handleCheckboxOptionChange}
|
||||
handleNumberOptionChange={handleNumberOptionChange}
|
||||
handleParentChange={() => {}}
|
||||
parentCandidates={[]}
|
||||
/>);
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
// Ensure the state changes are reflected
|
||||
component.update();
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should display size field when dynamic options is disabled', async () => {
|
||||
const controlParams = {
|
||||
id: '1',
|
||||
indexPattern: 'mockIndexPattern',
|
||||
fieldName: 'keywordField',
|
||||
type: 'list',
|
||||
options: {
|
||||
type: 'terms',
|
||||
multiselect: true,
|
||||
dynamicOptions: false,
|
||||
size: 5,
|
||||
}
|
||||
};
|
||||
const component = shallow(<ListControlEditor
|
||||
getIndexPatterns={getIndexPatternsMock}
|
||||
getIndexPattern={getIndexPatternMock}
|
||||
controlIndex={0}
|
||||
controlParams={controlParams}
|
||||
handleFieldNameChange={handleFieldNameChange}
|
||||
handleIndexPatternChange={handleIndexPatternChange}
|
||||
handleCheckboxOptionChange={handleCheckboxOptionChange}
|
||||
handleNumberOptionChange={handleNumberOptionChange}
|
||||
handleParentChange={() => {}}
|
||||
parentCandidates={[]}
|
||||
/>);
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
// Ensure the state changes are reflected
|
||||
component.update();
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should display disabled dynamic options with tooltip for non-string fields', async () => {
|
||||
const controlParams = {
|
||||
id: '1',
|
||||
indexPattern: 'mockIndexPattern',
|
||||
fieldName: 'numberField',
|
||||
type: 'list',
|
||||
options: {
|
||||
type: 'terms',
|
||||
multiselect: true,
|
||||
dynamicOptions: true,
|
||||
size: 5,
|
||||
}
|
||||
};
|
||||
const component = shallow(<ListControlEditor
|
||||
getIndexPatterns={getIndexPatternsMock}
|
||||
getIndexPattern={getIndexPatternMock}
|
||||
controlIndex={0}
|
||||
controlParams={controlParams}
|
||||
handleFieldNameChange={handleFieldNameChange}
|
||||
handleIndexPatternChange={handleIndexPatternChange}
|
||||
handleCheckboxOptionChange={handleCheckboxOptionChange}
|
||||
handleNumberOptionChange={handleNumberOptionChange}
|
||||
handleParentChange={() => {}}
|
||||
parentCandidates={[]}
|
||||
/>);
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
// Ensure the state changes are reflected
|
||||
component.update();
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('parentCandidates', () => {
|
||||
const parentCandidates = [
|
||||
{ value: '1', text: 'fieldA' },
|
||||
{ value: '2', text: 'fieldB' }
|
||||
];
|
||||
const component = shallow(<ListControlEditor
|
||||
getIndexPatterns={getIndexPatternsMock}
|
||||
getIndexPattern={getIndexPatternMock}
|
||||
controlIndex={0}
|
||||
controlParams={controlParams}
|
||||
handleFieldNameChange={handleFieldNameChange}
|
||||
handleIndexPatternChange={handleIndexPatternChange}
|
||||
handleCheckboxOptionChange={handleCheckboxOptionChange}
|
||||
handleNumberOptionChange={handleNumberOptionChange}
|
||||
handleParentChange={() => {}}
|
||||
parentCandidates={parentCandidates}
|
||||
/>);
|
||||
expect(component).toMatchSnapshot(); // eslint-disable-line
|
||||
});
|
||||
|
||||
test('handleCheckboxOptionChange - multiselect', () => {
|
||||
test('handleCheckboxOptionChange - multiselect', async () => {
|
||||
const component = mount(<ListControlEditor
|
||||
getIndexPatterns={getIndexPatternsMock}
|
||||
getIndexPattern={getIndexPatternMock}
|
||||
|
@ -101,6 +232,12 @@ test('handleCheckboxOptionChange - multiselect', () => {
|
|||
handleParentChange={() => {}}
|
||||
parentCandidates={[]}
|
||||
/>);
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
// Ensure the state changes are reflected
|
||||
component.update();
|
||||
|
||||
const checkbox = findTestSubject(component, 'listControlMultiselectInput');
|
||||
checkbox.simulate('change', { target: { checked: true } });
|
||||
sinon.assert.notCalled(handleFieldNameChange);
|
||||
|
@ -120,7 +257,7 @@ test('handleCheckboxOptionChange - multiselect', () => {
|
|||
}, 'unexpected checkbox input event'));
|
||||
});
|
||||
|
||||
test('handleNumberOptionChange - size', () => {
|
||||
test('handleNumberOptionChange - size', async () => {
|
||||
const component = mount(<ListControlEditor
|
||||
getIndexPatterns={getIndexPatternsMock}
|
||||
getIndexPattern={getIndexPatternMock}
|
||||
|
@ -133,6 +270,12 @@ test('handleNumberOptionChange - size', () => {
|
|||
handleParentChange={() => {}}
|
||||
parentCandidates={[]}
|
||||
/>);
|
||||
|
||||
// Ensure all promises resolve
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
// Ensure the state changes are reflected
|
||||
component.update();
|
||||
|
||||
const input = findTestSubject(component, 'listControlSizeInput');
|
||||
input.simulate('change', { target: { value: 7 } });
|
||||
sinon.assert.notCalled(handleCheckboxOptionChange);
|
||||
|
|
|
@ -27,6 +27,7 @@ exports[`Apply and Cancel change btns enabled when there are changes 1`] = `
|
|||
<ListControl
|
||||
controlIndex={0}
|
||||
disableMsg={null}
|
||||
fetchOptions={[Function]}
|
||||
id="mock-list-control"
|
||||
label="list control"
|
||||
multiselect={true}
|
||||
|
@ -153,6 +154,7 @@ exports[`Clear btns enabled when there are values 1`] = `
|
|||
<ListControl
|
||||
controlIndex={0}
|
||||
disableMsg={null}
|
||||
fetchOptions={[Function]}
|
||||
id="mock-list-control"
|
||||
label="list control"
|
||||
multiselect={true}
|
||||
|
@ -279,6 +281,7 @@ exports[`Renders list control 1`] = `
|
|||
<ListControl
|
||||
controlIndex={0}
|
||||
disableMsg={null}
|
||||
fetchOptions={[Function]}
|
||||
id="mock-list-control"
|
||||
label="list control"
|
||||
multiselect={true}
|
||||
|
|
|
@ -9,6 +9,7 @@ exports[`renders ListControl 1`] = `
|
|||
<EuiComboBox
|
||||
data-test-subj="listControlSelect0"
|
||||
isClearable={true}
|
||||
isLoading={false}
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
|
|
|
@ -63,8 +63,10 @@ export class InputControlVis extends Component {
|
|||
selectedOptions={control.value}
|
||||
disableMsg={control.isEnabled() ? null : control.disabledReason}
|
||||
multiselect={control.options.multiselect}
|
||||
dynamicOptions={control.options.dynamicOptions}
|
||||
controlIndex={index}
|
||||
stageFilter={this.props.stageFilter}
|
||||
fetchOptions={query => { this.props.refreshControl(index, query); }}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
|
@ -158,5 +160,6 @@ InputControlVis.propTypes = {
|
|||
controls: PropTypes.array.isRequired,
|
||||
updateFiltersOnChange: PropTypes.bool,
|
||||
hasChanges: PropTypes.func.isRequired,
|
||||
hasValues: PropTypes.func.isRequired
|
||||
hasValues: PropTypes.func.isRequired,
|
||||
refreshControl: PropTypes.func.isRequired,
|
||||
};
|
||||
|
|
|
@ -78,6 +78,7 @@ test('Renders list control', () => {
|
|||
updateFiltersOnChange={updateFiltersOnChange}
|
||||
hasChanges={() => { return false; }}
|
||||
hasValues={() => { return false; }}
|
||||
refreshControl={() => {}}
|
||||
/>);
|
||||
expect(component).toMatchSnapshot(); // eslint-disable-line
|
||||
});
|
||||
|
@ -92,6 +93,7 @@ test('Renders range control', () => {
|
|||
updateFiltersOnChange={updateFiltersOnChange}
|
||||
hasChanges={() => { return false; }}
|
||||
hasValues={() => { return false; }}
|
||||
refreshControl={() => {}}
|
||||
/>);
|
||||
expect(component).toMatchSnapshot(); // eslint-disable-line
|
||||
});
|
||||
|
@ -106,6 +108,7 @@ test('Apply and Cancel change btns enabled when there are changes', () => {
|
|||
updateFiltersOnChange={updateFiltersOnChange}
|
||||
hasChanges={() => { return true; }}
|
||||
hasValues={() => { return false; }}
|
||||
refreshControl={() => {}}
|
||||
/>);
|
||||
expect(component).toMatchSnapshot(); // eslint-disable-line
|
||||
});
|
||||
|
@ -120,6 +123,7 @@ test('Clear btns enabled when there are values', () => {
|
|||
updateFiltersOnChange={updateFiltersOnChange}
|
||||
hasChanges={() => { return false; }}
|
||||
hasValues={() => { return true; }}
|
||||
refreshControl={() => {}}
|
||||
/>);
|
||||
expect(component).toMatchSnapshot(); // eslint-disable-line
|
||||
});
|
||||
|
@ -134,6 +138,7 @@ test('clearControls', () => {
|
|||
updateFiltersOnChange={updateFiltersOnChange}
|
||||
hasChanges={() => { return true; }}
|
||||
hasValues={() => { return true; }}
|
||||
refreshControl={() => {}}
|
||||
/>);
|
||||
findTestSubject(component, 'inputControlClearBtn').simulate('click');
|
||||
sinon.assert.calledOnce(clearControls);
|
||||
|
@ -152,6 +157,7 @@ test('submitFilters', () => {
|
|||
updateFiltersOnChange={updateFiltersOnChange}
|
||||
hasChanges={() => { return true; }}
|
||||
hasValues={() => { return true; }}
|
||||
refreshControl={() => {}}
|
||||
/>);
|
||||
findTestSubject(component, 'inputControlSubmitBtn').simulate('click');
|
||||
sinon.assert.calledOnce(submitFilters);
|
||||
|
@ -170,6 +176,7 @@ test('resetControls', () => {
|
|||
updateFiltersOnChange={updateFiltersOnChange}
|
||||
hasChanges={() => { return true; }}
|
||||
hasValues={() => { return true; }}
|
||||
refreshControl={() => {}}
|
||||
/>);
|
||||
findTestSubject(component, 'inputControlCancelBtn').simulate('click');
|
||||
sinon.assert.calledOnce(resetControls);
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import _ from 'lodash';
|
||||
import { FormRow } from './form_row';
|
||||
|
||||
import {
|
||||
|
@ -28,10 +29,38 @@ import {
|
|||
|
||||
export class ListControl extends Component {
|
||||
|
||||
state = {
|
||||
isLoading: false
|
||||
}
|
||||
|
||||
componentDidMount = () => {
|
||||
this._isMounted = true;
|
||||
}
|
||||
|
||||
componentWillUnmount = () => {
|
||||
this._isMounted = false;
|
||||
}
|
||||
|
||||
handleOnChange = (selectedOptions) => {
|
||||
this.props.stageFilter(this.props.controlIndex, selectedOptions);
|
||||
}
|
||||
|
||||
debouncedFetch = _.debounce(async (searchValue) => {
|
||||
await this.props.fetchOptions(searchValue);
|
||||
|
||||
if (this._isMounted) {
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
});
|
||||
}
|
||||
}, 300);
|
||||
|
||||
onSearchChange = (searchValue) => {
|
||||
this.setState({
|
||||
isLoading: true,
|
||||
}, this.debouncedFetch.bind(null, searchValue));
|
||||
}
|
||||
|
||||
renderControl() {
|
||||
if (this.props.disableMsg) {
|
||||
return (
|
||||
|
@ -54,6 +83,9 @@ export class ListControl extends Component {
|
|||
<EuiComboBox
|
||||
placeholder="Select..."
|
||||
options={options}
|
||||
isLoading={this.state.isLoading}
|
||||
async={this.props.dynamicOptions}
|
||||
onSearchChange={this.props.dynamicOptions ? this.onSearchChange : undefined}
|
||||
selectedOptions={this.props.selectedOptions}
|
||||
onChange={this.handleOnChange}
|
||||
singleSelection={!this.props.multiselect}
|
||||
|
@ -87,9 +119,16 @@ ListControl.propTypes = {
|
|||
selectedOptions: PropTypes.arrayOf(comboBoxOptionShape).isRequired,
|
||||
options: PropTypes.arrayOf(comboBoxOptionShape),
|
||||
disableMsg: PropTypes.string,
|
||||
multiselect: PropTypes.bool.isRequired,
|
||||
multiselect: PropTypes.bool,
|
||||
dynamicOptions: PropTypes.bool,
|
||||
controlIndex: PropTypes.number.isRequired,
|
||||
stageFilter: PropTypes.func.isRequired
|
||||
stageFilter: PropTypes.func.isRequired,
|
||||
fetchOptions: PropTypes.func,
|
||||
};
|
||||
|
||||
ListControl.defaultProps = {
|
||||
dynamicOptions: false,
|
||||
multiselect: true,
|
||||
};
|
||||
|
||||
ListControl.defaultProps = {
|
||||
|
|
|
@ -26,16 +26,22 @@ import {
|
|||
import { PhraseFilterManager } from './filter_manager/phrase_filter_manager';
|
||||
import { createSearchSource } from './create_search_source';
|
||||
|
||||
const termsAgg = (field, size, direction) => {
|
||||
if (size < 1) {
|
||||
size = 1;
|
||||
}
|
||||
function getEscapedQuery(query = '') {
|
||||
// https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html#_standard_operators
|
||||
return query.replace(/[.?+*|{}[\]()"\\#@&<>~]/g, (match) => `\\${match}`);
|
||||
}
|
||||
|
||||
const termsAgg = ({ field, size, direction, query }) => {
|
||||
const terms = {
|
||||
size: size,
|
||||
order: {
|
||||
_count: direction
|
||||
}
|
||||
};
|
||||
|
||||
if (size) {
|
||||
terms.size = size < 1 ? 1 : size;
|
||||
}
|
||||
|
||||
if (field.scripted) {
|
||||
terms.script = {
|
||||
inline: field.script,
|
||||
|
@ -45,6 +51,11 @@ const termsAgg = (field, size, direction) => {
|
|||
} else {
|
||||
terms.field = field.name;
|
||||
}
|
||||
|
||||
if (query) {
|
||||
terms.include = `.*${getEscapedQuery(query)}.*`;
|
||||
}
|
||||
|
||||
return {
|
||||
'termsAgg': {
|
||||
'terms': terms
|
||||
|
@ -54,7 +65,7 @@ const termsAgg = (field, size, direction) => {
|
|||
|
||||
class ListControl extends Control {
|
||||
|
||||
async fetch() {
|
||||
fetch = async (query) => {
|
||||
const indexPattern = this.filterManager.getIndexPattern();
|
||||
if (!indexPattern) {
|
||||
this.disable(noIndexPatternMsg(this.controlParams.indexPattern));
|
||||
|
@ -83,10 +94,12 @@ class ListControl extends Control {
|
|||
timeout: '1s',
|
||||
terminate_after: 100000
|
||||
};
|
||||
const aggs = termsAgg(
|
||||
indexPattern.fields.byName[fieldName],
|
||||
_.get(this.options, 'size', 5),
|
||||
'desc');
|
||||
const aggs = termsAgg({
|
||||
field: indexPattern.fields.byName[fieldName],
|
||||
size: this.options.dynamicOptions ? null : _.get(this.options, 'size', 5),
|
||||
direction: 'desc',
|
||||
query
|
||||
});
|
||||
const searchSource = createSearchSource(
|
||||
this.kbnApi,
|
||||
initialSearchSourceState,
|
||||
|
@ -95,6 +108,7 @@ class ListControl extends Control {
|
|||
this.useTimeFilter,
|
||||
ancestorFilters);
|
||||
|
||||
this.lastQuery = query;
|
||||
let resp;
|
||||
try {
|
||||
resp = await searchSource.fetch();
|
||||
|
@ -102,13 +116,19 @@ class ListControl extends Control {
|
|||
this.disable(`Unable to fetch terms, error: ${error.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (query && this.lastQuery !== query) {
|
||||
// search results returned out of order - ignore results from old query
|
||||
return;
|
||||
}
|
||||
|
||||
const selectOptions = _.get(resp, 'aggregations.termsAgg.buckets', []).map((bucket) => {
|
||||
return { label: this.format(bucket.key), value: bucket.key.toString() };
|
||||
}).sort((a, b) => {
|
||||
return a.label.toLowerCase().localeCompare(b.label.toLowerCase());
|
||||
});
|
||||
|
||||
if(selectOptions.length === 0) {
|
||||
if(selectOptions.length === 0 && !query) {
|
||||
this.disable(noValuesDisableMsg(fieldName, indexPattern.title));
|
||||
return;
|
||||
}
|
||||
|
@ -123,6 +143,16 @@ export async function listControlFactory(controlParams, kbnApi, useTimeFilter) {
|
|||
let indexPattern;
|
||||
try {
|
||||
indexPattern = await kbnApi.indexPatterns.get(controlParams.indexPattern);
|
||||
|
||||
// dynamic options are only allowed on String fields but the setting defaults to true so it could
|
||||
// be enabled for non-string fields (since UI input is hidden for non-string fields).
|
||||
// If field is not string, then disable dynamic options.
|
||||
const field = indexPattern.fields.find((field) => {
|
||||
return field.name === controlParams.fieldName;
|
||||
});
|
||||
if (field && field.type !== 'string') {
|
||||
controlParams.options.dynamicOptions = false;
|
||||
}
|
||||
} catch (err) {
|
||||
// ignore not found error and return control so it can be displayed in disabled state.
|
||||
}
|
||||
|
|
|
@ -75,6 +75,7 @@ export const getDefaultOptions = (type) => {
|
|||
case CONTROL_TYPES.LIST:
|
||||
defaultOptions.type = 'terms';
|
||||
defaultOptions.multiselect = true;
|
||||
defaultOptions.dynamicOptions = true;
|
||||
defaultOptions.size = 5;
|
||||
defaultOptions.order = 'desc';
|
||||
break;
|
||||
|
|
|
@ -48,17 +48,18 @@ class VisController {
|
|||
unmountComponentAtNode(this.el);
|
||||
}
|
||||
|
||||
drawVis() {
|
||||
drawVis = () => {
|
||||
render(
|
||||
<InputControlVis
|
||||
updateFiltersOnChange={this.vis.params.updateFiltersOnChange}
|
||||
controls={this.controls}
|
||||
stageFilter={this.stageFilter.bind(this)}
|
||||
submitFilters={this.submitFilters.bind(this)}
|
||||
resetControls={this.updateControlsFromKbn.bind(this)}
|
||||
clearControls={this.clearControls.bind(this)}
|
||||
hasChanges={this.hasChanges.bind(this)}
|
||||
hasValues={this.hasValues.bind(this)}
|
||||
stageFilter={this.stageFilter}
|
||||
submitFilters={this.submitFilters}
|
||||
resetControls={this.updateControlsFromKbn}
|
||||
clearControls={this.clearControls}
|
||||
hasChanges={this.hasChanges}
|
||||
hasValues={this.hasValues}
|
||||
refreshControl={this.refreshControl}
|
||||
/>,
|
||||
this.el);
|
||||
}
|
||||
|
@ -98,7 +99,7 @@ class VisController {
|
|||
return controls;
|
||||
}
|
||||
|
||||
async stageFilter(controlIndex, newValue) {
|
||||
stageFilter = async (controlIndex, newValue) => {
|
||||
this.controls[controlIndex].set(newValue);
|
||||
if (this.vis.params.updateFiltersOnChange) {
|
||||
// submit filters on each control change
|
||||
|
@ -110,7 +111,7 @@ class VisController {
|
|||
}
|
||||
}
|
||||
|
||||
submitFilters() {
|
||||
submitFilters = () => {
|
||||
// Clean up filter pills for nested controls that are now disabled because ancestors are not set
|
||||
this.controls.map(async (control) => {
|
||||
if (control.hasAncestors() && control.hasUnsetAncestor()) {
|
||||
|
@ -142,14 +143,14 @@ class VisController {
|
|||
this.vis.API.queryFilter.addFilters(newFilters, this.vis.params.pinFilters);
|
||||
}
|
||||
|
||||
clearControls() {
|
||||
clearControls = () => {
|
||||
this.controls.forEach((control) => {
|
||||
control.clear();
|
||||
});
|
||||
this.drawVis();
|
||||
}
|
||||
|
||||
async updateControlsFromKbn() {
|
||||
updateControlsFromKbn = async () => {
|
||||
this.controls.forEach((control) => {
|
||||
control.reset();
|
||||
});
|
||||
|
@ -166,7 +167,7 @@ class VisController {
|
|||
return await Promise.all(fetchPromises);
|
||||
}
|
||||
|
||||
hasChanges() {
|
||||
hasChanges = () => {
|
||||
return this.controls.map((control) => {
|
||||
return control.hasChanged();
|
||||
})
|
||||
|
@ -175,7 +176,7 @@ class VisController {
|
|||
});
|
||||
}
|
||||
|
||||
hasValues() {
|
||||
hasValues = () => {
|
||||
return this.controls.map((control) => {
|
||||
return control.hasValue();
|
||||
})
|
||||
|
@ -183,6 +184,11 @@ class VisController {
|
|||
return a || b;
|
||||
});
|
||||
}
|
||||
|
||||
refreshControl = async (controlIndex, query) => {
|
||||
await this.controls[controlIndex].fetch(query);
|
||||
this.drawVis();
|
||||
}
|
||||
}
|
||||
|
||||
export { VisController };
|
||||
|
|
|
@ -182,6 +182,49 @@ export default function ({ getService, getPageObjects }) {
|
|||
});
|
||||
});
|
||||
|
||||
describe('dynamic options', () => {
|
||||
beforeEach(async () => {
|
||||
await PageObjects.common.navigateToUrl('visualize', 'new');
|
||||
await PageObjects.visualize.clickInputControlVis();
|
||||
await PageObjects.visualize.clickVisEditorTab('controls');
|
||||
|
||||
await PageObjects.visualize.addInputControl();
|
||||
await PageObjects.visualize.setComboBox('indexPatternSelect-0', 'logstash');
|
||||
await PageObjects.common.sleep(1000); // give time for index-pattern to be fetched
|
||||
await PageObjects.visualize.setComboBox('fieldSelect-0', 'geo.src');
|
||||
|
||||
await PageObjects.visualize.clickGo();
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
});
|
||||
|
||||
it('should fetch new options when string field is filtered', async () => {
|
||||
const initialOptions = await PageObjects.visualize.getComboBoxOptions('listControlSelect0');
|
||||
expect(initialOptions.trim().split('\n').join()).to.equal('BD,BR,CN,ID,IN,JP,NG,PK,RU,US');
|
||||
|
||||
await PageObjects.visualize.filterComboBoxOptions('listControlSelect0', 'R');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
||||
const updatedOptions = await PageObjects.visualize.getComboBoxOptions('listControlSelect0');
|
||||
expect(updatedOptions.trim().split('\n').join()).to.equal('AR,BR,FR,GR,IR,KR,RO,RU,RW,TR');
|
||||
});
|
||||
|
||||
it('should not fetch new options when non-string is filtered', async () => {
|
||||
await PageObjects.visualize.setComboBox('fieldSelect-0', 'clientip');
|
||||
await PageObjects.visualize.clickGo();
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
||||
const initialOptions = await PageObjects.visualize.getComboBoxOptions('listControlSelect0');
|
||||
expect(initialOptions.trim().split('\n').join()).to.equal(
|
||||
'135.206.117.161,177.194.175.66,18.55.141.62,243.158.217.196,32.146.206.24');
|
||||
|
||||
await PageObjects.visualize.filterComboBoxOptions('listControlSelect0', '17');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
||||
const updatedOptions = await PageObjects.visualize.getComboBoxOptions('listControlSelect0');
|
||||
expect(updatedOptions.trim().split('\n').join()).to.equal('135.206.117.161,177.194.175.66,243.158.217.196');
|
||||
});
|
||||
});
|
||||
|
||||
describe('chained controls', () => {
|
||||
|
||||
before(async () => {
|
||||
|
@ -206,7 +249,7 @@ export default function ({ getService, getPageObjects }) {
|
|||
|
||||
it('should disable child control when parent control is not set', async () => {
|
||||
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('BD,BR,CN,ID,IN,JP,NG,PK,RU,US');
|
||||
|
||||
const childControlInput = await find.byCssSelector('[data-test-subj="inputControl1"] input');
|
||||
const isDisabled = await childControlInput.getProperty('disabled');
|
||||
|
|
|
@ -246,6 +246,14 @@ export function VisualizePageProvider({ getService, getPageObjects }) {
|
|||
await this.closeComboBoxOptionsList(element);
|
||||
}
|
||||
|
||||
async filterComboBoxOptions(comboBoxSelector, value) {
|
||||
const comboBox = await testSubjects.find(comboBoxSelector);
|
||||
const input = await comboBox.findByTagName('input');
|
||||
await input.clearValue();
|
||||
await input.type(value);
|
||||
await this.closeComboBoxOptionsList(comboBox);
|
||||
}
|
||||
|
||||
async getComboBoxOptions(comboBoxSelector) {
|
||||
await testSubjects.click(comboBoxSelector);
|
||||
const menu = await retry.try(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue