[Security Solution] Analyze event moved outside of overflow popover (#115478)

This commit is contained in:
Kevin Qualters 2021-10-19 08:34:15 -04:00 committed by GitHub
parent 0e5f2524b4
commit 340271fba2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 90 additions and 19 deletions

View file

@ -34,7 +34,6 @@ import { useExceptionActions } from './use_add_exception_actions';
import { useEventFilterModal } from './use_event_filter_modal';
import { Status } from '../../../../../common/detection_engine/schemas/common/schemas';
import { useKibana } from '../../../../common/lib/kibana';
import { useInvestigateInResolverContextItem } from './investigate_in_resolver';
import { ATTACH_ALERT_TO_CASE_FOR_ROW } from '../../../../timelines/components/timeline/body/translations';
import { useEventFilterAction } from './use_event_filter_action';
import { useAddToCaseActions } from './use_add_to_case_actions';
@ -163,30 +162,19 @@ const AlertContextMenuComponent: React.FC<AlertContextMenuProps & PropsFromRedux
isEndpointAlert: isAlertFromEndpointAlert({ ecsData: ecsRowData }),
onAddExceptionTypeClick: handleOnAddExceptionTypeClick,
});
const investigateInResolverActionItems = useInvestigateInResolverContextItem({
timelineId,
ecsData: ecsRowData,
onClose: afterItemSelection,
});
const { eventFilterActionItems } = useEventFilterAction({
onAddEventFilterClick: handleOnAddEventFilterClick,
});
const items: React.ReactElement[] = useMemo(
() =>
!isEvent && ruleId
? [
...investigateInResolverActionItems,
...addToCaseActionItems,
...statusActionItems,
...exceptionActionItems,
]
: [...investigateInResolverActionItems, ...addToCaseActionItems, ...eventFilterActionItems],
? [...addToCaseActionItems, ...statusActionItems, ...exceptionActionItems]
: [...addToCaseActionItems, ...eventFilterActionItems],
[
statusActionItems,
addToCaseActionItems,
eventFilterActionItems,
exceptionActionItems,
investigateInResolverActionItems,
isEvent,
ruleId,
]

View file

@ -185,5 +185,34 @@ describe('Actions', () => {
wrapper.find('[data-test-subj="timeline-context-menu-button"]').first().prop('isDisabled')
).toBe(false);
});
test('it shows the analyze event button when the event is from an endpoint', () => {
const ecsData = {
...mockTimelineData[0].ecs,
event: { kind: ['alert'] },
agent: { type: ['endpoint'] },
process: { entity_id: ['1'] },
};
const wrapper = mount(
<TestProviders>
<Actions {...defaultProps} ecsData={ecsData} />
</TestProviders>
);
expect(wrapper.find('[data-test-subj="view-in-analyzer"]').exists()).toBe(true);
});
test('it does not render the analyze event button when the event is from an unsupported source', () => {
const ecsData = {
...mockTimelineData[0].ecs,
event: { kind: ['alert'] },
agent: { type: ['notendpoint'] },
};
const wrapper = mount(
<TestProviders>
<Actions {...defaultProps} ecsData={ecsData} />
</TestProviders>
);
expect(wrapper.find('[data-test-subj="view-in-analyzer"]').exists()).toBe(false);
});
});
});

View file

@ -21,9 +21,23 @@ import { EventsTdContent } from '../../styles';
import * as i18n from '../translations';
import { DEFAULT_ICON_BUTTON_WIDTH } from '../../helpers';
import { useShallowEqualSelector } from '../../../../../common/hooks/use_selector';
import { TimelineId, ActionProps, OnPinEvent } from '../../../../../../common/types/timeline';
import {
setActiveTabTimeline,
updateTimelineGraphEventId,
} from '../../../../store/timeline/actions';
import {
useGlobalFullScreen,
useTimelineFullScreen,
} from '../../../../../common/containers/use_full_screen';
import {
TimelineId,
ActionProps,
OnPinEvent,
TimelineTabs,
} from '../../../../../../common/types/timeline';
import { timelineActions, timelineSelectors } from '../../../../store/timeline';
import { timelineDefaults } from '../../../../store/timeline/defaults';
import { isInvestigateInResolverActionEnabled } from '../../../../../detections/components/alerts_table/timeline_actions/investigate_in_resolver';
const ActionsContainer = styled.div`
align-items: center;
@ -100,6 +114,24 @@ const ActionsComponent: React.FC<ActionProps> = ({
[eventType, ecsData.event?.kind, ecsData.agent?.type]
);
const isDisabled = useMemo(() => !isInvestigateInResolverActionEnabled(ecsData), [ecsData]);
const { setGlobalFullScreen } = useGlobalFullScreen();
const { setTimelineFullScreen } = useTimelineFullScreen();
const handleClick = useCallback(() => {
const dataGridIsFullScreen = document.querySelector('.euiDataGrid--fullScreen');
dispatch(updateTimelineGraphEventId({ id: timelineId, graphEventId: ecsData._id }));
if (timelineId === TimelineId.active) {
if (dataGridIsFullScreen) {
setTimelineFullScreen(true);
}
dispatch(setActiveTabTimeline({ id: timelineId, activeTab: TimelineTabs.graph }));
} else {
if (dataGridIsFullScreen) {
setGlobalFullScreen(true);
}
}
}, [dispatch, ecsData._id, timelineId, setGlobalFullScreen, setTimelineFullScreen]);
return (
<ActionsContainer>
{showCheckboxes && !tGridEnabled && (
@ -171,6 +203,26 @@ const ActionsComponent: React.FC<ActionProps> = ({
refetch={refetch ?? noop}
onRuleChange={onRuleChange}
/>
{isDisabled === false ? (
<div>
<EventsTdContent textAlign="center" width={36}>
<EuiToolTip
data-test-subj="view-in-analyzer-tool-tip"
content={i18n.ACTION_INVESTIGATE_IN_RESOLVER}
>
<EuiButtonIcon
aria-label={i18n.ACTION_INVESTIGATE_IN_RESOLVER_FOR_ROW({
ariaRowindex,
columnValues,
})}
data-test-subj="view-in-analyzer"
iconType="analyzeEvent"
onClick={handleClick}
/>
</EuiToolTip>
</EventsTdContent>
</div>
) : null}
</>
</ActionsContainer>
);

View file

@ -521,7 +521,7 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = `
"compare": null,
"type": [Function],
},
"width": 108,
"width": 140,
},
]
}

View file

@ -9,7 +9,7 @@ import { ControlColumnProps } from '../../../../../../common/types/timeline';
import { Actions } from '../actions';
import { HeaderActions } from '../actions/header_actions';
const DEFAULT_CONTROL_COLUMN_WIDTH = 108;
const DEFAULT_CONTROL_COLUMN_WIDTH = 140;
export const defaultControlColumn: ControlColumnProps = {
id: 'default-timeline-control-column',

View file

@ -17,8 +17,10 @@ import {
import { OnPinEvent, OnUnPinEvent } from '../events';
import * as i18n from './translations';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const omitTypenameAndEmpty = (k: string, v: any): any | undefined =>
export const omitTypenameAndEmpty = (
k: string,
v: string | object | Array<string | object>
): string | object | Array<string | object> | undefined =>
k !== '__typename' && v != null ? v : undefined;
export const stringifyEvent = (ecs: Ecs): string => JSON.stringify(ecs, omitTypenameAndEmpty, 2);