diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/all/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/all/index.ts index cefd43fe5f99e..2c1743e262ead 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/all/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/all/index.ts @@ -9,10 +9,13 @@ import type { IEsSearchRequest, IEsSearchResponse } from '@kbn/data-plugin/commo import type { ESQuery } from '../../../../typed_json'; import type { Inspect, Maybe, SortField, TimerangeInput } from '../../../common'; +import type { RiskScoreEntity } from '../common'; export interface RiskScoreRequestOptions extends IEsSearchRequest { defaultIndex: string[]; + riskScoreEntity: RiskScoreEntity; timerange?: TimerangeInput; + includeAlertsCount?: boolean; onlyLatest?: boolean; pagination?: { cursorStart: number; @@ -47,6 +50,7 @@ export interface HostRiskScore { name: string; risk: RiskStats; }; + alertsCount?: number; } export interface UserRiskScore { @@ -55,6 +59,7 @@ export interface UserRiskScore { name: string; risk: RiskStats; }; + alertsCount?: number; } export interface RuleRisk { @@ -73,6 +78,7 @@ export const enum RiskScoreFields { userName = 'user.name', userRiskScore = 'user.risk.calculated_score_norm', userRisk = 'user.risk.calculated_level', + alertsCount = 'alertsCount', } export interface RiskScoreItem { @@ -85,6 +91,8 @@ export interface RiskScoreItem { [RiskScoreFields.hostRiskScore]: Maybe; [RiskScoreFields.userRiskScore]: Maybe; + + [RiskScoreFields.alertsCount]: Maybe; } export const enum RiskSeverity { diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/columns.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/columns.tsx index 055a172e54b73..a19168b5e864b 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/columns.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/columns.tsx @@ -7,19 +7,28 @@ import React from 'react'; import type { EuiBasicTableColumn } from '@elastic/eui'; -import { EuiIcon, EuiToolTip } from '@elastic/eui'; +import { EuiLink, EuiIcon, EuiToolTip } from '@elastic/eui'; +import { get } from 'lodash/fp'; import { UsersTableType } from '../../../../users/store/model'; import { getEmptyTagValue } from '../../../../common/components/empty_value'; import { HostDetailsLink, UserDetailsLink } from '../../../../common/components/links'; import { HostsTableType } from '../../../../hosts/store/model'; import { RiskScore } from '../../../../common/components/severity/common'; -import type { HostRiskScore, RiskSeverity } from '../../../../../common/search_strategy'; +import type { + HostRiskScore, + RiskSeverity, + UserRiskScore, +} from '../../../../../common/search_strategy'; import { RiskScoreEntity, RiskScoreFields } from '../../../../../common/search_strategy'; import * as i18n from './translations'; +import { FormattedCount } from '../../../../common/components/formatted_number'; -type HostRiskScoreColumns = Array>; +type HostRiskScoreColumns = Array>; -export const getRiskScoreColumns = (riskEntity: RiskScoreEntity): HostRiskScoreColumns => [ +export const getRiskScoreColumns = ( + riskEntity: RiskScoreEntity, + openEntityInTimeline: (entityName: string) => void +): HostRiskScoreColumns => [ { field: riskEntity === RiskScoreEntity.host ? 'host.name' : 'user.name', name: i18n.ENTITY_NAME(riskEntity), @@ -75,4 +84,20 @@ export const getRiskScoreColumns = (riskEntity: RiskScoreEntity): HostRiskScoreC return getEmptyTagValue(); }, }, + { + field: RiskScoreFields.alertsCount, + width: '15%', + name: i18n.ALERTS, + truncateText: false, + mobileOptions: { show: true }, + render: (alertCount: number, risk) => ( + openEntityInTimeline(get('host.name', risk) ?? get('user.name', risk))} + > + + + ), + }, ]; diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.test.tsx index 89011082a2f81..70faf174ae32f 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.test.tsx @@ -9,6 +9,7 @@ import { render } from '@testing-library/react'; import React from 'react'; import { TestProviders } from '../../../../common/mock'; import { EntityAnalyticsRiskScores } from '.'; +import type { UserRiskScore } from '../../../../../common/search_strategy'; import { RiskScoreEntity, RiskSeverity } from '../../../../../common/search_strategy'; import type { SeverityCount } from '../../../../common/components/severity/types'; import { useRiskScore, useRiskScoreKpi } from '../../../../risk_score/containers'; @@ -116,5 +117,38 @@ describe.each([RiskScoreEntity.host, RiskScoreEntity.user])( expect(queryByTestId('entity_analytics_content')).not.toBeInTheDocument(); }); + + it('renders alerts count', () => { + mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: jest.fn() }); + mockUseRiskScoreKpi.mockReturnValue({ + severityCount: mockSeverityCount, + loading: false, + }); + const alertsCount = 999; + const data: UserRiskScore[] = [ + { + '@timestamp': '1234567899', + user: { + name: 'testUsermame', + risk: { + rule_risks: [], + calculated_level: RiskSeverity.high, + calculated_score_norm: 75, + multipliers: [], + }, + }, + alertsCount, + }, + ]; + mockUseRiskScore.mockReturnValue({ ...defaultProps, data }); + + const { queryByTestId } = render( + + + + ); + + expect(queryByTestId('risk-score-alerts')).toHaveTextContent(alertsCount.toString()); + }); } ); diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx index 066c4f29d2787..13899e88f38f9 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { useDispatch } from 'react-redux'; @@ -40,6 +40,7 @@ import { Loader } from '../../../../common/components/loader'; import { Panel } from '../../../../common/components/panel'; import * as commonI18n from '../common/translations'; import { usersActions } from '../../../../users/store'; +import { useNavigateToTimeline } from '../../detection_response/hooks/use_navigate_to_timeline'; const HOST_RISK_TABLE_QUERY_ID = 'hostRiskDashboardTable'; const HOST_RISK_KPI_QUERY_ID = 'headerHostRiskScoreKpiQuery'; @@ -90,8 +91,24 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc [dispatch, riskEntity] ); + const { openHostInTimeline, openUserInTimeline } = useNavigateToTimeline(); + + const openEntityInTimeline = useCallback( + (entityName: string) => { + if (riskEntity === RiskScoreEntity.host) { + openHostInTimeline({ hostName: entityName }); + } else if (riskEntity === RiskScoreEntity.user) { + openUserInTimeline({ userName: entityName }); + } + }, + [riskEntity, openHostInTimeline, openUserInTimeline] + ); + const { toggleStatus, setToggleStatus } = useQueryToggle(entity.tableQueryId); - const columns = useMemo(() => getRiskScoreColumns(riskEntity), [riskEntity]); + const columns = useMemo( + () => getRiskScoreColumns(riskEntity, openEntityInTimeline), + [riskEntity, openEntityInTimeline] + ); const [selectedSeverity, setSelectedSeverity] = useState([]); const getSecuritySolutionLinkProps = useGetSecuritySolutionLinkProps(); @@ -146,6 +163,7 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc }, timerange, riskEntity, + includeAlertsCount: true, }); useQueryInspector({ diff --git a/x-pack/plugins/security_solution/public/risk_score/components/translations.ts b/x-pack/plugins/security_solution/public/risk_score/components/translations.ts index 2016dd1d57990..8d845c6d99987 100644 --- a/x-pack/plugins/security_solution/public/risk_score/components/translations.ts +++ b/x-pack/plugins/security_solution/public/risk_score/components/translations.ts @@ -50,3 +50,7 @@ export const getRiskEntityTranslation = ( return riskEntity === RiskScoreEntity.host ? HOST : USER; }; + +export const ALERTS = i18n.translate('xpack.securitySolution.riskScore.overview.alerts', { + defaultMessage: 'Alerts', +}); diff --git a/x-pack/plugins/security_solution/public/risk_score/containers/all/index.test.tsx b/x-pack/plugins/security_solution/public/risk_score/containers/all/index.test.tsx index c9a8f448c352d..dc2b8dac58493 100644 --- a/x-pack/plugins/security_solution/public/risk_score/containers/all/index.test.tsx +++ b/x-pack/plugins/security_solution/public/risk_score/containers/all/index.test.tsx @@ -168,6 +168,8 @@ describe.each([RiskScoreEntity.host, RiskScoreEntity.user])( expect(mockSearch).toHaveBeenCalledWith({ defaultIndex: [`ml_${riskEntity}_risk_score_latest_default`], factoryQueryType: `${riskEntity}sRiskScore`, + riskScoreEntity: riskEntity, + includeAlertsCount: false, }); }); diff --git a/x-pack/plugins/security_solution/public/risk_score/containers/all/index.tsx b/x-pack/plugins/security_solution/public/risk_score/containers/all/index.tsx index b700f83f7f702..977bc10caecc5 100644 --- a/x-pack/plugins/security_solution/public/risk_score/containers/all/index.tsx +++ b/x-pack/plugins/security_solution/public/risk_score/containers/all/index.tsx @@ -45,6 +45,7 @@ export interface RiskScoreState): RiskScoreState => { const spaceId = useSpaceId(); const defaultIndex = spaceId @@ -158,6 +160,8 @@ export const useRiskScore = { expect(buildHostsRiskQuery).toHaveBeenCalledWith({ defaultIndex: ['ml_host_risk_score_latest_test-space'], filterQuery: { terms: { 'host.name': [hostName] } }, + riskScoreEntity: RiskScoreEntity.host, }); }); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.ts index fb4a4aa27353b..86a3cfae8b4f3 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.ts @@ -19,7 +19,11 @@ import type { } from '../../../../../../common/search_strategy/security_solution/hosts'; import type { HostRiskScore } from '../../../../../../common/search_strategy'; -import { getHostRiskIndex, buildHostNamesFilter } from '../../../../../../common/search_strategy'; +import { + RiskScoreEntity, + getHostRiskIndex, + buildHostNamesFilter, +} from '../../../../../../common/search_strategy'; import { inspectStringifyObject } from '../../../../../utils/build_query'; import type { SecuritySolutionFactory } from '../../types'; @@ -116,6 +120,7 @@ async function getHostRiskData( buildRiskScoreQuery({ defaultIndex: [getHostRiskIndex(spaceId)], filterQuery: buildHostNamesFilter(hostNames), + riskScoreEntity: RiskScoreEntity.host, }) ); return hostRiskResponse; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.test.ts new file mode 100644 index 0000000000000..58b2a55bc1594 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.test.ts @@ -0,0 +1,136 @@ +/* + * 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 { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; +import type { KibanaRequest } from '@kbn/core-http-server'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import { riskScore } from '.'; +import type { IEsSearchResponse } from '@kbn/data-plugin/public'; +import { allowedExperimentalValues } from '../../../../../../common/experimental_features'; +import type { + HostRiskScore, + RiskScoreRequestOptions, +} from '../../../../../../common/search_strategy'; +import { RiskScoreEntity, RiskSeverity } from '../../../../../../common/search_strategy'; +import type { EndpointAppContextService } from '../../../../../endpoint/endpoint_app_context_services'; +import type { EndpointAppContext } from '../../../../../endpoint/types'; +import * as buildQuery from './query.risk_score.dsl'; +import { get } from 'lodash/fp'; +import { ruleRegistryMocks } from '@kbn/rule-registry-plugin/server/mocks'; +import type { IRuleDataClient } from '@kbn/rule-registry-plugin/server'; + +export const mockSearchStrategyResponse: IEsSearchResponse = { + rawResponse: { + took: 1, + timed_out: false, + _shards: { + total: 2, + successful: 2, + skipped: 1, + failed: 0, + }, + hits: { + max_score: null, + hits: [ + { + _id: '4', + _index: 'index', + _source: { + '@timestamp': '1234567899', + host: { + name: 'testUsermame', + risk: { + rule_risks: [], + calculated_level: RiskSeverity.high, + calculated_score_norm: 75, + multipliers: [], + }, + }, + }, + }, + ], + }, + }, + isPartial: false, + isRunning: false, + total: 2, + loaded: 2, +}; + +const searchMock = jest.fn(); + +const mockDeps = { + esClient: {} as IScopedClusterClient, + ruleDataClient: { + ...(ruleRegistryMocks.createRuleDataClient('.alerts-security.alerts') as IRuleDataClient), + getReader: jest.fn((_options?: { namespace?: string }) => ({ + search: searchMock, + getDynamicIndexPattern: jest.fn(), + })), + }, + savedObjectsClient: {} as SavedObjectsClientContract, + endpointContext: { + logFactory: { + get: jest.fn().mockReturnValue({ + warn: jest.fn(), + }), + }, + config: jest.fn().mockResolvedValue({}), + experimentalFeatures: { + ...allowedExperimentalValues, + }, + service: {} as EndpointAppContextService, + } as EndpointAppContext, + request: {} as KibanaRequest, +}; + +export const mockOptions: RiskScoreRequestOptions = { + defaultIndex: ['logs-*'], + riskScoreEntity: RiskScoreEntity.host, + includeAlertsCount: true, +}; + +describe('buildRiskScoreQuery search strategy', () => { + const buildKpiRiskScoreQuery = jest.spyOn(buildQuery, 'buildRiskScoreQuery'); + + describe('buildDsl', () => { + test('should build dsl query', () => { + riskScore.buildDsl(mockOptions); + expect(buildKpiRiskScoreQuery).toHaveBeenCalledWith(mockOptions); + }); + }); + + test('should not enhance data when includeAlertsCount is false', async () => { + const result = await riskScore.parse( + { ...mockOptions, includeAlertsCount: false }, + mockSearchStrategyResponse, + mockDeps + ); + + expect(get('data[0].alertsCount', result)).toBeUndefined(); + }); + + test('should enhance data with alerts count', async () => { + const alertsCunt = 9999; + searchMock.mockReturnValue({ + aggregations: { + alertsByEntity: { + buckets: [ + { + key: 'testUsermame', + doc_count: alertsCunt, + }, + ], + }, + }, + }); + + const result = await riskScore.parse(mockOptions, mockSearchStrategyResponse, mockDeps); + + expect(get('data[0].alertsCount', result)).toBe(alertsCunt); + }); +}); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.ts index 0d2c01b735a5f..5e46ac2b4f440 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.ts @@ -5,12 +5,19 @@ * 2.0. */ -import type { IEsSearchResponse } from '@kbn/data-plugin/common'; +import type { IEsSearchResponse, SearchRequest } from '@kbn/data-plugin/common'; +import { get, getOr } from 'lodash/fp'; + +import type { IRuleDataClient } from '@kbn/rule-registry-plugin/server'; import type { SecuritySolutionFactory } from '../../types'; import type { RiskScoreRequestOptions, RiskQueries, + BucketItem, + HostRiskScore, + UserRiskScore, } from '../../../../../../common/search_strategy'; +import { RiskScoreEntity } from '../../../../../../common/search_strategy'; import { inspectStringifyObject } from '../../../../../utils/build_query'; import { buildRiskScoreQuery } from './query.risk_score.dsl'; import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants'; @@ -26,7 +33,14 @@ export const riskScore: SecuritySolutionFactory< return buildRiskScoreQuery(options); }, - parse: async (options: RiskScoreRequestOptions, response: IEsSearchResponse) => { + parse: async ( + options: RiskScoreRequestOptions, + response: IEsSearchResponse, + deps?: { + spaceId?: string; + ruleDataClient?: IRuleDataClient | null; + } + ) => { const inspect = { dsl: [inspectStringifyObject(buildRiskScoreQuery(options))], }; @@ -34,11 +48,65 @@ export const riskScore: SecuritySolutionFactory< const totalCount = getTotalCount(response.rawResponse.hits.total); const hits = response?.rawResponse?.hits?.hits; const data = hits?.map((hit) => hit._source) ?? []; + const nameField = options.riskScoreEntity === RiskScoreEntity.host ? 'host.name' : 'user.name'; + const names = data.map((risk) => get(nameField, risk) ?? ''); + + const enhancedData = + deps && options.includeAlertsCount + ? await enhanceData(data, names, nameField, deps.ruleDataClient, deps.spaceId) + : data; + return { ...response, inspect, totalCount, - data, + data: enhancedData, }; }, }; + +async function enhanceData( + data: Array, + names: string[], + nameField: string, + ruleDataClient?: IRuleDataClient | null, + spaceId?: string +): Promise> { + const ruleDataReader = ruleDataClient?.getReader({ namespace: spaceId }); + const query = getAlertsQueryForEntity(names, nameField); + + const response = await ruleDataReader?.search(query); + const buckets: BucketItem[] = getOr([], 'aggregations.alertsByEntity.buckets', response); + + const alertsCountByEntityName: Record = buckets.reduce( + (acc, { key, doc_count: count }) => ({ + ...acc, + [key]: count, + }), + {} + ); + + return data.map((risk) => ({ + ...risk, + alertsCount: alertsCountByEntityName[get(nameField, risk)] ?? 0, + })); +} + +const getAlertsQueryForEntity = (names: string[], nameField: string): SearchRequest => ({ + size: 0, + query: { + bool: { + filter: [ + { term: { 'kibana.alert.workflow_status': 'open' } }, + { terms: { [nameField]: names } }, + ], + }, + }, + aggs: { + alertsByEntity: { + terms: { + field: nameField, + }, + }, + }, +}); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/types.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/types.ts index 51529bc21d801..f222e2130ee26 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/types.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/types.ts @@ -11,6 +11,7 @@ import type { SavedObjectsClientContract, } from '@kbn/core/server'; import type { IEsSearchResponse, ISearchRequestParams } from '@kbn/data-plugin/common'; +import type { IRuleDataClient } from '@kbn/rule-registry-plugin/server'; import type { FactoryQueryTypes, StrategyRequestType, @@ -29,6 +30,7 @@ export interface SecuritySolutionFactory { endpointContext: EndpointAppContext; request: KibanaRequest; spaceId?: string; + ruleDataClient?: IRuleDataClient | null; } ) => Promise>; } diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.test.ts index 1f17bbfc870fc..18bc75edb5304 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.test.ts @@ -14,6 +14,7 @@ import type { UsersRequestOptions } from '../../../../../../common/search_strate import * as buildRiskQuery from '../../risk_score/all/query.risk_score.dsl'; import { get } from 'lodash/fp'; +import { RiskScoreEntity } from '../../../../../../common/search_strategy'; class IndexNotFoundException extends Error { meta: { body: { error: { type: string } } }; @@ -115,6 +116,7 @@ describe('allHosts search strategy', () => { expect(buildHostsRiskQuery).toHaveBeenCalledWith({ defaultIndex: ['ml_user_risk_score_latest_test-space'], filterQuery: { terms: { 'user.name': userName } }, + riskScoreEntity: RiskScoreEntity.user, }); }); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.ts index d5c13e0d2b52e..c936ad85f79e0 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.ts @@ -23,7 +23,11 @@ import type { import type { AllUsersAggEsItem } from '../../../../../../common/search_strategy/security_solution/users/common'; import { buildRiskScoreQuery } from '../../risk_score/all/query.risk_score.dsl'; import type { RiskSeverity, UserRiskScore } from '../../../../../../common/search_strategy'; -import { buildUserNamesFilter, getUserRiskIndex } from '../../../../../../common/search_strategy'; +import { + RiskScoreEntity, + buildUserNamesFilter, + getUserRiskIndex, +} from '../../../../../../common/search_strategy'; export const allUsers: SecuritySolutionFactory = { buildDsl: (options: UsersRequestOptions) => { @@ -123,6 +127,7 @@ async function getUserRiskData( buildRiskScoreQuery({ defaultIndex: [getUserRiskIndex(spaceId)], filterQuery: buildUserNamesFilter(userNames), + riskScoreEntity: RiskScoreEntity.user, }) ); return userRiskResponse; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts index faee7d6b6d8f3..1acb6687b8ace 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts @@ -10,6 +10,7 @@ import type { ISearchStrategy, PluginStart } from '@kbn/data-plugin/server'; import { shimHitsTotal } from '@kbn/data-plugin/server'; import { ENHANCED_ES_SEARCH_STRATEGY } from '@kbn/data-plugin/common'; import type { KibanaRequest } from '@kbn/core/server'; +import type { IRuleDataClient } from '@kbn/rule-registry-plugin/server'; import type { FactoryQueryTypes, StrategyResponseType, @@ -33,7 +34,8 @@ function assertValidRequestType( export const securitySolutionSearchStrategyProvider = ( data: PluginStart, endpointContext: EndpointAppContext, - getSpaceId?: (request: KibanaRequest) => string + getSpaceId?: (request: KibanaRequest) => string, + ruleDataClient?: IRuleDataClient | null ): ISearchStrategy, StrategyResponseType> => { const es = data.search.getSearchStrategy(ENHANCED_ES_SEARCH_STRATEGY); @@ -60,6 +62,7 @@ export const securitySolutionSearchStrategyProvider =