diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx index 946e71eb4856b..322203f0caf28 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx @@ -11,7 +11,12 @@ import styled from 'styled-components'; import type { Filter } from '@kbn/es-query'; import { inputsModel, State } from '../../store'; import { inputsActions } from '../../store/actions'; -import { ControlColumnProps, RowRenderer, TimelineId } from '../../../../common/types/timeline'; +import { + ControlColumnProps, + RowRenderer, + TimelineId, + TimelineTabs, +} from '../../../../common/types/timeline'; import { APP_ID, APP_UI_ID } from '../../../../common/constants'; import { timelineActions } from '../../../timelines/store/timeline'; import type { SubsetTimelineModel } from '../../../timelines/store/timeline/model'; @@ -33,6 +38,7 @@ import { useFieldBrowserOptions, FieldEditorActions, } from '../../../timelines/components/fields_browser'; +import { useLoadDetailPanel } from '../../../timelines/components/side_panel/hooks/use_load_detail_panel'; const EMPTY_CONTROL_COLUMNS: ControlColumnProps[] = []; @@ -156,11 +162,22 @@ const StatefulEventsViewerComponent: React.FC = ({ const globalFilters = useMemo(() => [...filters, ...(pageFilters ?? [])], [filters, pageFilters]); const trailingControlColumns: ControlColumnProps[] = EMPTY_CONTROL_COLUMNS; + + const { openDetailsPanel, FlyoutDetailsPanel } = useLoadDetailPanel({ + isFlyoutView: true, + entityType, + sourcerScope: SourcererScopeName.timeline, + timelineId: id, + tabType: TimelineTabs.query, + }); + const graphOverlay = useMemo(() => { const shouldShowOverlay = (graphEventId != null && graphEventId.length > 0) || sessionViewId !== null; - return shouldShowOverlay ? : null; - }, [graphEventId, id, sessionViewId]); + return shouldShowOverlay ? ( + + ) : null; + }, [graphEventId, id, sessionViewId, openDetailsPanel]); const setQuery = useCallback( (inspect, loading, refetch) => { dispatch(inputsActions.setQuery({ id, inputId: 'global', inspect, loading, refetch })); @@ -240,14 +257,7 @@ const StatefulEventsViewerComponent: React.FC = ({ })} - + {FlyoutDetailsPanel} ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.tsx index af03bc7d0d7d2..245e86a96b41b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.tsx @@ -81,7 +81,8 @@ const ScrollableFlexItem = styled(EuiFlexItem)` width: 100%; `; -interface OwnProps { +interface GraphOverlayProps { + openDetailsPanel: (eventId?: string, onClose?: () => void) => void; timelineId: TimelineId; } @@ -133,7 +134,7 @@ NavigationComponent.displayName = 'NavigationComponent'; const Navigation = React.memo(NavigationComponent); -const GraphOverlayComponent: React.FC = ({ timelineId }) => { +const GraphOverlayComponent: React.FC = ({ timelineId, openDetailsPanel }) => { const dispatch = useDispatch(); const { globalFullScreen, setGlobalFullScreen } = useGlobalFullScreen(); const { timelineFullScreen, setTimelineFullScreen } = useTimelineFullScreen(); @@ -147,8 +148,13 @@ const GraphOverlayComponent: React.FC = ({ timelineId }) => { (state) => (getTimeline(state, timelineId) ?? timelineDefaults).sessionViewId ); const sessionViewMain = useMemo(() => { - return sessionViewId !== null ? sessionView.getSessionView(sessionViewId) : null; - }, [sessionView, sessionViewId]); + return sessionViewId !== null + ? sessionView.getSessionView({ + sessionEntityId: sessionViewId, + loadAlertDetails: openDetailsPanel, + }) + : null; + }, [sessionView, sessionViewId, openDetailsPanel]); const getStartSelector = useMemo(() => startSelector(), []); const getEndSelector = useMemo(() => endSelector(), []); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/hooks/use_load_detail_panel.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/hooks/use_load_detail_panel.tsx new file mode 100644 index 0000000000000..b8b8d2ccbfc67 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/hooks/use_load_detail_panel.tsx @@ -0,0 +1,136 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo, useCallback, useRef } from 'react'; +import { useDispatch } from 'react-redux'; +import { timelineActions, timelineSelectors } from '../../../store/timeline'; +import type { EntityType } from '../../../../../../timelines/common'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; +import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; +import { activeTimeline } from '../../../containers/active_timeline_context'; +import { TimelineId, TimelineTabs } from '../../../../../common/types/timeline'; +import { timelineDefaults } from '../../../store/timeline/defaults'; +import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; +import { DetailsPanel } from '..'; + +export interface UseLoadDetailPanelConfig { + entityType?: EntityType; + isFlyoutView?: boolean; + sourcerScope: SourcererScopeName; + timelineId: TimelineId; + tabType?: TimelineTabs; +} + +export interface UseLoadDetailPanelReturn { + openDetailsPanel: (eventId?: string, onClose?: () => void) => void; + handleOnDetailsPanelClosed: () => void; + FlyoutDetailsPanel: JSX.Element; + shouldShowFlyoutDetailsPanel: boolean; +} + +export const useLoadDetailPanel = ({ + entityType, + isFlyoutView, + sourcerScope, + timelineId, + tabType, +}: UseLoadDetailPanelConfig): UseLoadDetailPanelReturn => { + const { browserFields, docValueFields, selectedPatterns, runtimeMappings } = + useSourcererDataView(sourcerScope); + const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); + const dispatch = useDispatch(); + + const expandedDetail = useDeepEqualSelector( + (state) => (getTimeline(state, timelineId) ?? timelineDefaults).expandedDetail + ); + const onFlyoutClose = useRef(() => {}); + + const shouldShowFlyoutDetailsPanel = useMemo(() => { + if ( + tabType && + expandedDetail && + expandedDetail[tabType] && + !!expandedDetail[tabType]?.panelView + ) { + return true; + } + return false; + }, [expandedDetail, tabType]); + + const loadDetailsPanel = useCallback( + (eventId?: string) => { + if (eventId) { + dispatch( + timelineActions.toggleDetailPanel({ + panelView: 'eventDetail', + tabType, + timelineId, + params: { + eventId, + indexName: selectedPatterns.join(','), + }, + }) + ); + } + }, + [dispatch, selectedPatterns, tabType, timelineId] + ); + + const openDetailsPanel = useCallback( + (eventId?: string, onClose?: () => void) => { + loadDetailsPanel(eventId); + onFlyoutClose.current = onClose ?? (() => {}); + }, + [loadDetailsPanel] + ); + + const handleOnDetailsPanelClosed = useCallback(() => { + if (onFlyoutClose.current) onFlyoutClose.current(); + dispatch(timelineActions.toggleDetailPanel({ tabType, timelineId })); + + if ( + tabType && + expandedDetail[tabType]?.panelView && + timelineId === TimelineId.active && + shouldShowFlyoutDetailsPanel + ) { + activeTimeline.toggleExpandedDetail({}); + } + }, [dispatch, timelineId, expandedDetail, tabType, shouldShowFlyoutDetailsPanel]); + + const FlyoutDetailsPanel = useMemo( + () => ( + + ), + [ + browserFields, + docValueFields, + entityType, + handleOnDetailsPanelClosed, + isFlyoutView, + runtimeMappings, + tabType, + timelineId, + ] + ); + + return { + openDetailsPanel, + handleOnDetailsPanelClosed, + shouldShowFlyoutDetailsPanel, + FlyoutDetailsPanel, + }; +}; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/session_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/session_tab_content/index.tsx index 700462f00aa79..299018d96154a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/session_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/session_tab_content/index.tsx @@ -10,9 +10,11 @@ import React, { useMemo } from 'react'; import styled from 'styled-components'; import { timelineSelectors } from '../../../store/timeline'; import { useKibana } from '../../../../common/lib/kibana'; -import { TimelineId } from '../../../../../common/types/timeline'; +import { TimelineId, TimelineTabs } from '../../../../../common/types/timeline'; import { timelineDefaults } from '../../../../timelines/store/timeline/defaults'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; +import { useLoadDetailPanel } from '../../side_panel/hooks/use_load_detail_panel'; +import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; const FullWidthFlexGroup = styled(EuiFlexGroup)` margin: 0; @@ -25,23 +27,50 @@ const ScrollableFlexItem = styled(EuiFlexItem)` overflow: hidden; `; +const VerticalRule = styled.div` + width: 2px; + height: 100%; + background: ${({ theme }) => theme.eui.euiColorLightShade}; +`; + interface Props { timelineId: TimelineId; } const SessionTabContent: React.FC = ({ timelineId }) => { const { sessionView } = useKibana().services; - const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); const sessionViewId = useDeepEqualSelector( (state) => (getTimeline(state, timelineId) ?? timelineDefaults).sessionViewId ); + const { openDetailsPanel, shouldShowFlyoutDetailsPanel, FlyoutDetailsPanel } = useLoadDetailPanel( + { + sourcerScope: SourcererScopeName.timeline, + timelineId, + tabType: TimelineTabs.session, + } + ); const sessionViewMain = useMemo(() => { - return sessionViewId !== null ? sessionView.getSessionView(sessionViewId) : null; - }, [sessionView, sessionViewId]); + return sessionViewId !== null + ? sessionView.getSessionView({ + sessionEntityId: sessionViewId, + loadAlertDetails: openDetailsPanel, + }) + : null; + }, [openDetailsPanel, sessionView, sessionViewId]); - return {sessionViewMain}; + return ( + + {sessionViewMain} + {shouldShowFlyoutDetailsPanel && ( + <> + + {FlyoutDetailsPanel} + + )} + + ); }; // eslint-disable-next-line import/no-default-export diff --git a/x-pack/plugins/session_view/public/components/process_tree_node/index.tsx b/x-pack/plugins/session_view/public/components/process_tree_node/index.tsx index b1c42dd95efb9..e93a92d4eac6f 100644 --- a/x-pack/plugins/session_view/public/components/process_tree_node/index.tsx +++ b/x-pack/plugins/session_view/public/components/process_tree_node/index.tsx @@ -166,7 +166,7 @@ export function ProcessTreeNode({ const shouldRenderChildren = childrenExpanded && children && children.length > 0; const childrenTreeDepth = depth + 1; - const showUserEscalation = user.id !== parent.user.id; + const showUserEscalation = user?.id !== parent?.user?.id; const interactiveSession = !!tty; const sessionIcon = interactiveSession ? 'consoleApp' : 'compute'; const hasExec = process.hasExec(); diff --git a/x-pack/plugins/session_view/public/types.ts b/x-pack/plugins/session_view/public/types.ts index 6cc051bee0795..a9778d454a226 100644 --- a/x-pack/plugins/session_view/public/types.ts +++ b/x-pack/plugins/session_view/public/types.ts @@ -70,5 +70,5 @@ export interface SessionViewStart { }: { onOpenSessionView: (eventId: string) => void; }) => JSX.Element; - getSessionView: (sessionEntityId: string) => JSX.Element; + getSessionView: (sessionDeps: SessionViewDeps) => JSX.Element; } diff --git a/x-pack/plugins/timelines/common/types/timeline/index.ts b/x-pack/plugins/timelines/common/types/timeline/index.ts index a6c8ed1b74bff..6572d6cb695fa 100644 --- a/x-pack/plugins/timelines/common/types/timeline/index.ts +++ b/x-pack/plugins/timelines/common/types/timeline/index.ts @@ -462,6 +462,7 @@ export enum TimelineTabs { graph = 'graph', notes = 'notes', pinned = 'pinned', + session = 'session', eql = 'eql', }