Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
88c4be6
--wip-- [skip ci]
lgestc May 23, 2025
12582f8
--wip-- [skip ci]
lgestc May 23, 2025
a9147a1
--wip-- [skip ci]
lgestc May 23, 2025
95c4093
--wip-- [skip ci]
lgestc May 26, 2025
bd866c0
--wip-- [skip ci]
lgestc May 26, 2025
2f1f5d0
--wip-- [skip ci]
lgestc May 26, 2025
3e032ac
move signal init to manager hook
lgestc May 26, 2025
2a572eb
add missing index selector
lgestc May 27, 2025
0519fea
naming
lgestc May 27, 2025
37ff8fc
naming
lgestc May 27, 2025
03155b1
add tests for create default data view
lgestc May 27, 2025
17a478c
fix broken test
lgestc May 27, 2025
29a7b5d
Merge branch 'main' into default_data_view_init
lgestc May 27, 2025
5487e61
Merge branch 'main' into default_data_view_init
lgestc May 29, 2025
40f17f4
Merge branch 'main' into default_data_view_init
lgestc Jun 2, 2025
d260e4b
Merge branch 'main' into default_data_view_init
lgestc Jun 3, 2025
670ff03
improvements
lgestc Jun 3, 2025
ebf054a
remove hardcoded data view
lgestc Jun 3, 2025
04145f5
add docs
lgestc Jun 3, 2025
134fdeb
fix alerts filters
lgestc Jun 3, 2025
ab6edb2
fix broken test
lgestc Jun 3, 2025
e7a8cdc
Merge branch 'main' into default_data_view_init
lgestc Jun 3, 2025
0fb0d03
Merge branch 'main' into default_data_view_init
lgestc Jun 16, 2025
194e129
fix type error
lgestc Jun 16, 2025
97a0c9b
redo signal index population in data view manager
lgestc Jun 16, 2025
7abb46b
type error
lgestc Jun 16, 2025
35fb7ab
fixed failing test
lgestc Jun 17, 2025
0a6ceca
fix tests
lgestc Jun 17, 2025
7ddcecc
restored use user info
lgestc Jun 18, 2025
f0832b7
re-enable use init sourcerer
lgestc Jun 18, 2025
6ca3ca6
skip sourcerer init entirely if the new picker is enabled
lgestc Jun 18, 2025
b6f8814
remove todo
lgestc Jun 18, 2025
0a34eeb
use data view id from manager in poll index
lgestc Jun 18, 2025
3571299
Merge branch 'main' into default_data_view_init
lgestc Jun 18, 2025
08cdfde
Update x-pack/solutions/security/plugins/security_solution/public/dat…
lgestc Jun 19, 2025
fdbdee6
Merge branch 'main' into default_data_view_init
lgestc Jun 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -39,23 +34,17 @@ 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';
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 { createDefaultDataView } from '../../data_view_manager/utils/create_default_data_view';

let store: Store<State, Action> | null = null;

Expand All @@ -66,46 +55,15 @@ export const createStoreFactory = async (
storage: Storage,
enableExperimental: ExperimentalFeatures
): Promise<Store<State, Action>> => {
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 createDefaultDataView({
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: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -57,6 +57,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<SourcererUrlState>(URL_PARAM_KEY.sourcerer);

Expand Down Expand Up @@ -145,7 +154,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,
};
Expand All @@ -154,22 +163,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 (
<div data-test-subj={DATA_VIEW_PICKER_TEST_ID}>
<UnifiedDataViewPicker
isDisabled={status !== 'ready' || disabled}
currentDataViewId={dataViewId || DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID}
currentDataViewId={dataViewId || (defaultDataViewId ?? undefined)}
trigger={triggerConfig}
onChangeDataView={handleChangeDataView}
onEditDataView={handleDataViewModified}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +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 { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features';
import { useUserInfo } from '../../detections/components/user_info';

type OriginalListener = Parameters<typeof originalAddListener>[0];

Expand All @@ -40,15 +41,51 @@ const removeListener = <T extends AnyAction>(listener: Listener<T>) =>
export const useInitDataViewManager = () => {
const dispatch = useDispatch();
const services = useKibana().services;
const { newDataViewPickerEnabled } = useEnableExperimental();
const newDataViewPickerEnabled = useIsExperimentalFeatureEnabled('newDataViewPickerEnabled');

const {
loading: loadingSignalIndex,
signalIndexName,
signalIndexMappingOutdated,
} = useUserInfo();

const onSignalIndexUpdated = useCallback(() => {
if (!loadingSignalIndex && signalIndexName != null) {
dispatch(
sharedDataViewManagerSlice.actions.setSignalIndex({
name: signalIndexName,
isOutdated: !!signalIndexMappingOutdated,
})
);
}
}, [dispatch, loadingSignalIndex, signalIndexMappingOutdated, 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) {
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,
});

dispatch(addListener(dataViewsLoadingListener));
Expand Down Expand Up @@ -78,7 +115,15 @@ export const useInitDataViewManager = () => {
dispatch(removeListener(dataViewSelectedListener));
});
};
}, [dispatch, newDataViewPickerEnabled, services.dataViews]);
}, [
dispatch,
newDataViewPickerEnabled,
services.application,
services.dataViews,
services.http,
services.spaces,
services.uiSettings,
]);

return useCallback(
(initialSelection: SelectDataViewAsyncPayload[]) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -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());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,18 @@ import { useSelector } from 'react-redux';
import { useMemo } from 'react';
import { useKibana } from '../../common/lib/kibana';
import { sharedStateSelector } from '../redux/selectors';
import { DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID } from '../constants';

export const useManagedDataViews = () => {
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]);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -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());
Expand All @@ -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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,19 @@ 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 ?? '',
name: spec.name,
}));

return savedViewsAsListItems;
}, [dataViews]);
}, [dataViews, defaultDataViewId]);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* 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 { useSelector } from 'react-redux';
import { signalIndexNameSelector } from '../redux/selectors';

export const useSignalIndexName = () => useSelector(signalIndexNameSelector);
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -65,6 +65,8 @@ const mockedState: RootState = {
},
],
status: 'pristine',
signalIndex: { name: '', isOutdated: false },
defaultDataViewId: DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID,
},
},
};
Expand Down
Loading