diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/session_view.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/session_view.tsx
index 3b45cd71b0a6f..dfb2c9821fc95 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/session_view.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/session_view.tsx
@@ -10,23 +10,23 @@ import React, { useCallback, useMemo } from 'react';
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
import type { TableId } from '@kbn/securitysolution-data-table';
import { EuiPanel } from '@elastic/eui';
-import {
- ANCESTOR_INDEX,
- ENTRY_LEADER_ENTITY_ID,
- ENTRY_LEADER_START,
-} from '../../shared/constants/field_names';
-import { getField } from '../../shared/utils';
+import type { Process } from '@kbn/session-view-plugin/common';
+import type { CustomProcess } from '../../session_view/context';
+import { useUserPrivileges } from '../../../../common/components/user_privileges';
import { SESSION_VIEW_TEST_ID } from './test_ids';
import { isActiveTimeline } from '../../../../helpers';
import { useSourcererDataView } from '../../../../sourcerer/containers';
-import { DocumentDetailsPreviewPanelKey } from '../../shared/constants/panel_keys';
+import {
+ DocumentDetailsPreviewPanelKey,
+ DocumentDetailsSessionViewPanelKey,
+} from '../../shared/constants/panel_keys';
import { useKibana } from '../../../../common/lib/kibana';
import { useDocumentDetailsContext } from '../../shared/context';
import { SourcererScopeName } from '../../../../sourcerer/store/model';
import { detectionsTimelineIds } from '../../../../timelines/containers/helpers';
import { ALERT_PREVIEW_BANNER } from '../../preview/constants';
import { useLicense } from '../../../../common/hooks/use_license';
-import { useSessionPreview } from '../../right/hooks/use_session_preview';
+import { useSessionViewConfig } from '../../shared/hooks/use_session_view_config';
import { SessionViewNoDataMessage } from '../../shared/components/session_view_no_data_message';
import { DocumentEventTypes } from '../../../../common/lib/telemetry';
@@ -37,18 +37,22 @@ export const SESSION_VIEW_ID = 'session-view';
*/
export const SessionView: FC = () => {
const { sessionView, telemetry } = useKibana().services;
- const { getFieldsData, indexName, scopeId, dataFormattedForFieldBrowser } =
- useDocumentDetailsContext();
+ const {
+ eventId,
+ indexName,
+ getFieldsData,
+ scopeId,
+ dataFormattedForFieldBrowser,
+ jumpToEntityId,
+ jumpToCursor,
+ } = useDocumentDetailsContext();
+
+ const { canReadPolicyManagement } = useUserPrivileges().endpointPrivileges;
- const sessionViewConfig = useSessionPreview({ getFieldsData, dataFormattedForFieldBrowser });
+ const sessionViewConfig = useSessionViewConfig({ getFieldsData, dataFormattedForFieldBrowser });
const isEnterprisePlus = useLicense().isEnterprise();
const isEnabled = sessionViewConfig && isEnterprisePlus;
- const ancestorIndex = getField(getFieldsData(ANCESTOR_INDEX)); // e.g in case of alert, we want to grab it's origin index
- const sessionEntityId = getField(getFieldsData(ENTRY_LEADER_ENTITY_ID)) || '';
- const sessionStartTime = getField(getFieldsData(ENTRY_LEADER_START)) || '';
- const index = ancestorIndex || indexName;
-
const sourcererScope = useMemo(() => {
if (isActiveTimeline(scopeId)) {
return SourcererScopeName.timeline;
@@ -64,17 +68,24 @@ export const SessionView: FC = () => {
const { openPreviewPanel } = useExpandableFlyoutApi();
const openAlertDetailsPreview = useCallback(
- (eventId?: string, onClose?: () => void) => {
- openPreviewPanel({
- id: DocumentDetailsPreviewPanelKey,
- params: {
- id: eventId,
- indexName: eventDetailsIndex,
- scopeId,
- banner: ALERT_PREVIEW_BANNER,
- isPreviewMode: true,
- },
- });
+ (evtId?: string, onClose?: () => void) => {
+ // In the SessionView component, when the user clicks on the
+ // expand button to open a alert in the preview panel, this actually also selects the row and opens
+ // the detailed panel in preview.
+ // In order to NOT modify the SessionView code, the setTimeout here guarantees that the alert details preview
+ // will be opened in second, so that we have a correct order in the opened preview panels
+ setTimeout(() => {
+ openPreviewPanel({
+ id: DocumentDetailsPreviewPanelKey,
+ params: {
+ id: evtId,
+ indexName: eventDetailsIndex,
+ scopeId,
+ banner: ALERT_PREVIEW_BANNER,
+ isPreviewMode: true,
+ },
+ });
+ }, 100);
telemetry.reportEvent(DocumentEventTypes.DetailsFlyoutOpened, {
location: scopeId,
panel: 'preview',
@@ -83,14 +94,59 @@ export const SessionView: FC = () => {
[openPreviewPanel, eventDetailsIndex, scopeId, telemetry]
);
+ const openDetails = useCallback(
+ (selectedProcess: Process | null) => {
+ // We cannot pass the original Process object sent from the SessionView component
+ // as it contains functions (that should not put into Redux)
+ // and also some recursive properties (that will break rison.encode when updating the URL)
+ const simplifiedSelectedProcess: CustomProcess | null = selectedProcess
+ ? {
+ id: selectedProcess.id,
+ details: selectedProcess.getDetails(),
+ endTime: selectedProcess.getEndTime(),
+ }
+ : null;
+
+ openPreviewPanel({
+ id: DocumentDetailsSessionViewPanelKey,
+ params: {
+ eventId,
+ indexName,
+ selectedProcess: simplifiedSelectedProcess,
+ index: sessionViewConfig?.index,
+ sessionEntityId: sessionViewConfig?.sessionEntityId,
+ sessionStartTime: sessionViewConfig?.sessionStartTime,
+ investigatedAlertId: sessionViewConfig?.investigatedAlertId,
+ scopeId,
+ jumpToEntityId,
+ jumpToCursor,
+ },
+ });
+ },
+ [
+ openPreviewPanel,
+ eventId,
+ indexName,
+ sessionViewConfig?.index,
+ sessionViewConfig?.sessionEntityId,
+ sessionViewConfig?.sessionStartTime,
+ sessionViewConfig?.investigatedAlertId,
+ scopeId,
+ jumpToEntityId,
+ jumpToCursor,
+ ]
+ );
+
return isEnabled ? (
{sessionView.getSessionView({
- index,
- sessionEntityId,
- sessionStartTime,
+ ...sessionViewConfig,
isFullScreen: true,
loadAlertDetails: openAlertDetailsPreview,
+ openDetails: (selectedProcess: Process | null) => openDetails(selectedProcess),
+ canReadPolicyManagement,
+ resetJumpToEntityId: jumpToEntityId,
+ resetJumpToCursor: jumpToCursor,
})}
) : (
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/session_preview_container.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/session_preview_container.tsx
index 4098b35a0abfc..4072dfbf250f8 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/session_preview_container.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/session_preview_container.tsx
@@ -13,7 +13,7 @@ import { useUiSetting$ } from '@kbn/kibana-react-plugin/public';
import { ENABLE_VISUALIZATIONS_IN_FLYOUT_SETTING } from '../../../../../common/constants';
import { useLicense } from '../../../../common/hooks/use_license';
import { SessionPreview } from './session_preview';
-import { useSessionPreview } from '../hooks/use_session_preview';
+import { useSessionViewConfig } from '../../shared/hooks/use_session_view_config';
import { useInvestigateInTimeline } from '../../../../detections/components/alerts_table/timeline_actions/use_investigate_in_timeline';
import { useDocumentDetailsContext } from '../../shared/context';
import { ALERTS_ACTIONS } from '../../../../common/lib/apm/user_actions';
@@ -47,7 +47,7 @@ export const SessionPreviewContainer: FC = () => {
);
// decide whether to show the session view or not
- const sessionViewConfig = useSessionPreview({ getFieldsData, dataFormattedForFieldBrowser });
+ const sessionViewConfig = useSessionViewConfig({ getFieldsData, dataFormattedForFieldBrowser });
const isEnterprisePlus = useLicense().isEnterprise();
const isEnabled = sessionViewConfig && isEnterprisePlus;
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/session_view/content.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/session_view/content.tsx
new file mode 100644
index 0000000000000..ec6ec077f285d
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/session_view/content.tsx
@@ -0,0 +1,36 @@
+/*
+ * 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 type { FC } from 'react';
+import React, { useMemo } from 'react';
+import type { SessionViewPanelPaths } from '.';
+import type { SessionViewPanelTabType } from './tabs';
+import { FlyoutBody } from '../../shared/components/flyout_body';
+
+export interface PanelContentProps {
+ /**
+ * Id of the tab selected in the parent component to display its content
+ */
+ selectedTabId: SessionViewPanelPaths;
+ /**
+ * Tabs display right below the flyout's header
+ */
+ tabs: SessionViewPanelTabType[];
+}
+
+/**
+ * SessionView preview panel content, that renders the process, metadata and alerts tab contents.
+ */
+export const PanelContent: FC = ({ selectedTabId, tabs }) => {
+ const selectedTabContent = useMemo(() => {
+ return tabs.find((tab) => tab.id === selectedTabId)?.content;
+ }, [selectedTabId, tabs]);
+
+ return {selectedTabContent};
+};
+
+PanelContent.displayName = 'PanelContent';
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/session_view/context.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/session_view/context.tsx
new file mode 100644
index 0000000000000..6dd2e878957ce
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/session_view/context.tsx
@@ -0,0 +1,147 @@
+/*
+ * 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, { createContext, memo, useContext, useMemo } from 'react';
+import type { ProcessEvent } from '@kbn/session-view-plugin/common';
+import { FlyoutError } from '../../shared/components/flyout_error';
+import type { SessionViewPanelProps } from '.';
+
+export interface CustomProcess {
+ /**
+ * Id of the process
+ */
+ id: string;
+ /**
+ * Details of the process (see implementation under getDetailsMemo here: x-pack/plugins/session_view/public/components/process_tree/hooks.ts)
+ */
+ details: ProcessEvent;
+ /**
+ * Timestamp of the 'end' event (see implementation under getEndTime here x-pack/plugins/session_view/public/components/process_tree/hooks.ts)
+ */
+ endTime: string;
+}
+
+export interface SessionViewPanelContext {
+ /**
+ * Id of the document that was initially being investigated in the expandable flyout.
+ * This context needs to store it as it is used within the SessionView preview panel to be able to reopen the left panel with the same document.
+ */
+ eventId: string;
+ /**
+ * Index used when investigating the initial document in the expandable flyout.
+ * This context needs to store it as it is used within the SessionView preview panel to be able to reopen the left panel with the same document.
+ */
+ indexName: string;
+ /**
+ * ScopeId used when investigating the initial document in the expandable flyout.
+ * This context needs to store it as it is used within the SessionView preview panel to be able to reopen the left panel with the same document.
+ */
+ scopeId: string;
+ /**
+ * Store a subset of properties from the SessionView component.
+ * The original object had functions as well as recursive properties, which we should not store in the context.
+ */
+ selectedProcess: CustomProcess | null;
+ /**
+ * index used within the SessionView component
+ */
+ index: string;
+ /**
+ * sessionEntityId value used to correctly render the SessionView component
+ */
+ sessionEntityId: string;
+ /**
+ * sessionStartTime value used to correctly render the SessionView component
+ */
+ sessionStartTime: string;
+ /**
+ * investigatedAlertId value used to correctly render the SessionView component
+ */
+ investigatedAlertId: string;
+}
+
+export const SessionViewPanelContext = createContext(
+ undefined
+);
+
+export type SessionViewPanelProviderProps = {
+ /**
+ * React components to render
+ */
+ children: React.ReactNode;
+} & Partial;
+
+export const SessionViewPanelProvider = memo(
+ ({
+ eventId,
+ indexName,
+ selectedProcess,
+ index,
+ sessionEntityId,
+ sessionStartTime,
+ scopeId,
+ investigatedAlertId,
+ children,
+ }: SessionViewPanelProviderProps) => {
+ const contextValue = useMemo(
+ () =>
+ eventId &&
+ indexName &&
+ selectedProcess &&
+ index &&
+ sessionEntityId &&
+ sessionStartTime &&
+ scopeId &&
+ investigatedAlertId
+ ? {
+ eventId,
+ indexName,
+ selectedProcess,
+ index,
+ sessionEntityId,
+ sessionStartTime,
+ scopeId,
+ investigatedAlertId,
+ }
+ : undefined,
+ [
+ eventId,
+ indexName,
+ selectedProcess,
+ index,
+ sessionEntityId,
+ sessionStartTime,
+ scopeId,
+ investigatedAlertId,
+ ]
+ );
+
+ if (!contextValue) {
+ return ;
+ }
+
+ return (
+
+ {children}
+
+ );
+ }
+);
+
+SessionViewPanelProvider.displayName = 'SessionViewPanelProvider';
+
+export const useSessionViewPanelContext = (): SessionViewPanelContext => {
+ const contextValue = useContext(SessionViewPanelContext);
+
+ if (!contextValue) {
+ throw new Error(
+ 'SessionViewPanelContext can only be used within SessionViewPanelContext provider'
+ );
+ }
+
+ return contextValue;
+};
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/session_view/header.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/session_view/header.tsx
new file mode 100644
index 0000000000000..e1fa7aae991c6
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/session_view/header.tsx
@@ -0,0 +1,59 @@
+/*
+ * 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 type { EuiFlyoutHeader } from '@elastic/eui';
+import { EuiTab } from '@elastic/eui';
+import type { FC } from 'react';
+import React, { memo } from 'react';
+import type { SessionViewPanelTabType } from './tabs';
+import type { SessionViewPanelPaths } from '.';
+import { FlyoutHeader } from '../../shared/components/flyout_header';
+import { FlyoutHeaderTabs } from '../../shared/components/flyout_header_tabs';
+
+export interface PanelHeaderProps extends React.ComponentProps {
+ /**
+ * Id of the tab selected in the parent component to display its content
+ */
+ selectedTabId: SessionViewPanelPaths;
+ /**
+ * Callback to set the selected tab id in the parent component
+ * @param selected
+ */
+ setSelectedTabId: (selected: SessionViewPanelPaths) => void;
+ /**
+ * Tabs to display in the header
+ */
+ tabs: SessionViewPanelTabType[];
+}
+
+/**
+ * Renders the process, metadata and alerts tabs in the SessionView preview panel header.
+ */
+export const PanelHeader: FC = memo(
+ ({ selectedTabId, setSelectedTabId, tabs, ...flyoutHeaderProps }) => {
+ const onSelectedTabChanged = (id: SessionViewPanelPaths) => setSelectedTabId(id);
+
+ const renderTabs = tabs.map((tab, index) => (
+ onSelectedTabChanged(tab.id)}
+ isSelected={tab.id === selectedTabId}
+ key={index}
+ data-test-subj={tab['data-test-subj']}
+ >
+ {tab.name}
+
+ ));
+
+ return (
+
+ {renderTabs}
+
+ );
+ }
+);
+
+PanelHeader.displayName = 'PanelHeader';
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/session_view/index.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/session_view/index.tsx
new file mode 100644
index 0000000000000..ee80994f54563
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/session_view/index.tsx
@@ -0,0 +1,112 @@
+/*
+ * 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 type { FC } from 'react';
+import React, { memo, useCallback, useMemo } from 'react';
+import type { FlyoutPanelProps } from '@kbn/expandable-flyout';
+import { type PanelPath, useExpandableFlyoutApi } from '@kbn/expandable-flyout';
+import { PanelContent } from './content';
+import { PanelHeader } from './header';
+import type { CustomProcess } from './context';
+import { useSessionViewPanelContext } from './context';
+import * as tabs from './tabs';
+import { DocumentDetailsSessionViewPanelKey } from '../shared/constants/panel_keys';
+import type { SessionViewPanelTabType } from './tabs';
+
+export const allTabs = [tabs.processTab, tabs.metadataTab, tabs.alertsTab];
+export type SessionViewPanelPaths = 'process' | 'metadata' | 'alerts';
+
+export interface SessionViewPanelProps extends FlyoutPanelProps {
+ key: typeof DocumentDetailsSessionViewPanelKey;
+ path?: PanelPath;
+ params: {
+ eventId: string;
+ indexName: string;
+ selectedProcess: CustomProcess | null;
+ index: string;
+ sessionEntityId: string;
+ sessionStartTime: string;
+ scopeId: string;
+ investigatedAlertId: string;
+ };
+}
+
+/**
+ * Displays node details panel for session view
+ */
+export const SessionViewPanel: FC> = memo(({ path }) => {
+ const { openPreviewPanel } = useExpandableFlyoutApi();
+ const {
+ eventId,
+ indexName,
+ selectedProcess,
+ index,
+ sessionEntityId,
+ sessionStartTime,
+ scopeId,
+ investigatedAlertId,
+ } = useSessionViewPanelContext();
+
+ const selectedTabId = useMemo(() => {
+ // we use the value passed from the url and use it if it exists in the list of tabs to display
+ if (path) {
+ const selectedTab = allTabs.map((tab) => tab.id).find((tabId) => tabId === path.tab);
+ if (selectedTab) {
+ return selectedTab;
+ }
+ }
+
+ // we default back to the first tab of the list of tabs to display in case everything else has failed
+ const defaultTab = allTabs[0].id;
+ return defaultTab;
+ }, [path]);
+
+ const setSelectedTabId = useCallback(
+ (tabId: SessionViewPanelTabType['id']) => {
+ openPreviewPanel({
+ id: DocumentDetailsSessionViewPanelKey,
+ path: {
+ tab: tabId,
+ },
+ params: {
+ eventId,
+ indexName,
+ selectedProcess,
+ index,
+ sessionEntityId,
+ sessionStartTime,
+ scopeId,
+ investigatedAlertId,
+ },
+ });
+ },
+ [
+ eventId,
+ index,
+ indexName,
+ investigatedAlertId,
+ openPreviewPanel,
+ scopeId,
+ selectedProcess,
+ sessionEntityId,
+ sessionStartTime,
+ ]
+ );
+
+ return (
+ <>
+
+
+ >
+ );
+});
+
+SessionViewPanel.displayName = 'SessionViewPanel';
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/session_view/tabs.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/session_view/tabs.tsx
new file mode 100644
index 0000000000000..e536448840837
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/session_view/tabs.tsx
@@ -0,0 +1,58 @@
+/*
+ * 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 type { ReactElement } from 'react';
+import React from 'react';
+import { FormattedMessage } from '@kbn/i18n-react';
+import { ProcessTab } from './tabs/process_tab';
+import { MetadataTab } from './tabs/metadata_tab';
+import { AlertsTab } from './tabs/alerts_tab';
+import { ALERTS_TAB_TEST_ID, METADATA_TAB_TEST_ID, PROCESS_TAB_TEST_ID } from './test_ids';
+import type { SessionViewPanelPaths } from '.';
+
+export interface SessionViewPanelTabType {
+ id: SessionViewPanelPaths;
+ name: ReactElement;
+ content: React.ReactElement;
+ 'data-test-subj': string;
+}
+
+export const processTab: SessionViewPanelTabType = {
+ id: 'process',
+ 'data-test-subj': PROCESS_TAB_TEST_ID,
+ name: (
+
+ ),
+ content: ,
+};
+
+export const metadataTab: SessionViewPanelTabType = {
+ id: 'metadata',
+ 'data-test-subj': METADATA_TAB_TEST_ID,
+ name: (
+
+ ),
+ content: ,
+};
+
+export const alertsTab: SessionViewPanelTabType = {
+ id: 'alerts',
+ 'data-test-subj': ALERTS_TAB_TEST_ID,
+ name: (
+
+ ),
+ content: ,
+};
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/session_view/tabs/alerts_tab.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/session_view/tabs/alerts_tab.tsx
new file mode 100644
index 0000000000000..7bb55d43c5a0b
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/session_view/tabs/alerts_tab.tsx
@@ -0,0 +1,124 @@
+/*
+ * 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, { memo, useCallback, useMemo } from 'react';
+import { EuiPanel } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { DetailPanelAlertTab, useFetchSessionViewAlerts } from '@kbn/session-view-plugin/public';
+import type { ProcessEvent } from '@kbn/session-view-plugin/common';
+import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
+import { SESSION_VIEW_ID } from '../../left/components/session_view';
+import {
+ DocumentDetailsLeftPanelKey,
+ DocumentDetailsPreviewPanelKey,
+} from '../../shared/constants/panel_keys';
+import { ALERT_PREVIEW_BANNER } from '../../preview/constants';
+import { useSessionViewPanelContext } from '../context';
+
+/**
+ * Tab displayed in the SessionView preview panel, shows alerts related to the session.
+ */
+export const AlertsTab = memo(() => {
+ const { eventId, indexName, investigatedAlertId, sessionEntityId, sessionStartTime, scopeId } =
+ useSessionViewPanelContext();
+ const {
+ data: alertsData,
+ fetchNextPage: fetchNextPageAlerts,
+ isFetching: isFetchingAlerts,
+ hasNextPage: hasNextPageAlerts,
+ } = useFetchSessionViewAlerts(sessionEntityId, sessionStartTime, undefined);
+
+ // this code mimics what is being done in the x-pack/plugins/session_view/public/components/session_view/index.tsx file
+ const alerts = useMemo(() => {
+ let events: ProcessEvent[] = [];
+
+ if (alertsData) {
+ alertsData.pages.forEach((page) => {
+ events = events.concat(page.events);
+ });
+ }
+
+ return events;
+ }, [alertsData]);
+
+ const { openPreviewPanel, openLeftPanel } = useExpandableFlyoutApi();
+ const openAlertDetailsPreview = useCallback(
+ (evtId?: string, onClose?: () => void) => {
+ openPreviewPanel({
+ id: DocumentDetailsPreviewPanelKey,
+ params: {
+ id: evtId,
+ indexName,
+ scopeId,
+ banner: ALERT_PREVIEW_BANNER,
+ isPreviewMode: true,
+ },
+ });
+ },
+ [openPreviewPanel, indexName, scopeId]
+ );
+
+ // this code mimics what is being done in the x-pack/plugins/session_view/public/components/session_view/index.tsx file
+ const jumpToEvent = useCallback(
+ (event: ProcessEvent) => {
+ let jumpToEntityId = null;
+ let jumpToCursor = null;
+ if (event.process) {
+ const { entity_id: entityId } = event.process;
+ if (entityId !== sessionEntityId) {
+ const alert = event.kibana?.alert;
+ const cursor = alert ? alert?.original_time : event['@timestamp'];
+
+ if (cursor) {
+ jumpToEntityId = entityId;
+ jumpToCursor = cursor;
+ }
+ }
+ }
+
+ openLeftPanel({
+ id: DocumentDetailsLeftPanelKey,
+ params: {
+ id: eventId,
+ indexName,
+ scopeId,
+ jumpToEntityId,
+ jumpToCursor,
+ },
+ path: {
+ tab: 'visualize',
+ subTab: SESSION_VIEW_ID,
+ },
+ });
+ },
+ [eventId, indexName, openLeftPanel, scopeId, sessionEntityId]
+ );
+
+ return (
+
+
+
+ );
+});
+
+AlertsTab.displayName = 'AlertsTab';
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/session_view/tabs/metadata_tab.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/session_view/tabs/metadata_tab.tsx
new file mode 100644
index 0000000000000..2d4486bf7fa68
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/session_view/tabs/metadata_tab.tsx
@@ -0,0 +1,40 @@
+/*
+ * 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, { memo } from 'react';
+import { EuiPanel } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { DetailPanelMetadataTab } from '@kbn/session-view-plugin/public';
+import { useSessionViewPanelContext } from '../context';
+
+/**
+ * Tab displayed in the SessionView preview panel, shows metadata related process selected in the SessionView tree.
+ */
+export const MetadataTab = memo(() => {
+ const { selectedProcess } = useSessionViewPanelContext();
+
+ return (
+
+
+
+ );
+});
+
+MetadataTab.displayName = 'MetadataTab';
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/session_view/tabs/process_tab.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/session_view/tabs/process_tab.tsx
new file mode 100644
index 0000000000000..b5117a2be0f6f
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/session_view/tabs/process_tab.tsx
@@ -0,0 +1,50 @@
+/*
+ * 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, { memo, useMemo } from 'react';
+import { EuiPanel } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { DetailPanelProcessTab } from '@kbn/session-view-plugin/public';
+import type { Process } from '@kbn/session-view-plugin/common';
+import { useSessionViewPanelContext } from '../context';
+
+/**
+ * Tab displayed in the SessionView preview panel, shows the details related to the process selected in the SessionView tree.
+ */
+export const ProcessTab = memo(() => {
+ const { selectedProcess, index } = useSessionViewPanelContext();
+
+ // We need to partially recreate the Process object here, as the SessionView code
+ // is expecting a Process object with at least the following properties
+ const process: Process | null = useMemo(
+ () =>
+ selectedProcess
+ ? ({
+ getDetails: () => selectedProcess.details,
+ id: selectedProcess.id,
+ getEndTime: () => selectedProcess.endTime,
+ } as Process)
+ : null,
+ [selectedProcess]
+ );
+
+ return (
+
+
+
+ );
+});
+
+ProcessTab.displayName = 'ProcessTab';
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/session_view/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/document_details/session_view/test_ids.ts
new file mode 100644
index 0000000000000..c9f19314ef450
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/session_view/test_ids.ts
@@ -0,0 +1,12 @@
+/*
+ * 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 { PREFIX } from '../../shared/test_ids';
+
+export const PROCESS_TAB_TEST_ID = `${PREFIX}ProcessTab` as const;
+export const METADATA_TAB_TEST_ID = `${PREFIX}MetadataTab` as const;
+export const ALERTS_TAB_TEST_ID = `${PREFIX}AlertsTab` as const;
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/constants/panel_keys.ts b/x-pack/plugins/security_solution/public/flyout/document_details/shared/constants/panel_keys.ts
index fa40f1e0e6674..e68313ed6707e 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/constants/panel_keys.ts
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/constants/panel_keys.ts
@@ -12,3 +12,4 @@ export const DocumentDetailsPreviewPanelKey = 'document-details-preview' as cons
export const DocumentDetailsIsolateHostPanelKey = 'document-details-isolate-host' as const;
export const DocumentDetailsAlertReasonPanelKey = 'document-details-alert-reason' as const;
export const DocumentDetailsAnalyzerPanelKey = 'document-details-analyzer-details' as const;
+export const DocumentDetailsSessionViewPanelKey = 'document-details-sessions-view-details' as const;
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/context.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/context.tsx
index 12e2ad4f2a0b6..72da35f9286b2 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/context.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/context.tsx
@@ -67,6 +67,14 @@ export interface DocumentDetailsContext {
* Boolean to indicate whether it is a preview panel
*/
isPreviewMode: boolean;
+ /**
+ * To allow communication between the SessionView in the left panel and its preview panels
+ */
+ jumpToEntityId?: string;
+ /**
+ * To allow communication between the SessionView in the left panel and its preview panels
+ */
+ jumpToCursor?: string;
}
/**
@@ -82,7 +90,15 @@ export type DocumentDetailsProviderProps = {
} & Partial;
export const DocumentDetailsProvider = memo(
- ({ id, indexName, scopeId, isPreviewMode, children }: DocumentDetailsProviderProps) => {
+ ({
+ id,
+ indexName,
+ scopeId,
+ jumpToEntityId,
+ jumpToCursor,
+ isPreviewMode,
+ children,
+ }: DocumentDetailsProviderProps) => {
const {
browserFields,
dataAsNestedObject,
@@ -117,20 +133,24 @@ export const DocumentDetailsProvider = memo(
getFieldsData,
isPreview: scopeId === TableId.rulePreview,
isPreviewMode: Boolean(isPreviewMode),
+ jumpToEntityId,
+ jumpToCursor,
}
: undefined,
[
id,
- maybeRule,
indexName,
scopeId,
- browserFields,
dataAsNestedObject,
dataFormattedForFieldBrowser,
searchHit,
+ browserFields,
+ maybeRule?.investigation_fields?.field_names,
refetchFlyoutData,
getFieldsData,
isPreviewMode,
+ jumpToEntityId,
+ jumpToCursor,
]
);
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_session_preview.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_session_view_config.test.tsx
similarity index 79%
rename from x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_session_preview.test.tsx
rename to x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_session_view_config.test.tsx
index 4e2e19c6b54fa..11cb97548e94c 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_session_preview.test.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_session_view_config.test.tsx
@@ -7,15 +7,15 @@
import type { RenderHookResult } from '@testing-library/react';
import { renderHook } from '@testing-library/react';
-import type { UseSessionPreviewParams } from './use_session_preview';
-import { useSessionPreview } from './use_session_preview';
+import type { UseSessionViewConfigParams } from './use_session_view_config';
+import { useSessionViewConfig } from './use_session_view_config';
import type { SessionViewConfig } from '@kbn/securitysolution-data-table/common/types';
-import type { GetFieldsData } from '../../shared/hooks/use_get_fields_data';
-import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser';
-import { mockFieldData, mockGetFieldsData } from '../../shared/mocks/mock_get_fields_data';
+import type { GetFieldsData } from './use_get_fields_data';
+import { mockDataFormattedForFieldBrowser } from '../mocks/mock_data_formatted_for_field_browser';
+import { mockFieldData, mockGetFieldsData } from '../mocks/mock_get_fields_data';
-describe('useSessionPreview', () => {
- let hookResult: RenderHookResult;
+describe('useSessionViewConfig', () => {
+ let hookResult: RenderHookResult;
it(`should return a session view config object if alert ancestor index is available`, () => {
const getFieldsData: GetFieldsData = (field: string) => {
@@ -36,7 +36,7 @@ describe('useSessionPreview', () => {
},
];
- hookResult = renderHook((props: UseSessionPreviewParams) => useSessionPreview(props), {
+ hookResult = renderHook((props: UseSessionViewConfigParams) => useSessionViewConfig(props), {
initialProps: {
getFieldsData,
dataFormattedForFieldBrowser,
@@ -71,7 +71,7 @@ describe('useSessionPreview', () => {
isObjectArray: false,
},
];
- hookResult = renderHook((props: UseSessionPreviewParams) => useSessionPreview(props), {
+ hookResult = renderHook((props: UseSessionViewConfigParams) => useSessionViewConfig(props), {
initialProps: {
getFieldsData: mockGetFieldsData,
dataFormattedForFieldBrowser,
@@ -91,7 +91,7 @@ describe('useSessionPreview', () => {
it(`should return null if data isn't ready for session view`, () => {
const getFieldsData: GetFieldsData = (field: string) => '';
- hookResult = renderHook((props: UseSessionPreviewParams) => useSessionPreview(props), {
+ hookResult = renderHook((props: UseSessionViewConfigParams) => useSessionViewConfig(props), {
initialProps: {
getFieldsData,
dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser,
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_session_preview.ts b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_session_view_config.ts
similarity index 82%
rename from x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_session_preview.ts
rename to x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_session_view_config.ts
index 4b2132d265871..891b550c1aee8 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_session_preview.ts
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_session_view_config.ts
@@ -7,11 +7,11 @@
import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common';
import type { SessionViewConfig } from '@kbn/securitysolution-data-table/common/types';
-import type { GetFieldsData } from '../../shared/hooks/use_get_fields_data';
-import { getField } from '../../shared/utils';
-import { useBasicDataFromDetailsData } from '../../shared/hooks/use_basic_data_from_details_data';
+import type { GetFieldsData } from './use_get_fields_data';
+import { getField } from '../utils';
+import { useBasicDataFromDetailsData } from './use_basic_data_from_details_data';
-export interface UseSessionPreviewParams {
+export interface UseSessionViewConfigParams {
/**
* Retrieves searchHit values for the provided field
*/
@@ -25,10 +25,10 @@ export interface UseSessionPreviewParams {
/**
* Hook that returns the session view configuration if the session view is available for the alert
*/
-export const useSessionPreview = ({
+export const useSessionViewConfig = ({
getFieldsData,
dataFormattedForFieldBrowser,
-}: UseSessionPreviewParams): SessionViewConfig | null => {
+}: UseSessionViewConfigParams): SessionViewConfig | null => {
const { indexName: _index, alertId: _id } = useBasicDataFromDetailsData(
dataFormattedForFieldBrowser
);
diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/types.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/types.tsx
index 00fb1da32449c..bc32e3e5b33bb 100644
--- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/types.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/types.tsx
@@ -19,5 +19,7 @@ export interface DocumentDetailsProps extends FlyoutPanelProps {
indexName: string;
scopeId: string;
isPreviewMode?: boolean;
+ jumpToEntityId?: string;
+ jumpToCursor?: string;
};
}
diff --git a/x-pack/plugins/security_solution/public/flyout/index.tsx b/x-pack/plugins/security_solution/public/flyout/index.tsx
index 64fac23dfa98e..fd83f121265a4 100644
--- a/x-pack/plugins/security_solution/public/flyout/index.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/index.tsx
@@ -8,6 +8,9 @@
import React, { memo, useCallback } from 'react';
import { ExpandableFlyout, type ExpandableFlyoutProps } from '@kbn/expandable-flyout';
import { useEuiTheme } from '@elastic/eui';
+import { SessionViewPanelProvider } from './document_details/session_view/context';
+import type { SessionViewPanelProps } from './document_details/session_view';
+import { SessionViewPanel } from './document_details/session_view';
import type { NetworkExpandableFlyoutProps } from './network_details';
import { Flyouts } from './document_details/shared/constants/flyouts';
import {
@@ -17,6 +20,7 @@ import {
DocumentDetailsPreviewPanelKey,
DocumentDetailsAlertReasonPanelKey,
DocumentDetailsAnalyzerPanelKey,
+ DocumentDetailsSessionViewPanelKey,
} from './document_details/shared/constants/panel_keys';
import type { IsolateHostPanelProps } from './document_details/isolate_host';
import { IsolateHostPanel } from './document_details/isolate_host';
@@ -104,6 +108,14 @@ const expandableFlyoutDocumentsPanels: ExpandableFlyoutProps['registeredPanels']
),
},
+ {
+ key: DocumentDetailsSessionViewPanelKey,
+ component: (props) => (
+
+
+
+ ),
+ },
{
key: UserPanelKey,
component: (props) => ,
diff --git a/x-pack/plugins/security_solution/public/resolver/view/controls/show_panel.tsx b/x-pack/plugins/security_solution/public/resolver/view/controls/show_panel.tsx
index 72fa6c925f680..9a63ea3c233a1 100644
--- a/x-pack/plugins/security_solution/public/resolver/view/controls/show_panel.tsx
+++ b/x-pack/plugins/security_solution/public/resolver/view/controls/show_panel.tsx
@@ -27,7 +27,7 @@ export const ShowPanelButton = memo(({ showPanelOnClick }: { showPanelOnClick: (
title={showPanelButtonTitle}
aria-label={showPanelButtonTitle}
onClick={showPanelOnClick}
- iconType={'eye'}
+ iconType={'expand'}
$backgroundColor={colorMap.graphControlsBackground}
$iconColor={colorMap.graphControls}
$borderColor={colorMap.graphControlsBorderColor}
diff --git a/x-pack/solutions/security/plugins/session_view/public/components/process_tree_node/index.tsx b/x-pack/solutions/security/plugins/session_view/public/components/process_tree_node/index.tsx
index a6201ea2ed504..b493591b94370 100644
--- a/x-pack/solutions/security/plugins/session_view/public/components/process_tree_node/index.tsx
+++ b/x-pack/solutions/security/plugins/session_view/public/components/process_tree_node/index.tsx
@@ -316,7 +316,12 @@ export function ProcessTreeNode({
-
+
diff --git a/x-pack/solutions/security/plugins/session_view/public/components/session_view/index.tsx b/x-pack/solutions/security/plugins/session_view/public/components/session_view/index.tsx
index bdb7e3ddc8c2b..bf6cf9e7c67f3 100644
--- a/x-pack/solutions/security/plugins/session_view/public/components/session_view/index.tsx
+++ b/x-pack/solutions/security/plugins/session_view/public/components/session_view/index.tsx
@@ -62,6 +62,9 @@ export const SessionView = ({
loadAlertDetails,
canReadPolicyManagement,
trackEvent,
+ openDetails,
+ resetJumpToEntityId,
+ resetJumpToCursor,
}: SessionViewDeps & { trackEvent: (name: SessionViewTelemetryKey) => void }) => {
// don't engage jumpTo if jumping to session leader.
if (jumpToEntityId === sessionEntityId) {
@@ -114,9 +117,18 @@ export const SessionView = ({
return !!(!displayOptions?.verboseMode && searchQuery && searchResults?.length === 0);
}, [displayOptions?.verboseMode, searchResults, searchQuery]);
- const onProcessSelected = useCallback((process: Process | null) => {
- setSelectedProcess(process);
- }, []);
+ const onProcessSelected = useCallback(
+ (process: Process | null) => {
+ setSelectedProcess(process);
+
+ // used when SessionView is displayed in the expandable flyout
+ // This refreshes the detailed panel rendered in the flyout preview panel
+ if (openDetails) {
+ openDetails(process);
+ }
+ },
+ [openDetails]
+ );
const onJumpToEvent = useCallback(
(event: ProcessEvent) => {
@@ -220,6 +232,19 @@ export const SessionView = ({
fetchAlertStatus[0] ?? ''
);
+ /**
+ * This useEffect should only impact the SessionView component when displayed in the expandable flyout.
+ * The SessionView tree and its detailed panel are separated and this allows the detailed panel to reset the
+ * view of the tree from the preview panel.
+ */
+ useEffect(() => {
+ if (resetJumpToEntityId && resetJumpToCursor) {
+ setSelectedProcess(null);
+ setCurrentJumpToEntityId(resetJumpToEntityId);
+ setCurrentJumpToCursor(resetJumpToCursor);
+ }
+ }, [resetJumpToCursor, resetJumpToEntityId]);
+
useEffect(() => {
if (newUpdatedAlertsStatus) {
setUpdatedAlertsStatus({ ...newUpdatedAlertsStatus });
@@ -261,6 +286,12 @@ export const SessionView = ({
}
}, [isDetailOpen, trackEvent]);
+ const toggleDetailPanelInFlyout = useCallback(() => {
+ if (openDetails) {
+ openDetails(selectedProcess);
+ }
+ }, [openDetails, selectedProcess]);
+
const onShowAlertDetails = useCallback(
(alertUuid: string) => {
if (loadAlertDetails) {
@@ -294,6 +325,86 @@ export const SessionView = ({
[displayOptions?.timestamp, displayOptions?.verboseMode, setDisplayOptions, trackEvent]
);
+ const errorEmptyPrompt = useMemo(
+ () =>
+ hasError ? (
+
+
+
+ }
+ body={
+
+
+
+ }
+ />
+ ) : null,
+ [hasError]
+ );
+
+ const processTree = useMemo(
+ () =>
+ hasData ? (
+
+ ) : null,
+ [
+ currentJumpToCursor,
+ currentJumpToEntityId,
+ data?.pages,
+ displayOptions?.timestamp,
+ displayOptions?.verboseMode,
+ fetchNextPage,
+ fetchPreviousPage,
+ hasData,
+ hasNextPage,
+ hasPreviousPage,
+ investigatedAlertId,
+ isFetching,
+ onJumpToOutput,
+ onProcessSelected,
+ onShowAlertDetails,
+ searchQuery,
+ selectedProcess,
+ sessionEntityId,
+ styles.processTree,
+ trackEvent,
+ updatedAlertsStatus,
+ ]
+ );
+
if (renderIsLoading) {
return (
@@ -390,103 +501,66 @@ export const SessionView = ({
-
- {DETAIL_PANEL}
-
+ {openDetails ? (
+
+ ) : (
+
+ {DETAIL_PANEL}
+
+ )}
-
- {(EuiResizablePanel, EuiResizableButton, { togglePanel }) => {
- detailPanelCollapseFn.current = () => {
- togglePanel?.(sessionViewId, { direction: 'left' });
- };
-
- return (
- <>
-
- {hasError ? (
-
-
-
- }
- body={
-
-
-
- }
+ {openDetails ? (
+ <>
+ {errorEmptyPrompt}
+ {processTree}
+ >
+ ) : (
+
+ {(EuiResizablePanel, EuiResizableButton, { togglePanel }) => {
+ detailPanelCollapseFn.current = () => {
+ togglePanel?.(sessionViewId, { direction: 'left' });
+ };
+
+ return (
+ <>
+
+ {errorEmptyPrompt}
+ {processTree}
+
+
+
+
- ) : null}
-
- {hasData && (
-
- )}
-
-
-
-
-
-
- >
- );
- }}
-
+
+ >
+ );
+ }}
+
+ )}
void
) => void;
canReadPolicyManagement?: boolean;
+ /**
+ * Function used when SessionView is used within the expandable flyout, where the tree and the detailed panel are
+ * separated and need to communicate with each other.
+ */
+ openDetails?: (selectedProcess: Process | null) => void;
+ /**
+ *
+ */
+ resetJumpToEntityId?: string;
+ /**
+ *
+ */
+ resetJumpToCursor?: string;
}
export interface EuiTabProps {