diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index 55838b6d880f8..46b21e71a8d57 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -479,6 +479,7 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D blocklist: `${SECURITY_SOLUTION_DOCS}blocklist.html`, threatIntelInt: `${SECURITY_SOLUTION_DOCS}es-threat-intel-integrations.html`, endpointArtifacts: `${SECURITY_SOLUTION_DOCS}endpoint-artifacts.html`, + eventMerging: `${SECURITY_SOLUTION_DOCS}endpoint-data-volume.html`, policyResponseTroubleshooting: { full_disk_access: `${SECURITY_SOLUTION_DOCS}deploy-elastic-endpoint.html#enable-fda-endpoint`, macos_system_ext: `${SECURITY_SOLUTION_DOCS}deploy-elastic-endpoint.html#system-extension-endpoint`, diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts index 703a0f6584584..53118549726c9 100644 --- a/packages/kbn-doc-links/src/types.ts +++ b/packages/kbn-doc-links/src/types.ts @@ -346,6 +346,7 @@ export interface DocLinks { readonly avcResults: string; readonly trustedApps: string; readonly eventFilters: string; + readonly eventMerging: string; readonly blocklist: string; readonly endpointArtifacts: string; readonly policyResponseTroubleshooting: { diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/event_merging_banner.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/event_merging_banner.test.tsx new file mode 100644 index 0000000000000..b36975ee9b0be --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/event_merging_banner.test.tsx @@ -0,0 +1,43 @@ +/* + * 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 type { RenderResult } from '@testing-library/react'; +import React from 'react'; +import { createAppRootMockRenderer } from '../../../../../../common/mock/endpoint'; +import { EventMergingBanner, type EventMergingBannerProps } from './event_merging_banner'; + +describe('EventMergingBanner component', () => { + let formProps: EventMergingBannerProps; + let renderResult: RenderResult; + + beforeEach(() => { + const mockedContext = createAppRootMockRenderer(); + + formProps = { + onDismiss: jest.fn(), + }; + + renderResult = mockedContext.render(); + }); + + it('should render event merging banner', () => { + expect(renderResult.getByTestId('eventMergingCallout')).toBeInTheDocument(); + }); + + it('should contain a link to documentation', () => { + const docLink = renderResult.getByTestId('eventMergingDocLink'); + + expect(docLink).toBeInTheDocument(); + expect(docLink.getAttribute('href')).toContain('endpoint-data-volume.html'); + }); + + it('should call `onDismiss` callback when user clicks dismiss', () => { + renderResult.getByTestId('euiDismissCalloutButton').click(); + + expect(formProps.onDismiss).toBeCalled(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/event_merging_banner.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/event_merging_banner.tsx new file mode 100644 index 0000000000000..26b229e219c2e --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/event_merging_banner.tsx @@ -0,0 +1,52 @@ +/* + * 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 React, { memo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiCallOut, EuiLink, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; + +export interface EventMergingBannerProps { + onDismiss: () => void; +} + +export const EventMergingBanner = memo(({ onDismiss }) => { + const { docLinks } = useKibana().services; + const bannerTitle = i18n.translate( + 'xpack.securitySolution.endpoint.policy.eventMergingBanner.title', + { + defaultMessage: "We've recently changed Linux event collection", + } + ); + + return ( + + + + + + ), + }} + /> + + + ); +}); +EventMergingBanner.displayName = 'EventMergingBanner'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/mocks.ts b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/mocks.ts index b2ef180dd4bfd..9448b93c7627e 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/mocks.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/mocks.ts @@ -176,7 +176,7 @@ export const getPolicySettingsFormTestSubjects = ( export const expectIsViewOnly = (elem: HTMLElement): void => { elem .querySelectorAll( - 'button:not(.euiLink, [data-test-subj*="advancedSection-showButton"]),input,select,textarea' + 'button:not(.euiLink, [data-test-subj*="advancedSection-showButton"], [data-test-subj="euiDismissCalloutButton"]),input,select,textarea' ) .forEach((inputElement) => { expect(inputElement).toHaveAttribute('disabled'); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/policy_settings_form.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/policy_settings_form.test.tsx index 96a32b7679843..d2e4c58adfb56 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/policy_settings_form.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/policy_settings_form.test.tsx @@ -23,6 +23,7 @@ import type { PolicyConfig } from '../../../../../../common/endpoint/types'; import { AntivirusRegistrationModes } from '../../../../../../common/endpoint/types'; import userEvent from '@testing-library/user-event'; import { cloneDeep } from 'lodash'; +import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; jest.mock('../../../../../common/hooks/use_license'); @@ -33,11 +34,13 @@ describe('Endpoint Policy Settings Form', () => { let render: () => ReturnType; let renderResult: ReturnType; let upsellingService: UpsellingService; + let storageMock: IStorageWrapper; beforeEach(() => { const mockedContext = createAppRootMockRenderer(); upsellingService = mockedContext.startServices.upselling; + storageMock = mockedContext.startServices.storage; formProps = { policy: new FleetPackagePolicyGenerator('seed').generateEndpointPackagePolicy().inputs[0] @@ -50,6 +53,45 @@ describe('Endpoint Policy Settings Form', () => { render = () => (renderResult = mockedContext.render()); }); + describe('event merging banner', () => { + it('should show the event merging banner for 8.16 if it has never been dismissed', () => { + render(); + + expect(renderResult.getByTestId('eventMergingCallout')).toBeInTheDocument(); + }); + + it('should show the event merging banner for 8.16 if `securitySolution.showEventMergingBanner` is `true`', () => { + storageMock.set('securitySolution.showEventMergingBanner', true); + render(); + + expect(renderResult.getByTestId('eventMergingCallout')).toBeInTheDocument(); + }); + + it('should hide the event merging banner when user dismisses it', () => { + render(); + expect(renderResult.getByTestId('eventMergingCallout')).toBeInTheDocument(); + + renderResult.getByTestId('euiDismissCalloutButton').click(); + + expect(renderResult.queryByTestId('eventMergingCallout')).not.toBeInTheDocument(); + }); + + it('should persist that event merging banner have been dismissed', () => { + render(); + + renderResult.getByTestId('euiDismissCalloutButton').click(); + + expect(storageMock.get('securitySolution.showEventMergingBanner')).toBe(false); + }); + + it('should not show the banner if it was dismissed before', () => { + storageMock.set('securitySolution.showEventMergingBanner', false); + render(); + + expect(renderResult.queryByTestId('eventMergingCallout')).not.toBeInTheDocument(); + }); + }); + it.each([ ['malware', testSubj.malware.card], ['ransomware', testSubj.ransomware.card], @@ -91,7 +133,7 @@ describe('Endpoint Policy Settings Form', () => { ])('should include %s card', (_, testSubjSelector) => { render(); - expect(renderResult.queryByTestId(testSubjSelector)).toBeNull(); + expect(renderResult.queryByTestId(testSubjSelector)).not.toBeInTheDocument(); }); it('should display upselling component', () => { diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/policy_settings_form.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/policy_settings_form.tsx index b0502786ea05a..7d83170df2d26 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/policy_settings_form.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/policy_settings_form.tsx @@ -6,11 +6,13 @@ */ import type { PropsWithChildren } from 'react'; -import React, { memo } from 'react'; +import React, { memo, useState, useCallback } from 'react'; import { EuiSpacer, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { useKibana } from '../../../../../common/lib/kibana'; import { updateAntivirusRegistrationEnabled } from '../../../../../../common/endpoint/utils/update_antivirus_registration_enabled'; import { useGetProtectionsUnavailableComponent } from './hooks/use_get_protections_unavailable_component'; +import { EventMergingBanner } from './components/event_merging_banner'; import { AntivirusRegistrationCard } from './components/cards/antivirus_registration_card'; import { LinuxEventCollectionCard } from './components/cards/linux_event_collection_card'; import { MacEventCollectionCard } from './components/cards/mac_event_collection_card'; @@ -41,6 +43,15 @@ export const PolicySettingsForm = memo((props) => { const getTestId = useTestIdGenerator(props['data-test-subj']); const ProtectionsUpSellingComponent = useGetProtectionsUnavailableComponent(); + const { storage } = useKibana().services; + const [showEventMergingBanner, setShowEventMergingBanner] = useState( + storage.get('securitySolution.showEventMergingBanner') ?? true + ); + const onBannerDismiss = useCallback(() => { + setShowEventMergingBanner(false); + storage.set('securitySolution.showEventMergingBanner', false); + }, [storage]); + const onChangeProxy: PolicySettingsFormProps['onChange'] = ({ isValid, updatedPolicy }) => { // perform tasks that synchronises changes between settings updateAntivirusRegistrationEnabled(updatedPolicy); @@ -50,8 +61,13 @@ export const PolicySettingsForm = memo((props) => { return (
+ {showEventMergingBanner && ( + <> + + + + )} {PROTECTIONS_SECTION_TITLE} - {ProtectionsUpSellingComponent && ( <> diff --git a/x-pack/test/security_solution_endpoint/page_objects/page_utils.ts b/x-pack/test/security_solution_endpoint/page_objects/page_utils.ts index 132e93ce23cf0..fe00c50d5d360 100644 --- a/x-pack/test/security_solution_endpoint/page_objects/page_utils.ts +++ b/x-pack/test/security_solution_endpoint/page_objects/page_utils.ts @@ -19,6 +19,7 @@ export function EndpointPageUtils({ getService }: FtrProviderContext) { */ async clickOnEuiCheckbox(euiCheckBoxTestId: string) { const euiCheckboxInput = await testSubjects.find(euiCheckBoxTestId); + await euiCheckboxInput.scrollIntoView(); await euiCheckboxInput.click(); },