mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[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:
parent
ec7889a938
commit
64dff78dce
23 changed files with 209 additions and 485 deletions
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
),
|
||||
};
|
||||
|
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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 &&
|
||||
|
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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>,
|
||||
];
|
||||
};
|
||||
|
|
|
@ -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 };
|
||||
};
|
||||
|
|
|
@ -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 };
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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',
|
||||
{
|
||||
|
|
|
@ -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>,
|
||||
]
|
||||
: [],
|
||||
[
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
]
|
||||
: [];
|
|
@ -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;
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -96,7 +96,7 @@ const BulkActionsComponent: React.FC<BulkActionsProps> = ({
|
|||
<EuiPopover
|
||||
isOpen={isActionsPopoverOpen}
|
||||
anchorPosition="upCenter"
|
||||
panelPaddingSize="s"
|
||||
panelPaddingSize="none"
|
||||
button={
|
||||
<EuiButtonEmpty
|
||||
aria-label="selectedShowBulkActions"
|
||||
|
|
|
@ -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',
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue