[Security Solution] Bugfix for disable state of External Alert context menu (#109914) (#110129)

Co-authored-by: Steph Milovic <stephanie.milovic@elastic.co>
This commit is contained in:
Kibana Machine 2021-08-25 17:16:17 -04:00 committed by GitHub
parent 5280e3523d
commit d39268d52d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 146 additions and 109 deletions

View file

@ -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);
});
});
});

View file

@ -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}
/>

View file

@ -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';

View file

@ -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;