From eaa631857a2f03332a41dbd072f7f41606752ade Mon Sep 17 00:00:00 2001 From: PhilippeOberti Date: Mon, 16 Sep 2024 10:54:52 -0500 Subject: [PATCH] wip --- .../left/components/session_view.tsx | 34 ++- .../document_details/session_view/content.tsx | 37 +++ .../document_details/session_view/context.tsx | 79 +++++++ .../document_details/session_view/header.tsx | 56 +++++ .../document_details/session_view/index.tsx | 89 ++++++++ .../document_details/session_view/tabs.tsx | 58 +++++ .../session_view/tabs/alerts_tab.tsx | 121 ++++++++++ .../session_view/tabs/metadata_tab.tsx | 40 ++++ .../session_view/tabs/process_tab.tsx | 35 +++ .../document_details/session_view/test_ids.ts | 12 + .../shared/constants/panel_keys.ts | 1 + .../document_details/shared/context.tsx | 22 +- .../flyout/document_details/shared/types.tsx | 2 + .../security_solution/public/flyout/index.tsx | 12 + x-pack/plugins/session_view/kibana.jsonc | 2 +- .../detail_panel_process_tab/index.tsx | 4 + .../public/components/process_tree/hooks.ts | 2 +- .../components/process_tree_node/index.tsx | 7 +- .../public/components/session_view/index.tsx | 214 +++++++++++------- .../session_view_detail_panel/index.tsx | 16 +- x-pack/plugins/session_view/public/index.ts | 5 + .../session_view/public/methods/index.tsx | 15 ++ x-pack/plugins/session_view/public/plugin.ts | 5 +- x-pack/plugins/session_view/public/types.ts | 4 + .../server/routes/process_events_route.ts | 42 ++-- 25 files changed, 796 insertions(+), 118 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/flyout/document_details/session_view/content.tsx create mode 100644 x-pack/plugins/security_solution/public/flyout/document_details/session_view/context.tsx create mode 100644 x-pack/plugins/security_solution/public/flyout/document_details/session_view/header.tsx create mode 100644 x-pack/plugins/security_solution/public/flyout/document_details/session_view/index.tsx create mode 100644 x-pack/plugins/security_solution/public/flyout/document_details/session_view/tabs.tsx create mode 100644 x-pack/plugins/security_solution/public/flyout/document_details/session_view/tabs/alerts_tab.tsx create mode 100644 x-pack/plugins/security_solution/public/flyout/document_details/session_view/tabs/metadata_tab.tsx create mode 100644 x-pack/plugins/security_solution/public/flyout/document_details/session_view/tabs/process_tab.tsx create mode 100644 x-pack/plugins/security_solution/public/flyout/document_details/session_view/test_ids.ts 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..6e2563fe90ced 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,6 +10,7 @@ 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 type { Process } from '@kbn/session-view-plugin/common'; import { ANCESTOR_INDEX, ENTRY_LEADER_ENTITY_ID, @@ -19,7 +20,10 @@ import { getField } from '../../shared/utils'; 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'; @@ -37,8 +41,14 @@ export const SESSION_VIEW_ID = 'session-view'; */ export const SessionView: FC = () => { const { sessionView, telemetry } = useKibana().services; - const { getFieldsData, indexName, scopeId, dataFormattedForFieldBrowser } = - useDocumentDetailsContext(); + const { + getFieldsData, + indexName, + scopeId, + dataFormattedForFieldBrowser, + jumpToEntityId, + jumpToCursor, + } = useDocumentDetailsContext(); const sessionViewConfig = useSessionPreview({ getFieldsData, dataFormattedForFieldBrowser }); const isEnterprisePlus = useLicense().isEnterprise(); @@ -83,14 +93,32 @@ export const SessionView: FC = () => { [openPreviewPanel, eventDetailsIndex, scopeId, telemetry] ); + const openDetails = useCallback( + (selectedProcess: Process | null) => + openPreviewPanel({ + id: DocumentDetailsSessionViewPanelKey, + params: { + selectedProcess, + index, + sessionEntityId, + sessionStartTime, + scopeId, + }, + }), + [openPreviewPanel, index, sessionEntityId, sessionStartTime, scopeId] + ); + return isEnabled ? (
{sessionView.getSessionView({ index, sessionEntityId, sessionStartTime, + jumpToEntityId, + jumpToCursor, isFullScreen: true, loadAlertDetails: openAlertDetailsPreview, + openDetails: (selectedProcess: Process | null) => openDetails(selectedProcess), })}
) : ( 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..8cb60ccc1a697 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/session_view/content.tsx @@ -0,0 +1,37 @@ +/* + * 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[]; +} + +/** + * Document details expandable flyout right section, that will display the content + * of the overview, table and json tabs. + */ +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..85ad5269d75b4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/session_view/context.tsx @@ -0,0 +1,79 @@ +/* + * 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 { Process } from '@kbn/session-view-plugin/common'; +import { FlyoutError } from '../../shared/components/flyout_error'; +import type { SessionViewPanelProps } from '.'; + +export interface SessionViewPanelContext { + selectedProcess: Process | null; + index: string; + sessionEntityId: string; + sessionStartTime: string; + scopeId: string; +} + +export const SessionViewPanelContext = createContext( + undefined +); + +export type SessionViewPanelProviderProps = { + /** + * React components to render + */ + children: React.ReactNode; +} & Partial; + +export const SessionViewPanelProvider = memo( + ({ + selectedProcess, + index, + sessionEntityId, + sessionStartTime, + scopeId, + children, + }: SessionViewPanelProviderProps) => { + const contextValue = useMemo( + () => + selectedProcess && index && sessionEntityId && sessionStartTime && scopeId + ? { + selectedProcess, + index, + sessionEntityId, + sessionStartTime, + scopeId, + } + : undefined, + [selectedProcess, index, sessionEntityId, sessionStartTime, scopeId] + ); + + 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..81574f16a18e1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/session_view/header.tsx @@ -0,0 +1,56 @@ +/* + * 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[]; +} + +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..4a39b522188a9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/session_view/index.tsx @@ -0,0 +1,89 @@ +/* + * 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 { useCallback } from 'react'; +import React, { memo, useMemo } from 'react'; +import type { FlyoutPanelProps } from '@kbn/expandable-flyout'; +import { type PanelPath, useExpandableFlyoutApi } from '@kbn/expandable-flyout'; +import type { Process } from '@kbn/session-view-plugin/common'; +import { PanelContent } from './content'; +import { PanelHeader } from './header'; +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: { + selectedProcess: Process | null; + index: string; + sessionEntityId: string; + sessionStartTime: string; + scopeId: string; + }; +} + +/** + * Displays node details panel for session view + */ +export const SessionViewPanel: FC> = memo(({ path }) => { + const { openPreviewPanel } = useExpandableFlyoutApi(); + const { selectedProcess, index, sessionEntityId, sessionStartTime, scopeId } = + 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: { + selectedProcess, + index, + sessionEntityId, + sessionStartTime, + scopeId, + }, + }); + }, + [index, openPreviewPanel, 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..269d55a60ccfe --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/session_view/tabs/alerts_tab.tsx @@ -0,0 +1,121 @@ +/* + * 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'; + +/** + * + */ +export const AlertsTab = memo(() => { + const { index, sessionEntityId, sessionStartTime, scopeId } = useSessionViewPanelContext(); + const { + data: alertsData, + fetchNextPage: fetchNextPageAlerts, + isFetching: isFetchingAlerts, + hasNextPage: hasNextPageAlerts, + } = useFetchSessionViewAlerts(sessionEntityId, sessionStartTime, undefined); + + 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( + (eventId?: string, onClose?: () => void) => { + openPreviewPanel({ + id: DocumentDetailsPreviewPanelKey, + params: { + id: eventId, + indexName: index, + scopeId, + banner: ALERT_PREVIEW_BANNER, + isPreviewMode: true, + }, + }); + }, + [openPreviewPanel, index, scopeId] + ); + + 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: event.kibana?.alert?.uuid, + indexName: index, + scopeId, + jumpToEntityId, + jumpToCursor, + }, + path: { + tab: 'visualize', + subTab: SESSION_VIEW_ID, + }, + }); + }, + [index, 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..f4212abc614b0 --- /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'; + +/** + * + */ +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..3a16222821eb9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/session_view/tabs/process_tab.tsx @@ -0,0 +1,35 @@ +/* + * 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 { DetailPanelProcessTab } from '@kbn/session-view-plugin/public'; +import { useSessionViewPanelContext } from '../context'; + +/** + * + */ +export const ProcessTab = memo(() => { + const { selectedProcess, index } = useSessionViewPanelContext(); + + 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..756e112eee324 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; + /** + * + */ + jumpToEntityId: string | undefined; + /** + * + */ + jumpToCursor: string | undefined; } /** @@ -82,7 +90,15 @@ export type DocumentDetailsProviderProps = { } & Partial; export const DocumentDetailsProvider = memo( - ({ id, indexName, scopeId, isPreviewMode, children }: DocumentDetailsProviderProps) => { + ({ + id, + indexName, + scopeId, + isPreviewMode, + jumpToEntityId, + jumpToCursor, + children, + }: DocumentDetailsProviderProps) => { const { browserFields, dataAsNestedObject, @@ -117,6 +133,8 @@ export const DocumentDetailsProvider = memo( getFieldsData, isPreview: scopeId === TableId.rulePreview, isPreviewMode: Boolean(isPreviewMode), + jumpToEntityId, + jumpToCursor, } : undefined, [ @@ -131,6 +149,8 @@ export const DocumentDetailsProvider = memo( refetchFlyoutData, getFieldsData, isPreviewMode, + jumpToEntityId, + jumpToCursor, ] ); 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 ab5c874898ef0..a996d52f5fada 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/session_view/kibana.jsonc b/x-pack/plugins/session_view/kibana.jsonc index 3ec03862e6af9..f7f5e1b9a8c26 100644 --- a/x-pack/plugins/session_view/kibana.jsonc +++ b/x-pack/plugins/session_view/kibana.jsonc @@ -23,4 +23,4 @@ "esUiShared" ] } -} \ No newline at end of file +} diff --git a/x-pack/plugins/session_view/public/components/detail_panel_process_tab/index.tsx b/x-pack/plugins/session_view/public/components/detail_panel_process_tab/index.tsx index 13157546ccdb8..910f50a29337f 100644 --- a/x-pack/plugins/session_view/public/components/detail_panel_process_tab/index.tsx +++ b/x-pack/plugins/session_view/public/components/detail_panel_process_tab/index.tsx @@ -72,6 +72,10 @@ const LEADER_FIELD_PREFIX = [ * Detail panel in the session view. */ export const DetailPanelProcessTab = ({ selectedProcess, index }: DetailPanelProcessTabDeps) => { + // console.group('DetailPanelProcessTab'); + // console.log('selectedProcess', selectedProcess); + // console.log('index', index); + // console.groupEnd(); const styles = useStyles(); const processDetail = useMemo( diff --git a/x-pack/plugins/session_view/public/components/process_tree/hooks.ts b/x-pack/plugins/session_view/public/components/process_tree/hooks.ts index 867ae911843e9..0bd3c1622635b 100644 --- a/x-pack/plugins/session_view/public/components/process_tree/hooks.ts +++ b/x-pack/plugins/session_view/public/components/process_tree/hooks.ts @@ -241,7 +241,7 @@ export class ProcessImpl implements Process { const filtered = events.filter((processEvent) => { const action = processEvent?.event?.action; - return action?.includes('fork') || action?.includes('exec') || action?.includes('end'); + // return action?.includes('fork') || action?.includes('exec') || action?.includes('end'); }); // there are some anomalous processes which are omitting event.action 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 a6201ea2ed504..f41ff15bb69ab 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 @@ -316,7 +316,12 @@ export function ProcessTreeNode({ - + diff --git a/x-pack/plugins/session_view/public/components/session_view/index.tsx b/x-pack/plugins/session_view/public/components/session_view/index.tsx index bdb7e3ddc8c2b..0d653e0d55c23 100644 --- a/x-pack/plugins/session_view/public/components/session_view/index.tsx +++ b/x-pack/plugins/session_view/public/components/session_view/index.tsx @@ -62,6 +62,7 @@ export const SessionView = ({ loadAlertDetails, canReadPolicyManagement, trackEvent, + openDetails, }: SessionViewDeps & { trackEvent: (name: SessionViewTelemetryKey) => void }) => { // don't engage jumpTo if jumping to session leader. if (jumpToEntityId === sessionEntityId) { @@ -254,12 +255,16 @@ export const SessionView = ({ detailPanelCollapseFn.current(); setIsDetailOpen(newValue); + if (openDetails) { + openDetails(selectedProcess); + } + if (newValue) { trackEvent('details_opened'); } else { trackEvent('details_closed'); } - }, [isDetailOpen, trackEvent]); + }, [isDetailOpen, openDetails, selectedProcess, trackEvent]); const onShowAlertDetails = useCallback( (alertUuid: string) => { @@ -294,6 +299,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 ( @@ -402,91 +487,50 @@ export const SessionView = ({ - - {(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 && ( -
- -
- )} -
- - - - - - - ); - }} -
+
+ + ); + }} +
+ )} { + // console.group('SessionViewDetailPanel'); + // console.log('index', index); + // console.log('alerts', alerts); + // console.log('alertsCount', alertsCount); + // console.log('isFetchingAlerts', isFetchingAlerts); + // console.log('hasNextPageAlerts', hasNextPageAlerts); + // // console.log('fetchNextPageAlerts', fetchNextPageAlerts); + // console.log('investigatedAlertId', investigatedAlertId); + // console.log('selectedProcess', selectedProcess); + // // console.log('onJumpToEvent', onJumpToEvent); + // // console.log('onShowAlertDetails', onShowAlertDetails); + // console.groupEnd(); const [selectedTabId, setSelectedTabId] = useState('process'); const alertsCountStr = useMemo(() => { @@ -142,3 +154,5 @@ export const SessionViewDetailPanel = ({ ); }; +// eslint-disable-next-line import/no-default-export +export { SessionViewDetailPanel as default }; diff --git a/x-pack/plugins/session_view/public/index.ts b/x-pack/plugins/session_view/public/index.ts index 9911332807808..7a0113476e929 100644 --- a/x-pack/plugins/session_view/public/index.ts +++ b/x-pack/plugins/session_view/public/index.ts @@ -13,3 +13,8 @@ export { ENTRY_SESSION_ENTITY_ID_PROPERTY } from '../common'; export function plugin() { return new SessionViewPlugin(); } + +export { DetailPanelProcessTab } from './components/detail_panel_process_tab'; +export { DetailPanelMetadataTab } from './components/detail_panel_metadata_tab'; +export { DetailPanelAlertTab } from './components/detail_panel_alert_tab'; +export { useFetchSessionViewAlerts } from './components/session_view/hooks'; diff --git a/x-pack/plugins/session_view/public/methods/index.tsx b/x-pack/plugins/session_view/public/methods/index.tsx index 43295737c21f1..755999f370e3e 100644 --- a/x-pack/plugins/session_view/public/methods/index.tsx +++ b/x-pack/plugins/session_view/public/methods/index.tsx @@ -10,6 +10,7 @@ import { EuiLoadingSpinner } from '@elastic/eui'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; import { METRIC_TYPE } from '@kbn/analytics'; +import { SessionViewDetailPanelDeps } from '../components/session_view_detail_panel'; import type { SessionViewIndex } from '../../common/types/v1'; import { SessionViewDeps, SessionViewTelemetryKey } from '../types'; import { USAGE_COLLECTION_APP_NAME } from '../../common/constants'; @@ -18,6 +19,7 @@ import { USAGE_COLLECTION_APP_NAME } from '../../common/constants'; const queryClient = new QueryClient(); const SessionViewLazy = lazy(() => import('../components/session_view')); +const SessionViewDetailPanelLazy = lazy(() => import('../components/session_view_detail_panel')); export const ELASTIC_DEFEND_DATA_SOURCE = 'endpoint'; export const CLOUD_DEFEND_DATA_SOURCE = 'cloud_defend'; @@ -79,3 +81,16 @@ export const getSessionViewLazy = ( ); }; + +export const getSessionViewDetailPanelLazy = (props: SessionViewDetailPanelDeps) => { + console.log('getSessionViewDetailPanelLazy'); + const index = getIndexPattern(props.index); + + return ( + + }> + + + + ); +}; diff --git a/x-pack/plugins/session_view/public/plugin.ts b/x-pack/plugins/session_view/public/plugin.ts index 0805f0c889b19..fd471056e9a45 100644 --- a/x-pack/plugins/session_view/public/plugin.ts +++ b/x-pack/plugins/session_view/public/plugin.ts @@ -6,6 +6,7 @@ */ import { CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; +import { SessionViewDetailPanelDeps } from './components/session_view_detail_panel'; import { SessionViewPluginStart, SessionViewPluginStartDeps, @@ -13,7 +14,7 @@ import { SessionViewPluginSetupDeps, SessionViewDeps, } from './types'; -import { getSessionViewLazy } from './methods'; +import { getSessionViewLazy, getSessionViewDetailPanelLazy } from './methods'; export class SessionViewPlugin implements Plugin { public setup(core: CoreSetup) { @@ -24,6 +25,8 @@ export class SessionViewPlugin implements Plugin getSessionViewLazy({ ...sessionViewDeps, usageCollection: plugins?.usageCollection }), + getSessionViewDetailPanel: (sessionViewDetailPanelDeps: SessionViewDetailPanelDeps) => + getSessionViewDetailPanelLazy(sessionViewDetailPanelDeps), }; } diff --git a/x-pack/plugins/session_view/public/types.ts b/x-pack/plugins/session_view/public/types.ts index 79161248d7a51..f11137094f44d 100644 --- a/x-pack/plugins/session_view/public/types.ts +++ b/x-pack/plugins/session_view/public/types.ts @@ -9,6 +9,8 @@ import type { UsageCollectionSetup, UsageCollectionStart, } from '@kbn/usage-collection-plugin/public'; +import type { Process } from '../common'; +import { SessionViewDetailPanelDeps } from './components/session_view_detail_panel'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface SessionViewPluginSetup {} @@ -84,6 +86,7 @@ export interface SessionViewDeps { handleOnAlertDetailsClosed: () => void ) => void; canReadPolicyManagement?: boolean; + openDetails?: (selectedProcess: Process | null) => void; } export interface EuiTabProps { @@ -196,4 +199,5 @@ export interface DetailPanelCloud { export interface SessionViewStart { getSessionView: (props: SessionViewDeps) => JSX.Element; + getSessionViewDetailPanel: (props: SessionViewDetailPanelDeps) => JSX.Element; } diff --git a/x-pack/plugins/session_view/server/routes/process_events_route.ts b/x-pack/plugins/session_view/server/routes/process_events_route.ts index bc6b24fc36bc5..801ba9caecf59 100644 --- a/x-pack/plugins/session_view/server/routes/process_events_route.ts +++ b/x-pack/plugins/session_view/server/routes/process_events_route.ts @@ -13,18 +13,12 @@ import type { AlertsClient, RuleRegistryPluginStartContract, } from '@kbn/rule-registry-plugin/server'; -import { EVENT_ACTION } from '@kbn/rule-data-utils'; import { ALERTS_PER_PROCESS_EVENTS_PAGE, PROCESS_EVENTS_ROUTE, PROCESS_EVENTS_PER_PAGE, ENTRY_SESSION_ENTITY_ID_PROPERTY, - TIMESTAMP_PROPERTY, PROCESS_EVENT_FIELDS, - EVENT_ACTION_EXECUTED, - EVENT_ACTION_END, - EVENT_ACTION_EXEC, - EVENT_ACTION_FORK, } from '../../common/constants'; import { ProcessEvent } from '../../common'; import { searchAlerts } from './alerts_route'; @@ -112,24 +106,24 @@ export const fetchEventsAndScopedAlerts = async ( bool: { must: [ { term: { [ENTRY_SESSION_ENTITY_ID_PROPERTY]: sessionEntityId } }, - { - bool: { - should: [ - { term: { [EVENT_ACTION]: EVENT_ACTION_FORK } }, - { term: { [EVENT_ACTION]: EVENT_ACTION_EXEC } }, - { term: { [EVENT_ACTION]: EVENT_ACTION_EXECUTED } }, - { term: { [EVENT_ACTION]: EVENT_ACTION_END } }, - ], - }, - }, - { - range: { - // optimization to prevent data before this session from being hit. - [TIMESTAMP_PROPERTY]: { - gte: sessionStartTime, - }, - }, - }, + // { + // bool: { + // should: [ + // { term: { [EVENT_ACTION]: EVENT_ACTION_FORK } }, + // { term: { [EVENT_ACTION]: EVENT_ACTION_EXEC } }, + // { term: { [EVENT_ACTION]: EVENT_ACTION_EXECUTED } }, + // { term: { [EVENT_ACTION]: EVENT_ACTION_END } }, + // ], + // }, + // }, + // { + // range: { + // // optimization to prevent data before this session from being hit. + // [TIMESTAMP_PROPERTY]: { + // gte: sessionStartTime, + // }, + // }, + // }, ], }, },