From bea88026b7af657c350ea66b63286b3eae7e7f55 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 9 May 2023 15:16:10 -0400 Subject: [PATCH] [8.8] [Security Solution] Add Search Bar to Security D&R and EA Dashboards (#156832) (#157115) # Backport This will backport the following commits from `main` to `8.8`: - [[Security Solution] Add Search Bar to Security D&R and EA Dashboards (#156832)](https://github.com/elastic/kibana/pull/156832) ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) Co-authored-by: Pablo Machado --- .../security_solution/risk_score/all/index.ts | 1 + .../hooks/use_global_filter_query.test.ts | 141 ++++++++++++++++++ .../common/hooks/use_global_filter_query.ts | 78 ++++++++++ .../risk_score_onboarding/translations.ts | 4 +- .../containers/risk_score/all/index.tsx | 1 + .../containers/risk_score/kpi/index.tsx | 4 +- .../cases_by_status/cases_by_status.tsx | 2 + .../cases_table/cases_table.tsx | 1 + .../host_alerts_table.test.tsx | 6 + .../host_alerts_table/host_alerts_table.tsx | 3 + .../use_host_alerts_items.ts | 39 +++-- .../rule_alerts_table.test.tsx | 6 + .../rule_alerts_table/rule_alerts_table.tsx | 4 + .../use_rule_alerts_items.test.ts | 16 ++ .../use_rule_alerts_items.ts | 40 +++-- .../detection_response/translations.ts | 17 +++ .../use_user_alerts_items.ts | 37 +++-- .../user_alerts_table.test.tsx | 6 + .../user_alerts_table/user_alerts_table.tsx | 3 + .../entity_analytics/anomalies/index.tsx | 1 + .../anomalies/translations.ts | 7 + .../entity_analytics/header/index.tsx | 4 + .../entity_analytics/risk_score/index.tsx | 18 ++- .../risk_score/translations.ts | 16 ++ .../pages/detection_response.test.tsx | 12 +- .../overview/pages/detection_response.tsx | 21 +-- .../overview/pages/entity_analytics.tsx | 18 +-- .../factory/risk_score/all/index.test.ts | 29 ++++ .../factory/risk_score/all/index.ts | 74 ++++++--- 29 files changed, 512 insertions(+), 97 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/hooks/use_global_filter_query.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/hooks/use_global_filter_query.ts 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 531dcfefe2f62a..be1a577e684ad6 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 @@ -15,6 +15,7 @@ export interface RiskScoreRequestOptions extends IEsSearchRequest { defaultIndex: string[]; riskScoreEntity: RiskScoreEntity; timerange?: TimerangeInput; + alertsTimerange?: TimerangeInput; includeAlertsCount?: boolean; onlyLatest?: boolean; pagination?: { diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_global_filter_query.test.ts b/x-pack/plugins/security_solution/public/common/hooks/use_global_filter_query.test.ts new file mode 100644 index 00000000000000..7bba81268cd97f --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/hooks/use_global_filter_query.test.ts @@ -0,0 +1,141 @@ +/* + * 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 { renderHook } from '@testing-library/react-hooks'; +import { TestProviders } from '../mock'; +import { useGlobalFilterQuery } from './use_global_filter_query'; +import type { Filter, Query } from '@kbn/es-query'; + +const DEFAULT_QUERY: Query = { query: '', language: 'kuery' }; + +const mockGlobalFiltersQuerySelector = jest.fn(); +const mockGlobalQuerySelector = jest.fn(); +const mockUseInvalidFilterQuery = jest.fn(); + +jest.mock('../store', () => { + const original = jest.requireActual('../store'); + return { + ...original, + inputsSelectors: { + ...original.inputsSelectors, + globalFiltersQuerySelector: () => mockGlobalFiltersQuerySelector, + globalQuerySelector: () => mockGlobalQuerySelector, + }, + }; +}); + +jest.mock('./use_invalid_filter_query', () => ({ + useInvalidFilterQuery: (...args: unknown[]) => mockUseInvalidFilterQuery(...args), +})); + +describe('useGlobalFilterQuery', () => { + beforeEach(() => { + mockGlobalFiltersQuerySelector.mockReturnValue([]); + mockGlobalQuerySelector.mockReturnValue(DEFAULT_QUERY); + }); + + it('returns filterQuery', () => { + const { result } = renderHook(() => useGlobalFilterQuery(), { wrapper: TestProviders }); + + expect(result.current.filterQuery).toEqual({ + bool: { must: [], filter: [], should: [], must_not: [] }, + }); + }); + + it('filters by KQL search', () => { + mockGlobalQuerySelector.mockReturnValue({ query: 'test: 123', language: 'kuery' }); + const { result } = renderHook(() => useGlobalFilterQuery(), { wrapper: TestProviders }); + + expect(result.current.filterQuery).toEqual({ + bool: { + must: [], + filter: [ + { + bool: { + minimum_should_match: 1, + should: [ + { + match: { + test: '123', + }, + }, + ], + }, + }, + ], + should: [], + must_not: [], + }, + }); + }); + + it('filters by global filters', () => { + const query = { + match_phrase: { + test: '1234', + }, + }; + const globalFilter: Filter[] = [ + { + meta: { + disabled: false, + }, + query, + }, + ]; + mockGlobalFiltersQuerySelector.mockReturnValue(globalFilter); + const { result } = renderHook(() => useGlobalFilterQuery(), { wrapper: TestProviders }); + + expect(result.current.filterQuery).toEqual({ + bool: { + must: [], + filter: [query], + should: [], + must_not: [], + }, + }); + }); + + it('filters by extra filter', () => { + const query = { + match_phrase: { + test: '12345', + }, + }; + const extraFilter: Filter = { + meta: { + disabled: false, + }, + query, + }; + + const { result } = renderHook(() => useGlobalFilterQuery({ extraFilter }), { + wrapper: TestProviders, + }); + + expect(result.current.filterQuery).toEqual({ + bool: { + must: [], + filter: [query], + should: [], + must_not: [], + }, + }); + }); + + it('displays the KQL error when query is invalid', () => { + mockGlobalQuerySelector.mockReturnValue({ query: ': :', language: 'kuery' }); + const { result } = renderHook(() => useGlobalFilterQuery(), { wrapper: TestProviders }); + + expect(result.current.filterQuery).toEqual(undefined); + expect(mockUseInvalidFilterQuery).toHaveBeenLastCalledWith( + expect.objectContaining({ + kqlError: expect.anything(), + }) + ); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_global_filter_query.ts b/x-pack/plugins/security_solution/public/common/hooks/use_global_filter_query.ts new file mode 100644 index 00000000000000..68c0d5d0afa195 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/hooks/use_global_filter_query.ts @@ -0,0 +1,78 @@ +/* + * 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 { useMemo } from 'react'; +import { getEsQueryConfig } from '@kbn/data-plugin/common'; +import type { DataViewBase, EsQueryConfig, Filter, Query } from '@kbn/es-query'; +import { buildEsQuery } from '@kbn/es-query'; +import { useGlobalTime } from '../containers/use_global_time'; +import { useKibana } from '../lib/kibana'; +import { inputsSelectors } from '../store'; +import { useDeepEqualSelector } from './use_selector'; +import { useInvalidFilterQuery } from './use_invalid_filter_query'; +import type { ESBoolQuery } from '../../../common/typed_json'; + +interface GlobalFilterQueryProps { + extraFilter?: Filter; + dataView?: DataViewBase; +} + +/** + * It builds a global filterQuery from KQL search bar and global filters. + * It also validates the query and shows a warning if it's invalid. + */ +export const useGlobalFilterQuery = ({ extraFilter, dataView }: GlobalFilterQueryProps = {}) => { + const { from, to } = useGlobalTime(); + const getGlobalFiltersQuerySelector = useMemo( + () => inputsSelectors.globalFiltersQuerySelector(), + [] + ); + const getGlobalQuerySelector = useMemo(() => inputsSelectors.globalQuerySelector(), []); + const query = useDeepEqualSelector(getGlobalQuerySelector); + const globalFilters = useDeepEqualSelector(getGlobalFiltersQuerySelector); + const { uiSettings } = useKibana().services; + + const filters = useMemo(() => { + const enabledFilters = globalFilters.filter((f) => f.meta.disabled === false); + + return extraFilter ? [...enabledFilters, extraFilter] : enabledFilters; + }, [extraFilter, globalFilters]); + + const { filterQuery, kqlError } = useMemo( + () => buildQueryOrError(query, filters, getEsQueryConfig(uiSettings), dataView), + [dataView, query, filters, uiSettings] + ); + + const filterQueryStringified = useMemo( + () => (filterQuery ? JSON.stringify(filterQuery) : undefined), + [filterQuery] + ); + + useInvalidFilterQuery({ + id: 'GlobalFilterQuery', // It prevents displaying multiple times the same error popup + filterQuery: filterQueryStringified, + kqlError, + query, + startDate: from, + endDate: to, + }); + + return { filterQuery }; +}; + +const buildQueryOrError = ( + query: Query, + filters: Filter[], + config: EsQueryConfig, + dataView?: DataViewBase +): { filterQuery?: ESBoolQuery; kqlError?: Error } => { + try { + return { filterQuery: buildEsQuery(dataView, [query], filters, config) }; + } catch (kqlError) { + return { kqlError }; + } +}; diff --git a/x-pack/plugins/security_solution/public/explore/components/risk_score/risk_score_onboarding/translations.ts b/x-pack/plugins/security_solution/public/explore/components/risk_score/risk_score_onboarding/translations.ts index 7ae675bdd61113..6b5edc80d65af6 100644 --- a/x-pack/plugins/security_solution/public/explore/components/risk_score/risk_score_onboarding/translations.ts +++ b/x-pack/plugins/security_solution/public/explore/components/risk_score/risk_score_onboarding/translations.ts @@ -30,14 +30,14 @@ export const USER_WARNING_TITLE = i18n.translate( export const HOST_WARNING_BODY = i18n.translate( 'xpack.securitySolution.riskScore.hostsDashboardWarningPanelBody', { - defaultMessage: `We haven't detected any host risk score data from the hosts in your environment. The data might need an hour to be generated after enabling the module.`, + defaultMessage: `We haven’t found any host risk score data. Check if you have any global filters in the global KQL search bar. If you have just enabled the host risk module, the risk engine might need an hour to generate host risk score data and display in this panel.`, } ); export const USER_WARNING_BODY = i18n.translate( 'xpack.securitySolution.riskScore.usersDashboardWarningPanelBody', { - defaultMessage: `We haven't detected any user risk score data from the users in your environment. The data might need an hour to be generated after enabling the module.`, + defaultMessage: `We haven’t found any user risk score data. Check if you have any global filters in the global KQL search bar. If you have just enabled the user risk module, the risk engine might need an hour to generate user risk score data and display in this panel.`, } ); diff --git a/x-pack/plugins/security_solution/public/explore/containers/risk_score/all/index.tsx b/x-pack/plugins/security_solution/public/explore/containers/risk_score/all/index.tsx index 5ad61b1b29b72a..063997512e0633 100644 --- a/x-pack/plugins/security_solution/public/explore/containers/risk_score/all/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/containers/risk_score/all/index.tsx @@ -175,6 +175,7 @@ export const useRiskScore = { toggleQuery={setToggleStatus} subtitle={} showInspectButton={false} + tooltip={CASES_BY_STATUS_SECTION_TOOLTIP} > diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/cases_table/cases_table.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/cases_table/cases_table.tsx index c6d819699a8db5..5cea6eba489300 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/cases_table/cases_table.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/cases_table/cases_table.tsx @@ -71,6 +71,7 @@ export const CasesTable = React.memo(() => { toggleQuery={setToggleStatus} subtitle={} showInspectButton={false} + tooltip={i18n.CASES_TABLE_SECTION_TOOLTIP} /> {toggleStatus && ( diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/host_alerts_table.test.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/host_alerts_table.test.tsx index f0e532f9c97f41..f0ef8f8d00da7f 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/host_alerts_table.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/host_alerts_table.test.tsx @@ -21,6 +21,12 @@ jest.mock('../../../../common/hooks/use_navigate_to_alerts_page_with_filters', ( }; }); +jest.mock('../../../../common/hooks/use_global_filter_query', () => { + return { + useGlobalFilterQuery: () => ({}), + }; +}); + type UseHostAlertsItemsReturn = ReturnType; const defaultUseHostAlertsItemsReturn: UseHostAlertsItemsReturn = { items: [], diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/host_alerts_table.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/host_alerts_table.tsx index 8928d3aaf312b2..5a3d2462e3e9ab 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/host_alerts_table.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/host_alerts_table.tsx @@ -38,6 +38,7 @@ import { SecurityCellActions, SecurityCellActionsTrigger, } from '../../../../common/components/cell_actions'; +import { useGlobalFilterQuery } from '../../../../common/hooks/use_global_filter_query'; interface HostAlertsTableProps { signalIndexName: string | null; @@ -51,6 +52,7 @@ const DETECTION_RESPONSE_HOST_SEVERITY_QUERY_ID = 'vulnerableHostsBySeverityQuer export const HostAlertsTable = React.memo(({ signalIndexName }: HostAlertsTableProps) => { const openAlertsPageWithFilters = useNavigateToAlertsPageWithFilters(); + const { filterQuery } = useGlobalFilterQuery(); const openHostInAlerts = useCallback( ({ hostName, severity }: { hostName: string; severity?: string }) => @@ -81,6 +83,7 @@ export const HostAlertsTable = React.memo(({ signalIndexName }: HostAlertsTableP skip: !toggleStatus, queryId: DETECTION_RESPONSE_HOST_SEVERITY_QUERY_ID, signalIndexName, + filterQuery, }); const columns = useMemo(() => getTableColumns(openHostInAlerts), [openHostInAlerts]); diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/use_host_alerts_items.ts b/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/use_host_alerts_items.ts index 301ea396e418d8..ab77ce2ac87148 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/use_host_alerts_items.ts +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/use_host_alerts_items.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { firstNonNullValue } from '../../../../../common/endpoint/models/ecs_safety_helpers'; import { useQueryInspector } from '../../../../common/components/page/manage_query'; @@ -14,6 +14,7 @@ import type { GenericBuckets } from '../../../../../common/search_strategy'; import { useQueryAlerts } from '../../../../detections/containers/detection_engine/alerts/use_query'; import { ALERTS_QUERY_NAMES } from '../../../../detections/containers/detection_engine/alerts/constants'; import { getPageCount, ITEMS_PER_PAGE } from '../utils'; +import type { ESBoolQuery } from '../../../../../common/typed_json'; const HOSTS_BY_SEVERITY_AGG = 'hostsBySeverity'; const defaultPagination = { @@ -30,6 +31,7 @@ export interface UseHostAlertsItemsProps { skip: boolean; queryId: string; signalIndexName: string | null; + filterQuery?: ESBoolQuery; } export interface HostAlertsItem { @@ -53,13 +55,28 @@ interface Pagination { currentPage: number; } -export const useHostAlertsItems: UseHostAlertsItems = ({ skip, queryId, signalIndexName }) => { +export const useHostAlertsItems: UseHostAlertsItems = ({ + skip, + queryId, + signalIndexName, + filterQuery, +}) => { const [updatedAt, setUpdatedAt] = useState(Date.now()); const [items, setItems] = useState([]); const [paginationData, setPaginationData] = useState(defaultPagination); - const { to, from, setQuery: setGlobalQuery, deleteQuery } = useGlobalTime(); + const query = useMemo( + () => + buildVulnerableHostAggregationQuery({ + from, + to, + currentPage: paginationData.currentPage, + filterQuery, + }), + [filterQuery, from, paginationData.currentPage, to] + ); + const { data, request, @@ -68,21 +85,15 @@ export const useHostAlertsItems: UseHostAlertsItems = ({ skip, queryId, signalIn loading, refetch: refetchQuery, } = useQueryAlerts<{}, AlertCountersBySeverityAndHostAggregation>({ - query: buildVulnerableHostAggregationQuery({ - from, - to, - currentPage: paginationData.currentPage, - }), + query, indexName: signalIndexName, skip, queryName: ALERTS_QUERY_NAMES.VULNERABLE_HOSTS, }); useEffect(() => { - setQuery( - buildVulnerableHostAggregationQuery({ from, to, currentPage: paginationData.currentPage }) - ); - }, [setQuery, from, to, paginationData.currentPage]); + setQuery(query); + }, [setQuery, paginationData.currentPage, query]); useEffect(() => { if (data == null || !data.aggregations) { @@ -138,7 +149,8 @@ export const buildVulnerableHostAggregationQuery = ({ from, to, currentPage, -}: TimeRange & { currentPage: number }) => { + filterQuery, +}: TimeRange & { currentPage: number; filterQuery?: ESBoolQuery }) => { const fromValue = ITEMS_PER_PAGE * currentPage; return { @@ -158,6 +170,7 @@ export const buildVulnerableHostAggregationQuery = ({ }, }, }, + ...(filterQuery ? [filterQuery] : []), ], }, }, diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/rule_alerts_table/rule_alerts_table.test.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/rule_alerts_table/rule_alerts_table.test.tsx index 6e15e195a52a4e..f4d2f0e7669c1d 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/rule_alerts_table/rule_alerts_table.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/rule_alerts_table/rule_alerts_table.test.tsx @@ -34,6 +34,12 @@ jest.mock('../../../../common/hooks/use_navigate_to_alerts_page_with_filters', ( }; }); +jest.mock('../../../../common/hooks/use_global_filter_query', () => { + return { + useGlobalFilterQuery: () => ({}), + }; +}); + type UseRuleAlertsItemsReturn = ReturnType; const defaultUseRuleAlertsItemsReturn: UseRuleAlertsItemsReturn = { items: [], diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/rule_alerts_table/rule_alerts_table.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/rule_alerts_table/rule_alerts_table.tsx index 38f7836e26a42b..8665d5b1cb3e8d 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/rule_alerts_table/rule_alerts_table.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/rule_alerts_table/rule_alerts_table.tsx @@ -39,6 +39,7 @@ import { BUTTON_CLASS as INSPECT_BUTTON_CLASS } from '../../../../common/compone import { LastUpdatedAt } from '../../../../common/components/last_updated_at'; import { FormattedCount } from '../../../../common/components/formatted_number'; import { SecurityCellActions } from '../../../../common/components/cell_actions'; +import { useGlobalFilterQuery } from '../../../../common/hooks/use_global_filter_query'; export interface RuleAlertsTableProps { signalIndexName: string | null; @@ -134,10 +135,13 @@ export const getTableColumns: GetTableColumns = ({ export const RuleAlertsTable = React.memo(({ signalIndexName }) => { const { getAppUrl, navigateTo } = useNavigation(); const { toggleStatus, setToggleStatus } = useQueryToggle(DETECTION_RESPONSE_RULE_ALERTS_QUERY_ID); + const { filterQuery } = useGlobalFilterQuery(); + const { items, isLoading, updatedAt } = useRuleAlertsItems({ signalIndexName, queryId: DETECTION_RESPONSE_RULE_ALERTS_QUERY_ID, skip: !toggleStatus, + filterQuery, }); const openAlertsPageWithFilter = useNavigateToAlertsPageWithFilters(); diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/rule_alerts_table/use_rule_alerts_items.test.ts b/x-pack/plugins/security_solution/public/overview/components/detection_response/rule_alerts_table/use_rule_alerts_items.test.ts index d9ba56351779f0..bf60d795c26f16 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/rule_alerts_table/use_rule_alerts_items.test.ts +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/rule_alerts_table/use_rule_alerts_items.test.ts @@ -17,6 +17,7 @@ import { } from './mock_data'; import type { UseRuleAlertsItems, UseRuleAlertsItemsProps } from './use_rule_alerts_items'; import { useRuleAlertsItems } from './use_rule_alerts_items'; +import type { ESBoolQuery } from '../../../../../common/typed_json'; const dateNow = new Date('2022-04-08T12:00:00.000Z').valueOf(); const mockDateNow = jest.fn().mockReturnValue(dateNow); @@ -129,4 +130,19 @@ describe('useRuleAlertsItems', () => { updatedAt: dateNow, }); }); + + it('should add filterQuery to query', () => { + const filterQuery: ESBoolQuery = { + bool: { + filter: [{ match_phrase: { test: '123' } }], + must: [], + must_not: [], + should: [], + }, + }; + + renderUseRuleAlertsItems({ filterQuery }); + + expect(mockUseQueryAlerts.mock.calls[0][0].query.query.bool.filter).toContain(filterQuery); + }); }); diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/rule_alerts_table/use_rule_alerts_items.ts b/x-pack/plugins/security_solution/public/overview/components/detection_response/rule_alerts_table/use_rule_alerts_items.ts index 5b29548a94af4b..1340297f241eff 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/rule_alerts_table/use_rule_alerts_items.ts +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/rule_alerts_table/use_rule_alerts_items.ts @@ -5,12 +5,13 @@ * 2.0. */ -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useState, useMemo } from 'react'; import type { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; import { useGlobalTime } from '../../../../common/containers/use_global_time'; import { useQueryAlerts } from '../../../../detections/containers/detection_engine/alerts/use_query'; import { ALERTS_QUERY_NAMES } from '../../../../detections/containers/detection_engine/alerts/constants'; import { useQueryInspector } from '../../../../common/components/page/manage_query'; +import type { ESBoolQuery } from '../../../../../common/typed_json'; // Formatted item result export interface RuleAlertsItem { @@ -48,13 +49,22 @@ export interface SeverityRuleAlertsAggsResponse { }; } -const getSeverityRuleAlertsQuery = ({ from, to }: { from: string; to: string }) => ({ +const getSeverityRuleAlertsQuery = ({ + from, + to, + filterQuery, +}: { + from: string; + to: string; + filterQuery?: ESBoolQuery; +}) => ({ size: 0, query: { bool: { filter: [ { term: { 'kibana.alert.workflow_status': 'open' } }, { range: { '@timestamp': { gte: from, lte: to } } }, + ...(filterQuery ? [filterQuery] : []), ], }, }, @@ -107,6 +117,7 @@ export interface UseRuleAlertsItemsProps { queryId: string; signalIndexName: string | null; skip?: boolean; + filterQuery?: ESBoolQuery; } export type UseRuleAlertsItems = (props: UseRuleAlertsItemsProps) => { items: RuleAlertsItem[]; @@ -118,11 +129,22 @@ export const useRuleAlertsItems: UseRuleAlertsItems = ({ queryId, signalIndexName, skip = false, + filterQuery, }) => { const [items, setItems] = useState([]); const [updatedAt, setUpdatedAt] = useState(Date.now()); const { to, from, deleteQuery, setQuery } = useGlobalTime(); + const query = useMemo( + () => + getSeverityRuleAlertsQuery({ + from, + to, + filterQuery, + }), + [filterQuery, from, to] + ); + const { loading: isLoading, data, @@ -131,23 +153,15 @@ export const useRuleAlertsItems: UseRuleAlertsItems = ({ request, refetch: refetchQuery, } = useQueryAlerts<{}, SeverityRuleAlertsAggsResponse>({ - query: getSeverityRuleAlertsQuery({ - from, - to, - }), + query, indexName: signalIndexName, skip, queryName: ALERTS_QUERY_NAMES.BY_SEVERITY, }); useEffect(() => { - setAlertsQuery( - getSeverityRuleAlertsQuery({ - from, - to, - }) - ); - }, [setAlertsQuery, from, to]); + setAlertsQuery(query); + }, [setAlertsQuery, query]); useEffect(() => { if (data == null) { diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/translations.ts b/x-pack/plugins/security_solution/public/overview/components/detection_response/translations.ts index a1c7020f222eed..5066a8c184e875 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/translations.ts +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/translations.ts @@ -73,11 +73,13 @@ export const UPDATING = i18n.translate('xpack.securitySolution.detectionResponse export const UPDATED = i18n.translate('xpack.securitySolution.detectionResponse.updated', { defaultMessage: 'Updated', }); + export const CASES = (totalCases: number) => i18n.translate('xpack.securitySolution.detectionResponse.casesByStatus.totalCases', { values: { totalCases }, defaultMessage: 'total {totalCases, plural, =1 {case} other {cases}}', }); + export const CASES_BY_STATUS_SECTION_TITLE = i18n.translate( 'xpack.securitySolution.detectionResponse.casesByStatusSectionTitle', { @@ -85,6 +87,13 @@ export const CASES_BY_STATUS_SECTION_TITLE = i18n.translate( } ); +export const CASES_BY_STATUS_SECTION_TOOLTIP = i18n.translate( + 'xpack.securitySolution.detectionResponse.casesByStatusSectionTooltip', + { + defaultMessage: 'The cases table is not filterable via the SIEM global KQL search.', + } +); + export const VIEW_CASES = i18n.translate('xpack.securitySolution.detectionResponse.viewCases', { defaultMessage: 'View cases', }); @@ -117,6 +126,14 @@ export const CASES_TABLE_SECTION_TITLE = i18n.translate( } ); +export const CASES_TABLE_SECTION_TOOLTIP = i18n.translate( + 'xpack.securitySolution.detectionResponse.caseSectionTooltip', + { + defaultMessage: + 'The recently created cases table is not filterable via the SIEM global KQL search.', + } +); + export const NO_ALERTS_FOUND = i18n.translate( 'xpack.securitySolution.detectionResponse.noRuleAlerts', { diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/use_user_alerts_items.ts b/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/use_user_alerts_items.ts index 496d332ed8f764..e7db448e9d7c0f 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/use_user_alerts_items.ts +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/use_user_alerts_items.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { firstNonNullValue } from '../../../../../common/endpoint/models/ecs_safety_helpers'; import { useQueryInspector } from '../../../../common/components/page/manage_query'; @@ -14,6 +14,7 @@ import type { GenericBuckets } from '../../../../../common/search_strategy'; import { useQueryAlerts } from '../../../../detections/containers/detection_engine/alerts/use_query'; import { ALERTS_QUERY_NAMES } from '../../../../detections/containers/detection_engine/alerts/constants'; import { getPageCount, ITEMS_PER_PAGE } from '../utils'; +import type { ESBoolQuery } from '../../../../../common/typed_json'; const USERS_BY_SEVERITY_AGG = 'usersBySeverity'; const defaultPagination = { @@ -30,6 +31,7 @@ export interface UseUserAlertsItemsProps { skip: boolean; queryId: string; signalIndexName: string | null; + filterQuery?: ESBoolQuery; } export interface UserAlertsItem { @@ -53,12 +55,27 @@ interface Pagination { currentPage: number; } -export const useUserAlertsItems: UseUserAlertsItems = ({ skip, queryId, signalIndexName }) => { +export const useUserAlertsItems: UseUserAlertsItems = ({ + skip, + queryId, + signalIndexName, + filterQuery, +}) => { const [updatedAt, setUpdatedAt] = useState(Date.now()); const [items, setItems] = useState([]); const [paginationData, setPaginationData] = useState(defaultPagination); const { to, from, setQuery: setGlobalQuery, deleteQuery } = useGlobalTime(); + const query = useMemo( + () => + buildVulnerableUserAggregationQuery({ + from, + to, + currentPage: paginationData.currentPage, + filterQuery, + }), + [filterQuery, from, paginationData.currentPage, to] + ); const { setQuery, @@ -68,21 +85,15 @@ export const useUserAlertsItems: UseUserAlertsItems = ({ skip, queryId, signalIn response, refetch: refetchQuery, } = useQueryAlerts<{}, AlertCountersBySeverityAggregation>({ - query: buildVulnerableUserAggregationQuery({ - from, - to, - currentPage: paginationData.currentPage, - }), + query, indexName: signalIndexName, skip, queryName: ALERTS_QUERY_NAMES.VULNERABLE_USERS, }); useEffect(() => { - setQuery( - buildVulnerableUserAggregationQuery({ from, to, currentPage: paginationData.currentPage }) - ); - }, [setQuery, from, to, paginationData.currentPage]); + setQuery(query); + }, [setQuery, paginationData.currentPage, query]); useEffect(() => { if (data == null || !data.aggregations) { @@ -138,7 +149,8 @@ export const buildVulnerableUserAggregationQuery = ({ from, to, currentPage, -}: TimeRange & { currentPage: number }) => { + filterQuery, +}: TimeRange & { currentPage: number; filterQuery?: ESBoolQuery }) => { const fromValue = ITEMS_PER_PAGE * currentPage; return { @@ -158,6 +170,7 @@ export const buildVulnerableUserAggregationQuery = ({ }, }, }, + ...(filterQuery ? [filterQuery] : []), ], }, }, diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/user_alerts_table.test.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/user_alerts_table.test.tsx index 3942f7c41c9094..3796c70233a538 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/user_alerts_table.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/user_alerts_table.test.tsx @@ -33,6 +33,12 @@ jest.mock('../../../../common/hooks/use_navigate_to_alerts_page_with_filters', ( }; }); +jest.mock('../../../../common/hooks/use_global_filter_query', () => { + return { + useGlobalFilterQuery: () => ({}), + }; +}); + type UseUserAlertsItemsReturn = ReturnType; const defaultUseUserAlertsItemsReturn: UseUserAlertsItemsReturn = { items: [], diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/user_alerts_table.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/user_alerts_table.tsx index 914c3c93ff240b..875556f12fb8db 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/user_alerts_table.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/user_alerts_table.tsx @@ -35,6 +35,7 @@ import { ITEMS_PER_PAGE, SEVERITY_COLOR } from '../utils'; import type { UserAlertsItem } from './use_user_alerts_items'; import { useUserAlertsItems } from './use_user_alerts_items'; import { SecurityCellActions } from '../../../../common/components/cell_actions'; +import { useGlobalFilterQuery } from '../../../../common/hooks/use_global_filter_query'; interface UserAlertsTableProps { signalIndexName: string | null; @@ -48,6 +49,7 @@ const DETECTION_RESPONSE_USER_SEVERITY_QUERY_ID = 'vulnerableUsersBySeverityQuer export const UserAlertsTable = React.memo(({ signalIndexName }: UserAlertsTableProps) => { const openAlertsPageWithFilters = useNavigateToAlertsPageWithFilters(); + const { filterQuery } = useGlobalFilterQuery(); const openUserInAlerts = useCallback( ({ userName, severity }: { userName: string; severity?: string }) => @@ -78,6 +80,7 @@ export const UserAlertsTable = React.memo(({ signalIndexName }: UserAlertsTableP skip: !toggleStatus, queryId: DETECTION_RESPONSE_USER_SEVERITY_QUERY_ID, signalIndexName, + filterQuery, }); const columns = useMemo(() => getTableColumns(openUserInAlerts), [openUserInAlerts]); diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.tsx index e72369c97379d9..3e6f2e570cc22c 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.tsx @@ -129,6 +129,7 @@ export const EntityAnalyticsAnomalies = () => { subtitle={} toggleStatus={toggleStatus} toggleQuery={setToggleStatus} + tooltip={i18n.ANOMALIES_TOOLTIP} > diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/translations.ts b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/translations.ts index 733a0d8728f3c7..8d165184b31136 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/translations.ts +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/translations.ts @@ -97,3 +97,10 @@ export const ANOMALY_DETECTION_DOCS = i18n.translate( defaultMessage: 'Anomaly Detection with Machine Learning', } ); + +export const ANOMALIES_TOOLTIP = i18n.translate( + 'xpack.securitySolution.entityAnalytics.anomalies.anomaliesTooltip', + { + defaultMessage: 'The anomalies table is not filterable via the SIEM global KQL search.', + } +); diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/header/index.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/header/index.tsx index 088c5851ca2902..3be1dd65f48eb4 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/header/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/header/index.tsx @@ -33,6 +33,7 @@ import { ENTITY_ANALYTICS_ANOMALIES_PANEL } from '../anomalies'; import { isJobStarted } from '../../../../../common/machine_learning/helpers'; import { FormattedCount } from '../../../../common/components/formatted_number'; import { SEVERITY_COLOR } from '../../detection_response/utils'; +import { useGlobalFilterQuery } from '../../../../common/hooks/use_global_filter_query'; const StyledEuiTitle = styled(EuiTitle)` color: ${({ theme: { eui } }) => SEVERITY_COLOR.critical}; @@ -43,6 +44,7 @@ const USER_RISK_QUERY_ID = 'userRiskScoreKpiQuery'; export const EntityAnalyticsHeader = () => { const { from, to } = useGlobalTime(); + const { filterQuery } = useGlobalFilterQuery(); const timerange = useMemo( () => ({ from, @@ -59,6 +61,7 @@ export const EntityAnalyticsHeader = () => { } = useRiskScoreKpi({ timerange, riskEntity: RiskScoreEntity.host, + filterQuery, }); const { @@ -67,6 +70,7 @@ export const EntityAnalyticsHeader = () => { refetch: refetchUserRiskScore, inspect: inspectUserRiskScore, } = useRiskScoreKpi({ + filterQuery, timerange, riskEntity: RiskScoreEntity.user, }); 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 649e0a6d24f1d2..4393a2ae506eb1 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 @@ -27,13 +27,14 @@ import { useRefetchQueries } from '../../../../common/hooks/use_refetch_queries' import { Loader } from '../../../../common/components/loader'; import { Panel } from '../../../../common/components/panel'; import * as commonI18n from '../common/translations'; - +import * as i18n from './translations'; import { useEntityInfo } from './use_entity'; import { RiskScoreHeaderContent } from './header_content'; import { ChartContent } from './chart_content'; import { useNavigateToAlertsPageWithFilters } from '../../../../common/hooks/use_navigate_to_alerts_page_with_filters'; import { getRiskEntityTranslation } from './translations'; import { useKibana } from '../../../../common/lib/kibana'; +import { useGlobalFilterQuery } from '../../../../common/hooks/use_global_filter_query'; const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskScoreEntity }) => { const { deleteQuery, setQuery, from, to } = useGlobalTime(); @@ -69,10 +70,13 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc const severityFilter = useMemo(() => { const [filter] = generateSeverityFilter(selectedSeverity, riskEntity); - - return filter ? JSON.stringify(filter.query) : undefined; + return filter ? filter : undefined; }, [riskEntity, selectedSeverity]); + const { filterQuery } = useGlobalFilterQuery({ + extraFilter: severityFilter, + }); + const timerange = useMemo( () => ({ from, @@ -87,7 +91,7 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc refetch: refetchKpi, inspect: inspectKpi, } = useRiskScoreKpi({ - filterQuery: severityFilter, + filterQuery, skip: !toggleStatus, timerange, riskEntity, @@ -110,7 +114,7 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc isLicenseValid, isModuleEnabled, } = useRiskScore({ - filterQuery: severityFilter, + filterQuery, skip: !toggleStatus, pagination: { cursorStart: 0, @@ -174,8 +178,8 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc toggleQuery={setToggleStatus} tooltip={ riskEntity === RiskScoreEntity.host - ? commonI18n.HOST_RISK_TABLE_TOOLTIP - : commonI18n.USER_RISK_TABLE_TOOLTIP + ? i18n.HOST_RISK_TABLE_TOOLTIP + : i18n.USER_RISK_TABLE_TOOLTIP } tooltipTitle={commonI18n.RISK_TABLE_TOOLTIP_TITLE} > diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/translations.ts b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/translations.ts index 3d8332c771bd94..12c126e75014af 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/translations.ts +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/translations.ts @@ -41,3 +41,19 @@ export const LEARN_MORE = i18n.translate( defaultMessage: 'Learn more', } ); + +export const HOST_RISK_TABLE_TOOLTIP = i18n.translate( + 'xpack.securitySolution.entityAnalytics.riskDashboard.hostsTableTooltip', + { + defaultMessage: + 'The Host Risk Score panel displays the list of risky hosts and their latest risk score. You may filter this list using global filters in the KQL search bar. The time-range picker filter will display Alerts within the selected time range only and does not filter the list of risky hosts.', + } +); + +export const USER_RISK_TABLE_TOOLTIP = i18n.translate( + 'xpack.securitySolution.entityAnalytics.riskDashboard.usersTableTooltip', + { + defaultMessage: + 'The User Risk Score panel displays the list of risky users and their latest risk score. You may filter this list using global filters in the KQL search bar. The time-range picker filter will display Alerts within the selected time range only and does not filter the list of risky users.', + } +); diff --git a/x-pack/plugins/security_solution/public/overview/pages/detection_response.test.tsx b/x-pack/plugins/security_solution/public/overview/pages/detection_response.test.tsx index 1f00c787b4e821..cc1c48e60aedc6 100644 --- a/x-pack/plugins/security_solution/public/overview/pages/detection_response.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/pages/detection_response.test.tsx @@ -37,7 +37,11 @@ jest.mock('../components/detection_response/cases_by_status', () => ({ })); jest.mock('../../common/components/search_bar', () => ({ - SiemSearchBar: () =>
, + SiemSearchBar: () =>
, +})); + +jest.mock('../../common/components/filters_global', () => ({ + FiltersGlobal: ({ children }: { children: React.ReactNode }) =>
{children}
, })); const defaultUseSourcererReturn = { @@ -97,7 +101,7 @@ describe('DetectionResponse', () => { ); expect(result.queryByTestId('detectionResponsePage')).toBeInTheDocument(); - expect(result.queryByTestId('mock_globalDatePicker')).toBeInTheDocument(); + expect(result.queryByTestId('mock_globalSearchBar')).toBeInTheDocument(); expect(result.queryByTestId('detectionResponseSections')).toBeInTheDocument(); expect(result.queryByTestId('detectionResponseLoader')).not.toBeInTheDocument(); expect(result.getByText('Detection & Response')).toBeInTheDocument(); @@ -119,7 +123,7 @@ describe('DetectionResponse', () => { expect(result.getByTestId('siem-landing-page')).toBeInTheDocument(); expect(result.queryByTestId('detectionResponsePage')).not.toBeInTheDocument(); - expect(result.queryByTestId('mock_globalDatePicker')).not.toBeInTheDocument(); + expect(result.queryByTestId('mock_globalSearchBar')).not.toBeInTheDocument(); }); it('should render loader if sourcerer is loading', () => { @@ -137,7 +141,7 @@ describe('DetectionResponse', () => { ); expect(result.queryByTestId('detectionResponsePage')).toBeInTheDocument(); - expect(result.queryByTestId('mock_globalDatePicker')).toBeInTheDocument(); + expect(result.queryByTestId('mock_globalSearchBar')).toBeInTheDocument(); expect(result.queryByTestId('detectionResponseLoader')).toBeInTheDocument(); expect(result.queryByTestId('detectionResponseSections')).not.toBeInTheDocument(); }); diff --git a/x-pack/plugins/security_solution/public/overview/pages/detection_response.tsx b/x-pack/plugins/security_solution/public/overview/pages/detection_response.tsx index 6766f35d33f1d4..067357734f640c 100644 --- a/x-pack/plugins/security_solution/public/overview/pages/detection_response.tsx +++ b/x-pack/plugins/security_solution/public/overview/pages/detection_response.tsx @@ -29,8 +29,11 @@ import * as i18n from './translations'; import { CasesTable } from '../components/detection_response/cases_table'; import { CasesByStatus } from '../components/detection_response/cases_by_status'; import { NoPrivileges } from '../../common/components/no_privileges'; +import { FiltersGlobal } from '../../common/components/filters_global'; +import { useGlobalFilterQuery } from '../../common/hooks/use_global_filter_query'; const DetectionResponseComponent = () => { + const { filterQuery } = useGlobalFilterQuery(); const { indicesExist, indexPattern, loading: isSourcererLoading } = useSourcererDataView(); const { signalIndexName } = useSignalIndex(); const { hasKibanaREAD, hasIndexRead } = useAlertsPrivileges(); @@ -45,16 +48,11 @@ const DetectionResponseComponent = () => { <> {indicesExist ? ( <> + + + - - - - + {isSourcererLoading ? ( ) : ( @@ -63,7 +61,10 @@ const DetectionResponseComponent = () => { {canReadAlerts && ( - + )} {canReadCases && ( diff --git a/x-pack/plugins/security_solution/public/overview/pages/entity_analytics.tsx b/x-pack/plugins/security_solution/public/overview/pages/entity_analytics.tsx index 94fcd69e962c6b..6de861c18aa621 100644 --- a/x-pack/plugins/security_solution/public/overview/pages/entity_analytics.tsx +++ b/x-pack/plugins/security_solution/public/overview/pages/entity_analytics.tsx @@ -24,6 +24,7 @@ import { EntityAnalyticsHeader } from '../components/entity_analytics/header'; import { EntityAnalyticsAnomalies } from '../components/entity_analytics/anomalies'; import { SiemSearchBar } from '../../common/components/search_bar'; import { InputsModelId } from '../../common/store/inputs/constants'; +import { FiltersGlobal } from '../../common/components/filters_global'; const EntityAnalyticsComponent = () => { const { indicesExist, loading: isSourcererLoading, indexPattern } = useSourcererDataView(); @@ -33,17 +34,14 @@ const EntityAnalyticsComponent = () => { <> {indicesExist ? ( <> + {isPlatinumOrTrialLicense && capabilitiesFetched && ( + + + + )} - - {isPlatinumOrTrialLicense && capabilitiesFetched && ( - - )} - + + {!isPlatinumOrTrialLicense && capabilitiesFetched ? ( ) : isSourcererLoading ? ( 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 index b114b283d624ac..4eb72798264d57 100644 --- 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 @@ -97,6 +97,10 @@ export const mockOptions: RiskScoreRequestOptions = { describe('buildRiskScoreQuery search strategy', () => { const buildKpiRiskScoreQuery = jest.spyOn(buildQuery, 'buildRiskScoreQuery'); + afterEach(() => { + jest.clearAllMocks(); + }); + describe('buildDsl', () => { test('should build dsl query', () => { riskScore.buildDsl(mockOptions); @@ -167,4 +171,29 @@ describe('buildRiskScoreQuery search strategy', () => { expect(get('data[0].oldestAlertTimestamp', result)).toBe(oldestAlertTimestamp); }); + + test('should filter enhance query by time range', async () => { + await riskScore.parse( + { + ...mockOptions, + alertsTimerange: { + from: 'now-5m', + to: 'now', + interval: '1m', + }, + }, + mockSearchStrategyResponse, + mockDeps + ); + + expect(searchMock.mock.calls[0][0].query.bool.filter).toEqual( + expect.arrayContaining([ + { + range: { + '@timestamp': { format: 'strict_date_optional_time', gte: 'now-5m', lte: 'now' }, + }, + }, + ]) + ); + }); }); 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 96bcb5c426d1a7..6506892c25287e 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,9 +5,8 @@ * 2.0. */ -import type { IEsSearchResponse, SearchRequest } from '@kbn/data-plugin/common'; +import type { IEsSearchResponse, SearchRequest, TimeRange } from '@kbn/data-plugin/common'; import { get, getOr } from 'lodash/fp'; - import type { IRuleDataClient } from '@kbn/rule-registry-plugin/server'; import type { AggregationsMinAggregate } from '@elastic/elasticsearch/lib/api/types'; import type { SecuritySolutionFactory } from '../../types'; @@ -54,7 +53,14 @@ export const riskScore: SecuritySolutionFactory< const enhancedData = deps && options.includeAlertsCount - ? await enhanceData(data, names, nameField, deps.ruleDataClient, deps.spaceId) + ? await enhanceData( + data, + names, + nameField, + deps.ruleDataClient, + deps.spaceId, + options.alertsTimerange + ) : data; return { @@ -75,10 +81,11 @@ async function enhanceData( names: string[], nameField: string, ruleDataClient?: IRuleDataClient | null, - spaceId?: string + spaceId?: string, + timerange?: TimeRange ): Promise> { const ruleDataReader = ruleDataClient?.getReader({ namespace: spaceId }); - const query = getAlertsQueryForEntity(names, nameField); + const query = getAlertsQueryForEntity(names, nameField, timerange); const response = await ruleDataReader?.search(query); const buckets: EnhancedDataBucket[] = getOr([], 'aggregations.alertsByEntity.buckets', response); @@ -101,26 +108,45 @@ async function enhanceData( })); } -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, +const getAlertsQueryForEntity = ( + names: string[], + nameField: string, + timerange: TimeRange | undefined +): SearchRequest => { + return { + size: 0, + query: { + bool: { + filter: [ + { term: { 'kibana.alert.workflow_status': 'open' } }, + { terms: { [nameField]: names } }, + ...(timerange + ? [ + { + range: { + '@timestamp': { + gte: timerange.from, + lte: timerange.to, + format: 'strict_date_optional_time', + }, + }, + }, + ] + : []), + ], }, - aggs: { - oldestAlertTimestamp: { - min: { field: '@timestamp' }, + }, + aggs: { + alertsByEntity: { + terms: { + field: nameField, + }, + aggs: { + oldestAlertTimestamp: { + min: { field: '@timestamp' }, + }, }, }, }, - }, -}); + }; +};