diff --git a/x-pack/solutions/security/packages/navigation/index.ts b/x-pack/solutions/security/packages/navigation/index.ts index 35c4437e1d4a4..0f455183fbcf0 100644 --- a/x-pack/solutions/security/packages/navigation/index.ts +++ b/x-pack/solutions/security/packages/navigation/index.ts @@ -13,6 +13,6 @@ export { SecurityGroupName, LinkCategoryType, SECURITY_UI_APP_ID, - ATTACKS_ALERTS_ALIGNMENT_ENABLED, + ENABLE_ALERTS_AND_ATTACKS_ALIGNMENT_SETTING, } from './src/constants'; export * from './src/types'; diff --git a/x-pack/solutions/security/packages/navigation/src/constants.ts b/x-pack/solutions/security/packages/navigation/src/constants.ts index 64e98964726fd..8e8f2b2ce0a2b 100644 --- a/x-pack/solutions/security/packages/navigation/src/constants.ts +++ b/x-pack/solutions/security/packages/navigation/src/constants.ts @@ -36,5 +36,6 @@ export enum SecurityGroupName { alertDetections = 'securityGroup:alertDetections', } -/** Feature flag for the alerts and attacks alignment feature */ -export const ATTACKS_ALERTS_ALIGNMENT_ENABLED = 'securitySolution.attacksAlertsAlignment' as const; +/** This Kibana Advanced Setting allows users to enable/disable the Alerts and Attacks Alignment feature */ +export const ENABLE_ALERTS_AND_ATTACKS_ALIGNMENT_SETTING = + 'securitySolution:enableAlertsAndAttacksAlignment' as const; diff --git a/x-pack/solutions/security/plugins/security_solution/common/constants.ts b/x-pack/solutions/security/plugins/security_solution/common/constants.ts index a1d9bbfa07300..7ada1026b2248 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/constants.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/constants.ts @@ -12,8 +12,9 @@ import * as i18n from './translations'; export { SecurityPageName, - ATTACKS_ALERTS_ALIGNMENT_ENABLED, + ENABLE_ALERTS_AND_ATTACKS_ALIGNMENT_SETTING, } from '@kbn/security-solution-navigation'; + /** * as const * diff --git a/x-pack/solutions/security/plugins/security_solution/common/experimental_features.ts b/x-pack/solutions/security/plugins/security_solution/common/experimental_features.ts index 38943c8a510a5..103e3ce63d1dd 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/experimental_features.ts @@ -180,7 +180,7 @@ export const allowedExperimentalValues = Object.freeze({ /** * Protects all the work related to the attacks and alerts alignment effort */ - attacksAlertsAlignment: false, + enableAlertsAndAttacksAlignment: false, /** * Enables the QRadar rules import feature */ diff --git a/x-pack/solutions/security/plugins/security_solution/public/app/links/app_links.ts b/x-pack/solutions/security/plugins/security_solution/public/app/links/app_links.ts index f927937cff9f5..498ea7c08392a 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/app/links/app_links.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/app/links/app_links.ts @@ -9,7 +9,7 @@ import { firstValueFrom } from 'rxjs'; import { AIChatExperience } from '@kbn/ai-assistant-common'; import { AI_CHAT_EXPERIENCE_TYPE } from '@kbn/management-settings-ids'; -import { ATTACKS_ALERTS_ALIGNMENT_ENABLED } from '../../../common/constants'; +import { ENABLE_ALERTS_AND_ATTACKS_ALIGNMENT_SETTING } from '../../../common/constants'; import { aiValueLinks } from '../../reports/links'; import { configurationsLinks, getConfigurationsLinks } from '../../configurations/links'; import { links as attackDiscoveryLinks } from '../../attack_discovery/links'; @@ -66,7 +66,7 @@ export const getFilteredLinks = async ( return Object.freeze([ dashboardsLinks, - core.featureFlags.getBooleanValue(ATTACKS_ALERTS_ALIGNMENT_ENABLED, false) + core.uiSettings.get(ENABLE_ALERTS_AND_ATTACKS_ALIGNMENT_SETTING, false) ? alertDetectionsLinks : alertsLink, alertSummaryLink, diff --git a/x-pack/solutions/security/plugins/security_solution/public/app/links/get_filtered_links.test.ts b/x-pack/solutions/security/plugins/security_solution/public/app/links/get_filtered_links.test.ts index d0114f90103e3..4c737899375c3 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/app/links/get_filtered_links.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/app/links/get_filtered_links.test.ts @@ -90,9 +90,9 @@ describe('getFilteredLinks', () => { expect(mockGetManagementFilteredLinks).toHaveBeenCalledWith(mockCore, mockPlugins); }); - describe('`securitySolution.attacksAlertsAlignment` feature flag', () => { - it('includes correct base links in the result when feature flag is disabled', async () => { - mockCore.featureFlags.getBooleanValue.mockReturnValue(false); + describe('`securitySolution:enableAlertsAndAttacksAlignment` setting', () => { + it('includes correct base links in the result when setting is disabled', async () => { + mockCore.uiSettings.get.mockReturnValue(false); mockGetManagementFilteredLinks.mockResolvedValue(mockManagementLinks); const result = await getFilteredLinks(mockCore, mockPlugins); @@ -110,8 +110,8 @@ describe('getFilteredLinks', () => { expect(resultIds).toContain('ai_value'); // AI Value is now included statically }); - it('includes all base links in the result when feature flag is enabled', async () => { - mockCore.featureFlags.getBooleanValue.mockReturnValue(true); + it('includes all base links in the result when setting is enabled', async () => { + mockCore.uiSettings.get.mockReturnValue(true); mockGetManagementFilteredLinks.mockResolvedValue(mockManagementLinks); const result = await getFilteredLinks(mockCore, mockPlugins); diff --git a/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/index.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/index.test.tsx index f29740ab8408a..a3ad98f2f894a 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/index.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/index.test.tsx @@ -15,7 +15,11 @@ import React from 'react'; import useLocalStorage from 'react-use/lib/useLocalStorage'; import { TestProviders } from '../../common/mock'; -import { ATTACK_DISCOVERY_PATH, SECURITY_FEATURE_ID } from '../../../common/constants'; +import { + ATTACK_DISCOVERY_PATH, + ENABLE_ALERTS_AND_ATTACKS_ALIGNMENT_SETTING, + SECURITY_FEATURE_ID, +} from '../../../common/constants'; import { mockHistory } from '../../common/utils/route/mocks'; import { AttackDiscoveryPage } from '.'; import { mockTimelines } from '../../common/mock/mock_timelines_plugin'; @@ -315,9 +319,14 @@ describe('AttackDiscovery', () => { }); }); - describe('`attacksAlertsAlignmentEnabled` feature', () => { + describe('`enableAlertsAndAttacksAlignment` feature', () => { it('renders callout about new Attacks page when feature is enabled', () => { - mockUseKibanaReturnValue.services.featureFlags.getBooleanValue.mockReturnValue(true); + mockUseKibanaReturnValue.services.uiSettings.get.mockImplementation((key) => { + if (key === ENABLE_ALERTS_AND_ATTACKS_ALIGNMENT_SETTING) { + return true; + } + return false; + }); render( @@ -333,7 +342,12 @@ describe('AttackDiscovery', () => { }); it('does not render callout about new Attacks page when feature is disabled', () => { - mockUseKibanaReturnValue.services.featureFlags.getBooleanValue.mockReturnValue(false); + mockUseKibanaReturnValue.services.uiSettings.get.mockImplementation((key) => { + if (key === ENABLE_ALERTS_AND_ATTACKS_ALIGNMENT_SETTING) { + return false; + } + return false; + }); render( diff --git a/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/index.tsx index 3482f2d1d05d7..4de9597b8fa4c 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/index.tsx @@ -25,7 +25,10 @@ import type { Filter, Query } from '@kbn/es-query'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import useLocalStorage from 'react-use/lib/useLocalStorage'; -import { ATTACKS_ALERTS_ALIGNMENT_ENABLED, SecurityPageName } from '../../../common/constants'; +import { + ENABLE_ALERTS_AND_ATTACKS_ALIGNMENT_SETTING, + SecurityPageName, +} from '../../../common/constants'; import { HeaderPage } from '../../common/components/header_page'; import { useInvalidFilterQuery } from '../../common/hooks/use_invalid_filter_query'; import { useKibana } from '../../common/lib/kibana'; @@ -52,7 +55,7 @@ export const ID = 'attackDiscoveryQuery'; const AttackDiscoveryPageComponent: React.FC = () => { const { - services: { featureFlags, uiSettings, settings }, + services: { uiSettings, settings }, } = useKibana(); const { http, inferenceEnabled } = useAssistantContext(); @@ -224,8 +227,8 @@ const AttackDiscoveryPageComponent: React.FC = () => { const onClose = useCallback(() => setShowFlyout(false), []); - const attacksAlertsAlignmentEnabled = featureFlags.getBooleanValue( - ATTACKS_ALERTS_ALIGNMENT_ENABLED, + const enableAlertsAndAttacksAlignment = uiSettings.get( + ENABLE_ALERTS_AND_ATTACKS_ALIGNMENT_SETTING, false ); @@ -251,7 +254,7 @@ const AttackDiscoveryPageComponent: React.FC = () => { - {attacksAlertsAlignmentEnabled && ( + {enableAlertsAndAttacksAlignment && ( <> diff --git a/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/page_title/index.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/page_title/index.test.tsx index dccce2a527587..e552e3d98b308 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/page_title/index.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/page_title/index.test.tsx @@ -15,16 +15,16 @@ import { useKibana } from '../../../common/lib/kibana'; jest.mock('../../../common/lib/kibana'); describe('PageTitle', () => { - const mockGetBooleanValue = jest.fn(); + const mockGetSetting = jest.fn(); beforeEach(() => { jest.clearAllMocks(); (useKibana as jest.Mock).mockReturnValue({ services: { - featureFlags: { getBooleanValue: mockGetBooleanValue }, + uiSettings: { get: mockGetSetting }, }, }); - mockGetBooleanValue.mockReturnValue(false); + mockGetSetting.mockReturnValue(false); }); it('renders the expected title', () => { @@ -36,16 +36,16 @@ describe('PageTitle', () => { }); describe('Attacks page announcement', () => { - it('renders the Attacks page announcement when `attacksAlertsAlignmentEnabled` feature flag is enabled', () => { - mockGetBooleanValue.mockReturnValue(true); + it('renders the Attacks page announcement when `enableAlertsAndAttacksAlignment` setting is enabled', () => { + mockGetSetting.mockReturnValue(true); render(); expect(screen.getByTestId('attackDiscoveryAnnouncementBadge')).toBeInTheDocument(); }); - it('does not render the Attacks page announcement when `attacksAlertsAlignmentEnabled` feature flag is disabled', () => { - mockGetBooleanValue.mockReturnValue(false); + it('does not render the Attacks page announcement when `enableAlertsAndAttacksAlignment` setting is disabled', () => { + mockGetSetting.mockReturnValue(false); render(); diff --git a/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/page_title/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/page_title/index.tsx index c8667134bef3c..fd9a0c2aae736 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/page_title/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/attack_discovery/pages/page_title/index.tsx @@ -9,7 +9,7 @@ import { EuiBetaBadge, EuiFlexGroup, EuiFlexItem, EuiTitle, useEuiTheme } from ' import { css } from '@emotion/react'; import React from 'react'; -import { ATTACKS_ALERTS_ALIGNMENT_ENABLED } from '@kbn/security-solution-navigation'; +import { ENABLE_ALERTS_AND_ATTACKS_ALIGNMENT_SETTING } from '@kbn/security-solution-navigation'; import { useKibana } from '../../../common/lib/kibana'; import * as i18n from './translations'; import { IconAnnouncementBadge } from './announcement_badge'; @@ -17,11 +17,11 @@ import { IconAnnouncementBadge } from './announcement_badge'; const PageTitleComponent: React.FC = () => { const { euiTheme } = useEuiTheme(); const { - services: { featureFlags }, + services: { uiSettings }, } = useKibana(); - const attacksAlertsAlignmentEnabled = featureFlags.getBooleanValue( - ATTACKS_ALERTS_ALIGNMENT_ENABLED, + const enableAlertsAndAttacksAlignment = uiSettings.get( + ENABLE_ALERTS_AND_ATTACKS_ALIGNMENT_SETTING, false ); @@ -33,7 +33,7 @@ const PageTitleComponent: React.FC = () => { - {attacksAlertsAlignmentEnabled && ( + {enableAlertsAndAttacksAlignment && ( { return [ { @@ -20,7 +20,9 @@ export const getNavCategories = ( type: LinkCategoryType.separator, linkIds: [ SecurityPageName.rulesLanding, - attacksAlertsAlignmentEnabled ? SecurityPageName.alertDetections : SecurityPageName.alerts, + enableAlertsAndAttacksAlignment + ? SecurityPageName.alertDetections + : SecurityPageName.alerts, SecurityPageName.attackDiscovery, SecurityPageName.cloudSecurityPostureFindings, SecurityPageName.case, diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/components/navigation/security_side_nav/security_side_nav.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/common/components/navigation/security_side_nav/security_side_nav.test.tsx index db7a5f0152c0c..c2a0ec1b4657d 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/components/navigation/security_side_nav/security_side_nav.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/common/components/navigation/security_side_nav/security_side_nav.test.tsx @@ -242,4 +242,26 @@ describe('SecuritySideNav', () => { ); }); }); + + describe('enableAlertsAndAttacksAlignment setting', () => { + it('should call getNavCategories with true when setting is enabled', () => { + useKibana().services.uiSettings.get = jest.fn().mockReturnValue(true); + renderNav(); + expect(mockSolutionSideNav).toHaveBeenCalledWith( + expect.objectContaining({ + categories: getNavCategories(true), + }) + ); + }); + + it('should call getNavCategories with false when setting is disabled', () => { + useKibana().services.uiSettings.get = jest.fn().mockReturnValue(false); + renderNav(); + expect(mockSolutionSideNav).toHaveBeenCalledWith( + expect.objectContaining({ + categories: getNavCategories(false), + }) + ); + }); + }); }); diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/components/navigation/security_side_nav/security_side_nav.tsx b/x-pack/solutions/security/plugins/security_solution/public/common/components/navigation/security_side_nav/security_side_nav.tsx index 870936b7ce50d..6bf356589d7b2 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/components/navigation/security_side_nav/security_side_nav.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/common/components/navigation/security_side_nav/security_side_nav.tsx @@ -13,7 +13,7 @@ import { type SolutionSideNavItem, } from '@kbn/security-solution-side-nav'; import useObservable from 'react-use/lib/useObservable'; -import { ATTACKS_ALERTS_ALIGNMENT_ENABLED } from '../../../../../common/constants'; +import { ENABLE_ALERTS_AND_ATTACKS_ALIGNMENT_SETTING } from '../../../../../common/constants'; import { SecurityPageName } from '../../../../app/types'; import type { NavigationLink } from '../../../links'; import { useRouteSpy } from '../../../utils/route/use_route_spy'; @@ -135,19 +135,19 @@ const usePanelBottomOffset = (): string | undefined => { * It takes the links to render from the generic application `links` configs. */ export const SecuritySideNav: React.FC = () => { - const { featureFlags } = useKibana().services; + const { uiSettings } = useKibana().services; const items = useSolutionSideNavItems(); const selectedId = useSelectedId(); const panelTopOffset = usePanelTopOffset(); const panelBottomOffset = usePanelBottomOffset(); const categories = useMemo(() => { - const attacksAlertsAlignmentEnabled = featureFlags.getBooleanValue( - ATTACKS_ALERTS_ALIGNMENT_ENABLED, + const enableAlertsAndAttacksAlignment = uiSettings.get( + ENABLE_ALERTS_AND_ATTACKS_ALIGNMENT_SETTING, false ); - return getNavCategories(attacksAlertsAlignmentEnabled); - }, [featureFlags]); + return getNavCategories(enableAlertsAndAttacksAlignment); + }, [uiSettings]); if (!items) { return ; diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_init_data_view_manager.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_init_data_view_manager.ts index 007736038e432..8dc937b66d9ab 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_init_data_view_manager.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_init_data_view_manager.ts @@ -12,7 +12,7 @@ import { addListener as originalAddListener, removeListener as originalRemoveListener, } from '@reduxjs/toolkit'; -import { ATTACKS_ALERTS_ALIGNMENT_ENABLED } from '../../../common/constants'; +import { ENABLE_ALERTS_AND_ATTACKS_ALIGNMENT_SETTING } from '../../../common/constants'; import type { RootState } from '../redux/reducer'; import { useKibana } from '../../common/lib/kibana'; import { createDataViewSelectedListener } from '../redux/listeners/data_view_selected'; @@ -44,8 +44,8 @@ export const useInitDataViewManager = () => { const dispatch = useDispatch(); const services = useKibana().services; const newDataViewPickerEnabled = useIsExperimentalFeatureEnabled('newDataViewPickerEnabled'); - const attacksAlertsAlignmentEnabled = services.featureFlags.getBooleanValue( - ATTACKS_ALERTS_ALIGNMENT_ENABLED, + const enableAlertsAndAttacksAlignment = services.uiSettings.get( + ENABLE_ALERTS_AND_ATTACKS_ALIGNMENT_SETTING, false ); @@ -103,7 +103,7 @@ export const useInitDataViewManager = () => { storage: services.storage, logger: createInitListenerLogger, }, - attacksAlertsAlignmentEnabled + enableAlertsAndAttacksAlignment ); logger.debug('Registering data view manager listeners'); @@ -144,7 +144,7 @@ export const useInitDataViewManager = () => { }); }; }, [ - attacksAlertsAlignmentEnabled, + enableAlertsAndAttacksAlignment, dispatch, logger, newDataViewPickerEnabled, diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/init_listener.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/init_listener.ts index abeed945942b1..7f1f39e587347 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/init_listener.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/init_listener.ts @@ -33,7 +33,7 @@ import { createExploreDataView } from '../../utils/create_explore_data_view'; * and that state is not reset for slices that already have selections. * * @param dependencies - Core and plugin services required for data view creation and retrieval. - * @param attacksAlertsAlignmentEnabled - Prevent attacks dataview creation if feature flag is not enabled. + * @param enableAlertsAndAttacksAlignment - Prevent attacks dataview creation if feature flag is not enabled. * @returns An object with the actionCreator and effect for Redux listener middleware. */ export const createInitListener = ( @@ -46,7 +46,7 @@ export const createInitListener = ( storage: Storage; logger: Logger; }, - attacksAlertsAlignmentEnabled: boolean + enableAlertsAndAttacksAlignment: boolean ) => { return { actionCreator: sharedDataViewManagerSlice.actions.init, @@ -63,14 +63,14 @@ export const createInitListener = ( spaces: dependencies.spaces, application: dependencies.application, http: dependencies.http, - attacksAlertsAlignmentEnabled, + enableAlertsAndAttacksAlignment, }); - logger.debug(`Default data views created: - - Default Data View: ${defaultDataView.title} (ID: ${defaultDataView.id}) - - Alert Data View: ${alertDataView.title} (ID: ${alertDataView.id}) + logger.debug(`Default data views created: + - Default Data View: ${defaultDataView.title} (ID: ${defaultDataView.id}) + - Alert Data View: ${alertDataView.title} (ID: ${alertDataView.id}) ${ - attacksAlertsAlignmentEnabled + enableAlertsAndAttacksAlignment ? `- Attack Data View: ${attackDataView.title} (ID: ${attackDataView.id})` : '' }`); @@ -84,7 +84,7 @@ export const createInitListener = ( alertDataView.title ); - logger.debug(`Explore Data View created: + logger.debug(`Explore Data View created: - Explore Data View: ${exploreDataView.title} (ID: ${exploreDataView.id})`); // Store the created data views in the Redux state diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/utils/create_default_data_view.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/utils/create_default_data_view.ts index d072a2cec77c4..e631c8dbe331c 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/utils/create_default_data_view.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/utils/create_default_data_view.ts @@ -31,7 +31,7 @@ export interface CreateDefaultDataViewDependencies { /** * If true, will create the attack data view along with the default and alert data views. */ - attacksAlertsAlignmentEnabled?: boolean; + enableAlertsAndAttacksAlignment?: boolean; } export const createDefaultDataView = async ({ @@ -41,7 +41,7 @@ export const createDefaultDataView = async ({ skip = false, http, application, - attacksAlertsAlignmentEnabled = false, + enableAlertsAndAttacksAlignment = false, }: CreateDefaultDataViewDependencies) => { const configPatternList = uiSettings.get(DEFAULT_INDEX_KEY); let defaultDataView: SourcererModel['defaultDataView']; @@ -85,7 +85,7 @@ export const createDefaultDataView = async ({ dataViewId: `${DEFAULT_ALERT_DATA_VIEW_ID}-${currentSpaceId}`, indexName: signal.name ?? undefined, }, - ...(attacksAlertsAlignmentEnabled && { + ...(enableAlertsAndAttacksAlignment && { attackDetails: { dataViewId: `${DEFAULT_ATTACK_DATA_VIEW_ID}-${currentSpaceId}`, patternList: [ diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/create_sourcerer_data_view.ts b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/create_sourcerer_data_view.ts index 73d5a22faf81b..d42c0f29e88b4 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/create_sourcerer_data_view.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/create_sourcerer_data_view.ts @@ -234,7 +234,7 @@ const getAttackDataView = async ({ export interface SecurityDataView { defaultDataView: KibanaDataView; alertDataView: KibanaDataView; - attackDataView?: KibanaDataView; // TODO remove optional when we remove the attacksAlertsAlignment feature flag + attackDataView?: KibanaDataView; // TODO remove optional when we remove the enableAlertsAndAttacksAlignment feature flag kibanaDataViews: Array>; } diff --git a/x-pack/solutions/security/plugins/security_solution/server/ui_settings.test.ts b/x-pack/solutions/security/plugins/security_solution/server/ui_settings.test.ts new file mode 100644 index 0000000000000..9578dd5117ede --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/ui_settings.test.ts @@ -0,0 +1,50 @@ +/* + * 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 { coreMock } from '@kbn/core/server/mocks'; +import { initUiSettings } from './ui_settings'; +import type { ExperimentalFeatures } from '../common/experimental_features'; +import { ENABLE_ALERTS_AND_ATTACKS_ALIGNMENT_SETTING } from '../common/constants'; + +describe('initUiSettings', () => { + let mockUiSettings: ReturnType['uiSettings']; + const mockExperimentalFeatures = { + enableAlertsAndAttacksAlignment: false, + siemReadinessDashboard: false, + extendedRuleExecutionLoggingEnabled: false, + } as ExperimentalFeatures; + + beforeEach(() => { + mockUiSettings = coreMock.createSetup().uiSettings; + }); + + it('does NOT register ENABLE_ALERTS_AND_ATTACKS_ALIGNMENT_SETTING when feature flag is disabled', () => { + initUiSettings(mockUiSettings, mockExperimentalFeatures, false); + + const registeredSettings = (mockUiSettings.register as jest.Mock).mock.calls[0][0]; + expect(registeredSettings).not.toHaveProperty(ENABLE_ALERTS_AND_ATTACKS_ALIGNMENT_SETTING); + }); + + it('registers ENABLE_ALERTS_AND_ATTACKS_ALIGNMENT_SETTING when feature flag is enabled', () => { + const enabledFeatures = { + ...mockExperimentalFeatures, + enableAlertsAndAttacksAlignment: true, + }; + + initUiSettings(mockUiSettings, enabledFeatures, false); + + const registeredSettings = (mockUiSettings.register as jest.Mock).mock.calls[0][0]; + expect(registeredSettings).toHaveProperty(ENABLE_ALERTS_AND_ATTACKS_ALIGNMENT_SETTING); + expect(registeredSettings[ENABLE_ALERTS_AND_ATTACKS_ALIGNMENT_SETTING]).toEqual( + expect.objectContaining({ + name: 'Enable alerts and attacks alignment', + value: false, + type: 'boolean', + }) + ); + }); +}); diff --git a/x-pack/solutions/security/plugins/security_solution/server/ui_settings.ts b/x-pack/solutions/security/plugins/security_solution/server/ui_settings.ts index 931dce0860715..bbfc2dc752108 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/ui_settings.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/ui_settings.ts @@ -33,6 +33,7 @@ import { DEFAULT_THREAT_INDEX_VALUE, DEFAULT_TO, ENABLE_ASSET_INVENTORY_SETTING, + ENABLE_ALERTS_AND_ATTACKS_ALIGNMENT_SETTING, ENABLE_CCS_READ_WARNING_SETTING, ENABLE_CLOUD_CONNECTOR_SETTING, ENABLE_GRAPH_VISUALIZATION_SETTING, @@ -233,6 +234,29 @@ export const initUiSettings = ( solutionViews: ['classic', 'security'], technicalPreview: true, }, + ...(experimentalFeatures.enableAlertsAndAttacksAlignment && { + [ENABLE_ALERTS_AND_ATTACKS_ALIGNMENT_SETTING]: { + name: i18n.translate( + 'xpack.securitySolution.uiSettings.enableAlertsAndAttacksAlignmentLabel', + { + defaultMessage: 'Enable alerts and attacks alignment', + } + ), + description: i18n.translate( + 'xpack.securitySolution.uiSettings.enableAlertsAndAttacksAlignmentDescription', + { + defaultMessage: + 'Enabling this setting will reveal a new Attacks page under the Detections navigation item. Similarly, the Alerts page will be part of the Detections navigation item.', + } + ), + type: 'boolean', + value: false, + category: [APP_ID], + requiresPageReload: true, + schema: schema.boolean(), + solutionViews: ['classic', 'security'], + }, + }), [ENABLE_ASSET_INVENTORY_SETTING]: { name: i18n.translate('xpack.securitySolution.uiSettings.enableAssetInventoryLabel', { defaultMessage: 'Enable Security Asset Inventory', diff --git a/x-pack/solutions/security/plugins/security_solution_ess/public/navigation/navigation_tree.ts b/x-pack/solutions/security/plugins/security_solution_ess/public/navigation/navigation_tree.ts index 138eae1878b52..d5e52ef06452b 100644 --- a/x-pack/solutions/security/plugins/security_solution_ess/public/navigation/navigation_tree.ts +++ b/x-pack/solutions/security/plugins/security_solution_ess/public/navigation/navigation_tree.ts @@ -8,7 +8,7 @@ import type { AppDeepLinkId, NavigationTreeDefinition } from '@kbn/core-chrome-browser'; import { AIChatExperience } from '@kbn/ai-assistant-common'; import { - ATTACKS_ALERTS_ALIGNMENT_ENABLED, + ENABLE_ALERTS_AND_ATTACKS_ALIGNMENT_SETTING, SecurityPageName, } from '@kbn/security-solution-navigation'; import { i18nStrings, securityLink } from '@kbn/security-solution-navigation/links'; @@ -40,7 +40,7 @@ export const createNavigationTree = ( }, defaultNavigationTree.dashboards(), defaultNavigationTree.rules(), - services.featureFlags.getBooleanValue(ATTACKS_ALERTS_ALIGNMENT_ENABLED, false) + services.uiSettings.get(ENABLE_ALERTS_AND_ATTACKS_ALIGNMENT_SETTING, false) ? defaultNavigationTree.alertDetections() : { id: SecurityPageName.alerts, diff --git a/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/navigation_tree.ts b/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/navigation_tree.ts index 7bf1ccb370ddb..1b851f4ab801f 100644 --- a/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/navigation_tree.ts +++ b/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/navigation_tree.ts @@ -9,7 +9,7 @@ import type { AppDeepLinkId, NavigationTreeDefinition } from '@kbn/core-chrome-b import { i18n } from '@kbn/i18n'; import { AIChatExperience } from '@kbn/ai-assistant-common'; import { - ATTACKS_ALERTS_ALIGNMENT_ENABLED, + ENABLE_ALERTS_AND_ATTACKS_ALIGNMENT_SETTING, SecurityGroupName, SecurityPageName, } from '@kbn/security-solution-navigation'; @@ -47,7 +47,7 @@ export const createNavigationTree = async ( }, defaultNavigationTree.dashboards(), defaultNavigationTree.rules(), - services.featureFlags.getBooleanValue(ATTACKS_ALERTS_ALIGNMENT_ENABLED, false) + services.uiSettings.get(ENABLE_ALERTS_AND_ATTACKS_ALIGNMENT_SETTING, false) ? defaultNavigationTree.alertDetections() : { id: SecurityPageName.alerts,