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 Solution] [Timeline] Timeline manager tweaks #69988

Merged
merged 11 commits into from
Jul 7, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
SetEventsLoadingProps,
UpdateTimelineLoading,
} from './types';
import { Ecs } from '../../../graphql/types';

export const buildAlertStatusFilter = (status: Status): Filter[] => [
{
Expand Down Expand Up @@ -173,11 +174,28 @@ export const requiredFieldsForActions = [
'signal.rule.id',
];

interface AlertActionArgs {
apolloClient?: ApolloClient<{}>;
canUserCRUD: boolean;
createTimeline: CreateTimeline;
dispatch: Dispatch;
ecsRowData: Ecs;
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;
}

export const getAlertActions = ({
apolloClient,
canUserCRUD,
createTimeline,
dispatch,
ecsRowData,
hasIndexWrite,
onAlertStatusUpdateFailure,
onAlertStatusUpdateSuccess,
Expand All @@ -186,20 +204,7 @@ export const getAlertActions = ({
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[] => {
}: AlertActionArgs): TimelineRowAction[] => {
const openAlertActionComponent: TimelineRowAction = {
ariaLabel: 'Open alert',
content: <EuiText size="m">{i18n.ACTION_OPEN_ALERT}</EuiText>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
import React from 'react';
import { shallow } from 'enzyme';

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

describe('AlertsTableComponent', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import {
displaySuccessToast,
displayErrorToast,
} from '../../../common/components/toasters';
import { Ecs } from '../../../graphql/types';
import { getInvestigateInResolverAction } from '../../../timelines/components/timeline/body/helpers';

interface OwnProps {
Expand Down Expand Up @@ -290,20 +291,21 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({

// Send to Timeline / Update Alert Status Actions for each table row
const additionalActions = useMemo(
() =>
() => (ecsRowData: Ecs) =>
getAlertActions({
apolloClient,
canUserCRUD,
createTimeline: createTimelineCallback,
ecsRowData,
dispatch,
hasIndexWrite,
createTimeline: createTimelineCallback,
setEventsLoading: setEventsLoadingCallback,
onAlertStatusUpdateFailure,
onAlertStatusUpdateSuccess,
setEventsDeleted: setEventsDeletedCallback,
setEventsLoading: setEventsLoadingCallback,
status: filterGroup,
timelineId,
updateTimelineIsLoading,
onAlertStatusUpdateSuccess,
onAlertStatusUpdateFailure,
}),
[
apolloClient,
Expand All @@ -328,17 +330,19 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
return [...defaultFilters, ...buildAlertStatusFilter(filterGroup)];
}
}, [defaultFilters, filterGroup]);
const { filterManager } = useKibana().services.data.query;
const { initializeTimeline, setTimelineRowActions } = useManageTimeline();

useEffect(() => {
initializeTimeline({
defaultModel: alertsDefaultModel,
documentType: i18n.ALERTS_DOCUMENT_TYPE,
filterManager,
footerText: i18n.TOTAL_COUNT_OF_ALERTS,
id: timelineId,
loadingText: i18n.LOADING_ALERTS,
selectAll: canUserCRUD ? selectAll : false,
timelineRowActions: [getInvestigateInResolverAction({ dispatch, timelineId })],
timelineRowActions: () => [getInvestigateInResolverAction({ dispatch, timelineId })],
title: i18n.ALERTS_TABLE_TITLE,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ 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';
import { useKibana } from '../../lib/kibana';

export interface OwnProps {
end: number;
Expand Down Expand Up @@ -69,15 +70,17 @@ const AlertsTableComponent: React.FC<Props> = ({
}) => {
const dispatch = useDispatch();
const alertsFilter = useMemo(() => [...defaultAlertsFilters, ...pageFilters], [pageFilters]);
const { filterManager } = useKibana().services.data.query;
const { initializeTimeline } = useManageTimeline();

useEffect(() => {
initializeTimeline({
id: timelineId,
documentType: i18n.ALERTS_DOCUMENT_TYPE,
filterManager,
defaultModel: alertsDefaultModel,
footerText: i18n.TOTAL_COUNT_OF_ALERTS,
timelineRowActions: [getInvestigateInResolverAction({ dispatch, timelineId })],
timelineRowActions: () => [getInvestigateInResolverAction({ dispatch, timelineId })],
title: i18n.ALERTS_TABLE_TITLE,
unit: i18n.UNIT,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,23 +93,14 @@ const EventsViewerComponent: React.FC<Props> = ({
}) => {
const columnsHeader = isEmpty(columns) ? defaultHeaders : columns;
const kibana = useKibana();
const { filterManager } = useKibana().services.data.query;
const [isQueryLoading, setIsQueryLoading] = useState(false);

const {
getManageTimelineById,
setIsTimelineLoading,
setTimelineFilterManager,
} = useManageTimeline();
const { getManageTimelineById, setIsTimelineLoading } = useManageTimeline();

useEffect(() => {
setIsTimelineLoading({ id, isLoading: isQueryLoading });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isQueryLoading]);
useEffect(() => {
setTimelineFilterManager({ id, filterManager });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [filterManager]);

const { queryFields, title, unit } = useMemo(() => getManageTimelineById(id), [
getManageTimelineById,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export const EventsQueryTabBody = ({
initializeTimeline({
id: TimelineId.hostsPageEvents,
defaultModel: eventsDefaultModel,
timelineRowActions: [
timelineRowActions: () => [
getInvestigateInResolverAction({ dispatch, timelineId: TimelineId.hostsPageEvents }),
],
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,18 @@ import { SubsetTimelineModel } from '../../store/timeline/model';
import * as i18n from '../../../common/components/events_viewer/translations';
import * as i18nF from '../timeline/footer/translations';
import { timelineDefaults as timelineDefaultModel } from '../../store/timeline/defaults';
import { Ecs } from '../../../graphql/types';

interface ManageTimelineInit {
documentType?: string;
defaultModel?: SubsetTimelineModel;
filterManager?: FilterManager;
footerText?: string;
id: string;
indexToAdd?: string[] | null;
loadingText?: string;
selectAll?: boolean;
timelineRowActions: TimelineRowAction[];
timelineRowActions: (ecsData: Ecs) => TimelineRowAction[];
title?: string;
unit?: (totalCount: number) => string;
}
Expand All @@ -39,7 +41,7 @@ interface ManageTimeline {
loadingText: string;
queryFields: string[];
selectAll: boolean;
timelineRowActions: TimelineRowAction[];
timelineRowActions: (ecsData: Ecs) => TimelineRowAction[];
title: string;
unit: (totalCount: number) => string;
}
Expand Down Expand Up @@ -67,12 +69,10 @@ type ActionManageTimeline =
| {
type: 'SET_TIMELINE_ACTIONS';
id: string;
payload: { queryFields?: string[]; timelineRowActions: TimelineRowAction[] };
}
| {
type: 'SET_TIMELINE_FILTER_MANAGER';
id: string;
payload: { filterManager: FilterManager };
payload: {
queryFields?: string[];
timelineRowActions: (ecsData: Ecs) => TimelineRowAction[];
};
};

export const getTimelineDefaults = (id: string) => ({
Expand All @@ -85,7 +85,7 @@ export const getTimelineDefaults = (id: string) => ({
id,
isLoading: false,
queryFields: [],
timelineRowActions: [],
timelineRowActions: () => [],
title: i18n.EVENTS,
unit: (n: number) => i18n.UNIT(n),
});
Expand All @@ -112,7 +112,6 @@ const reducerManageTimeline = (
},
} as ManageTimelineById;
case 'SET_TIMELINE_ACTIONS':
case 'SET_TIMELINE_FILTER_MANAGER':
return {
...state,
[action.id]: {
Expand Down Expand Up @@ -143,9 +142,8 @@ interface UseTimelineManager {
setTimelineRowActions: (actionsArgs: {
id: string;
queryFields?: string[];
timelineRowActions: TimelineRowAction[];
timelineRowActions: (ecsData: Ecs) => TimelineRowAction[];
}) => void;
setTimelineFilterManager: (filterArgs: { id: string; filterManager: FilterManager }) => void;
}

const useTimelineManager = (manageTimelineForTesting?: ManageTimelineById): UseTimelineManager => {
Expand All @@ -169,7 +167,7 @@ const useTimelineManager = (manageTimelineForTesting?: ManageTimelineById): UseT
}: {
id: string;
queryFields?: string[];
timelineRowActions: TimelineRowAction[];
timelineRowActions: (ecsData: Ecs) => TimelineRowAction[];
}) => {
dispatch({
type: 'SET_TIMELINE_ACTIONS',
Expand All @@ -180,17 +178,6 @@ const useTimelineManager = (manageTimelineForTesting?: ManageTimelineById): UseT
[]
);

const setTimelineFilterManager = useCallback(
({ id, filterManager }: { id: string; filterManager: FilterManager }) => {
dispatch({
type: 'SET_TIMELINE_FILTER_MANAGER',
id,
payload: { filterManager },
});
},
[]
);

const setIsTimelineLoading = useCallback(
({ id, isLoading }: { id: string; isLoading: boolean }) => {
dispatch({
Expand Down Expand Up @@ -219,7 +206,7 @@ const useTimelineManager = (manageTimelineForTesting?: ManageTimelineById): UseT
if (state[id] != null) {
return state[id];
}
initializeTimeline({ id, timelineRowActions: [] });
initializeTimeline({ id, timelineRowActions: () => [] });
return getTimelineDefaults(id);
},
[initializeTimeline, state]
Expand All @@ -234,7 +221,6 @@ const useTimelineManager = (manageTimelineForTesting?: ManageTimelineById): UseT
setIndexToAdd,
setIsTimelineLoading,
setTimelineRowActions,
setTimelineFilterManager,
};
};

Expand All @@ -246,7 +232,6 @@ const init = {
initializeTimeline: () => noop,
setIsTimelineLoading: () => noop,
setTimelineRowActions: () => noop,
setTimelineFilterManager: () => noop,
};

const ManageTimelineContext = createContext<UseTimelineManager>(init);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,10 @@ export const EventColumnView = React.memo<Props>(
updateNote,
}) => {
const { getManageTimelineById } = useManageTimeline();
const timelineActions = useMemo(() => getManageTimelineById(timelineId).timelineRowActions, [
getManageTimelineById,
timelineId,
]);
const timelineActions = useMemo(
() => getManageTimelineById(timelineId).timelineRowActions(ecsData),
[ecsData, getManageTimelineById, timelineId]
);
const [isPopoverOpen, setPopover] = useState(false);

const onButtonClick = useCallback(() => {
Expand All @@ -105,6 +105,7 @@ export const EventColumnView = React.memo<Props>(

const button = (
<EuiButtonIcon
aria-label="context menu"
data-test-subj="timeline-context-menu-button"
size="s"
iconType="boxesHorizontal"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,10 @@ export const Body = React.memo<BodyProps>(
}) => {
const containerElementRef = useRef<HTMLDivElement>(null);
const { getManageTimelineById } = useManageTimeline();
const timelineActions = useMemo(() => getManageTimelineById(id).timelineRowActions, [
getManageTimelineById,
id,
]);
const timelineActions = useMemo(
() => (data.length > 0 ? getManageTimelineById(id).timelineRowActions(data[0].ecs) : []),
[data, getManageTimelineById, id]
);

const additionalActionWidth = useMemo(() => {
let hasContextMenu = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,26 +172,19 @@ export const TimelineComponent: React.FC<Props> = ({
[sort.columnId, sort.sortDirection]
);
const [isQueryLoading, setIsQueryLoading] = useState(false);
const {
initializeTimeline,
setIndexToAdd,
setIsTimelineLoading,
setTimelineFilterManager,
} = useManageTimeline();
const { initializeTimeline, setIndexToAdd, setIsTimelineLoading } = useManageTimeline();
useEffect(() => {
initializeTimeline({
filterManager,
id,
indexToAdd,
timelineRowActions: [getInvestigateInResolverAction({ dispatch, timelineId: id })],
timelineRowActions: () => [getInvestigateInResolverAction({ dispatch, timelineId: id })],
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
setIsTimelineLoading({ id, isLoading: isQueryLoading || loadingIndexName });
}, [loadingIndexName, id, isQueryLoading, setIsTimelineLoading]);
useEffect(() => {
setTimelineFilterManager({ id, filterManager });
}, [filterManager, id, setTimelineFilterManager]);

useEffect(() => {
setIndexToAdd({ id, indexToAdd });
Expand Down