From 88c4be63155533e02dbc15b9f52953ae92f74c19 Mon Sep 17 00:00:00 2001 From: lgestc Date: Fri, 23 May 2025 10:21:19 +0200 Subject: [PATCH 01/28] --wip-- [skip ci] --- .../public/common/store/store.ts | 64 +++----------- .../hooks/use_init_data_view_manager.ts | 16 +++- .../redux/listeners/init_listener.ts | 20 ++++- .../utils/create_default_data_view.ts | 85 +++++++++++++++++++ .../containers/use_signal_helpers.tsx | 22 +++-- 5 files changed, 145 insertions(+), 62 deletions(-) create mode 100644 x-pack/solutions/security/plugins/security_solution/public/data_view_manager/utils/create_default_data_view.ts diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/store/store.ts b/x-pack/solutions/security/plugins/security_solution/public/common/store/store.ts index 8b3f8ead09175..5be274b26bae0 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/store/store.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/common/store/store.ts @@ -25,11 +25,6 @@ import { TimelineTypeEnum } from '../../../common/api/timeline'; import { TimelineId } from '../../../common/types'; import { initialGroupingState } from './grouping/reducer'; import type { GroupState } from './grouping/types'; -import { - DEFAULT_DATA_VIEW_ID, - DEFAULT_INDEX_KEY, - DETECTION_ENGINE_INDEX_URL, -} from '../../../common/constants'; import { telemetryMiddleware } from '../lib/telemetry'; import * as timelineActions from '../../timelines/store/actions'; import type { TimelineModel } from '../../timelines/store/model'; @@ -39,15 +34,9 @@ import type { AppAction } from './actions'; import type { Immutable } from '../../../common/endpoint/types'; import type { State } from './types'; import type { TimelineState } from '../../timelines/store/types'; -import type { - KibanaDataView, - SourcererModel, - SourcererDataView, -} from '../../sourcerer/store/model'; -import { initDataView } from '../../sourcerer/store/model'; +import type { SourcererDataView } from '../../sourcerer/store/model'; import type { StartedSubPlugins, StartPlugins } from '../../types'; import type { ExperimentalFeatures } from '../../../common/experimental_features'; -import { createSourcererDataView } from '../../sourcerer/containers/create_sourcerer_data_view'; import type { AnalyzerState } from '../../resolver/types'; import { resolverMiddlewareFactory } from '../../resolver/store/middleware'; import { dataAccessLayerFactory } from '../../resolver/data_access_layer/factory'; @@ -55,7 +44,7 @@ import { sourcererActions } from '../../sourcerer/store'; import { createMiddlewares } from './middlewares'; import { addNewTimeline } from '../../timelines/store/helpers'; import { initialNotesState } from '../../notes/store/notes.slice'; -import { hasAccessToSecuritySolution } from '../../helpers_access'; +import { bootstrapSourcererDataViews } from '../../data_view_manager/utils/create_default_data_view'; let store: Store | null = null; @@ -66,46 +55,15 @@ export const createStoreFactory = async ( storage: Storage, enableExperimental: ExperimentalFeatures ): Promise> => { - let signal: { name: string | null; index_mapping_outdated: null | boolean } = { - name: null, - index_mapping_outdated: null, - }; - try { - if (hasAccessToSecuritySolution(coreStart.application.capabilities)) { - signal = await coreStart.http.fetch(DETECTION_ENGINE_INDEX_URL, { - version: '2023-10-31', - method: 'GET', - }); - } - } catch { - signal = { name: null, index_mapping_outdated: null }; - } - - const configPatternList = coreStart.uiSettings.get(DEFAULT_INDEX_KEY); - let defaultDataView: SourcererModel['defaultDataView']; - let kibanaDataViews: SourcererModel['kibanaDataViews']; - try { - // check for/generate default Security Solution Kibana data view - const sourcererDataViews = await createSourcererDataView({ - body: { - patternList: [...configPatternList, ...(signal.name != null ? [signal.name] : [])], - }, - dataViewService: startPlugins.data.dataViews, - dataViewId: `${DEFAULT_DATA_VIEW_ID}-${(await startPlugins.spaces?.getActiveSpace())?.id}`, - }); - - if (sourcererDataViews === undefined) { - throw new Error(''); - } - defaultDataView = { ...initDataView, ...sourcererDataViews.defaultDataView }; - kibanaDataViews = sourcererDataViews.kibanaDataViews.map((dataView: KibanaDataView) => ({ - ...initDataView, - ...dataView, - })); - } catch (error) { - defaultDataView = { ...initDataView, error }; - kibanaDataViews = []; - } + const { kibanaDataViews, defaultDataView, signal } = await bootstrapSourcererDataViews({ + application: coreStart.application, + http: coreStart.http, + dataViewService: startPlugins.data.dataViews, + uiSettings: coreStart.uiSettings, + spaces: startPlugins.spaces, + // TODO: (new data view picker) remove this in cleanup phase https://github.com/elastic/security-team/issues/12665 + skip: enableExperimental.newDataViewPickerEnabled, + }); const timelineInitialState = { timeline: { 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 d48af1ec4c650..c5b4b7c9710e1 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 @@ -41,12 +41,18 @@ export const useInitDataViewManager = () => { const { newDataViewPickerEnabled } = useEnableExperimental(); useEffect(() => { + // TODO: (new data view picker) remove this in cleanup phase https://github.com/elastic/security-team/issues/12665 if (!newDataViewPickerEnabled) { return; } + // NOTE: init listener contains logic that preloads default security solution data view const dataViewsLoadingListener = createInitListener({ dataViews: services.dataViews, + http: services.http, + uiSettings: services.uiSettings, + application: services.application, + spaces: services.spaces, }); const dataViewSelectedListener = createDataViewSelectedListener({ @@ -63,5 +69,13 @@ export const useInitDataViewManager = () => { dispatch(removeListener(dataViewsLoadingListener)); dispatch(removeListener(dataViewSelectedListener)); }; - }, [dispatch, newDataViewPickerEnabled, services.dataViews]); + }, [ + dispatch, + newDataViewPickerEnabled, + services.application, + services.dataViews, + services.http, + services.spaces, + services.uiSettings, + ]); }; 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 7e460cb47b36b..371f551655529 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 @@ -11,8 +11,15 @@ import type { RootState } from '../reducer'; import { sharedDataViewManagerSlice } from '../slices'; import { DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID, DataViewManagerScopeName } from '../../constants'; import { selectDataViewAsync } from '../actions'; +import { bootstrapSourcererDataViews } from '../../utils/create_default_data_view'; -export const createInitListener = (dependencies: { dataViews: DataViewsServicePublic }) => { +export const createInitListener = (dependencies: { + dataViews: DataViewsServicePublic; + http: any; + spaces: any; + application: any; + uiSettings: any; +}) => { return { actionCreator: sharedDataViewManagerSlice.actions.init, effect: async ( @@ -20,6 +27,17 @@ export const createInitListener = (dependencies: { dataViews: DataViewsServicePu listenerApi: ListenerEffectAPI> ) => { try { + // Initialize default security data view first + // Note: this is subject to change, as we might want to add specific data view just for alerts + + await bootstrapSourcererDataViews({ + dataViewService: dependencies.dataViews, + uiSettings: dependencies.uiSettings, + spaces: dependencies.spaces, + application: dependencies.application, + http: dependencies.http, + }); + // NOTE: This is later used in the data view manager drop-down selector const dataViews = await dependencies.dataViews.getAllDataViewLazy(); const dataViewSpecs = await Promise.all(dataViews.map((dataView) => dataView.toSpec())); 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 new file mode 100644 index 0000000000000..e0d293c5fa8e6 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/utils/create_default_data_view.ts @@ -0,0 +1,85 @@ +/* + * 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 { KibanaDataView, SourcererModel } from '../../sourcerer/store/model'; +import { initDataView } from '../../sourcerer/store/model'; +import { createSourcererDataView } from '../../sourcerer/containers/create_sourcerer_data_view'; +import { + DEFAULT_DATA_VIEW_ID, + DEFAULT_INDEX_KEY, + DETECTION_ENGINE_INDEX_URL, +} from '../../../common/constants'; +import { hasAccessToSecuritySolution } from '../../helpers_access'; + +export const bootstrapSourcererDataViews = async ({ + uiSettings, + dataViewService, + spaces, + skip, + http, + application, +}: { + dataViewService: any; + uiSettings: any; + spaces: any; + http: any; + application: any; + skip?: boolean; +}) => { + const configPatternList = uiSettings.get(DEFAULT_INDEX_KEY); + let defaultDataView: SourcererModel['defaultDataView']; + let kibanaDataViews: SourcererModel['kibanaDataViews']; + + let signal: { name: string | null; index_mapping_outdated: null | boolean } = { + name: null, + index_mapping_outdated: null, + }; + + try { + if (hasAccessToSecuritySolution(application.capabilities)) { + signal = await http.fetch(DETECTION_ENGINE_INDEX_URL, { + version: '2023-10-31', + method: 'GET', + }); + } + } catch { + // NOTE: this is empty intentionally + } + + if (skip) { + return { + kibanaDataViews: [], + defaultDataView: { ...initDataView }, + signal, + }; + } + + try { + // check for/generate default Security Solution Kibana data view + const sourcererDataViews = await createSourcererDataView({ + body: { + patternList: [...configPatternList, ...(signal.name != null ? [signal.name] : [])], + }, + dataViewService, + dataViewId: `${DEFAULT_DATA_VIEW_ID}-${(await spaces?.getActiveSpace())?.id}`, + }); + + if (sourcererDataViews === undefined) { + throw new Error(''); + } + defaultDataView = { ...initDataView, ...sourcererDataViews.defaultDataView }; + kibanaDataViews = sourcererDataViews.kibanaDataViews.map((dataView: KibanaDataView) => ({ + ...initDataView, + ...dataView, + })); + } catch (error) { + defaultDataView = { ...initDataView, error }; + kibanaDataViews = []; + } + + return { kibanaDataViews, defaultDataView, signal }; +}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_signal_helpers.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_signal_helpers.tsx index af8c05d22b460..f6d17b0187116 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_signal_helpers.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_signal_helpers.tsx @@ -11,10 +11,11 @@ import { useDispatch, useSelector } from 'react-redux'; import { sourcererSelectors, sourcererActions } from '../store'; import { useSourcererDataView } from '.'; import { SourcererScopeName } from '../store/model'; -import { useDataView } from '../../common/containers/source/use_data_view'; +import { useDataView as useOldDataView } from '../../common/containers/source/use_data_view'; import { useAppToasts } from '../../common/hooks/use_app_toasts'; import { useKibana } from '../../common/lib/kibana'; import { createSourcererDataView } from './create_sourcerer_data_view'; +import { useDataView } from '../../data_view_manager/hooks/use_data_view'; export const useSignalHelpers = (): { /* when defined, signal index has been initiated but does not exist */ @@ -23,7 +24,7 @@ export const useSignalHelpers = (): { signalIndexNeedsInit: boolean; } => { const { indicesExist, dataViewId } = useSourcererDataView(SourcererScopeName.detections); - const { indexFieldsSearch } = useDataView(); + const { indexFieldsSearch } = useOldDataView(); const dispatch = useDispatch(); const { addError } = useAppToasts(); const abortCtrl = useRef(new AbortController()); @@ -32,10 +33,17 @@ export const useSignalHelpers = (): { } = useKibana().services; const signalIndexNameSourcerer = useSelector(sourcererSelectors.signalIndexName); - const defaultDataView = useSelector(sourcererSelectors.defaultDataView); + const oldDefaultDataView = useSelector(sourcererSelectors.defaultDataView); + + const { dataView: experimentalDefaultDataView } = useDataView(SourcererScopeName.default); + + const defaultDataViewPattern = experimentalDefaultDataView + ? experimentalDefaultDataView.getIndexPattern() + : oldDefaultDataView.title; + const signalIndexNeedsInit = useMemo( - () => !defaultDataView.title.includes(`${signalIndexNameSourcerer}`), - [defaultDataView.title, signalIndexNameSourcerer] + () => !defaultDataViewPattern.includes(`${signalIndexNameSourcerer}`), + [defaultDataViewPattern, signalIndexNameSourcerer] ); const shouldWePollForIndex = useMemo( () => !indicesExist && !signalIndexNeedsInit, @@ -47,7 +55,7 @@ export const useSignalHelpers = (): { abortCtrl.current = new AbortController(); try { const sourcererDataView = await createSourcererDataView({ - body: { patternList: defaultDataView.title.split(',') }, + body: { patternList: defaultDataViewPattern.split(',') }, signal: abortCtrl.current.signal, dataViewId, dataViewService: dataViews, @@ -86,7 +94,7 @@ export const useSignalHelpers = (): { addError, dataViewId, dataViews, - defaultDataView.title, + defaultDataViewPattern, dispatch, indexFieldsSearch, signalIndexNameSourcerer, From 12582f814d2315adea1a7ee9a19d9e4ce9d19b84 Mon Sep 17 00:00:00 2001 From: lgestc Date: Fri, 23 May 2025 10:46:45 +0200 Subject: [PATCH 02/28] --wip-- [skip ci] --- .../utils/create_default_data_view.ts | 17 ++++++----------- .../sourcerer/containers/use_signal_helpers.tsx | 4 +++- 2 files changed, 9 insertions(+), 12 deletions(-) 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 e0d293c5fa8e6..8eacd25dfb9f5 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 @@ -39,17 +39,6 @@ export const bootstrapSourcererDataViews = async ({ index_mapping_outdated: null, }; - try { - if (hasAccessToSecuritySolution(application.capabilities)) { - signal = await http.fetch(DETECTION_ENGINE_INDEX_URL, { - version: '2023-10-31', - method: 'GET', - }); - } - } catch { - // NOTE: this is empty intentionally - } - if (skip) { return { kibanaDataViews: [], @@ -59,6 +48,12 @@ export const bootstrapSourcererDataViews = async ({ } try { + if (hasAccessToSecuritySolution(application.capabilities)) { + signal = await http.fetch(DETECTION_ENGINE_INDEX_URL, { + version: '2023-10-31', + method: 'GET', + }); + } // check for/generate default Security Solution Kibana data view const sourcererDataViews = await createSourcererDataView({ body: { diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_signal_helpers.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_signal_helpers.tsx index f6d17b0187116..bcc398a07540b 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_signal_helpers.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_signal_helpers.tsx @@ -16,6 +16,7 @@ import { useAppToasts } from '../../common/hooks/use_app_toasts'; import { useKibana } from '../../common/lib/kibana'; import { createSourcererDataView } from './create_sourcerer_data_view'; import { useDataView } from '../../data_view_manager/hooks/use_data_view'; +import { useUserInfo } from '../../detections/components/user_info'; export const useSignalHelpers = (): { /* when defined, signal index has been initiated but does not exist */ @@ -32,7 +33,8 @@ export const useSignalHelpers = (): { data: { dataViews }, } = useKibana().services; - const signalIndexNameSourcerer = useSelector(sourcererSelectors.signalIndexName); + const { signalIndexName: signalIndexNameSourcerer } = useUserInfo(); + const oldDefaultDataView = useSelector(sourcererSelectors.defaultDataView); const { dataView: experimentalDefaultDataView } = useDataView(SourcererScopeName.default); From a9147a1360efb96ed2b4493951e913a58170418c Mon Sep 17 00:00:00 2001 From: lgestc Date: Fri, 23 May 2025 11:17:14 +0200 Subject: [PATCH 03/28] --wip-- [skip ci] --- .../data_view_manager/redux/listeners/init_listener.ts | 8 +++++--- .../data_view_manager/utils/create_default_data_view.ts | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) 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 371f551655529..4d4b4128d07b7 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 @@ -9,7 +9,7 @@ import type { AnyAction, Dispatch, ListenerEffectAPI } from '@reduxjs/toolkit'; import type { DataViewsServicePublic } from '@kbn/data-views-plugin/public'; import type { RootState } from '../reducer'; import { sharedDataViewManagerSlice } from '../slices'; -import { DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID, DataViewManagerScopeName } from '../../constants'; +import { DataViewManagerScopeName } from '../../constants'; import { selectDataViewAsync } from '../actions'; import { bootstrapSourcererDataViews } from '../../utils/create_default_data_view'; @@ -30,7 +30,9 @@ export const createInitListener = (dependencies: { // Initialize default security data view first // Note: this is subject to change, as we might want to add specific data view just for alerts - await bootstrapSourcererDataViews({ + // TODO: store this in shared reducer and replace all the uses of constant data view id (DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID) + // Issue: https://github.com/elastic/security-team/issues/12667 + const { defaultDataView } = await bootstrapSourcererDataViews({ dataViewService: dependencies.dataViews, uiSettings: dependencies.uiSettings, spaces: dependencies.spaces, @@ -48,7 +50,7 @@ export const createInitListener = (dependencies: { // NOTE: we will remove this ideally and load only when particular dataview is necessary listenerApi.dispatch( selectDataViewAsync({ - id: DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID, + id: defaultDataView.id, scope: [ DataViewManagerScopeName.default, DataViewManagerScopeName.detections, 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 8eacd25dfb9f5..739557c66b170 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 @@ -54,6 +54,7 @@ export const bootstrapSourcererDataViews = async ({ method: 'GET', }); } + // check for/generate default Security Solution Kibana data view const sourcererDataViews = await createSourcererDataView({ body: { From 95c4093ceec00515df888fcdc78bfc20dc4db966 Mon Sep 17 00:00:00 2001 From: lgestc Date: Mon, 26 May 2025 10:07:46 +0200 Subject: [PATCH 04/28] --wip-- [skip ci] --- .../hooks/use_signal_index_name.ts | 10 +++++++ .../public/data_view_manager/redux/slices.ts | 4 +++ .../public/data_view_manager/redux/types.ts | 1 + .../containers/use_init_sourcerer.tsx | 2 ++ .../containers/use_signal_helpers.tsx | 29 ++++++++++++------- 5 files changed, 36 insertions(+), 10 deletions(-) create mode 100644 x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_signal_index_name.ts diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_signal_index_name.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_signal_index_name.ts new file mode 100644 index 0000000000000..827841112fb54 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_signal_index_name.ts @@ -0,0 +1,10 @@ +/* + * 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. + */ + +export const useSignalIndexName = () => { + return ''; +}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/slices.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/slices.ts index a83df00443be6..985e3f3a849de 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/slices.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/slices.ts @@ -22,6 +22,7 @@ export const initialSharedState: SharedDataViewSelectionState = { dataViews: [], adhocDataViews: [], status: 'pristine', + signalIndexName: '', }; export const sharedDataViewManagerSlice = createSlice({ @@ -32,6 +33,9 @@ export const sharedDataViewManagerSlice = createSlice({ state.dataViews = action.payload; state.status = 'ready'; }, + setSignalIndexName: (state, action: PayloadAction) => { + state.signalIndexName = action.payload; + }, addDataView: (state, action: PayloadAction) => { const dataViewSpec = action.payload.toSpec(); diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/types.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/types.ts index 662e64a594eb8..283b86f83364a 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/types.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/types.ts @@ -23,6 +23,7 @@ export interface SharedDataViewSelectionState { dataViews: DataViewSpec[]; adhocDataViews: DataViewSpec[]; status: 'pristine' | 'loading' | 'error' | 'ready'; + signalIndexName: string; } export { type DataViewSpec }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_init_sourcerer.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_init_sourcerer.tsx index 7092c4a985943..e5b5126d1c176 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_init_sourcerer.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_init_sourcerer.tsx @@ -24,6 +24,7 @@ import { useInitializeUrlParam, useUpdateUrlParam } from '../../common/utils/glo import { URL_PARAM_KEY } from '../../common/hooks/use_url_state'; import { useKibana } from '../../common/lib/kibana'; import { useSourcererDataView } from '.'; +import { sharedDataViewManagerSlice } from '../../data_view_manager/redux/slices'; export const useInitSourcerer = ( scopeId: SourcererScopeName.default | SourcererScopeName.detections = SourcererScopeName.default @@ -305,6 +306,7 @@ export const useInitSourcerer = ( ) { updateSourcererDataView(signalIndexName); dispatch(sourcererActions.setSignalIndexName({ signalIndexName })); + dispatch(sharedDataViewManagerSlice.actions.setSignalIndexName(signalIndexName)); } }, [ defaultDataView.id.length, diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_signal_helpers.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_signal_helpers.tsx index bcc398a07540b..d19a1deb84823 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_signal_helpers.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_signal_helpers.tsx @@ -16,7 +16,8 @@ import { useAppToasts } from '../../common/hooks/use_app_toasts'; import { useKibana } from '../../common/lib/kibana'; import { createSourcererDataView } from './create_sourcerer_data_view'; import { useDataView } from '../../data_view_manager/hooks/use_data_view'; -import { useUserInfo } from '../../detections/components/user_info'; +import { useSignalIndexName } from '../../data_view_manager/hooks/use_signal_index_name'; +import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; export const useSignalHelpers = (): { /* when defined, signal index has been initiated but does not exist */ @@ -33,19 +34,27 @@ export const useSignalHelpers = (): { data: { dataViews }, } = useKibana().services; - const { signalIndexName: signalIndexNameSourcerer } = useUserInfo(); + const signalIndexNameSourcerer = useSelector(sourcererSelectors.signalIndexName); + + const newDataViewPickerEnabled = useIsExperimentalFeatureEnabled('newDataViewPickerEnabled'); + + const experimentalSignalIndexName = useSignalIndexName(); const oldDefaultDataView = useSelector(sourcererSelectors.defaultDataView); + const signalIndexName = newDataViewPickerEnabled + ? experimentalSignalIndexName + : signalIndexNameSourcerer; + const { dataView: experimentalDefaultDataView } = useDataView(SourcererScopeName.default); - const defaultDataViewPattern = experimentalDefaultDataView - ? experimentalDefaultDataView.getIndexPattern() + const defaultDataViewPattern = newDataViewPickerEnabled + ? experimentalDefaultDataView?.getIndexPattern() ?? '' : oldDefaultDataView.title; const signalIndexNeedsInit = useMemo( - () => !defaultDataViewPattern.includes(`${signalIndexNameSourcerer}`), - [defaultDataViewPattern, signalIndexNameSourcerer] + () => !defaultDataViewPattern.includes(`${signalIndexName}`), + [defaultDataViewPattern, signalIndexName] ); const shouldWePollForIndex = useMemo( () => !indicesExist && !signalIndexNeedsInit, @@ -64,8 +73,8 @@ export const useSignalHelpers = (): { }); if ( - signalIndexNameSourcerer !== null && - sourcererDataView?.defaultDataView.patternList.includes(signalIndexNameSourcerer) + signalIndexName !== null && + sourcererDataView?.defaultDataView.patternList.includes(signalIndexName) ) { // first time signals is defined and validated in the sourcerer // redo indexFieldsSearch @@ -88,7 +97,7 @@ export const useSignalHelpers = (): { } }; - if (signalIndexNameSourcerer !== null) { + if (signalIndexName !== null) { abortCtrl.current.abort(); asyncSearch(); } @@ -99,7 +108,7 @@ export const useSignalHelpers = (): { defaultDataViewPattern, dispatch, indexFieldsSearch, - signalIndexNameSourcerer, + signalIndexName, ]); return { From bd866c0bf8669bafb8e407a9f208ba0a344f30de Mon Sep 17 00:00:00 2001 From: lgestc Date: Mon, 26 May 2025 11:43:12 +0200 Subject: [PATCH 05/28] --wip-- [skip ci] --- .../listeners/data_view_selected.test.ts | 1 + .../redux/listeners/init_listener.test.ts | 27 ++++++++++++++++++- .../redux/listeners/init_listener.ts | 10 ++++--- .../utils/create_default_data_view.ts | 13 +++++---- 4 files changed, 41 insertions(+), 10 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/data_view_selected.test.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/data_view_selected.test.ts index 9371597f0bb7c..39075558d368b 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/data_view_selected.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/data_view_selected.test.ts @@ -65,6 +65,7 @@ const mockedState: RootState = { }, ], status: 'pristine', + signalIndexName: '', }, }, }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/init_listener.test.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/init_listener.test.ts index efdcfbed30ca9..d0ed44d12971f 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/init_listener.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/init_listener.test.ts @@ -13,6 +13,13 @@ import type { RootState } from '../reducer'; import { sharedDataViewManagerSlice } from '../slices'; import { DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID, DataViewManagerScopeName } from '../../constants'; import { selectDataViewAsync } from '../actions'; +import type { CoreStart } from '@kbn/core/public'; +import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; +import { bootstrapSourcererDataViews } from '../../utils/create_default_data_view'; + +jest.mock('../../utils/create_default_data_view', () => ({ + bootstrapSourcererDataViews: jest.fn(), +})); const mockDataViewsService = { get: jest.fn(), @@ -24,6 +31,11 @@ const mockDataViewsService = { getAllDataViewLazy: jest.fn().mockReturnValue([]), } as unknown as DataViewsServicePublic; +const http = {} as unknown as CoreStart['http']; +const application = {} as unknown as CoreStart['application']; +const uiSettings = {} as unknown as CoreStart['uiSettings']; +const spaces = {} as unknown as SpacesPluginStart; + const mockDispatch = jest.fn(); const mockGetState = jest.fn(() => mockDataViewManagerState); @@ -37,12 +49,25 @@ describe('createInitListener', () => { beforeEach(() => { jest.clearAllMocks(); - listener = createInitListener({ dataViews: mockDataViewsService }); + listener = createInitListener({ + dataViews: mockDataViewsService, + http, + application, + uiSettings, + spaces, + }); + + jest.mocked(bootstrapSourcererDataViews).mockResolvedValue({ + defaultDataView: { id: DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID }, + kibanaDataViews: [], + } as unknown as Awaited>); }); it('should load the data views and dispatch further actions', async () => { await listener.effect(sharedDataViewManagerSlice.actions.init(), mockListenerApi); + expect(jest.mocked(bootstrapSourcererDataViews)).toHaveBeenCalled(); + expect(jest.mocked(mockDataViewsService.getAllDataViewLazy)).toHaveBeenCalled(); expect(jest.mocked(mockListenerApi.dispatch)).toBeCalledWith( 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 4d4b4128d07b7..9e00612da8f58 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 @@ -7,6 +7,8 @@ import type { AnyAction, Dispatch, ListenerEffectAPI } from '@reduxjs/toolkit'; import type { DataViewsServicePublic } from '@kbn/data-views-plugin/public'; +import type { CoreStart } from '@kbn/core/public'; +import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import type { RootState } from '../reducer'; import { sharedDataViewManagerSlice } from '../slices'; import { DataViewManagerScopeName } from '../../constants'; @@ -14,11 +16,11 @@ import { selectDataViewAsync } from '../actions'; import { bootstrapSourcererDataViews } from '../../utils/create_default_data_view'; export const createInitListener = (dependencies: { + http: CoreStart['http']; + application: CoreStart['application']; + uiSettings: CoreStart['uiSettings']; dataViews: DataViewsServicePublic; - http: any; - spaces: any; - application: any; - uiSettings: any; + spaces: SpacesPluginStart; }) => { return { actionCreator: sharedDataViewManagerSlice.actions.init, 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 739557c66b170..d9a6e888a198b 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 @@ -5,6 +5,9 @@ * 2.0. */ +import type { CoreStart } from '@kbn/core/public'; +import type { DataViewsServicePublic } from '@kbn/data-views-plugin/public'; +import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import type { KibanaDataView, SourcererModel } from '../../sourcerer/store/model'; import { initDataView } from '../../sourcerer/store/model'; import { createSourcererDataView } from '../../sourcerer/containers/create_sourcerer_data_view'; @@ -23,11 +26,11 @@ export const bootstrapSourcererDataViews = async ({ http, application, }: { - dataViewService: any; - uiSettings: any; - spaces: any; - http: any; - application: any; + http: CoreStart['http']; + application: CoreStart['application']; + uiSettings: CoreStart['uiSettings']; + dataViewService: DataViewsServicePublic; + spaces: SpacesPluginStart; skip?: boolean; }) => { const configPatternList = uiSettings.get(DEFAULT_INDEX_KEY); From 2f1f5d0b7a693b44d70c7b7f56c3e89defdfa82f Mon Sep 17 00:00:00 2001 From: lgestc Date: Mon, 26 May 2025 12:18:40 +0200 Subject: [PATCH 06/28] --wip-- [skip ci] --- .../public/data_view_manager/utils/create_default_data_view.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d9a6e888a198b..7f9b53c07dc82 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 @@ -6,8 +6,8 @@ */ import type { CoreStart } from '@kbn/core/public'; -import type { DataViewsServicePublic } from '@kbn/data-views-plugin/public'; import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; +import type { DataViewsServicePublic } from '@kbn/data-views-plugin/public/types'; import type { KibanaDataView, SourcererModel } from '../../sourcerer/store/model'; import { initDataView } from '../../sourcerer/store/model'; import { createSourcererDataView } from '../../sourcerer/containers/create_sourcerer_data_view'; From 3e032accdc299834d51d381ad6676f47fc37264a Mon Sep 17 00:00:00 2001 From: lgestc Date: Mon, 26 May 2025 16:15:09 +0200 Subject: [PATCH 07/28] move signal init to manager hook --- .../hooks/use_init_data_view_manager.ts | 24 ++++++++++++++++++- .../containers/use_init_sourcerer.tsx | 2 -- 2 files changed, 23 insertions(+), 3 deletions(-) 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 c5b4b7c9710e1..9d2ef80fdfb1c 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 @@ -6,7 +6,7 @@ */ import { useDispatch } from 'react-redux'; -import { useEffect } from 'react'; +import { useCallback, useEffect } from 'react'; import type { AnyAction, Dispatch, ListenerEffectAPI } from '@reduxjs/toolkit'; import { addListener as originalAddListener, @@ -18,6 +18,7 @@ import { createDataViewSelectedListener } from '../redux/listeners/data_view_sel import { createInitListener } from '../redux/listeners/init_listener'; import { useEnableExperimental } from '../../common/hooks/use_experimental_features'; import { sharedDataViewManagerSlice } from '../redux/slices'; +import { useUserInfo } from '../../detections/components/user_info'; type OriginalListener = Parameters[0]; @@ -40,6 +41,27 @@ export const useInitDataViewManager = () => { const services = useKibana().services; const { newDataViewPickerEnabled } = useEnableExperimental(); + const { loading: loadingSignalIndex, signalIndexName } = useUserInfo(); + + const onSignalIndexUpdated = useCallback(() => { + if (!loadingSignalIndex && signalIndexName != null) { + dispatch(sharedDataViewManagerSlice.actions.setSignalIndexName(signalIndexName)); + } + }, [dispatch, loadingSignalIndex, signalIndexName]); + + useEffect(() => { + // TODO: (new data view picker) remove this in cleanup phase https://github.com/elastic/security-team/issues/12665 + // Also, make sure it works exactly as x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_init_sourcerer.tsx + if (!newDataViewPickerEnabled) { + return; + } + + onSignalIndexUpdated(); + // because we only want onSignalIndexUpdated to run when signalIndexName updates, + // but we want to know about the updates from the dependencies of onSignalIndexUpdated + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [signalIndexName]); + useEffect(() => { // TODO: (new data view picker) remove this in cleanup phase https://github.com/elastic/security-team/issues/12665 if (!newDataViewPickerEnabled) { diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_init_sourcerer.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_init_sourcerer.tsx index e5b5126d1c176..7092c4a985943 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_init_sourcerer.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_init_sourcerer.tsx @@ -24,7 +24,6 @@ import { useInitializeUrlParam, useUpdateUrlParam } from '../../common/utils/glo import { URL_PARAM_KEY } from '../../common/hooks/use_url_state'; import { useKibana } from '../../common/lib/kibana'; import { useSourcererDataView } from '.'; -import { sharedDataViewManagerSlice } from '../../data_view_manager/redux/slices'; export const useInitSourcerer = ( scopeId: SourcererScopeName.default | SourcererScopeName.detections = SourcererScopeName.default @@ -306,7 +305,6 @@ export const useInitSourcerer = ( ) { updateSourcererDataView(signalIndexName); dispatch(sourcererActions.setSignalIndexName({ signalIndexName })); - dispatch(sharedDataViewManagerSlice.actions.setSignalIndexName(signalIndexName)); } }, [ defaultDataView.id.length, From 2a572eb04a095d0538b29a8a25361a2beb11a5a5 Mon Sep 17 00:00:00 2001 From: lgestc Date: Tue, 27 May 2025 10:16:23 +0200 Subject: [PATCH 08/28] add missing index selector --- .../data_view_manager/hooks/use_signal_index_name.ts | 7 ++++--- .../public/data_view_manager/redux/selectors.ts | 5 +++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_signal_index_name.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_signal_index_name.ts index 827841112fb54..2c5b344251384 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_signal_index_name.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_signal_index_name.ts @@ -5,6 +5,7 @@ * 2.0. */ -export const useSignalIndexName = () => { - return ''; -}; +import { useSelector } from 'react-redux'; +import { signalIndexNameSelector } from '../redux/selectors'; + +export const useSignalIndexName = () => useSelector(signalIndexNameSelector); diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/selectors.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/selectors.ts index f5691bc26788e..6989fede4af73 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/selectors.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/selectors.ts @@ -23,3 +23,8 @@ export const sharedStateSelector = createSelector( [(state: RootState) => state.dataViewManager], (dataViewManager) => dataViewManager.shared ); + +export const signalIndexNameSelector = createSelector( + [(state: RootState) => state.dataViewManager], + (dataViewManager) => dataViewManager.shared.signalIndexName +); From 0519fea09396f59f23b7cd86063d718655473723 Mon Sep 17 00:00:00 2001 From: lgestc Date: Tue, 27 May 2025 10:36:20 +0200 Subject: [PATCH 09/28] naming --- .../public/data_view_manager/utils/create_default_data_view.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 7f9b53c07dc82..aabc83821a644 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 @@ -18,7 +18,7 @@ import { } from '../../../common/constants'; import { hasAccessToSecuritySolution } from '../../helpers_access'; -export const bootstrapSourcererDataViews = async ({ +export const createDefaultDataView = async ({ uiSettings, dataViewService, spaces, From 37ff8fc3e26e30388f532030c84542a794ef2c2a Mon Sep 17 00:00:00 2001 From: lgestc Date: Tue, 27 May 2025 10:42:23 +0200 Subject: [PATCH 10/28] naming --- .../security_solution/public/common/store/store.ts | 4 ++-- .../redux/listeners/init_listener.test.ts | 8 ++++---- .../data_view_manager/redux/listeners/init_listener.ts | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/store/store.ts b/x-pack/solutions/security/plugins/security_solution/public/common/store/store.ts index 5be274b26bae0..4a50e197fab36 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/store/store.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/common/store/store.ts @@ -44,7 +44,7 @@ import { sourcererActions } from '../../sourcerer/store'; import { createMiddlewares } from './middlewares'; import { addNewTimeline } from '../../timelines/store/helpers'; import { initialNotesState } from '../../notes/store/notes.slice'; -import { bootstrapSourcererDataViews } from '../../data_view_manager/utils/create_default_data_view'; +import { createDefaultDataView } from '../../data_view_manager/utils/create_default_data_view'; let store: Store | null = null; @@ -55,7 +55,7 @@ export const createStoreFactory = async ( storage: Storage, enableExperimental: ExperimentalFeatures ): Promise> => { - const { kibanaDataViews, defaultDataView, signal } = await bootstrapSourcererDataViews({ + const { kibanaDataViews, defaultDataView, signal } = await createDefaultDataView({ application: coreStart.application, http: coreStart.http, dataViewService: startPlugins.data.dataViews, diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/init_listener.test.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/init_listener.test.ts index d0ed44d12971f..87d6850129fbb 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/init_listener.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/init_listener.test.ts @@ -15,7 +15,7 @@ import { DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID, DataViewManagerScopeName } from import { selectDataViewAsync } from '../actions'; import type { CoreStart } from '@kbn/core/public'; import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; -import { bootstrapSourcererDataViews } from '../../utils/create_default_data_view'; +import { createDefaultDataView } from '../../utils/create_default_data_view'; jest.mock('../../utils/create_default_data_view', () => ({ bootstrapSourcererDataViews: jest.fn(), @@ -57,16 +57,16 @@ describe('createInitListener', () => { spaces, }); - jest.mocked(bootstrapSourcererDataViews).mockResolvedValue({ + jest.mocked(createDefaultDataView).mockResolvedValue({ defaultDataView: { id: DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID }, kibanaDataViews: [], - } as unknown as Awaited>); + } as unknown as Awaited>); }); it('should load the data views and dispatch further actions', async () => { await listener.effect(sharedDataViewManagerSlice.actions.init(), mockListenerApi); - expect(jest.mocked(bootstrapSourcererDataViews)).toHaveBeenCalled(); + expect(jest.mocked(createDefaultDataView)).toHaveBeenCalled(); expect(jest.mocked(mockDataViewsService.getAllDataViewLazy)).toHaveBeenCalled(); 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 9e00612da8f58..692eb88cd5037 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 @@ -13,7 +13,7 @@ import type { RootState } from '../reducer'; import { sharedDataViewManagerSlice } from '../slices'; import { DataViewManagerScopeName } from '../../constants'; import { selectDataViewAsync } from '../actions'; -import { bootstrapSourcererDataViews } from '../../utils/create_default_data_view'; +import { createDefaultDataView } from '../../utils/create_default_data_view'; export const createInitListener = (dependencies: { http: CoreStart['http']; @@ -34,7 +34,7 @@ export const createInitListener = (dependencies: { // TODO: store this in shared reducer and replace all the uses of constant data view id (DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID) // Issue: https://github.com/elastic/security-team/issues/12667 - const { defaultDataView } = await bootstrapSourcererDataViews({ + const { defaultDataView } = await createDefaultDataView({ dataViewService: dependencies.dataViews, uiSettings: dependencies.uiSettings, spaces: dependencies.spaces, From 03155b16ed0276c45719583cfe010071b446cb89 Mon Sep 17 00:00:00 2001 From: lgestc Date: Tue, 27 May 2025 10:58:41 +0200 Subject: [PATCH 11/28] add tests for create default data view --- .../utils/create_default_data_view.test.ts | 99 +++++++++++++++++++ .../utils/create_default_data_view.ts | 18 ++-- 2 files changed, 109 insertions(+), 8 deletions(-) create mode 100644 x-pack/solutions/security/plugins/security_solution/public/data_view_manager/utils/create_default_data_view.test.ts diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/utils/create_default_data_view.test.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/utils/create_default_data_view.test.ts new file mode 100644 index 0000000000000..66c18e343754d --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/utils/create_default_data_view.test.ts @@ -0,0 +1,99 @@ +/* + * 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 CreateDefaultDataViewDependencies, + createDefaultDataView, +} from './create_default_data_view'; +import { initDataView } from '../../sourcerer/store/model'; +import * as helpersAccess from '../../helpers_access'; +import * as createSourcererDataViewModule from '../../sourcerer/containers/create_sourcerer_data_view'; +import { DEFAULT_DATA_VIEW_ID, DETECTION_ENGINE_INDEX_URL } from '../../../common/constants'; + +jest.mock('../../helpers_access'); +jest.mock('../../sourcerer/containers/create_sourcerer_data_view'); + +const mockUiSettings = { + get: jest.fn(), +}; + +const mockDataViewService = {}; + +const mockSpaces = { + getActiveSpace: jest.fn(), +}; + +const mockHttp = { + fetch: jest.fn(), +}; + +const mockApplication = { + capabilities: {}, +}; + +const defaultDeps = { + http: mockHttp, + application: mockApplication, + uiSettings: mockUiSettings, + dataViewService: mockDataViewService, + spaces: mockSpaces, +} as unknown as CreateDefaultDataViewDependencies; + +describe('createDefaultDataView', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockUiSettings.get.mockReturnValue(['pattern-*']); + mockSpaces.getActiveSpace.mockResolvedValue({ id: 'space1' }); + (helpersAccess.hasAccessToSecuritySolution as jest.Mock).mockReturnValue(true); + mockHttp.fetch.mockResolvedValue({ name: 'signal-index', index_mapping_outdated: false }); + (createSourcererDataViewModule.createSourcererDataView as jest.Mock).mockResolvedValue({ + defaultDataView: { id: 'dv1', title: 'title1' }, + kibanaDataViews: [{ id: 'dv1', title: 'title1' }], + }); + }); + + it('returns default values if skip is true', async () => { + const result = await createDefaultDataView({ ...defaultDeps, skip: true }); + expect(result.kibanaDataViews).toEqual([]); + expect(result.defaultDataView).toEqual(initDataView); + expect(result.signal).toEqual({ name: null, index_mapping_outdated: null }); + }); + + it('fetches signal index and creates data views when user has access', async () => { + const result = await createDefaultDataView(defaultDeps); + expect(helpersAccess.hasAccessToSecuritySolution).toHaveBeenCalledWith( + mockApplication.capabilities + ); + expect(mockHttp.fetch).toHaveBeenCalledWith(DETECTION_ENGINE_INDEX_URL, expect.any(Object)); + expect(createSourcererDataViewModule.createSourcererDataView).toHaveBeenCalledWith( + expect.objectContaining({ + body: { patternList: ['pattern-*', 'signal-index'] }, + dataViewService: mockDataViewService, + dataViewId: `${DEFAULT_DATA_VIEW_ID}-space1`, + }) + ); + expect(result.defaultDataView).toMatchObject({ id: 'dv1', title: 'title1' }); + expect(result.kibanaDataViews[0]).toMatchObject({ id: 'dv1', title: 'title1' }); + expect(result.signal).toEqual({ name: 'signal-index', index_mapping_outdated: false }); + }); + + it('does not fetch signal index if user has no access', async () => { + (helpersAccess.hasAccessToSecuritySolution as jest.Mock).mockReturnValue(false); + const result = await createDefaultDataView(defaultDeps); + expect(mockHttp.fetch).not.toHaveBeenCalled(); + expect(result.signal).toEqual({ name: null, index_mapping_outdated: null }); + }); + + it('returns error in defaultDataView if an exception is thrown', async () => { + (createSourcererDataViewModule.createSourcererDataView as jest.Mock).mockImplementation(() => { + throw new Error('fail'); + }); + const result = await createDefaultDataView(defaultDeps); + expect(result.defaultDataView.error).toBeInstanceOf(Error); + expect(result.kibanaDataViews).toEqual([]); + }); +}); 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 aabc83821a644..d58c83bcf92f2 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 @@ -18,6 +18,15 @@ import { } from '../../../common/constants'; import { hasAccessToSecuritySolution } from '../../helpers_access'; +export interface CreateDefaultDataViewDependencies { + http: CoreStart['http']; + application: CoreStart['application']; + uiSettings: CoreStart['uiSettings']; + dataViewService: DataViewsServicePublic; + spaces: SpacesPluginStart; + skip?: boolean; +} + export const createDefaultDataView = async ({ uiSettings, dataViewService, @@ -25,14 +34,7 @@ export const createDefaultDataView = async ({ skip, http, application, -}: { - http: CoreStart['http']; - application: CoreStart['application']; - uiSettings: CoreStart['uiSettings']; - dataViewService: DataViewsServicePublic; - spaces: SpacesPluginStart; - skip?: boolean; -}) => { +}: CreateDefaultDataViewDependencies) => { const configPatternList = uiSettings.get(DEFAULT_INDEX_KEY); let defaultDataView: SourcererModel['defaultDataView']; let kibanaDataViews: SourcererModel['kibanaDataViews']; From 17a478c5e4424d71fe02d2a4d043d1debc8c069e Mon Sep 17 00:00:00 2001 From: lgestc Date: Tue, 27 May 2025 13:15:06 +0200 Subject: [PATCH 12/28] fix broken test --- .../data_view_manager/redux/listeners/init_listener.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/init_listener.test.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/init_listener.test.ts index 87d6850129fbb..acc79bcda74d6 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/init_listener.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/init_listener.test.ts @@ -18,7 +18,7 @@ import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import { createDefaultDataView } from '../../utils/create_default_data_view'; jest.mock('../../utils/create_default_data_view', () => ({ - bootstrapSourcererDataViews: jest.fn(), + createDefaultDataView: jest.fn(), })); const mockDataViewsService = { From 670ff031cae7c6a07507069bb158a469e540d0cb Mon Sep 17 00:00:00 2001 From: lgestc Date: Tue, 3 Jun 2025 14:37:43 +0200 Subject: [PATCH 13/28] improvements --- .../redux/listeners/data_view_selected.ts | 6 ++- .../redux/listeners/init_listener.ts | 37 +++++++++++++++---- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/data_view_selected.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/data_view_selected.ts index 4a208f37ddb3a..b9d4f96c9350e 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/data_view_selected.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/data_view_selected.ts @@ -12,7 +12,7 @@ import type { RootState } from '../reducer'; import { scopes } from '../reducer'; import { selectDataViewAsync } from '../actions'; import { sharedDataViewManagerSlice } from '../slices'; -import type { DataViewManagerScopeName } from '../../constants'; +import { DataViewManagerScopeName } from '../../constants'; export const createDataViewSelectedListener = (dependencies: { scope: DataViewManagerScopeName; @@ -28,6 +28,10 @@ export const createDataViewSelectedListener = (dependencies: { return; } + if (dependencies.scope === DataViewManagerScopeName.timeline) { + console.log('createDataViewSelectedListener', action); + } + // Cancel effects running for the current scope to prevent race conditions listenerApi.cancelActiveListeners(); 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 449a86ad93dea..9ac877581bf32 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 @@ -15,6 +15,23 @@ import { DataViewManagerScopeName } from '../../constants'; import { selectDataViewAsync } from '../actions'; import { createDefaultDataView } from '../../utils/create_default_data_view'; +/** + * Creates a Redux listener for initializing the Data View Manager state. + * + * This listener is responsible for: + * - Creating and preloading the default security data view using the provided dependencies. + * - Fetching all available data views and dispatching them to the store for use in selectors. + * - Preloading the default data view for all defined scopes (detections, analyzer, timeline, default), + * but only for those scopes that have not already been initialized. + * - Handling any additional data view selections provided in the action payload (e.g., from URL storage). + * - Dispatching an error action if initialization fails. + * + * The listener ensures that race conditions are avoided by only initializing scopes that are not already set, + * 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. + * @returns An object with the actionCreator and effect for Redux listener middleware. + */ export const createInitListener = (dependencies: { http: CoreStart['http']; application: CoreStart['application']; @@ -51,19 +68,23 @@ export const createInitListener = (dependencies: { // Preload the default data view for all the scopes // Immediate calls that would dispatch this call from other places will cancel this action, // preventing race conditions + // Whats more, portions of the state that already have selections applied to them will not be reset in the init listener. [ DataViewManagerScopeName.detections, DataViewManagerScopeName.analyzer, DataViewManagerScopeName.timeline, DataViewManagerScopeName.default, - ].forEach((scope) => { - listenerApi.dispatch( - selectDataViewAsync({ - id: defaultDataView.id, - scope, - }) - ); - }); + ] + // NOTE: only init default data view for slices that are not initialized yet + .filter((scope) => !listenerApi.getState().dataViewManager[scope].dataViewId) + .forEach((scope) => { + listenerApi.dispatch( + selectDataViewAsync({ + id: defaultDataView.id, + scope, + }) + ); + }); // NOTE: if there is a list of data views to preload other than default one (eg. coming in from the url storage) action.payload.forEach((defaultSelection) => { From ebf054a33c80a5de9609a19b9839591235f47bc3 Mon Sep 17 00:00:00 2001 From: lgestc Date: Tue, 3 Jun 2025 14:52:00 +0200 Subject: [PATCH 14/28] remove hardcoded data view --- .../components/data_view_picker/index.tsx | 26 +++++++++---------- .../hooks/use_managed_data_views.ts | 7 +++-- .../hooks/use_saved_data_views.ts | 7 +++-- .../redux/listeners/data_view_selected.ts | 6 +---- .../redux/listeners/init_listener.ts | 6 +++++ .../public/data_view_manager/redux/slices.ts | 4 +++ .../public/data_view_manager/redux/types.ts | 1 + 7 files changed, 31 insertions(+), 26 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/components/data_view_picker/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/components/data_view_picker/index.tsx index e083a01046e8f..fc95b83e940d7 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/components/data_view_picker/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/components/data_view_picker/index.tsx @@ -18,7 +18,7 @@ import { useDataViewSpec } from '../../hooks/use_data_view_spec'; import { sharedStateSelector } from '../../redux/selectors'; import { sharedDataViewManagerSlice } from '../../redux/slices'; import { useSelectDataView } from '../../hooks/use_select_data_view'; -import { DataViewManagerScopeName, DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID } from '../../constants'; +import { DataViewManagerScopeName } from '../../constants'; import { useManagedDataViews } from '../../hooks/use_managed_data_views'; import { useSavedDataViews } from '../../hooks/use_saved_data_views'; import { DEFAULT_SECURITY_DATA_VIEW, LOADING } from './translations'; @@ -51,6 +51,15 @@ export const DataViewPicker = memo(({ scope, onClosePopover, disabled }: DataVie const { dataViewSpec, status } = useDataViewSpec(scope); + const { adhocDataViews: adhocDataViewSpecs, defaultDataViewId } = + useSelector(sharedStateSelector); + const adhocDataViews = useMemo(() => { + return adhocDataViewSpecs.map((spec) => new DataView({ spec, fieldFormats })); + }, [adhocDataViewSpecs, fieldFormats]); + + const managedDataViews = useManagedDataViews(); + const savedDataViews = useSavedDataViews(); + const isDefaultSourcerer = scope === DataViewManagerScopeName.default; const updateUrlParam = useUpdateUrlParam(URL_PARAM_KEY.sourcerer); @@ -134,7 +143,7 @@ export const DataViewPicker = memo(({ scope, onClosePopover, disabled }: DataVie return { label: LOADING }; } - if (dataViewSpec.id === DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID) { + if (dataViewSpec.id === defaultDataViewId) { return { label: DEFAULT_SECURITY_DATA_VIEW, }; @@ -143,22 +152,13 @@ export const DataViewPicker = memo(({ scope, onClosePopover, disabled }: DataVie return { label: dataViewSpec?.name || dataViewSpec?.id || 'Data view', }; - }, [dataViewSpec.id, dataViewSpec?.name, status]); - - const { adhocDataViews: adhocDataViewSpecs } = useSelector(sharedStateSelector); - - const adhocDataViews = useMemo(() => { - return adhocDataViewSpecs.map((spec) => new DataView({ spec, fieldFormats })); - }, [adhocDataViewSpecs, fieldFormats]); - - const managedDataViews = useManagedDataViews(); - const savedDataViews = useSavedDataViews(); + }, [dataViewSpec.id, dataViewSpec?.name, defaultDataViewId, status]); return (
{ - const { dataViews } = useSelector(sharedStateSelector); + const { dataViews, defaultDataViewId } = useSelector(sharedStateSelector); const { services: { fieldFormats }, } = useKibana(); return useMemo(() => { const managed = dataViews - .filter((dv) => dv.id === DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID) + .filter((dv) => dv.id === defaultDataViewId) .map((spec) => new DataView({ spec, fieldFormats })); return managed; - }, [dataViews, fieldFormats]); + }, [dataViews, defaultDataViewId, fieldFormats]); }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_saved_data_views.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_saved_data_views.ts index 7b2ca3abfca63..ba048880ccc61 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_saved_data_views.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_saved_data_views.ts @@ -9,14 +9,13 @@ import { type DataViewListItem } from '@kbn/data-views-plugin/public'; import { useMemo } from 'react'; import { useSelector } from 'react-redux'; import { sharedStateSelector } from '../redux/selectors'; -import { DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID } from '../constants'; export const useSavedDataViews = () => { - const { dataViews } = useSelector(sharedStateSelector); + const { dataViews, defaultDataViewId } = useSelector(sharedStateSelector); return useMemo(() => { const savedViewsAsListItems: DataViewListItem[] = dataViews - .filter((dv) => dv.id !== DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID) + .filter((dv) => dv.id !== defaultDataViewId) .map((spec) => ({ id: spec.id ?? '', title: spec.title ?? '', @@ -24,5 +23,5 @@ export const useSavedDataViews = () => { })); return savedViewsAsListItems; - }, [dataViews]); + }, [dataViews, defaultDataViewId]); }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/data_view_selected.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/data_view_selected.ts index b9d4f96c9350e..4a208f37ddb3a 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/data_view_selected.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/data_view_selected.ts @@ -12,7 +12,7 @@ import type { RootState } from '../reducer'; import { scopes } from '../reducer'; import { selectDataViewAsync } from '../actions'; import { sharedDataViewManagerSlice } from '../slices'; -import { DataViewManagerScopeName } from '../../constants'; +import type { DataViewManagerScopeName } from '../../constants'; export const createDataViewSelectedListener = (dependencies: { scope: DataViewManagerScopeName; @@ -28,10 +28,6 @@ export const createDataViewSelectedListener = (dependencies: { return; } - if (dependencies.scope === DataViewManagerScopeName.timeline) { - console.log('createDataViewSelectedListener', action); - } - // Cancel effects running for the current scope to prevent race conditions listenerApi.cancelActiveListeners(); 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 9ac877581bf32..56218b9fcdece 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 @@ -65,6 +65,12 @@ export const createInitListener = (dependencies: { listenerApi.dispatch(sharedDataViewManagerSlice.actions.setDataViews(dataViewSpecs)); + // NOTE: save default data id for the given space in the store. + // this is used to identify the default selection in pickers across Kibana Space + listenerApi.dispatch( + sharedDataViewManagerSlice.actions.setDefaultDataViewId(defaultDataView.id) + ); + // Preload the default data view for all the scopes // Immediate calls that would dispatch this call from other places will cancel this action, // preventing race conditions diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/slices.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/slices.ts index 291a44297e938..431c173c610b7 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/slices.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/slices.ts @@ -23,6 +23,7 @@ export const initialSharedState: SharedDataViewSelectionState = { adhocDataViews: [], status: 'pristine', signalIndexName: '', + defaultDataViewId: null, }; export const sharedDataViewManagerSlice = createSlice({ @@ -36,6 +37,9 @@ export const sharedDataViewManagerSlice = createSlice({ setSignalIndexName: (state, action: PayloadAction) => { state.signalIndexName = action.payload; }, + setDefaultDataViewId: (state, action: PayloadAction) => { + state.defaultDataViewId = action.payload; + }, addDataView: (state, action: PayloadAction) => { const dataViewSpec = action.payload.toSpec(); diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/types.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/types.ts index 283b86f83364a..6fdc4f4ceb0de 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/types.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/types.ts @@ -24,6 +24,7 @@ export interface SharedDataViewSelectionState { adhocDataViews: DataViewSpec[]; status: 'pristine' | 'loading' | 'error' | 'ready'; signalIndexName: string; + defaultDataViewId: string | null; } export { type DataViewSpec }; From 04145f58de54e94dfd2fe623f0890f703da85eec Mon Sep 17 00:00:00 2001 From: lgestc Date: Tue, 3 Jun 2025 15:03:16 +0200 Subject: [PATCH 15/28] add docs --- .../redux/listeners/data_view_selected.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/data_view_selected.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/data_view_selected.ts index 4a208f37ddb3a..e965a98f37a6c 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/data_view_selected.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/data_view_selected.ts @@ -14,6 +14,22 @@ import { selectDataViewAsync } from '../actions'; import { sharedDataViewManagerSlice } from '../slices'; import type { DataViewManagerScopeName } from '../../constants'; +/** + * Creates a Redux listener for handling data view selection logic in the data view manager. + * + * This listener responds to the `selectDataViewAsync` action for a specific scope. It attempts to resolve + * the selected data view by: + * 1. Checking for a cached data view (either ad-hoc or persisted) in the Redux state. + * 2. If not found, attempting to fetch a lazy data view by ID from the DataViews service. + * 3. If still not found, creating a new ad-hoc data view using fallback patterns. + * + * The listener ensures that only one effect runs per scope at a time to prevent race conditions. + * If a data view is successfully resolved, it dispatches an action to set it as selected for the current scope. + * If an error occurs during fetching or creation, it dispatches an error action for the current scope. + * + * @param dependencies - The dependencies required for the listener, including the scope and DataViews service. + * @returns An object with the action creator and effect for Redux middleware. + */ export const createDataViewSelectedListener = (dependencies: { scope: DataViewManagerScopeName; dataViews: DataViewsServicePublic; From 134fdebf0fbac55da1ff3419f51074689caf67a0 Mon Sep 17 00:00:00 2001 From: lgestc Date: Tue, 3 Jun 2025 17:03:36 +0200 Subject: [PATCH 16/28] fix alerts filters --- .../detection_engine_filters.tsx | 45 +++++++++++-------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/detections/components/detection_engine_filters/detection_engine_filters.tsx b/x-pack/solutions/security/plugins/security_solution/public/detections/components/detection_engine_filters/detection_engine_filters.tsx index bfa2f41d54de1..9d2dd174a73b1 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detections/components/detection_engine_filters/detection_engine_filters.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detections/components/detection_engine_filters/detection_engine_filters.tsx @@ -57,18 +57,32 @@ export const DetectionEngineFilters = ({ [urlStorage] ); - const dataViewSpec = useMemo( - () => - indexPattern - ? { - id: SECURITY_ALERT_DATA_VIEW.id, - name: SECURITY_ALERT_DATA_VIEW.name, - allowNoIndex: true, - title: indexPattern.title, - timeFieldName: '@timestamp', - } - : null, - [indexPattern] + const dataViewSpec = useMemo(() => { + // NOTE: index pattern should have a title or an id to be considered valid + // it is possible that the empty id or title witll be set for compatibility reasons. + // to be removed after the cleanup work in scope of https://github.com/elastic/security-team/issues/11959 + // is done. + const isIndexPatternValid = indexPattern && (indexPattern.title || indexPattern.id); + + return isIndexPatternValid + ? { + id: SECURITY_ALERT_DATA_VIEW.id, + name: SECURITY_ALERT_DATA_VIEW.name, + allowNoIndex: true, + title: indexPattern.title, + timeFieldName: '@timestamp', + } + : null; + }, [indexPattern]); + + const services = useMemo( + () => ({ + http, + notifications, + dataViews, + storage: Storage, + }), + [dataViews, http, notifications] ); if (!spaceId || !dataViewSpec) { @@ -85,12 +99,7 @@ export const DetectionEngineFilters = ({ chainingSystem="HIERARCHICAL" defaultControls={DEFAULT_DETECTION_PAGE_FILTERS} dataViewSpec={dataViewSpec} - services={{ - http, - notifications, - dataViews, - storage: Storage, - }} + services={services} ControlGroupRenderer={ControlGroupRenderer} maxControls={4} {...props} From ab6edb278260fc34df49e6c186e3afbe5b0176ba Mon Sep 17 00:00:00 2001 From: lgestc Date: Tue, 3 Jun 2025 17:30:22 +0200 Subject: [PATCH 17/28] fix broken test --- .../hooks/use_managed_data_views.test.ts | 10 ++++++++-- .../hooks/use_saved_data_views.test.ts | 15 ++++++++++++--- .../redux/listeners/init_listener.test.ts | 17 ++++++++++++++++- 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_managed_data_views.test.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_managed_data_views.test.ts index 814eed5bbd4b5..54220d36f84e0 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_managed_data_views.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_managed_data_views.test.ts @@ -54,7 +54,10 @@ describe('useManagedDataViews', () => { ]; // Mock the Redux selector - (useSelector as jest.Mock).mockReturnValue({ dataViews: mockDataViews }); + (useSelector as jest.Mock).mockReturnValue({ + dataViews: mockDataViews, + defaultDataViewId: DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID, + }); // Render the hook const { result } = renderHook(() => useManagedDataViews()); @@ -85,7 +88,10 @@ describe('useManagedDataViews', () => { { id: 'some-id', title: 'Some Data View' }, { id: 'another-id', title: 'Another Data View' }, ]; - (useSelector as jest.Mock).mockReturnValue({ dataViews: mockDataViews }); + (useSelector as jest.Mock).mockReturnValue({ + dataViews: mockDataViews, + defaultDataViewId: DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID, + }); const { result } = renderHook(() => useManagedDataViews()); diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_saved_data_views.test.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_saved_data_views.test.ts index a2a0914bb9fae..e67efd6963ba9 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_saved_data_views.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_saved_data_views.test.ts @@ -40,7 +40,10 @@ describe('useSavedDataViews', () => { ]; // Mock the useSelector to return our test data - (useSelector as jest.Mock).mockReturnValue({ dataViews: mockDataViews }); + (useSelector as jest.Mock).mockReturnValue({ + dataViews: mockDataViews, + defaultDataViewId: DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID, + }); // Render the hook const { result } = renderHook(() => useSavedDataViews()); @@ -68,7 +71,10 @@ describe('useSavedDataViews', () => { it('should handle empty data views array', () => { // Mock the useSelector to return an empty array - (useSelector as jest.Mock).mockReturnValue({ dataViews: [] }); + (useSelector as jest.Mock).mockReturnValue({ + dataViews: [], + defaultDataViewId: DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID, + }); // Render the hook const { result } = renderHook(() => useSavedDataViews()); @@ -93,7 +99,10 @@ describe('useSavedDataViews', () => { ]; // Mock the useSelector - (useSelector as jest.Mock).mockReturnValue({ dataViews: mockDataViews }); + (useSelector as jest.Mock).mockReturnValue({ + dataViews: mockDataViews, + defaultDataViewId: DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID, + }); // Render the hook const { result } = renderHook(() => useSavedDataViews()); diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/init_listener.test.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/init_listener.test.ts index fc7d5b86d65bc..03c4c6e95f297 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/init_listener.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/init_listener.test.ts @@ -37,7 +37,16 @@ const uiSettings = {} as unknown as CoreStart['uiSettings']; const spaces = {} as unknown as SpacesPluginStart; const mockDispatch = jest.fn(); -const mockGetState = jest.fn(() => mockDataViewManagerState); +const mockGetState = jest.fn(() => { + const state = structuredClone(mockDataViewManagerState); + + state.dataViewManager.default.dataViewId = null; + state.dataViewManager.detections = structuredClone(state.dataViewManager.default); + state.dataViewManager.timeline = structuredClone(state.dataViewManager.default); + state.dataViewManager.analyzer = structuredClone(state.dataViewManager.default); + + return state; +}); const mockListenerApi = { dispatch: mockDispatch, @@ -73,6 +82,12 @@ describe('createInitListener', () => { expect(jest.mocked(mockListenerApi.dispatch)).toBeCalledWith( sharedDataViewManagerSlice.actions.setDataViews([]) ); + expect(jest.mocked(mockListenerApi.dispatch)).toBeCalledWith( + sharedDataViewManagerSlice.actions.setDefaultDataViewId( + DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID + ) + ); + expect(jest.mocked(mockListenerApi.dispatch)).toBeCalledWith( selectDataViewAsync({ id: DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID, From 194e129f42981cef1983d1a5a973ad9e69b08c6d Mon Sep 17 00:00:00 2001 From: lgestc Date: Mon, 16 Jun 2025 09:46:40 +0200 Subject: [PATCH 18/28] fix type error --- .../redux/listeners/data_view_selected.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/data_view_selected.test.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/data_view_selected.test.ts index a68c45dc3679b..53958170f8f9b 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/data_view_selected.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/data_view_selected.test.ts @@ -10,7 +10,7 @@ import { selectDataViewAsync } from '../actions'; import type { DataViewsServicePublic, FieldSpec } from '@kbn/data-views-plugin/public'; import type { AnyAction, Dispatch, ListenerEffectAPI } from '@reduxjs/toolkit'; import type { RootState } from '../reducer'; -import { DataViewManagerScopeName } from '../../constants'; +import { DataViewManagerScopeName, DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID } from '../../constants'; const mockDataViewsService = { getDataViewLazy: jest.fn(), @@ -66,6 +66,7 @@ const mockedState: RootState = { ], status: 'pristine', signalIndexName: '', + defaultDataViewId: DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID, }, }, }; From 97a0c9b558bd4451f98c3890ba212bbf41f39aa7 Mon Sep 17 00:00:00 2001 From: lgestc Date: Mon, 16 Jun 2025 11:57:46 +0200 Subject: [PATCH 19/28] redo signal index population in data view manager --- .../hooks/use_init_data_view_manager.ts | 17 +++++++++++---- .../data_view_manager/redux/selectors.ts | 9 +++++++- .../public/data_view_manager/redux/slices.ts | 12 +++++++---- .../public/data_view_manager/redux/types.ts | 7 ++++++- .../utils/create_default_data_view.ts | 8 +++---- .../alerts/use_signal_index.tsx | 21 +++++++++++++++---- 6 files changed, 56 insertions(+), 18 deletions(-) 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 36e27d58cae57..ccc1e847a9446 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 @@ -18,9 +18,9 @@ import { createDataViewSelectedListener } from '../redux/listeners/data_view_sel import { createInitListener } from '../redux/listeners/init_listener'; import { useEnableExperimental } from '../../common/hooks/use_experimental_features'; import { sharedDataViewManagerSlice } from '../redux/slices'; -import { useUserInfo } from '../../detections/components/user_info'; import { type SelectDataViewAsyncPayload } from '../redux/actions'; import { DataViewManagerScopeName } from '../constants'; +import { useSignalIndex } from '../../detections/containers/detection_engine/alerts/use_signal_index'; type OriginalListener = Parameters[0]; @@ -43,13 +43,22 @@ export const useInitDataViewManager = () => { const services = useKibana().services; const { newDataViewPickerEnabled } = useEnableExperimental(); - const { loading: loadingSignalIndex, signalIndexName } = useUserInfo(); + const { + signalIndexName, + loading: loadingSignalIndex, + signalIndexMappingOutdated, + } = useSignalIndex(); const onSignalIndexUpdated = useCallback(() => { if (!loadingSignalIndex && signalIndexName != null) { - dispatch(sharedDataViewManagerSlice.actions.setSignalIndexName(signalIndexName)); + dispatch( + sharedDataViewManagerSlice.actions.setSignalIndex({ + name: signalIndexName, + isOutdated: !!signalIndexMappingOutdated, + }) + ); } - }, [dispatch, loadingSignalIndex, signalIndexName]); + }, [dispatch, loadingSignalIndex, signalIndexMappingOutdated, signalIndexName]); useEffect(() => { // TODO: (new data view picker) remove this in cleanup phase https://github.com/elastic/security-team/issues/12665 diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/selectors.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/selectors.ts index 6989fede4af73..17de68e5ce174 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/selectors.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/selectors.ts @@ -24,7 +24,14 @@ export const sharedStateSelector = createSelector( (dataViewManager) => dataViewManager.shared ); +// NOTE: This will be subject to cleanup tasks https://github.com/elastic/security-team/issues/11959 export const signalIndexNameSelector = createSelector( [(state: RootState) => state.dataViewManager], - (dataViewManager) => dataViewManager.shared.signalIndexName + (dataViewManager) => dataViewManager.shared.signalIndex?.name ?? '' +); + +// NOTE: This will be subject to cleanup tasks https://github.com/elastic/security-team/issues/11959 +export const signalIndexOutdatedSelector = createSelector( + [(state: RootState) => state.dataViewManager], + (dataViewManager) => !!dataViewManager.shared.signalIndex?.isOutdated ); diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/slices.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/slices.ts index 431c173c610b7..e786487ed4358 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/slices.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/slices.ts @@ -10,7 +10,11 @@ import { createSlice } from '@reduxjs/toolkit'; import type { DataViewSpec, DataView } from '@kbn/data-views-plugin/common'; import type { DataViewManagerScopeName } from '../constants'; import { SLICE_PREFIX } from '../constants'; -import type { ScopedDataViewSelectionState, SharedDataViewSelectionState } from './types'; +import type { + ScopedDataViewSelectionState, + SharedDataViewSelectionState, + SignalIndexMetadata, +} from './types'; import { selectDataViewAsync, type SelectDataViewAsyncPayload } from './actions'; export const initialScopeState: ScopedDataViewSelectionState = { @@ -22,7 +26,7 @@ export const initialSharedState: SharedDataViewSelectionState = { dataViews: [], adhocDataViews: [], status: 'pristine', - signalIndexName: '', + signalIndex: null, defaultDataViewId: null, }; @@ -34,8 +38,8 @@ export const sharedDataViewManagerSlice = createSlice({ state.dataViews = action.payload; state.status = 'ready'; }, - setSignalIndexName: (state, action: PayloadAction) => { - state.signalIndexName = action.payload; + setSignalIndex: (state, action: PayloadAction) => { + state.signalIndex = action.payload; }, setDefaultDataViewId: (state, action: PayloadAction) => { state.defaultDataViewId = action.payload; diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/types.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/types.ts index 6fdc4f4ceb0de..107dddd37c220 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/types.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/types.ts @@ -23,8 +23,13 @@ export interface SharedDataViewSelectionState { dataViews: DataViewSpec[]; adhocDataViews: DataViewSpec[]; status: 'pristine' | 'loading' | 'error' | 'ready'; - signalIndexName: string; defaultDataViewId: string | null; + signalIndex: SignalIndexMetadata | null; +} + +export interface SignalIndexMetadata { + name: string; + isOutdated: boolean; } export { type DataViewSpec }; 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 d58c83bcf92f2..ddfa091bf53bb 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 @@ -61,7 +61,7 @@ export const createDefaultDataView = async ({ } // check for/generate default Security Solution Kibana data view - const sourcererDataViews = await createSourcererDataView({ + const sourcererDataView = await createSourcererDataView({ body: { patternList: [...configPatternList, ...(signal.name != null ? [signal.name] : [])], }, @@ -69,11 +69,11 @@ export const createDefaultDataView = async ({ dataViewId: `${DEFAULT_DATA_VIEW_ID}-${(await spaces?.getActiveSpace())?.id}`, }); - if (sourcererDataViews === undefined) { + if (sourcererDataView === undefined) { throw new Error(''); } - defaultDataView = { ...initDataView, ...sourcererDataViews.defaultDataView }; - kibanaDataViews = sourcererDataViews.kibanaDataViews.map((dataView: KibanaDataView) => ({ + defaultDataView = { ...initDataView, ...sourcererDataView.defaultDataView }; + kibanaDataViews = sourcererDataView.kibanaDataViews.map((dataView: KibanaDataView) => ({ ...initDataView, ...dataView, })); diff --git a/x-pack/solutions/security/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.tsx b/x-pack/solutions/security/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.tsx index 8e72b2390b9ec..ed7544f14d9c1 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.tsx @@ -9,6 +9,9 @@ import { useEffect, useState } from 'react'; import { isSecurityAppError } from '@kbn/securitysolution-t-grid'; import { useSelector } from 'react-redux'; +import { signalIndexOutdatedSelector } from '../../../../data_view_manager/redux/selectors'; +import { useSignalIndexName } from '../../../../data_view_manager/hooks/use_signal_index_name'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { createSignalIndex, getSignalIndex } from './api'; import * as i18n from './translations'; @@ -28,8 +31,6 @@ export interface ReturnSignalIndex { /** * Hook for managing signal index - * - * */ export const useSignalIndex = (): ReturnSignalIndex => { const [loading, setLoading] = useState(true); @@ -42,13 +43,25 @@ export const useSignalIndex = (): ReturnSignalIndex => { const { addError } = useAppToasts(); const { hasIndexRead } = useAlertsPrivileges(); - const signalIndexMappingOutdated = useSelector((state: State) => { + const newDataViewPickerEnabled = useIsExperimentalFeatureEnabled('newDataViewPickerEnabled'); + + const oldSignalIndexMappingOutdated = useSelector((state: State) => { return sourcererSelectors.signalIndexMappingOutdated(state); }); + const experimentalSignalIndexMappingOutdated = useSelector(signalIndexOutdatedSelector); + + const signalIndexMappingOutdated = newDataViewPickerEnabled + ? experimentalSignalIndexMappingOutdated + : oldSignalIndexMappingOutdated; - const signalIndexName = useSelector((state: State) => { + const oldSignalIndexName = useSelector((state: State) => { return sourcererSelectors.signalIndexName(state); }); + const experimentalSignalIndexName = useSignalIndexName(); + + const signalIndexName = newDataViewPickerEnabled + ? experimentalSignalIndexName + : oldSignalIndexName; useEffect(() => { let isSubscribed = true; From 7abb46b5804699478bbf85b7a99eb11009a2ed57 Mon Sep 17 00:00:00 2001 From: lgestc Date: Mon, 16 Jun 2025 13:43:49 +0200 Subject: [PATCH 20/28] type error --- .../redux/listeners/data_view_selected.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/data_view_selected.test.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/data_view_selected.test.ts index 53958170f8f9b..f1904f36ffdd2 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/data_view_selected.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/data_view_selected.test.ts @@ -65,7 +65,7 @@ const mockedState: RootState = { }, ], status: 'pristine', - signalIndexName: '', + signalIndex: { name: '', isOutdated: false }, defaultDataViewId: DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID, }, }, From 35fb7ab9a1bde852846f5ef1208467d017086efb Mon Sep 17 00:00:00 2001 From: lgestc Date: Tue, 17 Jun 2025 09:53:33 +0200 Subject: [PATCH 21/28] fixed failing test --- .../hooks/use_init_data_view_manager.test.ts | 2 +- .../data_view_manager/hooks/use_init_data_view_manager.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_init_data_view_manager.test.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_init_data_view_manager.test.ts index c924783f3b0e5..c9eb2dd9c822a 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_init_data_view_manager.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_init_data_view_manager.test.ts @@ -12,7 +12,7 @@ import { useDispatch } from 'react-redux'; import { sharedDataViewManagerSlice } from '../redux/slices'; jest.mock('../../common/hooks/use_experimental_features', () => ({ - useEnableExperimental: () => ({ newDataViewPickerEnabled: true }), + useIsExperimentalFeatureEnabled: () => true, })); jest.mock('react-redux', () => { 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 ccc1e847a9446..ff2dd74cf01f3 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 @@ -16,11 +16,11 @@ import type { RootState } from '../redux/reducer'; import { useKibana } from '../../common/lib/kibana'; import { createDataViewSelectedListener } from '../redux/listeners/data_view_selected'; import { createInitListener } from '../redux/listeners/init_listener'; -import { useEnableExperimental } from '../../common/hooks/use_experimental_features'; import { sharedDataViewManagerSlice } from '../redux/slices'; import { type SelectDataViewAsyncPayload } from '../redux/actions'; import { DataViewManagerScopeName } from '../constants'; import { useSignalIndex } from '../../detections/containers/detection_engine/alerts/use_signal_index'; +import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; type OriginalListener = Parameters[0]; @@ -41,7 +41,7 @@ const removeListener = (listener: Listener) => export const useInitDataViewManager = () => { const dispatch = useDispatch(); const services = useKibana().services; - const { newDataViewPickerEnabled } = useEnableExperimental(); + const newDataViewPickerEnabled = useIsExperimentalFeatureEnabled('newDataViewPickerEnabled'); const { signalIndexName, From 0a6cecab47c1087ae809d4f49ec6d46a1ead6d04 Mon Sep 17 00:00:00 2001 From: lgestc Date: Tue, 17 Jun 2025 10:36:53 +0200 Subject: [PATCH 22/28] fix tests --- .../detection_engine_filters/detection_engine_filters.tsx | 2 +- .../public/detections/pages/alerts/detection_engine.test.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/detections/components/detection_engine_filters/detection_engine_filters.tsx b/x-pack/solutions/security/plugins/security_solution/public/detections/components/detection_engine_filters/detection_engine_filters.tsx index 9d2dd174a73b1..57837db80eced 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detections/components/detection_engine_filters/detection_engine_filters.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detections/components/detection_engine_filters/detection_engine_filters.tsx @@ -59,7 +59,7 @@ export const DetectionEngineFilters = ({ const dataViewSpec = useMemo(() => { // NOTE: index pattern should have a title or an id to be considered valid - // it is possible that the empty id or title witll be set for compatibility reasons. + // it is possible that the empty id or title are set temporarily during page load (compatibility reasons as we support adhoc data views now). // to be removed after the cleanup work in scope of https://github.com/elastic/security-team/issues/11959 // is done. const isIndexPatternValid = indexPattern && (indexPattern.title || indexPattern.id); diff --git a/x-pack/solutions/security/plugins/security_solution/public/detections/pages/alerts/detection_engine.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/detections/pages/alerts/detection_engine.test.tsx index 79ea0b2974b43..f5b67bb90e8f5 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detections/pages/alerts/detection_engine.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detections/pages/alerts/detection_engine.test.tsx @@ -199,7 +199,7 @@ describe('DetectionEnginePageComponent', () => { browserFields: mockBrowserFields, sourcererDataView: { fields: {}, - title: '', + title: 'mock-*', }, }); jest From 7ddcecc41251a6c2035867fac890b77e92758e8f Mon Sep 17 00:00:00 2001 From: lgestc Date: Wed, 18 Jun 2025 12:21:50 +0200 Subject: [PATCH 23/28] restored use user info --- .../plugins/security_solution/public/app/home/index.tsx | 2 +- .../data_view_manager/hooks/use_init_data_view_manager.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/app/home/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/app/home/index.tsx index a1d7ec1a5af5a..aeacf735c829b 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/app/home/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/app/home/index.tsx @@ -41,7 +41,7 @@ const HomePageComponent: React.FC = ({ children }) => { const { pathname } = useLocation(); const sourcererScope = getScopeFromPath(pathname); - const { browserFields: oldBrowserFields } = useInitSourcerer(sourcererScope); + // const { browserFields: oldBrowserFields } = useInitSourcerer(sourcererScope); const { browserFields: experimentalBrowserFields } = useBrowserFields(sourcererScope); useRestoreDataViewManagerStateFromURL(useInitDataViewManager(), sourcererScope); 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 ff2dd74cf01f3..8caa8238c8263 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 @@ -19,8 +19,8 @@ import { createInitListener } from '../redux/listeners/init_listener'; import { sharedDataViewManagerSlice } from '../redux/slices'; import { type SelectDataViewAsyncPayload } from '../redux/actions'; import { DataViewManagerScopeName } from '../constants'; -import { useSignalIndex } from '../../detections/containers/detection_engine/alerts/use_signal_index'; import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; +import { useUserInfo } from '../../detections/components/user_info'; type OriginalListener = Parameters[0]; @@ -44,10 +44,10 @@ export const useInitDataViewManager = () => { const newDataViewPickerEnabled = useIsExperimentalFeatureEnabled('newDataViewPickerEnabled'); const { - signalIndexName, loading: loadingSignalIndex, + signalIndexName, signalIndexMappingOutdated, - } = useSignalIndex(); + } = useUserInfo(); const onSignalIndexUpdated = useCallback(() => { if (!loadingSignalIndex && signalIndexName != null) { From f0832b71452f652a665cfd03826ddafbea15e428 Mon Sep 17 00:00:00 2001 From: lgestc Date: Wed, 18 Jun 2025 12:24:21 +0200 Subject: [PATCH 24/28] re-enable use init sourcerer --- .../plugins/security_solution/public/app/home/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/app/home/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/app/home/index.tsx index aeacf735c829b..a1d7ec1a5af5a 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/app/home/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/app/home/index.tsx @@ -41,7 +41,7 @@ const HomePageComponent: React.FC = ({ children }) => { const { pathname } = useLocation(); const sourcererScope = getScopeFromPath(pathname); - // const { browserFields: oldBrowserFields } = useInitSourcerer(sourcererScope); + const { browserFields: oldBrowserFields } = useInitSourcerer(sourcererScope); const { browserFields: experimentalBrowserFields } = useBrowserFields(sourcererScope); useRestoreDataViewManagerStateFromURL(useInitDataViewManager(), sourcererScope); From 6ca3ca6b25d8c7881a41f6c9138a833bb700ca9e Mon Sep 17 00:00:00 2001 From: lgestc Date: Wed, 18 Jun 2025 12:33:13 +0200 Subject: [PATCH 25/28] skip sourcerer init entirely if the new picker is enabled --- .../sourcerer/containers/use_init_sourcerer.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_init_sourcerer.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_init_sourcerer.tsx index 5903d8a140d6f..6117794852704 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_init_sourcerer.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_init_sourcerer.tsx @@ -22,10 +22,22 @@ import type { State } from '../../common/store/types'; import { useKibana } from '../../common/lib/kibana'; import { useSourcererDataView } from '.'; import { useSyncSourcererUrlState } from '../../data_view_manager/hooks/use_sync_url_state'; +import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; + +const defaultInitResult = { browserFields: {} }; export const useInitSourcerer = ( scopeId: SourcererScopeName.default | SourcererScopeName.detections = SourcererScopeName.default ) => { + const newDataViewPickerEnabled = useIsExperimentalFeatureEnabled('newDataViewPickerEnabled'); + + /* eslint-disable react-hooks/rules-of-hooks */ + // NOTE: skipping the entire hook on purpose when the new picker is enabled + // will be removed as part of the cleanup in https://github.com/elastic/security-team/issues/11959 + if (newDataViewPickerEnabled) { + return defaultInitResult; + } + const dispatch = useDispatch(); const { data: { dataViews }, From b6f8814b064091645e20d9f93b8a12bca5bdd7fd Mon Sep 17 00:00:00 2001 From: lgestc Date: Wed, 18 Jun 2025 12:47:23 +0200 Subject: [PATCH 26/28] remove todo --- .../public/data_view_manager/redux/listeners/init_listener.ts | 2 -- 1 file changed, 2 deletions(-) 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 56218b9fcdece..b0d61b66c82a8 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 @@ -49,8 +49,6 @@ export const createInitListener = (dependencies: { // Initialize default security data view first // Note: this is subject to change, as we might want to add specific data view just for alerts - // TODO: store this in shared reducer and replace all the uses of constant data view id (DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID) - // Issue: https://github.com/elastic/security-team/issues/12667 const { defaultDataView } = await createDefaultDataView({ dataViewService: dependencies.dataViews, uiSettings: dependencies.uiSettings, From 0a34eeb9064ed34e2ce8611965e2230a91d33fb9 Mon Sep 17 00:00:00 2001 From: lgestc Date: Wed, 18 Jun 2025 12:52:38 +0200 Subject: [PATCH 27/28] use data view id from manager in poll index --- .../sourcerer/containers/use_signal_helpers.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_signal_helpers.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_signal_helpers.tsx index d19a1deb84823..83f6b07c967cf 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_signal_helpers.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_signal_helpers.tsx @@ -25,7 +25,15 @@ export const useSignalHelpers = (): { /* when false, signal index has been initiated */ signalIndexNeedsInit: boolean; } => { - const { indicesExist, dataViewId } = useSourcererDataView(SourcererScopeName.detections); + const newDataViewPickerEnabled = useIsExperimentalFeatureEnabled('newDataViewPickerEnabled'); + + const { indicesExist, dataViewId: oldDataViewId } = useSourcererDataView( + SourcererScopeName.detections + ); + const { dataView: detectionsDataView } = useDataView(SourcererScopeName.detections); + + const dataViewId = newDataViewPickerEnabled ? oldDataViewId : detectionsDataView?.id ?? null; + const { indexFieldsSearch } = useOldDataView(); const dispatch = useDispatch(); const { addError } = useAppToasts(); @@ -36,8 +44,6 @@ export const useSignalHelpers = (): { const signalIndexNameSourcerer = useSelector(sourcererSelectors.signalIndexName); - const newDataViewPickerEnabled = useIsExperimentalFeatureEnabled('newDataViewPickerEnabled'); - const experimentalSignalIndexName = useSignalIndexName(); const oldDefaultDataView = useSelector(sourcererSelectors.defaultDataView); From 08cdfde53e09b0bd3f35a674822a90607974788d Mon Sep 17 00:00:00 2001 From: Luke Gmys <11671118+lgestc@users.noreply.github.com> Date: Thu, 19 Jun 2025 09:32:31 +0200 Subject: [PATCH 28/28] Update x-pack/solutions/security/plugins/security_solution/public/data_view_manager/redux/listeners/init_listener.ts Co-authored-by: Michael Olorunnisola --- .../public/data_view_manager/redux/listeners/init_listener.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b0d61b66c82a8..ef72b842b00d2 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 @@ -63,7 +63,7 @@ export const createInitListener = (dependencies: { listenerApi.dispatch(sharedDataViewManagerSlice.actions.setDataViews(dataViewSpecs)); - // NOTE: save default data id for the given space in the store. + // NOTE: save default dataview id for the given space in the store. // this is used to identify the default selection in pickers across Kibana Space listenerApi.dispatch( sharedDataViewManagerSlice.actions.setDefaultDataViewId(defaultDataView.id)