[Security Solution][Entity details] - move prevalence related hooks to flyout folder (#190109)

This commit is contained in:
Philippe Oberti 2024-08-19 17:00:45 +02:00 committed by GitHub
parent c2933dee94
commit 35c0671414
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 657 additions and 115 deletions

View file

@ -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;

View file

@ -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';

View file

@ -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'
);

View file

@ -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', () => {

View file

@ -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;

View file

@ -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',

View file

@ -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

View file

@ -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);
});
});

View file

@ -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,
};
}
}

View file

@ -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);
});
});

View file

@ -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[];
};
}

View file

@ -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);
});
});

View file

@ -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,

View file

@ -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'];

View file

@ -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 {

View file

@ -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';

View file

@ -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,
});

View file

@ -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';

View file

@ -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,
});