mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution] Bugfix for disable state of External Alert context menu (#109914)
This commit is contained in:
parent
a299604c58
commit
27af6ef068
4 changed files with 146 additions and 109 deletions
|
@ -10,16 +10,24 @@ import React from 'react';
|
|||
|
||||
import { TestProviders, mockTimelineModel, mockTimelineData } from '../../../../../common/mock';
|
||||
import { Actions } from '.';
|
||||
import { useShallowEqualSelector } from '../../../../../common/hooks/use_selector';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features';
|
||||
import { mockTimelines } from '../../../../../common/mock/mock_timelines_plugin';
|
||||
|
||||
jest.mock('../../../../../common/hooks/use_experimental_features');
|
||||
const useIsExperimentalFeatureEnabledMock = useIsExperimentalFeatureEnabled as jest.Mock;
|
||||
|
||||
jest.mock('../../../../../common/hooks/use_selector', () => ({
|
||||
useShallowEqualSelector: jest.fn(),
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features';
|
||||
jest.mock('../../../../../common/hooks/use_experimental_features', () => ({
|
||||
useIsExperimentalFeatureEnabled: jest.fn().mockReturnValue(false),
|
||||
}));
|
||||
jest.mock('../../../../../common/hooks/use_selector', () => ({
|
||||
useShallowEqualSelector: jest.fn().mockReturnValue(mockTimelineModel),
|
||||
}));
|
||||
jest.mock(
|
||||
'../../../../../detections/components/alerts_table/timeline_actions/use_investigate_in_timeline',
|
||||
() => ({
|
||||
useInvestigateInTimeline: jest.fn().mockReturnValue({
|
||||
investigateInTimelineActionItems: [],
|
||||
investigateInTimelineAlertClick: jest.fn(),
|
||||
showInvestigateInTimelineAction: false,
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
jest.mock('@kbn/alerts', () => ({
|
||||
useGetUserAlertsPermissions: () => ({
|
||||
|
@ -56,38 +64,35 @@ jest.mock('../../../../../common/lib/kibana', () => ({
|
|||
useGetUserCasesPermissions: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('Actions', () => {
|
||||
beforeEach(() => {
|
||||
(useShallowEqualSelector as jest.Mock).mockReturnValue(mockTimelineModel);
|
||||
useIsExperimentalFeatureEnabledMock.mockReturnValue(false);
|
||||
});
|
||||
const defaultProps = {
|
||||
ariaRowindex: 2,
|
||||
checked: false,
|
||||
columnId: '',
|
||||
columnValues: 'abc def',
|
||||
data: mockTimelineData[0].data,
|
||||
ecsData: mockTimelineData[0].ecs,
|
||||
eventId: 'abc',
|
||||
eventIdToNoteIds: {},
|
||||
index: 2,
|
||||
isEventPinned: false,
|
||||
loadingEventIds: [],
|
||||
onEventDetailsPanelOpened: () => {},
|
||||
onRowSelected: () => {},
|
||||
refetch: () => {},
|
||||
rowIndex: 10,
|
||||
setEventsDeleted: () => {},
|
||||
setEventsLoading: () => {},
|
||||
showCheckboxes: true,
|
||||
showNotes: false,
|
||||
timelineId: 'test',
|
||||
toggleShowNotes: () => {},
|
||||
};
|
||||
|
||||
describe('Actions', () => {
|
||||
test('it renders a checkbox for selecting the event when `showCheckboxes` is `true`', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<Actions
|
||||
ariaRowindex={2}
|
||||
columnId={''}
|
||||
index={2}
|
||||
checked={false}
|
||||
columnValues={'abc def'}
|
||||
data={mockTimelineData[0].data}
|
||||
ecsData={mockTimelineData[0].ecs}
|
||||
eventIdToNoteIds={{}}
|
||||
eventId="abc"
|
||||
loadingEventIds={[]}
|
||||
onEventDetailsPanelOpened={jest.fn()}
|
||||
onRowSelected={jest.fn()}
|
||||
showNotes={false}
|
||||
isEventPinned={false}
|
||||
rowIndex={10}
|
||||
toggleShowNotes={jest.fn()}
|
||||
timelineId={'test'}
|
||||
refetch={jest.fn()}
|
||||
showCheckboxes={true}
|
||||
setEventsLoading={jest.fn()}
|
||||
setEventsDeleted={jest.fn()}
|
||||
/>
|
||||
<Actions {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
|
@ -97,29 +102,7 @@ describe('Actions', () => {
|
|||
test('it does NOT render a checkbox for selecting the event when `showCheckboxes` is `false`', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<Actions
|
||||
ariaRowindex={2}
|
||||
checked={false}
|
||||
columnValues={'abc def'}
|
||||
data={mockTimelineData[0].data}
|
||||
ecsData={mockTimelineData[0].ecs}
|
||||
eventIdToNoteIds={{}}
|
||||
showNotes={false}
|
||||
isEventPinned={false}
|
||||
rowIndex={10}
|
||||
toggleShowNotes={jest.fn()}
|
||||
timelineId={'test'}
|
||||
refetch={jest.fn()}
|
||||
columnId={''}
|
||||
index={2}
|
||||
eventId="abc"
|
||||
loadingEventIds={[]}
|
||||
onEventDetailsPanelOpened={jest.fn()}
|
||||
onRowSelected={jest.fn()}
|
||||
showCheckboxes={false}
|
||||
setEventsLoading={jest.fn()}
|
||||
setEventsDeleted={jest.fn()}
|
||||
/>
|
||||
<Actions {...defaultProps} showCheckboxes={false} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
|
@ -127,36 +110,88 @@ describe('Actions', () => {
|
|||
});
|
||||
|
||||
test('it does NOT render a checkbox for selecting the event when `tGridEnabled` is `true`', () => {
|
||||
useIsExperimentalFeatureEnabledMock.mockReturnValue(true);
|
||||
|
||||
(useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true);
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<Actions
|
||||
ariaRowindex={2}
|
||||
checked={false}
|
||||
columnValues={'abc def'}
|
||||
data={mockTimelineData[0].data}
|
||||
ecsData={mockTimelineData[0].ecs}
|
||||
eventIdToNoteIds={{}}
|
||||
showNotes={false}
|
||||
isEventPinned={false}
|
||||
rowIndex={10}
|
||||
toggleShowNotes={jest.fn()}
|
||||
timelineId={'test'}
|
||||
refetch={jest.fn()}
|
||||
columnId={''}
|
||||
index={2}
|
||||
eventId="abc"
|
||||
loadingEventIds={[]}
|
||||
onEventDetailsPanelOpened={jest.fn()}
|
||||
onRowSelected={jest.fn()}
|
||||
showCheckboxes={true}
|
||||
setEventsLoading={jest.fn()}
|
||||
setEventsDeleted={jest.fn()}
|
||||
/>
|
||||
<Actions {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="select-event"]').exists()).toBe(false);
|
||||
});
|
||||
describe('Alert context menu enabled?', () => {
|
||||
test('it disables for eventType=raw', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<Actions {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper.find('[data-test-subj="timeline-context-menu-button"]').first().prop('isDisabled')
|
||||
).toBe(true);
|
||||
});
|
||||
test('it enables for eventType=signal', () => {
|
||||
const ecsData = {
|
||||
...mockTimelineData[0].ecs,
|
||||
signal: { rule: { id: ['123'] } },
|
||||
};
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<Actions {...defaultProps} ecsData={ecsData} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper.find('[data-test-subj="timeline-context-menu-button"]').first().prop('isDisabled')
|
||||
).toBe(false);
|
||||
});
|
||||
test('it disables for event.kind: undefined and agent.type: endpoint', () => {
|
||||
const ecsData = {
|
||||
...mockTimelineData[0].ecs,
|
||||
agent: { type: ['endpoint'] },
|
||||
};
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<Actions {...defaultProps} ecsData={ecsData} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper.find('[data-test-subj="timeline-context-menu-button"]').first().prop('isDisabled')
|
||||
).toBe(true);
|
||||
});
|
||||
test('it enables for event.kind: event and agent.type: endpoint', () => {
|
||||
const ecsData = {
|
||||
...mockTimelineData[0].ecs,
|
||||
event: { kind: ['event'] },
|
||||
agent: { type: ['endpoint'] },
|
||||
};
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<Actions {...defaultProps} ecsData={ecsData} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper.find('[data-test-subj="timeline-context-menu-button"]').first().prop('isDisabled')
|
||||
).toBe(false);
|
||||
});
|
||||
test('it enables for event.kind: alert and agent.type: endpoint', () => {
|
||||
const ecsData = {
|
||||
...mockTimelineData[0].ecs,
|
||||
event: { kind: ['alert'] },
|
||||
agent: { type: ['endpoint'] },
|
||||
};
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<Actions {...defaultProps} ecsData={ecsData} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper.find('[data-test-subj="timeline-context-menu-button"]').first().prop('isDisabled')
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -15,8 +15,8 @@ import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use
|
|||
import { eventHasNotes, getEventType, getPinOnClick } from '../helpers';
|
||||
import { AlertContextMenu } from '../../../../../detections/components/alerts_table/timeline_actions/alert_context_menu';
|
||||
import { InvestigateInTimelineAction } from '../../../../../detections/components/alerts_table/timeline_actions/investigate_in_timeline_action';
|
||||
import { AddEventNoteAction } from '../actions/add_note_icon_item';
|
||||
import { PinEventAction } from '../actions/pin_event_action';
|
||||
import { AddEventNoteAction } from './add_note_icon_item';
|
||||
import { PinEventAction } from './pin_event_action';
|
||||
import { EventsTdContent } from '../../styles';
|
||||
import * as i18n from '../translations';
|
||||
import { DEFAULT_ICON_BUTTON_WIDTH } from '../../helpers';
|
||||
|
@ -32,21 +32,20 @@ const ActionsContainer = styled.div`
|
|||
|
||||
const ActionsComponent: React.FC<ActionProps> = ({
|
||||
ariaRowindex,
|
||||
width,
|
||||
checked,
|
||||
columnValues,
|
||||
eventId,
|
||||
data,
|
||||
ecsData,
|
||||
eventId,
|
||||
eventIdToNoteIds,
|
||||
isEventPinned = false,
|
||||
isEventViewer = false,
|
||||
loadingEventIds,
|
||||
onEventDetailsPanelOpened,
|
||||
onRowSelected,
|
||||
onRuleChange,
|
||||
refetch,
|
||||
showCheckboxes,
|
||||
onRuleChange,
|
||||
showNotes,
|
||||
timelineId,
|
||||
toggleShowNotes,
|
||||
|
@ -91,9 +90,14 @@ const ActionsComponent: React.FC<ActionProps> = ({
|
|||
);
|
||||
const eventType = getEventType(ecsData);
|
||||
|
||||
const isEventContextMenuEnabledForEndpoint = useMemo(
|
||||
() => ecsData.event?.kind?.includes('event') && ecsData.agent?.type?.includes('endpoint'),
|
||||
[ecsData.event?.kind, ecsData.agent?.type]
|
||||
const isContextMenuDisabled = useMemo(
|
||||
() =>
|
||||
eventType !== 'signal' &&
|
||||
!(
|
||||
(ecsData.event?.kind?.includes('event') || ecsData.event?.kind?.includes('alert')) &&
|
||||
ecsData.agent?.type?.includes('endpoint')
|
||||
),
|
||||
[eventType, ecsData.event?.kind, ecsData.agent?.type]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -163,7 +167,7 @@ const ActionsComponent: React.FC<ActionProps> = ({
|
|||
key="alert-context-menu"
|
||||
ecsRowData={ecsData}
|
||||
timelineId={timelineId}
|
||||
disabled={eventType !== 'signal' && !isEventContextMenuEnabledForEndpoint}
|
||||
disabled={isContextMenuDisabled}
|
||||
refetch={refetch ?? noop}
|
||||
onRuleChange={onRuleChange}
|
||||
/>
|
||||
|
|
|
@ -125,6 +125,4 @@ export const getEventType = (event: Ecs): Omit<TimelineEventsType, 'all'> => {
|
|||
|
||||
export const ROW_RENDERER_CLASS_NAME = 'row-renderer';
|
||||
|
||||
export const NOTES_CONTAINER_CLASS_NAME = 'notes-container';
|
||||
|
||||
export const NOTE_CONTENT_CLASS_NAME = 'note-content';
|
||||
|
|
|
@ -14,33 +14,33 @@ import { TimelineNonEcsData } from '../../../search_strategy';
|
|||
import { Ecs } from '../../../ecs';
|
||||
|
||||
export interface ActionProps {
|
||||
ariaRowindex: number;
|
||||
action?: RowCellRender;
|
||||
width?: number;
|
||||
ariaRowindex: number;
|
||||
checked: boolean;
|
||||
columnId: string;
|
||||
columnValues: string;
|
||||
checked: boolean;
|
||||
disabled?: boolean;
|
||||
onRowSelected: OnRowSelected;
|
||||
eventId: string;
|
||||
loadingEventIds: Readonly<string[]>;
|
||||
onEventDetailsPanelOpened: () => void;
|
||||
showCheckboxes: boolean;
|
||||
data: TimelineNonEcsData[];
|
||||
disabled?: boolean;
|
||||
ecsData: Ecs;
|
||||
index: number;
|
||||
eventId: string;
|
||||
eventIdToNoteIds?: Readonly<Record<string, string[]>>;
|
||||
index: number;
|
||||
isEventPinned?: boolean;
|
||||
isEventViewer?: boolean;
|
||||
rowIndex: number;
|
||||
setEventsLoading: SetEventsLoading;
|
||||
setEventsDeleted: SetEventsDeleted;
|
||||
refetch?: () => void;
|
||||
loadingEventIds: Readonly<string[]>;
|
||||
onEventDetailsPanelOpened: () => void;
|
||||
onRowSelected: OnRowSelected;
|
||||
onRuleChange?: () => void;
|
||||
refetch?: () => void;
|
||||
rowIndex: number;
|
||||
setEventsDeleted: SetEventsDeleted;
|
||||
setEventsLoading: SetEventsLoading;
|
||||
showCheckboxes: boolean;
|
||||
showNotes?: boolean;
|
||||
tabType?: TimelineTabs;
|
||||
timelineId: string;
|
||||
toggleShowNotes?: () => void;
|
||||
width?: number;
|
||||
}
|
||||
|
||||
export type SetEventsLoading = (params: { eventIds: string[]; isLoading: boolean }) => void;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue