diff --git a/x-pack/plugins/security_solution/public/alerts/components/alerts_table/default_config.tsx b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/default_config.tsx index 6d82897aaf010..d21551287fb3e 100644 --- a/x-pack/plugins/security_solution/public/alerts/components/alerts_table/default_config.tsx +++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/default_config.tsx @@ -36,6 +36,7 @@ import { SetEventsLoadingProps, UpdateTimelineLoading, } from './types'; +import { Ecs } from '../../../graphql/types'; export const buildAlertStatusFilter = (status: Status): Filter[] => [ { @@ -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, @@ -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: {i18n.ACTION_OPEN_ALERT}, diff --git a/x-pack/plugins/security_solution/public/alerts/components/alerts_table/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/index.test.tsx index 9ff368aff2bf6..f99a0256c0b3f 100644 --- a/x-pack/plugins/security_solution/public/alerts/components/alerts_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/index.test.tsx @@ -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', () => { diff --git a/x-pack/plugins/security_solution/public/alerts/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/index.tsx index 65f225c054598..b655c100a1c6f 100644 --- a/x-pack/plugins/security_solution/public/alerts/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/alerts/components/alerts_table/index.tsx @@ -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 { @@ -290,20 +291,21 @@ export const AlertsTableComponent: React.FC = ({ // 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, @@ -328,17 +330,19 @@ export const AlertsTableComponent: React.FC = ({ 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 diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_viewer/alerts_table.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/alerts_table.tsx index 12b2853f8f7e1..bf2d8948b7292 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_viewer/alerts_table.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/alerts_table.tsx @@ -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; @@ -69,15 +70,17 @@ const AlertsTableComponent: React.FC = ({ }) => { 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, }); diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx index 910030d41a23e..0a1f95d51e300 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx @@ -93,23 +93,14 @@ const EventsViewerComponent: React.FC = ({ }) => { 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, diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx b/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx index 58026a28a04ce..e2ec1b0e95b58 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx @@ -65,7 +65,7 @@ export const EventsQueryTabBody = ({ initializeTimeline({ id: TimelineId.hostsPageEvents, defaultModel: eventsDefaultModel, - timelineRowActions: [ + timelineRowActions: () => [ getInvestigateInResolverAction({ dispatch, timelineId: TimelineId.hostsPageEvents }), ], }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.tsx index c71087de4a07e..99d79a2441ccc 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/manage_timeline/index.tsx @@ -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; } @@ -39,7 +41,7 @@ interface ManageTimeline { loadingText: string; queryFields: string[]; selectAll: boolean; - timelineRowActions: TimelineRowAction[]; + timelineRowActions: (ecsData: Ecs) => TimelineRowAction[]; title: string; unit: (totalCount: number) => string; } @@ -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) => ({ @@ -85,7 +85,7 @@ export const getTimelineDefaults = (id: string) => ({ id, isLoading: false, queryFields: [], - timelineRowActions: [], + timelineRowActions: () => [], title: i18n.EVENTS, unit: (n: number) => i18n.UNIT(n), }); @@ -112,7 +112,6 @@ const reducerManageTimeline = ( }, } as ManageTimelineById; case 'SET_TIMELINE_ACTIONS': - case 'SET_TIMELINE_FILTER_MANAGER': return { ...state, [action.id]: { @@ -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 => { @@ -169,7 +167,7 @@ const useTimelineManager = (manageTimelineForTesting?: ManageTimelineById): UseT }: { id: string; queryFields?: string[]; - timelineRowActions: TimelineRowAction[]; + timelineRowActions: (ecsData: Ecs) => TimelineRowAction[]; }) => { dispatch({ type: 'SET_TIMELINE_ACTIONS', @@ -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({ @@ -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] @@ -234,7 +221,6 @@ const useTimelineManager = (manageTimelineForTesting?: ManageTimelineById): UseT setIndexToAdd, setIsTimelineLoading, setTimelineRowActions, - setTimelineFilterManager, }; }; @@ -246,7 +232,6 @@ const init = { initializeTimeline: () => noop, setIsTimelineLoading: () => noop, setTimelineRowActions: () => noop, - setTimelineFilterManager: () => noop, }; const ManageTimelineContext = createContext(init); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx index ae00edf5d1429..88ee8346c8ab2 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx @@ -89,10 +89,10 @@ export const EventColumnView = React.memo( 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(() => { @@ -105,6 +105,7 @@ export const EventColumnView = React.memo( const button = ( ( }) => { const containerElementRef = useRef(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; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.tsx index ed7f8a447c51d..b930325c3d35d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.tsx @@ -172,26 +172,19 @@ export const TimelineComponent: React.FC = ({ [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 });