From f53d18aa19215af67eb65b9747b6cf8e1ae450d2 Mon Sep 17 00:00:00 2001 From: Michael Olorunnisola Date: Mon, 9 Dec 2024 20:21:00 -0500 Subject: [PATCH 01/32] create saved dataview on sourcerer update --- .../components/alerts_sourcerer.test.tsx | 4 +- .../sourcerer/components/index.test.tsx | 4 +- .../public/sourcerer/components/index.tsx | 23 +++++------ .../public/sourcerer/components/misc.test.tsx | 4 +- .../components/sourcerer_integration.test.tsx | 4 +- .../components/timeline_sourcerer.test.tsx | 4 +- .../components/use_update_data_view.test.tsx | 8 ++-- .../components/use_update_data_view.tsx | 40 +++++++++++-------- .../public/sourcerer/store/helpers.ts | 1 + 9 files changed, 47 insertions(+), 45 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/alerts_sourcerer.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/alerts_sourcerer.test.tsx index 619f7e91eae82..912eb0812c04f 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/alerts_sourcerer.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/alerts_sourcerer.test.tsx @@ -18,9 +18,9 @@ const mockDispatch = jest.fn(); jest.mock('../containers'); jest.mock('../containers/use_signal_helpers'); -const mockUseUpdateDataView = jest.fn().mockReturnValue(() => true); +const mockuseCreateAdhocDataView = jest.fn().mockReturnValue(() => true); jest.mock('./use_update_data_view', () => ({ - useUpdateDataView: () => mockUseUpdateDataView, + useCreateAdhocDataView: () => mockuseCreateAdhocDataView, })); jest.mock('react-redux', () => { const original = jest.requireActual('react-redux'); diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.test.tsx index 1f39bc02c68c4..fa2546c9008ee 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.test.tsx @@ -24,9 +24,9 @@ const mockDispatch = jest.fn(); jest.mock('../containers'); jest.mock('../containers/use_signal_helpers'); -const mockUseUpdateDataView = jest.fn().mockReturnValue(() => true); +const mockuseCreateAdhocDataView = jest.fn().mockReturnValue(() => true); jest.mock('./use_update_data_view', () => ({ - useUpdateDataView: () => mockUseUpdateDataView, + useCreateAdhocDataView: () => mockuseCreateAdhocDataView, })); jest.mock('react-redux', () => { const original = jest.requireActual('react-redux'); diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx index ad5a939b69995..8517986ab3f39 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx @@ -28,7 +28,7 @@ import { usePickIndexPatterns } from './use_pick_index_patterns'; import { FormRow, PopoverContent, StyledButtonEmpty, StyledFormRow } from './helpers'; import { TemporarySourcerer } from './temporary'; import { useSourcererDataView } from '../containers'; -import { useUpdateDataView } from './use_update_data_view'; +import { useCreateAdhocDataView } from './use_update_data_view'; import { Trigger } from './trigger'; import { AlertsCheckbox, SaveButtons, SourcererCallout } from './sub_components'; import { useSignalHelpers } from '../containers/use_signal_helpers'; @@ -318,29 +318,24 @@ export const Sourcerer = React.memo(({ scope: scopeId } resetDataSources(); }, [resetDataSources]); - const updateDataView = useUpdateDataView(onOpenAndReset); + const createAdHocDataView = useCreateAdhocDataView(onOpenAndReset); const onUpdateDataView = useCallback(async () => { - const isUiSettingsSuccess = await updateDataView(missingPatterns); + const dataView = await createAdHocDataView(missingPatterns); setIsShowingUpdateModal(false); setPopoverIsOpen(false); - if (isUiSettingsSuccess) { + if (dataView && dataView.id) { + console.log("DISPATCHING DATAVIEW CREATION: ", dataView); + const patterns = dataView.getIndexPattern().split(','); dispatchChangeDataView( - defaultDataView.id, + dataView.id, // to be at this stage, activePatterns is defined, the ?? selectedPatterns is to make TS happy - activePatterns ?? selectedPatterns, + patterns, false ); setIsTriggerDisabled(true); } - }, [ - activePatterns, - defaultDataView.id, - missingPatterns, - dispatchChangeDataView, - selectedPatterns, - updateDataView, - ]); + }, [missingPatterns, dispatchChangeDataView, createAdHocDataView]); useEffect(() => { setDataViewId(selectedDataViewId); diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/misc.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/misc.test.tsx index 1897acca1c6dd..9988b6379f667 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/misc.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/misc.test.tsx @@ -25,9 +25,9 @@ const mockDispatch = jest.fn(); jest.mock('../containers'); jest.mock('../containers/use_signal_helpers'); -const mockUseUpdateDataView = jest.fn().mockReturnValue(() => true); +const mockuseCreateAdhocDataView = jest.fn().mockReturnValue(() => true); jest.mock('./use_update_data_view', () => ({ - useUpdateDataView: () => mockUseUpdateDataView, + useCreateAdhocDataView: () => mockuseCreateAdhocDataView, })); jest.mock('react-redux', () => { const original = jest.requireActual('react-redux'); diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/sourcerer_integration.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/sourcerer_integration.test.tsx index 5f21a814da363..1b0fae0a332ac 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/sourcerer_integration.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/sourcerer_integration.test.tsx @@ -21,9 +21,9 @@ const mockDispatch = jest.fn(); jest.mock('../containers'); jest.mock('../containers/use_signal_helpers'); -const mockUseUpdateDataView = jest.fn().mockReturnValue(() => true); +const mockuseCreateAdhocDataView = jest.fn().mockReturnValue(() => true); jest.mock('./use_update_data_view', () => ({ - useUpdateDataView: () => mockUseUpdateDataView, + useCreateAdhocDataView: () => mockuseCreateAdhocDataView, })); jest.mock('react-redux', () => { const original = jest.requireActual('react-redux'); diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/timeline_sourcerer.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/timeline_sourcerer.test.tsx index 35a26856f4930..6eb8f531da5bd 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/timeline_sourcerer.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/timeline_sourcerer.test.tsx @@ -19,9 +19,9 @@ const mockDispatch = jest.fn(); jest.mock('../containers'); jest.mock('../containers/use_signal_helpers'); -const mockUseUpdateDataView = jest.fn().mockReturnValue(() => true); +const mockuseCreateAdhocDataView = jest.fn().mockReturnValue(() => true); jest.mock('./use_update_data_view', () => ({ - useUpdateDataView: () => mockUseUpdateDataView, + useCreateAdhocDataView: () => mockuseCreateAdhocDataView, })); jest.mock('react-redux', () => { const original = jest.requireActual('react-redux'); diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.test.tsx index bb4d935dbdc99..865fa598e23a9 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.test.tsx @@ -6,7 +6,7 @@ */ import { renderHook } from '@testing-library/react'; -import { useUpdateDataView } from './use_update_data_view'; +import { useCreateAdhocDataView } from './use_update_data_view'; import { useKibana as mockUseKibana } from '../../common/lib/kibana/__mocks__'; import * as i18n from './translations'; const mockAddSuccess = jest.fn(); @@ -47,7 +47,7 @@ describe('use_update_data_view', () => { }); test('Successful uiSettings updates with correct index pattern, and shows success toast', async () => { - const { result } = renderHook(() => useUpdateDataView(mockError)); + const { result } = renderHook(() => useCreateAdhocDataView(mockError)); const updateDataView = result.current; const isUiSettingsSuccess = await updateDataView(['missing-*']); expect(mockedUseKibana.services.uiSettings.set.mock.calls[0][1]).toEqual( @@ -59,7 +59,7 @@ describe('use_update_data_view', () => { test('Failed uiSettings update returns false and shows error toast', async () => { mockedUseKibana.services.uiSettings.set.mockImplementation(() => false); - const { result } = renderHook(() => useUpdateDataView(mockError)); + const { result } = renderHook(() => useCreateAdhocDataView(mockError)); const updateDataView = result.current; const isUiSettingsSuccess = await updateDataView(['missing-*']); expect(mockedUseKibana.services.uiSettings.set.mock.calls[0][1]).toEqual( @@ -74,7 +74,7 @@ describe('use_update_data_view', () => { mockedUseKibana.services.uiSettings.get.mockImplementation(() => { throw new Error('Uh oh bad times over here'); }); - const { result } = renderHook(() => useUpdateDataView(mockError)); + const { result } = renderHook(() => useCreateAdhocDataView(mockError)); const updateDataView = result.current; const isUiSettingsSuccess = await updateDataView(['missing-*']); expect(isUiSettingsSuccess).toEqual(false); diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.tsx index a18d37d7c10f5..f37ccfe487add 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.tsx @@ -9,6 +9,7 @@ import React, { useCallback } from 'react'; import { EuiLink } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { toMountPoint } from '@kbn/react-kibana-mount'; +import type { DataView } from '@kbn/data-views-plugin/common'; import { useKibana } from '../../common/lib/kibana'; import { DEFAULT_INDEX_KEY } from '../../../common/constants'; import * as i18n from './translations'; @@ -16,28 +17,33 @@ import { RefreshButton } from './refresh_button'; import { useAppToasts } from '../../common/hooks/use_app_toasts'; import { ensurePatternFormat } from '../../../common/utils/sourcerer'; -export const useUpdateDataView = ( +export const useCreateAdhocDataView = ( onOpenAndReset: () => void -): ((missingPatterns: string[]) => Promise) => { - const { uiSettings, ...startServices } = useKibana().services; +): ((missingPatterns: string[]) => Promise) => { + const { dataViews, uiSettings, ...startServices } = useKibana().services; const { addSuccess, addError } = useAppToasts(); return useCallback( - async (missingPatterns: string[]): Promise => { - const asyncSearch = async (): Promise<[boolean, Error | null]> => { + async (missingPatterns: string[]): Promise => { + const asyncSearch = async (): Promise<[DataView | null, Error | null]> => { try { const defaultPatterns = uiSettings.get(DEFAULT_INDEX_KEY); - const uiSettingsIndexPattern = [...defaultPatterns, ...missingPatterns]; - const isSuccess = await uiSettings.set( - DEFAULT_INDEX_KEY, - ensurePatternFormat(uiSettingsIndexPattern) - ); - return [isSuccess, null]; + const combinedPatterns = [...defaultPatterns, ...missingPatterns]; + const validatedPatterns = ensurePatternFormat(combinedPatterns); + const patternsString = validatedPatterns.join(','); + const adHocDataView = await dataViews.createAndSave({ + id: patternsString, + title: patternsString, + }); + if (adHocDataView.fields.getByName('@timestamp')?.type === 'date') { + adHocDataView.timeFieldName = '@timestamp'; + } + return [adHocDataView, null]; } catch (e) { - return [false, e]; + return [null, e]; } }; - const [isUiSettingsSuccess, possibleError] = await asyncSearch(); - if (isUiSettingsSuccess) { + const [dataView, possibleError] = await asyncSearch(); + if (dataView) { addSuccess({ color: 'success', title: toMountPoint(i18n.SUCCESS_TOAST_TITLE, startServices), @@ -45,7 +51,7 @@ export const useUpdateDataView = ( iconType: undefined, toastLifeTimeMs: 600000, }); - return true; + return dataView; } addError(possibleError !== null ? possibleError : new Error(i18n.FAILURE_TOAST_TITLE), { title: i18n.FAILURE_TOAST_TITLE, @@ -65,8 +71,8 @@ export const useUpdateDataView = ( ) as unknown as string, }); - return false; + return null; }, - [addError, addSuccess, onOpenAndReset, uiSettings, startServices] + [addError, onOpenAndReset, uiSettings, dataViews, addSuccess, startServices] ); }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/store/helpers.ts b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/store/helpers.ts index 2fccdbdd23025..8b51de411ff01 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/store/helpers.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/store/helpers.ts @@ -62,6 +62,7 @@ export const validateSelectedPatterns = ( const dedupeAllDefaultPatterns = ensurePatternFormat( (dataView ?? state.defaultDataView).title.split(',') ); + // TODO: If we having missing patterns here, just create a new dataView...don't worry about missingPatterns anymore... missingPatterns = dedupePatterns.filter( (pattern) => !dedupeAllDefaultPatterns.includes(pattern) ); From 0ba913252927b6e4fb4913154496b09939b6da8a Mon Sep 17 00:00:00 2001 From: lgestc Date: Tue, 28 Jan 2025 14:19:13 +0100 Subject: [PATCH 02/32] wip --- .../public/sourcerer/components/temporary.tsx | 6 +++--- .../public/sourcerer/components/translations.ts | 2 +- .../components/update_default_data_view_modal.tsx | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/temporary.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/temporary.tsx index 66899affeb4aa..a804f41711f2b 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/temporary.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/temporary.tsx @@ -19,7 +19,7 @@ import { import React, { useMemo } from 'react'; import * as i18n from './translations'; import { Blockquote, ResetButton } from './helpers'; -import { UpdateDefaultDataViewModal } from './update_default_data_view_modal'; +import { CreateAdHocDataViewModal } from './update_default_data_view_modal'; import { TimelineId } from '../../../common/types'; import { TimelineTypeEnum } from '../../../common/api/timeline'; import { timelineSelectors } from '../../timelines/store'; @@ -217,12 +217,12 @@ export const TemporarySourcerer = React.memo( onUpdate={onUpdateStepOne} selectedPatterns={selectedPatterns} /> - ) diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/translations.ts b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/translations.ts index 51698a9fa7547..2629bad08cfb8 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/translations.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/translations.ts @@ -99,7 +99,7 @@ export const ALERTS_BADGE_TITLE = i18n.translate( export const DEPRECATED_BADGE_TITLE = i18n.translate( 'xpack.securitySolution.indexPatterns.updateAvailableBadgeTitle', { - defaultMessage: 'Update available', + defaultMessage: 'Action required', } ); diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/update_default_data_view_modal.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/update_default_data_view_modal.tsx index c010a4687c8a3..78fa5d0709128 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/update_default_data_view_modal.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/update_default_data_view_modal.tsx @@ -27,7 +27,7 @@ interface Props { missingPatterns: string[]; onDismissModal: () => void; onContinue: () => void; - onUpdate: () => void; + onAddDataView: () => void; } const MyEuiModal = styled(EuiModal)` .euiModal__flex { @@ -39,8 +39,8 @@ const MyEuiModal = styled(EuiModal)` } `; -export const UpdateDefaultDataViewModal = React.memo( - ({ isShowing, onDismissModal, onContinue, onUpdate, missingPatterns }) => +export const CreateAdHocDataViewModal = React.memo( + ({ isShowing, onDismissModal, onContinue, onAddDataView: onUpdate, missingPatterns }) => isShowing ? ( @@ -90,4 +90,4 @@ export const UpdateDefaultDataViewModal = React.memo( ) : null ); -UpdateDefaultDataViewModal.displayName = 'UpdateDefaultDataViewModal'; +CreateAdHocDataViewModal.displayName = 'UpdateDefaultDataViewModal'; From 5740773dea87605e4b1178ff0dee33742165dc69 Mon Sep 17 00:00:00 2001 From: lgestc Date: Tue, 28 Jan 2025 16:29:33 +0100 Subject: [PATCH 03/32] wip --- .../public/sourcerer/components/use_update_data_view.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.tsx index f37ccfe487add..d315080688ec4 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.tsx @@ -31,7 +31,9 @@ export const useCreateAdhocDataView = ( const validatedPatterns = ensurePatternFormat(combinedPatterns); const patternsString = validatedPatterns.join(','); const adHocDataView = await dataViews.createAndSave({ - id: patternsString, + id: `adhoc_sourcerer_${Date.now()}`, + // NOTE: setting name here - it will not render duplicate warning this way. + name: `adhoc_sourcerer_${Date.now()}`, title: patternsString, }); if (adHocDataView.fields.getByName('@timestamp')?.type === 'date') { From a0178b3cb8cbdd8cf429498f7adf4b6b67b86efe Mon Sep 17 00:00:00 2001 From: lgestc Date: Wed, 29 Jan 2025 11:59:48 +0100 Subject: [PATCH 04/32] --wip-- [skip ci] --- .../public/sourcerer/components/index.tsx | 195 ++++++--------- .../public/sourcerer/components/temporary.tsx | 231 ------------------ .../public/sourcerer/components/trigger.tsx | 10 - .../components/use_update_data_view.tsx | 108 ++++---- 4 files changed, 134 insertions(+), 410 deletions(-) delete mode 100644 x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/temporary.tsx diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx index 8517986ab3f39..a4b8a8694cbd9 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx @@ -14,7 +14,7 @@ import { EuiSuperSelect, } from '@elastic/eui'; import type { ChangeEventHandler } from 'react'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import * as i18n from './translations'; @@ -26,7 +26,6 @@ import type { ModifiedTypes } from './use_pick_index_patterns'; import { SourcererScopeName } from '../store/model'; import { usePickIndexPatterns } from './use_pick_index_patterns'; import { FormRow, PopoverContent, StyledButtonEmpty, StyledFormRow } from './helpers'; -import { TemporarySourcerer } from './temporary'; import { useSourcererDataView } from '../containers'; import { useCreateAdhocDataView } from './use_update_data_view'; import { Trigger } from './trigger'; @@ -129,6 +128,8 @@ export const Sourcerer = React.memo(({ scope: scopeId } const isDefaultSourcerer = scopeId === SourcererScopeName.default; const updateUrlParam = useUpdateUrlParam(URL_PARAM_KEY.sourcerer); + const autoupdateRef = useRef(false); + const signalIndexName = useSelector(sourcererSelectors.signalIndexName); const defaultDataView = useSelector(sourcererSelectors.defaultDataView); const kibanaDataViews = useSelector(sourcererSelectors.kibanaDataViews); @@ -221,7 +222,6 @@ export const Sourcerer = React.memo(({ scope: scopeId } ); const [expandAdvancedOptions, setExpandAdvancedOptions] = useState(false); - const [isShowingUpdateModal, setIsShowingUpdateModal] = useState(false); const setPopoverIsOpenCb = useCallback(() => { setPopoverIsOpen((prevState) => !prevState); @@ -291,51 +291,34 @@ export const Sourcerer = React.memo(({ scope: scopeId } sourcererMissingPatterns, ]); - // deprecated timeline index pattern handlers - const onContinueUpdateDeprecated = useCallback(() => { - setIsShowingUpdateModal(false); - const patterns = selectedPatterns.filter((pattern) => - defaultDataView.patternList.includes(pattern) - ); - dispatchChangeDataView(defaultDataView.id, patterns); - setPopoverIsOpen(false); - }, [defaultDataView.id, defaultDataView.patternList, dispatchChangeDataView, selectedPatterns]); - - const onUpdateDeprecated = useCallback(() => { - // are all the patterns in the default? - if (missingPatterns.length === 0) { - onContinueUpdateDeprecated(); - } else { - // open modal - setIsShowingUpdateModal(true); - } - }, [missingPatterns, onContinueUpdateDeprecated]); - - const [isTriggerDisabled, setIsTriggerDisabled] = useState(false); - const onOpenAndReset = useCallback(() => { setPopoverIsOpen(true); resetDataSources(); }, [resetDataSources]); - const createAdHocDataView = useCreateAdhocDataView(onOpenAndReset); - const onUpdateDataView = useCallback(async () => { - const dataView = await createAdHocDataView(missingPatterns); - setIsShowingUpdateModal(false); + const { createAdhocDataView, isLoading: isAdhocDataviewLoading } = + useCreateAdhocDataView(onOpenAndReset); + + const createAdhocDataViewForCompatibility = useCallback(async () => { + const adhocDataView = await createAdhocDataView(missingPatterns); setPopoverIsOpen(false); - if (dataView && dataView.id) { - console.log("DISPATCHING DATAVIEW CREATION: ", dataView); - const patterns = dataView.getIndexPattern().split(','); - dispatchChangeDataView( - dataView.id, - // to be at this stage, activePatterns is defined, the ?? selectedPatterns is to make TS happy - patterns, - false - ); - setIsTriggerDisabled(true); + if (adhocDataView && adhocDataView.id) { + const patterns = adhocDataView.getIndexPattern().split(','); + dispatchChangeDataView(adhocDataView.id, patterns, false); + autoupdateRef.current = false; } - }, [missingPatterns, dispatchChangeDataView, createAdHocDataView]); + }, [createAdhocDataView, missingPatterns, dispatchChangeDataView]); + + useEffect(() => { + if (isAdhocDataviewLoading) { + return; + } + + if ((dataViewId === null && isModified === 'deprecated') || isModified === 'missingPatterns') { + createAdhocDataViewForCompatibility(); + } + }, [dataViewId, isModified, createAdhocDataViewForCompatibility, isAdhocDataviewLoading]); useEffect(() => { setDataViewId(selectedDataViewId); @@ -355,7 +338,7 @@ export const Sourcerer = React.memo(({ scope: scopeId } (({ scope: scopeId } title={isTimelineSourcerer ? i18n.CALL_OUT_TIMELINE_TITLE : i18n.CALL_OUT_TITLE} /> - {(dataViewId === null && isModified === 'deprecated') || - isModified === 'missingPatterns' ? ( - setIsShowingUpdateModal(false)} - onReset={resetDataSources} - onUpdateStepOne={isModified === 'deprecated' ? onUpdateDeprecated : onUpdateDataView} - onUpdateStepTwo={onUpdateDataView} - selectedPatterns={selectedPatterns} - /> - ) : ( - - <> - - {dataViewId && ( - - - - )} - - - - {i18n.INDEX_PATTERNS_ADVANCED_OPTIONS_TITLE} - - {expandAdvancedOptions && } - - + <> + + {dataViewId && ( + + - - - + )} + + + + {i18n.INDEX_PATTERNS_ADVANCED_OPTIONS_TITLE} + + {expandAdvancedOptions && } + + - - - - )} + + + + + + ); diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/temporary.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/temporary.tsx deleted file mode 100644 index a804f41711f2b..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/temporary.tsx +++ /dev/null @@ -1,231 +0,0 @@ -/* - * 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 { FormattedMessage } from '@kbn/i18n-react'; -import { - EuiCallOut, - EuiText, - EuiTextColor, - EuiSpacer, - EuiFlexGroup, - EuiFlexItem, - EuiButton, - EuiToolTip, -} from '@elastic/eui'; -import React, { useMemo } from 'react'; -import * as i18n from './translations'; -import { Blockquote, ResetButton } from './helpers'; -import { CreateAdHocDataViewModal } from './update_default_data_view_modal'; -import { TimelineId } from '../../../common/types'; -import { TimelineTypeEnum } from '../../../common/api/timeline'; -import { timelineSelectors } from '../../timelines/store'; -import { useDeepEqualSelector } from '../../common/hooks/use_selector'; -import { timelineDefaults } from '../../timelines/store/defaults'; -import { - BadCurrentPatternsMessage, - CurrentPatternsMessage, - DeprecatedMessage, - MissingPatternsMessage, -} from './utils'; - -interface Props { - activePatterns?: string[]; - indicesExist: boolean; - isModified: 'deprecated' | 'missingPatterns'; - missingPatterns: string[]; - onDismiss: () => void; - onReset: () => void; - onUpdate: () => void; - selectedPatterns: string[]; -} - -const translations = { - deprecated: { - title: { - [TimelineTypeEnum.default]: i18n.CALL_OUT_DEPRECATED_TITLE, - [TimelineTypeEnum.template]: i18n.CALL_OUT_DEPRECATED_TEMPLATE_TITLE, - }, - update: i18n.UPDATE_INDEX_PATTERNS, - }, - missingPatterns: { - title: { - [TimelineTypeEnum.default]: i18n.CALL_OUT_MISSING_PATTERNS_TITLE, - [TimelineTypeEnum.template]: i18n.CALL_OUT_MISSING_PATTERNS_TEMPLATE_TITLE, - }, - update: i18n.ADD_INDEX_PATTERN, - }, -}; - -export const TemporarySourcererComp = React.memo( - ({ - activePatterns, - indicesExist, - isModified, - onDismiss, - onReset, - onUpdate, - selectedPatterns, - missingPatterns, - }) => { - const trigger = useMemo( - () => ( - - {translations[isModified].update} - - ), - [indicesExist, isModified, onUpdate] - ); - const buttonWithTooltip = useMemo( - () => - !indicesExist ? ( - - {trigger} - - ) : ( - trigger - ), - [indicesExist, trigger] - ); - - const deadPatterns = - activePatterns && activePatterns.length > 0 - ? selectedPatterns.filter((p) => !activePatterns.includes(p)) - : []; - const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); - - const timelineType = useDeepEqualSelector( - (state) => (getTimeline(state, TimelineId.active) ?? timelineDefaults).timelineType - ); - - return ( - <> - - - - -

- {activePatterns && activePatterns.length > 0 ? ( - - ) : ( - - )} - - {isModified === 'deprecated' && ( - - )} - {isModified === 'missingPatterns' && ( - <> - - {missingPatterns.join(', ')}, - }} - /> - - - - )} -

-
-
- - - - {i18n.INDEX_PATTERNS_CLOSE} - - - {buttonWithTooltip} - - - ); - } -); - -TemporarySourcererComp.displayName = 'TemporarySourcererComp'; - -interface TemporarySourcererProps { - activePatterns?: string[]; - indicesExist: boolean; - isModified: 'deprecated' | 'missingPatterns'; - isShowingUpdateModal: boolean; - missingPatterns: string[]; - onContinueWithoutUpdate: () => void; - onDismiss: () => void; - onDismissModal: () => void; - onReset: () => void; - onUpdateStepOne: () => void; - onUpdateStepTwo: () => void; - selectedPatterns: string[]; -} - -export const TemporarySourcerer = React.memo( - ({ - activePatterns, - indicesExist, - isModified, - missingPatterns, - onContinueWithoutUpdate, - onDismiss, - onReset, - onUpdateStepOne, - onUpdateStepTwo, - selectedPatterns, - isShowingUpdateModal, - onDismissModal, - }) => ( - <> - - - - ) -); - -TemporarySourcerer.displayName = 'TemporarySourcerer'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/trigger.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/trigger.tsx index 5dc7ab8522189..19d74cf775fa4 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/trigger.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/trigger.tsx @@ -51,17 +51,7 @@ export const TriggerComponent: FC = ({ ); case 'deprecated': - return ( - - {i18n.DEPRECATED_BADGE_TITLE} - - ); case 'missingPatterns': - return ( - - {i18n.DEPRECATED_BADGE_TITLE} - - ); case '': default: return null; diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.tsx index d315080688ec4..e146fb8f975d9 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.tsx @@ -5,76 +5,76 @@ * 2.0. */ -import React, { useCallback } from 'react'; +import React, { useCallback, useState } from 'react'; import { EuiLink } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { toMountPoint } from '@kbn/react-kibana-mount'; import type { DataView } from '@kbn/data-views-plugin/common'; import { useKibana } from '../../common/lib/kibana'; import { DEFAULT_INDEX_KEY } from '../../../common/constants'; import * as i18n from './translations'; -import { RefreshButton } from './refresh_button'; import { useAppToasts } from '../../common/hooks/use_app_toasts'; import { ensurePatternFormat } from '../../../common/utils/sourcerer'; +export interface UseCreateAdhocDataViewReturnValue { + isLoading: boolean; + createAdhocDataView: (missingPatterns: string[]) => Promise; +} + export const useCreateAdhocDataView = ( onOpenAndReset: () => void -): ((missingPatterns: string[]) => Promise) => { - const { dataViews, uiSettings, ...startServices } = useKibana().services; - const { addSuccess, addError } = useAppToasts(); - return useCallback( +): UseCreateAdhocDataViewReturnValue => { + const { dataViews, uiSettings } = useKibana().services; + const { addError } = useAppToasts(); + + const [isLoading, setIsLoading] = useState(false); + + const createAdhocDataView = useCallback( async (missingPatterns: string[]): Promise => { - const asyncSearch = async (): Promise<[DataView | null, Error | null]> => { - try { - const defaultPatterns = uiSettings.get(DEFAULT_INDEX_KEY); - const combinedPatterns = [...defaultPatterns, ...missingPatterns]; - const validatedPatterns = ensurePatternFormat(combinedPatterns); - const patternsString = validatedPatterns.join(','); - const adHocDataView = await dataViews.createAndSave({ - id: `adhoc_sourcerer_${Date.now()}`, - // NOTE: setting name here - it will not render duplicate warning this way. - name: `adhoc_sourcerer_${Date.now()}`, - title: patternsString, - }); - if (adHocDataView.fields.getByName('@timestamp')?.type === 'date') { - adHocDataView.timeFieldName = '@timestamp'; - } - return [adHocDataView, null]; - } catch (e) { - return [null, e]; + setIsLoading(true); + + const asyncSearch = async (): Promise => { + const defaultPatterns = uiSettings.get(DEFAULT_INDEX_KEY); + const combinedPatterns = [...defaultPatterns, ...missingPatterns]; + const validatedPatterns = ensurePatternFormat(combinedPatterns); + const patternsString = validatedPatterns.join(','); + const adHocDataView = await dataViews.create({ + id: `adhoc_sourcerer_${Date.now()}`, + title: patternsString, + }); + + if (adHocDataView.fields.getByName('@timestamp')?.type === 'date') { + adHocDataView.timeFieldName = '@timestamp'; } + + return adHocDataView; }; - const [dataView, possibleError] = await asyncSearch(); - if (dataView) { - addSuccess({ - color: 'success', - title: toMountPoint(i18n.SUCCESS_TOAST_TITLE, startServices), - text: toMountPoint(, startServices), - iconType: undefined, - toastLifeTimeMs: 600000, + try { + return await asyncSearch(); + } catch (possibleError) { + addError(possibleError !== null ? possibleError : new Error(i18n.FAILURE_TOAST_TITLE), { + title: i18n.FAILURE_TOAST_TITLE, + toastMessage: ( + <> + + {i18n.TOGGLE_TO_NEW_SOURCERER} + + ), + }} + /> + + ) as unknown as string, }); - return dataView; + + return null; } - addError(possibleError !== null ? possibleError : new Error(i18n.FAILURE_TOAST_TITLE), { - title: i18n.FAILURE_TOAST_TITLE, - toastMessage: ( - <> - - {i18n.TOGGLE_TO_NEW_SOURCERER} - - ), - }} - /> - - ) as unknown as string, - }); - return null; }, - [addError, onOpenAndReset, uiSettings, dataViews, addSuccess, startServices] + [addError, onOpenAndReset, uiSettings, dataViews] ); + + return { createAdhocDataView, isLoading }; }; From 01c2796219eecae386010d36afea0df68eb4198d Mon Sep 17 00:00:00 2001 From: lgestc Date: Wed, 29 Jan 2025 12:07:07 +0100 Subject: [PATCH 05/32] --wip-- [skip ci] --- .../security_solution/public/sourcerer/components/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx index a4b8a8694cbd9..f6b19c32a2440 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx @@ -301,7 +301,6 @@ export const Sourcerer = React.memo(({ scope: scopeId } const createAdhocDataViewForCompatibility = useCallback(async () => { const adhocDataView = await createAdhocDataView(missingPatterns); - setPopoverIsOpen(false); if (adhocDataView && adhocDataView.id) { const patterns = adhocDataView.getIndexPattern().split(','); From cf436e678764003ef46b18d23bc53c320106bc47 Mon Sep 17 00:00:00 2001 From: lgestc Date: Wed, 29 Jan 2025 12:08:19 +0100 Subject: [PATCH 06/32] --wip-- [skip ci] --- .../security_solution/public/sourcerer/components/index.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx index f6b19c32a2440..becb449ee92a7 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx @@ -14,7 +14,7 @@ import { EuiSuperSelect, } from '@elastic/eui'; import type { ChangeEventHandler } from 'react'; -import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import * as i18n from './translations'; @@ -128,8 +128,6 @@ export const Sourcerer = React.memo(({ scope: scopeId } const isDefaultSourcerer = scopeId === SourcererScopeName.default; const updateUrlParam = useUpdateUrlParam(URL_PARAM_KEY.sourcerer); - const autoupdateRef = useRef(false); - const signalIndexName = useSelector(sourcererSelectors.signalIndexName); const defaultDataView = useSelector(sourcererSelectors.defaultDataView); const kibanaDataViews = useSelector(sourcererSelectors.kibanaDataViews); From 98c1ef3161636ffecc8a96854842c546b8da5328 Mon Sep 17 00:00:00 2001 From: lgestc Date: Wed, 29 Jan 2025 12:12:46 +0100 Subject: [PATCH 07/32] --wip-- [skip ci] --- .../update_default_data_view_modal.tsx | 93 ------------------- 1 file changed, 93 deletions(-) delete mode 100644 x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/update_default_data_view_modal.tsx diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/update_default_data_view_modal.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/update_default_data_view_modal.tsx deleted file mode 100644 index 78fa5d0709128..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/update_default_data_view_modal.tsx +++ /dev/null @@ -1,93 +0,0 @@ -/* - * 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 { FormattedMessage } from '@kbn/i18n-react'; -import { - EuiButton, - EuiFlexGroup, - EuiFlexItem, - EuiModal, - EuiModalBody, - EuiModalHeader, - EuiModalHeaderTitle, - EuiText, - EuiTextColor, -} from '@elastic/eui'; -import React from 'react'; -import styled from 'styled-components'; -import * as i18n from './translations'; -import { Blockquote, ResetButton } from './helpers'; - -interface Props { - isShowing: boolean; - missingPatterns: string[]; - onDismissModal: () => void; - onContinue: () => void; - onAddDataView: () => void; -} -const MyEuiModal = styled(EuiModal)` - .euiModal__flex { - width: 60vw; - } - .euiCodeBlock { - height: auto !important; - max-width: 718px; - } -`; - -export const CreateAdHocDataViewModal = React.memo( - ({ isShowing, onDismissModal, onContinue, onAddDataView: onUpdate, missingPatterns }) => - isShowing ? ( - - - {i18n.UPDATE_SECURITY_DATA_VIEW} - - - - -

- {missingPatterns.join(', ')}, - }} - /> - {i18n.UPDATE_DATA_VIEW} -

-
-
- - - - {i18n.CONTINUE_WITHOUT_ADDING} - - - - - {i18n.ADD_INDEX_PATTERN} - - - -
-
- ) : null -); - -CreateAdHocDataViewModal.displayName = 'UpdateDefaultDataViewModal'; From b9b1f534e33b1d3087d7cab5fe0f2a106dcf34cb Mon Sep 17 00:00:00 2001 From: lgestc Date: Wed, 29 Jan 2025 12:41:14 +0100 Subject: [PATCH 08/32] --wip-- [skip ci] --- .../security_solution/public/sourcerer/components/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx index becb449ee92a7..4907aa2aaa1f7 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx @@ -303,7 +303,6 @@ export const Sourcerer = React.memo(({ scope: scopeId } if (adhocDataView && adhocDataView.id) { const patterns = adhocDataView.getIndexPattern().split(','); dispatchChangeDataView(adhocDataView.id, patterns, false); - autoupdateRef.current = false; } }, [createAdhocDataView, missingPatterns, dispatchChangeDataView]); From 4a5fdc7f950f90fcba9e2bffbd5c06b148e22c9f Mon Sep 17 00:00:00 2001 From: lgestc Date: Wed, 29 Jan 2025 16:44:38 +0100 Subject: [PATCH 09/32] --wip-- [skip ci] --- .../public/sourcerer/components/index.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx index 4907aa2aaa1f7..c5bd4e4cb26de 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx @@ -14,7 +14,7 @@ import { EuiSuperSelect, } from '@elastic/eui'; import type { ChangeEventHandler } from 'react'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import * as i18n from './translations'; @@ -127,6 +127,7 @@ export const Sourcerer = React.memo(({ scope: scopeId } const isTimelineSourcerer = scopeId === SourcererScopeName.timeline; const isDefaultSourcerer = scopeId === SourcererScopeName.default; const updateUrlParam = useUpdateUrlParam(URL_PARAM_KEY.sourcerer); + const adhocDataViewRef = useRef(false); const signalIndexName = useSelector(sourcererSelectors.signalIndexName); const defaultDataView = useSelector(sourcererSelectors.defaultDataView); @@ -306,12 +307,14 @@ export const Sourcerer = React.memo(({ scope: scopeId } } }, [createAdhocDataView, missingPatterns, dispatchChangeDataView]); + // FIXME: ensure the view is created only once useEffect(() => { - if (isAdhocDataviewLoading) { - return; - } - if ((dataViewId === null && isModified === 'deprecated') || isModified === 'missingPatterns') { + if (adhocDataViewRef.current) { + return; + } + + adhocDataViewRef.current = true; createAdhocDataViewForCompatibility(); } }, [dataViewId, isModified, createAdhocDataViewForCompatibility, isAdhocDataviewLoading]); From 9d5adcacbc08045926f59bb9a7ca28cc369e1ff6 Mon Sep 17 00:00:00 2001 From: lgestc Date: Wed, 29 Jan 2025 17:02:46 +0100 Subject: [PATCH 10/32] --wip-- [skip ci] --- .../security_solution/public/sourcerer/components/index.tsx | 1 + .../public/sourcerer/components/use_update_data_view.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx index c5bd4e4cb26de..07d3d5917676d 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx @@ -303,6 +303,7 @@ export const Sourcerer = React.memo(({ scope: scopeId } if (adhocDataView && adhocDataView.id) { const patterns = adhocDataView.getIndexPattern().split(','); + console.info('[adhoc] adhoc dataview created', adhocDataView); dispatchChangeDataView(adhocDataView.id, patterns, false); } }, [createAdhocDataView, missingPatterns, dispatchChangeDataView]); diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.tsx index e146fb8f975d9..1c103922ba7b8 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.tsx @@ -38,7 +38,7 @@ export const useCreateAdhocDataView = ( const validatedPatterns = ensurePatternFormat(combinedPatterns); const patternsString = validatedPatterns.join(','); const adHocDataView = await dataViews.create({ - id: `adhoc_sourcerer_${Date.now()}`, + id: `adhoc_sourcerer_${patternsString}`, title: patternsString, }); From 21151e796a861e9577d7aa6d55e4298bedc833ea Mon Sep 17 00:00:00 2001 From: lgestc Date: Wed, 29 Jan 2025 21:05:43 +0100 Subject: [PATCH 11/32] --wip-- [skip ci] --- .../public/sourcerer/components/index.tsx | 48 +++++++++++-------- .../components/use_update_data_view.tsx | 9 +--- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx index 07d3d5917676d..5eac1fe4253b4 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx @@ -14,7 +14,7 @@ import { EuiSuperSelect, } from '@elastic/eui'; import type { ChangeEventHandler } from 'react'; -import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import * as i18n from './translations'; @@ -127,7 +127,6 @@ export const Sourcerer = React.memo(({ scope: scopeId } const isTimelineSourcerer = scopeId === SourcererScopeName.timeline; const isDefaultSourcerer = scopeId === SourcererScopeName.default; const updateUrlParam = useUpdateUrlParam(URL_PARAM_KEY.sourcerer); - const adhocDataViewRef = useRef(false); const signalIndexName = useSelector(sourcererSelectors.signalIndexName); const defaultDataView = useSelector(sourcererSelectors.defaultDataView); @@ -295,30 +294,37 @@ export const Sourcerer = React.memo(({ scope: scopeId } resetDataSources(); }, [resetDataSources]); - const { createAdhocDataView, isLoading: isAdhocDataviewLoading } = - useCreateAdhocDataView(onOpenAndReset); + const { createAdhocDataView } = useCreateAdhocDataView(onOpenAndReset); - const createAdhocDataViewForCompatibility = useCallback(async () => { - const adhocDataView = await createAdhocDataView(missingPatterns); + const missingPatternsString = useMemo(() => missingPatterns.join(), [missingPatterns]); - if (adhocDataView && adhocDataView.id) { - const patterns = adhocDataView.getIndexPattern().split(','); - console.info('[adhoc] adhoc dataview created', adhocDataView); - dispatchChangeDataView(adhocDataView.id, patterns, false); - } - }, [createAdhocDataView, missingPatterns, dispatchChangeDataView]); - - // FIXME: ensure the view is created only once useEffect(() => { - if ((dataViewId === null && isModified === 'deprecated') || isModified === 'missingPatterns') { - if (adhocDataViewRef.current) { - return; + let ignore = false; + + (async () => { + if ( + (dataViewId === null && isModified === 'deprecated') || + isModified === 'missingPatterns' + ) { + const adhocDataView = await createAdhocDataView(missingPatternsString.split(',')); + + if (ignore) { + return; + } + + if (adhocDataView && adhocDataView.id) { + const patterns = adhocDataView.getIndexPattern().split(','); + console.info('[adhoc] adhoc dataview created', missingPatternsString, adhocDataView); + dispatchChangeDataView(adhocDataView.id, patterns, false); + } } + })(); - adhocDataViewRef.current = true; - createAdhocDataViewForCompatibility(); - } - }, [dataViewId, isModified, createAdhocDataViewForCompatibility, isAdhocDataviewLoading]); + return () => { + console.log('[adhoc] ignore update', missingPatternsString); + ignore = true; + }; + }, [dataViewId, isModified, createAdhocDataView, missingPatternsString, dispatchChangeDataView]); useEffect(() => { setDataViewId(selectedDataViewId); diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.tsx index 1c103922ba7b8..4481b8f508f78 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useState } from 'react'; +import React, { useCallback } from 'react'; import { EuiLink } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import type { DataView } from '@kbn/data-views-plugin/common'; @@ -16,7 +16,6 @@ import { useAppToasts } from '../../common/hooks/use_app_toasts'; import { ensurePatternFormat } from '../../../common/utils/sourcerer'; export interface UseCreateAdhocDataViewReturnValue { - isLoading: boolean; createAdhocDataView: (missingPatterns: string[]) => Promise; } @@ -26,12 +25,8 @@ export const useCreateAdhocDataView = ( const { dataViews, uiSettings } = useKibana().services; const { addError } = useAppToasts(); - const [isLoading, setIsLoading] = useState(false); - const createAdhocDataView = useCallback( async (missingPatterns: string[]): Promise => { - setIsLoading(true); - const asyncSearch = async (): Promise => { const defaultPatterns = uiSettings.get(DEFAULT_INDEX_KEY); const combinedPatterns = [...defaultPatterns, ...missingPatterns]; @@ -76,5 +71,5 @@ export const useCreateAdhocDataView = ( [addError, onOpenAndReset, uiSettings, dataViews] ); - return { createAdhocDataView, isLoading }; + return { createAdhocDataView }; }; From 29eed10c29d516b626a410bc61b8a905a023a9c1 Mon Sep 17 00:00:00 2001 From: lgestc Date: Thu, 30 Jan 2025 13:32:08 +0100 Subject: [PATCH 12/32] --wip-- [skip ci] --- .../public/sourcerer/components/index.tsx | 26 +++++++++++++------ .../components/use_update_data_view.tsx | 4 +-- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx index 5eac1fe4253b4..d727a7f4a7d0c 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx @@ -296,9 +296,13 @@ export const Sourcerer = React.memo(({ scope: scopeId } const { createAdhocDataView } = useCreateAdhocDataView(onOpenAndReset); - const missingPatternsString = useMemo(() => missingPatterns.join(), [missingPatterns]); + // NOTE: this exists because missingPatterns array reference is swapped very often and we only care about + // the values here + const stableMissingPatternsString = useMemo(() => missingPatterns.join(), [missingPatterns]); useEffect(() => { + // NOTE: this lets us prevent setting the index too often, + // as there is no way to pass in abort signal into the data view creation apis let ignore = false; (async () => { @@ -306,25 +310,31 @@ export const Sourcerer = React.memo(({ scope: scopeId } (dataViewId === null && isModified === 'deprecated') || isModified === 'missingPatterns' ) { - const adhocDataView = await createAdhocDataView(missingPatternsString.split(',')); + const adhocDataView = await createAdhocDataView(stableMissingPatternsString.split(',')); if (ignore) { return; } - if (adhocDataView && adhocDataView.id) { - const patterns = adhocDataView.getIndexPattern().split(','); - console.info('[adhoc] adhoc dataview created', missingPatternsString, adhocDataView); - dispatchChangeDataView(adhocDataView.id, patterns, false); + if (!adhocDataView || !adhocDataView.id) { + return; } + + const patterns = adhocDataView.getIndexPattern().split(','); + dispatchChangeDataView(adhocDataView.id, patterns, false); } })(); return () => { - console.log('[adhoc] ignore update', missingPatternsString); ignore = true; }; - }, [dataViewId, isModified, createAdhocDataView, missingPatternsString, dispatchChangeDataView]); + }, [ + dataViewId, + isModified, + createAdhocDataView, + stableMissingPatternsString, + dispatchChangeDataView, + ]); useEffect(() => { setDataViewId(selectedDataViewId); diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.tsx index 4481b8f508f78..965fd59eeb98f 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.tsx @@ -27,7 +27,7 @@ export const useCreateAdhocDataView = ( const createAdhocDataView = useCallback( async (missingPatterns: string[]): Promise => { - const asyncSearch = async (): Promise => { + const createDataView = async (): Promise => { const defaultPatterns = uiSettings.get(DEFAULT_INDEX_KEY); const combinedPatterns = [...defaultPatterns, ...missingPatterns]; const validatedPatterns = ensurePatternFormat(combinedPatterns); @@ -44,7 +44,7 @@ export const useCreateAdhocDataView = ( return adHocDataView; }; try { - return await asyncSearch(); + return await createDataView(); } catch (possibleError) { addError(possibleError !== null ? possibleError : new Error(i18n.FAILURE_TOAST_TITLE), { title: i18n.FAILURE_TOAST_TITLE, From 53e81d036847af4cdcbd134765b9dc00c0d6d116 Mon Sep 17 00:00:00 2001 From: lgestc Date: Thu, 30 Jan 2025 14:05:01 +0100 Subject: [PATCH 13/32] --wip-- [skip ci] --- .../public/sourcerer/components/index.tsx | 56 +++---------- ...sx => use_create_adhoc_data_view.test.tsx} | 2 +- ...iew.tsx => use_create_adhoc_data_view.tsx} | 6 +- .../components/use_data_view_fallback.ts | 82 +++++++++++++++++++ 4 files changed, 99 insertions(+), 47 deletions(-) rename x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/{use_update_data_view.test.tsx => use_create_adhoc_data_view.test.tsx} (97%) rename x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/{use_update_data_view.tsx => use_create_adhoc_data_view.tsx} (93%) create mode 100644 x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_data_view_fallback.ts diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx index d727a7f4a7d0c..dde07387753ee 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx @@ -27,12 +27,12 @@ import { SourcererScopeName } from '../store/model'; import { usePickIndexPatterns } from './use_pick_index_patterns'; import { FormRow, PopoverContent, StyledButtonEmpty, StyledFormRow } from './helpers'; import { useSourcererDataView } from '../containers'; -import { useCreateAdhocDataView } from './use_update_data_view'; import { Trigger } from './trigger'; import { AlertsCheckbox, SaveButtons, SourcererCallout } from './sub_components'; import { useSignalHelpers } from '../containers/use_signal_helpers'; import { useUpdateUrlParam } from '../../common/utils/global_query_string'; import { URL_PARAM_KEY } from '../../common/hooks/use_url_state'; +import { useDataViewFallback } from './use_data_view_fallback'; export interface SourcererComponentProps { scope: sourcererModel.SourcererScopeName; @@ -289,52 +289,22 @@ export const Sourcerer = React.memo(({ scope: scopeId } sourcererMissingPatterns, ]); - const onOpenAndReset = useCallback(() => { + const handleDataViewFallbackError = useCallback(() => { setPopoverIsOpen(true); resetDataSources(); }, [resetDataSources]); - const { createAdhocDataView } = useCreateAdhocDataView(onOpenAndReset); - - // NOTE: this exists because missingPatterns array reference is swapped very often and we only care about - // the values here - const stableMissingPatternsString = useMemo(() => missingPatterns.join(), [missingPatterns]); - - useEffect(() => { - // NOTE: this lets us prevent setting the index too often, - // as there is no way to pass in abort signal into the data view creation apis - let ignore = false; - - (async () => { - if ( - (dataViewId === null && isModified === 'deprecated') || - isModified === 'missingPatterns' - ) { - const adhocDataView = await createAdhocDataView(stableMissingPatternsString.split(',')); - - if (ignore) { - return; - } - - if (!adhocDataView || !adhocDataView.id) { - return; - } - - const patterns = adhocDataView.getIndexPattern().split(','); - dispatchChangeDataView(adhocDataView.id, patterns, false); - } - })(); - - return () => { - ignore = true; - }; - }, [ - dataViewId, - isModified, - createAdhocDataView, - stableMissingPatternsString, - dispatchChangeDataView, - ]); + // NOTE: this hook will enable the fallback data view (adhoc), + // depending on the state of this component (see "enableFallback" below) + useDataViewFallback({ + onApplyFallbackDataView: dispatchChangeDataView, + enableFallback: + // NOTE: there are cases where sourcerer does not know the dataViewId to display and only knows required patterns. + // In that case, we enable the fallback dataview creation that will try to create adhoc data view based on the patterns & will select it. + (dataViewId === null && isModified === 'deprecated') || isModified === 'missingPatterns', + missingPatterns, + onResolveErrorManually: handleDataViewFallbackError, + }); useEffect(() => { setDataViewId(selectedDataViewId); diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_create_adhoc_data_view.test.tsx similarity index 97% rename from x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.test.tsx rename to x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_create_adhoc_data_view.test.tsx index 865fa598e23a9..0b36b52c128fa 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_create_adhoc_data_view.test.tsx @@ -6,7 +6,7 @@ */ import { renderHook } from '@testing-library/react'; -import { useCreateAdhocDataView } from './use_update_data_view'; +import { useCreateAdhocDataView } from './use_create_adhoc_data_view'; import { useKibana as mockUseKibana } from '../../common/lib/kibana/__mocks__'; import * as i18n from './translations'; const mockAddSuccess = jest.fn(); diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_create_adhoc_data_view.tsx similarity index 93% rename from x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.tsx rename to x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_create_adhoc_data_view.tsx index 965fd59eeb98f..28d6e8d53688c 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_update_data_view.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_create_adhoc_data_view.tsx @@ -20,7 +20,7 @@ export interface UseCreateAdhocDataViewReturnValue { } export const useCreateAdhocDataView = ( - onOpenAndReset: () => void + onResolveErrorManually: () => void ): UseCreateAdhocDataViewReturnValue => { const { dataViews, uiSettings } = useKibana().services; const { addError } = useAppToasts(); @@ -55,7 +55,7 @@ export const useCreateAdhocDataView = ( defaultMessage="Unexpected error occurred on update. If you would like to modify your data, you can manually select a data view {link}." values={{ link: ( - + {i18n.TOGGLE_TO_NEW_SOURCERER} ), @@ -68,7 +68,7 @@ export const useCreateAdhocDataView = ( return null; } }, - [addError, onOpenAndReset, uiSettings, dataViews] + [addError, onResolveErrorManually, uiSettings, dataViews] ); return { createAdhocDataView }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_data_view_fallback.ts b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_data_view_fallback.ts new file mode 100644 index 0000000000000..c8a9614cd6a93 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_data_view_fallback.ts @@ -0,0 +1,82 @@ +/* + * 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 { useEffect, useMemo } from 'react'; +import { useCreateAdhocDataView } from './use_create_adhoc_data_view'; + +interface UseDataViewFallbackConfig { + /** + * Called when user interacts with the erorr notification + */ + onResolveErrorManually: VoidFunction; + + /** + * Missing patterns that need to be wrapped in an adhoc adhocDataView + */ + missingPatterns: string[]; + + /** + * Is fallback process enabled? + */ + enableFallback: boolean; + + /** + * Calls external function on dataview change + */ + onApplyFallbackDataView: ( + newSelectedDataView: string, + newSelectedPatterns: string[], + shouldValidateSelectedPatterns?: boolean + ) => void; +} + +export const useDataViewFallback = ({ + onResolveErrorManually, + missingPatterns, + enableFallback, + onApplyFallbackDataView, +}: UseDataViewFallbackConfig) => { + const { createAdhocDataView } = useCreateAdhocDataView(onResolveErrorManually); + + // NOTE: this exists because missingPatterns array reference is swapped very often and we only care about + // the values here + const stableMissingPatternsString = useMemo(() => missingPatterns.join(), [missingPatterns]); + + useEffect(() => { + // NOTE: this lets us prevent setting the index on every commit, + // as there is no way to pass in abort signal into the data view creation apis + let ignore = false; + + if (!enableFallback) { + return; + } + + if (!stableMissingPatternsString.length) { + return; + } + + (async () => { + const adhocDataView = await createAdhocDataView(stableMissingPatternsString.split(',')); + + if (ignore) { + return; + } + + if (!adhocDataView || !adhocDataView.id) { + return; + } + + const patterns = adhocDataView.getIndexPattern().split(','); + + onApplyFallbackDataView(adhocDataView.id, patterns, false); + })(); + + return () => { + ignore = true; + }; + }, [createAdhocDataView, onApplyFallbackDataView, enableFallback, stableMissingPatternsString]); +}; From bb777b03ee7066e0d037eb69367bc4fc986a638f Mon Sep 17 00:00:00 2001 From: lgestc Date: Thu, 30 Jan 2025 14:28:26 +0100 Subject: [PATCH 14/32] --wip-- [skip ci] --- .../use_create_adhoc_data_view.test.tsx | 76 +++++++++---------- .../components/use_create_adhoc_data_view.tsx | 24 +++--- 2 files changed, 49 insertions(+), 51 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_create_adhoc_data_view.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_create_adhoc_data_view.test.tsx index 0b36b52c128fa..a08a0f21e73c3 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_create_adhoc_data_view.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_create_adhoc_data_view.test.tsx @@ -5,14 +5,16 @@ * 2.0. */ -import { renderHook } from '@testing-library/react'; +import { act, renderHook } from '@testing-library/react'; import { useCreateAdhocDataView } from './use_create_adhoc_data_view'; import { useKibana as mockUseKibana } from '../../common/lib/kibana/__mocks__'; import * as i18n from './translations'; const mockAddSuccess = jest.fn(); const mockAddError = jest.fn(); -const mockPatterns = ['packetbeat-*', 'winlogbeat-*']; const mockedUseKibana = mockUseKibana(); +mockedUseKibana.services.dataViews = { create: jest.fn() }; +mockedUseKibana.services.uiSettings = { get: jest.fn().mockReturnValue([]) }; + jest.mock('../../common/hooks/use_app_toasts', () => { const original = jest.requireActual('../../common/hooks/use_app_toasts'); @@ -37,48 +39,46 @@ jest.mock('@kbn/react-kibana-mount', () => { toMountPoint: jest.fn(), }; }); -describe('use_update_data_view', () => { - const mockError = jest.fn(); - beforeEach(() => { - mockedUseKibana.services.uiSettings.get.mockImplementation(() => mockPatterns); - mockedUseKibana.services.uiSettings.set.mockResolvedValue(true); - jest.clearAllMocks(); - }); +describe('useCreateAdhocDataView', () => { + beforeEach(() => {}); - test('Successful uiSettings updates with correct index pattern, and shows success toast', async () => { - const { result } = renderHook(() => useCreateAdhocDataView(mockError)); - const updateDataView = result.current; - const isUiSettingsSuccess = await updateDataView(['missing-*']); - expect(mockedUseKibana.services.uiSettings.set.mock.calls[0][1]).toEqual( - [...mockPatterns, 'missing-*'].sort() - ); - expect(isUiSettingsSuccess).toEqual(true); - expect(mockAddSuccess).toHaveBeenCalled(); - }); + afterEach(() => {}); - test('Failed uiSettings update returns false and shows error toast', async () => { - mockedUseKibana.services.uiSettings.set.mockImplementation(() => false); - const { result } = renderHook(() => useCreateAdhocDataView(mockError)); - const updateDataView = result.current; - const isUiSettingsSuccess = await updateDataView(['missing-*']); - expect(mockedUseKibana.services.uiSettings.set.mock.calls[0][1]).toEqual( - [...mockPatterns, 'missing-*'].sort() + it('should create data view successfully with given patterns', async () => { + const { result } = renderHook(() => useCreateAdhocDataView(jest.fn())); + + const mockDataViews = mockedUseKibana.services.dataViews; + + await act(async () => { + await result.current.createAdhocDataView(['pattern-1-*', 'pattern-2-*']); + }); + + expect(mockDataViews.create).toHaveBeenCalledWith( + expect.objectContaining({ + title: 'pattern-1-*,pattern-2-*', + id: 'adhoc_sourcerer_pattern-1-*,pattern-2-*', + }) ); - expect(isUiSettingsSuccess).toEqual(false); - expect(mockAddError).toHaveBeenCalled(); - expect(mockAddError.mock.calls[0][0]).toEqual(new Error(i18n.FAILURE_TOAST_TITLE)); }); - test('Failed uiSettings throws error and shows error toast', async () => { - mockedUseKibana.services.uiSettings.get.mockImplementation(() => { - throw new Error('Uh oh bad times over here'); + it('should add error on failure', async () => { + const onResolveErrorManually = jest.fn(); + const error = new Error('error'); + const mockDataViews = mockedUseKibana.services.dataViews; + + mockDataViews.create.mockRejectedValueOnce(error); + + const { result } = renderHook(() => useCreateAdhocDataView(onResolveErrorManually)); + + await act(async () => { + const dataView = await result.current.createAdhocDataView(['pattern-1-*']); + expect(dataView).toBeNull(); + }); + + expect(mockAddError).toHaveBeenCalledWith(error, { + title: i18n.FAILURE_TOAST_TITLE, + toastMessage: expect.any(String), }); - const { result } = renderHook(() => useCreateAdhocDataView(mockError)); - const updateDataView = result.current; - const isUiSettingsSuccess = await updateDataView(['missing-*']); - expect(isUiSettingsSuccess).toEqual(false); - expect(mockAddError).toHaveBeenCalled(); - expect(mockAddError.mock.calls[0][0]).toEqual(new Error('Uh oh bad times over here')); }); }); diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_create_adhoc_data_view.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_create_adhoc_data_view.tsx index 28d6e8d53688c..d3504fbcbe817 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_create_adhoc_data_view.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_create_adhoc_data_view.tsx @@ -49,19 +49,17 @@ export const useCreateAdhocDataView = ( addError(possibleError !== null ? possibleError : new Error(i18n.FAILURE_TOAST_TITLE), { title: i18n.FAILURE_TOAST_TITLE, toastMessage: ( - <> - - {i18n.TOGGLE_TO_NEW_SOURCERER} - - ), - }} - /> - + + {i18n.TOGGLE_TO_NEW_SOURCERER} + + ), + }} + /> ) as unknown as string, }); From ceb1240979096adedff5e35630271be51687d644 Mon Sep 17 00:00:00 2001 From: lgestc Date: Thu, 30 Jan 2025 14:59:26 +0100 Subject: [PATCH 15/32] --wip-- [skip ci] --- .../sourcerer/components/use_create_adhoc_data_view.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_create_adhoc_data_view.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_create_adhoc_data_view.test.tsx index a08a0f21e73c3..b8f1d6b9dfd02 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_create_adhoc_data_view.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_create_adhoc_data_view.test.tsx @@ -78,7 +78,7 @@ describe('useCreateAdhocDataView', () => { expect(mockAddError).toHaveBeenCalledWith(error, { title: i18n.FAILURE_TOAST_TITLE, - toastMessage: expect.any(String), + toastMessage: expect.anything(), }); }); }); From 5a823df0fed0f4c4dd8615a593fab3686239f220 Mon Sep 17 00:00:00 2001 From: lgestc Date: Thu, 30 Jan 2025 15:07:16 +0100 Subject: [PATCH 16/32] add test skeleton --- .../components/use_data_view_fallback.test.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_data_view_fallback.test.ts diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_data_view_fallback.test.ts b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_data_view_fallback.test.ts new file mode 100644 index 0000000000000..33f142b46f224 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_data_view_fallback.test.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. + */ + +describe('useDataViewFallback', () => { + it.todo('should create adhoc data views when enabled', () => {}); +}); From ca0013b0fb0659f879c2e29d13c0882eb972a8bb Mon Sep 17 00:00:00 2001 From: lgestc Date: Mon, 3 Feb 2025 09:03:56 +0100 Subject: [PATCH 17/32] add test for the dataview fallback hook --- .../components/use_data_view_fallback.test.ts | 68 ++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_data_view_fallback.test.ts b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_data_view_fallback.test.ts index 33f142b46f224..1ed8f4e71ad25 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_data_view_fallback.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_data_view_fallback.test.ts @@ -5,6 +5,72 @@ * 2.0. */ +import { act, renderHook } from '@testing-library/react'; +import { useDataViewFallback } from './use_data_view_fallback'; +import { useCreateAdhocDataView } from './use_create_adhoc_data_view'; +import type { DataView } from '@kbn/data-views-plugin/common'; + +jest.mock('./use_create_adhoc_data_view'); + +const mockDataView = { + id: 'mock', + getIndexPattern: () => 'pattern1,pattern2', +} as unknown as DataView; + describe('useDataViewFallback', () => { - it.todo('should create adhoc data views when enabled', () => {}); + let onResolveErrorManually: VoidFunction; + let onApplyFallbackDataView: VoidFunction; + + beforeEach(() => { + onResolveErrorManually = jest.fn(); + onApplyFallbackDataView = jest.fn(); + jest.mocked(useCreateAdhocDataView).mockReturnValue({ + createAdhocDataView: jest.fn(async (): Promise => mockDataView), + }); + }); + + it('should not create an adhoc data view when `enableFallback` is false', async () => { + renderHook(() => + useDataViewFallback({ + onResolveErrorManually, + missingPatterns: ['pattern1'], + enableFallback: false, + onApplyFallbackDataView, + }) + ); + + expect(useCreateAdhocDataView(() => {}).createAdhocDataView).not.toHaveBeenCalled(); + }); + + it('should create adhoc data views when `enableFallback` and `missingPatterns` are provided', async () => { + renderHook(() => + useDataViewFallback({ + onResolveErrorManually, + missingPatterns: ['pattern1'], + enableFallback: true, + onApplyFallbackDataView, + }) + ); + + expect(useCreateAdhocDataView(() => {}).createAdhocDataView).toHaveBeenCalledWith(['pattern1']); + }); + + it('should apply fallback data view once when a data view is successfully created', async () => { + const result = renderHook(() => + useDataViewFallback({ + onResolveErrorManually, + missingPatterns: ['pattern1'], + enableFallback: true, + onApplyFallbackDataView, + }) + ); + + // NOTE: test if multiple renders with the same props result with a single dataview selection call + await act(async () => result.rerender()); + await act(async () => result.rerender()); + await act(async () => result.rerender()); + + expect(onApplyFallbackDataView).toHaveBeenCalledWith('mock', ['pattern1', 'pattern2'], false); + expect(onApplyFallbackDataView).toHaveBeenCalledTimes(1); + }); }); From 36a93867af13ff1e188d9074c8d773f34119266d Mon Sep 17 00:00:00 2001 From: lgestc Date: Mon, 3 Feb 2025 10:28:17 +0100 Subject: [PATCH 18/32] --wip-- [skip ci] --- .../public/sourcerer/components/index.tsx | 21 +++++++++++++++++-- .../sourcerer/components/translations.ts | 6 +++--- .../public/sourcerer/components/trigger.tsx | 7 +++++++ .../components/use_create_adhoc_data_view.tsx | 7 ++++++- .../components/use_pick_index_patterns.tsx | 2 +- 5 files changed, 36 insertions(+), 7 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx index dde07387753ee..0aeaee799014c 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx @@ -33,6 +33,7 @@ import { useSignalHelpers } from '../containers/use_signal_helpers'; import { useUpdateUrlParam } from '../../common/utils/global_query_string'; import { URL_PARAM_KEY } from '../../common/hooks/use_url_state'; import { useDataViewFallback } from './use_data_view_fallback'; +import { isAdhocDataView } from './use_create_adhoc_data_view'; export interface SourcererComponentProps { scope: sourcererModel.SourcererScopeName; @@ -294,10 +295,26 @@ export const Sourcerer = React.memo(({ scope: scopeId } resetDataSources(); }, [resetDataSources]); + const handleApplyFallbackDataView = useCallback( + (newSelectedDataViewId: string, patterns: string[], shouldValidate?: boolean) => { + dispatchChangeDataView(newSelectedDataViewId, patterns, false); + setDataViewId(newSelectedDataViewId); + }, + [dispatchChangeDataView] + ); + + const modificationStatus = useMemo(() => { + if (isAdhocDataView(dataViewId)) { + return 'adhoc'; + } + + return isModified; + }, [dataViewId, isModified]); + // NOTE: this hook will enable the fallback data view (adhoc), // depending on the state of this component (see "enableFallback" below) useDataViewFallback({ - onApplyFallbackDataView: dispatchChangeDataView, + onApplyFallbackDataView: handleApplyFallbackDataView, enableFallback: // NOTE: there are cases where sourcerer does not know the dataViewId to display and only knows required patterns. // In that case, we enable the fallback dataview creation that will try to create adhoc data view based on the patterns & will select it. @@ -325,7 +342,7 @@ export const Sourcerer = React.memo(({ scope: scopeId } showSourcerer={showSourcerer} activePatterns={activePatterns} isTriggerDisabled={false} - isModified={isModified} + isModified={modificationStatus} isOnlyDetectionAlerts={isOnlyDetectionAlerts} isPopoverOpen={isPopoverOpen} isTimelineSourcerer={isTimelineSourcerer} diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/translations.ts b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/translations.ts index 2629bad08cfb8..d73c5e3632185 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/translations.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/translations.ts @@ -96,10 +96,10 @@ export const ALERTS_BADGE_TITLE = i18n.translate( } ); -export const DEPRECATED_BADGE_TITLE = i18n.translate( - 'xpack.securitySolution.indexPatterns.updateAvailableBadgeTitle', +export const COMPAT_BADGE_TITLE = i18n.translate( + 'xpack.securitySolution.indexPatterns.compatBadgeTitle', { - defaultMessage: 'Action required', + defaultMessage: 'Compat mode', } ); diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/trigger.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/trigger.tsx index 19d74cf775fa4..5a44566a8093d 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/trigger.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/trigger.tsx @@ -50,6 +50,13 @@ export const TriggerComponent: FC = ({ {i18n.ALERTS_BADGE_TITLE} ); + case 'adhoc': { + return ( + + {i18n.COMPAT_BADGE_TITLE} + + ); + } case 'deprecated': case 'missingPatterns': case '': diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_create_adhoc_data_view.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_create_adhoc_data_view.tsx index d3504fbcbe817..7b76b30a68477 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_create_adhoc_data_view.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_create_adhoc_data_view.tsx @@ -19,6 +19,11 @@ export interface UseCreateAdhocDataViewReturnValue { createAdhocDataView: (missingPatterns: string[]) => Promise; } +const ADHOC_ID_PREFIX = 'adhoc_sourcerer_' as const; + +export const isAdhocDataView = (dataViewId: string | null) => + dataViewId?.startsWith(ADHOC_ID_PREFIX) ?? false; + export const useCreateAdhocDataView = ( onResolveErrorManually: () => void ): UseCreateAdhocDataViewReturnValue => { @@ -33,7 +38,7 @@ export const useCreateAdhocDataView = ( const validatedPatterns = ensurePatternFormat(combinedPatterns); const patternsString = validatedPatterns.join(','); const adHocDataView = await dataViews.create({ - id: `adhoc_sourcerer_${patternsString}`, + id: `${ADHOC_ID_PREFIX}${patternsString}`, title: patternsString, }); diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_pick_index_patterns.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_pick_index_patterns.tsx index 8798a84409d13..335c4fa8efd05 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_pick_index_patterns.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_pick_index_patterns.tsx @@ -29,7 +29,7 @@ interface UsePickIndexPatternsProps { signalIndexName: string | null; } -export type ModifiedTypes = 'modified' | 'alerts' | 'deprecated' | 'missingPatterns' | ''; +export type ModifiedTypes = 'modified' | 'alerts' | 'deprecated' | 'missingPatterns' | 'adhoc' | ''; interface UsePickIndexPatterns { allOptions: Array>; From 2a70690c6917d612ebb3dd151bc6914aacf31ac6 Mon Sep 17 00:00:00 2001 From: lgestc Date: Mon, 3 Feb 2025 13:22:00 +0100 Subject: [PATCH 19/32] fix broken paths --- .../public/sourcerer/components/alerts_sourcerer.test.tsx | 2 +- .../public/sourcerer/components/index.test.tsx | 2 +- .../security_solution/public/sourcerer/components/misc.test.tsx | 2 +- .../public/sourcerer/components/sourcerer_integration.test.tsx | 2 +- .../public/sourcerer/components/timeline_sourcerer.test.tsx | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/alerts_sourcerer.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/alerts_sourcerer.test.tsx index 912eb0812c04f..1ea969328ce4d 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/alerts_sourcerer.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/alerts_sourcerer.test.tsx @@ -19,7 +19,7 @@ const mockDispatch = jest.fn(); jest.mock('../containers'); jest.mock('../containers/use_signal_helpers'); const mockuseCreateAdhocDataView = jest.fn().mockReturnValue(() => true); -jest.mock('./use_update_data_view', () => ({ +jest.mock('./use_create_adhoc_data_view', () => ({ useCreateAdhocDataView: () => mockuseCreateAdhocDataView, })); jest.mock('react-redux', () => { diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.test.tsx index fa2546c9008ee..de1f8c37bc74a 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.test.tsx @@ -25,7 +25,7 @@ const mockDispatch = jest.fn(); jest.mock('../containers'); jest.mock('../containers/use_signal_helpers'); const mockuseCreateAdhocDataView = jest.fn().mockReturnValue(() => true); -jest.mock('./use_update_data_view', () => ({ +jest.mock('./use_create_adhoc_data_view', () => ({ useCreateAdhocDataView: () => mockuseCreateAdhocDataView, })); jest.mock('react-redux', () => { diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/misc.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/misc.test.tsx index 9988b6379f667..8600e8bff7396 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/misc.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/misc.test.tsx @@ -26,7 +26,7 @@ const mockDispatch = jest.fn(); jest.mock('../containers'); jest.mock('../containers/use_signal_helpers'); const mockuseCreateAdhocDataView = jest.fn().mockReturnValue(() => true); -jest.mock('./use_update_data_view', () => ({ +jest.mock('./use_create_adhoc_data_view', () => ({ useCreateAdhocDataView: () => mockuseCreateAdhocDataView, })); jest.mock('react-redux', () => { diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/sourcerer_integration.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/sourcerer_integration.test.tsx index 1b0fae0a332ac..3d1fc1ddedbda 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/sourcerer_integration.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/sourcerer_integration.test.tsx @@ -22,7 +22,7 @@ const mockDispatch = jest.fn(); jest.mock('../containers'); jest.mock('../containers/use_signal_helpers'); const mockuseCreateAdhocDataView = jest.fn().mockReturnValue(() => true); -jest.mock('./use_update_data_view', () => ({ +jest.mock('./use_create_adhoc_data_view', () => ({ useCreateAdhocDataView: () => mockuseCreateAdhocDataView, })); jest.mock('react-redux', () => { diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/timeline_sourcerer.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/timeline_sourcerer.test.tsx index 6eb8f531da5bd..e127c4e9821cb 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/timeline_sourcerer.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/timeline_sourcerer.test.tsx @@ -20,7 +20,7 @@ const mockDispatch = jest.fn(); jest.mock('../containers'); jest.mock('../containers/use_signal_helpers'); const mockuseCreateAdhocDataView = jest.fn().mockReturnValue(() => true); -jest.mock('./use_update_data_view', () => ({ +jest.mock('./use_create_adhoc_data_view', () => ({ useCreateAdhocDataView: () => mockuseCreateAdhocDataView, })); jest.mock('react-redux', () => { From 8ab8c154fb43bd19307dd26896d74fdd40bdbd76 Mon Sep 17 00:00:00 2001 From: lgestc Date: Mon, 3 Feb 2025 13:31:28 +0100 Subject: [PATCH 20/32] remove unused translations --- .../plugins/private/translations/translations/fr-FR.json | 3 --- .../plugins/private/translations/translations/ja-JP.json | 3 --- .../plugins/private/translations/translations/zh-CN.json | 3 --- 3 files changed, 9 deletions(-) diff --git a/x-pack/platform/plugins/private/translations/translations/fr-FR.json b/x-pack/platform/plugins/private/translations/translations/fr-FR.json index fd248475915aa..b0be682999d04 100644 --- a/x-pack/platform/plugins/private/translations/translations/fr-FR.json +++ b/x-pack/platform/plugins/private/translations/translations/fr-FR.json @@ -38251,8 +38251,6 @@ "xpack.securitySolution.indexPatterns.failureToastTitle": "Impossible de mettre à jour la vue de données", "xpack.securitySolution.indexPatterns.inactive": "Modèles d'indexation inactifs", "xpack.securitySolution.indexPatterns.indexPatternsLabel": "Modèles d'indexation", - "xpack.securitySolution.indexPatterns.missingPatterns": "La vue de données Security ne contient pas les modèles d'indexation suivants nécessaires pour recréer la vue de données de la chronologie précédente : {callout}", - "xpack.securitySolution.indexPatterns.missingPatterns.callout": "La vue de données Security ne contient pas les modèles d'indexation suivants : {callout}", "xpack.securitySolution.indexPatterns.missingPatterns.timeline.description": "Nous avons préservé votre chronologie en créant une vue de données temporaires. Si vous souhaitez modifier vos données, nous pouvons ajouter les modèles d'indexation manquants à la vue de données Security. Vous pouvez également sélectionner manuellement une vue de données {link}.", "xpack.securitySolution.indexPatterns.missingPatterns.timelineTemplate.description": "Nous avons conservé votre modèle de chronologie en créant une vue de données temporaires. Si vous souhaitez modifier vos données, nous pouvons ajouter les modèles d'indexation manquants à la vue de données Security. Vous pouvez également sélectionner manuellement une vue de données {link}.", "xpack.securitySolution.indexPatterns.modifiedBadgeTitle": "Modifié", @@ -38276,7 +38274,6 @@ "xpack.securitySolution.indexPatterns.timelineTemplate.toggleToNewSourcerer": "Nous avons conservé votre modèle de chronologie en créant une vue de données temporaires. Si vous souhaitez modifier vos données, nous pouvons recréer votre vue de données temporaires à l'aide du sélecteur de vue de nouvelles données. Vous pouvez également sélectionner manuellement une vue de données {link}.", "xpack.securitySolution.indexPatterns.toggleToNewSourcerer.link": "ici", "xpack.securitySolution.indexPatterns.update": "Mettre à jour et recréer la vue de données", - "xpack.securitySolution.indexPatterns.updateAvailableBadgeTitle": "Mise à jour disponible", "xpack.securitySolution.indexPatterns.updateDataView": "Souhaitez-vous ajouter ce modèle d'indexation à la vue de données Security ? Sinon, nous pouvons recréer la vue de données sans les modèles d'indexation manquants.", "xpack.securitySolution.indexPatterns.updateSecurityDataView": "Mettre à jour la vue de données Security", "xpack.securitySolution.inputCapture.ariaPlaceHolder": "Saisir une commande", diff --git a/x-pack/platform/plugins/private/translations/translations/ja-JP.json b/x-pack/platform/plugins/private/translations/translations/ja-JP.json index 1d94b42b99ed0..f6e3d37dc39c8 100644 --- a/x-pack/platform/plugins/private/translations/translations/ja-JP.json +++ b/x-pack/platform/plugins/private/translations/translations/ja-JP.json @@ -38112,8 +38112,6 @@ "xpack.securitySolution.indexPatterns.failureToastTitle": "データビューを更新できません", "xpack.securitySolution.indexPatterns.inactive": "非アクティブなインデックスパターン", "xpack.securitySolution.indexPatterns.indexPatternsLabel": "インデックスパターン", - "xpack.securitySolution.indexPatterns.missingPatterns": "以前のタイムラインのデータビューを再作成するには、セキュリティデータビューに次のインデックスパターンがありません:{callout}", - "xpack.securitySolution.indexPatterns.missingPatterns.callout": "セキュリティデータビューには次のインデックスパターンがありません:{callout}", "xpack.securitySolution.indexPatterns.missingPatterns.timeline.description": "一時データビューを作成することで、タイムラインを保持しています。データを修正する場合は、見つからないインデックスパターンをセキュリティデータビューに追加できます。手動でデータビュー{link}を選択することもできます。", "xpack.securitySolution.indexPatterns.missingPatterns.timelineTemplate.description": "一時データビューを作成することで、タイムラインテンプレートを保持しています。データを修正する場合は、見つからないインデックスパターンをセキュリティデータビューに追加できます。手動でデータビュー{link}を選択することもできます。", "xpack.securitySolution.indexPatterns.modifiedBadgeTitle": "変更済み", @@ -38137,7 +38135,6 @@ "xpack.securitySolution.indexPatterns.timelineTemplate.toggleToNewSourcerer": "一時データビューを作成することで、タイムラインテンプレートを保持しています。データを修正する場合は、新しいデータビューセレクターを使用して、一時データビューを再作成できます。手動でデータビュー{link}を選択することもできます。", "xpack.securitySolution.indexPatterns.toggleToNewSourcerer.link": "こちら", "xpack.securitySolution.indexPatterns.update": "データビューを更新して再作成", - "xpack.securitySolution.indexPatterns.updateAvailableBadgeTitle": "更新が利用可能です", "xpack.securitySolution.indexPatterns.updateDataView": "このインデックスパターンをセキュリティデータビューに追加しますか?そうでない場合は、見つからないインデックスパターンなしで、データビューを再作成できます。", "xpack.securitySolution.indexPatterns.updateSecurityDataView": "セキュリティデータビューを更新", "xpack.securitySolution.inputCapture.ariaPlaceHolder": "コマンドを入力", diff --git a/x-pack/platform/plugins/private/translations/translations/zh-CN.json b/x-pack/platform/plugins/private/translations/translations/zh-CN.json index e791fef214c9e..445b20941fc97 100644 --- a/x-pack/platform/plugins/private/translations/translations/zh-CN.json +++ b/x-pack/platform/plugins/private/translations/translations/zh-CN.json @@ -37543,8 +37543,6 @@ "xpack.securitySolution.indexPatterns.failureToastTitle": "无法更新数据视图", "xpack.securitySolution.indexPatterns.inactive": "非活动索引模式", "xpack.securitySolution.indexPatterns.indexPatternsLabel": "索引模式", - "xpack.securitySolution.indexPatterns.missingPatterns": "要重新创建上一时间线的数据视图,安全数据视图缺少以下索引模式:{callout}", - "xpack.securitySolution.indexPatterns.missingPatterns.callout": "安全数据视图缺少以下索引模式:{callout}", "xpack.securitySolution.indexPatterns.missingPatterns.timeline.description": "我们已通过创建临时数据视图来保留您的时间线。如果您要修改数据,我们可以将缺失的索引模式添加到安全数据视图。您还可以手动选择数据视图 {link}。", "xpack.securitySolution.indexPatterns.missingPatterns.timelineTemplate.description": "我们已通过创建临时数据视图来保留您的时间线模板。如果您要修改数据,我们可以将缺失的索引模式添加到安全数据视图。您还可以手动选择数据视图 {link}。", "xpack.securitySolution.indexPatterns.modifiedBadgeTitle": "已修改", @@ -37568,7 +37566,6 @@ "xpack.securitySolution.indexPatterns.timelineTemplate.toggleToNewSourcerer": "我们已通过创建临时数据视图来保留您的时间线模板。如果您要修改数据,我们可以使用新的数据视图选择器重新创建临时数据视图。您还可以手动选择数据视图 {link}。", "xpack.securitySolution.indexPatterns.toggleToNewSourcerer.link": "此处", "xpack.securitySolution.indexPatterns.update": "更新并重新创建数据视图", - "xpack.securitySolution.indexPatterns.updateAvailableBadgeTitle": "有可用更新", "xpack.securitySolution.indexPatterns.updateDataView": "是否要将此索引模式添加到安全数据视图?否则,我们可以不使用缺失的索引模式来重新创建数据视图。", "xpack.securitySolution.indexPatterns.updateSecurityDataView": "更新安全数据视图", "xpack.securitySolution.inputCapture.ariaPlaceHolder": "输入命令", From 1413cde4d4a753f153742b4d9812a2676b782401 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 3 Feb 2025 12:42:45 +0000 Subject: [PATCH 21/32] [CI] Auto-commit changed files from 'node scripts/styled_components_mapping' --- packages/kbn-babel-preset/styled_components_files.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/kbn-babel-preset/styled_components_files.js b/packages/kbn-babel-preset/styled_components_files.js index 4e7a6f913acf1..a51ab690e7674 100644 --- a/packages/kbn-babel-preset/styled_components_files.js +++ b/packages/kbn-babel-preset/styled_components_files.js @@ -444,7 +444,6 @@ module.exports = { /x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]resolver[\/\\]view[\/\\]symbol_definitions.tsx/, /x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]sourcerer[\/\\]components[\/\\]helpers.tsx/, /x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]sourcerer[\/\\]components[\/\\]refresh_button.tsx/, - /x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]sourcerer[\/\\]components[\/\\]update_default_data_view_modal.tsx/, /x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]timelines[\/\\]components[\/\\]certificate_fingerprint[\/\\]index.tsx/, /x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]timelines[\/\\]components[\/\\]edit_data_provider[\/\\]index.tsx/, /x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]timelines[\/\\]components[\/\\]fields_browser[\/\\]create_field_button[\/\\]index.tsx/, From 5276cf4571e00e3cdb8fda72225ab2cd72b72c08 Mon Sep 17 00:00:00 2001 From: lgestc Date: Tue, 4 Feb 2025 09:41:14 +0100 Subject: [PATCH 22/32] --wip-- [skip ci] --- .../__mocks__/use_create_adhoc_data_view.tsx | 17 ++ .../components/alerts_sourcerer.test.tsx | 5 +- .../sourcerer/components/index.test.tsx | 5 +- .../public/sourcerer/components/misc.test.tsx | 163 +++--------------- .../components/sourcerer_integration.test.tsx | 5 +- .../components/timeline_sourcerer.test.tsx | 5 +- 6 files changed, 41 insertions(+), 159 deletions(-) create mode 100644 x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/__mocks__/use_create_adhoc_data_view.tsx diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/__mocks__/use_create_adhoc_data_view.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/__mocks__/use_create_adhoc_data_view.tsx new file mode 100644 index 0000000000000..929780f67f2e7 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/__mocks__/use_create_adhoc_data_view.tsx @@ -0,0 +1,17 @@ +/* + * 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 useCreateAdhocDataView = jest.fn(() => { + return { + createAdhocDataView: jest.fn(() => ({ + id: 'adhoc_sourcerer_mock_view', + getIndexPattern: () => 'pattern1,pattern2', + })), + }; +}); + +export const isAdhocDataView = jest.requireActual('../use_create_adhoc_data_view').isAdhocDataView; diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/alerts_sourcerer.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/alerts_sourcerer.test.tsx index 1ea969328ce4d..2450c4143b32f 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/alerts_sourcerer.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/alerts_sourcerer.test.tsx @@ -18,10 +18,7 @@ const mockDispatch = jest.fn(); jest.mock('../containers'); jest.mock('../containers/use_signal_helpers'); -const mockuseCreateAdhocDataView = jest.fn().mockReturnValue(() => true); -jest.mock('./use_create_adhoc_data_view', () => ({ - useCreateAdhocDataView: () => mockuseCreateAdhocDataView, -})); +jest.mock('./use_create_adhoc_data_view'); jest.mock('react-redux', () => { const original = jest.requireActual('react-redux'); diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.test.tsx index de1f8c37bc74a..62a16f85f8d6a 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.test.tsx @@ -24,10 +24,7 @@ const mockDispatch = jest.fn(); jest.mock('../containers'); jest.mock('../containers/use_signal_helpers'); -const mockuseCreateAdhocDataView = jest.fn().mockReturnValue(() => true); -jest.mock('./use_create_adhoc_data_view', () => ({ - useCreateAdhocDataView: () => mockuseCreateAdhocDataView, -})); +jest.mock('./use_create_adhoc_data_view'); jest.mock('react-redux', () => { const original = jest.requireActual('react-redux'); diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/misc.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/misc.test.tsx index 8600e8bff7396..f4eb8ead0f3c9 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/misc.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/misc.test.tsx @@ -8,7 +8,6 @@ import React from 'react'; import type { ReactWrapper } from 'enzyme'; import { mount } from 'enzyme'; -import { cloneDeep } from 'lodash'; import { initialSourcererState, type SelectedDataView, SourcererScopeName } from '../store/model'; import { Sourcerer } from '.'; @@ -19,16 +18,13 @@ import { useSignalHelpers } from '../containers/use_signal_helpers'; import { TimelineId } from '../../../common/types/timeline'; import { type TimelineType, TimelineTypeEnum } from '../../../common/api/timeline'; import { sortWithExcludesAtEnd } from '../../../common/utils/sourcerer'; -import { render, fireEvent, screen, waitFor } from '@testing-library/react'; +import { render, fireEvent, screen, waitFor, act } from '@testing-library/react'; const mockDispatch = jest.fn(); jest.mock('../containers'); jest.mock('../containers/use_signal_helpers'); -const mockuseCreateAdhocDataView = jest.fn().mockReturnValue(() => true); -jest.mock('./use_create_adhoc_data_view', () => ({ - useCreateAdhocDataView: () => mockuseCreateAdhocDataView, -})); +jest.mock('./use_create_adhoc_data_view'); jest.mock('react-redux', () => { const original = jest.requireActual('react-redux'); @@ -134,8 +130,8 @@ describe('No data', () => { }); }); -describe('Update available', () => { - const state2 = { +describe('Compat mode', () => { + const state = { ...mockGlobalState, sourcerer: { ...mockGlobalState.sourcerer, @@ -167,7 +163,7 @@ describe('Update available', () => { }, }, }; - let store = createMockStore(state2); + let store = createMockStore(state); const pollForSignalIndexMock = jest.fn(); beforeEach(() => { (useSignalHelpers as jest.Mock).mockReturnValue({ @@ -176,9 +172,8 @@ describe('Update available', () => { }); (useSourcererDataView as jest.Mock).mockReturnValue({ ...sourcererDataView, - activePatterns: ['myFakebeat-*'], }); - store = createMockStore(state2); + store = createMockStore(state); render( @@ -190,8 +185,8 @@ describe('Update available', () => { jest.clearAllMocks(); }); - test('Show Update available label', () => { - expect(screen.getByTestId('sourcerer-deprecated-badge')).toBeInTheDocument(); + test('Show Compat mode badge', () => { + expect(screen.getByText('Compat mode')).toBeInTheDocument(); }); test('Show correct tooltip', async () => { @@ -200,65 +195,10 @@ describe('Update available', () => { expect(screen.getByTestId('sourcerer-tooltip').textContent).toBe('myFakebeat-*'); }); }); - - test('Show UpdateDefaultDataViewModal', () => { - fireEvent.click(screen.queryAllByTestId('timeline-sourcerer-trigger')[0]); - - fireEvent.click(screen.queryAllByTestId('sourcerer-deprecated-update')[0]); - - expect(screen.getByTestId('sourcerer-update-data-view-modal')).toBeVisible(); - }); - - test('Show UpdateDefaultDataViewModal Callout', () => { - fireEvent.click(screen.queryAllByTestId('timeline-sourcerer-trigger')[0]); - - fireEvent.click(screen.queryAllByTestId('sourcerer-deprecated-update')[0]); - - expect(screen.queryAllByTestId('sourcerer-deprecated-callout')[0].textContent).toBe( - 'This timeline uses a legacy data view selector' - ); - - expect(screen.queryAllByTestId('sourcerer-current-patterns-message')[0].textContent).toBe( - 'The active index patterns in this timeline are: myFakebeat-*' - ); - - expect(screen.queryAllByTestId('sourcerer-deprecated-message')[0].textContent).toBe( - "We have preserved your timeline by creating a temporary data view. If you'd like to modify your data, we can recreate your temporary data view with the new data view selector. You can also manually select a data view here." - ); - }); - - test('Show Add index pattern in UpdateDefaultDataViewModal', () => { - fireEvent.click(screen.queryAllByTestId('timeline-sourcerer-trigger')[0]); - - fireEvent.click(screen.queryAllByTestId('sourcerer-deprecated-update')[0]); - - expect(screen.queryAllByTestId('sourcerer-update-data-view')[0].textContent).toBe( - 'Add index pattern' - ); - }); - - test('Set all the index patterns from legacy timeline to sourcerer, after clicking on "Add index pattern"', async () => { - fireEvent.click(screen.queryAllByTestId('timeline-sourcerer-trigger')[0]); - - fireEvent.click(screen.queryAllByTestId('sourcerer-deprecated-update')[0]); - - fireEvent.click(screen.queryAllByTestId('sourcerer-update-data-view')[0]); - - await waitFor(() => { - expect(mockDispatch).toHaveBeenCalledWith( - sourcererActions.setSelectedDataView({ - id: SourcererScopeName.timeline, - selectedDataViewId: 'security-solution', - selectedPatterns: ['myFakebeat-*'], - shouldValidateSelectedPatterns: false, - }) - ); - }); - }); }); -describe('Update available for timeline template', () => { - const state2 = { +describe('Compat mode for timeline template', () => { + const state = { ...mockGlobalState, timeline: { ...mockGlobalState.timeline, @@ -300,14 +240,13 @@ describe('Update available for timeline template', () => { }, }, }; - let store = createMockStore(state2); + let store = createMockStore(state); beforeEach(() => { (useSourcererDataView as jest.Mock).mockReturnValue({ ...sourcererDataView, - activePatterns: ['myFakebeat-*'], }); - store = createMockStore(state2); + store = createMockStore(state); render( @@ -319,22 +258,13 @@ describe('Update available for timeline template', () => { jest.clearAllMocks(); }); - test('Show UpdateDefaultDataViewModal CallOut', () => { - fireEvent.click(screen.getByTestId('timeline-sourcerer-trigger')); - fireEvent.click(screen.getByTestId('sourcerer-deprecated-update')); - - expect(screen.getByTestId('sourcerer-deprecated-callout')).toHaveTextContent( - 'This timeline template uses a legacy data view selector' - ); - - expect(screen.getByTestId('sourcerer-deprecated-message')).toHaveTextContent( - "We have preserved your timeline template by creating a temporary data view. If you'd like to modify your data, we can recreate your temporary data view with the new data view selector. You can also manually select a data view here." - ); + test('Show Compat mode badge', () => { + expect(screen.getByText('Compat mode')).toBeInTheDocument(); }); }); describe('Missing index patterns', () => { - const state2 = { + const state = { ...mockGlobalState, timeline: { ...mockGlobalState.timeline, @@ -376,7 +306,7 @@ describe('Missing index patterns', () => { }, }, }; - let store = createMockStore(state2); + let store = createMockStore(state); beforeEach(() => { const pollForSignalIndexMock = jest.fn(); (useSignalHelpers as jest.Mock).mockReturnValue({ @@ -389,45 +319,11 @@ describe('Missing index patterns', () => { jest.clearAllMocks(); }); - test('Show UpdateDefaultDataViewModal CallOut for timeline', async () => { + test('Show Compat mode badge', async () => { (useSourcererDataView as jest.Mock).mockReturnValue({ ...sourcererDataView, - activePatterns: ['myFakebeat-*'], }); - const state3 = cloneDeep(state2); - state3.timeline.timelineById[TimelineId.active].timelineType = TimelineTypeEnum.default; - store = createMockStore(state3); - - render( - - - - ); - - fireEvent.click(screen.getByTestId('timeline-sourcerer-trigger')); - - fireEvent.click(screen.getByTestId('sourcerer-deprecated-update')); - - expect(screen.getByTestId('sourcerer-deprecated-callout').textContent).toBe( - 'This timeline is out of date with the Security Data View' - ); - expect(screen.getByTestId('sourcerer-current-patterns-message').textContent).toBe( - 'The active index patterns in this timeline are: myFakebeat-*' - ); - expect(screen.queryAllByTestId('sourcerer-missing-patterns-callout')[0].textContent).toBe( - 'Security Data View is missing the following index patterns: myFakebeat-*' - ); - expect(screen.queryAllByTestId('sourcerer-missing-patterns-message')[0].textContent).toBe( - "We have preserved your timeline by creating a temporary data view. If you'd like to modify your data, we can add the missing index patterns to the Security Data View. You can also manually select a data view here." - ); - }); - - test('Show UpdateDefaultDataViewModal CallOut for timeline template', async () => { - (useSourcererDataView as jest.Mock).mockReturnValue({ - ...sourcererDataView, - activePatterns: ['myFakebeat-*'], - }); - store = createMockStore(state2); + store = createMockStore(state); render( @@ -435,27 +331,9 @@ describe('Missing index patterns', () => { ); - fireEvent.click(screen.getByTestId('timeline-sourcerer-trigger')); - - fireEvent.click(screen.getByTestId('sourcerer-deprecated-update')); - - await waitFor(() => { - expect(screen.queryAllByTestId('sourcerer-deprecated-callout')[0].textContent).toBe( - 'This timeline template is out of date with the Security Data View' - ); + await act(async () => {}); - expect(screen.queryAllByTestId('sourcerer-current-patterns-message')[0].textContent).toBe( - 'The active index patterns in this timeline template are: myFakebeat-*' - ); - - expect(screen.queryAllByTestId('sourcerer-missing-patterns-callout')[0].textContent).toBe( - 'Security Data View is missing the following index patterns: myFakebeat-*' - ); - - expect(screen.queryAllByTestId('sourcerer-missing-patterns-message')[0].textContent).toBe( - "We have preserved your timeline template by creating a temporary data view. If you'd like to modify your data, we can add the missing index patterns to the Security Data View. You can also manually select a data view here." - ); - }); + expect(screen.getByText('Compat mode')).toBeInTheDocument(); }); }); @@ -495,7 +373,6 @@ describe('Sourcerer integration tests', () => { (useSourcererDataView as jest.Mock).mockReturnValue({ ...sourcererDataView, - activePatterns: ['myFakebeat-*'], }); store = createMockStore(state); jest.clearAllMocks(); diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/sourcerer_integration.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/sourcerer_integration.test.tsx index 3d1fc1ddedbda..2047ea6407beb 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/sourcerer_integration.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/sourcerer_integration.test.tsx @@ -21,10 +21,7 @@ const mockDispatch = jest.fn(); jest.mock('../containers'); jest.mock('../containers/use_signal_helpers'); -const mockuseCreateAdhocDataView = jest.fn().mockReturnValue(() => true); -jest.mock('./use_create_adhoc_data_view', () => ({ - useCreateAdhocDataView: () => mockuseCreateAdhocDataView, -})); +jest.mock('./use_create_adhoc_data_view'); jest.mock('react-redux', () => { const original = jest.requireActual('react-redux'); diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/timeline_sourcerer.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/timeline_sourcerer.test.tsx index e127c4e9821cb..627926b103056 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/timeline_sourcerer.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/timeline_sourcerer.test.tsx @@ -19,10 +19,7 @@ const mockDispatch = jest.fn(); jest.mock('../containers'); jest.mock('../containers/use_signal_helpers'); -const mockuseCreateAdhocDataView = jest.fn().mockReturnValue(() => true); -jest.mock('./use_create_adhoc_data_view', () => ({ - useCreateAdhocDataView: () => mockuseCreateAdhocDataView, -})); +jest.mock('./use_create_adhoc_data_view'); jest.mock('react-redux', () => { const original = jest.requireActual('react-redux'); From 77332659fbb427771418b8755e6f43c443c17ff5 Mon Sep 17 00:00:00 2001 From: lgestc Date: Tue, 4 Feb 2025 10:02:06 +0100 Subject: [PATCH 23/32] remove enzyme --- .../public/sourcerer/components/misc.test.tsx | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/misc.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/misc.test.tsx index f4eb8ead0f3c9..6e978ecd7eab6 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/misc.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/misc.test.tsx @@ -6,8 +6,6 @@ */ import React from 'react'; -import type { ReactWrapper } from 'enzyme'; -import { mount } from 'enzyme'; import { initialSourcererState, type SelectedDataView, SourcererScopeName } from '../store/model'; import { Sourcerer } from '.'; @@ -57,11 +55,13 @@ const defaultProps = { scope: sourcererModel.SourcererScopeName.default, }; -const checkOptionsAndSelections = (wrapper: ReactWrapper, patterns: string[]) => ({ +const checkOptionsAndSelections = (patterns: string[]) => ({ availableOptionCount: - wrapper.find('List').length > 0 ? wrapper.find('List').prop('itemCount') : 0, - optionsSelected: patterns.every((pattern) => - wrapper.find(`[data-test-subj="sourcerer-combo-box"] span[title="${pattern}"]`).first().exists() + screen.queryAllByTestId('List').length > 0 ? screen.queryAllByTestId('List').length : 0, + optionsSelected: patterns.every( + (pattern) => + screen.getByTestId(`sourcerer-combo-box`).querySelectorAll(`span[title="${pattern}"]`) + .length === 1 ), }); @@ -102,31 +102,31 @@ describe('No data', () => { }); test('Hide sourcerer - default ', () => { - const wrapper = mount( + render( ); - expect(wrapper.find(`[data-test-subj="sourcerer-trigger"]`).exists()).toEqual(false); + expect(screen.queryAllByTestId('sourcerer-trigger')).toHaveLength(0); }); test('Hide sourcerer - detections ', () => { - const wrapper = mount( + render( ); - expect(wrapper.find(`[data-test-subj="sourcerer-trigger"]`).exists()).toEqual(false); + expect(screen.queryAllByTestId('sourcerer-trigger')).toHaveLength(0); }); test('Hide sourcerer - timeline ', () => { - const wrapper = mount( + render( ); - expect(wrapper.find(`[data-test-subj="timeline-sourcerer-trigger"]`).exists()).toEqual(true); + expect(screen.queryAllByTestId('timeline-sourcerer-trigger')).toHaveLength(1); }); }); @@ -379,20 +379,21 @@ describe('Sourcerer integration tests', () => { }); it('Selects a different index pattern', async () => { - const wrapper = mount( + render( ); - wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click'); - wrapper.find(`button[data-test-subj="sourcerer-select"]`).first().simulate('click'); - wrapper.find(`[data-test-subj="dataView-option-super"]`).first().simulate('click'); - expect(checkOptionsAndSelections(wrapper, ['fakebeat-*'])).toEqual({ + fireEvent.click(screen.getByTestId('sourcerer-trigger')); + fireEvent.click(screen.getByTestId('sourcerer-select')); + + fireEvent.click(screen.queryAllByTestId('dataView-option-super')[0]); + expect(checkOptionsAndSelections(['fakebeat-*'])).toEqual({ availableOptionCount: 0, optionsSelected: true, }); - wrapper.find(`button[data-test-subj="sourcerer-save"]`).first().simulate('click'); + fireEvent.click(screen.getByTestId('sourcerer-save')); expect(mockDispatch).toHaveBeenCalledWith( sourcererActions.setSelectedDataView({ From ab33ec361599a0da1162c91ae9bf236372cc78fa Mon Sep 17 00:00:00 2001 From: Luke Gmys <11671118+lgestc@users.noreply.github.com> Date: Fri, 7 Feb 2025 09:02:57 +0100 Subject: [PATCH 24/32] Update x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_data_view_fallback.ts Co-authored-by: Jatin Kathuria --- .../public/sourcerer/components/use_data_view_fallback.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_data_view_fallback.ts b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_data_view_fallback.ts index c8a9614cd6a93..b36732b8e866b 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_data_view_fallback.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_data_view_fallback.ts @@ -10,7 +10,7 @@ import { useCreateAdhocDataView } from './use_create_adhoc_data_view'; interface UseDataViewFallbackConfig { /** - * Called when user interacts with the erorr notification + * Called when user interacts with the error notification */ onResolveErrorManually: VoidFunction; From b40c7471f28cc2c71e7dfdd89c7aef4ccc2be206 Mon Sep 17 00:00:00 2001 From: lgestc Date: Fri, 7 Feb 2025 11:37:42 +0100 Subject: [PATCH 25/32] fix fields loading --- .../public/sourcerer/components/index.tsx | 34 +++++------ .../components/use_data_view_fallback.ts | 11 +--- .../public/sourcerer/containers/index.tsx | 56 ++++--------------- .../public/sourcerer/store/actions.ts | 2 + .../public/sourcerer/store/helpers.ts | 12 +++- 5 files changed, 39 insertions(+), 76 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx index 0aeaee799014c..85b7edf9c45de 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx @@ -17,6 +17,7 @@ import type { ChangeEventHandler } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; +import type { DataViewSpec } from '@kbn/data-views-plugin/common'; import * as i18n from './translations'; import type { sourcererModel } from '../store'; import { sourcererActions, sourcererSelectors } from '../store'; @@ -52,12 +53,7 @@ interface SourcererPopoverProps { signalIndexName: string | null; handleClosePopOver: () => void; isTimelineSourcerer: boolean; - selectedDataViewId: string | null; - sourcererMissingPatterns: string[]; - onUpdateDetectionAlertsChecked: () => void; handleOutsideClick: () => void; - setMissingPatterns: (missingPatterns: string[]) => void; - setDataViewId: (dataViewId: string | null) => void; scopeId: sourcererModel.SourcererScopeName; children: React.ReactNode; } @@ -76,11 +72,6 @@ const SourcererPopover = React.memo( signalIndexName, handleClosePopOver, isTimelineSourcerer, - selectedDataViewId, - sourcererMissingPatterns, - onUpdateDetectionAlertsChecked, - setMissingPatterns, - setDataViewId, scopeId, children, }) => { @@ -230,7 +221,9 @@ export const Sourcerer = React.memo(({ scope: scopeId } ( newSelectedDataView: string, newSelectedPatterns: string[], - shouldValidateSelectedPatterns?: boolean + shouldValidateSelectedPatterns?: boolean, + // Overrides data view entirely with a specified one. Only works with shouldValidateSelectedPatterns set to false. + dataView?: DataViewSpec ) => { dispatch( sourcererActions.setSelectedDataView({ @@ -238,6 +231,7 @@ export const Sourcerer = React.memo(({ scope: scopeId } selectedDataViewId: newSelectedDataView, selectedPatterns: newSelectedPatterns, shouldValidateSelectedPatterns, + dataView, }) ); @@ -296,9 +290,13 @@ export const Sourcerer = React.memo(({ scope: scopeId } }, [resetDataSources]); const handleApplyFallbackDataView = useCallback( - (newSelectedDataViewId: string, patterns: string[], shouldValidate?: boolean) => { - dispatchChangeDataView(newSelectedDataViewId, patterns, false); - setDataViewId(newSelectedDataViewId); + (dataView: DataViewSpec) => { + if (!dataView.id) { + return; + } + + dispatchChangeDataView(dataView.id, dataView.title?.split(',') || [], false, dataView); + setDataViewId(dataView.id); }, [dispatchChangeDataView] ); @@ -318,7 +316,8 @@ export const Sourcerer = React.memo(({ scope: scopeId } enableFallback: // NOTE: there are cases where sourcerer does not know the dataViewId to display and only knows required patterns. // In that case, we enable the fallback dataview creation that will try to create adhoc data view based on the patterns & will select it. - (dataViewId === null && isModified === 'deprecated') || isModified === 'missingPatterns', + (!loading && dataViewId === null && isModified === 'deprecated') || + isModified === 'missingPatterns', missingPatterns, onResolveErrorManually: handleDataViewFallbackError, }); @@ -352,11 +351,6 @@ export const Sourcerer = React.memo(({ scope: scopeId } selectedPatterns={selectedPatterns} signalIndexName={signalIndexName} handleClosePopOver={handleClosePopOver} - selectedDataViewId={selectedDataViewId} - sourcererMissingPatterns={sourcererMissingPatterns} - onUpdateDetectionAlertsChecked={onUpdateDetectionAlertsChecked} - setMissingPatterns={setMissingPatterns} - setDataViewId={setDataViewId} scopeId={scopeId} > diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_data_view_fallback.ts b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_data_view_fallback.ts index b36732b8e866b..5fdf64a673b49 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_data_view_fallback.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_data_view_fallback.ts @@ -6,6 +6,7 @@ */ import { useEffect, useMemo } from 'react'; +import type { DataViewSpec } from '@kbn/data-views-plugin/common'; import { useCreateAdhocDataView } from './use_create_adhoc_data_view'; interface UseDataViewFallbackConfig { @@ -27,11 +28,7 @@ interface UseDataViewFallbackConfig { /** * Calls external function on dataview change */ - onApplyFallbackDataView: ( - newSelectedDataView: string, - newSelectedPatterns: string[], - shouldValidateSelectedPatterns?: boolean - ) => void; + onApplyFallbackDataView: (dataView: DataViewSpec) => void; } export const useDataViewFallback = ({ @@ -70,9 +67,7 @@ export const useDataViewFallback = ({ return; } - const patterns = adhocDataView.getIndexPattern().split(','); - - onApplyFallbackDataView(adhocDataView.id, patterns, false); + onApplyFallbackDataView(adhocDataView.toSpec()); })(); return () => { diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/index.tsx index 2ad961fac3c42..06148b0151ab7 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/index.tsx @@ -5,15 +5,13 @@ * 2.0. */ -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useCallback, useMemo } from 'react'; import { useSelector } from 'react-redux'; -import type { FieldSpec } from '@kbn/data-plugin/common'; import { sourcererSelectors } from '../store'; -import type { SelectedDataView, SourcererDataView, RunTimeMappings } from '../store/model'; +import type { SelectedDataView } from '../store/model'; import { SourcererScopeName } from '../store/model'; import { checkIfIndicesExist } from '../store/helpers'; import { getDataViewStateFromIndexFields } from '../../common/containers/source/use_data_view'; -import { useFetchIndex } from '../../common/containers/source'; import type { State } from '../../common/store/types'; import { sortWithExcludesAtEnd } from '../../../common/utils/sourcerer'; @@ -35,56 +33,22 @@ export const useSourcererDataView = ( const scopeSelectedPatterns = useSelector((state: State) => { return sourcererSelectors.sourcererScopeSelectedPatterns(state, scopeId); }); - const missingPatterns = useSelector((state: State) => { - return sourcererSelectors.sourcererScopeMissingPatterns(state, scopeId); - }); const selectedPatterns = useMemo( () => sortWithExcludesAtEnd(scopeSelectedPatterns), [scopeSelectedPatterns] ); - const [legacyPatterns, setLegacyPatterns] = useState([]); - - const [indexPatternsLoading, fetchIndexReturn] = useFetchIndex(legacyPatterns); - - const legacyDataView: Omit & { id: string | null } = useMemo( - () => ({ - ...fetchIndexReturn, - dataView: fetchIndexReturn.dataView, - runtimeMappings: (fetchIndexReturn.dataView?.runtimeFieldMap as RunTimeMappings) ?? {}, - title: fetchIndexReturn.dataView?.title ?? '', - id: fetchIndexReturn.dataView?.id ?? null, - loading: indexPatternsLoading, - patternList: fetchIndexReturn.indexes, - indexFields: fetchIndexReturn.indexPatterns.fields as FieldSpec[], - fields: fetchIndexReturn.dataView?.fields, - }), - [fetchIndexReturn, indexPatternsLoading] - ); - - useEffect(() => { - if (selectedDataView == null || missingPatterns.length > 0) { - // old way of fetching indices, legacy timeline - setLegacyPatterns(selectedPatterns); - } else { - setLegacyPatterns([]); - } - }, [missingPatterns, selectedDataView, selectedPatterns]); - const sourcererDataView = useMemo(() => { - const _dv = - selectedDataView == null || missingPatterns.length > 0 ? legacyDataView : selectedDataView; - // Make sure the title is up to date, so that the correct index patterns are used everywhere return { - ..._dv, + ...selectedDataView, dataView: { - ..._dv.dataView, + ...selectedDataView?.dataView, title: selectedPatterns.join(','), - name: selectedPatterns.join(','), + name: selectedDataView?.dataView?.name ?? selectedPatterns.join(','), }, }; - }, [legacyDataView, missingPatterns.length, selectedDataView, selectedPatterns]); + }, [selectedDataView, selectedPatterns]); const indicesExist = useMemo(() => { if (loading || sourcererDataView.loading) { @@ -93,7 +57,7 @@ export const useSourcererDataView = ( return checkIfIndicesExist({ scopeId, signalIndexName, - patternList: sourcererDataView.patternList, + patternList: sourcererDataView.patternList ?? [], isDefaultDataViewSelected: sourcererDataView.id === defaultDataView.id, }); } @@ -109,7 +73,7 @@ export const useSourcererDataView = ( const browserFields = useCallback(() => { const { browserFields: dataViewBrowserFields } = getDataViewStateFromIndexFields( - sourcererDataView.patternList.join(','), + sourcererDataView?.patternList?.join(',') || '', sourcererDataView.fields ); return dataViewBrowserFields; @@ -118,9 +82,9 @@ export const useSourcererDataView = ( return useMemo( () => ({ browserFields: browserFields(), - dataViewId: sourcererDataView.id, + dataViewId: sourcererDataView.id ?? null, indicesExist, - loading: loading || sourcererDataView.loading, + loading: loading || !!sourcererDataView?.loading, // selected patterns in DATA_VIEW including filter selectedPatterns, sourcererDataView: sourcererDataView.dataView, diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/store/actions.ts b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/store/actions.ts index 54aa7dfa2fef8..fb6b0a6fdbe74 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/store/actions.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/store/actions.ts @@ -7,6 +7,7 @@ import actionCreatorFactory from 'typescript-fsa'; +import type { DataViewSpec } from '@kbn/data-views-plugin/common'; import type { SelectedDataView, SourcererDataView, SourcererScopeName } from './model'; import type { SecurityDataView } from '../containers/create_sourcerer_data_view'; @@ -35,5 +36,6 @@ export interface SelectedDataViewPayload { selectedDataViewId: SelectedDataView['dataViewId']; selectedPatterns: SelectedDataView['selectedPatterns']; shouldValidateSelectedPatterns?: boolean; + dataView?: DataViewSpec; } export const setSelectedDataView = actionCreator('SET_SELECTED_DATA_VIEW'); diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/store/helpers.ts b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/store/helpers.ts index 8b51de411ff01..da8c4195e9f30 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/store/helpers.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/store/helpers.ts @@ -52,7 +52,7 @@ export const validateSelectedPatterns = ( payload: SelectedDataViewPayload, shouldValidateSelectedPatterns: boolean ): Partial => { - const { id, ...rest } = payload; + const { id, dataView: dataViewOverride, ...rest } = payload; const dataView = state.kibanaDataViews.find((p) => p.id === rest.selectedDataViewId); // dedupe because these could come from a silly url or pre 8.0 timeline const dedupePatterns = ensurePatternFormat(rest.selectedPatterns); @@ -90,11 +90,19 @@ export const validateSelectedPatterns = ( const signalIndexName = state.signalIndexName; selectedPatterns = getPatternListFromScope(id, selectedPatterns, signalIndexName); + let selectedDataViewId = dataView?.id ?? null; + + if (dataViewOverride) { + selectedPatterns = payload.selectedPatterns; + missingPatterns = []; + selectedDataViewId = String(dataViewOverride.id); + } + return { [id]: { ...state.sourcererScopes[id], ...rest, - selectedDataViewId: dataView?.id ?? null, + selectedDataViewId, selectedPatterns, missingPatterns, // if in timeline, allow for empty in case pattern was deleted From 22413b9426e8dbb9ab9c66a5494b6ae9817e4bc9 Mon Sep 17 00:00:00 2001 From: lgestc Date: Fri, 7 Feb 2025 12:13:40 +0100 Subject: [PATCH 26/32] --wip-- [skip ci] --- .../kbn-unified-field-list/src/hooks/use_grouped_fields.ts | 3 +++ .../sourcerer/components/use_create_adhoc_data_view.tsx | 4 ++-- .../security_solution/public/sourcerer/containers/index.tsx | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/platform/packages/shared/kbn-unified-field-list/src/hooks/use_grouped_fields.ts b/src/platform/packages/shared/kbn-unified-field-list/src/hooks/use_grouped_fields.ts index f0d7d63dc3cd5..b783a66e6a96d 100644 --- a/src/platform/packages/shared/kbn-unified-field-list/src/hooks/use_grouped_fields.ts +++ b/src/platform/packages/shared/kbn-unified-field-list/src/hooks/use_grouped_fields.ts @@ -85,6 +85,7 @@ export function useGroupedFields({ const onFilterFieldList = fieldListFilters.onFilterField; const [dataView, setDataView] = useState(null); + const isAffectedByTimeFilter = Boolean(dataView?.timeFieldName); const fieldsExistenceInfoUnavailable: boolean = dataViewId ? fieldsExistenceReader.isFieldsExistenceInfoUnavailable(dataViewId) @@ -93,6 +94,8 @@ export function useGroupedFields({ ? fieldsExistenceReader.hasFieldData : hasFieldDataByDefault; + console.log('useGroupedFields', dataViewId, dataView, fieldsExistenceInfoUnavailable); + useEffect(() => { const getDataView = async () => { if (dataViewId) { diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_create_adhoc_data_view.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_create_adhoc_data_view.tsx index 7b76b30a68477..4cd5e78ff3d44 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_create_adhoc_data_view.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/use_create_adhoc_data_view.tsx @@ -31,10 +31,10 @@ export const useCreateAdhocDataView = ( const { addError } = useAppToasts(); const createAdhocDataView = useCallback( - async (missingPatterns: string[]): Promise => { + async (patterns: string[]): Promise => { const createDataView = async (): Promise => { const defaultPatterns = uiSettings.get(DEFAULT_INDEX_KEY); - const combinedPatterns = [...defaultPatterns, ...missingPatterns]; + const combinedPatterns = [...defaultPatterns, ...patterns]; const validatedPatterns = ensurePatternFormat(combinedPatterns); const patternsString = validatedPatterns.join(','); const adHocDataView = await dataViews.create({ diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/index.tsx index 06148b0151ab7..f25ba894a9e44 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/index.tsx @@ -46,9 +46,10 @@ export const useSourcererDataView = ( ...selectedDataView?.dataView, title: selectedPatterns.join(','), name: selectedDataView?.dataView?.name ?? selectedPatterns.join(','), + id: selectedDataViewId ?? undefined, }, }; - }, [selectedDataView, selectedPatterns]); + }, [selectedDataView, selectedDataViewId, selectedPatterns]); const indicesExist = useMemo(() => { if (loading || sourcererDataView.loading) { From 33dfaf0b89967793fccbd744419f1c8b8258b26d Mon Sep 17 00:00:00 2001 From: lgestc Date: Fri, 7 Feb 2025 12:37:45 +0100 Subject: [PATCH 27/32] --wip-- [skip ci] --- .../kbn-unified-field-list/src/hooks/use_grouped_fields.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/platform/packages/shared/kbn-unified-field-list/src/hooks/use_grouped_fields.ts b/src/platform/packages/shared/kbn-unified-field-list/src/hooks/use_grouped_fields.ts index b783a66e6a96d..0b2630c703f76 100644 --- a/src/platform/packages/shared/kbn-unified-field-list/src/hooks/use_grouped_fields.ts +++ b/src/platform/packages/shared/kbn-unified-field-list/src/hooks/use_grouped_fields.ts @@ -94,8 +94,6 @@ export function useGroupedFields({ ? fieldsExistenceReader.hasFieldData : hasFieldDataByDefault; - console.log('useGroupedFields', dataViewId, dataView, fieldsExistenceInfoUnavailable); - useEffect(() => { const getDataView = async () => { if (dataViewId) { From b857c2be94fab27f425b94b547176b0fd13a3a70 Mon Sep 17 00:00:00 2001 From: lgestc Date: Fri, 7 Feb 2025 13:44:32 +0100 Subject: [PATCH 28/32] --wip-- [skip ci] --- .../public/sourcerer/containers/index.tsx | 13 ++++++++++++- .../public/sourcerer/store/helpers.ts | 1 + .../public/sourcerer/store/model.ts | 2 ++ .../public/sourcerer/store/selectors.ts | 10 ++++++++++ 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/index.tsx index f25ba894a9e44..889d74710ae11 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/index.tsx @@ -34,6 +34,11 @@ export const useSourcererDataView = ( return sourcererSelectors.sourcererScopeSelectedPatterns(state, scopeId); }); + // NOTE: currently this is only defined when an adhoc DV is created + const optionalSelectedDataViewSpec = useSelector((state: State) => { + return sourcererSelectors.sourcererScopeSelectedDataViewSpec(state, scopeId); + }); + const selectedPatterns = useMemo( () => sortWithExcludesAtEnd(scopeSelectedPatterns), [scopeSelectedPatterns] @@ -47,9 +52,15 @@ export const useSourcererDataView = ( title: selectedPatterns.join(','), name: selectedDataView?.dataView?.name ?? selectedPatterns.join(','), id: selectedDataViewId ?? undefined, + fields: optionalSelectedDataViewSpec?.fields, }, }; - }, [selectedDataView, selectedDataViewId, selectedPatterns]); + }, [ + optionalSelectedDataViewSpec?.fields, + selectedDataView, + selectedDataViewId, + selectedPatterns, + ]); const indicesExist = useMemo(() => { if (loading || sourcererDataView.loading) { diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/store/helpers.ts b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/store/helpers.ts index da8c4195e9f30..c958ca23b2be7 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/store/helpers.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/store/helpers.ts @@ -118,6 +118,7 @@ export const validateSelectedPatterns = ( } : {}), loading: false, + dataViewSpec: dataViewOverride, }, }; }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/store/model.ts b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/store/model.ts index 807c74a9c3f8f..28317e6a655f5 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/store/model.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/store/model.ts @@ -35,6 +35,8 @@ export interface SourcererScope { * selectedDataViewId === null OR defaultDataView.id * saved timeline has pattern that is not in the default */ missingPatterns: string[]; + + dataViewSpec?: DataViewSpec; } export type SourcererScopeById = Record; diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/store/selectors.ts b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/store/selectors.ts index 28e59276b5777..0a95ec8cdec52 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/store/selectors.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/store/selectors.ts @@ -45,6 +45,16 @@ export const sourcererScopeSelectedDataViewId = createSelector( } ); +export const sourcererScopeSelectedDataViewSpec = createSelector( + sourcererScope, + (scope) => scope.dataViewSpec, + { + memoizeOptions: { + maxSize: SOURCERER_SCOPE_MAX_SIZE, + }, + } +); + export const sourcererScopeSelectedPatterns = createSelector( sourcererScope, (scope) => scope.selectedPatterns, From 17aa6431f3656306101a18e5c9653515381f9d1b Mon Sep 17 00:00:00 2001 From: lgestc Date: Fri, 7 Feb 2025 13:48:29 +0100 Subject: [PATCH 29/32] fix missing fields when regular dv is present --- .../security_solution/public/sourcerer/containers/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/index.tsx index 889d74710ae11..a0c5a0b87865a 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/index.tsx @@ -52,7 +52,7 @@ export const useSourcererDataView = ( title: selectedPatterns.join(','), name: selectedDataView?.dataView?.name ?? selectedPatterns.join(','), id: selectedDataViewId ?? undefined, - fields: optionalSelectedDataViewSpec?.fields, + fields: optionalSelectedDataViewSpec?.fields ?? selectedDataView?.dataView?.fields, }, }; }, [ From 22a727f34f9feeccef9975d5df2d15eacee39f83 Mon Sep 17 00:00:00 2001 From: lgestc Date: Fri, 7 Feb 2025 13:51:47 +0100 Subject: [PATCH 30/32] document new scope field --- .../plugins/security_solution/public/sourcerer/store/model.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/store/model.ts b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/store/model.ts index 28317e6a655f5..703c5d971a242 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/store/model.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/store/model.ts @@ -36,6 +36,10 @@ export interface SourcererScope { * saved timeline has pattern that is not in the default */ missingPatterns: string[]; + /** + * The full dataview spec that might be set, depending on the data view loading strategy. + * Currently only available in the legacy compatibility flow when adhoc dv is created. + */ dataViewSpec?: DataViewSpec; } From 5418f0fb89ac870ecdcae231d5c9023a7c8bd5d0 Mon Sep 17 00:00:00 2001 From: lgestc Date: Fri, 7 Feb 2025 14:00:05 +0100 Subject: [PATCH 31/32] remove whitespace --- .../kbn-unified-field-list/src/hooks/use_grouped_fields.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/platform/packages/shared/kbn-unified-field-list/src/hooks/use_grouped_fields.ts b/src/platform/packages/shared/kbn-unified-field-list/src/hooks/use_grouped_fields.ts index 0b2630c703f76..f0d7d63dc3cd5 100644 --- a/src/platform/packages/shared/kbn-unified-field-list/src/hooks/use_grouped_fields.ts +++ b/src/platform/packages/shared/kbn-unified-field-list/src/hooks/use_grouped_fields.ts @@ -85,7 +85,6 @@ export function useGroupedFields({ const onFilterFieldList = fieldListFilters.onFilterField; const [dataView, setDataView] = useState(null); - const isAffectedByTimeFilter = Boolean(dataView?.timeFieldName); const fieldsExistenceInfoUnavailable: boolean = dataViewId ? fieldsExistenceReader.isFieldsExistenceInfoUnavailable(dataViewId) From eb8694fbc2b2649b0a3ae546d8db5e6890e9a9e9 Mon Sep 17 00:00:00 2001 From: lgestc Date: Mon, 10 Feb 2025 13:48:46 +0100 Subject: [PATCH 32/32] change fallback enable logic --- .../security_solution/public/sourcerer/components/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx index 85b7edf9c45de..a5e2f6b29d3bd 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/components/index.tsx @@ -316,8 +316,8 @@ export const Sourcerer = React.memo(({ scope: scopeId } enableFallback: // NOTE: there are cases where sourcerer does not know the dataViewId to display and only knows required patterns. // In that case, we enable the fallback dataview creation that will try to create adhoc data view based on the patterns & will select it. - (!loading && dataViewId === null && isModified === 'deprecated') || - isModified === 'missingPatterns', + !loading && + ((dataViewId === null && isModified === 'deprecated') || isModified === 'missingPatterns'), missingPatterns, onResolveErrorManually: handleDataViewFallbackError, });