"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.useSourcererDataView = exports.useInitSourcerer = exports.sourcererPaths = exports.showSourcererByPath = exports.getScopeFromPath = void 0; var _react = require("react"); var _reactRedux = require("react-redux"); var _i18n = require("@kbn/i18n"); var _reactRouterDom = require("react-router-dom"); var _sourcerer = require("../../store/sourcerer"); var _model = require("../../store/sourcerer/model"); var _user_info = require("../../../detections/components/user_info"); var _timeline = require("../../../timelines/store/timeline"); var _constants = require("../../../../common/constants"); var _types = require("../../../../common/types"); var _use_selector = require("../../hooks/use_selector"); var _helpers = require("../../store/sourcerer/helpers"); var _use_app_toasts = require("../../hooks/use_app_toasts"); var _create_sourcerer_data_view = require("./create_sourcerer_data_view"); var _use_data_view = require("../source/use_data_view"); var _source = require("../source"); var _global_query_string = require("../../utils/global_query_string"); var _use_url_state = require("../../hooks/use_url_state"); var _sourcerer2 = require("../../../../common/utils/sourcerer"); var _kibana = require("../../lib/kibana"); /* * 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. */ const useInitSourcerer = (scopeId = _model.SourcererScopeName.default) => { const dispatch = (0, _reactRedux.useDispatch)(); const { data: { dataViews } } = (0, _kibana.useKibana)().services; const abortCtrl = (0, _react.useRef)(new AbortController()); const initialTimelineSourcerer = (0, _react.useRef)(true); const initialDetectionSourcerer = (0, _react.useRef)(true); const { loading: loadingSignalIndex, isSignalIndexExists, signalIndexName } = (0, _user_info.useUserInfo)(); const updateUrlParam = (0, _global_query_string.useUpdateUrlParam)(_use_url_state.URL_PARAM_KEY.sourcerer); const getDataViewsSelector = (0, _react.useMemo)(() => _sourcerer.sourcererSelectors.getSourcererDataViewsSelector(), []); const { defaultDataView, signalIndexName: signalIndexNameSourcerer } = (0, _use_selector.useDeepEqualSelector)(state => getDataViewsSelector(state)); const { addError, addWarning } = (0, _use_app_toasts.useAppToasts)(); (0, _react.useEffect)(() => { if (defaultDataView.error != null) { addWarning({ title: _i18n.i18n.translate('xpack.securitySolution.sourcerer.permissions.title', { defaultMessage: 'Write role required to generate data' }), text: _i18n.i18n.translate('xpack.securitySolution.sourcerer.permissions.toastMessage', { defaultMessage: 'Users with write permission need to access the Elastic Security app to initialize the app source data.' }) }); } }, [addWarning, defaultDataView.error]); const getTimelineSelector = (0, _react.useMemo)(() => _timeline.timelineSelectors.getTimelineByIdSelector(), []); const activeTimeline = (0, _use_selector.useDeepEqualSelector)(state => getTimelineSelector(state, _types.TimelineId.active)); const sourcererScopeSelector = (0, _react.useMemo)(() => _sourcerer.sourcererSelectors.getSourcererScopeSelector(), []); const { sourcererScope: { selectedDataViewId: scopeDataViewId, selectedPatterns, missingPatterns } } = (0, _use_selector.useDeepEqualSelector)(state => sourcererScopeSelector(state, scopeId)); const { selectedDataView: timelineSelectedDataView, sourcererScope: { selectedDataViewId: timelineDataViewId, selectedPatterns: timelineSelectedPatterns, missingPatterns: timelineMissingPatterns } } = (0, _use_selector.useDeepEqualSelector)(state => sourcererScopeSelector(state, _model.SourcererScopeName.timeline)); const { indexFieldsSearch } = (0, _use_data_view.useDataView)(); const onInitializeUrlParam = (0, _react.useCallback)(initialState => { // Initialize the store with value from UrlParam. if (initialState != null) { Object.keys(initialState).forEach(scope => { if (!(scope === _model.SourcererScopeName.default && scopeId === _model.SourcererScopeName.detections)) { var _initialState$scope$i, _initialState$scope, _initialState$scope$s, _initialState$scope2; dispatch(_sourcerer.sourcererActions.setSelectedDataView({ id: scope, selectedDataViewId: (_initialState$scope$i = (_initialState$scope = initialState[scope]) === null || _initialState$scope === void 0 ? void 0 : _initialState$scope.id) !== null && _initialState$scope$i !== void 0 ? _initialState$scope$i : null, selectedPatterns: (_initialState$scope$s = (_initialState$scope2 = initialState[scope]) === null || _initialState$scope2 === void 0 ? void 0 : _initialState$scope2.selectedPatterns) !== null && _initialState$scope$s !== void 0 ? _initialState$scope$s : [] })); } }); } else { // Initialize the UrlParam with values from the store. // It isn't strictly necessary but I am keeping it for compatibility with the previous implementation. if (scopeDataViewId) { updateUrlParam({ [_model.SourcererScopeName.default]: { id: scopeDataViewId, selectedPatterns } }); } } }, [dispatch, scopeDataViewId, scopeId, selectedPatterns, updateUrlParam]); (0, _global_query_string.useInitializeUrlParam)(_use_url_state.URL_PARAM_KEY.sourcerer, onInitializeUrlParam); /* * Note for future engineer: * we changed the logic to not fetch all the index fields for every data view on the loading of the app * because user can have a lot of them and it can slow down the loading of the app * and maybe blow up the memory of the browser. We decided to load this data view on demand, * we know that will only have to load this dataview on default and timeline scope. * We will use two conditions to see if we need to fetch and initialize the dataview selected. * First, we will make sure that we did not already fetch them by using `searchedIds` * and then we will init them if selectedPatterns and missingPatterns are empty. */ const searchedIds = (0, _react.useRef)([]); (0, _react.useEffect)(() => { const activeDataViewIds = [...new Set([scopeDataViewId, timelineDataViewId])]; activeDataViewIds.forEach((id, i) => { if (id != null && id.length > 0 && !searchedIds.current.includes(id)) { searchedIds.current = [...searchedIds.current, id]; const currentScope = i === 0 ? _model.SourcererScopeName.default : _model.SourcererScopeName.timeline; const needToBeInit = id === scopeDataViewId ? selectedPatterns.length === 0 && missingPatterns.length === 0 : timelineDataViewId === id ? timelineMissingPatterns.length === 0 && (timelineSelectedDataView === null || timelineSelectedDataView === void 0 ? void 0 : timelineSelectedDataView.patternList.length) === 0 : false; indexFieldsSearch({ dataViewId: id, scopeId: currentScope, needToBeInit, ...(needToBeInit && currentScope === _model.SourcererScopeName.timeline ? { skipScopeUpdate: timelineSelectedPatterns.length > 0 } : {}) }); } }); }, [indexFieldsSearch, missingPatterns.length, scopeDataViewId, selectedPatterns.length, timelineDataViewId, timelineMissingPatterns.length, timelineSelectedDataView, timelineSelectedPatterns.length]); // Related to timeline (0, _react.useEffect)(() => { if (!loadingSignalIndex && signalIndexName != null && signalIndexNameSourcerer == null && (activeTimeline == null || activeTimeline.savedObjectId == null) && initialTimelineSourcerer.current && defaultDataView.id.length > 0) { initialTimelineSourcerer.current = false; dispatch(_sourcerer.sourcererActions.setSelectedDataView({ id: _model.SourcererScopeName.timeline, selectedDataViewId: defaultDataView.id, selectedPatterns: (0, _helpers.getScopePatternListSelection)(defaultDataView, _model.SourcererScopeName.timeline, signalIndexName, true) })); } else if (signalIndexNameSourcerer != null && (activeTimeline == null || activeTimeline.savedObjectId == null) && initialTimelineSourcerer.current && defaultDataView.id.length > 0) { initialTimelineSourcerer.current = false; dispatch(_sourcerer.sourcererActions.setSelectedDataView({ id: _model.SourcererScopeName.timeline, selectedDataViewId: defaultDataView.id, selectedPatterns: (0, _helpers.getScopePatternListSelection)(defaultDataView, _model.SourcererScopeName.timeline, signalIndexNameSourcerer, true) })); } }, [activeTimeline, defaultDataView, dispatch, loadingSignalIndex, signalIndexName, signalIndexNameSourcerer]); const { dataViewId } = useSourcererDataView(scopeId); const updateSourcererDataView = (0, _react.useCallback)(newSignalsIndex => { const asyncSearch = async newPatternList => { abortCtrl.current = new AbortController(); dispatch(_sourcerer.sourcererActions.setSourcererScopeLoading({ loading: true })); try { const response = await (0, _create_sourcerer_data_view.createSourcererDataView)({ body: { patternList: newPatternList }, signal: abortCtrl.current.signal, dataViewService: dataViews, dataViewId }); if (response !== null && response !== void 0 && response.defaultDataView.patternList.includes(newSignalsIndex)) { // first time signals is defined and validated in the sourcerer // redo indexFieldsSearch indexFieldsSearch({ dataViewId: response.defaultDataView.id }); dispatch(_sourcerer.sourcererActions.setSourcererDataViews(response)); } dispatch(_sourcerer.sourcererActions.setSourcererScopeLoading({ loading: false })); } catch (err) { if (err.name === 'AbortError') { // the fetch was canceled, we don't need to do anything about it } else { addError(err, { title: _i18n.i18n.translate('xpack.securitySolution.sourcerer.error.title', { defaultMessage: 'Error updating Security Data View' }), toastMessage: _i18n.i18n.translate('xpack.securitySolution.sourcerer.error.toastMessage', { defaultMessage: 'Refresh the page' }) }); } dispatch(_sourcerer.sourcererActions.setSourcererScopeLoading({ loading: false })); } }; if (defaultDataView.title.indexOf(newSignalsIndex) === -1) { abortCtrl.current.abort(); asyncSearch([...defaultDataView.title.split(','), newSignalsIndex]); } }, [defaultDataView.title, dispatch, dataViews, dataViewId, indexFieldsSearch, addError]); const onSignalIndexUpdated = (0, _react.useCallback)(() => { if (!loadingSignalIndex && signalIndexName != null && signalIndexNameSourcerer == null && defaultDataView.id.length > 0) { updateSourcererDataView(signalIndexName); dispatch(_sourcerer.sourcererActions.setSignalIndexName({ signalIndexName })); } }, [defaultDataView.id.length, dispatch, loadingSignalIndex, signalIndexName, signalIndexNameSourcerer, updateSourcererDataView]); (0, _react.useEffect)(() => { onSignalIndexUpdated(); // because we only want onSignalIndexUpdated to run when signalIndexName updates, // but we want to know about the updates from the dependencies of onSignalIndexUpdated // eslint-disable-next-line react-hooks/exhaustive-deps }, [signalIndexName]); // Related to the detection page (0, _react.useEffect)(() => { if (scopeId === _model.SourcererScopeName.detections && isSignalIndexExists && signalIndexName != null && initialDetectionSourcerer.current && defaultDataView.id.length > 0) { initialDetectionSourcerer.current = false; dispatch(_sourcerer.sourcererActions.setSelectedDataView({ id: _model.SourcererScopeName.detections, selectedDataViewId: defaultDataView.id, selectedPatterns: (0, _helpers.getScopePatternListSelection)(defaultDataView, _model.SourcererScopeName.detections, signalIndexName, true) })); } else if (scopeId === _model.SourcererScopeName.detections && signalIndexNameSourcerer != null && initialTimelineSourcerer.current && defaultDataView.id.length > 0) { initialDetectionSourcerer.current = false; _sourcerer.sourcererActions.setSelectedDataView({ id: _model.SourcererScopeName.detections, selectedDataViewId: defaultDataView.id, selectedPatterns: (0, _helpers.getScopePatternListSelection)(defaultDataView, _model.SourcererScopeName.detections, signalIndexNameSourcerer, true) }); } }, [defaultDataView, dispatch, isSignalIndexExists, scopeId, signalIndexName, signalIndexNameSourcerer]); }; exports.useInitSourcerer = useInitSourcerer; const useSourcererDataView = (scopeId = _model.SourcererScopeName.default) => { const { getDataViewsSelector, getSourcererDataViewSelector, getScopeSelector } = (0, _react.useMemo)(() => ({ getDataViewsSelector: _sourcerer.sourcererSelectors.getSourcererDataViewsSelector(), getSourcererDataViewSelector: _sourcerer.sourcererSelectors.sourcererDataViewSelector(), getScopeSelector: _sourcerer.sourcererSelectors.scopeIdSelector() }), []); const { defaultDataView, signalIndexName, selectedDataView, sourcererScope: { missingPatterns, selectedPatterns: scopeSelectedPatterns, loading } } = (0, _use_selector.useDeepEqualSelector)(state => { const sourcererScope = getScopeSelector(state, scopeId); return { ...getDataViewsSelector(state), selectedDataView: getSourcererDataViewSelector(state, sourcererScope.selectedDataViewId), sourcererScope }; }); const selectedPatterns = (0, _react.useMemo)(() => (0, _sourcerer2.sortWithExcludesAtEnd)(scopeSelectedPatterns), [scopeSelectedPatterns]); const [legacyPatterns, setLegacyPatterns] = (0, _react.useState)([]); const [indexPatternsLoading, fetchIndexReturn] = (0, _source.useFetchIndex)(legacyPatterns); const legacyDataView = (0, _react.useMemo)(() => { var _ref, _fetchIndexReturn$dat, _fetchIndexReturn$dat2, _fetchIndexReturn$dat3, _fetchIndexReturn$dat4, _fetchIndexReturn$dat5, _fetchIndexReturn$dat6; return { ...fetchIndexReturn, dataView: fetchIndexReturn.dataView, runtimeMappings: (_ref = (_fetchIndexReturn$dat = fetchIndexReturn.dataView) === null || _fetchIndexReturn$dat === void 0 ? void 0 : _fetchIndexReturn$dat.runtimeFieldMap) !== null && _ref !== void 0 ? _ref : {}, title: (_fetchIndexReturn$dat2 = (_fetchIndexReturn$dat3 = fetchIndexReturn.dataView) === null || _fetchIndexReturn$dat3 === void 0 ? void 0 : _fetchIndexReturn$dat3.title) !== null && _fetchIndexReturn$dat2 !== void 0 ? _fetchIndexReturn$dat2 : '', id: (_fetchIndexReturn$dat4 = (_fetchIndexReturn$dat5 = fetchIndexReturn.dataView) === null || _fetchIndexReturn$dat5 === void 0 ? void 0 : _fetchIndexReturn$dat5.id) !== null && _fetchIndexReturn$dat4 !== void 0 ? _fetchIndexReturn$dat4 : null, loading: indexPatternsLoading, patternList: fetchIndexReturn.indexes, indexFields: fetchIndexReturn.indexPatterns.fields, fields: (_fetchIndexReturn$dat6 = fetchIndexReturn.dataView) === null || _fetchIndexReturn$dat6 === void 0 ? void 0 : _fetchIndexReturn$dat6.fields }; }, [fetchIndexReturn, indexPatternsLoading]); (0, _react.useEffect)(() => { if (selectedDataView == null || missingPatterns.length > 0) { // old way of fetching indices, legacy timeline setLegacyPatterns(selectedPatterns); } else { setLegacyPatterns([]); } }, [missingPatterns, selectedDataView, selectedPatterns]); const sourcererDataView = (0, _react.useMemo)(() => selectedDataView == null || missingPatterns.length > 0 ? legacyDataView : selectedDataView, [legacyDataView, missingPatterns.length, selectedDataView]); const indicesExist = (0, _react.useMemo)(() => loading || sourcererDataView.loading ? true : (0, _helpers.checkIfIndicesExist)({ scopeId, signalIndexName, patternList: sourcererDataView.patternList, isDefaultDataViewSelected: sourcererDataView.id === defaultDataView.id }), [defaultDataView.id, loading, scopeId, signalIndexName, sourcererDataView.id, sourcererDataView.loading, sourcererDataView.patternList]); const browserFields = (0, _react.useCallback)(() => { const { browserFields: dataViewBrowserFields } = (0, _use_data_view.getDataViewStateFromIndexFields)(sourcererDataView.patternList.join(','), sourcererDataView.fields); return dataViewBrowserFields; }, [sourcererDataView.fields, sourcererDataView.patternList]); return (0, _react.useMemo)(() => ({ browserFields: browserFields(), dataViewId: sourcererDataView.id, indexPattern: { fields: sourcererDataView.indexFields, title: selectedPatterns.join(','), getName: () => selectedPatterns.join(',') }, indicesExist, loading: loading || sourcererDataView.loading, runtimeMappings: sourcererDataView.runtimeMappings, // all active & inactive patterns in DATA_VIEW patternList: sourcererDataView.title.split(','), // selected patterns in DATA_VIEW including filter selectedPatterns, // if we have to do an update to data view, tell us which patterns are active ...(legacyPatterns.length > 0 ? { activePatterns: sourcererDataView.patternList } : {}), sourcererDataView: sourcererDataView.dataView }), [browserFields, sourcererDataView, selectedPatterns, indicesExist, loading, legacyPatterns.length]); }; exports.useSourcererDataView = useSourcererDataView; const detectionsPaths = [_constants.ALERTS_PATH, `${_constants.RULES_PATH}/id/:id`, `${_constants.CASES_PATH}/:detailName`]; const getScopeFromPath = pathname => (0, _reactRouterDom.matchPath)(pathname, { path: detectionsPaths, strict: false }) == null ? _model.SourcererScopeName.default : _model.SourcererScopeName.detections; exports.getScopeFromPath = getScopeFromPath; const sourcererPaths = [_constants.ALERTS_PATH, _constants.DATA_QUALITY_PATH, `${_constants.RULES_PATH}/id/:id`, _constants.HOSTS_PATH, _constants.USERS_PATH, _constants.NETWORK_PATH, _constants.OVERVIEW_PATH]; exports.sourcererPaths = sourcererPaths; const showSourcererByPath = pathname => (0, _reactRouterDom.matchPath)(pathname, { path: sourcererPaths, strict: false }) != null; exports.showSourcererByPath = showSourcererByPath;