Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security] Investigate in Resolver Timeline Integration #70111

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import React from 'react';
import ApolloClient from 'apollo-client';
import { Dispatch } from 'redux';

import { EuiText } from '@elastic/eui';
import { Status } from '../../../../common/detection_engine/schemas/common/schemas';
Expand All @@ -17,10 +18,12 @@ import {
TimelineRowActionOnClick,
} from '../../../timelines/components/timeline/body/actions';
import { defaultColumnHeaderType } from '../../../timelines/components/timeline/body/column_headers/default_headers';
import { getInvestigateInResolverAction } from '../../../timelines/components/timeline/body/helpers';
import {
DEFAULT_COLUMN_MIN_WIDTH,
DEFAULT_DATE_COLUMN_MIN_WIDTH,
} from '../../../timelines/components/timeline/body/constants';
import { DEFAULT_ICON_BUTTON_WIDTH } from '../../../timelines/components/timeline/helpers';
import { ColumnHeaderOptions, SubsetTimelineModel } from '../../../timelines/store/timeline/model';
import { timelineDefaults } from '../../../timelines/store/timeline/defaults';

Expand Down Expand Up @@ -174,23 +177,27 @@ export const getAlertActions = ({
apolloClient,
canUserCRUD,
createTimeline,
dispatch,
hasIndexWrite,
onAlertStatusUpdateFailure,
onAlertStatusUpdateSuccess,
setEventsDeleted,
setEventsLoading,
status,
timelineId,
updateTimelineIsLoading,
}: {
apolloClient?: ApolloClient<{}>;
canUserCRUD: boolean;
createTimeline: CreateTimeline;
dispatch: Dispatch;
hasIndexWrite: boolean;
onAlertStatusUpdateFailure: (status: Status, error: Error) => void;
onAlertStatusUpdateSuccess: (count: number, status: Status) => void;
setEventsDeleted: ({ eventIds, isDeleted }: SetEventsDeletedProps) => void;
setEventsLoading: ({ eventIds, isLoading }: SetEventsLoadingProps) => void;
status: Status;
timelineId: string;
updateTimelineIsLoading: UpdateTimelineLoading;
}): TimelineRowAction[] => {
const openAlertActionComponent: TimelineRowAction = {
Expand All @@ -199,7 +206,7 @@ export const getAlertActions = ({
dataTestSubj: 'open-alert-status',
displayType: 'contextMenu',
id: FILTER_OPEN,
isActionDisabled: !canUserCRUD || !hasIndexWrite,
isActionDisabled: () => !canUserCRUD || !hasIndexWrite,
onClick: ({ eventId }: TimelineRowActionOnClick) =>
updateAlertStatusAction({
alertIds: [eventId],
Expand All @@ -210,7 +217,7 @@ export const getAlertActions = ({
status,
selectedStatus: FILTER_OPEN,
}),
width: 26,
width: DEFAULT_ICON_BUTTON_WIDTH,
};

const closeAlertActionComponent: TimelineRowAction = {
Expand All @@ -219,7 +226,7 @@ export const getAlertActions = ({
dataTestSubj: 'close-alert-status',
displayType: 'contextMenu',
id: FILTER_CLOSED,
isActionDisabled: !canUserCRUD || !hasIndexWrite,
isActionDisabled: () => !canUserCRUD || !hasIndexWrite,
onClick: ({ eventId }: TimelineRowActionOnClick) =>
updateAlertStatusAction({
alertIds: [eventId],
Expand All @@ -230,7 +237,7 @@ export const getAlertActions = ({
status,
selectedStatus: FILTER_CLOSED,
}),
width: 26,
width: DEFAULT_ICON_BUTTON_WIDTH,
};

const inProgressAlertActionComponent: TimelineRowAction = {
Expand All @@ -239,7 +246,7 @@ export const getAlertActions = ({
dataTestSubj: 'in-progress-alert-status',
displayType: 'contextMenu',
id: FILTER_IN_PROGRESS,
isActionDisabled: !canUserCRUD || !hasIndexWrite,
isActionDisabled: () => !canUserCRUD || !hasIndexWrite,
onClick: ({ eventId }: TimelineRowActionOnClick) =>
updateAlertStatusAction({
alertIds: [eventId],
Expand All @@ -250,10 +257,13 @@ export const getAlertActions = ({
status,
selectedStatus: FILTER_IN_PROGRESS,
}),
width: 26,
width: DEFAULT_ICON_BUTTON_WIDTH,
};

return [
{
...getInvestigateInResolverAction({ dispatch, timelineId }),
},
{
ariaLabel: 'Send alert to timeline',
content: i18n.ACTION_INVESTIGATE_IN_TIMELINE,
Expand All @@ -268,7 +278,7 @@ export const getAlertActions = ({
ecsData,
updateTimelineIsLoading,
}),
width: 26,
width: DEFAULT_ICON_BUTTON_WIDTH,
},
// Context menu items
...(FILTER_OPEN !== status ? [openAlertActionComponent] : []),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,37 +7,40 @@
import React from 'react';
import { shallow } from 'enzyme';

import { TestProviders } from '../../../common/mock/test_providers';
import { TimelineId } from '../../../../common/types/timeline';
import { AlertsTableComponent } from './index';

describe('AlertsTableComponent', () => {
it('renders correctly', () => {
const wrapper = shallow(
<AlertsTableComponent
timelineId={TimelineId.test}
canUserCRUD
hasIndexWrite
from={0}
loading
signalsIndex="index"
to={1}
globalQuery={{
query: 'query',
language: 'language',
}}
globalFilters={[]}
deletedEventIds={[]}
loadingEventIds={[]}
selectedEventIds={{}}
isSelectAllChecked={false}
clearSelected={jest.fn()}
setEventsLoading={jest.fn()}
clearEventsLoading={jest.fn()}
setEventsDeleted={jest.fn()}
clearEventsDeleted={jest.fn()}
updateTimelineIsLoading={jest.fn()}
updateTimeline={jest.fn()}
/>
<TestProviders>
<AlertsTableComponent
timelineId={TimelineId.test}
canUserCRUD
hasIndexWrite
from={0}
loading
signalsIndex="index"
to={1}
globalQuery={{
query: 'query',
language: 'language',
}}
globalFilters={[]}
deletedEventIds={[]}
loadingEventIds={[]}
selectedEventIds={{}}
isSelectAllChecked={false}
clearSelected={jest.fn()}
setEventsLoading={jest.fn()}
clearEventsLoading={jest.fn()}
setEventsDeleted={jest.fn()}
clearEventsDeleted={jest.fn()}
updateTimelineIsLoading={jest.fn()}
updateTimeline={jest.fn()}
/>
</TestProviders>
);

expect(wrapper.find('[title="Alerts"]')).toBeTruthy();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import { EuiPanel, EuiLoadingContent } from '@elastic/eui';
import { isEmpty } from 'lodash/fp';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { connect, ConnectedProps } from 'react-redux';
import { connect, ConnectedProps, useDispatch } from 'react-redux';
import { Dispatch } from 'redux';

import { Status } from '../../../../common/detection_engine/schemas/common/schemas';
Expand Down Expand Up @@ -84,6 +84,7 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
updateTimeline,
updateTimelineIsLoading,
}) => {
const dispatch = useDispatch();
const [selectAll, setSelectAll] = useState(false);
const apolloClient = useApolloClient();

Expand Down Expand Up @@ -292,11 +293,13 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
getAlertActions({
apolloClient,
canUserCRUD,
dispatch,
hasIndexWrite,
createTimeline: createTimelineCallback,
setEventsLoading: setEventsLoadingCallback,
setEventsDeleted: setEventsDeletedCallback,
status: filterGroup,
timelineId,
updateTimelineIsLoading,
onAlertStatusUpdateSuccess,
onAlertStatusUpdateFailure,
Expand All @@ -305,10 +308,12 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
apolloClient,
canUserCRUD,
createTimelineCallback,
dispatch,
hasIndexWrite,
filterGroup,
setEventsLoadingCallback,
setEventsDeletedCallback,
timelineId,
updateTimelineIsLoading,
onAlertStatusUpdateSuccess,
onAlertStatusUpdateFailure,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@
*/

import React, { useEffect, useMemo } from 'react';
import { useDispatch } from 'react-redux';

import { Filter } from '../../../../../../../src/plugins/data/public';
import { TimelineIdLiteral } from '../../../../common/types/timeline';
import { StatefulEventsViewer } from '../events_viewer';
import { alertsDefaultModel } from './default_headers';
import { useManageTimeline } from '../../../timelines/components/manage_timeline';
import { getInvestigateInResolverAction } from '../../../timelines/components/timeline/body/helpers';
import * as i18n from './translations';

export interface OwnProps {
end: number;
id: string;
Expand Down Expand Up @@ -64,8 +67,9 @@ const AlertsTableComponent: React.FC<Props> = ({
startDate,
pageFilters = [],
}) => {
const dispatch = useDispatch();
const alertsFilter = useMemo(() => [...defaultAlertsFilters, ...pageFilters], [pageFilters]);
const { initializeTimeline } = useManageTimeline();
const { initializeTimeline, setTimelineRowActions } = useManageTimeline();

useEffect(() => {
initializeTimeline({
Expand All @@ -75,6 +79,10 @@ const AlertsTableComponent: React.FC<Props> = ({
title: i18n.ALERTS_TABLE_TITLE,
unit: i18n.UNIT,
});
setTimelineRowActions({
id: timelineId,
timelineRowActions: [getInvestigateInResolverAction({ dispatch, timelineId })],
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import { EuiPanel } from '@elastic/eui';
import { getOr, isEmpty, union } from 'lodash/fp';
import React, { useEffect, useMemo, useState } from 'react';
import { useDispatch } from 'react-redux';
import styled from 'styled-components';
import deepEqual from 'fast-deep-equal';

Expand Down Expand Up @@ -34,6 +35,7 @@ import {
} from '../../../../../../../src/plugins/data/public';
import { inputsModel } from '../../store';
import { useManageTimeline } from '../../../timelines/components/manage_timeline';
import { getInvestigateInResolverAction } from '../../../timelines/components/timeline/body/helpers';

const DEFAULT_EVENTS_VIEWER_HEIGHT = 500;

Expand Down Expand Up @@ -91,6 +93,7 @@ const EventsViewerComponent: React.FC<Props> = ({
toggleColumn,
utilityBar,
}) => {
const dispatch = useDispatch();
const columnsHeader = isEmpty(columns) ? defaultHeaders : columns;
const kibana = useKibana();
const { filterManager } = useKibana().services.data.query;
Expand All @@ -100,7 +103,16 @@ const EventsViewerComponent: React.FC<Props> = ({
getManageTimelineById,
setIsTimelineLoading,
setTimelineFilterManager,
setTimelineRowActions,
} = useManageTimeline();

useEffect(() => {
setTimelineRowActions({
id,
timelineRowActions: [getInvestigateInResolverAction({ dispatch, timelineId: id })],
});
}, [setTimelineRowActions, id, dispatch]);

useEffect(() => {
setIsTimelineLoading({ id, isLoading: isQueryLoading });
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand Down Expand Up @@ -179,9 +191,7 @@ const EventsViewerComponent: React.FC<Props> = ({
<HeaderSection id={id} subtitle={utilityBar ? undefined : subtitle} title={title}>
{headerFilterGroup}
</HeaderSection>

{utilityBar?.(refetch, totalCountMinusDeleted)}

<EventsContainerLoading data-test-subj={`events-container-loading-${loading}`}>
<TimelineRefetch
id={id}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ const getMockObject = (
timeline: {
id: '',
isOpen: false,
graphEventId: '',
},
timerange: {
global: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ describe('SIEM Navigation', () => {
[CONSTANTS.timeline]: {
id: '',
isOpen: false,
graphEventId: '',
},
},
};
Expand Down Expand Up @@ -160,6 +161,7 @@ describe('SIEM Navigation', () => {
timeline: {
id: '',
isOpen: false,
graphEventId: '',
},
timerange: {
global: {
Expand Down Expand Up @@ -266,7 +268,7 @@ describe('SIEM Navigation', () => {
search: '',
state: undefined,
tabName: 'authentications',
timeline: { id: '', isOpen: false },
timeline: { id: '', isOpen: false, graphEventId: '' },
timerange: {
global: {
linkTo: ['timeline'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ describe('Tab Navigation', () => {
[CONSTANTS.timeline]: {
id: '',
isOpen: false,
graphEventId: '',
},
};
test('it mounts with correct tab highlighted', () => {
Expand Down Expand Up @@ -128,6 +129,7 @@ describe('Tab Navigation', () => {
[CONSTANTS.timeline]: {
id: '',
isOpen: false,
graphEventId: '',
},
};
test('it mounts with correct tab highlighted', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,9 @@ export const makeMapStateToProps = () => {
? {
id: flyoutTimeline.savedObjectId != null ? flyoutTimeline.savedObjectId : '',
isOpen: flyoutTimeline.show,
graphEventId: flyoutTimeline.graphEventId ?? '',
}
: { id: '', isOpen: false };
: { id: '', isOpen: false, graphEventId: '' };

let searchAttr: {
[CONSTANTS.appQuery]?: Query;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export const dispatchSetInitialStateFromUrl = (
queryTimelineById({
apolloClient,
duplicate: false,
graphEventId: timeline.graphEventId,
timelineId: timeline.id,
openTimeline: timeline.isOpen,
updateIsLoading: updateTimelineIsLoading,
Expand Down
Loading