Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
PhilippeOberti committed Nov 14, 2024
1 parent ed002ec commit eaa6318
Show file tree
Hide file tree
Showing 25 changed files with 796 additions and 118 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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';
Expand All @@ -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();
Expand Down Expand Up @@ -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 ? (
<div data-test-subj={SESSION_VIEW_TEST_ID}>
{sessionView.getSessionView({
index,
sessionEntityId,
sessionStartTime,
jumpToEntityId,
jumpToCursor,
isFullScreen: true,
loadAlertDetails: openAlertDetailsPreview,
openDetails: (selectedProcess: Process | null) => openDetails(selectedProcess),
})}
</div>
) : (
Expand Down
Original file line number Diff line number Diff line change
@@ -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<PanelContentProps> = ({ selectedTabId, tabs }) => {
const selectedTabContent = useMemo(() => {
return tabs.find((tab) => tab.id === selectedTabId)?.content;
}, [selectedTabId, tabs]);

return <FlyoutBody>{selectedTabContent}</FlyoutBody>;
};

PanelContent.displayName = 'PanelContent';
Original file line number Diff line number Diff line change
@@ -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<SessionViewPanelContext | undefined>(
undefined
);

export type SessionViewPanelProviderProps = {
/**
* React components to render
*/
children: React.ReactNode;
} & Partial<SessionViewPanelProps['params']>;

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 <FlyoutError />;
}

return (
<SessionViewPanelContext.Provider value={contextValue}>
{children}
</SessionViewPanelContext.Provider>
);
}
);

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;
};
Original file line number Diff line number Diff line change
@@ -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<typeof EuiFlyoutHeader> {
/**
* 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<PanelHeaderProps> = memo(
({ selectedTabId, setSelectedTabId, tabs, ...flyoutHeaderProps }) => {
const onSelectedTabChanged = (id: SessionViewPanelPaths) => setSelectedTabId(id);

const renderTabs = tabs.map((tab, index) => (
<EuiTab
onClick={() => onSelectedTabChanged(tab.id)}
isSelected={tab.id === selectedTabId}
key={index}
data-test-subj={tab['data-test-subj']}
>
{tab.name}
</EuiTab>
));

return (
<FlyoutHeader {...flyoutHeaderProps}>
<FlyoutHeaderTabs>{renderTabs}</FlyoutHeaderTabs>
</FlyoutHeader>
);
}
);

PanelHeader.displayName = 'PanelHeader';
Original file line number Diff line number Diff line change
@@ -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<Partial<SessionViewPanelProps>> = 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 (
<>
<PanelHeader
tabs={allTabs}
selectedTabId={selectedTabId}
setSelectedTabId={setSelectedTabId}
/>
<PanelContent tabs={allTabs} selectedTabId={selectedTabId} />
</>
);
});

SessionViewPanel.displayName = 'SessionViewPanel';
Original file line number Diff line number Diff line change
@@ -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: (
<FormattedMessage
id="xpack.securitySolution.flyout.preview.sessionview.header.processTabLabel"
defaultMessage="Process"
/>
),
content: <ProcessTab />,
};

export const metadataTab: SessionViewPanelTabType = {
id: 'metadata',
'data-test-subj': METADATA_TAB_TEST_ID,
name: (
<FormattedMessage
id="xpack.securitySolution.flyout.preview.sessionview.header.metadataTabLabel"
defaultMessage="Metadata"
/>
),
content: <MetadataTab />,
};

export const alertsTab: SessionViewPanelTabType = {
id: 'alerts',
'data-test-subj': ALERTS_TAB_TEST_ID,
name: (
<FormattedMessage
id="xpack.securitySolution.flyout.preview.sessionview.header.alertsTabLabel"
defaultMessage="Alerts"
/>
),
content: <AlertsTab />,
};
Loading

0 comments on commit eaa6318

Please sign in to comment.