[Security Solution][Exceptions Builder] - Fixes operator selection bug (#71178)
### Summary This PR fixes two bugs in the exceptions builder. The first was that it was not allowing you to select any of the "excluded" operators. The second was that it was not adding the "and" badge when it should on initial render. It also adds unit tests for the EntryItemComponent.
This commit is contained in:
parent
3863921616
commit
8ad5ecef03
|
@ -0,0 +1,438 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import React from 'react';
|
||||||
|
import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
|
||||||
|
|
||||||
|
import { EntryItemComponent } from './entry_item';
|
||||||
|
import {
|
||||||
|
isOperator,
|
||||||
|
isNotOperator,
|
||||||
|
isOneOfOperator,
|
||||||
|
isNotOneOfOperator,
|
||||||
|
isInListOperator,
|
||||||
|
isNotInListOperator,
|
||||||
|
existsOperator,
|
||||||
|
doesNotExistOperator,
|
||||||
|
} from '../../autocomplete/operators';
|
||||||
|
import {
|
||||||
|
fields,
|
||||||
|
getField,
|
||||||
|
} from '../../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks.ts';
|
||||||
|
import { getFoundListSchemaMock } from '../../../../../../lists/common/schemas/response/found_list_schema.mock';
|
||||||
|
import { getEmptyValue } from '../../empty_value';
|
||||||
|
|
||||||
|
// mock out lists hook
|
||||||
|
const mockStart = jest.fn();
|
||||||
|
const mockResult = getFoundListSchemaMock();
|
||||||
|
jest.mock('../../../../common/lib/kibana');
|
||||||
|
jest.mock('../../../../lists_plugin_deps', () => {
|
||||||
|
const originalModule = jest.requireActual('../../../../lists_plugin_deps');
|
||||||
|
|
||||||
|
return {
|
||||||
|
...originalModule,
|
||||||
|
useFindLists: () => ({
|
||||||
|
loading: false,
|
||||||
|
start: mockStart.mockReturnValue(mockResult),
|
||||||
|
result: mockResult,
|
||||||
|
error: undefined,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('EntryItemComponent', () => {
|
||||||
|
test('it renders fields disabled if "isLoading" is "true"', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<EntryItemComponent
|
||||||
|
entry={{ field: undefined, operator: isOperator, value: undefined }}
|
||||||
|
entryIndex={0}
|
||||||
|
indexPattern={{
|
||||||
|
id: '1234',
|
||||||
|
title: 'logstash-*',
|
||||||
|
fields,
|
||||||
|
}}
|
||||||
|
showLabel={false}
|
||||||
|
isLoading={true}
|
||||||
|
onChange={jest.fn()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
wrapper.find('[data-test-subj="exceptionBuilderEntryField"] input').props().disabled
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
wrapper.find('[data-test-subj="exceptionBuilderEntryOperator"] input').props().disabled
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
wrapper.find('[data-test-subj="exceptionBuilderEntryFieldMatch"] input').props().disabled
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(wrapper.find('[data-test-subj="exceptionBuilderEntryFieldFormRow"]')).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it renders field labels if "showLabel" is "true"', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<EntryItemComponent
|
||||||
|
entry={{ field: undefined, operator: isOperator, value: undefined }}
|
||||||
|
entryIndex={0}
|
||||||
|
indexPattern={{
|
||||||
|
id: '1234',
|
||||||
|
title: 'logstash-*',
|
||||||
|
fields,
|
||||||
|
}}
|
||||||
|
showLabel={true}
|
||||||
|
isLoading={false}
|
||||||
|
onChange={jest.fn()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find('[data-test-subj="exceptionBuilderEntryFieldFormRow"]')).not.toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it renders field values correctly when operator is "isOperator"', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<EntryItemComponent
|
||||||
|
entry={{ field: getField('ip'), operator: isOperator, value: '1234' }}
|
||||||
|
entryIndex={0}
|
||||||
|
indexPattern={{
|
||||||
|
id: '1234',
|
||||||
|
title: 'logstash-*',
|
||||||
|
fields,
|
||||||
|
}}
|
||||||
|
showLabel={false}
|
||||||
|
isLoading={false}
|
||||||
|
onChange={jest.fn()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find('[data-test-subj="exceptionBuilderEntryField"]').text()).toEqual('ip');
|
||||||
|
expect(wrapper.find('[data-test-subj="exceptionBuilderEntryOperator"]').text()).toEqual('is');
|
||||||
|
expect(wrapper.find('[data-test-subj="exceptionBuilderEntryFieldMatch"]').text()).toEqual(
|
||||||
|
'1234'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it renders field values correctly when operator is "isNotOperator"', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<EntryItemComponent
|
||||||
|
entry={{ field: getField('ip'), operator: isNotOperator, value: '1234' }}
|
||||||
|
entryIndex={0}
|
||||||
|
indexPattern={{
|
||||||
|
id: '1234',
|
||||||
|
title: 'logstash-*',
|
||||||
|
fields,
|
||||||
|
}}
|
||||||
|
showLabel={false}
|
||||||
|
isLoading={false}
|
||||||
|
onChange={jest.fn()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find('[data-test-subj="exceptionBuilderEntryField"]').text()).toEqual('ip');
|
||||||
|
expect(wrapper.find('[data-test-subj="exceptionBuilderEntryOperator"]').text()).toEqual(
|
||||||
|
'is not'
|
||||||
|
);
|
||||||
|
expect(wrapper.find('[data-test-subj="exceptionBuilderEntryFieldMatch"]').text()).toEqual(
|
||||||
|
'1234'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it renders field values correctly when operator is "isOneOfOperator"', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<EntryItemComponent
|
||||||
|
entry={{ field: getField('ip'), operator: isOneOfOperator, value: ['1234'] }}
|
||||||
|
entryIndex={0}
|
||||||
|
indexPattern={{
|
||||||
|
id: '1234',
|
||||||
|
title: 'logstash-*',
|
||||||
|
fields,
|
||||||
|
}}
|
||||||
|
showLabel={false}
|
||||||
|
isLoading={false}
|
||||||
|
onChange={jest.fn()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find('[data-test-subj="exceptionBuilderEntryField"]').text()).toEqual('ip');
|
||||||
|
expect(wrapper.find('[data-test-subj="exceptionBuilderEntryOperator"]').text()).toEqual(
|
||||||
|
'is one of'
|
||||||
|
);
|
||||||
|
expect(wrapper.find('[data-test-subj="exceptionBuilderEntryFieldMatchAny"]').text()).toEqual(
|
||||||
|
'1234'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it renders field values correctly when operator is "isNotOneOfOperator"', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<EntryItemComponent
|
||||||
|
entry={{ field: getField('ip'), operator: isNotOneOfOperator, value: ['1234'] }}
|
||||||
|
entryIndex={0}
|
||||||
|
indexPattern={{
|
||||||
|
id: '1234',
|
||||||
|
title: 'logstash-*',
|
||||||
|
fields,
|
||||||
|
}}
|
||||||
|
showLabel={false}
|
||||||
|
isLoading={false}
|
||||||
|
onChange={jest.fn()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find('[data-test-subj="exceptionBuilderEntryField"]').text()).toEqual('ip');
|
||||||
|
expect(wrapper.find('[data-test-subj="exceptionBuilderEntryOperator"]').text()).toEqual(
|
||||||
|
'is not one of'
|
||||||
|
);
|
||||||
|
expect(wrapper.find('[data-test-subj="exceptionBuilderEntryFieldMatchAny"]').text()).toEqual(
|
||||||
|
'1234'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it renders field values correctly when operator is "isInListOperator"', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<EntryItemComponent
|
||||||
|
entry={{ field: getField('ip'), operator: isInListOperator, value: 'some-list-id' }}
|
||||||
|
entryIndex={0}
|
||||||
|
indexPattern={{
|
||||||
|
id: '1234',
|
||||||
|
title: 'logstash-*',
|
||||||
|
fields,
|
||||||
|
}}
|
||||||
|
showLabel={false}
|
||||||
|
isLoading={false}
|
||||||
|
onChange={jest.fn()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find('[data-test-subj="exceptionBuilderEntryField"]').text()).toEqual('ip');
|
||||||
|
expect(wrapper.find('[data-test-subj="exceptionBuilderEntryOperator"]').text()).toEqual(
|
||||||
|
'is in list'
|
||||||
|
);
|
||||||
|
expect(wrapper.find('[data-test-subj="exceptionBuilderEntryFieldList"]').text()).toEqual(
|
||||||
|
'some name'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it renders field values correctly when operator is "isNotInListOperator"', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<EntryItemComponent
|
||||||
|
entry={{ field: getField('ip'), operator: isNotInListOperator, value: 'some-list-id' }}
|
||||||
|
entryIndex={0}
|
||||||
|
indexPattern={{
|
||||||
|
id: '1234',
|
||||||
|
title: 'logstash-*',
|
||||||
|
fields,
|
||||||
|
}}
|
||||||
|
showLabel={false}
|
||||||
|
isLoading={false}
|
||||||
|
onChange={jest.fn()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find('[data-test-subj="exceptionBuilderEntryField"]').text()).toEqual('ip');
|
||||||
|
expect(wrapper.find('[data-test-subj="exceptionBuilderEntryOperator"]').text()).toEqual(
|
||||||
|
'is not in list'
|
||||||
|
);
|
||||||
|
expect(wrapper.find('[data-test-subj="exceptionBuilderEntryFieldList"]').text()).toEqual(
|
||||||
|
'some name'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it renders field values correctly when operator is "existsOperator"', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<EntryItemComponent
|
||||||
|
entry={{ field: getField('ip'), operator: existsOperator, value: undefined }}
|
||||||
|
entryIndex={0}
|
||||||
|
indexPattern={{
|
||||||
|
id: '1234',
|
||||||
|
title: 'logstash-*',
|
||||||
|
fields,
|
||||||
|
}}
|
||||||
|
showLabel={false}
|
||||||
|
isLoading={false}
|
||||||
|
onChange={jest.fn()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find('[data-test-subj="exceptionBuilderEntryField"]').text()).toEqual('ip');
|
||||||
|
expect(wrapper.find('[data-test-subj="exceptionBuilderEntryOperator"]').text()).toEqual(
|
||||||
|
'exists'
|
||||||
|
);
|
||||||
|
expect(wrapper.find('[data-test-subj="exceptionBuilderEntryFieldExists"]').text()).toEqual(
|
||||||
|
getEmptyValue()
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
wrapper.find('[data-test-subj="exceptionBuilderEntryFieldExists"] input').props().disabled
|
||||||
|
).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it renders field values correctly when operator is "doesNotExistOperator"', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<EntryItemComponent
|
||||||
|
entry={{ field: getField('ip'), operator: doesNotExistOperator, value: undefined }}
|
||||||
|
entryIndex={0}
|
||||||
|
indexPattern={{
|
||||||
|
id: '1234',
|
||||||
|
title: 'logstash-*',
|
||||||
|
fields,
|
||||||
|
}}
|
||||||
|
showLabel={false}
|
||||||
|
isLoading={false}
|
||||||
|
onChange={jest.fn()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find('[data-test-subj="exceptionBuilderEntryField"]').text()).toEqual('ip');
|
||||||
|
expect(wrapper.find('[data-test-subj="exceptionBuilderEntryOperator"]').text()).toEqual(
|
||||||
|
'does not exist'
|
||||||
|
);
|
||||||
|
expect(wrapper.find('[data-test-subj="exceptionBuilderEntryFieldExists"]').text()).toEqual(
|
||||||
|
getEmptyValue()
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
wrapper.find('[data-test-subj="exceptionBuilderEntryFieldExists"] input').props().disabled
|
||||||
|
).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it invokes "onChange" when new field is selected and resets operator and value fields', () => {
|
||||||
|
const mockOnChange = jest.fn();
|
||||||
|
const wrapper = mount(
|
||||||
|
<EntryItemComponent
|
||||||
|
entry={{ field: getField('ip'), operator: isOperator, value: '1234' }}
|
||||||
|
entryIndex={0}
|
||||||
|
indexPattern={{
|
||||||
|
id: '1234',
|
||||||
|
title: 'logstash-*',
|
||||||
|
fields,
|
||||||
|
}}
|
||||||
|
showLabel={false}
|
||||||
|
isLoading={false}
|
||||||
|
onChange={mockOnChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
((wrapper.find(EuiComboBox).at(0).props() as unknown) as {
|
||||||
|
onChange: (a: EuiComboBoxOptionOption[]) => void;
|
||||||
|
}).onChange([{ label: 'machine.os' }]);
|
||||||
|
|
||||||
|
expect(mockOnChange).toHaveBeenCalledWith(
|
||||||
|
{ field: 'machine.os', operator: 'included', type: 'match', value: undefined },
|
||||||
|
0
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it invokes "onChange" when new operator is selected and resets value field', () => {
|
||||||
|
const mockOnChange = jest.fn();
|
||||||
|
const wrapper = mount(
|
||||||
|
<EntryItemComponent
|
||||||
|
entry={{ field: getField('ip'), operator: isOperator, value: '1234' }}
|
||||||
|
entryIndex={0}
|
||||||
|
indexPattern={{
|
||||||
|
id: '1234',
|
||||||
|
title: 'logstash-*',
|
||||||
|
fields,
|
||||||
|
}}
|
||||||
|
showLabel={false}
|
||||||
|
isLoading={false}
|
||||||
|
onChange={mockOnChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
((wrapper.find(EuiComboBox).at(1).props() as unknown) as {
|
||||||
|
onChange: (a: EuiComboBoxOptionOption[]) => void;
|
||||||
|
}).onChange([{ label: 'is not' }]);
|
||||||
|
|
||||||
|
expect(mockOnChange).toHaveBeenCalledWith(
|
||||||
|
{ field: 'ip', operator: 'excluded', type: 'match', value: '' },
|
||||||
|
0
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it invokes "onChange" when new value field is entered for match operator', () => {
|
||||||
|
const mockOnChange = jest.fn();
|
||||||
|
const wrapper = mount(
|
||||||
|
<EntryItemComponent
|
||||||
|
entry={{ field: getField('ip'), operator: isNotOperator, value: '1234' }}
|
||||||
|
entryIndex={0}
|
||||||
|
indexPattern={{
|
||||||
|
id: '1234',
|
||||||
|
title: 'logstash-*',
|
||||||
|
fields,
|
||||||
|
}}
|
||||||
|
showLabel={false}
|
||||||
|
isLoading={false}
|
||||||
|
onChange={mockOnChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
((wrapper.find(EuiComboBox).at(2).props() as unknown) as {
|
||||||
|
onCreateOption: (a: string) => void;
|
||||||
|
}).onCreateOption('126.45.211.34');
|
||||||
|
|
||||||
|
expect(mockOnChange).toHaveBeenCalledWith(
|
||||||
|
{ field: 'ip', operator: 'excluded', type: 'match', value: '126.45.211.34' },
|
||||||
|
0
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it invokes "onChange" when new value field is entered for match_any operator', () => {
|
||||||
|
const mockOnChange = jest.fn();
|
||||||
|
const wrapper = mount(
|
||||||
|
<EntryItemComponent
|
||||||
|
entry={{ field: getField('ip'), operator: isOneOfOperator, value: '1234' }}
|
||||||
|
entryIndex={0}
|
||||||
|
indexPattern={{
|
||||||
|
id: '1234',
|
||||||
|
title: 'logstash-*',
|
||||||
|
fields,
|
||||||
|
}}
|
||||||
|
showLabel={false}
|
||||||
|
isLoading={false}
|
||||||
|
onChange={mockOnChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
((wrapper.find(EuiComboBox).at(2).props() as unknown) as {
|
||||||
|
onCreateOption: (a: string) => void;
|
||||||
|
}).onCreateOption('126.45.211.34');
|
||||||
|
|
||||||
|
expect(mockOnChange).toHaveBeenCalledWith(
|
||||||
|
{ field: 'ip', operator: 'included', type: 'match_any', value: ['126.45.211.34'] },
|
||||||
|
0
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it invokes "onChange" when new value field is entered for list operator', () => {
|
||||||
|
const mockOnChange = jest.fn();
|
||||||
|
const wrapper = mount(
|
||||||
|
<EntryItemComponent
|
||||||
|
entry={{ field: getField('ip'), operator: isNotInListOperator, value: '1234' }}
|
||||||
|
entryIndex={0}
|
||||||
|
indexPattern={{
|
||||||
|
id: '1234',
|
||||||
|
title: 'logstash-*',
|
||||||
|
fields,
|
||||||
|
}}
|
||||||
|
showLabel={false}
|
||||||
|
isLoading={false}
|
||||||
|
onChange={mockOnChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
((wrapper.find(EuiComboBox).at(2).props() as unknown) as {
|
||||||
|
onChange: (a: EuiComboBoxOptionOption[]) => void;
|
||||||
|
}).onChange([{ label: 'some name' }]);
|
||||||
|
|
||||||
|
expect(mockOnChange).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
field: 'ip',
|
||||||
|
operator: 'excluded',
|
||||||
|
type: 'list',
|
||||||
|
list: { id: 'some-list-id', type: 'ip' },
|
||||||
|
},
|
||||||
|
0
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -67,13 +67,13 @@ export const EntryItemComponent: React.FC<EntryItemProps> = ({
|
||||||
{
|
{
|
||||||
field: entry.field != null ? entry.field.name : undefined,
|
field: entry.field != null ? entry.field.name : undefined,
|
||||||
type: OperatorTypeEnum.MATCH,
|
type: OperatorTypeEnum.MATCH,
|
||||||
operator: isOperator.operator,
|
operator: entry.operator.operator,
|
||||||
value: newField,
|
value: newField,
|
||||||
},
|
},
|
||||||
entryIndex
|
entryIndex
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[onChange, entryIndex, entry.field]
|
[onChange, entryIndex, entry.field, entry.operator.operator]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFieldMatchAnyValueChange = useCallback(
|
const handleFieldMatchAnyValueChange = useCallback(
|
||||||
|
@ -82,13 +82,13 @@ export const EntryItemComponent: React.FC<EntryItemProps> = ({
|
||||||
{
|
{
|
||||||
field: entry.field != null ? entry.field.name : undefined,
|
field: entry.field != null ? entry.field.name : undefined,
|
||||||
type: OperatorTypeEnum.MATCH_ANY,
|
type: OperatorTypeEnum.MATCH_ANY,
|
||||||
operator: isOperator.operator,
|
operator: entry.operator.operator,
|
||||||
value: newField,
|
value: newField,
|
||||||
},
|
},
|
||||||
entryIndex
|
entryIndex
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[onChange, entryIndex, entry.field]
|
[onChange, entryIndex, entry.field, entry.operator.operator]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFieldListValueChange = useCallback(
|
const handleFieldListValueChange = useCallback(
|
||||||
|
@ -97,13 +97,13 @@ export const EntryItemComponent: React.FC<EntryItemProps> = ({
|
||||||
{
|
{
|
||||||
field: entry.field != null ? entry.field.name : undefined,
|
field: entry.field != null ? entry.field.name : undefined,
|
||||||
type: OperatorTypeEnum.LIST,
|
type: OperatorTypeEnum.LIST,
|
||||||
operator: isOperator.operator,
|
operator: entry.operator.operator,
|
||||||
list: { id: newField.id, type: newField.type },
|
list: { id: newField.id, type: newField.type },
|
||||||
},
|
},
|
||||||
entryIndex
|
entryIndex
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[onChange, entryIndex, entry.field]
|
[onChange, entryIndex, entry.field, entry.operator.operator]
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderFieldInput = (isFirst: boolean): JSX.Element => {
|
const renderFieldInput = (isFirst: boolean): JSX.Element => {
|
||||||
|
@ -114,9 +114,9 @@ export const EntryItemComponent: React.FC<EntryItemProps> = ({
|
||||||
selectedField={entry.field}
|
selectedField={entry.field}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
isClearable={false}
|
isClearable={false}
|
||||||
isDisabled={indexPattern == null}
|
isDisabled={isLoading}
|
||||||
onChange={handleFieldChange}
|
onChange={handleFieldChange}
|
||||||
data-test-subj="filterFieldSuggestionList"
|
data-test-subj="exceptionBuilderEntryField"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -137,11 +137,11 @@ export const EntryItemComponent: React.FC<EntryItemProps> = ({
|
||||||
placeholder={i18n.EXCEPTION_OPERATOR_PLACEHOLDER}
|
placeholder={i18n.EXCEPTION_OPERATOR_PLACEHOLDER}
|
||||||
selectedField={entry.field}
|
selectedField={entry.field}
|
||||||
operator={entry.operator}
|
operator={entry.operator}
|
||||||
isDisabled={false}
|
isDisabled={isLoading}
|
||||||
isLoading={false}
|
isLoading={false}
|
||||||
isClearable={false}
|
isClearable={false}
|
||||||
onChange={handleOperatorChange}
|
onChange={handleOperatorChange}
|
||||||
data-test-subj="filterFieldSuggestionList"
|
data-test-subj="exceptionBuilderEntryOperator"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -165,12 +165,12 @@ export const EntryItemComponent: React.FC<EntryItemProps> = ({
|
||||||
placeholder={i18n.EXCEPTION_FIELD_VALUE_PLACEHOLDER}
|
placeholder={i18n.EXCEPTION_FIELD_VALUE_PLACEHOLDER}
|
||||||
selectedField={entry.field}
|
selectedField={entry.field}
|
||||||
selectedValue={value}
|
selectedValue={value}
|
||||||
isDisabled={false}
|
isDisabled={isLoading}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
isClearable={false}
|
isClearable={false}
|
||||||
indexPattern={indexPattern}
|
indexPattern={indexPattern}
|
||||||
onChange={handleFieldMatchValueChange}
|
onChange={handleFieldMatchValueChange}
|
||||||
data-test-subj="filterFieldSuggestionList"
|
data-test-subj="exceptionBuilderEntryFieldMatch"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case OperatorTypeEnum.MATCH_ANY:
|
case OperatorTypeEnum.MATCH_ANY:
|
||||||
|
@ -180,12 +180,12 @@ export const EntryItemComponent: React.FC<EntryItemProps> = ({
|
||||||
placeholder={i18n.EXCEPTION_FIELD_VALUE_PLACEHOLDER}
|
placeholder={i18n.EXCEPTION_FIELD_VALUE_PLACEHOLDER}
|
||||||
selectedField={entry.field}
|
selectedField={entry.field}
|
||||||
selectedValue={values}
|
selectedValue={values}
|
||||||
isDisabled={false}
|
isDisabled={isLoading}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
isClearable={false}
|
isClearable={false}
|
||||||
indexPattern={indexPattern}
|
indexPattern={indexPattern}
|
||||||
onChange={handleFieldMatchAnyValueChange}
|
onChange={handleFieldMatchAnyValueChange}
|
||||||
data-test-subj="filterFieldSuggestionList"
|
data-test-subj="exceptionBuilderEntryFieldMatchAny"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case OperatorTypeEnum.LIST:
|
case OperatorTypeEnum.LIST:
|
||||||
|
@ -195,17 +195,18 @@ export const EntryItemComponent: React.FC<EntryItemProps> = ({
|
||||||
selectedField={entry.field}
|
selectedField={entry.field}
|
||||||
placeholder={i18n.EXCEPTION_FIELD_LISTS_PLACEHOLDER}
|
placeholder={i18n.EXCEPTION_FIELD_LISTS_PLACEHOLDER}
|
||||||
selectedValue={id}
|
selectedValue={id}
|
||||||
isLoading={false}
|
isLoading={isLoading}
|
||||||
isDisabled={false}
|
isDisabled={isLoading}
|
||||||
isClearable={false}
|
isClearable={false}
|
||||||
onChange={handleFieldListValueChange}
|
onChange={handleFieldListValueChange}
|
||||||
|
data-test-subj="exceptionBuilderEntryFieldList"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case OperatorTypeEnum.EXISTS:
|
case OperatorTypeEnum.EXISTS:
|
||||||
return (
|
return (
|
||||||
<AutocompleteFieldExistsComponent
|
<AutocompleteFieldExistsComponent
|
||||||
placeholder={getEmptyValue()}
|
placeholder={getEmptyValue()}
|
||||||
data-test-subj="filterFieldSuggestionList"
|
data-test-subj="exceptionBuilderEntryFieldExists"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -29,6 +29,7 @@ interface ExceptionListItemProps {
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
indexPattern: IIndexPattern;
|
indexPattern: IIndexPattern;
|
||||||
andLogicIncluded: boolean;
|
andLogicIncluded: boolean;
|
||||||
|
onCheckAndLogic: (item: ExceptionsBuilderExceptionItem[]) => void;
|
||||||
onDeleteExceptionItem: (item: ExceptionsBuilderExceptionItem, index: number) => void;
|
onDeleteExceptionItem: (item: ExceptionsBuilderExceptionItem, index: number) => void;
|
||||||
onExceptionItemChange: (item: ExceptionsBuilderExceptionItem, index: number) => void;
|
onExceptionItemChange: (item: ExceptionsBuilderExceptionItem, index: number) => void;
|
||||||
}
|
}
|
||||||
|
@ -41,6 +42,7 @@ export const ExceptionListItemComponent = React.memo<ExceptionListItemProps>(
|
||||||
indexPattern,
|
indexPattern,
|
||||||
isLoading,
|
isLoading,
|
||||||
andLogicIncluded,
|
andLogicIncluded,
|
||||||
|
onCheckAndLogic,
|
||||||
onDeleteExceptionItem,
|
onDeleteExceptionItem,
|
||||||
onExceptionItemChange,
|
onExceptionItemChange,
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -70,11 +72,12 @@ export const ExceptionListItemComponent = React.memo<ExceptionListItemProps>(
|
||||||
onDeleteExceptionItem(updatedExceptionItem, exceptionItemIndex);
|
onDeleteExceptionItem(updatedExceptionItem, exceptionItemIndex);
|
||||||
};
|
};
|
||||||
|
|
||||||
const entries = useMemo(
|
const entries = useMemo((): FormattedBuilderEntry[] => {
|
||||||
(): FormattedBuilderEntry[] =>
|
onCheckAndLogic([exceptionItem]);
|
||||||
indexPattern != null ? getFormattedBuilderEntries(indexPattern, exceptionItem.entries) : [],
|
return indexPattern != null
|
||||||
[indexPattern, exceptionItem.entries]
|
? getFormattedBuilderEntries(indexPattern, exceptionItem.entries)
|
||||||
);
|
: [];
|
||||||
|
}, [indexPattern, exceptionItem, onCheckAndLogic]);
|
||||||
|
|
||||||
const andBadge = useMemo((): JSX.Element => {
|
const andBadge = useMemo((): JSX.Element => {
|
||||||
const badge = <AndOrBadge includeAntennas type="and" />;
|
const badge = <AndOrBadge includeAntennas type="and" />;
|
||||||
|
|
|
@ -77,15 +77,21 @@ export const ExceptionBuilder = ({
|
||||||
indexPatternConfig ?? []
|
indexPatternConfig ?? []
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleCheckAndLogic = (items: ExceptionsBuilderExceptionItem[]): void => {
|
||||||
|
setAndLogicIncluded((includesAnd: boolean): boolean => {
|
||||||
|
if (includesAnd) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return items.filter(({ entries }) => entries.length > 1).length > 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Bubble up changes to parent
|
// Bubble up changes to parent
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onChange({ exceptionItems: filterExceptionItems(exceptions), exceptionsToDelete });
|
onChange({ exceptionItems: filterExceptionItems(exceptions), exceptionsToDelete });
|
||||||
}, [onChange, exceptionsToDelete, exceptions]);
|
}, [onChange, exceptionsToDelete, exceptions]);
|
||||||
|
|
||||||
const checkAndLogic = (items: ExceptionsBuilderExceptionItem[]): void => {
|
|
||||||
setAndLogicIncluded(items.filter(({ entries }) => entries.length > 1).length > 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeleteExceptionItem = (
|
const handleDeleteExceptionItem = (
|
||||||
item: ExceptionsBuilderExceptionItem,
|
item: ExceptionsBuilderExceptionItem,
|
||||||
itemIndex: number
|
itemIndex: number
|
||||||
|
@ -100,7 +106,7 @@ export const ExceptionBuilder = ({
|
||||||
...existingExceptions.slice(0, itemIndex),
|
...existingExceptions.slice(0, itemIndex),
|
||||||
...existingExceptions.slice(itemIndex + 1),
|
...existingExceptions.slice(itemIndex + 1),
|
||||||
];
|
];
|
||||||
checkAndLogic(updatedExceptions);
|
handleCheckAndLogic(updatedExceptions);
|
||||||
|
|
||||||
return updatedExceptions;
|
return updatedExceptions;
|
||||||
});
|
});
|
||||||
|
@ -118,7 +124,7 @@ export const ExceptionBuilder = ({
|
||||||
...exceptions.slice(index + 1),
|
...exceptions.slice(index + 1),
|
||||||
];
|
];
|
||||||
|
|
||||||
checkAndLogic(updatedExceptions);
|
handleCheckAndLogic(updatedExceptions);
|
||||||
setExceptions(updatedExceptions);
|
setExceptions(updatedExceptions);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -214,6 +220,7 @@ export const ExceptionBuilder = ({
|
||||||
isLoading={indexPatternLoading}
|
isLoading={indexPatternLoading}
|
||||||
exceptionItemIndex={index}
|
exceptionItemIndex={index}
|
||||||
andLogicIncluded={andLogicIncluded}
|
andLogicIncluded={andLogicIncluded}
|
||||||
|
onCheckAndLogic={handleCheckAndLogic}
|
||||||
onDeleteExceptionItem={handleDeleteExceptionItem}
|
onDeleteExceptionItem={handleDeleteExceptionItem}
|
||||||
onExceptionItemChange={handleExceptionItemChange}
|
onExceptionItemChange={handleExceptionItemChange}
|
||||||
/>
|
/>
|
||||||
|
|
Loading…
Reference in a new issue