diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/analyzer_preview.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/analyzer_preview.test.tsx index 67d7438e8bb68..724eaf979d8fd 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/analyzer_preview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/analyzer_preview.test.tsx @@ -8,7 +8,7 @@ import { render } from '@testing-library/react'; import React from 'react'; import { TestProviders } from '../../../../common/mock'; -import { useAlertPrevalenceFromProcessTree } from '../../../../common/containers/alerts/use_alert_prevalence_from_process_tree'; +import { useAlertPrevalenceFromProcessTree } from '../../shared/hooks/use_alert_prevalence_from_process_tree'; import { useTimelineDataFilters } from '../../../../timelines/containers/use_timeline_data_filters'; import { mockContextValue } from '../../shared/mocks/mock_context'; import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser'; @@ -17,7 +17,7 @@ import { AnalyzerPreview } from './analyzer_preview'; import { ANALYZER_PREVIEW_TEST_ID } from './test_ids'; import * as mock from '../mocks/mock_analyzer_data'; -jest.mock('../../../../common/containers/alerts/use_alert_prevalence_from_process_tree', () => ({ +jest.mock('../../shared/hooks/use_alert_prevalence_from_process_tree', () => ({ useAlertPrevalenceFromProcessTree: jest.fn(), })); const mockUseAlertPrevalenceFromProcessTree = useAlertPrevalenceFromProcessTree as jest.Mock; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/analyzer_preview.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/analyzer_preview.tsx index efae023e0d092..bbdcc4f8e3d6b 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/analyzer_preview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/analyzer_preview.tsx @@ -13,8 +13,8 @@ import { ANALYZER_PREVIEW_TEST_ID, ANALYZER_PREVIEW_LOADING_TEST_ID } from './te import { getTreeNodes } from '../utils/analyzer_helpers'; import { ANCESTOR_ID, RULE_INDICES } from '../../shared/constants/field_names'; import { useDocumentDetailsContext } from '../../shared/context'; -import { useAlertPrevalenceFromProcessTree } from '../../../../common/containers/alerts/use_alert_prevalence_from_process_tree'; -import type { StatsNode } from '../../../../common/containers/alerts/use_alert_prevalence_from_process_tree'; +import { useAlertPrevalenceFromProcessTree } from '../../shared/hooks/use_alert_prevalence_from_process_tree'; +import type { StatsNode } from '../../shared/hooks/use_alert_prevalence_from_process_tree'; import { isActiveTimeline } from '../../../../helpers'; import { getField } from '../../shared/utils'; import { useTimelineDataFilters } from '../../../../timelines/containers/use_timeline_data_filters'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/analyzer_preview_container.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/analyzer_preview_container.test.tsx index 5ce6fcebae76b..7dae9400358c5 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/analyzer_preview_container.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/analyzer_preview_container.test.tsx @@ -13,7 +13,7 @@ import { mockContextValue } from '../../shared/mocks/mock_context'; import { AnalyzerPreviewContainer } from './analyzer_preview_container'; import { useIsInvestigateInResolverActionEnabled } from '../../../../detections/components/alerts_table/timeline_actions/investigate_in_resolver'; import { ANALYZER_PREVIEW_TEST_ID } from './test_ids'; -import { useAlertPrevalenceFromProcessTree } from '../../../../common/containers/alerts/use_alert_prevalence_from_process_tree'; +import { useAlertPrevalenceFromProcessTree } from '../../shared/hooks/use_alert_prevalence_from_process_tree'; import * as mock from '../mocks/mock_analyzer_data'; import { EXPANDABLE_PANEL_CONTENT_TEST_ID, @@ -28,7 +28,7 @@ import { useInvestigateInTimeline } from '../../../../detections/components/aler jest.mock( '../../../../detections/components/alerts_table/timeline_actions/investigate_in_resolver' ); -jest.mock('../../../../common/containers/alerts/use_alert_prevalence_from_process_tree'); +jest.mock('../../shared/hooks/use_alert_prevalence_from_process_tree'); jest.mock( '../../../../detections/components/alerts_table/timeline_actions/use_investigate_in_timeline' ); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/insights_section.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/insights_section.test.tsx index eb1af2a74b8df..96dff8150e654 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/insights_section.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/insights_section.test.tsx @@ -25,13 +25,13 @@ import { usePrevalence } from '../../shared/hooks/use_prevalence'; import { mockGetFieldsData } from '../../shared/mocks/mock_get_fields_data'; import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser'; import { InsightsSection } from './insights_section'; -import { useAlertPrevalence } from '../../../../common/containers/alerts/use_alert_prevalence'; +import { useAlertPrevalence } from '../../shared/hooks/use_alert_prevalence'; import { useRiskScore } from '../../../../entity_analytics/api/hooks/use_risk_score'; import { useExpandSection } from '../hooks/use_expand_section'; import { useTimelineDataFilters } from '../../../../timelines/containers/use_timeline_data_filters'; import { useTourContext } from '../../../../common/components/guided_onboarding_tour'; -jest.mock('../../../../common/containers/alerts/use_alert_prevalence'); +jest.mock('../../shared/hooks/use_alert_prevalence'); const mockDispatch = jest.fn(); jest.mock('react-redux', () => { diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/visualizations_section.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/visualizations_section.test.tsx index 36fe53aa41dea..f204c18f9036a 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/visualizations_section.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/visualizations_section.test.tsx @@ -18,7 +18,7 @@ import { VisualizationsSection } from './visualizations_section'; import { mockContextValue } from '../../shared/mocks/mock_context'; import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser'; import { DocumentDetailsContext } from '../../shared/context'; -import { useAlertPrevalenceFromProcessTree } from '../../../../common/containers/alerts/use_alert_prevalence_from_process_tree'; +import { useAlertPrevalenceFromProcessTree } from '../../shared/hooks/use_alert_prevalence_from_process_tree'; import { useTimelineDataFilters } from '../../../../timelines/containers/use_timeline_data_filters'; import { TestProvider } from '@kbn/expandable-flyout/src/test/provider'; import { useExpandSection } from '../hooks/use_expand_section'; @@ -26,7 +26,7 @@ import { useInvestigateInTimeline } from '../../../../detections/components/aler import { useIsInvestigateInResolverActionEnabled } from '../../../../detections/components/alerts_table/timeline_actions/investigate_in_resolver'; jest.mock('../hooks/use_expand_section'); -jest.mock('../../../../common/containers/alerts/use_alert_prevalence_from_process_tree', () => ({ +jest.mock('../../shared/hooks/use_alert_prevalence_from_process_tree', () => ({ useAlertPrevalenceFromProcessTree: jest.fn(), })); const mockUseAlertPrevalenceFromProcessTree = useAlertPrevalenceFromProcessTree as jest.Mock; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/mocks/mock_analyzer_data.ts b/x-pack/plugins/security_solution/public/flyout/document_details/right/mocks/mock_analyzer_data.ts index fbd7dea83f79d..e0d35b796e76f 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/mocks/mock_analyzer_data.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/mocks/mock_analyzer_data.ts @@ -7,7 +7,7 @@ import React from 'react'; import { EuiToken } from '@elastic/eui'; import type { Node } from '@elastic/eui/src/components/tree_view/tree_view'; -import type { StatsNode } from '../../../../common/containers/alerts/use_alert_prevalence_from_process_tree'; +import type { StatsNode } from '../../shared/hooks/use_alert_prevalence_from_process_tree'; export const mockStatsNode: StatsNode = { id: '70e19mhyda', diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/utils/analyzer_helpers.ts b/x-pack/plugins/security_solution/public/flyout/document_details/right/utils/analyzer_helpers.ts index 15492f7e41377..5db8665bc3bbc 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/utils/analyzer_helpers.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/utils/analyzer_helpers.ts @@ -7,7 +7,7 @@ import React from 'react'; import type { Node } from '@elastic/eui/src/components/tree_view/tree_view'; import { EuiToken } from '@elastic/eui'; -import type { StatsNode } from '../../../../common/containers/alerts/use_alert_prevalence_from_process_tree'; +import type { StatsNode } from '../../shared/hooks/use_alert_prevalence_from_process_tree'; /** * Helper function to recursively create ancestor tree nodes diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_alert_document_analyzer_schema.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_alert_document_analyzer_schema.test.tsx new file mode 100644 index 0000000000000..3c31720b53f99 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_alert_document_analyzer_schema.test.tsx @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { RenderHookResult } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react-hooks'; +import { useQuery } from '@tanstack/react-query'; +import type { + UseAlertDocumentAnalyzerSchemaParams, + UseAlertDocumentAnalyzerSchemaResult, +} from './use_alert_document_analyzer_schema'; +import { useAlertDocumentAnalyzerSchema } from './use_alert_document_analyzer_schema'; +import { useHttp } from '../../../../common/lib/kibana'; + +jest.mock('../../../../common/lib/kibana'); +jest.mock('@tanstack/react-query'); + +describe('useAlertPrevalenceFromProcessTree', () => { + let hookResult: RenderHookResult< + UseAlertDocumentAnalyzerSchemaParams, + UseAlertDocumentAnalyzerSchemaResult + >; + + beforeEach(() => { + (useHttp as jest.Mock).mockReturnValue({ + get: jest.fn(), + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should return all properties when loading', () => { + (useQuery as jest.Mock).mockReturnValue({ + isLoading: true, + data: [], + }); + + hookResult = renderHook(() => + useAlertDocumentAnalyzerSchema({ + documentId: 'documentId', + indices: [], + }) + ); + + expect(hookResult.result.current.loading).toEqual(true); + expect(hookResult.result.current.error).toEqual(false); + expect(hookResult.result.current.id).toEqual(null); + expect(hookResult.result.current.schema).toEqual(null); + expect(hookResult.result.current.agentId).toEqual(null); + }); + + it('should return all properties with data', () => { + (useQuery as jest.Mock).mockReturnValue({ + isLoading: false, + data: [ + { + schema: {}, + id: 'id', + agentId: 'agentId', + }, + ], + }); + + hookResult = renderHook(() => + useAlertDocumentAnalyzerSchema({ + documentId: 'documentId', + indices: [], + }) + ); + + expect(hookResult.result.current.loading).toEqual(false); + expect(hookResult.result.current.error).toEqual(false); + expect(hookResult.result.current.id).toEqual('id'); + expect(hookResult.result.current.schema).toEqual({}); + expect(hookResult.result.current.agentId).toEqual('agentId'); + }); + + it('should return error when no data', () => { + (useQuery as jest.Mock).mockReturnValue({ + isLoading: false, + data: [], + }); + + hookResult = renderHook(() => + useAlertDocumentAnalyzerSchema({ + documentId: 'documentId', + indices: [], + }) + ); + + expect(hookResult.result.current.loading).toEqual(false); + expect(hookResult.result.current.error).toEqual(true); + expect(hookResult.result.current.id).toEqual(null); + expect(hookResult.result.current.schema).toEqual(null); + expect(hookResult.result.current.agentId).toEqual(null); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_alert_document_analyzer_schema.ts b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_alert_document_analyzer_schema.ts new file mode 100644 index 0000000000000..63cf63398bd16 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_alert_document_analyzer_schema.ts @@ -0,0 +1,95 @@ +/* + * 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 { useQuery } from '@tanstack/react-query'; +import { useHttp } from '../../../../common/lib/kibana'; + +interface EntityResponse { + id: string; + name: string; + schema: object; + agentId: string; +} + +export interface UseAlertDocumentAnalyzerSchemaParams { + /** + * The document ID of the alert to analyze + */ + documentId: string; + /** + * The indices to search for alerts + */ + indices: string[]; +} + +export interface UseAlertDocumentAnalyzerSchemaResult { + /** + * True if the request is still loading + */ + loading: boolean; + /** + * True if there was an error + */ + error: boolean; + /** + * The id returned by the API + */ + id: string | null; + /** + * The schema returned by the API + */ + schema: object | null; + /** + * The agent ID value returned byt the API + */ + agentId: string | null; +} + +export function useAlertDocumentAnalyzerSchema({ + documentId, + indices, +}: UseAlertDocumentAnalyzerSchemaParams): UseAlertDocumentAnalyzerSchemaResult { + const http = useHttp(); + + const query = useQuery(['getAlertPrevalenceSchema', documentId], () => { + return http.get(`/api/endpoint/resolver/entity`, { + query: { + _id: documentId, + indices, + }, + }); + }); + + if (query.isLoading) { + return { + loading: true, + error: false, + id: null, + schema: null, + agentId: null, + }; + } else if (query.data && query.data.length > 0) { + const { + data: [{ schema, id, agentId }], + } = query; + return { + loading: false, + error: false, + id, + schema, + agentId, + }; + } else { + return { + loading: false, + error: true, + id: null, + schema: null, + agentId: null, + }; + } +} diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_alert_prevalence.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_alert_prevalence.test.tsx new file mode 100644 index 0000000000000..231e0e5419441 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_alert_prevalence.test.tsx @@ -0,0 +1,151 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { RenderHookResult } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react-hooks'; +import { ALERT_PREVALENCE_AGG, useAlertPrevalence } from './use_alert_prevalence'; +import type { UseAlertPrevalenceParams, UserAlertPrevalenceResult } from './use_alert_prevalence'; +import { useGlobalTime } from '../../../../common/containers/use_global_time'; +import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; +import { useQueryAlerts } from '../../../../detections/containers/detection_engine/alerts/use_query'; + +jest.mock('../../../../common/containers/use_global_time'); +jest.mock('../../../../common/hooks/use_selector'); +jest.mock('../../../../detections/containers/detection_engine/alerts/use_query'); + +describe('useAlertPrevalence', () => { + let hookResult: RenderHookResult; + + beforeEach(() => { + (useDeepEqualSelector as jest.Mock).mockReturnValue({ + from: 'from', + to: 'to', + }); + (useGlobalTime as jest.Mock).mockReturnValue({ + from: 'from', + to: 'to', + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should return all properties', () => { + (useQueryAlerts as jest.Mock).mockReturnValue({ + loading: true, + data: undefined, + setQuery: jest.fn(), + }); + + hookResult = renderHook(() => + useAlertPrevalence({ + field: 'field', + value: 'value', + indexName: 'index', + isActiveTimelines: true, + includeAlertIds: false, + ignoreTimerange: false, + }) + ); + + expect(hookResult.result.current.loading).toEqual(true); + expect(hookResult.result.current.error).toEqual(false); + expect(hookResult.result.current.alertIds).toEqual(undefined); + expect(hookResult.result.current.count).toEqual(undefined); + }); + + it('should return error true if loading is done and no data', () => { + (useQueryAlerts as jest.Mock).mockReturnValue({ + loading: false, + data: undefined, + setQuery: jest.fn(), + }); + + hookResult = renderHook(() => + useAlertPrevalence({ + field: 'field', + value: 'value', + indexName: 'index', + isActiveTimelines: true, + includeAlertIds: false, + ignoreTimerange: false, + }) + ); + + expect(hookResult.result.current.loading).toEqual(false); + expect(hookResult.result.current.error).toEqual(true); + expect(hookResult.result.current.alertIds).toEqual(undefined); + expect(hookResult.result.current.count).toEqual(undefined); + }); + + it('should return correct count from aggregation', () => { + (useQueryAlerts as jest.Mock).mockReturnValue({ + loading: false, + data: { + aggregations: { + [ALERT_PREVALENCE_AGG]: { + buckets: [{ doc_count: 1 }], + }, + }, + hits: { + hits: [], + }, + }, + setQuery: jest.fn(), + }); + + hookResult = renderHook(() => + useAlertPrevalence({ + field: 'field', + value: 'value', + indexName: 'index', + isActiveTimelines: true, + includeAlertIds: false, + ignoreTimerange: false, + }) + ); + + expect(hookResult.result.current.loading).toEqual(false); + expect(hookResult.result.current.error).toEqual(false); + expect(hookResult.result.current.alertIds).toEqual([]); + expect(hookResult.result.current.count).toEqual(1); + }); + + it('should return alertIds if includeAlertIds is true', () => { + (useQueryAlerts as jest.Mock).mockReturnValue({ + loading: false, + data: { + aggregations: { + [ALERT_PREVALENCE_AGG]: { + buckets: [{ doc_count: 1 }], + }, + }, + hits: { + hits: [{ _id: 'id' }], + }, + }, + setQuery: jest.fn(), + }); + + hookResult = renderHook(() => + useAlertPrevalence({ + field: 'field', + value: 'value', + indexName: 'index', + isActiveTimelines: true, + includeAlertIds: true, + ignoreTimerange: false, + }) + ); + + expect(hookResult.result.current.loading).toEqual(false); + expect(hookResult.result.current.error).toEqual(false); + expect(hookResult.result.current.alertIds).toEqual(['id']); + expect(hookResult.result.current.count).toEqual(1); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/containers/alerts/use_alert_prevalence.ts b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_alert_prevalence.ts similarity index 66% rename from x-pack/plugins/security_solution/public/common/containers/alerts/use_alert_prevalence.ts rename to x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_alert_prevalence.ts index cc3ff5507ec40..a68a462c0ec0a 100644 --- a/x-pack/plugins/security_solution/public/common/containers/alerts/use_alert_prevalence.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_alert_prevalence.ts @@ -7,41 +7,80 @@ import { useEffect, useMemo, useState } from 'react'; -import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../common/constants'; -import { useGlobalTime } from '../use_global_time'; -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 { useDeepEqualSelector } from '../../hooks/use_selector'; -import { inputsSelectors } from '../../store'; - -const ALERT_PREVALENCE_AGG = 'countOfAlertsWithSameFieldAndValue'; +import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../common/constants'; +import { useGlobalTime } from '../../../../common/containers/use_global_time'; +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 { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; +import { inputsSelectors } from '../../../../common/store'; + +export const ALERT_PREVALENCE_AGG = 'countOfAlertsWithSameFieldAndValue'; +interface AlertPrevalenceAggregation { + [ALERT_PREVALENCE_AGG]: { + buckets: GenericBuckets[]; + }; +} -interface UseAlertPrevalenceOptions { +export interface UseAlertPrevalenceParams { + /** + * The field to search for + */ field: string; + /** + * The value to search for + */ value: string | string[] | undefined | null; + /** + * The index to search in + */ + indexName: string | null; + /** + * Whether to use the timeline time or the global time + */ isActiveTimelines: boolean; - signalIndexName: string | null; + /** + * Whether to include the alert ids in the response + */ includeAlertIds?: boolean; + /** + * Whether to ignore the timeline time and use the global time + */ ignoreTimerange?: boolean; } -interface UserAlertPrevalenceResult { +export interface UserAlertPrevalenceResult { + /** + * Whether the query is loading + */ loading: boolean; + /** + * The count of the prevalence aggregation + */ count: undefined | number; + /** + * Whether there was an error with the query + */ error: boolean; + /** + * The alert ids sorted by timestamp + */ alertIds?: string[]; } -// TODO: MOVE TO FLYOUT FOLDER - https://github.com/elastic/security-team/issues/7462 +/** + * Hook to get the prevalence of alerts with the field and value pair. + * By default, includeAlertIds is false, which means the call only returns the aggregation and not all the alerts themselves. If includeAlertIds is true, it will also return the alertIds sorted by timestamp. + * By default, includeAlertIds is false, which means we're fetching with the global time from and to values. If isActiveTimelines is true, we're getting the timeline time. + */ export const useAlertPrevalence = ({ field, value, + indexName, isActiveTimelines, - signalIndexName, includeAlertIds = false, ignoreTimerange = false, -}: UseAlertPrevalenceOptions): UserAlertPrevalenceResult => { +}: UseAlertPrevalenceParams): UserAlertPrevalenceResult => { const timelineTime = useDeepEqualSelector((state) => inputsSelectors.timelineTimeRangeSelector(state) ); @@ -57,7 +96,7 @@ export const useAlertPrevalence = ({ const { loading, data, setQuery } = useQueryAlerts<{ _id: string }, AlertPrevalenceAggregation>({ query: initialQuery, - indexName: signalIndexName, + indexName, queryName: ALERTS_QUERY_NAMES.PREVALENCE, }); @@ -165,9 +204,3 @@ const generateAlertPrevalenceQuery = ( runtime_mappings: {}, }; }; - -export interface AlertPrevalenceAggregation { - [ALERT_PREVALENCE_AGG]: { - buckets: GenericBuckets[]; - }; -} diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_alert_prevalence_from_process_tree.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_alert_prevalence_from_process_tree.test.tsx new file mode 100644 index 0000000000000..94b7cfa623507 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_alert_prevalence_from_process_tree.test.tsx @@ -0,0 +1,154 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { RenderHookResult } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react-hooks'; +import type { + UseAlertPrevalenceFromProcessTreeParams, + UserAlertPrevalenceFromProcessTreeResult, +} from './use_alert_prevalence_from_process_tree'; +import { useAlertPrevalenceFromProcessTree } from './use_alert_prevalence_from_process_tree'; +import { useHttp } from '../../../../common/lib/kibana'; +import { useTimelineDataFilters } from '../../../../timelines/containers/use_timeline_data_filters'; +import { useQuery } from '@tanstack/react-query'; +import { useAlertDocumentAnalyzerSchema } from './use_alert_document_analyzer_schema'; +import { mockStatsNode } from '../../right/mocks/mock_analyzer_data'; + +jest.mock('../../../../common/lib/kibana'); +jest.mock('../../../../timelines/containers/use_timeline_data_filters'); +jest.mock('./use_alert_document_analyzer_schema'); +jest.mock('@tanstack/react-query'); + +describe('useAlertPrevalenceFromProcessTree', () => { + let hookResult: RenderHookResult< + UseAlertPrevalenceFromProcessTreeParams, + UserAlertPrevalenceFromProcessTreeResult + >; + + beforeEach(() => { + (useHttp as jest.Mock).mockReturnValue({ + post: jest.fn(), + }); + (useTimelineDataFilters as jest.Mock).mockReturnValue({ + selectedPatterns: [], + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should return all properties when query is loading', () => { + (useQuery as jest.Mock).mockReturnValue({ + isLoading: true, + data: {}, + }); + (useAlertDocumentAnalyzerSchema as jest.Mock).mockReturnValue({ + loading: false, + error: false, + id: null, + schema: null, + agentId: null, + }); + + hookResult = renderHook(() => + useAlertPrevalenceFromProcessTree({ + documentId: 'documentId', + isActiveTimeline: true, + indices: [], + }) + ); + + expect(hookResult.result.current.loading).toEqual(true); + expect(hookResult.result.current.error).toEqual(false); + expect(hookResult.result.current.alertIds).toEqual(undefined); + expect(hookResult.result.current.statsNodes).toEqual(undefined); + }); + + it('should return all properties when analyzer query is loading', () => { + (useQuery as jest.Mock).mockReturnValue({ + isLoading: false, + data: {}, + }); + (useAlertDocumentAnalyzerSchema as jest.Mock).mockReturnValue({ + loading: true, + error: false, + id: null, + schema: null, + agentId: null, + }); + + hookResult = renderHook(() => + useAlertPrevalenceFromProcessTree({ + documentId: 'documentId', + isActiveTimeline: true, + indices: [], + }) + ); + + expect(hookResult.result.current.loading).toEqual(true); + expect(hookResult.result.current.error).toEqual(false); + expect(hookResult.result.current.alertIds).toEqual(undefined); + expect(hookResult.result.current.statsNodes).toEqual(undefined); + }); + + it('should return all properties data exists', () => { + (useQuery as jest.Mock).mockReturnValue({ + isLoading: false, + data: { + alertIds: ['alertIds'], + statsNodes: [mockStatsNode], + }, + }); + (useAlertDocumentAnalyzerSchema as jest.Mock).mockReturnValue({ + loading: false, + error: false, + id: null, + schema: null, + agentId: null, + }); + + hookResult = renderHook(() => + useAlertPrevalenceFromProcessTree({ + documentId: 'documentId', + isActiveTimeline: true, + indices: [], + }) + ); + + expect(hookResult.result.current.loading).toEqual(false); + expect(hookResult.result.current.error).toEqual(false); + expect(hookResult.result.current.alertIds).toEqual(['alertIds']); + expect(hookResult.result.current.statsNodes).toEqual([mockStatsNode]); + }); + + it('should return all properties data undefined', () => { + (useQuery as jest.Mock).mockReturnValue({ + isLoading: false, + }); + (useAlertDocumentAnalyzerSchema as jest.Mock).mockReturnValue({ + loading: false, + error: false, + id: null, + schema: null, + agentId: null, + }); + + hookResult = renderHook(() => + useAlertPrevalenceFromProcessTree({ + documentId: 'documentId', + isActiveTimeline: true, + indices: [], + }) + ); + + expect(hookResult.result.current.loading).toEqual(false); + expect(hookResult.result.current.error).toEqual(true); + expect(hookResult.result.current.alertIds).toEqual(undefined); + expect(hookResult.result.current.statsNodes).toEqual(undefined); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/containers/alerts/use_alert_prevalence_from_process_tree.ts b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_alert_prevalence_from_process_tree.ts similarity index 57% rename from x-pack/plugins/security_solution/public/common/containers/alerts/use_alert_prevalence_from_process_tree.ts rename to x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_alert_prevalence_from_process_tree.ts index 4e6747384fe34..f9c27f6e2ccb4 100644 --- a/x-pack/plugins/security_solution/public/common/containers/alerts/use_alert_prevalence_from_process_tree.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_alert_prevalence_from_process_tree.ts @@ -4,114 +4,120 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { useQuery } from '@tanstack/react-query'; -import { useHttp } from '../../lib/kibana'; -import { useTimelineDataFilters } from '../../../timelines/containers/use_timeline_data_filters'; -export const DETECTIONS_ALERTS_COUNT_ID = 'detections-alerts-count'; +import { useQuery } from '@tanstack/react-query'; +import { useMemo } from 'react'; +import { useAlertDocumentAnalyzerSchema } from './use_alert_document_analyzer_schema'; +import { useTimelineDataFilters } from '../../../../timelines/containers/use_timeline_data_filters'; +import { useHttp } from '../../../../common/lib/kibana'; export interface StatsNode { + /** + * The data of the node + */ data: object; + /** + * The ID of the node + */ id: string; + /** + * The name of the node + */ name: string; + /** + * The parent ID of the node + */ parent?: string; stats: { + /** + * The total number of alerts + */ total: number; + /** + * The total number of alerts by category + */ byCategory: { alerts?: number; }; }; } -interface UserAlertPrevalenceFromProcessTreeResult { - loading: boolean; - alertIds: undefined | string[]; - statsNodes: undefined | StatsNode[]; - count?: number; - error: boolean; -} - interface ProcessTreeAlertPrevalenceResponse { + /** + * The alert IDs found in the process tree + */ alertIds: string[] | undefined; + /** + * The stats nodes found in the process tree + */ statsNodes: StatsNode[] | undefined; } -interface EntityResponse { - id: string; - name: string; - schema: object; - agentId: string; +interface TreeResponse { + /** + * The alert IDs found in the process tree + */ + alertIds: string[]; + /** + * The stats nodes found in the process tree + */ + statsNodes: StatsNode[]; } -interface UseAlertPrevalenceFromProcessTree { +export interface UseAlertPrevalenceFromProcessTreeParams { + /** + * The document ID of the alert to analyze + */ documentId: string; + /** + * Whether or not the timeline is active + */ isActiveTimeline: boolean; + /** + * The indices to search for alerts + */ indices: string[]; } -interface UseAlertDocumentAnalyzerSchema { - documentId: string; - indices: string[]; -} - -interface TreeResponse { - statsNodes: StatsNode[]; - alertIds: string[]; -} - -function useAlertDocumentAnalyzerSchema({ documentId, indices }: UseAlertDocumentAnalyzerSchema) { - const http = useHttp(); - const query = useQuery(['getAlertPrevalenceSchema', documentId], () => { - return http.get(`/api/endpoint/resolver/entity`, { - query: { - _id: documentId, - indices, - }, - }); - }); - if (query.isLoading) { - return { - loading: true, - error: false, - id: null, - schema: null, - agentId: null, - }; - } else if (query.data && query.data.length > 0) { - const { - data: [{ schema, id, agentId }], - } = query; - return { - loading: false, - error: false, - id, - schema, - agentId, - }; - } else { - return { - loading: false, - error: true, - id: null, - schema: null, - agentId: null, - }; - } +export interface UserAlertPrevalenceFromProcessTreeResult { + /** + * Whether or not the query is loading + */ + loading: boolean; + /** + * The alert IDs found in the process tree + */ + alertIds: undefined | string[]; + /** + * The stats nodes found in the process tree + */ + statsNodes: undefined | StatsNode[]; + /** + * Whether or not the query errored + */ + error: boolean; } +/** + * Fetches the alert prevalence from the process tree + */ export function useAlertPrevalenceFromProcessTree({ documentId, isActiveTimeline, indices, -}: UseAlertPrevalenceFromProcessTree): UserAlertPrevalenceFromProcessTreeResult { +}: UseAlertPrevalenceFromProcessTreeParams): UserAlertPrevalenceFromProcessTreeResult { const http = useHttp(); const { selectedPatterns } = useTimelineDataFilters(isActiveTimeline); - const alertAndOriginalIndices = [...new Set(selectedPatterns.concat(indices))]; + const alertAndOriginalIndices = useMemo( + () => [...new Set(selectedPatterns.concat(indices))], + [indices, selectedPatterns] + ); const { loading, id, schema, agentId } = useAlertDocumentAnalyzerSchema({ documentId, indices: alertAndOriginalIndices, }); + const query = useQuery( ['getAlertPrevalenceFromProcessTree', id], () => { @@ -129,6 +135,7 @@ export function useAlertPrevalenceFromProcessTree({ }, { enabled: schema !== null && id !== null } ); + if (query.isLoading || loading) { return { loading: true, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_ancestry.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_ancestry.test.tsx index 9291b5e9a0c1a..4d65339c6b41a 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_ancestry.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_ancestry.test.tsx @@ -12,9 +12,9 @@ import type { UseFetchRelatedAlertsByAncestryResult, } from './use_fetch_related_alerts_by_ancestry'; import { useFetchRelatedAlertsByAncestry } from './use_fetch_related_alerts_by_ancestry'; -import { useAlertPrevalenceFromProcessTree } from '../../../../common/containers/alerts/use_alert_prevalence_from_process_tree'; +import { useAlertPrevalenceFromProcessTree } from './use_alert_prevalence_from_process_tree'; -jest.mock('../../../../common/containers/alerts/use_alert_prevalence_from_process_tree'); +jest.mock('./use_alert_prevalence_from_process_tree'); const documentId = 'documentId'; const indices = ['index1']; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_ancestry.ts b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_ancestry.ts index b44349a06eec9..826290a3dd3e9 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_ancestry.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_ancestry.ts @@ -6,7 +6,7 @@ */ import { useMemo } from 'react'; -import { useAlertPrevalenceFromProcessTree } from '../../../../common/containers/alerts/use_alert_prevalence_from_process_tree'; +import { useAlertPrevalenceFromProcessTree } from './use_alert_prevalence_from_process_tree'; import { isActiveTimeline } from '../../../../helpers'; export interface UseFetchRelatedAlertsByAncestryParams { diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_same_source_event.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_same_source_event.test.tsx index 4aaab73af1296..ff74774068adf 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_same_source_event.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_same_source_event.test.tsx @@ -12,9 +12,9 @@ import type { UseFetchRelatedAlertsBySameSourceEventResult, } from './use_fetch_related_alerts_by_same_source_event'; import { useFetchRelatedAlertsBySameSourceEvent } from './use_fetch_related_alerts_by_same_source_event'; -import { useAlertPrevalence } from '../../../../common/containers/alerts/use_alert_prevalence'; +import { useAlertPrevalence } from './use_alert_prevalence'; -jest.mock('../../../../common/containers/alerts/use_alert_prevalence'); +jest.mock('./use_alert_prevalence'); const originalEventId = 'originalEventId'; const scopeId = 'scopeId'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_same_source_event.ts b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_same_source_event.ts index 1946cef3e7de4..209bcb0c04051 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_same_source_event.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_same_source_event.ts @@ -7,7 +7,7 @@ import { useMemo } from 'react'; import { ANCESTOR_ID } from '../constants/field_names'; -import { useAlertPrevalence } from '../../../../common/containers/alerts/use_alert_prevalence'; +import { useAlertPrevalence } from './use_alert_prevalence'; import { isActiveTimeline } from '../../../../helpers'; export interface UseFetchRelatedAlertsBySameSourceEventParams { @@ -50,7 +50,7 @@ export const useFetchRelatedAlertsBySameSourceEvent = ({ field: ANCESTOR_ID, value: originalEventId, isActiveTimelines: isActiveTimeline(scopeId), - signalIndexName: null, + indexName: null, includeAlertIds: true, }); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_session.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_session.test.tsx index 6f6f2ea73158f..b38ef44178f9f 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_session.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_session.test.tsx @@ -13,9 +13,9 @@ import type { UseFetchRelatedAlertsBySessionResult, } from './use_fetch_related_alerts_by_session'; import { useFetchRelatedAlertsBySession } from './use_fetch_related_alerts_by_session'; -import { useAlertPrevalence } from '../../../../common/containers/alerts/use_alert_prevalence'; +import { useAlertPrevalence } from './use_alert_prevalence'; -jest.mock('../../../../common/containers/alerts/use_alert_prevalence'); +jest.mock('./use_alert_prevalence'); const entityId = 'entityId'; const scopeId = 'scopeId'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_session.ts b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_session.ts index 2c70714d07d5b..606c3523f60be 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_session.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_session.ts @@ -6,7 +6,7 @@ */ import { useMemo } from 'react'; -import { useAlertPrevalence } from '../../../../common/containers/alerts/use_alert_prevalence'; +import { useAlertPrevalence } from './use_alert_prevalence'; import { isActiveTimeline } from '../../../../helpers'; import { ENTRY_LEADER_ENTITY_ID } from '../constants/field_names'; @@ -50,7 +50,7 @@ export const useFetchRelatedAlertsBySession = ({ field: ENTRY_LEADER_ENTITY_ID, value: entityId, isActiveTimelines: isActiveTimeline(scopeId), - signalIndexName: null, + indexName: null, includeAlertIds: true, ignoreTimerange: true, });