Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
PhilippeOberti committed Dec 6, 2024
1 parent f84e491 commit b7ad4a4
Show file tree
Hide file tree
Showing 28 changed files with 1,029 additions and 176 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -37,18 +37,24 @@ 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,
resetSelectedProcess,
selectedProcess: customSelectedProcess, // TODO should be able to remove this
} = 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;
Expand All @@ -64,33 +70,81 @@ export const SessionView: FC = () => {

const { openPreviewPanel } = useExpandableFlyoutApi();
const openAlertDetailsPreview = useCallback(
(eventId?: string, onClose?: () => void) => {
(evtId?: string, onClose?: () => void) => {
setTimeout(() => {
openPreviewPanel({
id: DocumentDetailsPreviewPanelKey,
params: {
id: evtId,
indexName: eventDetailsIndex,
scopeId,
banner: ALERT_PREVIEW_BANNER,
isPreviewMode: true,
},
});
telemetry.reportEvent(DocumentEventTypes.DetailsFlyoutOpened, {
location: scopeId,
panel: 'preview',
});
}, [500]);
},
[openPreviewPanel, eventDetailsIndex, scopeId, telemetry]
);

const openDetails = useCallback(
(selectedProcess: Process | null) => {
// We need to create our own custom Process object here as we can't pass
// as is as it contains functions (that should not be saved into Redux and also some recursive
// properties that will blow up the rison.encode function that is used to update the URL)
const customSelectedProcess: CustomProcess | null = selectedProcess
? {
id: selectedProcess.id,
details: selectedProcess.getDetails(),
endTime: selectedProcess.getEndTime(),
}
: null;

openPreviewPanel({
id: DocumentDetailsPreviewPanelKey,
id: DocumentDetailsSessionViewPanelKey,
params: {
id: eventId,
indexName: eventDetailsIndex,
eventId,
indexName,
selectedProcess: customSelectedProcess,
index: sessionViewConfig?.index,
sessionEntityId: sessionViewConfig?.sessionEntityId,
sessionStartTime: sessionViewConfig?.sessionStartTime,
investigatedAlertId: sessionViewConfig?.investigatedAlertId,
scopeId,
banner: ALERT_PREVIEW_BANNER,
isPreviewMode: true,
jumpToEntityId,
jumpToCursor,
},
});
telemetry.reportEvent(DocumentEventTypes.DetailsFlyoutOpened, {
location: scopeId,
panel: 'preview',
});
},
[openPreviewPanel, eventDetailsIndex, scopeId, telemetry]
[
openPreviewPanel,
eventId,
indexName,
sessionViewConfig?.index,
sessionViewConfig?.sessionEntityId,
sessionViewConfig?.sessionStartTime,
sessionViewConfig?.investigatedAlertId,
scopeId,
jumpToEntityId,
jumpToCursor,
]
);

return isEnabled ? (
<div data-test-subj={SESSION_VIEW_TEST_ID}>
{sessionView.getSessionView({
index,
sessionEntityId,
sessionStartTime,
...sessionViewConfig,
isFullScreen: true,
loadAlertDetails: openAlertDetailsPreview,
openDetails: (selectedProcess: Process | null) => openDetails(selectedProcess),
canReadPolicyManagement,
resetSelectedProcess,
resetJumpToEntityId: jumpToEntityId,
resetJumpToCursor: jumpToCursor,
})}
</div>
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;

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,134 @@
/*
* 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: string;
details: ProcessEvent;
endTime: string;
}

export interface SessionViewPanelContext {
/**
* from original alert
*/
eventId: string;
/**
* from original alert
*/
indexName: string;
/**
*
*/
selectedProcess: CustomProcess | null;
/**
*
*/
index: string;
/**
*
*/
sessionEntityId: string;
/**
*
*/
sessionStartTime: string;
/**
*
*/
scopeId: string;
/**
*
*/
investigatedAlertId: 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(
({
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 <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;
};
Loading

0 comments on commit b7ad4a4

Please sign in to comment.