From 2d53e2ce82c6c17b0759d0a46171e94b35107f4f Mon Sep 17 00:00:00 2001 From: Ievgen Sorokopud Date: Tue, 23 Dec 2025 16:27:13 +0100 Subject: [PATCH 1/2] [Security Solution][Attacks/Alerts][Setup and miscellaneous] Unified Alerts: Hooks to work with the new endpoints (#247387) --- .github/CODEOWNERS | 1 + .../unified_alerts/__mocks__/index.ts | 9 + .../__mocks__/unified_alerts.ts | 51 ++++++ .../__mocks__/update_responses.ts | 29 ++++ .../unified_alerts/api/index.test.ts | 104 ++++++++++++ .../containers/unified_alerts/api/index.ts | 154 ++++++++++++++++++ .../unified_alerts/hooks/constants.ts | 34 ++++ .../unified_alerts/hooks/translations.ts | 57 +++++++ .../hooks/use_search_unified_alerts.test.tsx | 102 ++++++++++++ .../hooks/use_search_unified_alerts.ts | 66 ++++++++ .../use_set_unified_alerts_assignees.test.tsx | 101 ++++++++++++ .../hooks/use_set_unified_alerts_assignees.ts | 46 ++++++ .../use_set_unified_alerts_tags.test.tsx | 101 ++++++++++++ .../hooks/use_set_unified_alerts_tags.ts | 46 ++++++ ...et_unified_alerts_workflow_status.test.tsx | 95 +++++++++++ .../use_set_unified_alerts_workflow_status.ts | 46 ++++++ .../common/containers/unified_alerts/index.ts | 12 ++ 17 files changed, 1054 insertions(+) create mode 100644 x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/__mocks__/index.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/__mocks__/unified_alerts.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/__mocks__/update_responses.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/api/index.test.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/api/index.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/constants.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/translations.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_search_unified_alerts.test.tsx create mode 100644 x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_search_unified_alerts.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_set_unified_alerts_assignees.test.tsx create mode 100644 x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_set_unified_alerts_assignees.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_set_unified_alerts_tags.test.tsx create mode 100644 x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_set_unified_alerts_tags.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_set_unified_alerts_workflow_status.test.tsx create mode 100644 x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_set_unified_alerts_workflow_status.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/index.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 563ad8d68367c..6f7bc19c23f72 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -2631,6 +2631,7 @@ x-pack/solutions/security/test/serverless/functional/configs/config.context_awar /x-pack/solutions/security/plugins/security_solution/public/common/components/tables @elastic/security-threat-hunting-investigations /x-pack/solutions/security/plugins/security_solution/public/common/components/top_n @elastic/security-threat-hunting-investigations /x-pack/solutions/security/plugins/security_solution/public/common/components/visualization_actions @elastic/security-threat-hunting-investigations +/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts @elastic/security-threat-hunting-investigations /x-pack/solutions/security/plugins/security_solution/public/common/hooks/use_resolve_conflict.tsx @elastic/security-threat-hunting-investigations /x-pack/solutions/security/plugins/security_solution/public/common/hooks/use_create_data_view.ts @elastic/security-threat-hunting-investigations /x-pack/solutions/security/plugins/security_solution/public/common/lib/cell_actions @elastic/security-threat-hunting-investigations diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/__mocks__/index.ts b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/__mocks__/index.ts new file mode 100644 index 0000000000000..031b9bc2d8ddd --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/__mocks__/index.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export * from './unified_alerts'; +export * from './update_responses'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/__mocks__/unified_alerts.ts b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/__mocks__/unified_alerts.ts new file mode 100644 index 0000000000000..6b91088a7a0e3 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/__mocks__/unified_alerts.ts @@ -0,0 +1,51 @@ +/* + * 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 { SearchUnifiedAlertsResponse } from '../../../../../common/api/detection_engine/unified_alerts'; + +export const getSearchUnifiedAlertsResponseMock = ( + overrides?: Partial +): SearchUnifiedAlertsResponse => ({ + took: 5, + timed_out: false, + _shards: { + total: 1, + successful: 1, + skipped: 0, + failed: 0, + }, + hits: { + total: { + value: 2, + relation: 'eq' as const, + }, + max_score: 1.0, + hits: [ + { + _index: '.alerts-security.alerts-default', + _id: 'alert-1', + _score: 1.0, + _source: { + '@timestamp': '2024-01-01T00:00:00.000Z', + 'kibana.alert.rule.name': 'Test Rule 1', + 'kibana.alert.severity': 'high', + }, + }, + { + _index: '.alerts-security.alerts-default', + _id: 'alert-2', + _score: 1.0, + _source: { + '@timestamp': '2024-01-01T01:00:00.000Z', + 'kibana.alert.rule.name': 'Test Rule 2', + 'kibana.alert.severity': 'medium', + }, + }, + ], + }, + ...overrides, +}); diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/__mocks__/update_responses.ts b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/__mocks__/update_responses.ts new file mode 100644 index 0000000000000..eb15975bbd21e --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/__mocks__/update_responses.ts @@ -0,0 +1,29 @@ +/* + * 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 { estypes } from '@elastic/elasticsearch'; + +export const getUpdateByQueryResponseMock = ( + overrides?: Partial +): estypes.UpdateByQueryResponse => ({ + took: 10, + timed_out: false, + total: 5, + updated: 5, + deleted: 0, + batches: 1, + version_conflicts: 0, + noops: 0, + retries: { + bulk: 0, + search: 0, + }, + throttled_millis: 0, + requests_per_second: -1, + throttled_until_millis: 0, + ...overrides, +}); diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/api/index.test.ts b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/api/index.test.ts new file mode 100644 index 0000000000000..88c4411fa4e6a --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/api/index.test.ts @@ -0,0 +1,104 @@ +/* + * 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 { coreMock } from '@kbn/core/public/mocks'; +import { + DETECTION_ENGINE_SEARCH_UNIFIED_ALERTS_URL, + DETECTION_ENGINE_SET_UNIFIED_ALERTS_WORKFLOW_STATUS_URL, + DETECTION_ENGINE_SET_UNIFIED_ALERTS_TAGS_URL, + DETECTION_ENGINE_SET_UNIFIED_ALERTS_ASSIGNEES_URL, +} from '../../../../../common/constants'; +import { KibanaServices } from '../../../lib/kibana'; +import * as api from '.'; + +jest.mock('../../../lib/kibana'); +const mockKibanaServices = KibanaServices.get as jest.Mock; + +const signal = {} as AbortSignal; + +describe('Unified Alerts API', () => { + let mockHttp: ReturnType['http']; + + beforeEach(() => { + const coreStart = coreMock.createStart({ basePath: '/mock' }); + mockHttp = coreStart.http; + mockKibanaServices.mockReturnValue(coreStart); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('searchUnifiedAlerts', () => { + it('calls http.post with correct params', async () => { + const query = { query: { match_all: {} } }; + await api.searchUnifiedAlerts({ query, signal }); + expect(mockHttp.post).toHaveBeenCalledWith(DETECTION_ENGINE_SEARCH_UNIFIED_ALERTS_URL, { + version: '1', + body: JSON.stringify(query), + signal, + }); + }); + }); + + describe('setUnifiedAlertsWorkflowStatus', () => { + it('calls http.post with correct params', async () => { + const body = { + signal_ids: ['alert-1', 'alert-2'], + status: 'closed' as const, + }; + await api.setUnifiedAlertsWorkflowStatus({ body, signal }); + expect(mockHttp.post).toHaveBeenCalledWith( + DETECTION_ENGINE_SET_UNIFIED_ALERTS_WORKFLOW_STATUS_URL, + { + version: '1', + body: JSON.stringify(body), + signal, + } + ); + }); + }); + + describe('setUnifiedAlertsTags', () => { + it('calls http.post with correct params', async () => { + const body = { + tags: { + tags_to_add: ['tag-1'], + tags_to_remove: [], + }, + ids: ['alert-1', 'alert-2'], + }; + await api.setUnifiedAlertsTags({ body, signal }); + expect(mockHttp.post).toHaveBeenCalledWith(DETECTION_ENGINE_SET_UNIFIED_ALERTS_TAGS_URL, { + version: '1', + body: JSON.stringify(body), + signal, + }); + }); + }); + + describe('setUnifiedAlertsAssignees', () => { + it('calls http.post with correct params', async () => { + const body = { + assignees: { + add: ['user-1'], + remove: [], + }, + ids: ['alert-1', 'alert-2'], + }; + await api.setUnifiedAlertsAssignees({ body, signal }); + expect(mockHttp.post).toHaveBeenCalledWith( + DETECTION_ENGINE_SET_UNIFIED_ALERTS_ASSIGNEES_URL, + { + version: '1', + body: JSON.stringify(body), + signal, + } + ); + }); + }); +}); diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/api/index.ts b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/api/index.ts new file mode 100644 index 0000000000000..b7acbb36d839a --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/api/index.ts @@ -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 { estypes } from '@elastic/elasticsearch'; +import { + DETECTION_ENGINE_SEARCH_UNIFIED_ALERTS_URL, + DETECTION_ENGINE_SET_UNIFIED_ALERTS_WORKFLOW_STATUS_URL, + DETECTION_ENGINE_SET_UNIFIED_ALERTS_TAGS_URL, + DETECTION_ENGINE_SET_UNIFIED_ALERTS_ASSIGNEES_URL, +} from '../../../../../common/constants'; +import type { + SearchUnifiedAlertsRequestBody, + SearchUnifiedAlertsResponse, + SetUnifiedAlertsWorkflowStatusRequestBody, + SetUnifiedAlertsTagsRequestBody, + SetUnifiedAlertsAssigneesRequestBody, +} from '../../../../../common/api/detection_engine/unified_alerts'; +import { KibanaServices } from '../../../lib/kibana'; + +/** + * Parameters for searching unified alerts + */ +export interface SearchUnifiedAlertsParams { + /** The Elasticsearch query DSL object */ + query: SearchUnifiedAlertsRequestBody; + /** Optional AbortSignal for cancelling request */ + signal?: AbortSignal; +} + +/** + * Searches unified alerts (detection and attack alerts) by providing a query DSL. + * This endpoint searches across both detection alerts and attack alerts. + * + * @param params - The search parameters + * @param params.query - The Elasticsearch query DSL object + * @param params.signal - Optional AbortSignal for cancelling the request + * @returns Promise resolving to the search response containing alerts + */ +export const searchUnifiedAlerts = async ({ + query, + signal, +}: SearchUnifiedAlertsParams): Promise => { + return KibanaServices.get().http.post( + DETECTION_ENGINE_SEARCH_UNIFIED_ALERTS_URL, + { + version: '1', + body: JSON.stringify(query), + signal, + } + ); +}; + +/** + * Parameters for setting workflow status on unified alerts + */ +export interface SetUnifiedAlertsWorkflowStatusParams { + /** The request body containing status and alert IDs */ + body: SetUnifiedAlertsWorkflowStatusRequestBody; + /** Optional AbortSignal for cancelling request */ + signal?: AbortSignal; +} + +/** + * Sets the workflow status (e.g., open, closed, acknowledged) for unified alerts. + * Updates both detection alerts and attack alerts that match the provided IDs. + * + * @param params - The update parameters + * @param params.body - The request body containing the status and alert IDs to update + * @param params.signal - Optional AbortSignal for cancelling the request + * @returns Promise resolving to the update by query response with the number of updated alerts + */ +export const setUnifiedAlertsWorkflowStatus = async ({ + body, + signal, +}: SetUnifiedAlertsWorkflowStatusParams): Promise => { + return KibanaServices.get().http.post( + DETECTION_ENGINE_SET_UNIFIED_ALERTS_WORKFLOW_STATUS_URL, + { + version: '1', + body: JSON.stringify(body), + signal, + } + ); +}; + +/** + * Parameters for setting tags on unified alerts + */ +export interface SetUnifiedAlertsTagsParams { + /** The request body containing tags and alert IDs */ + body: SetUnifiedAlertsTagsRequestBody; + /** Optional AbortSignal for cancelling request */ + signal?: AbortSignal; +} + +/** + * Sets tags for unified alerts by adding or removing tags from the specified alerts. + * Updates both detection alerts and attack alerts that match the provided IDs. + * + * @param params - The update parameters + * @param params.body - The request body containing tags to add/remove and alert IDs to update + * @param params.signal - Optional AbortSignal for cancelling the request + * @returns Promise resolving to the update by query response with the number of updated alerts + */ +export const setUnifiedAlertsTags = async ({ + body, + signal, +}: SetUnifiedAlertsTagsParams): Promise => { + return KibanaServices.get().http.post( + DETECTION_ENGINE_SET_UNIFIED_ALERTS_TAGS_URL, + { + version: '1', + body: JSON.stringify(body), + signal, + } + ); +}; + +/** + * Parameters for setting assignees on unified alerts + */ +export interface SetUnifiedAlertsAssigneesParams { + /** The request body containing assignees and alert IDs */ + body: SetUnifiedAlertsAssigneesRequestBody; + /** Optional AbortSignal for cancelling request */ + signal?: AbortSignal; +} + +/** + * Sets assignees for unified alerts by adding or removing assignees from the specified alerts. + * Updates both detection alerts and attack alerts that match the provided IDs. + * + * @param params - The update parameters + * @param params.body - The request body containing assignees to add/remove and alert IDs to update + * @param params.signal - Optional AbortSignal for cancelling the request + * @returns Promise resolving to the update by query response with the number of updated alerts + */ +export const setUnifiedAlertsAssignees = async ({ + body, + signal, +}: SetUnifiedAlertsAssigneesParams): Promise => { + return KibanaServices.get().http.post( + DETECTION_ENGINE_SET_UNIFIED_ALERTS_ASSIGNEES_URL, + { + version: '1', + body: JSON.stringify(body), + signal, + } + ); +}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/constants.ts b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/constants.ts new file mode 100644 index 0000000000000..222f2c82b8079 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/constants.ts @@ -0,0 +1,34 @@ +/* + * 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 { + DETECTION_ENGINE_SEARCH_UNIFIED_ALERTS_URL, + DETECTION_ENGINE_SET_UNIFIED_ALERTS_WORKFLOW_STATUS_URL, + DETECTION_ENGINE_SET_UNIFIED_ALERTS_TAGS_URL, + DETECTION_ENGINE_SET_UNIFIED_ALERTS_ASSIGNEES_URL, +} from '../../../../../common/constants'; + +const ONE_MINUTE = 60000; + +export const DEFAULT_QUERY_OPTIONS = { + refetchIntervalInBackground: false, + staleTime: ONE_MINUTE * 5, +}; + +export const SEARCH_UNIFIED_ALERTS_QUERY_KEY = ['GET', DETECTION_ENGINE_SEARCH_UNIFIED_ALERTS_URL]; +export const SET_UNIFIED_ALERTS_WORKFLOW_STATUS_MUTATION_KEY = [ + 'POST', + DETECTION_ENGINE_SET_UNIFIED_ALERTS_WORKFLOW_STATUS_URL, +]; +export const SET_UNIFIED_ALERTS_TAGS_MUTATION_KEY = [ + 'POST', + DETECTION_ENGINE_SET_UNIFIED_ALERTS_TAGS_URL, +]; +export const SET_UNIFIED_ALERTS_ASSIGNEES_MUTATION_KEY = [ + 'POST', + DETECTION_ENGINE_SET_UNIFIED_ALERTS_ASSIGNEES_URL, +]; diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/translations.ts b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/translations.ts new file mode 100644 index 0000000000000..2227e8895bc81 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/translations.ts @@ -0,0 +1,57 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const SEARCH_UNIFIED_ALERTS_FAILURE = i18n.translate( + 'xpack.securitySolution.unifiedAlerts.searchUnifiedAlertsFailure', + { + defaultMessage: 'Failed to search unified alerts', + } +); + +export const SET_UNIFIED_ALERTS_WORKFLOW_STATUS_SUCCESS_TOAST = (totalAlerts: number) => + i18n.translate('xpack.securitySolution.unifiedAlerts.setWorkflowStatusSuccessToastMessage', { + values: { totalAlerts }, + defaultMessage: + 'Successfully updated workflow status for {totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}}.', + }); + +export const SET_UNIFIED_ALERTS_WORKFLOW_STATUS_FAILURE = i18n.translate( + 'xpack.securitySolution.unifiedAlerts.setWorkflowStatusFailure', + { + defaultMessage: 'Failed to update alert workflow status', + } +); + +export const SET_UNIFIED_ALERTS_TAGS_SUCCESS_TOAST = (totalAlerts: number) => + i18n.translate('xpack.securitySolution.unifiedAlerts.setTagsSuccessToastMessage', { + values: { totalAlerts }, + defaultMessage: + 'Successfully updated tags for {totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}}.', + }); + +export const SET_UNIFIED_ALERTS_TAGS_FAILURE = i18n.translate( + 'xpack.securitySolution.unifiedAlerts.setTagsFailure', + { + defaultMessage: 'Failed to update alert tags', + } +); + +export const SET_UNIFIED_ALERTS_ASSIGNEES_SUCCESS_TOAST = (totalAlerts: number) => + i18n.translate('xpack.securitySolution.unifiedAlerts.setAssigneesSuccessToastMessage', { + values: { totalAlerts }, + defaultMessage: + 'Successfully updated assignees for {totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}}.', + }); + +export const SET_UNIFIED_ALERTS_ASSIGNEES_FAILURE = i18n.translate( + 'xpack.securitySolution.unifiedAlerts.setAssigneesFailure', + { + defaultMessage: 'Failed to update alert assignees', + } +); diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_search_unified_alerts.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_search_unified_alerts.test.tsx new file mode 100644 index 0000000000000..0af36d79199eb --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_search_unified_alerts.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 React from 'react'; +import { renderHook, waitFor } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@kbn/react-query'; +import { useAppToasts } from '../../../hooks/use_app_toasts'; +import { + useSearchUnifiedAlerts, + useInvalidateSearchUnifiedAlerts, +} from './use_search_unified_alerts'; +import { searchUnifiedAlerts } from '../api'; +import { getSearchUnifiedAlertsResponseMock } from '../__mocks__'; + +jest.mock('../../../hooks/use_app_toasts'); +jest.mock('../api'); + +const createWrapper = () => { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }); + // eslint-disable-next-line react/display-name + return ({ children }: { children: React.ReactNode }) => ( + {children} + ); +}; + +describe('useSearchUnifiedAlerts', () => { + beforeEach(() => { + jest.clearAllMocks(); + (useAppToasts as jest.Mock).mockReturnValue({ + addSuccess: jest.fn(), + addError: jest.fn(), + }); + }); + + it('should call searchUnifiedAlerts with correct params', async () => { + const query = { query: { match_all: {} } }; + const mockResponse = getSearchUnifiedAlertsResponseMock(); + (searchUnifiedAlerts as jest.Mock).mockResolvedValueOnce(mockResponse); + + const { result } = renderHook(() => useSearchUnifiedAlerts(query), { + wrapper: createWrapper(), + }); + + await waitFor(() => { + expect(result.current.isSuccess).toBe(true); + }); + + expect(searchUnifiedAlerts).toHaveBeenCalledWith({ + query, + signal: expect.any(AbortSignal), + }); + expect(result.current.data).toEqual(mockResponse); + }); + + it('should handle errors', async () => { + const query = { query: { match_all: {} } }; + const error = new Error('Test error'); + (searchUnifiedAlerts as jest.Mock).mockRejectedValueOnce(error); + + const { addError } = useAppToasts(); + const { result } = renderHook(() => useSearchUnifiedAlerts(query), { + wrapper: createWrapper(), + }); + + await waitFor(() => { + expect(result.current.isError).toBe(true); + }); + + expect(addError).toHaveBeenCalledWith(error, { + title: expect.any(String), + }); + }); +}); + +describe('useInvalidateSearchUnifiedAlerts', () => { + it('should invalidate queries', () => { + const queryClient = new QueryClient(); + const invalidateQueriesSpy = jest.spyOn(queryClient, 'invalidateQueries'); + + const { result } = renderHook(() => useInvalidateSearchUnifiedAlerts(), { + wrapper: ({ children }: { children: React.ReactNode }) => ( + {children} + ), + }); + + result.current(); + + expect(invalidateQueriesSpy).toHaveBeenCalledWith(['GET', expect.any(String)], { + refetchType: 'active', + }); + }); +}); diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_search_unified_alerts.ts b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_search_unified_alerts.ts new file mode 100644 index 0000000000000..994fc138d8c40 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_search_unified_alerts.ts @@ -0,0 +1,66 @@ +/* + * 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 { useCallback } from 'react'; +import { useQuery, useQueryClient } from '@kbn/react-query'; + +import type { SearchUnifiedAlertsRequestBody } from '../../../../../common/api/detection_engine/unified_alerts'; +import { useAppToasts } from '../../../hooks/use_app_toasts'; +import { searchUnifiedAlerts } from '../api'; + +import * as i18n from './translations'; +import { DEFAULT_QUERY_OPTIONS, SEARCH_UNIFIED_ALERTS_QUERY_KEY } from './constants'; + +/** + * Hook for searching unified alerts (detection and attack alerts) using React Query. + * Provides automatic caching, error handling, and loading states. + * + * @param query - The Elasticsearch query DSL object to search unified alerts + * @returns React Query result object with data, loading state, error, and refetch function + */ +export const useSearchUnifiedAlerts = (query: SearchUnifiedAlertsRequestBody) => { + const { addError } = useAppToasts(); + + return useQuery( + [...SEARCH_UNIFIED_ALERTS_QUERY_KEY, query], + async ({ signal }) => { + const response = await searchUnifiedAlerts({ + query, + signal, + }); + + return response; + }, + { + ...DEFAULT_QUERY_OPTIONS, + onError: (error) => { + addError(error, { title: i18n.SEARCH_UNIFIED_ALERTS_FAILURE }); + }, + } + ); +}; + +/** + * We should use this hook to invalidate the unified alerts search cache. For + * example, unified alerts mutations, like setting workflow status, tags, or assignees, + * should lead to cache invalidation. + * + * @returns A unified alerts search cache invalidation callback + */ +export const useInvalidateSearchUnifiedAlerts = () => { + const queryClient = useQueryClient(); + + return useCallback(() => { + /** + * Invalidate all queries that start with SEARCH_UNIFIED_ALERTS_QUERY_KEY. This + * includes the in-memory query cache and paged query cache. + */ + queryClient.invalidateQueries(SEARCH_UNIFIED_ALERTS_QUERY_KEY, { + refetchType: 'active', + }); + }, [queryClient]); +}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_set_unified_alerts_assignees.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_set_unified_alerts_assignees.test.tsx new file mode 100644 index 0000000000000..7d0256fd3328e --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_set_unified_alerts_assignees.test.tsx @@ -0,0 +1,101 @@ +/* + * 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 React from 'react'; +import { renderHook, waitFor } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@kbn/react-query'; +import { useAppToasts } from '../../../hooks/use_app_toasts'; +import { useSetUnifiedAlertsAssignees } from './use_set_unified_alerts_assignees'; +import { setUnifiedAlertsAssignees } from '../api'; +import { useInvalidateSearchUnifiedAlerts } from './use_search_unified_alerts'; +import { getUpdateByQueryResponseMock } from '../__mocks__'; + +jest.mock('../../../hooks/use_app_toasts'); +jest.mock('../api'); +jest.mock('./use_search_unified_alerts'); + +const createWrapper = () => { + const queryClient = new QueryClient({ + defaultOptions: { + mutations: { + retry: false, + }, + }, + }); + // eslint-disable-next-line react/display-name + return ({ children }: { children: React.ReactNode }) => ( + {children} + ); +}; + +describe('useSetUnifiedAlertsAssignees', () => { + const mockInvalidate = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + (useAppToasts as jest.Mock).mockReturnValue({ + addSuccess: jest.fn(), + addError: jest.fn(), + }); + (useInvalidateSearchUnifiedAlerts as jest.Mock).mockReturnValue(mockInvalidate); + }); + + it('should call setUnifiedAlertsAssignees and show success toast', async () => { + const body = { + assignees: { + add: ['user-1'], + remove: [], + }, + ids: ['alert-1', 'alert-2'], + }; + const mockResponse = getUpdateByQueryResponseMock({ updated: 2 }); + (setUnifiedAlertsAssignees as jest.Mock).mockResolvedValueOnce(mockResponse); + + const { addSuccess } = useAppToasts(); + const { result } = renderHook(() => useSetUnifiedAlertsAssignees(), { + wrapper: createWrapper(), + }); + + result.current.mutate(body); + + await waitFor(() => { + expect(result.current.isSuccess).toBe(true); + }); + + expect(setUnifiedAlertsAssignees).toHaveBeenCalledWith({ body }); + expect(addSuccess).toHaveBeenCalledWith(expect.stringContaining('2')); + expect(mockInvalidate).toHaveBeenCalled(); + }); + + it('should handle errors', async () => { + const body = { + assignees: { + add: ['user-1'], + remove: [], + }, + ids: ['alert-1'], + }; + const error = new Error('Test error'); + (setUnifiedAlertsAssignees as jest.Mock).mockRejectedValueOnce(error); + + const { addError } = useAppToasts(); + const { result } = renderHook(() => useSetUnifiedAlertsAssignees(), { + wrapper: createWrapper(), + }); + + result.current.mutate(body); + + await waitFor(() => { + expect(result.current.isError).toBe(true); + }); + + expect(addError).toHaveBeenCalledWith(error, { + title: expect.any(String), + }); + expect(mockInvalidate).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_set_unified_alerts_assignees.ts b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_set_unified_alerts_assignees.ts new file mode 100644 index 0000000000000..bf7968590919f --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_set_unified_alerts_assignees.ts @@ -0,0 +1,46 @@ +/* + * 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 { useMutation } from '@kbn/react-query'; + +import type { SetUnifiedAlertsAssigneesRequestBody } from '../../../../../common/api/detection_engine/unified_alerts'; +import { useAppToasts } from '../../../hooks/use_app_toasts'; +import { setUnifiedAlertsAssignees } from '../api'; + +import * as i18n from './translations'; +import { SET_UNIFIED_ALERTS_ASSIGNEES_MUTATION_KEY } from './constants'; +import { useInvalidateSearchUnifiedAlerts } from './use_search_unified_alerts'; + +/** + * Hook for setting assignees on unified alerts using React Query mutations. + * Automatically shows success/error toasts and invalidates the search cache on completion. + * + * @returns React Query mutation object with mutate function and mutation state + */ +export const useSetUnifiedAlertsAssignees = () => { + const { addSuccess, addError } = useAppToasts(); + const invalidateSearchUnifiedAlerts = useInvalidateSearchUnifiedAlerts(); + + return useMutation<{ updated: number }, Error, SetUnifiedAlertsAssigneesRequestBody>( + async (body) => { + const response = await setUnifiedAlertsAssignees({ body }); + return { updated: response.updated ?? 0 }; + }, + { + mutationKey: SET_UNIFIED_ALERTS_ASSIGNEES_MUTATION_KEY, + onSuccess: (response) => { + addSuccess(i18n.SET_UNIFIED_ALERTS_ASSIGNEES_SUCCESS_TOAST(response.updated)); + }, + onError: (error) => { + addError(error, { title: i18n.SET_UNIFIED_ALERTS_ASSIGNEES_FAILURE }); + }, + onSettled: () => { + invalidateSearchUnifiedAlerts(); + }, + } + ); +}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_set_unified_alerts_tags.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_set_unified_alerts_tags.test.tsx new file mode 100644 index 0000000000000..b9d18e5db17b8 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_set_unified_alerts_tags.test.tsx @@ -0,0 +1,101 @@ +/* + * 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 React from 'react'; +import { renderHook, waitFor } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@kbn/react-query'; +import { useAppToasts } from '../../../hooks/use_app_toasts'; +import { useSetUnifiedAlertsTags } from './use_set_unified_alerts_tags'; +import { setUnifiedAlertsTags } from '../api'; +import { useInvalidateSearchUnifiedAlerts } from './use_search_unified_alerts'; +import { getUpdateByQueryResponseMock } from '../__mocks__'; + +jest.mock('../../../hooks/use_app_toasts'); +jest.mock('../api'); +jest.mock('./use_search_unified_alerts'); + +const createWrapper = () => { + const queryClient = new QueryClient({ + defaultOptions: { + mutations: { + retry: false, + }, + }, + }); + // eslint-disable-next-line react/display-name + return ({ children }: { children: React.ReactNode }) => ( + {children} + ); +}; + +describe('useSetUnifiedAlertsTags', () => { + const mockInvalidate = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + (useAppToasts as jest.Mock).mockReturnValue({ + addSuccess: jest.fn(), + addError: jest.fn(), + }); + (useInvalidateSearchUnifiedAlerts as jest.Mock).mockReturnValue(mockInvalidate); + }); + + it('should call setUnifiedAlertsTags and show success toast', async () => { + const body = { + tags: { + add: ['tag-1'], + remove: [], + }, + ids: ['alert-1', 'alert-2'], + }; + const mockResponse = getUpdateByQueryResponseMock({ updated: 2 }); + (setUnifiedAlertsTags as jest.Mock).mockResolvedValueOnce(mockResponse); + + const { addSuccess } = useAppToasts(); + const { result } = renderHook(() => useSetUnifiedAlertsTags(), { + wrapper: createWrapper(), + }); + + result.current.mutate(body); + + await waitFor(() => { + expect(result.current.isSuccess).toBe(true); + }); + + expect(setUnifiedAlertsTags).toHaveBeenCalledWith({ body }); + expect(addSuccess).toHaveBeenCalledWith(expect.stringContaining('2')); + expect(mockInvalidate).toHaveBeenCalled(); + }); + + it('should handle errors', async () => { + const body = { + tags: { + add: ['tag-1'], + remove: [], + }, + ids: ['alert-1'], + }; + const error = new Error('Test error'); + (setUnifiedAlertsTags as jest.Mock).mockRejectedValueOnce(error); + + const { addError } = useAppToasts(); + const { result } = renderHook(() => useSetUnifiedAlertsTags(), { + wrapper: createWrapper(), + }); + + result.current.mutate(body); + + await waitFor(() => { + expect(result.current.isError).toBe(true); + }); + + expect(addError).toHaveBeenCalledWith(error, { + title: expect.any(String), + }); + expect(mockInvalidate).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_set_unified_alerts_tags.ts b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_set_unified_alerts_tags.ts new file mode 100644 index 0000000000000..4813202deb35b --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_set_unified_alerts_tags.ts @@ -0,0 +1,46 @@ +/* + * 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 { useMutation } from '@kbn/react-query'; + +import type { SetUnifiedAlertsTagsRequestBody } from '../../../../../common/api/detection_engine/unified_alerts'; +import { useAppToasts } from '../../../hooks/use_app_toasts'; +import { setUnifiedAlertsTags } from '../api'; + +import * as i18n from './translations'; +import { SET_UNIFIED_ALERTS_TAGS_MUTATION_KEY } from './constants'; +import { useInvalidateSearchUnifiedAlerts } from './use_search_unified_alerts'; + +/** + * Hook for setting tags on unified alerts using React Query mutations. + * Automatically shows success/error toasts and invalidates the search cache on completion. + * + * @returns React Query mutation object with mutate function and mutation state + */ +export const useSetUnifiedAlertsTags = () => { + const { addSuccess, addError } = useAppToasts(); + const invalidateSearchUnifiedAlerts = useInvalidateSearchUnifiedAlerts(); + + return useMutation<{ updated: number }, Error, SetUnifiedAlertsTagsRequestBody>( + async (body) => { + const response = await setUnifiedAlertsTags({ body }); + return { updated: response.updated ?? 0 }; + }, + { + mutationKey: SET_UNIFIED_ALERTS_TAGS_MUTATION_KEY, + onSuccess: (response) => { + addSuccess(i18n.SET_UNIFIED_ALERTS_TAGS_SUCCESS_TOAST(response.updated)); + }, + onError: (error) => { + addError(error, { title: i18n.SET_UNIFIED_ALERTS_TAGS_FAILURE }); + }, + onSettled: () => { + invalidateSearchUnifiedAlerts(); + }, + } + ); +}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_set_unified_alerts_workflow_status.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_set_unified_alerts_workflow_status.test.tsx new file mode 100644 index 0000000000000..ab0fdea039c58 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_set_unified_alerts_workflow_status.test.tsx @@ -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 React from 'react'; +import { renderHook, waitFor } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@kbn/react-query'; +import { useAppToasts } from '../../../hooks/use_app_toasts'; +import { useSetUnifiedAlertsWorkflowStatus } from './use_set_unified_alerts_workflow_status'; +import { setUnifiedAlertsWorkflowStatus } from '../api'; +import { useInvalidateSearchUnifiedAlerts } from './use_search_unified_alerts'; +import { getUpdateByQueryResponseMock } from '../__mocks__'; + +jest.mock('../../../hooks/use_app_toasts'); +jest.mock('../api'); +jest.mock('./use_search_unified_alerts'); + +const createWrapper = () => { + const queryClient = new QueryClient({ + defaultOptions: { + mutations: { + retry: false, + }, + }, + }); + // eslint-disable-next-line react/display-name + return ({ children }: { children: React.ReactNode }) => ( + {children} + ); +}; + +describe('useSetUnifiedAlertsWorkflowStatus', () => { + const mockInvalidate = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + (useAppToasts as jest.Mock).mockReturnValue({ + addSuccess: jest.fn(), + addError: jest.fn(), + }); + (useInvalidateSearchUnifiedAlerts as jest.Mock).mockReturnValue(mockInvalidate); + }); + + it('should call setUnifiedAlertsWorkflowStatus and show success toast', async () => { + const body = { + signal_ids: ['alert-1', 'alert-2'], + status: 'closed' as const, + }; + const mockResponse = getUpdateByQueryResponseMock({ updated: 2 }); + (setUnifiedAlertsWorkflowStatus as jest.Mock).mockResolvedValueOnce(mockResponse); + + const { addSuccess } = useAppToasts(); + const { result } = renderHook(() => useSetUnifiedAlertsWorkflowStatus(), { + wrapper: createWrapper(), + }); + + result.current.mutate(body); + + await waitFor(() => { + expect(result.current.isSuccess).toBe(true); + }); + + expect(setUnifiedAlertsWorkflowStatus).toHaveBeenCalledWith({ body }); + expect(addSuccess).toHaveBeenCalledWith(expect.stringContaining('2')); + expect(mockInvalidate).toHaveBeenCalled(); + }); + + it('should handle errors', async () => { + const body = { + signal_ids: ['alert-1'], + status: 'open' as const, + }; + const error = new Error('Test error'); + (setUnifiedAlertsWorkflowStatus as jest.Mock).mockRejectedValueOnce(error); + + const { addError } = useAppToasts(); + const { result } = renderHook(() => useSetUnifiedAlertsWorkflowStatus(), { + wrapper: createWrapper(), + }); + + result.current.mutate(body); + + await waitFor(() => { + expect(result.current.isError).toBe(true); + }); + + expect(addError).toHaveBeenCalledWith(error, { + title: expect.any(String), + }); + expect(mockInvalidate).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_set_unified_alerts_workflow_status.ts b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_set_unified_alerts_workflow_status.ts new file mode 100644 index 0000000000000..7d3d7662fb0e1 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_set_unified_alerts_workflow_status.ts @@ -0,0 +1,46 @@ +/* + * 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 { useMutation } from '@kbn/react-query'; + +import type { SetUnifiedAlertsWorkflowStatusRequestBody } from '../../../../../common/api/detection_engine/unified_alerts'; +import { useAppToasts } from '../../../hooks/use_app_toasts'; +import { setUnifiedAlertsWorkflowStatus } from '../api'; + +import * as i18n from './translations'; +import { SET_UNIFIED_ALERTS_WORKFLOW_STATUS_MUTATION_KEY } from './constants'; +import { useInvalidateSearchUnifiedAlerts } from './use_search_unified_alerts'; + +/** + * Hook for setting workflow status on unified alerts using React Query mutations. + * Automatically shows success/error toasts and invalidates the search cache on completion. + * + * @returns React Query mutation object with mutate function and mutation state + */ +export const useSetUnifiedAlertsWorkflowStatus = () => { + const { addSuccess, addError } = useAppToasts(); + const invalidateSearchUnifiedAlerts = useInvalidateSearchUnifiedAlerts(); + + return useMutation<{ updated: number }, Error, SetUnifiedAlertsWorkflowStatusRequestBody>( + async (body) => { + const response = await setUnifiedAlertsWorkflowStatus({ body }); + return { updated: response.updated ?? 0 }; + }, + { + mutationKey: SET_UNIFIED_ALERTS_WORKFLOW_STATUS_MUTATION_KEY, + onSuccess: (response) => { + addSuccess(i18n.SET_UNIFIED_ALERTS_WORKFLOW_STATUS_SUCCESS_TOAST(response.updated)); + }, + onError: (error) => { + addError(error, { title: i18n.SET_UNIFIED_ALERTS_WORKFLOW_STATUS_FAILURE }); + }, + onSettled: () => { + invalidateSearchUnifiedAlerts(); + }, + } + ); +}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/index.ts b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/index.ts new file mode 100644 index 0000000000000..3b4c077d847c5 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/index.ts @@ -0,0 +1,12 @@ +/* + * 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. + */ + +export * from './api'; +export * from './hooks/use_search_unified_alerts'; +export * from './hooks/use_set_unified_alerts_workflow_status'; +export * from './hooks/use_set_unified_alerts_tags'; +export * from './hooks/use_set_unified_alerts_assignees'; From e61f7322ff660d68d404d4c8e95840939107c94a Mon Sep 17 00:00:00 2001 From: Ievgen Sorokopud Date: Tue, 23 Dec 2025 17:46:16 +0100 Subject: [PATCH 2/2] Fix types --- .../hooks/use_set_unified_alerts_tags.test.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_set_unified_alerts_tags.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_set_unified_alerts_tags.test.tsx index b9d18e5db17b8..bea663eee36df 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_set_unified_alerts_tags.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/common/containers/unified_alerts/hooks/use_set_unified_alerts_tags.test.tsx @@ -47,8 +47,8 @@ describe('useSetUnifiedAlertsTags', () => { it('should call setUnifiedAlertsTags and show success toast', async () => { const body = { tags: { - add: ['tag-1'], - remove: [], + tags_to_add: ['tag-1'], + tags_to_remove: [], }, ids: ['alert-1', 'alert-2'], }; @@ -74,8 +74,8 @@ describe('useSetUnifiedAlertsTags', () => { it('should handle errors', async () => { const body = { tags: { - add: ['tag-1'], - remove: [], + tags_to_add: ['tag-1'], + tags_to_remove: [], }, ids: ['alert-1'], };