diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_actions.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_actions.test.tsx index 84b9180733e834..042eeab219633c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_actions.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_actions.test.tsx @@ -10,6 +10,7 @@ import { useBulkActions, useBulkAddToCaseActions, useBulkUntrackActions } from ' import { AppMockRenderer, createAppMockRenderer } from '../../test_utils'; import { createCasesServiceMock } from '../index.mock'; import { AlertsTableQueryContext } from '../contexts/alerts_table_context'; +import { BulkActionsVerbs } from '../../../../types'; jest.mock('./apis/bulk_get_cases'); jest.mock('../../../../common/lib/kibana'); @@ -422,5 +423,45 @@ describe('bulk action hooks', () => { ] `); }); + + it('should not append duplicate items on rerender', async () => { + const onClick = () => {}; + const items = [ + { + label: 'test', + key: 'test', + 'data-test-subj': 'test', + disableOnQuery: true, + disabledLabel: 'test', + onClick, + }, + ]; + const customBulkActionConfig = [ + { + id: 0, + items, + }, + ]; + const useBulkActionsConfig = () => customBulkActionConfig; + const { result, rerender } = renderHook( + () => useBulkActions({ alerts: [], query: {}, casesConfig, refresh, useBulkActionsConfig }), + { + wrapper: appMockRender.AppWrapper, + } + ); + const initialBulkActions = result.current.bulkActions[0].items + ? [...result.current.bulkActions[0].items] + : []; + result.current.updateBulkActionsState({ action: BulkActionsVerbs.selectCurrentPage }); + rerender(); + result.current.updateBulkActionsState({ action: BulkActionsVerbs.clear }); + rerender(); + result.current.updateBulkActionsState({ action: BulkActionsVerbs.selectCurrentPage }); + rerender(); + result.current.updateBulkActionsState({ action: BulkActionsVerbs.selectAll }); + rerender(); + const newBulkActions = result.current.bulkActions[0].items; + expect(initialBulkActions).toEqual(newBulkActions); + }); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_actions.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_actions.ts index 3bc36d1bdd049b..17c1b5be3c7efa 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_actions.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_actions.ts @@ -16,6 +16,7 @@ import { BulkActionsPanelConfig, BulkActionsState, BulkActionsVerbs, + BulkActionsReducerAction, UseBulkActionsRegistry, } from '../../../../types'; import { @@ -50,6 +51,7 @@ export interface UseBulkActions { bulkActions: BulkActionsPanelConfig[]; setIsBulkActionsLoading: (isLoading: boolean) => void; clearSelection: () => void; + updateBulkActionsState: React.Dispatch; } type UseBulkAddToCaseActionsProps = Pick & @@ -90,7 +92,9 @@ const addItemsToInitialPanel = ({ }) => { if (panels.length > 0) { if (panels[0].items) { - panels[0].items.push(...items); + panels[0].items = [...panels[0].items, ...items].filter( + (item, index, self) => index === self.findIndex((newItem) => newItem.key === item.key) + ); } return panels; } else { @@ -205,6 +209,33 @@ export const useBulkUntrackActions = ({ const hasUptimePermission = application?.capabilities.uptime?.show; const hasSloPermission = application?.capabilities.slo?.show; const hasObservabilityPermission = application?.capabilities.observability?.show; + const onClick = useCallback( + async (alerts?: TimelineItem[]) => { + if (!alerts) return; + const alertUuids = alerts.map((alert) => alert._id); + const indices = alerts.map((alert) => alert._index ?? ''); + try { + setIsBulkActionsLoading(true); + if (isAllSelected) { + await untrackAlertsByQuery({ query, featureIds }); + } else { + await untrackAlerts({ indices, alertUuids }); + } + onSuccess(); + } finally { + setIsBulkActionsLoading(false); + } + }, + [ + query, + featureIds, + isAllSelected, + onSuccess, + setIsBulkActionsLoading, + untrackAlerts, + untrackAlertsByQuery, + ] + ); return useMemo(() => { // Check if at least one Observability feature is enabled @@ -225,28 +256,10 @@ export const useBulkUntrackActions = ({ disableOnQuery: false, disabledLabel: MARK_AS_UNTRACKED, 'data-test-subj': 'mark-as-untracked', - onClick: async (alerts?: TimelineItem[]) => { - if (!alerts) return; - const alertUuids = alerts.map((alert) => alert._id); - const indices = alerts.map((alert) => alert._index ?? ''); - try { - setIsBulkActionsLoading(true); - if (isAllSelected) { - await untrackAlertsByQuery({ query, featureIds }); - } else { - await untrackAlerts({ indices, alertUuids }); - } - onSuccess(); - } finally { - setIsBulkActionsLoading(false); - } - }, + onClick, }, ]; }, [ - onSuccess, - setIsBulkActionsLoading, - untrackAlerts, application?.capabilities, hasApmPermission, hasInfrastructurePermission, @@ -254,10 +267,7 @@ export const useBulkUntrackActions = ({ hasUptimePermission, hasSloPermission, hasObservabilityPermission, - featureIds, - query, - isAllSelected, - untrackAlertsByQuery, + onClick, ]); };