mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Defend Workflows] Fix bug when event filter value cannot be changed without using {backspace} (#192196)
## Summary This PR does 2 things around editing the value field for an Event Filter. 1. It fixes the issue when during editing an existing Event Filter you cannot update the value without pressing {backspace}, by clearing selection when user is typing:5da28aa254
Before:  ➡️ After:  2. Improves suggestions: before the change, suggestions were there initially, but after they are narrowed down by typing, they won't be displayed again after the user clears the input field. Thereforef9d60cf223
sets the suggestion search query unconditionally, helping with this issue. Before:  ➡️ After:  ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
fa748474c4
commit
b31d119e55
4 changed files with 200 additions and 34 deletions
|
@ -32,6 +32,13 @@ describe('AutocompleteFieldMatchComponent', () => {
|
|||
.fn()
|
||||
.mockResolvedValue([false, true, ['value 3', 'value 4'], jest.fn()]);
|
||||
|
||||
const findEuiComboBox = () =>
|
||||
wrapper.find(EuiComboBox).props() as unknown as {
|
||||
onChange: (a: EuiComboBoxOptionOption[]) => void;
|
||||
onSearchChange: (a: string) => void;
|
||||
onCreateOption: (a: string) => void;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
(useFieldValueAutocomplete as jest.Mock).mockReturnValue([
|
||||
false,
|
||||
|
@ -171,7 +178,7 @@ describe('AutocompleteFieldMatchComponent', () => {
|
|||
).toEqual('127.0.0.1');
|
||||
});
|
||||
|
||||
test('it invokes "onChange" when new value created', async () => {
|
||||
test('it invokes "onChange" when new value created', () => {
|
||||
const mockOnChange = jest.fn();
|
||||
wrapper = mount(
|
||||
<AutocompleteFieldMatchComponent
|
||||
|
@ -192,16 +199,12 @@ describe('AutocompleteFieldMatchComponent', () => {
|
|||
/>
|
||||
);
|
||||
|
||||
(
|
||||
wrapper.find(EuiComboBox).props() as unknown as {
|
||||
onCreateOption: (a: string) => void;
|
||||
}
|
||||
).onCreateOption('127.0.0.1');
|
||||
findEuiComboBox().onCreateOption('127.0.0.1');
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledWith('127.0.0.1');
|
||||
});
|
||||
|
||||
test('it invokes "onChange" when new value selected', async () => {
|
||||
test('it invokes "onChange" when new value selected', () => {
|
||||
const mockOnChange = jest.fn();
|
||||
wrapper = mount(
|
||||
<AutocompleteFieldMatchComponent
|
||||
|
@ -222,15 +225,39 @@ describe('AutocompleteFieldMatchComponent', () => {
|
|||
/>
|
||||
);
|
||||
|
||||
(
|
||||
wrapper.find(EuiComboBox).props() as unknown as {
|
||||
onChange: (a: EuiComboBoxOptionOption[]) => void;
|
||||
}
|
||||
).onChange([{ label: 'value 1' }]);
|
||||
findEuiComboBox().onChange([{ label: 'value 1' }]);
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledWith('value 1');
|
||||
});
|
||||
|
||||
test('it invokes "onChange" with empty value (i.e. clears selection) when new value searched', () => {
|
||||
const mockOnChange = jest.fn();
|
||||
wrapper = mount(
|
||||
<AutocompleteFieldMatchComponent
|
||||
autocompleteService={autocompleteStartMock}
|
||||
indexPattern={{
|
||||
fields,
|
||||
id: '1234',
|
||||
title: 'logstash-*',
|
||||
}}
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
onChange={mockOnChange}
|
||||
onError={jest.fn()}
|
||||
placeholder="Placeholder text"
|
||||
selectedField={getField('machine.os.raw')}
|
||||
selectedValue="value 1"
|
||||
/>
|
||||
);
|
||||
|
||||
act(() => {
|
||||
findEuiComboBox().onSearchChange('value 12');
|
||||
});
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledWith('');
|
||||
});
|
||||
|
||||
test('should show the warning helper text if the new value contains spaces when change', async () => {
|
||||
(useFieldValueAutocomplete as jest.Mock).mockReturnValue([
|
||||
false,
|
||||
|
@ -259,13 +286,7 @@ describe('AutocompleteFieldMatchComponent', () => {
|
|||
/>
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
(
|
||||
wrapper.find(EuiComboBox).props() as unknown as {
|
||||
onChange: (a: EuiComboBoxOptionOption[]) => void;
|
||||
}
|
||||
).onChange([{ label: ' value 1 ' }])
|
||||
);
|
||||
await waitFor(() => findEuiComboBox().onChange([{ label: ' value 1 ' }]));
|
||||
wrapper.update();
|
||||
expect(mockOnChange).toHaveBeenCalledWith(' value 1 ');
|
||||
|
||||
|
@ -293,11 +314,7 @@ describe('AutocompleteFieldMatchComponent', () => {
|
|||
/>
|
||||
);
|
||||
act(() => {
|
||||
(
|
||||
wrapper.find(EuiComboBox).props() as unknown as {
|
||||
onSearchChange: (a: string) => void;
|
||||
}
|
||||
).onSearchChange('value 1');
|
||||
findEuiComboBox().onSearchChange('value 1');
|
||||
});
|
||||
|
||||
expect(useFieldValueAutocomplete).toHaveBeenCalledWith({
|
||||
|
@ -313,6 +330,54 @@ describe('AutocompleteFieldMatchComponent', () => {
|
|||
selectedField: getField('machine.os.raw'),
|
||||
});
|
||||
});
|
||||
|
||||
test('it refreshes autocomplete with search query when input field is cleared', () => {
|
||||
wrapper = mount(
|
||||
<AutocompleteFieldMatchComponent
|
||||
autocompleteService={autocompleteStartMock}
|
||||
indexPattern={{
|
||||
fields,
|
||||
id: '1234',
|
||||
title: 'logstash-*',
|
||||
}}
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
onChange={jest.fn()}
|
||||
onError={jest.fn()}
|
||||
placeholder="Placeholder text"
|
||||
selectedField={getField('machine.os.raw')}
|
||||
selectedValue="windows"
|
||||
/>
|
||||
);
|
||||
|
||||
act(() => {
|
||||
findEuiComboBox().onSearchChange('value 1');
|
||||
});
|
||||
act(() => {
|
||||
findEuiComboBox().onSearchChange('');
|
||||
});
|
||||
|
||||
// 1st call is initial render, 2nd call sets the search query:
|
||||
expect(useFieldValueAutocomplete).toHaveBeenNthCalledWith(2, {
|
||||
autocompleteService: autocompleteStartMock,
|
||||
fieldValue: 'windows',
|
||||
indexPattern: { fields, id: '1234', title: 'logstash-*' },
|
||||
operatorType: 'match',
|
||||
query: 'value 1',
|
||||
selectedField: getField('machine.os.raw'),
|
||||
});
|
||||
// last call is the refresh when input field is cleared
|
||||
expect(useFieldValueAutocomplete).toHaveBeenLastCalledWith({
|
||||
autocompleteService: autocompleteStartMock,
|
||||
fieldValue: 'windows',
|
||||
indexPattern: { fields, id: '1234', title: 'logstash-*' },
|
||||
operatorType: 'match',
|
||||
query: '',
|
||||
selectedField: getField('machine.os.raw'),
|
||||
});
|
||||
});
|
||||
|
||||
test('should show the warning helper text if the new value contains spaces when searching a new query', () => {
|
||||
wrapper = mount(
|
||||
<AutocompleteFieldMatchComponent
|
||||
|
@ -333,11 +398,7 @@ describe('AutocompleteFieldMatchComponent', () => {
|
|||
/>
|
||||
);
|
||||
act(() => {
|
||||
(
|
||||
wrapper.find(EuiComboBox).props() as unknown as {
|
||||
onSearchChange: (a: string) => void;
|
||||
}
|
||||
).onSearchChange(' value 1');
|
||||
findEuiComboBox().onSearchChange(' value 1');
|
||||
});
|
||||
|
||||
wrapper.update();
|
||||
|
@ -345,6 +406,7 @@ describe('AutocompleteFieldMatchComponent', () => {
|
|||
expect(euiFormHelptext.length).toBeTruthy();
|
||||
expect(euiFormHelptext.text()).toEqual('Warning: there is a space');
|
||||
});
|
||||
|
||||
test('should show the warning helper text if selectedValue contains spaces when editing', () => {
|
||||
wrapper = mount(
|
||||
<AutocompleteFieldMatchComponent
|
||||
|
@ -368,6 +430,7 @@ describe('AutocompleteFieldMatchComponent', () => {
|
|||
expect(euiFormHelptext.length).toBeTruthy();
|
||||
expect(euiFormHelptext.text()).toEqual('Warning: there is a space');
|
||||
});
|
||||
|
||||
test('should not show the warning helper text if selectedValue is falsy', () => {
|
||||
wrapper = mount(
|
||||
<AutocompleteFieldMatchComponent
|
||||
|
|
|
@ -168,10 +168,26 @@ export const AutocompleteFieldMatchComponent: React.FC<AutocompleteFieldMatchPro
|
|||
handleWarning(warning);
|
||||
|
||||
if (!err) handleSpacesWarning(searchVal);
|
||||
setSearchQuery(searchVal);
|
||||
}
|
||||
|
||||
if (searchVal) {
|
||||
// Clear selected option when user types to allow user to modify value without {backspace}
|
||||
onChange('');
|
||||
}
|
||||
|
||||
// Update search query unconditionally to show correct suggestions even when input is cleared
|
||||
setSearchQuery(searchVal);
|
||||
},
|
||||
[handleError, handleSpacesWarning, isRequired, selectedField, touched, handleWarning, warning]
|
||||
[
|
||||
selectedField,
|
||||
onChange,
|
||||
isRequired,
|
||||
touched,
|
||||
handleError,
|
||||
handleWarning,
|
||||
warning,
|
||||
handleSpacesWarning,
|
||||
]
|
||||
);
|
||||
|
||||
const handleCreateOption = useCallback(
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { APP_EVENT_FILTERS_PATH } from '../../../../../common/constants';
|
||||
import type { ArtifactsFixtureType } from '../../fixtures/artifacts_page';
|
||||
import { getArtifactsListTestsData } from '../../fixtures/artifacts_page';
|
||||
import {
|
||||
createArtifactList,
|
||||
createPerPolicyArtifact,
|
||||
removeAllArtifacts,
|
||||
} from '../../tasks/artifacts';
|
||||
import { loadPage } from '../../tasks/common';
|
||||
import { indexEndpointHosts } from '../../tasks/index_endpoint_hosts';
|
||||
import { login } from '../../tasks/login';
|
||||
import type { ReturnTypeFromChainable } from '../../types';
|
||||
|
||||
describe('Event Filters', { tags: ['@ess', '@serverless'] }, () => {
|
||||
let endpointData: ReturnTypeFromChainable<typeof indexEndpointHosts> | undefined;
|
||||
|
||||
const CONDITION_VALUE = 'valuesAutocompleteMatch';
|
||||
const SUBMIT_BUTTON = 'EventFiltersListPage-flyout-submitButton';
|
||||
|
||||
before(() => {
|
||||
indexEndpointHosts().then((indexEndpoints) => {
|
||||
endpointData = indexEndpoints;
|
||||
});
|
||||
});
|
||||
|
||||
after(() => {
|
||||
removeAllArtifacts();
|
||||
|
||||
endpointData?.cleanup();
|
||||
endpointData = undefined;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
removeAllArtifacts();
|
||||
});
|
||||
|
||||
describe('when editing event filter value', () => {
|
||||
let eventFiltersMock: ArtifactsFixtureType;
|
||||
beforeEach(() => {
|
||||
login();
|
||||
|
||||
eventFiltersMock = getArtifactsListTestsData().find(
|
||||
({ tabId }) => tabId === 'eventFilters'
|
||||
) as ArtifactsFixtureType;
|
||||
|
||||
createArtifactList(eventFiltersMock.createRequestBody.list_id);
|
||||
createPerPolicyArtifact(eventFiltersMock.artifactName, eventFiltersMock.createRequestBody);
|
||||
|
||||
loadPage(APP_EVENT_FILTERS_PATH);
|
||||
|
||||
cy.getByTestSubj('EventFiltersListPage-card-header-actions-button').click();
|
||||
cy.getByTestSubj('EventFiltersListPage-card-cardEditAction').click();
|
||||
cy.getByTestSubj('EventFiltersListPage-flyout').should('exist');
|
||||
});
|
||||
|
||||
it('should be able to modify after deleting value with {backspace}', () => {
|
||||
cy.getByTestSubj(CONDITION_VALUE).type(' {backspace}.lnk{enter}');
|
||||
cy.getByTestSubj(SUBMIT_BUTTON).click();
|
||||
|
||||
cy.getByTestSubj('EventFiltersListPage-flyout').should('not.exist');
|
||||
cy.contains('notepad.exe.lnk');
|
||||
});
|
||||
|
||||
it('should be able to modify without using {backspace}', () => {
|
||||
cy.getByTestSubj(CONDITION_VALUE).type('.lnk{enter}');
|
||||
cy.getByTestSubj(SUBMIT_BUTTON).click();
|
||||
|
||||
cy.getByTestSubj('EventFiltersListPage-flyout').should('not.exist');
|
||||
cy.contains('notepad.exe.lnk');
|
||||
});
|
||||
|
||||
it('should show suggestions when filter value is cleared', () => {
|
||||
cy.getByTestSubj(CONDITION_VALUE).clear();
|
||||
cy.getByTestSubj(CONDITION_VALUE).type('aaaaaaaaaaaaaa as custom input');
|
||||
cy.get('button[role="option"]').should('have.length', 0);
|
||||
|
||||
cy.getByTestSubj(CONDITION_VALUE).find('input').clear();
|
||||
cy.get('button[role="option"]').should('have.length.above', 1);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -17,7 +17,7 @@ interface FormEditingDescription {
|
|||
}>;
|
||||
}
|
||||
|
||||
interface ArtifactsFixtureType {
|
||||
export interface ArtifactsFixtureType {
|
||||
title: string;
|
||||
pagePrefix: string;
|
||||
tabId: string;
|
||||
|
@ -275,10 +275,10 @@ export const getArtifactsListTestsData = (): ArtifactsFixtureType[] => [
|
|||
list_id: ENDPOINT_ARTIFACT_LISTS.eventFilters.id,
|
||||
entries: [
|
||||
{
|
||||
field: 'agent.id',
|
||||
field: 'process.name',
|
||||
operator: 'included',
|
||||
type: 'match',
|
||||
value: 'mr agent',
|
||||
value: 'notepad.exe',
|
||||
},
|
||||
],
|
||||
os_types: ['windows'],
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue