[Security Solution] Replaced sourcerer API with the dataView plugin matching indices capabilities (#139671)

Replaces the sourcerer api with the new DataView capability
`matchedIndices` (introduced by the changes
[here](https://github.com/elastic/kibana/pull/139067))

ref: https://github.com/elastic/kibana/issues/142904

Co-authored-by: Devin Hurley <devin.hurley@elastic.co>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Yuliia Naumenko 2023-01-19 12:10:34 -08:00 committed by GitHub
parent e38350f7f9
commit 09c9b480c3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 206 additions and 907 deletions

View file

@ -24,7 +24,7 @@ import {
checkAutoRefreshIsDisabled,
checkAutoRefreshIsEnabled,
} from '../../tasks/alerts_detection_rules';
import { login, visit } from '../../tasks/login';
import { login, visit, visitWithoutDateRange } from '../../tasks/login';
import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../urls/navigation';
import { createCustomRule } from '../../tasks/api_calls/rules';
@ -43,36 +43,36 @@ describe('Alerts detection rules table auto-refresh', () => {
});
it('Auto refreshes rules', () => {
visit(DETECTIONS_RULE_MANAGEMENT_URL);
visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL);
mockGlobalClock();
waitForRulesTableToBeLoaded();
// ensure rules have rendered. As there is no user interaction in this test,
// rules were not rendered before test completes
cy.get(RULE_CHECKBOX).should('have.length', 6);
// mock 1 minute passing to make sure refresh is conducted
// // mock 1 minute passing to make sure refresh is conducted
mockGlobalClock();
checkAutoRefresh(DEFAULT_RULE_REFRESH_INTERVAL_VALUE, 'be.visible');
cy.contains(REFRESH_RULES_STATUS, 'Updated now');
});
it('should prevent table from rules refetch if any rule selected', () => {
visit(DETECTIONS_RULE_MANAGEMENT_URL);
visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL);
mockGlobalClock();
waitForRulesTableToBeLoaded();
selectNumberOfRules(1);
// mock 1 minute passing to make sure refresh is not conducted
mockGlobalClock();
checkAutoRefresh(DEFAULT_RULE_REFRESH_INTERVAL_VALUE, 'not.exist');
// ensure rule is still selected
cy.get(RULE_CHECKBOX).first().should('be.checked');
cy.contains(REFRESH_RULES_STATUS, 'Updated 1 minute ago');
cy.get(REFRESH_RULES_STATUS).should('have.not.text', 'Updated now');
});
it('should disable auto refresh when any rule selected and enable it after rules unselected', () => {

View file

@ -32,16 +32,7 @@ jest.mock('react-router-dom', () => {
return { ...actual, useLocation: jest.fn().mockReturnValue({ pathname: '' }) };
});
jest.mock('../../../common/lib/kibana', () => {
const originalModule = jest.requireActual('../../../common/lib/kibana');
return {
...originalModule,
useKibana: jest.fn().mockReturnValue({
services: { theme: { theme$: {} }, http: { basePath: { prepend: jest.fn((href) => href) } } },
}),
useUiSetting$: jest.fn().mockReturnValue([]),
};
});
jest.mock('../../../common/lib/kibana');
jest.mock('../../../common/containers/source', () => ({
useFetchIndex: () => [false, { indicesExist: true, indexPatterns: mockIndexPattern }],

View file

@ -9,12 +9,13 @@ import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react'
import type { EuiComboBoxOptionOption, EuiSuperSelectOption } from '@elastic/eui';
import { useDispatch } from 'react-redux';
import { getSourcererDataView } from '../../containers/sourcerer/api';
import { getScopePatternListSelection } from '../../store/sourcerer/helpers';
import { sourcererActions, sourcererModel } from '../../store/sourcerer';
import { getDataViewSelectOptions, getPatternListWithoutSignals } from './helpers';
import { SourcererScopeName } from '../../store/sourcerer/model';
import { sortWithExcludesAtEnd } from '../../../../common/utils/sourcerer';
import { useKibana } from '../../lib/kibana';
import { getSourcererDataView } from '../../containers/sourcerer/get_sourcerer_data_view';
interface UsePickIndexPatternsProps {
dataViewId: string | null;
@ -61,6 +62,9 @@ export const usePickIndexPatterns = ({
signalIndexName,
}: UsePickIndexPatternsProps): UsePickIndexPatterns => {
const dispatch = useDispatch();
const {
data: { dataViews },
} = useKibana().services;
const isHookAlive = useRef(true);
const [loadingIndexPatterns, setLoadingIndexPatterns] = useState(false);
const alertsOptions = useMemo(
@ -191,15 +195,12 @@ export const usePickIndexPatterns = ({
try {
setLoadingIndexPatterns(true);
setSelectedOptions([]);
// TODO We will need to figure out how to pass an abortController, but as right now this hook is
// constantly getting destroy and re-init
const pickedDataViewData = await getSourcererDataView(newSelectedDataViewId);
const dataView = await getSourcererDataView(newSelectedDataViewId, dataViews);
if (isHookAlive.current) {
dispatch(sourcererActions.setDataView(pickedDataViewData));
dispatch(sourcererActions.setDataView(dataView));
setSelectedOptions(
isOnlyDetectionAlerts
? alertsOptions
: patternListToOptions(pickedDataViewData.patternList)
isOnlyDetectionAlerts ? alertsOptions : patternListToOptions(dataView.patternList)
);
}
} catch (err) {
@ -216,6 +217,7 @@ export const usePickIndexPatterns = ({
getDefaultSelectedOptionsByDataView,
isOnlyDetectionAlerts,
kibanaDataViews,
dataViews,
]
);

View file

@ -24,7 +24,7 @@ import { useAppToasts } from '../../hooks/use_app_toasts';
import { sourcererActions } from '../../store/sourcerer';
import * as i18n from './translations';
import { SourcererScopeName } from '../../store/sourcerer/model';
import { getSourcererDataView } from '../sourcerer/api';
import { getSourcererDataView } from '../sourcerer/get_sourcerer_data_view';
import { useTrackHttpRequest } from '../../lib/apm/use_track_http_request';
import { APP_UI_ID } from '../../../../common/constants';
@ -121,11 +121,8 @@ export const useDataView = (): {
const { endTracking } = startTracking({ name: `${APP_UI_ID} indexFieldsSearch` });
if (needToBeInit) {
const dataViewToUpdate = await getSourcererDataView(
dataViewId,
abortCtrl.current[dataViewId].signal
);
dispatch(sourcererActions.setDataView(dataViewToUpdate));
const dataView = await getSourcererDataView(dataViewId, data.dataViews);
dispatch(sourcererActions.setDataView(dataView));
}
return new Promise<void>((resolve) => {
@ -208,7 +205,7 @@ export const useDataView = (): {
}
return asyncSearch();
},
[addError, addWarning, data.search, dispatch, setLoading, startTracking]
[addError, addWarning, data.search, dispatch, setLoading, startTracking, data.dataViews]
);
useEffect(() => {

View file

@ -1,44 +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 { KibanaServices } from '../../lib/kibana';
import { SOURCERER_API_URL } from '../../../../common/constants';
import type { KibanaDataView } from '../../store/sourcerer/model';
export interface GetSourcererDataView {
signal: AbortSignal;
body: {
patternList: string[];
};
}
export interface SecurityDataView {
defaultDataView: KibanaDataView;
kibanaDataViews: KibanaDataView[];
}
export const postSourcererDataView = async ({
body,
signal,
}: GetSourcererDataView): Promise<SecurityDataView> =>
KibanaServices.get().http.fetch(SOURCERER_API_URL, {
method: 'POST',
body: JSON.stringify(body),
signal,
});
export const getSourcererDataView = async (
dataViewId: string,
signal?: AbortSignal
): Promise<KibanaDataView> => {
return KibanaServices.get().http.fetch<KibanaDataView>(SOURCERER_API_URL, {
method: 'GET',
query: { dataViewId },
asSystemRequest: true,
signal,
});
};

View file

@ -0,0 +1,102 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type {
DataViewListItem,
DataViewsContract,
DataView as DataViewType,
} from '@kbn/data-views-plugin/common';
import { transformError } from '@kbn/securitysolution-es-utils';
import { ensurePatternFormat } from '../../../../common/utils/sourcerer';
import type { KibanaDataView } from '../../store/sourcerer/model';
import { DEFAULT_TIME_FIELD } from '../../../../common/constants';
import { getSourcererDataView } from './get_sourcerer_data_view';
export interface GetSourcererDataView {
signal?: AbortSignal;
body: {
patternList: string[];
};
dataViewService: DataViewsContract;
dataViewId: string | null;
}
export interface SecurityDataView {
defaultDataView: KibanaDataView;
kibanaDataViews: KibanaDataView[];
}
export const createSourcererDataView = async ({
body,
dataViewService,
dataViewId,
}: GetSourcererDataView): Promise<SecurityDataView | undefined> => {
if (dataViewId === null) {
return;
}
let allDataViews: DataViewListItem[] = await dataViewService.getIdsWithTitle();
const siemDataViewExist = allDataViews.find((dv) => dv.id === dataViewId);
const { patternList } = body;
const patternListAsTitle = ensurePatternFormat(patternList).join();
let siemDataView: DataViewType;
if (siemDataViewExist === undefined) {
try {
siemDataView = await dataViewService.createAndSave(
{
allowNoIndex: true,
id: dataViewId,
title: patternListAsTitle,
timeFieldName: DEFAULT_TIME_FIELD,
},
// Override property - if a data view exists with the security solution pattern
// delete it and replace it with our data view
true
);
} catch (err) {
const error = transformError(err);
if (err.name === 'DuplicateDataViewError' || error.statusCode === 409) {
siemDataView = await dataViewService.get(dataViewId);
} else {
throw error;
}
}
} else {
const siemDataViewTitle = siemDataViewExist
? ensurePatternFormat(siemDataViewExist.title.split(',')).join()
: '';
siemDataView = await dataViewService.get(dataViewId);
if (patternListAsTitle !== siemDataViewTitle) {
siemDataView.title = patternListAsTitle;
await dataViewService.updateSavedObject(siemDataView);
}
}
if (allDataViews.some((dv) => dv.id === dataViewId)) {
allDataViews = allDataViews.map((v) =>
v.id === dataViewId ? { ...v, title: patternListAsTitle } : v
);
} else if (siemDataView !== null) {
allDataViews.push({ id: siemDataView.id ?? dataViewId, title: siemDataView?.title });
}
const siemSourcererDataView = await getSourcererDataView(dataViewId, dataViewService);
return {
defaultDataView: siemSourcererDataView,
kibanaDataViews: allDataViews.map((dv) =>
dv.id === dataViewId
? siemSourcererDataView
: {
id: dv.id,
patternList: dv.title.split(','),
title: dv.title,
}
),
};
};

View file

@ -0,0 +1,29 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { DataViewsContract } from '@kbn/data-views-plugin/common';
import { ensurePatternFormat } from '../../../../common/utils/sourcerer';
export const getSourcererDataView = async (
dataViewId: string,
dataViewsService: DataViewsContract
) => {
const dataViewData = await dataViewsService.get(dataViewId);
const defaultPatternsList = ensurePatternFormat(dataViewData.getIndexPattern().split(','));
const patternList = defaultPatternsList.reduce((res: string[], pattern) => {
if (dataViewData.matchedIndices.find((q) => q.includes(pattern.replaceAll('*', '')))) {
res.push(pattern);
}
return res;
}, []);
return {
id: dataViewData.id ?? '',
title: dataViewData.getIndexPattern(),
patternList,
};
};

View file

@ -33,10 +33,10 @@ import {
} from '../../mock';
import type { SelectedDataView } from '../../store/sourcerer/model';
import { SourcererScopeName } from '../../store/sourcerer/model';
import { postSourcererDataView } from './api';
import * as source from '../source/use_data_view';
import { sourcererActions } from '../../store/sourcerer';
import { useInitializeUrlParam, useUpdateUrlParam } from '../../utils/global_query_string';
import { createSourcererDataView } from './create_sourcerer_data_view';
const mockRouteSpy: RouteSpyState = {
pageName: SecurityPageName.overview,
@ -49,7 +49,7 @@ const mockDispatch = jest.fn();
const mockUseUserInfo = useUserInfo as jest.Mock;
jest.mock('../../lib/apm/use_track_http_request');
jest.mock('../../../detections/components/user_info');
jest.mock('./api');
jest.mock('./create_sourcerer_data_view');
jest.mock('../../utils/global_query_string');
jest.mock('react-redux', () => {
const original = jest.requireActual('react-redux');
@ -141,7 +141,7 @@ describe('Sourcerer Hooks', () => {
defaultDataView: mockSourcererState.defaultDataView,
kibanaDataViews: [mockSourcererState.defaultDataView],
};
(postSourcererDataView as jest.Mock).mockResolvedValue(mockNewDataViews);
(createSourcererDataView as jest.Mock).mockResolvedValue(mockNewDataViews);
store = createStore(
{

View file

@ -31,17 +31,21 @@ import { TimelineId } from '../../../../common/types';
import { useDeepEqualSelector } from '../../hooks/use_selector';
import { checkIfIndicesExist, getScopePatternListSelection } from '../../store/sourcerer/helpers';
import { useAppToasts } from '../../hooks/use_app_toasts';
import { postSourcererDataView } from './api';
import { createSourcererDataView } from './create_sourcerer_data_view';
import { useDataView } from '../source/use_data_view';
import { useFetchIndex } from '../source';
import { useInitializeUrlParam, useUpdateUrlParam } from '../../utils/global_query_string';
import { URL_PARAM_KEY } from '../../hooks/use_url_state';
import { sortWithExcludesAtEnd } from '../../../../common/utils/sourcerer';
import { useKibana } from '../../lib/kibana';
export const useInitSourcerer = (
scopeId: SourcererScopeName.default | SourcererScopeName.detections = SourcererScopeName.default
) => {
const dispatch = useDispatch();
const {
data: { dataViews },
} = useKibana().services;
const abortCtrl = useRef(new AbortController());
const initialTimelineSourcerer = useRef(true);
const initialDetectionSourcerer = useRef(true);
@ -229,6 +233,7 @@ export const useInitSourcerer = (
signalIndexName,
signalIndexNameSourcerer,
]);
const { dataViewId } = useSourcererDataView(scopeId);
const updateSourcererDataView = useCallback(
(newSignalsIndex: string) => {
@ -236,18 +241,21 @@ export const useInitSourcerer = (
abortCtrl.current = new AbortController();
dispatch(sourcererActions.setSourcererScopeLoading({ loading: true }));
try {
const response = await postSourcererDataView({
const response = await createSourcererDataView({
body: { patternList: newPatternList },
signal: abortCtrl.current.signal,
dataViewService: dataViews,
dataViewId,
});
if (response.defaultDataView.patternList.includes(newSignalsIndex)) {
if (response?.defaultDataView.patternList.includes(newSignalsIndex)) {
// first time signals is defined and validated in the sourcerer
// redo indexFieldsSearch
indexFieldsSearch({ dataViewId: response.defaultDataView.id });
dispatch(sourcererActions.setSourcererDataViews(response));
}
dispatch(sourcererActions.setSourcererDataViews(response));
dispatch(sourcererActions.setSourcererScopeLoading({ loading: false }));
} catch (err) {
addError(err, {
@ -267,7 +275,7 @@ export const useInitSourcerer = (
asyncSearch([...defaultDataView.title.split(','), newSignalsIndex]);
}
},
[defaultDataView.title, dispatch, indexFieldsSearch, addError]
[defaultDataView.title, dispatch, dataViews, dataViewId, indexFieldsSearch, addError]
);
const onSignalIndexUpdated = useCallback(() => {

View file

@ -12,10 +12,11 @@ import { sourcererSelectors } from '../../store';
import { useDeepEqualSelector } from '../../hooks/use_selector';
import { useSourcererDataView } from '.';
import { SourcererScopeName } from '../../store/sourcerer/model';
import { postSourcererDataView } from './api';
import { sourcererActions } from '../../store/sourcerer';
import { useDataView } from '../source/use_data_view';
import { useAppToasts } from '../../hooks/use_app_toasts';
import { useKibana } from '../../lib/kibana';
import { createSourcererDataView } from './create_sourcerer_data_view';
import { sourcererActions } from '../../store/sourcerer';
export const useSignalHelpers = (): {
/* when defined, signal index has been initiated but does not exist */
@ -23,11 +24,14 @@ export const useSignalHelpers = (): {
/* when false, signal index has been initiated */
signalIndexNeedsInit: boolean;
} => {
const { indicesExist } = useSourcererDataView(SourcererScopeName.detections);
const { indicesExist, dataViewId } = useSourcererDataView(SourcererScopeName.detections);
const { indexFieldsSearch } = useDataView();
const dispatch = useDispatch();
const { addError } = useAppToasts();
const abortCtrl = useRef(new AbortController());
const {
data: { dataViews },
} = useKibana().services;
const getDefaultDataViewSelector = useMemo(
() => sourcererSelectors.defaultDataViewSelector(),
@ -53,19 +57,21 @@ export const useSignalHelpers = (): {
const asyncSearch = async () => {
abortCtrl.current = new AbortController();
try {
const response = await postSourcererDataView({
const sourcererDataView = await createSourcererDataView({
body: { patternList: defaultDataView.title.split(',') },
signal: abortCtrl.current.signal,
dataViewId,
dataViewService: dataViews,
});
if (
signalIndexNameSourcerer !== null &&
response.defaultDataView.patternList.includes(signalIndexNameSourcerer)
sourcererDataView?.defaultDataView.patternList.includes(signalIndexNameSourcerer)
) {
// first time signals is defined and validated in the sourcerer
// redo indexFieldsSearch
indexFieldsSearch({ dataViewId: response.defaultDataView.id });
dispatch(sourcererActions.setSourcererDataViews(response));
indexFieldsSearch({ dataViewId: sourcererDataView.defaultDataView.id });
dispatch(sourcererActions.setSourcererDataViews(sourcererDataView));
}
} catch (err) {
addError(err, {
@ -83,7 +89,15 @@ export const useSignalHelpers = (): {
abortCtrl.current.abort();
asyncSearch();
}
}, [addError, defaultDataView.title, dispatch, indexFieldsSearch, signalIndexNameSourcerer]);
}, [
addError,
dataViewId,
dataViews,
defaultDataView.title,
dispatch,
indexFieldsSearch,
signalIndexNameSourcerer,
]);
return {
...(shouldWePollForIndex ? { pollForSignalIndex } : {}),

View file

@ -8,7 +8,7 @@
import actionCreatorFactory from 'typescript-fsa';
import type { SelectedDataView, SourcererDataView, SourcererScopeName } from './model';
import type { SecurityDataView } from '../../containers/sourcerer/api';
import type { SecurityDataView } from '../../containers/sourcerer/create_sourcerer_data_view';
const actionCreator = actionCreatorFactory('x-pack/security_solution/local/sourcerer');

View file

@ -25,10 +25,10 @@ import type { Storage } from '@kbn/kibana-utils-plugin/public';
import type { CoreStart } from '@kbn/core/public';
import reduceReducers from 'reduce-reducers';
import {
DEFAULT_DATA_VIEW_ID,
DEFAULT_INDEX_KEY,
DETECTION_ENGINE_INDEX_URL,
SERVER_APP_ID,
SOURCERER_API_URL,
} from '../../../common/constants';
import { telemetryMiddleware } from '../lib/telemetry';
import { appSelectors } from './app';
@ -45,8 +45,8 @@ import { dataTableSelectors } from './data_table';
import type { KibanaDataView, SourcererModel } from './sourcerer/model';
import { initDataView } from './sourcerer/model';
import type { AppObservableLibs, StartedSubPlugins, StartPlugins } from '../../types';
import type { SecurityDataView } from '../containers/sourcerer/api';
import type { ExperimentalFeatures } from '../../../common/experimental_features';
import { createSourcererDataView } from '../containers/sourcerer/create_sourcerer_data_view';
type ComposeType = typeof compose;
declare global {
@ -79,12 +79,17 @@ export const createStoreFactory = async (
let kibanaDataViews: SourcererModel['kibanaDataViews'];
try {
// check for/generate default Security Solution Kibana data view
const sourcererDataViews: SecurityDataView = await coreStart.http.fetch(SOURCERER_API_URL, {
method: 'POST',
body: JSON.stringify({
const sourcererDataViews = await createSourcererDataView({
body: {
patternList: [...configPatternList, ...(signal.name != null ? [signal.name] : [])],
}),
},
dataViewService: startPlugins.data.dataViews,
dataViewId: `${DEFAULT_DATA_VIEW_ID}-${(await startPlugins.spaces?.getActiveSpace())?.id}`,
});
if (sourcererDataViews === undefined) {
throw new Error('');
}
defaultDataView = { ...initDataView, ...sourcererDataViews.defaultDataView };
kibanaDataViews = sourcererDataViews.kibanaDataViews.map((dataView: KibanaDataView) => ({
...initDataView,

View file

@ -58,6 +58,7 @@ import { getLazyEndpointGenericErrorsListExtension } from './management/pages/po
import type { ExperimentalFeatures } from '../common/experimental_features';
import { parseExperimentalConfigValue } from '../common/experimental_features';
import { LazyEndpointCustomAssetsExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_custom_assets_extension';
import type { SecurityAppStore } from './common/store/types';
export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, StartPlugins> {

View file

@ -23,7 +23,6 @@ import { useTimelineEvents } from '../../../containers';
import { useTimelineEventsDetails } from '../../../containers/details';
import { useSourcererDataView } from '../../../../common/containers/sourcerer';
import { mockSourcererScope } from '../../../../common/containers/sourcerer/mocks';
import { mockCasesContext } from '@kbn/cases-plugin/public/mocks/mock_cases_context';
jest.mock('../../../containers', () => ({
useTimelineEvents: jest.fn(),
@ -47,52 +46,7 @@ const mockUseResizeObserver: jest.Mock = useResizeObserver as jest.Mock;
jest.mock('use-resize-observer/polyfilled');
mockUseResizeObserver.mockImplementation(() => ({}));
const useAddToTimeline = () => ({
beginDrag: jest.fn(),
cancelDrag: jest.fn(),
dragToLocation: jest.fn(),
endDrag: jest.fn(),
hasDraggableLock: jest.fn(),
startDragToTimeline: jest.fn(),
});
jest.mock('../../../../common/lib/kibana', () => {
const originalModule = jest.requireActual('../../../../common/lib/kibana');
return {
...originalModule,
useKibana: jest.fn().mockReturnValue({
services: {
theme: {
theme$: {},
},
application: {
navigateToApp: jest.fn(),
getUrlForApp: jest.fn(),
},
cases: {
ui: {
getCasesContext: () => mockCasesContext,
},
},
docLinks: { links: { query: { eql: 'url-eql_doc' } } },
uiSettings: {
get: jest.fn(),
},
savedObjects: {
client: {},
},
timelines: {
getLastUpdated: jest.fn(),
getUseAddToTimeline: () => useAddToTimeline,
},
triggersActionsUi: {
getFieldBrowser: jest.fn(),
},
},
}),
useGetUserSavedObjectPermissions: jest.fn(),
};
});
jest.mock('../../../../common/lib/kibana');
describe('Timeline', () => {
let props = {} as EqlTabContentComponentProps;

View file

@ -26,7 +26,6 @@ import { useTimelineEventsDetails } from '../../../containers/details';
import { useSourcererDataView } from '../../../../common/containers/sourcerer';
import { mockSourcererScope } from '../../../../common/containers/sourcerer/mocks';
import { Direction } from '../../../../../common/search_strategy';
import { mockCasesContext } from '@kbn/cases-plugin/public/mocks/mock_cases_context';
import * as helpers from '../../../../common/lib/kuery';
import { waitFor } from '@testing-library/react';
@ -54,52 +53,7 @@ const mockUseResizeObserver: jest.Mock = useResizeObserver as jest.Mock;
jest.mock('use-resize-observer/polyfilled');
mockUseResizeObserver.mockImplementation(() => ({}));
const useAddToTimeline = () => ({
beginDrag: jest.fn(),
cancelDrag: jest.fn(),
dragToLocation: jest.fn(),
endDrag: jest.fn(),
hasDraggableLock: jest.fn(),
startDragToTimeline: jest.fn(),
});
jest.mock('../../../../common/lib/kibana', () => {
const originalModule = jest.requireActual('../../../../common/lib/kibana');
return {
...originalModule,
useKibana: jest.fn().mockReturnValue({
services: {
theme: {
theme$: {},
},
application: {
navigateToApp: jest.fn(),
getUrlForApp: jest.fn(),
},
cases: {
ui: {
getCasesContext: () => mockCasesContext,
},
},
uiSettings: {
get: jest.fn(),
},
savedObjects: {
client: {},
},
triggersActionsUi: {
getFieldBrowser: jest.fn(),
},
timelines: {
getLastUpdated: jest.fn(),
getLoadingPanel: jest.fn(),
getUseAddToTimeline: () => useAddToTimeline,
},
},
}),
useGetUserSavedObjectPermissions: jest.fn(),
};
});
jest.mock('../../../../common/lib/kibana');
describe('Timeline', () => {
let props = {} as QueryTabContentComponentProps;

View file

@ -1,78 +0,0 @@
# Sourcerer API
### Model reference
```typescript
interface KibanaDataView {
/** Uniquely identifies a Kibana Data View */
id: string;
/** list of active patterns that return data */
patternList: string[];
/**
* title of Kibana Data View
* title also serves as "all pattern list", including inactive
* comma separated string
*/
title: string;
}
```
### API usage
The sourcerer API has one route with 2 methods
1. POST - `createSourcererDataViewRoute`
1. REQUEST:
```typescript
POST /internal/security_solution/sourcerer
{
patternList: [...configPatternList, ...(signal.name != null ? [signal.name] : [])]
}
```
2. RESPONSE:
```typescript
{
/** default security-solution data view */
defaultDataView: KibanaDataView;
/** all Kibana data views, including default security-solution */
kibanaDataViews: KibanaDataView[];
}
```
3. This route is called from `security_solution/public/plugin.tsx` on app load. It passes an argument of `patternList` which is an array of the config index patterns defined in Stack Management > Advanced Settings > Security Solution > Elasticsearch indices along with the default signal index
4. `dataViewService.getIdsWithTitle` is called to get all existing data views ids and titles
5. Next `dataViewService.get` method is called to attempt to retrieve the default security data view by id (`siemClient.getSourcererDataViewId()`). If the data view id does not exist, it uses `dataViewService.createAndSave` to create the default security data view.
6. `patternListAsTitle` (a string of the patternList passed) is compared to the current `siemDataViewTitle`. If they do not match, we use `dataViewService.updateSavedObject` to update the data view title. This may happen when a pattern is added or removed from the Stack Management > Advanced Settings > Security Solution > Elasticsearch indices.
7. Next we call `buildSourcererDataView` for the default data view only. This takes the `dataView.title` and finds which patterns on the list returns data. Valid patterns are returned in an array called `patternList`. The non-default data views will have an empty array for patternList, and we will call this function if/when the data view is selected to save time.
8. At the end we return a body of
```
{
/** default security-solution data view */
defaultDataView: KibanaDataView;
/** all Kibana data views, including default security-solution */
kibanaDataViews: KibanaDataView[];
}
```
9. The other place this POST is called is when the default signal index does not yet exist. In the front-end there is a method called `pollForSignalIndex` that is defined when the signal index has been initiated but does not have data. It is called whenever the detection sourcerer or timeline sourcerer mounts, or whenever the search bar is refreshed. If the signal index is defined, `pollForSignalIndex` ceases to exist and is not called.
10. One more place we call the POST method is when the signal index first has data, we send a POST in a method called `onSignalIndexUpdated` to include the newly created index in the data view
2. GET - `getSourcererDataViewRoute`
1. REQUEST:
```typescript
GET /internal/security_solution/sourcerer?dataViewId=security-solution-default
```
2. RESPONSE:
```typescript
KibanaDataView
```
3. When the user changes the data view from the default in the UI, we call the GET method to find which index patterns in the `dataView.title` are valid, returning a valid `patternList`.
4. We return a body of a single `KibanaDataView`
### Helpers
To build the valid pattern list, we call `findExistingIndices` which takes the pattern list as an argument, and returns a boolean array of which patterns are valid. To check if indices exist, we use the field caps API for each pattern checking for the field `_id`. This will return a list of valid indices. If the array is empty, no indices exist for the pattern. For example:
```typescript
// Given
findExistingIndices(['auditbeat-*', 'fakebeat-*', 'packetbeat-*'])
// Returns
[true, false, true]
```

View file

@ -1,46 +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 { findExistingIndices } from './helpers';
import type { ElasticsearchClient } from '@kbn/core/server';
const fieldCaps = jest
.fn()
.mockImplementation(() => new Promise((resolve) => resolve([true, true])));
const esClient = {
fieldCaps,
};
describe('sourcerer helpers', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('findExistingIndices calls with regular indices', async () => {
await findExistingIndices(['a', 'b'], esClient as unknown as ElasticsearchClient);
expect(esClient.fieldCaps.mock.calls[0][0].index).toEqual('a');
expect(esClient.fieldCaps.mock.calls[1][0].index).toEqual('b');
});
it('findExistingIndices calls with regular indices in place of exclude indices', async () => {
await findExistingIndices(['a', '-b'], esClient as unknown as ElasticsearchClient);
expect(esClient.fieldCaps.mock.calls[0][0].index).toEqual('a');
expect(esClient.fieldCaps.mock.calls[1][0].index).toEqual('b');
});
it('findExistingIndices removes leading / trailing whitespace, and dashes from exclude patterns', async () => {
await findExistingIndices(
[
' include-with-leading-and-trailing-whitespace ',
' -exclude-with-leading-and-trailing-whitespace ',
],
esClient as unknown as ElasticsearchClient
);
expect(esClient.fieldCaps.mock.calls[0][0].index).toEqual(
'include-with-leading-and-trailing-whitespace'
);
expect(esClient.fieldCaps.mock.calls[1][0].index).toEqual(
'exclude-with-leading-and-trailing-whitespace'
);
});
});

View file

@ -1,29 +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 type { ElasticsearchClient } from '@kbn/core/server';
export const findExistingIndices = async (
indices: string[],
esClient: ElasticsearchClient
): Promise<boolean[]> =>
Promise.all(
indices
.map(async (index) => {
const indexToQuery = index.trim().startsWith('-')
? index.trim().substring(1)
: index.trim();
const searchResponse = await esClient.fieldCaps({
index: indexToQuery,
fields: '_id',
ignore_unavailable: true,
allow_no_indices: false,
});
return searchResponse.indices.length > 0;
})
.map((p) => p.catch((e) => false))
);

View file

@ -1,330 +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 { createSourcererDataViewRoute } from '.';
import {
requestMock,
serverMock,
requestContextMock,
} from '../../detection_engine/routes/__mocks__';
import { SOURCERER_API_URL } from '../../../../common/constants';
import type { StartServicesAccessor } from '@kbn/core/server';
import type { StartPlugins } from '../../../plugin';
jest.mock('./helpers', () => {
const original = jest.requireActual('./helpers');
return {
...original,
findExistingIndices: () => new Promise((resolve) => resolve([true, true])),
};
});
const mockPattern = {
id: 'security-solution',
fields: [
{ name: '@timestamp', searchable: true, type: 'date', aggregatable: true },
{ name: '@version', searchable: true, type: 'string', aggregatable: true },
{ name: 'agent.ephemeral_id', searchable: true, type: 'string', aggregatable: true },
{ name: 'agent.hostname', searchable: true, type: 'string', aggregatable: true },
{ name: 'agent.id', searchable: true, type: 'string', aggregatable: true },
],
title:
'apm-*-transaction*,traces-apm*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,winlogbeat-*,ml_host_risk_score_*,.siem-signals-default',
};
const mockPatternList = [
'apm-*-transaction*',
'traces-apm*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
'logs-*',
'packetbeat-*',
'winlogbeat-*',
'ml_host_risk_score_*',
'.siem-signals-default',
];
const mockDataViews = [
{
id: 'metrics-*',
title: 'metrics-*',
},
{
id: 'logs-*',
title: 'logs-*',
},
mockPattern,
];
const getStartServices = jest.fn().mockReturnValue([
null,
{
data: {
indexPatterns: {
dataViewsServiceFactory: () => ({
getIdsWithTitle: () => new Promise((rs) => rs(mockDataViews)),
get: () => new Promise((rs) => rs(mockPattern)),
createAndSave: () => new Promise((rs) => rs(mockPattern)),
updateSavedObject: () => new Promise((rs) => rs(mockPattern)),
}),
},
},
},
] as unknown) as StartServicesAccessor<StartPlugins>;
const getStartServicesNotSiem = jest.fn().mockReturnValue([
null,
{
data: {
indexPatterns: {
dataViewsServiceFactory: () => ({
getIdsWithTitle: () =>
new Promise((rs) => rs(mockDataViews.filter((v) => v.id !== mockPattern.id))),
get: (id: string) =>
new Promise((rs) =>
id === mockPattern.id
? rs(null)
: rs({
id: 'dataview-lambda',
title: 'fun-*,dog-*,cat-*',
})
),
createAndSave: () => new Promise((rs) => rs(mockPattern)),
updateSavedObject: () => new Promise((rs) => rs(mockPattern)),
}),
},
},
},
] as unknown) as StartServicesAccessor<StartPlugins>;
const mockDataViewsTransformed = {
defaultDataView: {
id: 'security-solution',
patternList: ['apm-*-transaction*', 'traces-apm*'],
title:
'apm-*-transaction*,traces-apm*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,winlogbeat-*,ml_host_risk_score_*,.siem-signals-default',
},
kibanaDataViews: [
{
id: 'metrics-*',
title: 'metrics-*',
},
{
id: 'logs-*',
title: 'logs-*',
},
{
id: 'security-solution',
patternList: ['apm-*-transaction*', 'traces-apm*'],
title:
'apm-*-transaction*,traces-apm*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,winlogbeat-*,ml_host_risk_score_*,.siem-signals-default',
},
],
};
describe('sourcerer route', () => {
let server: ReturnType<typeof serverMock.create>;
let { context } = requestContextMock.createTools();
describe('post', () => {
const getSourcererRequest = (patternList: string[]) =>
requestMock.create({
method: 'post',
path: SOURCERER_API_URL,
body: { patternList },
});
describe('functional tests', () => {
beforeEach(() => {
server = serverMock.create();
({ context } = requestContextMock.createTools());
});
test('returns sourcerer formatted Data Views when SIEM Data View does NOT exist', async () => {
createSourcererDataViewRoute(server.router, getStartServicesNotSiem);
const response = await server.inject(
getSourcererRequest(mockPatternList),
requestContextMock.convertContext(context)
);
expect(response.status).toEqual(200);
expect(response.body).toEqual(mockDataViewsTransformed);
});
test('returns sourcerer formatted Data Views when SIEM Data View does NOT exist but has been created in the mean time', async () => {
const getMock = jest.fn();
getMock.mockResolvedValueOnce(mockPattern);
const getStartServicesSpecial = jest.fn().mockResolvedValue([
null,
{
data: {
indexPatterns: {
dataViewsServiceFactory: () => ({
getIdsWithTitle: () =>
new Promise((rs) => rs(mockDataViews.filter((v) => v.id !== mockPattern.id))),
get: getMock,
createAndSave: jest.fn().mockRejectedValue({ statusCode: 409 }),
updateSavedObject: () => new Promise((rs, rj) => rj(new Error('error'))),
}),
},
},
},
] as unknown) as StartServicesAccessor<StartPlugins>;
createSourcererDataViewRoute(server.router, getStartServicesSpecial);
const response = await server.inject(
getSourcererRequest(mockPatternList),
requestContextMock.convertContext(context)
);
expect(response.status).toEqual(200);
expect(response.body).toEqual(mockDataViewsTransformed);
});
test('passes sorted title on create and save', async () => {
const getMock = jest.fn();
getMock.mockResolvedValueOnce(null);
getMock.mockResolvedValueOnce(mockPattern);
const mockCreateAndSave = jest.fn();
const getStartServicesSpecial = jest.fn().mockResolvedValue([
null,
{
data: {
indexPatterns: {
dataViewsServiceFactory: () => ({
getIdsWithTitle: () =>
new Promise((rs) => rs(mockDataViews.filter((v) => v.id !== mockPattern.id))),
get: getMock,
createAndSave: mockCreateAndSave.mockImplementation(
() => new Promise((rs) => rs(mockPattern))
),
updateSavedObject: () => new Promise((rs, rj) => rj(new Error('error'))),
}),
},
},
},
] as unknown) as StartServicesAccessor<StartPlugins>;
createSourcererDataViewRoute(server.router, getStartServicesSpecial);
await server.inject(
getSourcererRequest(['-elastic-logs-*', ...mockPatternList]),
requestContextMock.convertContext(context)
);
expect(mockCreateAndSave.mock.calls[0][0].title).toEqual(
'.siem-signals-default,apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,ml_host_risk_score_*,packetbeat-*,traces-apm*,winlogbeat-*,-elastic-logs-*'
);
});
test('passes override=true on create and save', async () => {
const getMock = jest.fn();
getMock.mockResolvedValueOnce(null);
getMock.mockResolvedValueOnce(mockPattern);
const mockCreateAndSave = jest.fn();
const getStartServicesSpecial = jest.fn().mockResolvedValue([
null,
{
data: {
indexPatterns: {
dataViewsServiceFactory: () => ({
getIdsWithTitle: () =>
new Promise((rs) => rs(mockDataViews.filter((v) => v.id !== mockPattern.id))),
get: getMock,
createAndSave: mockCreateAndSave.mockImplementation(
() => new Promise((rs) => rs(mockPattern))
),
updateSavedObject: () => new Promise((rs, rj) => rj(new Error('error'))),
}),
},
},
},
] as unknown) as StartServicesAccessor<StartPlugins>;
createSourcererDataViewRoute(server.router, getStartServicesSpecial);
await server.inject(
getSourcererRequest(mockPatternList),
requestContextMock.convertContext(context)
);
expect(mockCreateAndSave).toHaveBeenCalled();
expect(mockCreateAndSave.mock.calls[0][1]).toEqual(true);
});
test('passes sorted title on updateSavedObject', async () => {
const getMock = jest.fn();
getMock.mockResolvedValueOnce(null);
getMock.mockResolvedValueOnce(mockPattern);
const mockCreateAndSave = jest.fn();
const mockUpdateSavedObject = jest.fn();
const getStartServicesSpecial = jest.fn().mockResolvedValue([
null,
{
data: {
indexPatterns: {
dataViewsServiceFactory: () => ({
getIdsWithTitle: () =>
new Promise((rs) =>
rs([{ id: 'security-solution', title: 'winlogbeat-*,-elastic-logs-*' }])
),
get: jest.fn().mockResolvedValue({
id: 'security-solution',
title: 'winlogbeat-*,-elastic-logs-*',
}),
createAndSave: mockCreateAndSave.mockImplementation(
() => new Promise((rs) => rs(mockPattern))
),
updateSavedObject: mockUpdateSavedObject.mockImplementation(
() => new Promise((rs) => rs(mockPattern))
),
}),
},
},
},
] as unknown) as StartServicesAccessor<StartPlugins>;
createSourcererDataViewRoute(server.router, getStartServicesSpecial);
await server.inject(
getSourcererRequest(['-elastic-logs-*', ...mockPatternList]),
requestContextMock.convertContext(context)
);
expect(mockUpdateSavedObject).toHaveBeenCalledWith({
id: 'security-solution',
title:
'.siem-signals-default,apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,ml_host_risk_score_*,packetbeat-*,traces-apm*,winlogbeat-*,-elastic-logs-*',
});
});
test('returns sourcerer formatted Data Views when SIEM Data View exists', async () => {
createSourcererDataViewRoute(server.router, getStartServices);
const response = await server.inject(
getSourcererRequest(mockPatternList),
requestContextMock.convertContext(context)
);
expect(response.status).toEqual(200);
expect(response.body).toEqual(mockDataViewsTransformed);
});
test('returns sourcerer formatted Data Views when SIEM Data View exists and patternList input is changed', async () => {
createSourcererDataViewRoute(server.router, getStartServices);
mockPatternList.shift();
const response = await server.inject(
getSourcererRequest(mockPatternList),
requestContextMock.convertContext(context)
);
expect(response.status).toEqual(200);
expect(response.body).toEqual({
defaultDataView: {
id: 'security-solution',
patternList: ['.siem-signals-default', 'auditbeat-*'],
title:
'.siem-signals-default,auditbeat-*,endgame-*,filebeat-*,logs-*,ml_host_risk_score_*,packetbeat-*,traces-apm*,winlogbeat-*',
},
kibanaDataViews: [
mockDataViewsTransformed.kibanaDataViews[0],
mockDataViewsTransformed.kibanaDataViews[1],
{
id: 'security-solution',
patternList: ['.siem-signals-default', 'auditbeat-*'],
title:
'.siem-signals-default,auditbeat-*,endgame-*,filebeat-*,logs-*,ml_host_risk_score_*,packetbeat-*,traces-apm*,winlogbeat-*',
},
],
});
});
});
});
});

View file

@ -1,210 +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 { transformError } from '@kbn/securitysolution-es-utils';
import type { ElasticsearchClient, StartServicesAccessor } from '@kbn/core/server';
import type { DataView, DataViewListItem } from '@kbn/data-views-plugin/common';
import { DEFAULT_TIME_FIELD, SOURCERER_API_URL } from '../../../../common/constants';
import type { SecuritySolutionPluginRouter } from '../../../types';
import { buildRouteValidation } from '../../../utils/build_validation/route_validation';
import type { StartPlugins } from '../../../plugin';
import { buildSiemResponse } from '../../detection_engine/routes/utils';
import { findExistingIndices } from './helpers';
import { sourcererDataViewSchema, sourcererSchema } from './schema';
import { ensurePatternFormat } from '../../../../common/utils/sourcerer';
export const createSourcererDataViewRoute = (
router: SecuritySolutionPluginRouter,
getStartServices: StartServicesAccessor<StartPlugins>
) => {
router.post(
{
path: SOURCERER_API_URL,
validate: {
body: buildRouteValidation(sourcererSchema),
},
options: {
authRequired: true,
tags: ['access:securitySolution'],
},
},
async (context, request, response) => {
const siemResponse = buildSiemResponse(response);
const coreContext = await context.core;
const siemClient = (await context.securitySolution)?.getAppClient();
const dataViewId = siemClient.getSourcererDataViewId();
try {
const [
,
{
data: { indexPatterns },
},
] = await getStartServices();
const dataViewService = await indexPatterns.dataViewsServiceFactory(
coreContext.savedObjects.client,
coreContext.elasticsearch.client.asCurrentUser,
request,
true
);
let allDataViews: DataViewListItem[] = await dataViewService.getIdsWithTitle();
let siemDataView: DataView | DataViewListItem | null =
allDataViews.find((dv) => dv.id === dataViewId) ?? null;
const { patternList } = request.body;
const patternListAsTitle = ensurePatternFormat(patternList).join();
const siemDataViewTitle = siemDataView
? ensurePatternFormat(siemDataView.title.split(',')).join()
: '';
if (siemDataView == null) {
try {
siemDataView = await dataViewService.createAndSave(
{
allowNoIndex: true,
id: dataViewId,
title: patternListAsTitle,
timeFieldName: DEFAULT_TIME_FIELD,
},
// Override property - if a data view exists with the security solution pattern
// delete it and replace it with our data view
true
);
} catch (err) {
const error = transformError(err);
if (err.name === 'DuplicateDataViewError' || error.statusCode === 409) {
siemDataView = await dataViewService.get(dataViewId);
} else {
throw error;
}
}
} else if (patternListAsTitle !== siemDataViewTitle) {
siemDataView = await dataViewService.get(dataViewId);
siemDataView.title = patternListAsTitle;
await dataViewService.updateSavedObject(siemDataView);
}
if (allDataViews.some((dv) => dv.id === dataViewId)) {
allDataViews = allDataViews.map((v) =>
v.id === dataViewId ? { ...v, title: patternListAsTitle } : v
);
} else {
allDataViews.push({ ...siemDataView, id: siemDataView.id ?? dataViewId });
}
const defaultDataView = await buildSourcererDataView(
siemDataView,
coreContext.elasticsearch.client.asCurrentUser
);
return response.ok({
body: {
defaultDataView,
kibanaDataViews: allDataViews.map((dv) =>
dv.id === dataViewId ? defaultDataView : dv
),
},
});
} catch (err) {
const error = transformError(err);
return siemResponse.error({
body:
error.statusCode === 403
? 'Users with write permissions need to access the Elastic Security app to initialize the app source data.'
: error.message,
statusCode: error.statusCode,
});
}
}
);
};
export const getSourcererDataViewRoute = (
router: SecuritySolutionPluginRouter,
getStartServices: StartServicesAccessor<StartPlugins>
) => {
router.get(
{
path: SOURCERER_API_URL,
validate: {
query: buildRouteValidation(sourcererDataViewSchema),
},
options: {
tags: ['access:securitySolution'],
},
},
async (context, request, response) => {
const siemResponse = buildSiemResponse(response);
const coreContext = await context.core;
const { dataViewId } = request.query;
try {
const [
,
{
data: { indexPatterns },
},
] = await getStartServices();
const dataViewService = await indexPatterns.dataViewsServiceFactory(
coreContext.savedObjects.client,
coreContext.elasticsearch.client.asCurrentUser,
request,
true
);
const allDataViews: DataViewListItem[] = await dataViewService.getIdsWithTitle();
const siemDataView: DataViewListItem | null =
allDataViews.find((dv) => dv.id === dataViewId) ?? null;
const kibanaDataView = siemDataView
? await buildSourcererDataView(
siemDataView,
coreContext.elasticsearch.client.asCurrentUser
)
: {};
return response.ok({
body: kibanaDataView,
});
} catch (err) {
const error = transformError(err);
return siemResponse.error({
body:
error.statusCode === 403
? 'Users with write permissions need to access the Elastic Security app to initialize the app source data.'
: error.message,
statusCode: error.statusCode,
});
}
}
);
};
interface KibanaDataView {
/** Uniquely identifies a Kibana Data View */
id: string;
/** list of active patterns that return data */
patternList: string[];
/**
* title of Kibana Data View
* title also serves as "all pattern list", including inactive
* comma separated string
*/
title: string;
}
const buildSourcererDataView = async (
dataView: DataView | DataViewListItem,
clientAsCurrentUser: ElasticsearchClient
): Promise<KibanaDataView> => {
const patternList = dataView.title.split(',');
const activePatternBools: boolean[] = await findExistingIndices(patternList, clientAsCurrentUser);
const activePatternLists: string[] = patternList.filter(
(pattern, j, self) => self.indexOf(pattern) === j && activePatternBools[j]
);
return { id: dataView.id ?? '', title: dataView.title, patternList: activePatternLists };
};

View file

@ -1,16 +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 * as t from 'io-ts';
export const sourcererSchema = t.type({
patternList: t.array(t.string),
});
export const sourcererDataViewSchema = t.type({
dataViewId: t.string,
});

View file

@ -55,7 +55,6 @@ import type {
CreateRuleOptions,
CreateSecurityRuleTypeWrapperProps,
} from '../lib/detection_engine/rule_types/types';
import { createSourcererDataViewRoute, getSourcererDataViewRoute } from '../lib/sourcerer/routes';
import type { ITelemetryReceiver } from '../lib/telemetry/receiver';
import { telemetryDetectionRulesPreviewRoute } from '../lib/detection_engine/routes/telemetry/telemetry_detection_rules_preview_route';
import { readAlertsIndexExistsRoute } from '../lib/detection_engine/routes/index/read_alerts_index_exists_route';
@ -148,10 +147,6 @@ export const initRoutes = (
// Privileges API to get the generic user privileges
readPrivilegesRoute(router, hasEncryptionKey);
// Sourcerer API to generate default pattern
createSourcererDataViewRoute(router, getStartServices);
getSourcererDataViewRoute(router, getStartServices);
// risky score module
createEsIndexRoute(router, logger);
deleteEsIndicesRoute(router);