From 8198dc84b5d1601f89a16a98c699f3033c35f973 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C5=91=20=C3=81brah=C3=A1m?= Date: Thu, 29 Aug 2024 13:17:09 +0200 Subject: [PATCH 1/8] add acceptance test --- .../cypress/e2e/artifacts/event_filters.cy.ts | 78 +++++++++++++++++++ .../cypress/fixtures/artifacts_page.ts | 2 +- 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/event_filters.cy.ts diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/event_filters.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/event_filters.cy.ts new file mode 100644 index 0000000000000..61e97513d4553 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/event_filters.cy.ts @@ -0,0 +1,78 @@ +/* + * 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 | 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} and co{enter}'); + cy.getByTestSubj(SUBMIT_BUTTON).click(); + + cy.getByTestSubj('EventFiltersListPage-flyout').should('not.exist'); + cy.contains('mr agent and co'); + }); + + it('should be able to modify without using {backspace}', () => { + cy.getByTestSubj(CONDITION_VALUE).type(' and co{enter}'); + cy.getByTestSubj(SUBMIT_BUTTON).click(); + + cy.getByTestSubj('EventFiltersListPage-flyout').should('not.exist'); + cy.contains('mr agent and co'); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/cypress/fixtures/artifacts_page.ts b/x-pack/plugins/security_solution/public/management/cypress/fixtures/artifacts_page.ts index 97a6807a59f0a..c97c3c912606a 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/fixtures/artifacts_page.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/fixtures/artifacts_page.ts @@ -17,7 +17,7 @@ interface FormEditingDescription { }>; } -interface ArtifactsFixtureType { +export interface ArtifactsFixtureType { title: string; pagePrefix: string; tabId: string; From 5da28aa25404e6a2bdc8a2e9cfce2f6546976d07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C5=91=20=C3=81brah=C3=A1m?= Date: Thu, 5 Sep 2024 10:56:39 +0200 Subject: [PATCH 2/8] clear selection in autocomplete field component when user types --- .../src/field_value_match/index.test.tsx | 35 +++++++++++++++++++ .../src/field_value_match/index.tsx | 15 +++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/packages/kbn-securitysolution-autocomplete/src/field_value_match/index.test.tsx b/packages/kbn-securitysolution-autocomplete/src/field_value_match/index.test.tsx index 5d4f8cd89f1cc..edf8256a86ab4 100644 --- a/packages/kbn-securitysolution-autocomplete/src/field_value_match/index.test.tsx +++ b/packages/kbn-securitysolution-autocomplete/src/field_value_match/index.test.tsx @@ -230,6 +230,38 @@ describe('AutocompleteFieldMatchComponent', () => { expect(mockOnChange).toHaveBeenCalledWith('value 1'); }); + test('it invokes "onChange" with empty value (i.e. clears selection) when new value searched', async () => { + const mockOnChange = jest.fn(); + wrapper = mount( + + ); + + act(() => { + ( + wrapper.find(EuiComboBox).props() as unknown as { + onSearchChange: (a: string) => void; + } + ).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, @@ -312,6 +344,7 @@ describe('AutocompleteFieldMatchComponent', () => { 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( { 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( { 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( Date: Thu, 5 Sep 2024 15:55:27 +0200 Subject: [PATCH 3/8] clear search query when input is cleared --- .../src/field_value_match/index.test.tsx | 55 +++++++++++++++++++ .../src/field_value_match/index.tsx | 3 +- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/packages/kbn-securitysolution-autocomplete/src/field_value_match/index.test.tsx b/packages/kbn-securitysolution-autocomplete/src/field_value_match/index.test.tsx index edf8256a86ab4..d9c162729361b 100644 --- a/packages/kbn-securitysolution-autocomplete/src/field_value_match/index.test.tsx +++ b/packages/kbn-securitysolution-autocomplete/src/field_value_match/index.test.tsx @@ -345,6 +345,61 @@ describe('AutocompleteFieldMatchComponent', () => { }); }); + test('it refreshes autocomplete with search query when input field is cleared', () => { + wrapper = mount( + + ); + + act(() => { + ( + wrapper.find(EuiComboBox).props() as unknown as { + onSearchChange: (a: string) => void; + } + ).onSearchChange('value 1'); + }); + act(() => { + ( + wrapper.find(EuiComboBox).props() as unknown as { + onSearchChange: (a: string) => void; + } + ).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( Date: Thu, 5 Sep 2024 16:09:07 +0200 Subject: [PATCH 4/8] small refactor --- .../src/field_value_match/index.test.tsx | 57 +++++-------------- 1 file changed, 15 insertions(+), 42 deletions(-) diff --git a/packages/kbn-securitysolution-autocomplete/src/field_value_match/index.test.tsx b/packages/kbn-securitysolution-autocomplete/src/field_value_match/index.test.tsx index d9c162729361b..3da97cd1f2f7a 100644 --- a/packages/kbn-securitysolution-autocomplete/src/field_value_match/index.test.tsx +++ b/packages/kbn-securitysolution-autocomplete/src/field_value_match/index.test.tsx @@ -31,6 +31,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, @@ -191,11 +198,7 @@ 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'); }); @@ -221,11 +224,7 @@ 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'); }); @@ -252,11 +251,7 @@ describe('AutocompleteFieldMatchComponent', () => { ); act(() => { - ( - wrapper.find(EuiComboBox).props() as unknown as { - onSearchChange: (a: string) => void; - } - ).onSearchChange('value 12'); + findEuiComboBox().onSearchChange('value 12'); }); expect(mockOnChange).toHaveBeenCalledWith(''); @@ -290,13 +285,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 '); @@ -324,11 +313,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({ @@ -366,18 +351,10 @@ describe('AutocompleteFieldMatchComponent', () => { ); act(() => { - ( - wrapper.find(EuiComboBox).props() as unknown as { - onSearchChange: (a: string) => void; - } - ).onSearchChange('value 1'); + findEuiComboBox().onSearchChange('value 1'); }); act(() => { - ( - wrapper.find(EuiComboBox).props() as unknown as { - onSearchChange: (a: string) => void; - } - ).onSearchChange(''); + findEuiComboBox().onSearchChange(''); }); // 1st call is initial render, 2nd call sets the search query: @@ -420,11 +397,7 @@ describe('AutocompleteFieldMatchComponent', () => { /> ); act(() => { - ( - wrapper.find(EuiComboBox).props() as unknown as { - onSearchChange: (a: string) => void; - } - ).onSearchChange(' value 1'); + findEuiComboBox().onSearchChange(' value 1'); }); wrapper.update(); From c917340946f168d7111cdc0ff3cfcd663a75f12d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C5=91=20=C3=81brah=C3=A1m?= Date: Fri, 6 Sep 2024 12:05:35 +0200 Subject: [PATCH 5/8] add cy testcase for showing suggestions after clear --- .../management/cypress/e2e/artifacts/event_filters.cy.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/event_filters.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/event_filters.cy.ts index 61e97513d4553..9a5710f33e1a2 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/event_filters.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/event_filters.cy.ts @@ -74,5 +74,14 @@ describe('Event Filters', { tags: ['@ess', '@serverless'] }, () => { cy.getByTestSubj('EventFiltersListPage-flyout').should('not.exist'); cy.contains('mr agent and co'); }); + + 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); + }); }); }); From 66ce206b0d89ff1faa1883d33b2ca4ff574fb5c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C5=91=20=C3=81brah=C3=A1m?= Date: Mon, 9 Sep 2024 10:21:59 +0200 Subject: [PATCH 6/8] fix cy test --- .../management/cypress/e2e/artifacts/event_filters.cy.ts | 8 ++++---- .../public/management/cypress/fixtures/artifacts_page.ts | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/event_filters.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/event_filters.cy.ts index 9a5710f33e1a2..469f3162a47df 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/event_filters.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/event_filters.cy.ts @@ -60,19 +60,19 @@ describe('Event Filters', { tags: ['@ess', '@serverless'] }, () => { }); it('should be able to modify after deleting value with {backspace}', () => { - cy.getByTestSubj(CONDITION_VALUE).type(' {backspace} and co{enter}'); + cy.getByTestSubj(CONDITION_VALUE).type(' {backspace}.lnk{enter}'); cy.getByTestSubj(SUBMIT_BUTTON).click(); cy.getByTestSubj('EventFiltersListPage-flyout').should('not.exist'); - cy.contains('mr agent and co'); + cy.contains('notepad.exe.lnk'); }); it('should be able to modify without using {backspace}', () => { - cy.getByTestSubj(CONDITION_VALUE).type(' and co{enter}'); + cy.getByTestSubj(CONDITION_VALUE).type('.lnk{enter}'); cy.getByTestSubj(SUBMIT_BUTTON).click(); cy.getByTestSubj('EventFiltersListPage-flyout').should('not.exist'); - cy.contains('mr agent and co'); + cy.contains('notepad.exe.lnk'); }); it('should show suggestions when filter value is cleared', () => { diff --git a/x-pack/plugins/security_solution/public/management/cypress/fixtures/artifacts_page.ts b/x-pack/plugins/security_solution/public/management/cypress/fixtures/artifacts_page.ts index c97c3c912606a..2113347bfc446 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/fixtures/artifacts_page.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/fixtures/artifacts_page.ts @@ -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'], From dd2bf53442178fa5150904e1f77b745c91b077b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C5=91=20=C3=81brah=C3=A1m?= Date: Fri, 13 Sep 2024 14:00:05 +0200 Subject: [PATCH 7/8] add some explanatory comments on the fixes --- .../src/field_value_match/index.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/kbn-securitysolution-autocomplete/src/field_value_match/index.tsx b/packages/kbn-securitysolution-autocomplete/src/field_value_match/index.tsx index d921aec85efc9..6a831376b3cbf 100644 --- a/packages/kbn-securitysolution-autocomplete/src/field_value_match/index.tsx +++ b/packages/kbn-securitysolution-autocomplete/src/field_value_match/index.tsx @@ -171,9 +171,11 @@ export const AutocompleteFieldMatchComponent: React.FC Date: Fri, 13 Sep 2024 14:02:18 +0200 Subject: [PATCH 8/8] remove unnecessary `async` keywords --- .../src/field_value_match/index.test.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/kbn-securitysolution-autocomplete/src/field_value_match/index.test.tsx b/packages/kbn-securitysolution-autocomplete/src/field_value_match/index.test.tsx index 862b99ad13e48..e83d79b180e90 100644 --- a/packages/kbn-securitysolution-autocomplete/src/field_value_match/index.test.tsx +++ b/packages/kbn-securitysolution-autocomplete/src/field_value_match/index.test.tsx @@ -178,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( { 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( { expect(mockOnChange).toHaveBeenCalledWith('value 1'); }); - test('it invokes "onChange" with empty value (i.e. clears selection) when new value searched', async () => { + test('it invokes "onChange" with empty value (i.e. clears selection) when new value searched', () => { const mockOnChange = jest.fn(); wrapper = mount(