[RAC] Actions popovers UI unification (#109221)

* popover padding size unified

* remove panels from all context menus

* action items order changed

* cases menu items test fixed

* translations and small changes

* remove components not used anywhere

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Angela Chuang <yi-chun.chuang@elastic.co>
This commit is contained in:
Sergi Massaneda 2021-08-23 14:42:24 +02:00 committed by GitHub
parent ec7889a938
commit 64dff78dce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 209 additions and 485 deletions

View file

@ -33,7 +33,7 @@ import {
EuiDataGridColumn,
EuiFlexGroup,
EuiFlexItem,
EuiContextMenu,
EuiContextMenuPanel,
EuiPopover,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
@ -212,26 +212,21 @@ function ObservabilityActions({
onUpdateFailure: onAlertStatusUpdated,
});
const actionsPanels = useMemo(() => {
const actionsMenuItems = useMemo(() => {
return [
{
id: 0,
content: [
timelines.getAddToExistingCaseButton({
event,
casePermissions,
appId: observabilityFeatureId,
onClose: afterCaseSelection,
}),
timelines.getAddToNewCaseButton({
event,
casePermissions,
appId: observabilityFeatureId,
onClose: afterCaseSelection,
}),
...(alertPermissions.crud ? statusActionItems : []),
],
},
timelines.getAddToExistingCaseButton({
event,
casePermissions,
appId: observabilityFeatureId,
onClose: afterCaseSelection,
}),
timelines.getAddToNewCaseButton({
event,
casePermissions,
appId: observabilityFeatureId,
onClose: afterCaseSelection,
}),
...(alertPermissions.crud ? statusActionItems : []),
];
}, [afterCaseSelection, casePermissions, timelines, event, statusActionItems, alertPermissions]);
@ -255,26 +250,28 @@ function ObservabilityActions({
aria-label="View alert in app"
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiPopover
button={
<EuiButtonIcon
display="empty"
size="s"
color="text"
iconType="boxesHorizontal"
aria-label="More"
onClick={() => toggleActionsPopover(eventId)}
/>
}
isOpen={openActionsPopoverId === eventId}
closePopover={closeActionsPopover}
panelPaddingSize="none"
anchorPosition="downLeft"
>
<EuiContextMenu panels={actionsPanels} initialPanelId={0} />
</EuiPopover>
</EuiFlexItem>
{actionsMenuItems.length > 0 && (
<EuiFlexItem>
<EuiPopover
button={
<EuiButtonIcon
display="empty"
size="s"
color="text"
iconType="boxesHorizontal"
aria-label="More"
onClick={() => toggleActionsPopover(eventId)}
/>
}
isOpen={openActionsPopoverId === eventId}
closePopover={closeActionsPopover}
panelPaddingSize="none"
anchorPosition="downLeft"
>
<EuiContextMenuPanel size="s" items={actionsMenuItems} />
</EuiPopover>
</EuiFlexItem>
)}
</EuiFlexGroup>
</>
);

View file

@ -19,6 +19,14 @@ export const mockTimelines = {
.fn()
.mockReturnValue(<div data-test-subj="add-to-case-action">{'Add to case'}</div>),
getAddToCaseAction: jest.fn(),
getAddToExistingCaseButton: jest.fn(),
getAddToNewCaseButton: jest.fn(),
getAddToExistingCaseButton: jest.fn().mockReturnValue(
<div key="add-to-existing-case-action" data-test-subj="add-to-existing-case-action">
{'Add to existing case'}
</div>
),
getAddToNewCaseButton: jest.fn().mockReturnValue(
<div key="add-to-new-case-action" data-test-subj="add-to-new-case-action">
{'Add to new case'}
</div>
),
};

View file

@ -1,36 +0,0 @@
/*
* 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 { EuiContextMenuItem } from '@elastic/eui';
import React from 'react';
import * as i18n from '../translations';
interface AddEndpointExceptionProps {
onClick: () => void;
disabled?: boolean;
}
const AddEndpointExceptionComponent: React.FC<AddEndpointExceptionProps> = ({
onClick,
disabled,
}) => {
return (
<EuiContextMenuItem
key="add-endpoint-exception-menu-item"
aria-label={i18n.ACTION_ADD_ENDPOINT_EXCEPTION}
data-test-subj="add-endpoint-exception-menu-item"
id="addEndpointException"
onClick={onClick}
disabled={disabled}
size="s"
>
{i18n.ACTION_ADD_ENDPOINT_EXCEPTION}
</EuiContextMenuItem>
);
};
export const AddEndpointException = React.memo(AddEndpointExceptionComponent);

View file

@ -1,32 +0,0 @@
/*
* 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 { EuiContextMenuItem } from '@elastic/eui';
import React from 'react';
import * as i18n from '../translations';
interface AddEventFilterProps {
onClick: () => void;
disabled?: boolean;
}
const AddEventFilterComponent: React.FC<AddEventFilterProps> = ({ onClick, disabled }) => {
return (
<EuiContextMenuItem
key="add-event-filter-menu-item"
aria-label={i18n.ACTION_ADD_EVENT_FILTER}
data-test-subj="add-event-filter-menu-item"
id="addEventFilter"
onClick={onClick}
disabled={disabled}
>
{i18n.ACTION_ADD_EVENT_FILTER}
</EuiContextMenuItem>
);
};
export const AddEventFilter = React.memo(AddEventFilterComponent);

View file

@ -1,33 +0,0 @@
/*
* 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 { EuiContextMenuItem } from '@elastic/eui';
import React from 'react';
import * as i18n from '../translations';
interface AddExceptionProps {
disabled?: boolean;
onClick: () => void;
}
const AddExceptionComponent: React.FC<AddExceptionProps> = ({ disabled, onClick }) => {
return (
<EuiContextMenuItem
key="add-exception-menu-item"
aria-label={i18n.ACTION_ADD_EXCEPTION}
data-test-subj="add-exception-menu-item"
id="addException"
onClick={onClick}
disabled={disabled}
size="s"
>
{i18n.ACTION_ADD_EXCEPTION}
</EuiContextMenuItem>
);
};
export const AddException = React.memo(AddExceptionComponent);

View file

@ -56,7 +56,8 @@ jest.mock('../../../../common/lib/kibana', () => ({
}));
const actionMenuButton = '[data-test-subj="timeline-context-menu-button"] button';
const addToCaseButton = '[data-test-subj="attach-alert-to-case-button"]';
const addToExistingCaseButton = '[data-test-subj="add-to-existing-case-action"]';
const addToNewCaseButton = '[data-test-subj="add-to-new-case-action"]';
describe('InvestigateInResolverAction', () => {
test('it render AddToCase context menu item if timelineId === TimelineId.detectionsPage', () => {
@ -65,7 +66,8 @@ describe('InvestigateInResolverAction', () => {
});
wrapper.find(actionMenuButton).simulate('click');
expect(wrapper.find(addToCaseButton).first().exists()).toEqual(true);
expect(wrapper.find(addToExistingCaseButton).first().exists()).toEqual(true);
expect(wrapper.find(addToNewCaseButton).first().exists()).toEqual(true);
});
test('it render AddToCase context menu item if timelineId === TimelineId.detectionsRulesDetailsPage', () => {
@ -77,7 +79,8 @@ describe('InvestigateInResolverAction', () => {
);
wrapper.find(actionMenuButton).simulate('click');
expect(wrapper.find(addToCaseButton).first().exists()).toEqual(true);
expect(wrapper.find(addToExistingCaseButton).first().exists()).toEqual(true);
expect(wrapper.find(addToNewCaseButton).first().exists()).toEqual(true);
});
test('it render AddToCase context menu item if timelineId === TimelineId.active', () => {
@ -86,7 +89,8 @@ describe('InvestigateInResolverAction', () => {
});
wrapper.find(actionMenuButton).simulate('click');
expect(wrapper.find(addToCaseButton).first().exists()).toEqual(true);
expect(wrapper.find(addToExistingCaseButton).first().exists()).toEqual(true);
expect(wrapper.find(addToNewCaseButton).first().exists()).toEqual(true);
});
test('it does NOT render AddToCase context menu item when timelineId is not in the allowed list', () => {
@ -94,6 +98,7 @@ describe('InvestigateInResolverAction', () => {
wrappingComponent: TestProviders,
});
wrapper.find(actionMenuButton).simulate('click');
expect(wrapper.find(addToCaseButton).first().exists()).toEqual(false);
expect(wrapper.find(addToExistingCaseButton).first().exists()).toEqual(false);
expect(wrapper.find(addToNewCaseButton).first().exists()).toEqual(false);
});
});

View file

@ -7,16 +7,11 @@
import React, { useCallback, useMemo, useState } from 'react';
import { EuiButtonIcon, EuiContextMenu, EuiPopover, EuiToolTip } from '@elastic/eui';
import { EuiButtonIcon, EuiContextMenuPanel, EuiPopover, EuiToolTip } from '@elastic/eui';
import { indexOf } from 'lodash';
import { ExceptionListType } from '@kbn/securitysolution-io-ts-list-types';
import { get, getOr } from 'lodash/fp';
import {
EuiContextMenuPanelDescriptor,
EuiContextMenuPanelItemDescriptor,
} from '@elastic/eui/src/components/context_menu/context_menu';
import styled from 'styled-components';
import { buildGetAlertByIdQuery } from '../../../../common/components/exceptions/helpers';
import { EventsTdContent } from '../../../../timelines/components/timeline/styles';
import { DEFAULT_ICON_BUTTON_WIDTH } from '../../../../timelines/components/timeline/helpers';
@ -54,11 +49,6 @@ interface AlertContextMenuProps {
onRuleChange?: () => void;
timelineId: string;
}
export const NestedWrapper = styled.span`
button.euiContextMenuItem {
padding: 0;
}
`;
const AlertContextMenuComponent: React.FC<AlertContextMenuProps> = ({
ariaLabel = i18n.MORE_ACTIONS,
@ -99,27 +89,18 @@ const AlertContextMenuComponent: React.FC<AlertContextMenuProps> = ({
]
);
const hasWritePermissions = useGetUserCasesPermissions()?.crud ?? false;
const addToCaseAction = useMemo(
const addToCaseActionItems = useMemo(
() =>
[
TimelineId.detectionsPage,
TimelineId.detectionsRulesDetailsPage,
TimelineId.active,
].includes(timelineId as TimelineId) && hasWritePermissions
? {
actionItem: [
{
name: i18n.ACTION_ADD_TO_CASE,
panel: 2,
'data-test-subj': 'attach-alert-to-case-button',
},
],
content: [
timelinesUi.getAddToExistingCaseButton(addToCaseActionProps),
timelinesUi.getAddToNewCaseButton(addToCaseActionProps),
],
}
: { actionItem: [], content: [] },
? [
timelinesUi.getAddToExistingCaseButton(addToCaseActionProps),
timelinesUi.getAddToNewCaseButton(addToCaseActionProps),
]
: [],
[addToCaseActionProps, hasWritePermissions, timelineId, timelinesUi]
);
@ -179,7 +160,7 @@ const AlertContextMenuComponent: React.FC<AlertContextMenuProps> = ({
onAddEventFilterClick,
} = useEventFilterModal();
const { actionItems } = useAlertsActions({
const { actionItems: statusActionItems } = useAlertsActions({
alertStatus,
eventId: ecsRowData?._id,
indexName: ecsRowData?._index ?? '',
@ -201,72 +182,59 @@ const AlertContextMenuComponent: React.FC<AlertContextMenuProps> = ({
closePopover();
}, [closePopover, onAddEventFilterClick]);
const { exceptionActions } = useExceptionActions({
const { exceptionActionItems } = useExceptionActions({
isEndpointAlert,
onAddExceptionTypeClick: handleOnAddExceptionTypeClick,
});
const investigateInResolverAction = useInvestigateInResolverContextItem({
const investigateInResolverActionItems = useInvestigateInResolverContextItem({
timelineId,
ecsData: ecsRowData,
onClose: afterItemSelection,
});
const eventFilterAction = useEventFilterAction({
const { eventFilterActionItems } = useEventFilterAction({
onAddEventFilterClick: handleOnAddEventFilterClick,
});
const items: EuiContextMenuPanelItemDescriptor[] = useMemo(
const items: React.ReactElement[] = useMemo(
() =>
!isEvent && ruleId
? [
...investigateInResolverAction,
...addToCaseAction.actionItem,
...actionItems.map((aI) => ({ name: <NestedWrapper>{aI}</NestedWrapper> })),
...exceptionActions,
...investigateInResolverActionItems,
...addToCaseActionItems,
...statusActionItems,
...exceptionActionItems,
]
: [...investigateInResolverAction, ...addToCaseAction.actionItem, eventFilterAction],
: [...investigateInResolverActionItems, ...addToCaseActionItems, ...eventFilterActionItems],
[
actionItems,
addToCaseAction.actionItem,
eventFilterAction,
exceptionActions,
investigateInResolverAction,
statusActionItems,
addToCaseActionItems,
eventFilterActionItems,
exceptionActionItems,
investigateInResolverActionItems,
isEvent,
ruleId,
]
);
const panels: EuiContextMenuPanelDescriptor[] = useMemo(
() => [
{
id: 0,
items,
},
{
id: 2,
title: i18n.ACTION_ADD_TO_CASE,
content: addToCaseAction.content,
},
],
[addToCaseAction.content, items]
);
return (
<>
{timelinesUi.getAddToCaseAction(addToCaseActionProps)}
<div key="actions-context-menu">
<EventsTdContent textAlign="center" width={DEFAULT_ICON_BUTTON_WIDTH}>
<EuiPopover
id="singlePanel"
button={button}
isOpen={isPopoverOpen}
closePopover={closePopover}
panelPaddingSize="none"
anchorPosition="downLeft"
repositionOnScroll
>
<EuiContextMenu size="s" initialPanelId={0} panels={panels} />
</EuiPopover>
</EventsTdContent>
</div>
{items.length > 0 && (
<div key="actions-context-menu">
<EventsTdContent textAlign="center" width={DEFAULT_ICON_BUTTON_WIDTH}>
<EuiPopover
id="singlePanel"
button={button}
isOpen={isPopoverOpen}
closePopover={closePopover}
panelPaddingSize="none"
anchorPosition="downLeft"
repositionOnScroll
>
<EuiContextMenuPanel size="s" items={items} />
</EuiPopover>
</EventsTdContent>
</div>
)}
{exceptionModalType != null &&
ruleId != null &&
ruleName != null &&

View file

@ -1,36 +0,0 @@
/*
* 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 { EuiContextMenuItem } from '@elastic/eui';
import React from 'react';
import { FILTER_ACKNOWLEDGED } from '../../alerts_filter_group';
import * as i18n from '../../translations';
interface AcknowledgedAlertStatusProps {
onClick: () => void;
disabled?: boolean;
}
const AcknowledgedAlertStatusComponent: React.FC<AcknowledgedAlertStatusProps> = ({
onClick,
disabled,
}) => {
return (
<EuiContextMenuItem
key="acknowledged-alert"
aria-label={i18n.ACTION_ACKNOWLEDGED_ALERT}
data-test-subj="acknowledged-alert-status"
id={FILTER_ACKNOWLEDGED}
onClick={onClick}
disabled={disabled}
>
{i18n.ACTION_ACKNOWLEDGED_ALERT}
</EuiContextMenuItem>
);
};
export const AcknowledgedAlertStatus = React.memo(AcknowledgedAlertStatusComponent);

View file

@ -1,33 +0,0 @@
/*
* 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 { EuiContextMenuItem } from '@elastic/eui';
import React from 'react';
import { FILTER_CLOSED } from '../../alerts_filter_group';
import * as i18n from '../../translations';
interface CloseAlertActionProps {
onClick: () => void;
disabled?: boolean;
}
const CloseAlertActionComponent: React.FC<CloseAlertActionProps> = ({ onClick, disabled }) => {
return (
<EuiContextMenuItem
key="close-alert"
aria-label={i18n.ACTION_CLOSE_ALERT}
data-test-subj="close-alert-status"
id={FILTER_CLOSED}
onClick={onClick}
disabled={disabled}
>
{i18n.ACTION_CLOSE_ALERT}
</EuiContextMenuItem>
);
};
export const CloseAlertAction = React.memo(CloseAlertActionComponent);

View file

@ -1,33 +0,0 @@
/*
* 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 { EuiContextMenuItem } from '@elastic/eui';
import React from 'react';
import { FILTER_OPEN } from '../../alerts_filter_group';
import * as i18n from '../../translations';
interface OpenAlertStatusProps {
onClick: () => void;
disabled?: boolean;
}
const OpenAlertStatusComponent: React.FC<OpenAlertStatusProps> = ({ onClick, disabled }) => {
return (
<EuiContextMenuItem
key="open-alert"
aria-label={i18n.ACTION_OPEN_ALERT}
data-test-subj="open-alert-status"
id={FILTER_OPEN}
onClick={onClick}
disabled={disabled}
>
{i18n.ACTION_OPEN_ALERT}
</EuiContextMenuItem>
);
};
export const OpenAlertStatus = React.memo(OpenAlertStatusComponent);

View file

@ -5,8 +5,9 @@
* 2.0.
*/
import { useCallback, useMemo } from 'react';
import React, { useCallback, useMemo } from 'react';
import { useDispatch } from 'react-redux';
import { EuiContextMenuItem } from '@elastic/eui';
import { get } from 'lodash/fp';
import {
setActiveTabTimeline,
@ -44,9 +45,12 @@ export const useInvestigateInResolverContextItem = ({
return isDisabled
? []
: [
{
name: ACTION_INVESTIGATE_IN_RESOLVER,
onClick: handleClick,
},
<EuiContextMenuItem
key="investigate-in-resolver-action-item"
data-test-subj="investigate-in-resolver-action-item"
onClick={handleClick}
>
{ACTION_INVESTIGATE_IN_RESOLVER}
</EuiContextMenuItem>,
];
};

View file

@ -5,26 +5,13 @@
* 2.0.
*/
import { useCallback, useMemo } from 'react';
import React, { useCallback, useMemo } from 'react';
import { EuiContextMenuItem } from '@elastic/eui';
import type { ExceptionListType } from '@kbn/securitysolution-io-ts-list-types';
import { useUserData } from '../../user_info';
import { ACTION_ADD_ENDPOINT_EXCEPTION, ACTION_ADD_EXCEPTION } from '../translations';
interface ExceptionActions {
name: string;
onClick: () => void;
disabled: boolean;
}
interface UseExceptionActions {
disabledAddEndpointException: boolean;
disabledAddException: boolean;
exceptionActions: ExceptionActions[];
handleEndpointExceptionModal: () => void;
handleDetectionExceptionModal: () => void;
}
interface UseExceptionActionProps {
isEndpointAlert: boolean;
onAddExceptionTypeClick: (type: ExceptionListType) => void;
@ -33,7 +20,7 @@ interface UseExceptionActionProps {
export const useExceptionActions = ({
isEndpointAlert,
onAddExceptionTypeClick,
}: UseExceptionActionProps): UseExceptionActions => {
}: UseExceptionActionProps) => {
const [{ canUserCRUD, hasIndexWrite }] = useUserData();
const handleDetectionExceptionModal = useCallback(() => {
@ -47,20 +34,25 @@ export const useExceptionActions = ({
const disabledAddEndpointException = !canUserCRUD || !hasIndexWrite || !isEndpointAlert;
const disabledAddException = !canUserCRUD || !hasIndexWrite;
const exceptionActions = useMemo(
const exceptionActionItems = useMemo(
() => [
{
name: ACTION_ADD_ENDPOINT_EXCEPTION,
onClick: handleEndpointExceptionModal,
disabled: disabledAddEndpointException,
[`data-test-subj`]: 'add-endpoint-exception-menu-item',
},
{
name: ACTION_ADD_EXCEPTION,
onClick: handleDetectionExceptionModal,
disabled: disabledAddException,
[`data-test-subj`]: 'add-exception-menu-item',
},
<EuiContextMenuItem
key="add-endpoint-exception-menu-item"
data-test-subj="add-endpoint-exception-menu-item"
disabled={disabledAddEndpointException}
onClick={handleEndpointExceptionModal}
>
{ACTION_ADD_ENDPOINT_EXCEPTION}
</EuiContextMenuItem>,
<EuiContextMenuItem
key="add-exception-menu-item"
data-test-subj="add-exception-menu-item"
disabled={disabledAddException}
onClick={handleDetectionExceptionModal}
>
{ACTION_ADD_EXCEPTION}
</EuiContextMenuItem>,
],
[
disabledAddEndpointException,
@ -70,11 +62,5 @@ export const useExceptionActions = ({
]
);
return {
disabledAddEndpointException,
disabledAddException,
exceptionActions,
handleEndpointExceptionModal,
handleDetectionExceptionModal,
};
return { exceptionActionItems };
};

View file

@ -5,7 +5,8 @@
* 2.0.
*/
import { useMemo } from 'react';
import React, { useMemo } from 'react';
import { EuiContextMenuItem } from '@elastic/eui';
import { ACTION_ADD_EVENT_FILTER } from '../translations';
export const useEventFilterAction = ({
@ -13,12 +14,17 @@ export const useEventFilterAction = ({
}: {
onAddEventFilterClick: () => void;
}) => {
const eventFilterActions = useMemo(
() => ({
name: ACTION_ADD_EVENT_FILTER,
onClick: onAddEventFilterClick,
}),
const eventFilterActionItems = useMemo(
() => [
<EuiContextMenuItem
key="add-event-filter-menu-item"
data-test-subj="add-event-filter-menu-item"
onClick={onAddEventFilterClick}
>
{ACTION_ADD_EVENT_FILTER}
</EuiContextMenuItem>,
],
[onAddEventFilterClick]
);
return eventFilterActions;
return { eventFilterActionItems };
};

View file

@ -4,9 +4,10 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { useCallback } from 'react';
import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { EuiContextMenuItem } from '@elastic/eui';
import { useKibana } from '../../../../common/lib/kibana';
import { TimelineId } from '../../../../../common/types/timeline';
@ -102,18 +103,21 @@ export const useInvestigateInTimeline = ({
updateTimelineIsLoading,
]);
const investigateInTimelineAction = showInvestigateInTimelineAction
const investigateInTimelineActionItems = showInvestigateInTimelineAction
? [
{
name: ACTION_INVESTIGATE_IN_TIMELINE,
onClick: investigateInTimelineAlertClick,
disabled: isFetchingAlertEcs,
},
<EuiContextMenuItem
key="investigate-in-timeline-action-item"
data-test-subj="investigate-in-timeline-action-item"
disabled={isFetchingAlertEcs === true}
onClick={investigateInTimelineAlertClick}
>
{ACTION_INVESTIGATE_IN_TIMELINE}
</EuiContextMenuItem>,
]
: [];
return {
investigateInTimelineAction,
investigateInTimelineActionItems,
investigateInTimelineAlertClick,
showInvestigateInTimelineAction,
};

View file

@ -185,13 +185,6 @@ export const ACTION_ADD_EVENT_FILTER = i18n.translate(
}
);
export const ACTION_ADD_TO_CASE = i18n.translate(
'xpack.securitySolution.detectionEngine.alerts.actions.addToCase',
{
defaultMessage: 'Add to case',
}
);
export const ACTION_ADD_ENDPOINT_EXCEPTION = i18n.translate(
'xpack.securitySolution.detectionEngine.alerts.actions.addEndpointException',
{

View file

@ -4,7 +4,8 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { useCallback, useMemo } from 'react';
import React, { useCallback, useMemo } from 'react';
import { EuiContextMenuItem } from '@elastic/eui';
import type { TimelineEventsDetailsItem } from '../../../../common';
import { isIsolationSupported } from '../../../../common/endpoint/service/host_isolation/utils';
import { HostStatus } from '../../../../common/endpoint/types';
@ -89,11 +90,14 @@ export const useHostIsolationAction = ({
isolationSupported &&
isHostIsolationPanelOpen === false
? [
{
name: isolateHostTitle,
onClick: isolateHostHandler,
disabled: loadingHostIsolationStatus || agentStatus === HostStatus.UNENROLLED,
},
<EuiContextMenuItem
key="isolate-host-action-item"
data-test-subj="isolate-host-action-item"
disabled={loadingHostIsolationStatus || agentStatus === HostStatus.UNENROLLED}
onClick={isolateHostHandler}
>
{isolateHostTitle}
</EuiContextMenuItem>,
]
: [],
[

View file

@ -1,18 +0,0 @@
/*
* 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 { ACTION_ADD_TO_CASE } from '../alerts_table/translations';
export const addToCaseActionItem = (timelineId: string | null | undefined) =>
['detections-page', 'detections-rules-details-page', 'timeline-1'].includes(timelineId ?? '')
? [
{
name: ACTION_ADD_TO_CASE,
panel: 2,
},
]
: [];

View file

@ -6,7 +6,7 @@
*/
import React, { useState, useCallback, useMemo } from 'react';
import { EuiContextMenu, EuiContextMenuPanel, EuiButton, EuiPopover } from '@elastic/eui';
import { EuiContextMenuPanel, EuiButton, EuiPopover } from '@elastic/eui';
import type { ExceptionListType } from '@kbn/securitysolution-io-ts-list-types';
import { TAKE_ACTION } from '../alerts_table/alerts_utility_bar/translations';
@ -15,13 +15,10 @@ import { TimelineEventsDetailsItem, TimelineNonEcsData } from '../../../../commo
import { useExceptionActions } from '../alerts_table/timeline_actions/use_add_exception_actions';
import { useAlertsActions } from '../alerts_table/timeline_actions/use_alerts_actions';
import { useInvestigateInTimeline } from '../alerts_table/timeline_actions/use_investigate_in_timeline';
import { ACTION_ADD_TO_CASE } from '../alerts_table/translations';
import { useGetUserCasesPermissions, useKibana } from '../../../common/lib/kibana';
import { useInsertTimeline } from '../../../cases/components/use_insert_timeline';
import { addToCaseActionItem } from './helpers';
import { useEventFilterAction } from '../alerts_table/timeline_actions/use_event_filter_action';
import { useHostIsolationAction } from '../host_isolation/use_host_isolation_action';
import { CHANGE_ALERT_STATUS } from './translations';
import { getFieldValue } from '../host_isolation/helpers';
import type { Ecs } from '../../../../common/ecs';
import { Status } from '../../../../common/detection_engine/schemas/common/schemas';
@ -121,7 +118,7 @@ export const TakeActionDropdown = React.memo(
[onAddIsolationStatusClick]
);
const hostIsolationAction = useHostIsolationAction({
const hostIsolationActionItems = useHostIsolationAction({
closePopover: closePopoverHandler,
detailsData,
onAddIsolationStatusClick: handleOnAddIsolationStatusClick,
@ -136,7 +133,7 @@ export const TakeActionDropdown = React.memo(
[onAddExceptionTypeClick]
);
const { exceptionActions } = useExceptionActions({
const { exceptionActionItems } = useExceptionActions({
isEndpointAlert,
onAddExceptionTypeClick: handleOnAddExceptionTypeClick,
});
@ -146,7 +143,7 @@ export const TakeActionDropdown = React.memo(
setIsPopoverOpen(false);
}, [onAddEventFilterClick]);
const eventFilterActions = useEventFilterAction({
const { eventFilterActionItems } = useEventFilterAction({
onAddEventFilterClick: handleOnAddEventFilterClick,
});
@ -154,7 +151,7 @@ export const TakeActionDropdown = React.memo(
closePopoverHandler();
}, [closePopoverHandler]);
const { actionItems } = useAlertsActions({
const { actionItems: statusActionItems } = useAlertsActions({
alertStatus: actionsData.alertStatus,
eventId: actionsData.eventId,
indexName,
@ -163,7 +160,7 @@ export const TakeActionDropdown = React.memo(
closePopover: closePopoverAndFlyout,
});
const { investigateInTimelineAction } = useInvestigateInTimeline({
const { investigateInTimelineActionItems } = useInvestigateInTimeline({
alertIds,
ecsRowData: ecsData,
onInvestigateInTimelineAlertClick: closePopoverHandler,
@ -172,15 +169,9 @@ export const TakeActionDropdown = React.memo(
const alertsActionItems = useMemo(
() =>
!isEvent && actionsData.ruleId
? [
{
name: CHANGE_ALERT_STATUS,
panel: 1,
},
...exceptionActions,
]
: [eventFilterActions],
[eventFilterActions, exceptionActions, isEvent, actionsData.ruleId]
? [...statusActionItems, ...exceptionActionItems]
: eventFilterActionItems,
[eventFilterActionItems, exceptionActionItems, statusActionItems, isEvent, actionsData.ruleId]
);
const addToCaseProps = useMemo(() => {
@ -197,55 +188,35 @@ export const TakeActionDropdown = React.memo(
}
}, [afterCaseSelection, casePermissions, ecsData, insertTimelineHook]);
const panels = useMemo(() => {
if (tGridEnabled) {
return [
{
id: 0,
items: [
...alertsActionItems,
...addToCaseActionItem(timelineId),
...hostIsolationAction,
...investigateInTimelineAction,
],
},
{
id: 1,
title: CHANGE_ALERT_STATUS,
content: <EuiContextMenuPanel size="s" items={actionItems} />,
},
{
id: 2,
title: ACTION_ADD_TO_CASE,
content: [
<>{addToCaseProps && timelinesUi.getAddToExistingCaseButton(addToCaseProps)}</>,
<>{addToCaseProps && timelinesUi.getAddToNewCaseButton(addToCaseProps)}</>,
],
},
];
} else {
return [
{
id: 0,
items: [...alertsActionItems, ...hostIsolationAction, ...investigateInTimelineAction],
},
{
id: 1,
title: CHANGE_ALERT_STATUS,
content: <EuiContextMenuPanel size="s" items={actionItems} />,
},
];
}
}, [
addToCaseProps,
alertsActionItems,
hostIsolationAction,
investigateInTimelineAction,
timelineId,
timelinesUi,
actionItems,
tGridEnabled,
]);
const addToCasesActionItems = useMemo(
() =>
addToCaseProps &&
['detections-page', 'detections-rules-details-page', 'timeline-1'].includes(
timelineId ?? ''
)
? [
timelinesUi.getAddToExistingCaseButton(addToCaseProps),
timelinesUi.getAddToNewCaseButton(addToCaseProps),
]
: [],
[timelinesUi, addToCaseProps, timelineId]
);
const items: React.ReactElement[] = useMemo(
() => [
...(tGridEnabled ? addToCasesActionItems : []),
...alertsActionItems,
...hostIsolationActionItems,
...investigateInTimelineActionItems,
],
[
tGridEnabled,
alertsActionItems,
addToCasesActionItems,
hostIsolationActionItems,
investigateInTimelineActionItems,
]
);
const takeActionButton = useMemo(() => {
return (
@ -255,10 +226,10 @@ export const TakeActionDropdown = React.memo(
);
}, [togglePopoverHandler]);
return panels[0].items?.length && !loadingEventDetails ? (
return items.length && !loadingEventDetails ? (
<>
<EuiPopover
id="hostIsolationTakeActionPanel"
id="AlertTakeActionPanel"
button={takeActionButton}
isOpen={isPopoverOpen}
closePopover={closePopoverHandler}
@ -266,7 +237,7 @@ export const TakeActionDropdown = React.memo(
anchorPosition="downLeft"
repositionOnScroll
>
<EuiContextMenu size="s" initialPanelId={0} panels={panels} />
<EuiContextMenuPanel size="s" items={items} />
</EuiPopover>
</>
) : null;

View file

@ -33,8 +33,9 @@ const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({
<EuiContextMenuItem
aria-label={ariaLabel}
data-test-subj="attach-alert-to-case-button"
size="s"
onClick={addExistingCaseClick}
// needs forced size="s" since it is lazy loaded and the EuiContextMenuPanel can not initialize the size
size="s"
disabled={isDisabled}
>
{i18n.ACTION_ADD_EXISTING_CASE}

View file

@ -34,8 +34,9 @@ const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({
<EuiContextMenuItem
aria-label={ariaLabel}
data-test-subj="attach-alert-to-case-button"
size="s"
onClick={addNewCaseClick}
// needs forced size="s" since it is lazy loaded and the EuiContextMenuPanel can not initialize the size
size="s"
disabled={isDisabled}
>
{i18n.ACTION_ADD_NEW_CASE}

View file

@ -96,7 +96,7 @@ const BulkActionsComponent: React.FC<BulkActionsProps> = ({
<EuiPopover
isOpen={isActionsPopoverOpen}
anchorPosition="upCenter"
panelPaddingSize="s"
panelPaddingSize="none"
button={
<EuiButtonEmpty
aria-label="selectedShowBulkActions"

View file

@ -29,7 +29,7 @@ export const BULK_ACTION_OPEN_SELECTED = i18n.translate(
export const BULK_ACTION_CLOSE_SELECTED = i18n.translate(
'xpack.timelines.timeline.closeSelectedTitle',
{
defaultMessage: 'Close selected',
defaultMessage: 'Mark as closed',
}
);

View file

@ -127,7 +127,6 @@ export const useStatusBulkActionItems = ({
key="open"
data-test-subj="open-alert-status"
onClick={() => onClickUpdate(FILTER_OPEN)}
size="s"
>
{i18n.BULK_ACTION_OPEN_SELECTED}
</EuiContextMenuItem>
@ -139,7 +138,6 @@ export const useStatusBulkActionItems = ({
key="acknowledge"
data-test-subj="acknowledged-alert-status"
onClick={() => onClickUpdate(FILTER_ACKNOWLEDGED)}
size="s"
>
{i18n.BULK_ACTION_ACKNOWLEDGED_SELECTED}
</EuiContextMenuItem>
@ -151,7 +149,6 @@ export const useStatusBulkActionItems = ({
key="close"
data-test-subj="close-alert-status"
onClick={() => onClickUpdate(FILTER_CLOSED)}
size="s"
>
{i18n.BULK_ACTION_CLOSE_SELECTED}
</EuiContextMenuItem>