diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx index e7e46a09b2cf4..096ada86f33f9 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx @@ -39,6 +39,7 @@ import { TableId, } from '@kbn/securitysolution-data-table'; import type { RunTimeMappings } from '@kbn/timelines-plugin/common/search_strategy'; +import { useGroupTakeActionsItems } from '../../../../detections/hooks/alerts_table/use_group_take_action_items'; import { defaultGroupStatsAggregations, defaultGroupStatsRenderer, @@ -559,6 +560,11 @@ const RuleDetailsPageComponent: React.FC = ({ confirmManualRuleRun, } = useManualRuleRunConfirmation(); + const groupTakeActionItems = useGroupTakeActionsItems({ + currentStatus: currentAlertStatusFilterValue, + showAlertStatusActions: Boolean(hasIndexWrite) && Boolean(hasIndexMaintenance), + }); + const accordionExtraActionGroupStats = useMemo( () => ({ aggregations: defaultGroupStatsAggregations, @@ -796,14 +802,12 @@ const RuleDetailsPageComponent: React.FC = ({ ; }; - currentAlertStatusFilterValue?: Status[]; defaultFilters?: Filter[]; /** * Default values to display in the group selection dropdown. @@ -66,8 +65,11 @@ export interface AlertsTableComponentProps { from: string; globalFilters: Filter[]; globalQuery: Query; - hasIndexMaintenance: boolean; - hasIndexWrite: boolean; + /** + * Allows to customize the content of the Take actions button rendered at the group level. + * If no value is provided, the Take actins button is not displayed. + */ + groupTakeActionItems?: GroupTakeActionItems; loading: boolean; renderChildComponent: (groupingFilters: Filter[]) => React.ReactElement; runtimeMappings: RunTimeMappings; @@ -326,6 +328,7 @@ const GroupedAlertsTableComponent: React.FC = (props) getGrouping={getGrouping} groupingLevel={level} groupStatsAggregations={groupStatusAggregations} + groupTakeActionItems={props.groupTakeActionItems} onGroupClose={() => resetGroupChildrenPagination(level)} pageIndex={pageIndex[level] ?? DEFAULT_PAGE_INDEX} pageSize={pageSize[level] ?? DEFAULT_PAGE_SIZE} diff --git a/x-pack/solutions/security/plugins/security_solution/public/detections/components/alerts_table/alerts_sub_grouping.tsx b/x-pack/solutions/security/plugins/security_solution/public/detections/components/alerts_table/alerts_sub_grouping.tsx index 1c37dbef28572..90aa4630cb18d 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detections/components/alerts_table/alerts_sub_grouping.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detections/components/alerts_table/alerts_sub_grouping.tsx @@ -15,11 +15,11 @@ import { getEsQueryConfig } from '@kbn/data-plugin/common'; import type { DynamicGroupingProps } from '@kbn/grouping/src'; import { parseGroupingQuery } from '@kbn/grouping/src'; import type { TableIdLiteral } from '@kbn/securitysolution-data-table'; +import type { GroupTakeActionItems } from './types'; import type { RunTimeMappings } from '../../../sourcerer/store/model'; import { SourcererScopeName } from '../../../sourcerer/store/model'; import { combineQueries } from '../../../common/lib/kuery'; import type { AlertsGroupingAggregation } from './grouping_settings/types'; -import type { Status } from '../../../../common/api/detection_engine'; import { InspectButton } from '../../../common/components/inspect'; import { useSourcererDataView } from '../../../sourcerer/containers'; import { useKibana } from '../../../common/lib/kibana'; @@ -31,13 +31,12 @@ import { buildTimeRangeFilter } from './helpers'; import * as i18n from './translations'; import { useQueryAlerts } from '../../containers/detection_engine/alerts/use_query'; import { ALERTS_QUERY_NAMES } from '../../containers/detection_engine/alerts/constants'; -import { getAlertsGroupingQuery, useGroupTakeActionsItems } from './grouping_settings'; +import { getAlertsGroupingQuery } from './grouping_settings'; const ALERTS_GROUPING_ID = 'alerts-grouping'; const DEFAULT_FILTERS: Filter[] = []; interface OwnProps { - currentAlertStatusFilterValue?: Status[]; defaultFilters?: Filter[]; from: string; getGrouping: ( @@ -51,8 +50,11 @@ interface OwnProps { * This is then used to render values in the EuiAccordion `extraAction` section. */ groupStatsAggregations: (field: string) => NamedAggregation[]; - hasIndexMaintenance: boolean; - hasIndexWrite: boolean; + /** + * Allows to customize the content of the Take actions button rendered at the group level. + * If no value is provided, the Take actins button is not displayed. + */ + groupTakeActionItems?: GroupTakeActionItems; loading: boolean; onGroupClose: () => void; pageIndex: number; @@ -71,7 +73,6 @@ interface OwnProps { export type AlertsTableComponentProps = OwnProps; export const GroupedSubLevelComponent: React.FC = ({ - currentAlertStatusFilterValue, defaultFilters = DEFAULT_FILTERS, from, getGrouping, @@ -79,8 +80,7 @@ export const GroupedSubLevelComponent: React.FC = ({ globalQuery, groupingLevel, groupStatsAggregations, - hasIndexMaintenance, - hasIndexWrite, + groupTakeActionItems, loading, onGroupClose, pageIndex, @@ -109,7 +109,7 @@ export const GroupedSubLevelComponent: React.FC = ({ indexPattern, browserFields, filters: [ - ...(defaultFilters ?? []), + ...defaultFilters, ...globalFilters, ...customFilters, ...(parentGroupingFilter ? JSON.parse(parentGroupingFilter) : []), @@ -243,20 +243,18 @@ export const GroupedSubLevelComponent: React.FC = ({ [uniqueQueryId] ); - const takeActionItems = useGroupTakeActionsItems({ - currentStatus: currentAlertStatusFilterValue, - showAlertStatusActions: hasIndexWrite && hasIndexMaintenance, - }); - const getTakeActionItems = useCallback( - (groupFilters: Filter[], groupNumber: number) => - takeActionItems({ + (groupFilters: Filter[], groupNumber: number) => { + const takeActionParams = { groupNumber, query: getGlobalQuery([...(defaultFilters ?? []), ...groupFilters])?.filterQuery, selectedGroup, tableId, - }), - [defaultFilters, getGlobalQuery, selectedGroup, tableId, takeActionItems] + }; + + return groupTakeActionItems?.(takeActionParams) ?? []; + }, + [defaultFilters, getGlobalQuery, groupTakeActionItems, selectedGroup, tableId] ); const onChangeGroupsItemsPerPage = useCallback( @@ -280,13 +278,14 @@ export const GroupedSubLevelComponent: React.FC = ({ onGroupClose, renderChildComponent, selectedGroup, - takeActionItems: getTakeActionItems, + ...(groupTakeActionItems && { takeActionItems: getTakeActionItems }), }), [ aggs, getGrouping, getTakeActionItems, groupingLevel, + groupTakeActionItems, inspect, isLoadingGroups, loading, diff --git a/x-pack/solutions/security/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/index.tsx index 83b03e1105cdb..a0b15a1bbf7b8 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/index.tsx @@ -9,5 +9,4 @@ export * from './default_grouping_options'; export * from './default_group_stats_aggregations'; export * from './default_group_stats_renderers'; export * from './default_group_title_renderers'; -export * from './group_take_action_items'; export * from './query_builder'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/detections/components/alerts_table/types.ts b/x-pack/solutions/security/plugins/security_solution/public/detections/components/alerts_table/types.ts index ee75ba69cc747..15c810a9fabc2 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detections/components/alerts_table/types.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/detections/components/alerts_table/types.ts @@ -17,7 +17,6 @@ import type { Status } from '../../../../common/api/detection_engine'; import type { Note } from '../../../../common/api/timeline'; import type { DataProvider } from '../../../timelines/components/timeline/data_providers/data_provider'; import type { TimelineModel } from '../../../timelines/store/model'; -import type { inputsModel } from '../../../common/store'; import type { ControlColumnProps, RowRenderer } from '../../../../common/types'; export interface SetEventsLoadingProps { @@ -30,23 +29,6 @@ export interface SetEventsDeletedProps { isDeleted: boolean; } -export interface UpdateAlertsStatusProps { - alertIds: string[]; - status: Status; - selectedStatus: Status; -} - -export type UpdateAlertsStatusCallback = ( - refetchQuery: inputsModel.Refetch, - { alertIds, status, selectedStatus }: UpdateAlertsStatusProps -) => void; - -export type UpdateAlertsStatus = ({ - alertIds, - status, - selectedStatus, -}: UpdateAlertsStatusProps) => void; - export interface UpdateAlertStatusActionProps { query?: string; alertIds: string[]; @@ -99,3 +81,22 @@ export type SecurityAlertsTableProps = AlertsTablePropsWithRef = NonNullable; export type { AlertWithLegacyFormats } from '@kbn/response-ops-alerts-table/types'; + +export type GroupTakeActionItems = (props: { + /** + * Query to run when an item is clicked (meaning when an alert status is updated) + */ + query?: string; + /** + * Id of the table (used for telemetry) + */ + tableId: string; + /** + * Group number to know which group to apply the logic to. This value is coming from the callback in the kbn-grouping package. + */ + groupNumber: number; + /** + * Selected group to know which group is extended/visible. This is coming from the getLevel function in the detections alert grouping code. + */ + selectedGroup: string; +}) => JSX.Element[]; diff --git a/x-pack/solutions/security/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_take_action_items.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/detections/hooks/alerts_table/use_group_take_action_items.test.tsx similarity index 95% rename from x-pack/solutions/security/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_take_action_items.test.tsx rename to x-pack/solutions/security/plugins/security_solution/public/detections/hooks/alerts_table/use_group_take_action_items.test.tsx index 7e1c9e0a1cfbb..0c369dbdec2ce 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_take_action_items.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detections/hooks/alerts_table/use_group_take_action_items.test.tsx @@ -5,10 +5,10 @@ * 2.0. */ -import { waitFor, renderHook } from '@testing-library/react'; +import { renderHook, waitFor } from '@testing-library/react'; import React from 'react'; -import { TestProviders } from '../../../../common/mock'; -import { useGroupTakeActionsItems } from '.'; +import { TestProviders } from '../../../common/mock'; +import { useGroupTakeActionsItems } from './use_group_take_action_items'; describe('useGroupTakeActionsItems', () => { const wrapperContainer: React.FC<{ children?: React.ReactNode }> = ({ children }) => ( diff --git a/x-pack/solutions/security/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_take_action_items.tsx b/x-pack/solutions/security/plugins/security_solution/public/detections/hooks/alerts_table/use_group_take_action_items.tsx similarity index 78% rename from x-pack/solutions/security/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_take_action_items.tsx rename to x-pack/solutions/security/plugins/security_solution/public/detections/hooks/alerts_table/use_group_take_action_items.tsx index e5e753b1c7763..b306bf8ff6613 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detections/components/alerts_table/grouping_settings/group_take_action_items.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detections/hooks/alerts_table/use_group_take_action_items.tsx @@ -8,33 +8,29 @@ import React, { useCallback, useMemo } from 'react'; import { EuiContextMenuItem } from '@elastic/eui'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import type { Status } from '../../../../../common/api/detection_engine'; -import type { inputsModel } from '../../../../common/store'; -import { inputsSelectors } from '../../../../common/store'; -import { useStartTransaction } from '../../../../common/lib/apm/use_start_transaction'; -import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import type { AlertWorkflowStatus } from '../../../../common/types'; -import { APM_USER_INTERACTIONS } from '../../../../common/lib/apm/constants'; -import { updateAlertStatus } from '../../../../common/components/toolbar/bulk_actions/update_alerts'; +import type { GroupTakeActionItems } from '../../components/alerts_table/types'; +import type { Status } from '../../../../common/api/detection_engine'; +import type { inputsModel } from '../../../common/store'; +import { inputsSelectors } from '../../../common/store'; +import { useStartTransaction } from '../../../common/lib/apm/use_start_transaction'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; +import type { AlertWorkflowStatus } from '../../../common/types'; +import { APM_USER_INTERACTIONS } from '../../../common/lib/apm/constants'; +import { updateAlertStatus } from '../../../common/components/toolbar/bulk_actions/update_alerts'; import { BULK_ACTION_ACKNOWLEDGED_SELECTED, BULK_ACTION_CLOSE_SELECTED, BULK_ACTION_OPEN_SELECTED, -} from '../../../../common/components/toolbar/bulk_actions/translations'; +} from '../../../common/components/toolbar/bulk_actions/translations'; import { UPDATE_ALERT_STATUS_FAILED, UPDATE_ALERT_STATUS_FAILED_DETAILED, -} from '../../../../common/translations'; -import { FILTER_ACKNOWLEDGED, FILTER_CLOSED, FILTER_OPEN } from '../../../../../common/types'; -import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; +} from '../../../common/translations'; +import { FILTER_ACKNOWLEDGED, FILTER_CLOSED, FILTER_OPEN } from '../../../../common/types'; +import { useDeepEqualSelector } from '../../../common/hooks/use_selector'; import * as i18n from '../translations'; -import { AlertsEventTypes, METRIC_TYPE, track } from '../../../../common/lib/telemetry'; -import type { StartServices } from '../../../../types'; - -export interface TakeActionsProps { - currentStatus?: Status[]; - showAlertStatusActions?: boolean; -} +import { AlertsEventTypes, METRIC_TYPE, track } from '../../../common/lib/telemetry'; +import type { StartServices } from '../../../types'; const getTelemetryEvent = { groupedAlertsTakeAction: ({ @@ -48,21 +44,38 @@ const getTelemetryEvent = { }) => `alerts_table_${tableId}_group-${groupNumber}_mark-${status}`, }; +export interface UseGroupTakeActionsItemsParams { + /** + * Optional property to allow filtering the options in the dropdown depending on the current alert statuses + */ + currentStatus?: Status[]; + /** + * If true, will show all the action items + */ + showAlertStatusActions?: boolean; +} + +/** + * Hook returning a set of action items to be accessed when users click on the Take actions button displayed at the grouping alerts table group level. + * Currently the action returned are: mark as opened, mark as acknowledged or mark as closed. + * The hook is used in the alerts page, the rule details page and the entity analytics top risk score section. + */ export const useGroupTakeActionsItems = ({ currentStatus, showAlertStatusActions = true, -}: TakeActionsProps) => { +}: UseGroupTakeActionsItemsParams): GroupTakeActionItems => { const { addSuccess, addError, addWarning } = useAppToasts(); const { startTransaction } = useStartTransaction(); const getGlobalQuerySelector = useMemo(() => inputsSelectors.globalQuery(), []); const globalQueries = useDeepEqualSelector(getGlobalQuerySelector); - const refetchQuery = useCallback(() => { - globalQueries.forEach((q) => q.refetch && (q.refetch as inputsModel.Refetch)()); - }, [globalQueries]); const { services: { telemetry }, } = useKibana(); + const refetchQuery = useCallback(() => { + globalQueries.forEach((q) => q.refetch && (q.refetch as inputsModel.Refetch)()); + }, [globalQueries]); + const reportAlertsGroupingTakeActionClick = useCallback( (params: { tableId: string; @@ -75,20 +88,6 @@ export const useGroupTakeActionsItems = ({ [telemetry] ); - const onUpdateSuccess = useCallback( - (updated: number, conflicts: number, newStatus: AlertWorkflowStatus) => { - refetchQuery(); - }, - [refetchQuery] - ); - - const onUpdateFailure = useCallback( - (newStatus: AlertWorkflowStatus, error: Error) => { - refetchQuery(); - }, - [refetchQuery] - ); - const onAlertStatusUpdateSuccess = useCallback( (updated: number, conflicts: number, newStatus: AlertWorkflowStatus) => { if (conflicts > 0) { @@ -111,11 +110,9 @@ export const useGroupTakeActionsItems = ({ } addSuccess({ title }); } - if (onUpdateSuccess) { - onUpdateSuccess(updated, conflicts, newStatus); - } + refetchQuery(); }, - [addSuccess, addWarning, onUpdateSuccess] + [addSuccess, addWarning, refetchQuery] ); const onAlertStatusUpdateFailure = useCallback( @@ -132,11 +129,9 @@ export const useGroupTakeActionsItems = ({ title = i18n.ACKNOWLEDGED_ALERT_FAILED_TOAST; } addError(error.message, { title }); - if (onUpdateFailure) { - onUpdateFailure(newStatus, error); - } + refetchQuery(); }, - [addError, onUpdateFailure] + [addError, refetchQuery] ); const onClickUpdate = useCallback( @@ -189,20 +184,15 @@ export const useGroupTakeActionsItems = ({ ] ); - return useMemo(() => { - const getActionItems = ({ - query, - tableId, - groupNumber, - selectedGroup, - }: { - query?: string; - tableId: string; - groupNumber: number; - selectedGroup: string; - }) => { - const actionItems: JSX.Element[] = []; - if (showAlertStatusActions) { + return useMemo( + (): GroupTakeActionItems => + ({ query, tableId, groupNumber, selectedGroup }) => { + const actionItems: JSX.Element[] = []; + + if (!showAlertStatusActions) { + return actionItems; + } + if (currentStatus && currentStatus.length === 1) { const singleStatus = currentStatus[0]; if (singleStatus !== FILTER_OPEN) { @@ -288,10 +278,9 @@ export const useGroupTakeActionsItems = ({ ) ); } - } - return actionItems; - }; - return getActionItems; - }, [currentStatus, onClickUpdate, showAlertStatusActions]); + return actionItems; + }, + [currentStatus, onClickUpdate, showAlertStatusActions] + ); }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/detections/pages/alerts/detection_engine.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/detections/pages/alerts/detection_engine.test.tsx index 166ecf083ff8c..fe7af88c25a88 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detections/pages/alerts/detection_engine.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detections/pages/alerts/detection_engine.test.tsx @@ -5,10 +5,10 @@ * 2.0. */ -import React, { useEffect } from 'react'; +import React from 'react'; import { render, waitFor } from '@testing-library/react'; import { useParams } from 'react-router-dom'; -import { mockGlobalState, TestProviders, createMockStore } from '../../../common/mock'; +import { createMockStore, mockGlobalState, TestProviders } from '../../../common/mock'; import { useUserData } from '../../components/user_info'; import { useSourcererDataView } from '../../../sourcerer/containers'; import type { State } from '../../../common/store'; @@ -22,10 +22,8 @@ import { createStubDataView } from '@kbn/data-views-plugin/common/data_view.stub import { useListsConfig } from '../../containers/detection_engine/lists/use_lists_config'; import * as alertFilterControlsPackage from '@kbn/alerts-ui-shared/src/alert_filter_controls/alert_filter_controls'; import { DetectionEnginePage } from './detection_engine'; -import type { AlertsTableComponentProps } from '../../components/alerts_table/alerts_grouping'; import { TableId } from '@kbn/securitysolution-data-table'; import { useUpsellingMessage } from '../../../common/hooks/use_upselling'; -import { mockAlertFilterControls } from '@kbn/alerts-ui-shared/src/alert_filter_controls/mocks'; // Test will fail because we will to need to mock some core services to make the test work // For now let's forget about SiemSearchBar and QueryBar @@ -39,23 +37,9 @@ jest.mock('../../../common/hooks/use_space_id', () => ({ useSpaceId: () => 'default', })); jest.mock('@kbn/alerts-ui-shared/src/alert_filter_controls/alert_filter_controls'); - -const mockStatusCapture = jest.fn(); -const GroupedAlertsTable: React.FC = ({ - currentAlertStatusFilterValue, -}) => { - useEffect(() => { - if (currentAlertStatusFilterValue) { - mockStatusCapture(currentAlertStatusFilterValue); - } - }, [currentAlertStatusFilterValue]); - return ; -}; - jest.mock('../../components/alerts_table/alerts_grouping', () => ({ - GroupedAlertsTable, + GroupedAlertsTable: () => , })); - jest.mock('../../containers/detection_engine/lists/use_lists_config'); jest.mock('../../components/user_info'); jest.mock('../../../sourcerer/containers'); @@ -220,9 +204,11 @@ describe('DetectionEnginePageComponent', () => { .mockImplementation(() => ); (useUpsellingMessage as jest.Mock).mockReturnValue('Go for Platinum!'); }); + beforeEach(() => { jest.clearAllMocks(); }); + it('renders correctly', async () => { const { getByTestId } = render( @@ -319,101 +305,4 @@ describe('DetectionEnginePageComponent', () => { expect.anything() ); }); - - it('the pageFiltersUpdateHandler updates status when a multi status filter is passed', async () => { - jest.spyOn(alertFilterControlsPackage, 'AlertFilterControls').mockImplementationOnce( - mockAlertFilterControls([ - { - meta: { - index: 'security-solution-default', - key: 'kibana.alert.workflow_status', - params: ['open', 'acknowledged'], - }, - }, - ]) - ); - - render( - - - - - - ); - // when statusFilter updates, we call mockStatusCapture in test mocks - expect(mockStatusCapture).toHaveBeenNthCalledWith(1, []); - expect(mockStatusCapture).toHaveBeenNthCalledWith(2, ['open', 'acknowledged']); - }); - - it('the pageFiltersUpdateHandler updates status when a single status filter is passed', async () => { - jest.spyOn(alertFilterControlsPackage, 'AlertFilterControls').mockImplementation( - mockAlertFilterControls([ - { - meta: { - index: 'security-solution-default', - key: 'kibana.alert.workflow_status', - disabled: false, - }, - query: { - match_phrase: { - 'kibana.alert.workflow_status': 'open', - }, - }, - }, - { - meta: { - index: 'security-solution-default', - key: 'kibana.alert.severity', - disabled: false, - }, - query: { - match_phrase: { - 'kibana.alert.severity': 'low', - }, - }, - }, - ]) - ); - - render( - - - - - - ); - // when statusFilter updates, we call mockStatusCapture in test mocks - expect(mockStatusCapture).toHaveBeenNthCalledWith(1, []); - expect(mockStatusCapture).toHaveBeenNthCalledWith(2, ['open']); - }); - - it('the pageFiltersUpdateHandler clears status when no status filter is passed', async () => { - jest.spyOn(alertFilterControlsPackage, 'AlertFilterControls').mockImplementation( - mockAlertFilterControls([ - { - meta: { - index: 'security-solution-default', - key: 'kibana.alert.severity', - disabled: false, - }, - query: { - match_phrase: { - 'kibana.alert.severity': 'low', - }, - }, - }, - ]) - ); - - render( - - - - - - ); - // when statusFilter updates, we call mockStatusCapture in test mocks - expect(mockStatusCapture).toHaveBeenNthCalledWith(1, []); - expect(mockStatusCapture).toHaveBeenNthCalledWith(2, []); - }); }); diff --git a/x-pack/solutions/security/plugins/security_solution/public/detections/pages/alerts/detection_engine.tsx b/x-pack/solutions/security/plugins/security_solution/public/detections/pages/alerts/detection_engine.tsx index 9d658a1606853..303855f9b0356 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detections/pages/alerts/detection_engine.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detections/pages/alerts/detection_engine.tsx @@ -34,6 +34,7 @@ import { import { isEqual } from 'lodash'; import type { FilterGroupHandler } from '@kbn/alerts-ui-shared'; import type { RunTimeMappings } from '@kbn/timelines-plugin/common/search_strategy'; +import { useGroupTakeActionsItems } from '../../hooks/alerts_table/use_group_take_action_items'; import { defaultGroupingOptions, defaultGroupStatsAggregations, @@ -329,6 +330,11 @@ const DetectionEnginePageComponent: React.FC = () [alertsTableDefaultFilters, isAlertTableLoading] ); + const groupTakeActionItems = useGroupTakeActionsItems({ + currentStatus: statusFilter, + showAlertStatusActions: Boolean(hasIndexWrite) && Boolean(hasIndexMaintenance), + }); + const accordionExtraActionGroupStats = useMemo( () => ({ aggregations: defaultGroupStatsAggregations, @@ -438,14 +444,12 @@ const DetectionEnginePageComponent: React.FC = () ({ const defaultFilters = useMemo(() => [...inputFilters, ...filters], [filters, inputFilters]); + const groupTakeActionItems = useGroupTakeActionsItems({ + showAlertStatusActions: Boolean(hasIndexWrite) && Boolean(hasIndexMaintenance), + }); + const accordionExtraActionGroupStats = useMemo( () => ({ aggregations: defaultGroupStatsAggregations, @@ -138,8 +143,7 @@ export const TopRiskScoreContributorsAlerts = ({ from={from} globalFilters={filters} globalQuery={query} - hasIndexMaintenance={hasIndexMaintenance ?? false} - hasIndexWrite={hasIndexWrite ?? false} + groupTakeActionItems={groupTakeActionItems} loading={userInfoLoading || loading} renderChildComponent={renderGroupedAlertTable} runtimeMappings={sourcererDataView?.runtimeFieldMap as RunTimeMappings}