[Security Solution] [Timeline] Bugfix for timeline row actions disappear sometimes (#70958)

This commit is contained in:
Steph Milovic 2020-07-07 12:50:47 -06:00 committed by GitHub
parent 465ed21194
commit 06bc389189
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 37 additions and 49 deletions

View file

@ -48,6 +48,7 @@ import {
displaySuccessToast, displaySuccessToast,
displayErrorToast, displayErrorToast,
} from '../../../common/components/toasters'; } from '../../../common/components/toasters';
import { getInvestigateInResolverAction } from '../../../timelines/components/timeline/body/helpers';
interface OwnProps { interface OwnProps {
timelineId: TimelineIdLiteral; timelineId: TimelineIdLiteral;
@ -331,13 +332,14 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
useEffect(() => { useEffect(() => {
initializeTimeline({ initializeTimeline({
id: timelineId,
documentType: i18n.ALERTS_DOCUMENT_TYPE,
defaultModel: alertsDefaultModel, defaultModel: alertsDefaultModel,
documentType: i18n.ALERTS_DOCUMENT_TYPE,
footerText: i18n.TOTAL_COUNT_OF_ALERTS, footerText: i18n.TOTAL_COUNT_OF_ALERTS,
id: timelineId,
loadingText: i18n.LOADING_ALERTS, loadingText: i18n.LOADING_ALERTS,
title: i18n.ALERTS_TABLE_TITLE,
selectAll: canUserCRUD ? selectAll : false, selectAll: canUserCRUD ? selectAll : false,
timelineRowActions: [getInvestigateInResolverAction({ dispatch, timelineId })],
title: i18n.ALERTS_TABLE_TITLE,
}); });
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);

View file

@ -69,7 +69,7 @@ const AlertsTableComponent: React.FC<Props> = ({
}) => { }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const alertsFilter = useMemo(() => [...defaultAlertsFilters, ...pageFilters], [pageFilters]); const alertsFilter = useMemo(() => [...defaultAlertsFilters, ...pageFilters], [pageFilters]);
const { initializeTimeline, setTimelineRowActions } = useManageTimeline(); const { initializeTimeline } = useManageTimeline();
useEffect(() => { useEffect(() => {
initializeTimeline({ initializeTimeline({
@ -77,13 +77,10 @@ const AlertsTableComponent: React.FC<Props> = ({
documentType: i18n.ALERTS_DOCUMENT_TYPE, documentType: i18n.ALERTS_DOCUMENT_TYPE,
defaultModel: alertsDefaultModel, defaultModel: alertsDefaultModel,
footerText: i18n.TOTAL_COUNT_OF_ALERTS, footerText: i18n.TOTAL_COUNT_OF_ALERTS,
timelineRowActions: [getInvestigateInResolverAction({ dispatch, timelineId })],
title: i18n.ALERTS_TABLE_TITLE, title: i18n.ALERTS_TABLE_TITLE,
unit: i18n.UNIT, unit: i18n.UNIT,
}); });
setTimelineRowActions({
id: timelineId,
timelineRowActions: [getInvestigateInResolverAction({ dispatch, timelineId })],
});
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
return ( return (

View file

@ -18,7 +18,7 @@ import { useAddToTimeline } from '../../hooks/use_add_to_timeline';
import { DraggableWrapperHoverContent } from './draggable_wrapper_hover_content'; import { DraggableWrapperHoverContent } from './draggable_wrapper_hover_content';
import { import {
ManageGlobalTimeline, ManageGlobalTimeline,
timelineDefaults, getTimelineDefaults,
} from '../../../timelines/components/manage_timeline'; } from '../../../timelines/components/manage_timeline';
import { TimelineId } from '../../../../common/types/timeline'; import { TimelineId } from '../../../../common/types/timeline';
@ -152,10 +152,7 @@ describe('DraggableWrapperHoverContent', () => {
beforeEach(() => { beforeEach(() => {
onFilterAdded = jest.fn(); onFilterAdded = jest.fn();
const manageTimelineForTesting = { const manageTimelineForTesting = {
[timelineId]: { [timelineId]: getTimelineDefaults(timelineId),
...timelineDefaults,
id: timelineId,
},
}; };
wrapper = mount( wrapper = mount(
@ -249,8 +246,7 @@ describe('DraggableWrapperHoverContent', () => {
const manageTimelineForTesting = { const manageTimelineForTesting = {
[timelineId]: { [timelineId]: {
...timelineDefaults, ...getTimelineDefaults(timelineId),
id: timelineId,
filterManager, filterManager,
}, },
}; };

View file

@ -7,7 +7,6 @@
import { EuiPanel } from '@elastic/eui'; import { EuiPanel } from '@elastic/eui';
import { getOr, isEmpty, union } from 'lodash/fp'; import { getOr, isEmpty, union } from 'lodash/fp';
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import { useDispatch } from 'react-redux';
import styled from 'styled-components'; import styled from 'styled-components';
import deepEqual from 'fast-deep-equal'; import deepEqual from 'fast-deep-equal';
@ -35,7 +34,6 @@ import {
} from '../../../../../../../src/plugins/data/public'; } from '../../../../../../../src/plugins/data/public';
import { inputsModel } from '../../store'; import { inputsModel } from '../../store';
import { useManageTimeline } from '../../../timelines/components/manage_timeline'; import { useManageTimeline } from '../../../timelines/components/manage_timeline';
import { getInvestigateInResolverAction } from '../../../timelines/components/timeline/body/helpers';
const DEFAULT_EVENTS_VIEWER_HEIGHT = 500; const DEFAULT_EVENTS_VIEWER_HEIGHT = 500;
@ -93,7 +91,6 @@ const EventsViewerComponent: React.FC<Props> = ({
toggleColumn, toggleColumn,
utilityBar, utilityBar,
}) => { }) => {
const dispatch = useDispatch();
const columnsHeader = isEmpty(columns) ? defaultHeaders : columns; const columnsHeader = isEmpty(columns) ? defaultHeaders : columns;
const kibana = useKibana(); const kibana = useKibana();
const { filterManager } = useKibana().services.data.query; const { filterManager } = useKibana().services.data.query;
@ -103,16 +100,8 @@ const EventsViewerComponent: React.FC<Props> = ({
getManageTimelineById, getManageTimelineById,
setIsTimelineLoading, setIsTimelineLoading,
setTimelineFilterManager, setTimelineFilterManager,
setTimelineRowActions,
} = useManageTimeline(); } = useManageTimeline();
useEffect(() => {
setTimelineRowActions({
id,
timelineRowActions: [getInvestigateInResolverAction({ dispatch, timelineId: id })],
});
}, [setTimelineRowActions, id, dispatch]);
useEffect(() => { useEffect(() => {
setIsTimelineLoading({ id, isLoading: isQueryLoading }); setIsTimelineLoading({ id, isLoading: isQueryLoading });
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps

View file

@ -25,7 +25,7 @@ import { Props } from './top_n';
import { StatefulTopN } from '.'; import { StatefulTopN } from '.';
import { import {
ManageGlobalTimeline, ManageGlobalTimeline,
timelineDefaults, getTimelineDefaults,
} from '../../../timelines/components/manage_timeline'; } from '../../../timelines/components/manage_timeline';
import { TimelineId } from '../../../../common/types/timeline'; import { TimelineId } from '../../../../common/types/timeline';
@ -272,8 +272,7 @@ describe('StatefulTopN', () => {
filterManager = new FilterManager(mockUiSettingsForFilterManager); filterManager = new FilterManager(mockUiSettingsForFilterManager);
const manageTimelineForTesting = { const manageTimelineForTesting = {
[TimelineId.active]: { [TimelineId.active]: {
...timelineDefaults, ...getTimelineDefaults(TimelineId.active),
id: TimelineId.active,
filterManager, filterManager,
}, },
}; };
@ -351,8 +350,7 @@ describe('StatefulTopN', () => {
const manageTimelineForTesting = { const manageTimelineForTesting = {
[TimelineId.active]: { [TimelineId.active]: {
...timelineDefaults, ...getTimelineDefaults(TimelineId.active),
id: TimelineId.active,
filterManager, filterManager,
documentType: 'alerts', documentType: 'alerts',
}, },

View file

@ -5,6 +5,7 @@
*/ */
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { TimelineId } from '../../../../common/types/timeline'; import { TimelineId } from '../../../../common/types/timeline';
import { StatefulEventsViewer } from '../../../common/components/events_viewer'; import { StatefulEventsViewer } from '../../../common/components/events_viewer';
import { HostsComponentsQueryProps } from './types'; import { HostsComponentsQueryProps } from './types';
@ -18,6 +19,7 @@ import { MatrixHistogramContainer } from '../../../common/components/matrix_hist
import * as i18n from '../translations'; import * as i18n from '../translations';
import { HistogramType } from '../../../graphql/types'; import { HistogramType } from '../../../graphql/types';
import { useManageTimeline } from '../../../timelines/components/manage_timeline'; import { useManageTimeline } from '../../../timelines/components/manage_timeline';
import { getInvestigateInResolverAction } from '../../../timelines/components/timeline/body/helpers';
const EVENTS_HISTOGRAM_ID = 'eventsOverTimeQuery'; const EVENTS_HISTOGRAM_ID = 'eventsOverTimeQuery';
@ -57,13 +59,17 @@ export const EventsQueryTabBody = ({
startDate, startDate,
}: HostsComponentsQueryProps) => { }: HostsComponentsQueryProps) => {
const { initializeTimeline } = useManageTimeline(); const { initializeTimeline } = useManageTimeline();
const dispatch = useDispatch();
useEffect(() => { useEffect(() => {
initializeTimeline({ initializeTimeline({
id: TimelineId.hostsPageEvents, id: TimelineId.hostsPageEvents,
defaultModel: eventsDefaultModel, defaultModel: eventsDefaultModel,
timelineRowActions: [
getInvestigateInResolverAction({ dispatch, timelineId: TimelineId.hostsPageEvents }),
],
}); });
}, [initializeTimeline]); }, [dispatch, initializeTimeline]);
useEffect(() => { useEffect(() => {
return () => { return () => {

View file

@ -6,6 +6,7 @@
import React, { createContext, useCallback, useContext, useReducer } from 'react'; import React, { createContext, useCallback, useContext, useReducer } from 'react';
import { noop } from 'lodash/fp'; import { noop } from 'lodash/fp';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths // eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { FilterManager } from '../../../../../../../src/plugins/data/public/query/filter_manager'; import { FilterManager } from '../../../../../../../src/plugins/data/public/query/filter_manager';
import { TimelineRowAction } from '../timeline/body/actions'; import { TimelineRowAction } from '../timeline/body/actions';
@ -22,6 +23,7 @@ interface ManageTimelineInit {
indexToAdd?: string[] | null; indexToAdd?: string[] | null;
loadingText?: string; loadingText?: string;
selectAll?: boolean; selectAll?: boolean;
timelineRowActions: TimelineRowAction[];
title?: string; title?: string;
unit?: (totalCount: number) => string; unit?: (totalCount: number) => string;
} }
@ -73,19 +75,20 @@ type ActionManageTimeline =
payload: { filterManager: FilterManager }; payload: { filterManager: FilterManager };
}; };
export const timelineDefaults = { export const getTimelineDefaults = (id: string) => ({
indexToAdd: null, indexToAdd: null,
defaultModel: timelineDefaultModel, defaultModel: timelineDefaultModel,
loadingText: i18n.LOADING_EVENTS, loadingText: i18n.LOADING_EVENTS,
footerText: i18nF.TOTAL_COUNT_OF_EVENTS, footerText: i18nF.TOTAL_COUNT_OF_EVENTS,
documentType: i18nF.TOTAL_COUNT_OF_EVENTS, documentType: i18nF.TOTAL_COUNT_OF_EVENTS,
selectAll: false, selectAll: false,
id,
isLoading: false, isLoading: false,
queryFields: [], queryFields: [],
timelineRowActions: [], timelineRowActions: [],
title: i18n.EVENTS, title: i18n.EVENTS,
unit: (n: number) => i18n.UNIT(n), unit: (n: number) => i18n.UNIT(n),
}; });
const reducerManageTimeline = ( const reducerManageTimeline = (
state: ManageTimelineById, state: ManageTimelineById,
action: ActionManageTimeline action: ActionManageTimeline
@ -95,7 +98,7 @@ const reducerManageTimeline = (
return { return {
...state, ...state,
[action.id]: { [action.id]: {
...timelineDefaults, ...getTimelineDefaults(action.id),
...state[action.id], ...state[action.id],
...action.payload, ...action.payload,
}, },
@ -216,8 +219,8 @@ const useTimelineManager = (manageTimelineForTesting?: ManageTimelineById): UseT
if (state[id] != null) { if (state[id] != null) {
return state[id]; return state[id];
} }
initializeTimeline({ id }); initializeTimeline({ id, timelineRowActions: [] });
return { ...timelineDefaults, id }; return getTimelineDefaults(id);
}, },
[initializeTimeline, state] [initializeTimeline, state]
); );
@ -236,7 +239,7 @@ const useTimelineManager = (manageTimelineForTesting?: ManageTimelineById): UseT
}; };
const init = { const init = {
getManageTimelineById: (id: string) => ({ ...timelineDefaults, id }), getManageTimelineById: (id: string) => getTimelineDefaults(id),
getTimelineFilterManager: () => undefined, getTimelineFilterManager: () => undefined,
setIndexToAdd: () => undefined, setIndexToAdd: () => undefined,
isManagedTimeline: () => false, isManagedTimeline: () => false,
@ -245,6 +248,7 @@ const init = {
setTimelineRowActions: () => noop, setTimelineRowActions: () => noop,
setTimelineFilterManager: () => noop, setTimelineFilterManager: () => noop,
}; };
const ManageTimelineContext = createContext<UseTimelineManager>(init); const ManageTimelineContext = createContext<UseTimelineManager>(init);
export const useManageTimeline = () => useContext(ManageTimelineContext); export const useManageTimeline = () => useContext(ManageTimelineContext);

View file

@ -194,8 +194,7 @@ export const EventColumnView = React.memo<Props>(
</EventsTdContent>, </EventsTdContent>,
] ]
: grouped.icon; : grouped.icon;
// eslint-disable-next-line react-hooks/exhaustive-deps }, [button, closePopover, id, onClickCb, ecsData, timelineActions, isPopoverOpen]);
}, [button, ecsData, timelineActions, isPopoverOpen]); // , isPopoverOpen, closePopover, onButtonClick]);
return ( return (
<EventsTrData data-test-subj="event-column-view"> <EventsTrData data-test-subj="event-column-view">

View file

@ -13,7 +13,7 @@ import { useMountAppended } from '../../../../common/utils/use_mount_appended';
import { DataProviders } from '.'; import { DataProviders } from '.';
import { DataProvider } from './data_provider'; import { DataProvider } from './data_provider';
import { mockDataProviders } from './mock/mock_data_providers'; import { mockDataProviders } from './mock/mock_data_providers';
import { ManageGlobalTimeline, timelineDefaults } from '../../manage_timeline'; import { ManageGlobalTimeline, getTimelineDefaults } from '../../manage_timeline';
import { FilterManager } from '../../../../../../../../src/plugins/data/public/query/filter_manager'; import { FilterManager } from '../../../../../../../../src/plugins/data/public/query/filter_manager';
import { createKibanaCoreStartMock } from '../../../../common/mock/kibana_core'; import { createKibanaCoreStartMock } from '../../../../common/mock/kibana_core';
const mockUiSettingsForFilterManager = createKibanaCoreStartMock().uiSettings; const mockUiSettingsForFilterManager = createKibanaCoreStartMock().uiSettings;
@ -28,8 +28,7 @@ describe('DataProviders', () => {
test('renders correctly against snapshot', () => { test('renders correctly against snapshot', () => {
const manageTimelineForTesting = { const manageTimelineForTesting = {
foo: { foo: {
...timelineDefaults, ...getTimelineDefaults('foo'),
id: 'foo',
filterManager, filterManager,
}, },
}; };

View file

@ -16,7 +16,7 @@ import { mockDataProviders } from './mock/mock_data_providers';
import { Providers } from './providers'; import { Providers } from './providers';
import { DELETE_CLASS_NAME, ENABLE_CLASS_NAME, EXCLUDE_CLASS_NAME } from './provider_item_actions'; import { DELETE_CLASS_NAME, ENABLE_CLASS_NAME, EXCLUDE_CLASS_NAME } from './provider_item_actions';
import { useMountAppended } from '../../../../common/utils/use_mount_appended'; import { useMountAppended } from '../../../../common/utils/use_mount_appended';
import { ManageGlobalTimeline, timelineDefaults } from '../../manage_timeline'; import { ManageGlobalTimeline, getTimelineDefaults } from '../../manage_timeline';
const mockUiSettingsForFilterManager = createKibanaCoreStartMock().uiSettings; const mockUiSettingsForFilterManager = createKibanaCoreStartMock().uiSettings;
@ -27,8 +27,7 @@ describe('Providers', () => {
const manageTimelineForTesting = { const manageTimelineForTesting = {
foo: { foo: {
...timelineDefaults, ...getTimelineDefaults('foo'),
id: 'foo',
filterManager, filterManager,
isLoading, isLoading,
}, },

View file

@ -177,12 +177,11 @@ export const TimelineComponent: React.FC<Props> = ({
setIndexToAdd, setIndexToAdd,
setIsTimelineLoading, setIsTimelineLoading,
setTimelineFilterManager, setTimelineFilterManager,
setTimelineRowActions,
} = useManageTimeline(); } = useManageTimeline();
useEffect(() => { useEffect(() => {
initializeTimeline({ id, indexToAdd }); initializeTimeline({
setTimelineRowActions({
id, id,
indexToAdd,
timelineRowActions: [getInvestigateInResolverAction({ dispatch, timelineId: id })], timelineRowActions: [getInvestigateInResolverAction({ dispatch, timelineId: id })],
}); });
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps