mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Security Solution][Entity details] - move prevalence related hooks to flyout folder (#190109)
This commit is contained in:
parent
c2933dee94
commit
35c0671414
19 changed files with 657 additions and 115 deletions
|
@ -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;
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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'
|
||||
);
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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<EntityResponse[]>(['getAlertPrevalenceSchema', documentId], () => {
|
||||
return http.get<EntityResponse[]>(`/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,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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<UseAlertPrevalenceParams, UserAlertPrevalenceResult>;
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
|
@ -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';
|
||||
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';
|
||||
|
||||
const ALERT_PREVALENCE_AGG = 'countOfAlertsWithSameFieldAndValue';
|
||||
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[];
|
||||
};
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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 UseAlertPrevalenceFromProcessTree {
|
||||
documentId: string;
|
||||
isActiveTimeline: boolean;
|
||||
indices: string[];
|
||||
}
|
||||
|
||||
interface UseAlertDocumentAnalyzerSchema {
|
||||
documentId: string;
|
||||
indices: string[];
|
||||
}
|
||||
|
||||
interface TreeResponse {
|
||||
statsNodes: StatsNode[];
|
||||
/**
|
||||
* The alert IDs found in the process tree
|
||||
*/
|
||||
alertIds: string[];
|
||||
/**
|
||||
* The stats nodes found in the process tree
|
||||
*/
|
||||
statsNodes: StatsNode[];
|
||||
}
|
||||
|
||||
function useAlertDocumentAnalyzerSchema({ documentId, indices }: UseAlertDocumentAnalyzerSchema) {
|
||||
const http = useHttp();
|
||||
const query = useQuery<EntityResponse[]>(['getAlertPrevalenceSchema', documentId], () => {
|
||||
return http.get<EntityResponse[]>(`/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 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[];
|
||||
}
|
||||
|
||||
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<ProcessTreeAlertPrevalenceResponse>(
|
||||
['getAlertPrevalenceFromProcessTree', id],
|
||||
() => {
|
||||
|
@ -129,6 +135,7 @@ export function useAlertPrevalenceFromProcessTree({
|
|||
},
|
||||
{ enabled: schema !== null && id !== null }
|
||||
);
|
||||
|
||||
if (query.isLoading || loading) {
|
||||
return {
|
||||
loading: true,
|
|
@ -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'];
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue