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

interface OwnProps {
timelineId: TimelineIdLiteral;
Expand Down Expand Up @@ -289,20 +290,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 @@ -327,12 +329,14 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
return [...defaultFilters, ...buildAlertStatusFilter(filterGroup)];
}
}, [defaultFilters, filterGroup]);
const { filterManager } = useKibana().services.data.query;
const { initializeTimeline, setTimelineRowActions } = useManageTimeline();

useEffect(() => {
initializeTimeline({
id: timelineId,
documentType: i18n.ALERTS_DOCUMENT_TYPE,
filterManager,
footerText: i18n.TOTAL_COUNT_OF_ALERTS,
loadingText: i18n.LOADING_ALERTS,
title: i18n.ALERTS_TABLE_TITLE,
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,19 +70,21 @@ const AlertsTableComponent: React.FC<Props> = ({
}) => {
const dispatch = useDispatch();
const alertsFilter = useMemo(() => [...defaultAlertsFilters, ...pageFilters], [pageFilters]);
const { filterManager } = useKibana().services.data.query;
const { initializeTimeline, setTimelineRowActions } = useManageTimeline();

useEffect(() => {
initializeTimeline({
id: timelineId,
documentType: i18n.ALERTS_DOCUMENT_TYPE,
filterManager,
footerText: i18n.TOTAL_COUNT_OF_ALERTS,
title: i18n.ALERTS_TABLE_TITLE,
unit: i18n.UNIT,
});
setTimelineRowActions({
id: timelineId,
timelineRowActions: [getInvestigateInResolverAction({ dispatch, timelineId })],
timelineRowActions: () => [getInvestigateInResolverAction({ dispatch, timelineId })],
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,31 +96,25 @@ const EventsViewerComponent: React.FC<Props> = ({
const dispatch = useDispatch();
const columnsHeader = isEmpty(columns) ? defaultHeaders : columns;
const kibana = useKibana();
const { filterManager } = useKibana().services.data.query;
const [isQueryLoading, setIsQueryLoading] = useState(false);

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

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

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 @@ -11,9 +11,11 @@ import { FilterManager } from '../../../../../../../src/plugins/data/public/quer
import { TimelineRowAction } from '../timeline/body/actions';
import * as i18n from '../../../common/components/events_viewer/translations';
import * as i18nF from '../timeline/footer/translations';
import { Ecs } from '../../../graphql/types';

interface ManageTimelineInit {
documentType?: string;
filterManager?: FilterManager;
footerText?: string;
id: string;
indexToAdd?: string[] | null;
Expand All @@ -33,7 +35,7 @@ interface ManageTimeline {
loadingText: string;
queryFields: string[];
selectAll: boolean;
timelineRowActions: TimelineRowAction[];
timelineRowActions: (ecsData: Ecs) => TimelineRowAction[];
title: string;
unit: (totalCount: number) => string;
}
Expand All @@ -56,12 +58,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 timelineDefaults = {
Expand All @@ -72,7 +72,7 @@ export const timelineDefaults = {
selectAll: false,
isLoading: false,
queryFields: [],
timelineRowActions: [],
timelineRowActions: () => [],
title: i18n.EVENTS,
unit: (n: number) => i18n.UNIT(n),
};
Expand All @@ -88,7 +88,6 @@ const reducerManageTimeline = (state: ManageTimelineById, action: ActionManageTi
},
};
case 'SET_TIMELINE_ACTIONS':
case 'SET_TIMELINE_FILTER_MANAGER':
return {
...state,
[action.id]: {
Expand Down Expand Up @@ -118,9 +117,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 @@ -145,7 +143,7 @@ const useTimelineManager = (manageTimelineForTesting?: ManageTimelineById): UseT
}: {
id: string;
queryFields?: string[];
timelineRowActions: TimelineRowAction[];
timelineRowActions: (ecsData: Ecs) => TimelineRowAction[];
}) => {
dispatch({
type: 'SET_TIMELINE_ACTIONS',
Expand All @@ -156,17 +154,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 @@ -202,7 +189,6 @@ const useTimelineManager = (manageTimelineForTesting?: ManageTimelineById): UseT
isManagedTimeline,
setIsTimelineLoading,
setTimelineRowActions,
setTimelineFilterManager,
};
};

Expand All @@ -213,7 +199,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,28 +172,19 @@ export const TimelineComponent: React.FC<Props> = ({
[sort.columnId, sort.sortDirection]
);
const [isQueryLoading, setIsQueryLoading] = useState(false);
const {
initializeTimeline,
setIsTimelineLoading,
setTimelineFilterManager,
setTimelineRowActions,
} = useManageTimeline();
const { initializeTimeline, setIsTimelineLoading, setTimelineRowActions } = useManageTimeline();
useEffect(() => {
initializeTimeline({ id, indexToAdd });
initializeTimeline({ filterManager, id, indexToAdd });
setTimelineRowActions({
id,
timelineRowActions: [getInvestigateInResolverAction({ dispatch, timelineId: id })],
timelineRowActions: () => [getInvestigateInResolverAction({ dispatch, timelineId: id })],
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
setIsTimelineLoading({ id, isLoading: isQueryLoading || loadingIndexName });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [loadingIndexName, isQueryLoading]);
useEffect(() => {
setTimelineFilterManager({ id, filterManager });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [filterManager]);

return (
<TimelineContainer data-test-subj="timeline">
Expand Down