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 531dcfefe2f62..be1a577e684ad 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 0000000000000..7bba81268cd97 --- /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 0000000000000..68c0d5d0afa19 --- /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 7ae675bdd6111..6b5edc80d65af 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 5ad61b1b29b72..063997512e063 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 c6d819699a8db..5cea6eba48930 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 f0e532f9c97f4..f0ef8f8d00da7 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 8928d3aaf312b..5a3d2462e3e9a 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 301ea396e418d..ab77ce2ac8714 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 6e15e195a52a4..f4d2f0e7669c1 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 38f7836e26a42..8665d5b1cb3e8 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 d9ba56351779f..bf60d795c26f1 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 5b29548a94af4..1340297f241ef 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 a1c7020f222ee..5066a8c184e87 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 496d332ed8f76..e7db448e9d7c0 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 3942f7c41c909..3796c70233a53 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 914c3c93ff240..875556f12fb8d 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 e72369c97379d..3e6f2e570cc22 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 733a0d8728f3c..8d165184b3113 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 088c5851ca290..3be1dd65f48eb 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 649e0a6d24f1d..4393a2ae506eb 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 3d8332c771bd9..12c126e75014a 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 1f00c787b4e82..cc1c48e60aedc 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 6766f35d33f1d..067357734f640 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 94fcd69e962c6..6de861c18aa62 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 b114b283d624a..4eb72798264d5 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 96bcb5c426d1a..6506892c25287 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' }, + }, }, }, }, - }, -}); + }; +};