[Security Solution] expandable flyout - hide visualize tab in left section and open session view and analyzer in timeline (#164111)

This commit is contained in:
Philippe Oberti 2023-08-24 17:11:00 +02:00 committed by GitHub
parent 0759c869d2
commit acedd23097
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 684 additions and 539 deletions

View file

@ -23,6 +23,7 @@ import {
CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID, CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID,
CORRELATIONS_DETAILS_CASES_SECTION_TABLE_TEST_ID, CORRELATIONS_DETAILS_CASES_SECTION_TABLE_TEST_ID,
CORRELATIONS_DETAILS_CASES_SECTION_TEST_ID, CORRELATIONS_DETAILS_CASES_SECTION_TEST_ID,
CORRELATIONS_DETAILS_TEST_ID,
} from './test_ids'; } from './test_ids';
import { useFetchRelatedAlertsBySession } from '../../shared/hooks/use_fetch_related_alerts_by_session'; import { useFetchRelatedAlertsBySession } from '../../shared/hooks/use_fetch_related_alerts_by_session';
import { useFetchRelatedAlertsByAncestry } from '../../shared/hooks/use_fetch_related_alerts_by_ancestry'; import { useFetchRelatedAlertsByAncestry } from '../../shared/hooks/use_fetch_related_alerts_by_ancestry';
@ -103,7 +104,7 @@ describe('CorrelationsDetails', () => {
expect(getByTestId(CORRELATIONS_DETAILS_CASES_SECTION_TABLE_TEST_ID)).toBeInTheDocument(); expect(getByTestId(CORRELATIONS_DETAILS_CASES_SECTION_TABLE_TEST_ID)).toBeInTheDocument();
}); });
it('should render no section if show values are false', () => { it('should render no section and show error message if show values are false', () => {
jest jest
.mocked(useShowRelatedAlertsByAncestry) .mocked(useShowRelatedAlertsByAncestry)
.mockReturnValue({ show: false, documentId: 'documentId', indices: ['index1'] }); .mockReturnValue({ show: false, documentId: 'documentId', indices: ['index1'] });
@ -115,12 +116,13 @@ describe('CorrelationsDetails', () => {
.mockReturnValue({ show: false, entityId: 'entityId' }); .mockReturnValue({ show: false, entityId: 'entityId' });
jest.mocked(useShowRelatedCases).mockReturnValue(false); jest.mocked(useShowRelatedCases).mockReturnValue(false);
const { queryByTestId } = renderCorrelationDetails(); const { getByTestId, queryByTestId } = renderCorrelationDetails();
expect(queryByTestId(CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TEST_ID)).not.toBeInTheDocument(); expect(queryByTestId(CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TEST_ID)).not.toBeInTheDocument();
expect(queryByTestId(CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID)).not.toBeInTheDocument(); expect(queryByTestId(CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TEST_ID)).not.toBeInTheDocument();
expect(queryByTestId(CORRELATIONS_DETAILS_BY_SESSION_SECTION_TEST_ID)).not.toBeInTheDocument(); expect(queryByTestId(CORRELATIONS_DETAILS_BY_SESSION_SECTION_TEST_ID)).not.toBeInTheDocument();
expect(queryByTestId(CORRELATIONS_DETAILS_CASES_SECTION_TEST_ID)).not.toBeInTheDocument(); expect(queryByTestId(CORRELATIONS_DETAILS_CASES_SECTION_TEST_ID)).not.toBeInTheDocument();
expect(getByTestId(`${CORRELATIONS_DETAILS_TEST_ID}Error`)).toBeInTheDocument();
}); });
it('should render no section if values are null', () => { it('should render no section if values are null', () => {

View file

@ -7,6 +7,8 @@
import React from 'react'; import React from 'react';
import { EuiSpacer } from '@elastic/eui'; import { EuiSpacer } from '@elastic/eui';
import { CORRELATIONS_ERROR_MESSAGE } from './translations';
import { CORRELATIONS_DETAILS_TEST_ID } from './test_ids';
import { RelatedAlertsBySession } from './related_alerts_by_session'; import { RelatedAlertsBySession } from './related_alerts_by_session';
import { RelatedAlertsBySameSourceEvent } from './related_alerts_by_same_source_event'; import { RelatedAlertsBySameSourceEvent } from './related_alerts_by_same_source_event';
import { RelatedCases } from './related_cases'; import { RelatedCases } from './related_cases';
@ -42,27 +44,38 @@ export const CorrelationsDetails: React.FC = () => {
const { show: showAlertsBySession, entityId } = useShowRelatedAlertsBySession({ getFieldsData }); const { show: showAlertsBySession, entityId } = useShowRelatedAlertsBySession({ getFieldsData });
const showCases = useShowRelatedCases(); const showCases = useShowRelatedCases();
const canShowAtLeastOneInsight =
showAlertsByAncestry || showSameSourceAlerts || showAlertsBySession || showCases;
return ( return (
<> <>
{showAlertsByAncestry && documentId && indices && ( {canShowAtLeastOneInsight ? (
<RelatedAlertsByAncestry documentId={documentId} indices={indices} scopeId={scopeId} /> <>
{showAlertsByAncestry && documentId && indices && (
<RelatedAlertsByAncestry documentId={documentId} indices={indices} scopeId={scopeId} />
)}
<EuiSpacer />
{showSameSourceAlerts && originalEventId && (
<RelatedAlertsBySameSourceEvent originalEventId={originalEventId} scopeId={scopeId} />
)}
<EuiSpacer />
{showAlertsBySession && entityId && (
<RelatedAlertsBySession entityId={entityId} scopeId={scopeId} />
)}
<EuiSpacer />
{showCases && <RelatedCases eventId={eventId} />}
</>
) : (
<div data-test-subj={`${CORRELATIONS_DETAILS_TEST_ID}Error`}>
{CORRELATIONS_ERROR_MESSAGE}
</div>
)} )}
<EuiSpacer />
{showSameSourceAlerts && originalEventId && (
<RelatedAlertsBySameSourceEvent originalEventId={originalEventId} scopeId={scopeId} />
)}
<EuiSpacer />
{showAlertsBySession && entityId && (
<RelatedAlertsBySession entityId={entityId} scopeId={scopeId} />
)}
<EuiSpacer />
{showCases && <RelatedCases eventId={eventId} />}
</> </>
); );
}; };

View file

@ -21,6 +21,13 @@ export const SESSION_VIEW_ERROR_MESSAGE = i18n.translate(
} }
); );
export const CORRELATIONS_ERROR_MESSAGE = i18n.translate(
'xpack.securitySolution.flyout.correlationsErrorMessage',
{
defaultMessage: 'No correlations data available',
}
);
export const USER_TITLE = i18n.translate('xpack.securitySolution.flyout.entities.userTitle', { export const USER_TITLE = i18n.translate('xpack.securitySolution.flyout.entities.userTitle', {
defaultMessage: 'User', defaultMessage: 'User',
}); });

View file

@ -20,11 +20,11 @@ export interface PanelContentProps {
/** /**
* Document details expandable flyout left section. Appears after the user clicks on the expand details button in the right section. * Document details expandable flyout left section. Appears after the user clicks on the expand details button in the right section.
* Will display the content of the visualize, investigation and insights tabs. * Displays the content of investigation and insights tabs (visualize is hidden for 8.9).
*/ */
export const PanelContent: VFC<PanelContentProps> = ({ selectedTabId }) => { export const PanelContent: VFC<PanelContentProps> = ({ selectedTabId }) => {
const selectedTabContent = useMemo(() => { const selectedTabContent = useMemo(() => {
return tabs.find((tab) => tab.id === selectedTabId)?.content; return tabs.filter((tab) => tab.visible).find((tab) => tab.id === selectedTabId)?.content;
}, [selectedTabId]); }, [selectedTabId]);
return <EuiFlyoutBody>{selectedTabContent}</EuiFlyoutBody>; return <EuiFlyoutBody>{selectedTabContent}</EuiFlyoutBody>;

View file

@ -13,15 +13,26 @@ import type { LeftPanelPaths } from '.';
import { tabs } from './tabs'; import { tabs } from './tabs';
export interface PanelHeaderProps { export interface PanelHeaderProps {
/**
* Id of the tab selected in the parent component to display its content
*/
selectedTabId: LeftPanelPaths; selectedTabId: LeftPanelPaths;
/**
* Callback to set the selected tab id in the parent component
* @param selected
*/
setSelectedTabId: (selected: LeftPanelPaths) => void; setSelectedTabId: (selected: LeftPanelPaths) => void;
handleOnEventClosed?: () => void;
} }
export const PanelHeader: VFC<PanelHeaderProps> = memo( /**
({ selectedTabId, setSelectedTabId, handleOnEventClosed }) => { * Header at the top of the left section.
const onSelectedTabChanged = (id: LeftPanelPaths) => setSelectedTabId(id); * Displays the investigation and insights tabs (visualize is hidden for 8.9).
const renderTabs = tabs.map((tab, index) => ( */
export const PanelHeader: VFC<PanelHeaderProps> = memo(({ selectedTabId, setSelectedTabId }) => {
const onSelectedTabChanged = (id: LeftPanelPaths) => setSelectedTabId(id);
const renderTabs = tabs
.filter((tab) => tab.visible)
.map((tab, index) => (
<EuiTab <EuiTab
onClick={() => onSelectedTabChanged(tab.id)} onClick={() => onSelectedTabChanged(tab.id)}
isSelected={tab.id === selectedTabId} isSelected={tab.id === selectedTabId}
@ -32,20 +43,19 @@ export const PanelHeader: VFC<PanelHeaderProps> = memo(
</EuiTab> </EuiTab>
)); ));
return ( return (
<EuiFlyoutHeader hasBorder> <EuiFlyoutHeader hasBorder>
<EuiTabs <EuiTabs
size="l" size="l"
expand expand
css={css` css={css`
margin-bottom: -25px; margin-bottom: -25px;
`} `}
> >
{renderTabs} {renderTabs}
</EuiTabs> </EuiTabs>
</EuiFlyoutHeader> </EuiFlyoutHeader>
); );
} });
);
PanelHeader.displayName = 'PanelHeader'; PanelHeader.displayName = 'PanelHeader';

View file

@ -39,9 +39,10 @@ export const LeftPanel: FC<Partial<LeftPanelProps>> = memo(({ path }) => {
const { eventId, indexName, scopeId } = useLeftPanelContext(); const { eventId, indexName, scopeId } = useLeftPanelContext();
const selectedTabId = useMemo(() => { const selectedTabId = useMemo(() => {
const defaultTab = tabs[0].id; const visibleTabs = tabs.filter((tab) => tab.visible);
const defaultTab = visibleTabs[0].id;
if (!path) return defaultTab; if (!path) return defaultTab;
return tabs.map((tab) => tab.id).find((tabId) => tabId === path.tab) ?? defaultTab; return visibleTabs.map((tab) => tab.id).find((tabId) => tabId === path.tab) ?? defaultTab;
}, [path]); }, [path]);
const setSelectedTabId = (tabId: LeftPanelTabsType[number]['id']) => { const setSelectedTabId = (tabId: LeftPanelTabsType[number]['id']) => {

View file

@ -24,6 +24,7 @@ export type LeftPanelTabsType = Array<{
'data-test-subj': string; 'data-test-subj': string;
name: string; name: string;
content: React.ReactElement; content: React.ReactElement;
visible: boolean;
}>; }>;
export const tabs: LeftPanelTabsType = [ export const tabs: LeftPanelTabsType = [
@ -32,23 +33,27 @@ export const tabs: LeftPanelTabsType = [
'data-test-subj': VISUALIZE_TAB_TEST_ID, 'data-test-subj': VISUALIZE_TAB_TEST_ID,
name: VISUALIZE_TAB, name: VISUALIZE_TAB,
content: <VisualizeTab />, content: <VisualizeTab />,
visible: false,
}, },
{ {
id: 'insights', id: 'insights',
'data-test-subj': INSIGHTS_TAB_TEST_ID, 'data-test-subj': INSIGHTS_TAB_TEST_ID,
name: INSIGHTS_TAB, name: INSIGHTS_TAB,
content: <InsightsTab />, content: <InsightsTab />,
visible: true,
}, },
{ {
id: 'investigation', id: 'investigation',
'data-test-subj': INVESTIGATION_TAB_TEST_ID, 'data-test-subj': INVESTIGATION_TAB_TEST_ID,
name: INVESTIGATIONS_TAB, name: INVESTIGATIONS_TAB,
content: <InvestigationTab />, content: <InvestigationTab />,
visible: true,
}, },
{ {
id: 'response', id: 'response',
'data-test-subj': RESPONSE_TAB_TEST_ID, 'data-test-subj': RESPONSE_TAB_TEST_ID,
name: RESPONSE_TAB, name: RESPONSE_TAB,
content: <ResponseTab />, content: <ResponseTab />,
visible: true,
}, },
]; ];

View file

@ -15,7 +15,6 @@ import { RightPanelContext } from '../context';
import { AnalyzerPreview } from './analyzer_preview'; import { AnalyzerPreview } from './analyzer_preview';
import { ANALYZER_PREVIEW_TEST_ID } from './test_ids'; import { ANALYZER_PREVIEW_TEST_ID } from './test_ids';
import * as mock from '../mocks/mock_analyzer_data'; import * as mock from '../mocks/mock_analyzer_data';
import { EXPANDABLE_PANEL_CONTENT_TEST_ID } from '../../shared/components/test_ids';
jest.mock('../../../common/containers/alerts/use_alert_prevalence_from_process_tree', () => ({ jest.mock('../../../common/containers/alerts/use_alert_prevalence_from_process_tree', () => ({
useAlertPrevalenceFromProcessTree: jest.fn(), useAlertPrevalenceFromProcessTree: jest.fn(),
@ -65,9 +64,7 @@ describe('<AnalyzerPreview />', () => {
documentId: 'ancestors-id', documentId: 'ancestors-id',
indices: ['rule-parameters-index'], indices: ['rule-parameters-index'],
}); });
expect( expect(wrapper.getByTestId(ANALYZER_PREVIEW_TEST_ID)).toBeInTheDocument();
wrapper.getByTestId(EXPANDABLE_PANEL_CONTENT_TEST_ID(ANALYZER_PREVIEW_TEST_ID))
).toBeInTheDocument();
}); });
it('does not show analyzer preview when documentid and index are not present', () => { it('does not show analyzer preview when documentid and index are not present', () => {
@ -90,8 +87,6 @@ describe('<AnalyzerPreview />', () => {
documentId: '', documentId: '',
indices: [], indices: [],
}); });
expect( expect(queryByTestId(ANALYZER_PREVIEW_TEST_ID)).not.toBeInTheDocument();
queryByTestId(EXPANDABLE_PANEL_CONTENT_TEST_ID(ANALYZER_PREVIEW_TEST_ID))
).not.toBeInTheDocument();
}); });
}); });

View file

@ -4,13 +4,16 @@
* 2.0; you may not use this file except in compliance with the Elastic License * 2.0; you may not use this file except in compliance with the Elastic License
* 2.0. * 2.0.
*/ */
import React, { useEffect, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import { find } from 'lodash/fp'; import { find } from 'lodash/fp';
import { EuiTreeView } from '@elastic/eui';
import { ANALYZER_PREVIEW_TEST_ID } from './test_ids';
import { getTreeNodes } from '../utils/analyzer_helpers';
import { ANALYZER_PREVIEW_TITLE } from './translations';
import { ANCESTOR_ID, RULE_PARAMETERS_INDEX } from '../../shared/constants/field_names'; import { ANCESTOR_ID, RULE_PARAMETERS_INDEX } from '../../shared/constants/field_names';
import { useRightPanelContext } from '../context'; import { useRightPanelContext } from '../context';
import { useAlertPrevalenceFromProcessTree } from '../../../common/containers/alerts/use_alert_prevalence_from_process_tree'; import { useAlertPrevalenceFromProcessTree } from '../../../common/containers/alerts/use_alert_prevalence_from_process_tree';
import type { StatsNode } from '../../../common/containers/alerts/use_alert_prevalence_from_process_tree'; import type { StatsNode } from '../../../common/containers/alerts/use_alert_prevalence_from_process_tree';
import { AnalyzerTree } from './analyzer_tree';
import { isActiveTimeline } from '../../../helpers'; import { isActiveTimeline } from '../../../helpers';
const CHILD_COUNT_LIMIT = 3; const CHILD_COUNT_LIMIT = 3;
@ -38,7 +41,7 @@ export const AnalyzerPreview: React.FC = () => {
const index = find({ category: 'kibana', field: RULE_PARAMETERS_INDEX }, data); const index = find({ category: 'kibana', field: RULE_PARAMETERS_INDEX }, data);
const indices = index?.values ?? []; const indices = index?.values ?? [];
const { loading, error, statsNodes } = useAlertPrevalenceFromProcessTree({ const { statsNodes } = useAlertPrevalenceFromProcessTree({
isActiveTimeline: isActiveTimeline(scopeId), isActiveTimeline: isActiveTimeline(scopeId),
documentId: processDocumentId, documentId: processDocumentId,
indices, indices,
@ -50,19 +53,24 @@ export const AnalyzerPreview: React.FC = () => {
} }
}, [statsNodes, setCache]); }, [statsNodes, setCache]);
if (!documentId || !index) { const items = useMemo(
() => getTreeNodes(cache.statsNodes ?? [], CHILD_COUNT_LIMIT, ANCESTOR_LEVEL, DESCENDANT_LEVEL),
[cache.statsNodes]
);
if (!documentId || !index || !items || items.length === 0) {
return null; return null;
} }
return ( return (
<AnalyzerTree <div data-test-subj={ANALYZER_PREVIEW_TEST_ID}>
statsNodes={cache.statsNodes} <EuiTreeView
loading={loading} items={items}
error={error} display="compressed"
childCountLimit={CHILD_COUNT_LIMIT} aria-label={ANALYZER_PREVIEW_TITLE}
ancestorLevel={ANCESTOR_LEVEL} showExpansionArrows
descendantLevel={DESCENDANT_LEVEL} />
/> </div>
); );
}; };

View file

@ -0,0 +1,126 @@
/*
* 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 { render, screen } from '@testing-library/react';
import { TestProviders } from '../../../common/mock';
import React from 'react';
import { RightPanelContext } from '../context';
import { AnalyzerPreviewContainer } from './analyzer_preview_container';
import { isInvestigateInResolverActionEnabled } from '../../../detections/components/alerts_table/timeline_actions/investigate_in_resolver';
import { ANALYZER_PREVIEW_TEST_ID } from './test_ids';
import { useAlertPrevalenceFromProcessTree } from '../../../common/containers/alerts/use_alert_prevalence_from_process_tree';
import * as mock from '../mocks/mock_analyzer_data';
import {
EXPANDABLE_PANEL_CONTENT_TEST_ID,
EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID,
EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID,
EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID,
EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID,
} from '../../shared/components/test_ids';
import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_context';
import { useInvestigateInTimeline } from '../../../detections/components/alerts_table/timeline_actions/use_investigate_in_timeline';
jest.mock('../../../detections/components/alerts_table/timeline_actions/investigate_in_resolver');
jest.mock('../../../common/containers/alerts/use_alert_prevalence_from_process_tree');
jest.mock(
'../../../detections/components/alerts_table/timeline_actions/use_investigate_in_timeline'
);
jest.mock('react-router-dom', () => {
const actual = jest.requireActual('react-router-dom');
return { ...actual, useLocation: jest.fn().mockReturnValue({ pathname: '' }) };
});
const panelContextValue = {
dataAsNestedObject: null,
dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser,
} as unknown as RightPanelContext;
const TEST_ID = ANALYZER_PREVIEW_TEST_ID;
const ERROR_TEST_ID = `${ANALYZER_PREVIEW_TEST_ID}Error`;
const renderAnalyzerPreview = () =>
render(
<TestProviders>
<RightPanelContext.Provider value={panelContextValue}>
<AnalyzerPreviewContainer />
</RightPanelContext.Provider>
</TestProviders>
);
describe('AnalyzerPreviewContainer', () => {
afterEach(() => {
jest.resetAllMocks();
});
it('should render component and link in header', () => {
(isInvestigateInResolverActionEnabled as jest.Mock).mockReturnValue(true);
(useAlertPrevalenceFromProcessTree as jest.Mock).mockReturnValue({
loading: false,
error: false,
alertIds: ['alertid'],
statsNodes: mock.mockStatsNodes,
});
(useInvestigateInTimeline as jest.Mock).mockReturnValue({
investigateInTimelineAlertClick: jest.fn(),
});
const { getByTestId, queryByTestId } = renderAnalyzerPreview();
expect(getByTestId(TEST_ID)).toBeInTheDocument();
expect(queryByTestId(ERROR_TEST_ID)).not.toBeInTheDocument();
expect(
getByTestId(EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(ANALYZER_PREVIEW_TEST_ID))
).toBeInTheDocument();
expect(
screen.queryByTestId(EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID(ANALYZER_PREVIEW_TEST_ID))
).not.toBeInTheDocument();
expect(
screen.getByTestId(EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID(ANALYZER_PREVIEW_TEST_ID))
).toBeInTheDocument();
expect(
screen.getByTestId(EXPANDABLE_PANEL_CONTENT_TEST_ID(ANALYZER_PREVIEW_TEST_ID))
).toBeInTheDocument();
expect(
screen.queryByTestId(EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(ANALYZER_PREVIEW_TEST_ID))
).not.toBeInTheDocument();
});
it('should render error message and text in header', () => {
(isInvestigateInResolverActionEnabled as jest.Mock).mockReturnValue(false);
(useInvestigateInTimeline as jest.Mock).mockReturnValue({
investigateInTimelineAlertClick: jest.fn(),
});
const { getByTestId, queryByTestId } = renderAnalyzerPreview();
expect(queryByTestId(TEST_ID)).not.toBeInTheDocument();
expect(getByTestId(ERROR_TEST_ID)).toBeInTheDocument();
expect(
getByTestId(EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(ANALYZER_PREVIEW_TEST_ID))
).toBeInTheDocument();
});
it('should navigate to left section Visualize tab when clicking on title', () => {
(isInvestigateInResolverActionEnabled as jest.Mock).mockReturnValue(true);
(useAlertPrevalenceFromProcessTree as jest.Mock).mockReturnValue({
loading: false,
error: false,
alertIds: ['alertid'],
statsNodes: mock.mockStatsNodes,
});
(useInvestigateInTimeline as jest.Mock).mockReturnValue({
investigateInTimelineAlertClick: jest.fn(),
});
const { getByTestId } = renderAnalyzerPreview();
const { investigateInTimelineAlertClick } = useInvestigateInTimeline({});
getByTestId(EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(ANALYZER_PREVIEW_TEST_ID)).click();
expect(investigateInTimelineAlertClick).toHaveBeenCalled();
});
});

View file

@ -0,0 +1,74 @@
/*
* 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, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { TimelineTabs } from '@kbn/securitysolution-data-table';
import { useStartTransaction } from '../../../common/lib/apm/use_start_transaction';
import { useInvestigateInTimeline } from '../../../detections/components/alerts_table/timeline_actions/use_investigate_in_timeline';
import { ALERTS_ACTIONS } from '../../../common/lib/apm/user_actions';
import { getScopedActions } from '../../../helpers';
import { setActiveTabTimeline } from '../../../timelines/store/timeline/actions';
import { useRightPanelContext } from '../context';
import { isInvestigateInResolverActionEnabled } from '../../../detections/components/alerts_table/timeline_actions/investigate_in_resolver';
import { AnalyzerPreview } from './analyzer_preview';
import { ANALYZER_PREVIEW_TEST_ID } from './test_ids';
import { ANALYZER_PREVIEW_ERROR, ANALYZER_PREVIEW_TITLE } from './translations';
import { ExpandablePanel } from '../../shared/components/expandable_panel';
const timelineId = 'timeline-1';
/**
* Analyzer preview under Overview, Visualizations. It shows a tree representation of analyzer.
*/
export const AnalyzerPreviewContainer: React.FC = () => {
const { dataAsNestedObject } = useRightPanelContext();
// decide whether to show the analyzer preview or not
const isEnabled = isInvestigateInResolverActionEnabled(dataAsNestedObject || undefined);
const dispatch = useDispatch();
const { startTransaction } = useStartTransaction();
const { investigateInTimelineAlertClick } = useInvestigateInTimeline({
ecsRowData: dataAsNestedObject,
});
// open timeline to the analyzer tab because the expandable flyout left panel Visualize => Analyzer tab is not ready
const goToAnalyzerTab = useCallback(() => {
// open timeline
investigateInTimelineAlertClick();
// open analyzer tab
startTransaction({ name: ALERTS_ACTIONS.OPEN_ANALYZER });
const scopedActions = getScopedActions(timelineId);
if (scopedActions && dataAsNestedObject) {
dispatch(
scopedActions.updateGraphEventId({ id: timelineId, graphEventId: dataAsNestedObject._id })
);
}
dispatch(setActiveTabTimeline({ id: timelineId, activeTab: TimelineTabs.graph }));
}, [dataAsNestedObject, dispatch, investigateInTimelineAlertClick, startTransaction]);
return (
<ExpandablePanel
header={{
title: ANALYZER_PREVIEW_TITLE,
iconType: 'timeline',
...(isEnabled && { callback: goToAnalyzerTab }),
}}
data-test-subj={ANALYZER_PREVIEW_TEST_ID}
>
{isEnabled ? (
<AnalyzerPreview />
) : (
<div data-test-subj={`${ANALYZER_PREVIEW_TEST_ID}Error`}>{ANALYZER_PREVIEW_ERROR}</div>
)}
</ExpandablePanel>
);
};
AnalyzerPreviewContainer.displayName = 'AnalyzerPreviewContainer';

View file

@ -1,111 +0,0 @@
/*
* 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 from 'react';
import type { Story } from '@storybook/react';
import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context';
import { AnalyzerTree } from './analyzer_tree';
import * as mock from '../mocks/mock_analyzer_data';
import { RightPanelContext } from '../context';
export default {
component: AnalyzerTree,
title: 'Flyout/AnalyzerTree',
};
const defaultProps = {
loading: false,
error: false,
};
const flyoutContextValue = {
openLeftPanel: () => {},
} as unknown as ExpandableFlyoutContext;
const contextValue = {
eventId: 'eventId',
indexName: 'indexName',
scopeId: 'alerts-page',
} as unknown as RightPanelContext;
const wrapper = (children: React.ReactNode) => (
<ExpandableFlyoutContext.Provider value={flyoutContextValue}>
<RightPanelContext.Provider value={contextValue}>{children}</RightPanelContext.Provider>
</ExpandableFlyoutContext.Provider>
);
export const Default: Story<void> = () => {
return wrapper(<AnalyzerTree {...defaultProps} statsNodes={mock.mockStatsNodes} />);
};
export const SingleNode: Story<void> = () => {
return wrapper(<AnalyzerTree {...defaultProps} statsNodes={[mock.mockStatsNode]} />);
};
export const ShowParent: Story<void> = () => {
return wrapper(<AnalyzerTree {...defaultProps} statsNodes={mock.mockStatsNodesHasParent} />);
};
export const ShowGrandparent: Story<void> = () => {
return wrapper(
<AnalyzerTree
{...defaultProps}
statsNodes={mock.mockStatsNodesHasGrandparent}
ancestorLevel={2}
/>
);
};
export const HideGrandparent: Story<void> = () => {
return wrapper(
<AnalyzerTree
{...defaultProps}
statsNodes={mock.mockStatsNodesHasGrandparent}
ancestorLevel={1}
/>
);
};
export const ShowChildren: Story<void> = () => {
return wrapper(<AnalyzerTree {...defaultProps} statsNodes={mock.mockStatsNodesHasChildren} />);
};
export const ShowOnlyOneChild: Story<void> = () => {
return wrapper(
<AnalyzerTree
{...defaultProps}
statsNodes={mock.mockStatsNodesHasChildren}
childCountLimit={1}
/>
);
};
export const ShowGrandchildren: Story<void> = () => {
return wrapper(<AnalyzerTree {...defaultProps} statsNodes={mock.mockStatsNodesHasChildren} />);
};
export const HideGrandchildren: Story<void> = () => {
return wrapper(
<AnalyzerTree
{...defaultProps}
statsNodes={mock.mockStatsNodesHasChildren}
descendantLevel={1}
/>
);
};
export const Loading: Story<void> = () => {
return wrapper(<AnalyzerTree loading={true} error={false} descendantLevel={3} />);
};
export const Error: Story<void> = () => {
return wrapper(<AnalyzerTree loading={false} error={true} descendantLevel={3} />);
};
export const Empty: Story<void> = () => {
return wrapper(<AnalyzerTree {...defaultProps} />);
};

View file

@ -1,112 +0,0 @@
/*
* 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 from 'react';
import { render } from '@testing-library/react';
import { ANALYZER_PREVIEW_TEST_ID } from './test_ids';
import { ANALYZE_GRAPH_ID } from '../../left/components/analyze_graph';
import { ANALYZER_PREVIEW_TITLE } from './translations';
import * as mock from '../mocks/mock_analyzer_data';
import type { AnalyzerTreeProps } from './analyzer_tree';
import { AnalyzerTree } from './analyzer_tree';
import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context';
import { TestProviders } from '@kbn/timelines-plugin/public/mock';
import { RightPanelContext } from '../context';
import { LeftPanelKey, LeftPanelVisualizeTab } from '../../left';
import {
EXPANDABLE_PANEL_CONTENT_TEST_ID,
EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID,
EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID,
EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID,
EXPANDABLE_PANEL_LOADING_TEST_ID,
EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID,
} from '../../shared/components/test_ids';
const TOGGLE_ICON_TEST_ID = EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID(ANALYZER_PREVIEW_TEST_ID);
const TITLE_LINK_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(ANALYZER_PREVIEW_TEST_ID);
const TITLE_ICON_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID(ANALYZER_PREVIEW_TEST_ID);
const TITLE_TEXT_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(ANALYZER_PREVIEW_TEST_ID);
const CONTENT_TEST_ID = EXPANDABLE_PANEL_CONTENT_TEST_ID(ANALYZER_PREVIEW_TEST_ID);
const LOADING_TEST_ID = EXPANDABLE_PANEL_LOADING_TEST_ID(ANALYZER_PREVIEW_TEST_ID);
const defaultProps: AnalyzerTreeProps = {
statsNodes: mock.mockStatsNodes,
loading: false,
error: false,
};
const flyoutContextValue = {
openLeftPanel: jest.fn(),
} as unknown as ExpandableFlyoutContext;
const panelContextValue = {
eventId: 'event id',
indexName: 'indexName',
browserFields: {},
dataFormattedForFieldBrowser: [],
} as unknown as RightPanelContext;
const renderAnalyzerTree = (children: React.ReactNode) =>
render(
<TestProviders>
<ExpandableFlyoutContext.Provider value={flyoutContextValue}>
<RightPanelContext.Provider value={panelContextValue}>
{children}
</RightPanelContext.Provider>
</ExpandableFlyoutContext.Provider>
</TestProviders>
);
describe('<AnalyzerTree />', () => {
it('should render wrapper component', () => {
const { getByTestId, queryByTestId } = renderAnalyzerTree(<AnalyzerTree {...defaultProps} />);
expect(queryByTestId(TOGGLE_ICON_TEST_ID)).not.toBeInTheDocument();
expect(getByTestId(TITLE_LINK_TEST_ID)).toBeInTheDocument();
expect(getByTestId(TITLE_ICON_TEST_ID)).toBeInTheDocument();
expect(getByTestId(CONTENT_TEST_ID)).toBeInTheDocument();
expect(queryByTestId(TITLE_TEXT_TEST_ID)).not.toBeInTheDocument();
});
it('should render the component when data is passed', () => {
const { getByTestId, getByText } = renderAnalyzerTree(<AnalyzerTree {...defaultProps} />);
expect(getByText(ANALYZER_PREVIEW_TITLE)).toBeInTheDocument();
expect(getByTestId(CONTENT_TEST_ID)).toBeInTheDocument();
});
it('should render blank when data is not passed', () => {
const { queryByTestId, queryByText } = renderAnalyzerTree(
<AnalyzerTree {...defaultProps} statsNodes={undefined} />
);
expect(queryByText(ANALYZER_PREVIEW_TITLE)).not.toBeInTheDocument();
expect(queryByTestId(CONTENT_TEST_ID)).not.toBeInTheDocument();
});
it('should render loading spinner when loading is true', () => {
const { getByTestId } = renderAnalyzerTree(<AnalyzerTree {...defaultProps} loading={true} />);
expect(getByTestId(LOADING_TEST_ID)).toBeInTheDocument();
});
it('should not render when error is true', () => {
const { getByTestId } = renderAnalyzerTree(<AnalyzerTree {...defaultProps} error={true} />);
expect(getByTestId(CONTENT_TEST_ID)).toBeEmptyDOMElement();
});
it('should navigate to left section Visualize tab when clicking on title', () => {
const { getByTestId } = renderAnalyzerTree(<AnalyzerTree {...defaultProps} />);
getByTestId(TITLE_LINK_TEST_ID).click();
expect(flyoutContextValue.openLeftPanel).toHaveBeenCalledWith({
id: LeftPanelKey,
path: { tab: LeftPanelVisualizeTab, subTab: ANALYZE_GRAPH_ID },
params: {
id: panelContextValue.eventId,
indexName: panelContextValue.indexName,
scopeId: panelContextValue.scopeId,
},
});
});
});

View file

@ -1,102 +0,0 @@
/*
* 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, { useCallback, useMemo } from 'react';
import { EuiTreeView } from '@elastic/eui';
import { useExpandableFlyoutContext } from '@kbn/expandable-flyout';
import { ExpandablePanel } from '../../shared/components/expandable_panel';
import { useRightPanelContext } from '../context';
import { LeftPanelKey, LeftPanelVisualizeTab } from '../../left';
import { ANALYZER_PREVIEW_TITLE } from './translations';
import { ANALYZER_PREVIEW_TEST_ID } from './test_ids';
import { ANALYZE_GRAPH_ID } from '../../left/components/analyze_graph';
import type { StatsNode } from '../../../common/containers/alerts/use_alert_prevalence_from_process_tree';
import { getTreeNodes } from '../utils/analyzer_helpers';
export interface AnalyzerTreeProps {
/**
* statsNode data from resolver tree api
*/
statsNodes?: StatsNode[];
/**
* Boolean value of whether data is in loading
*/
loading: boolean;
/**
* Boolean value of whether there is error in data fetching
*/
error: boolean;
/**
* Optional parameter to limit the number of child nodes to be displayed
*/
childCountLimit?: number;
/**
* Optional parameter to limit the depth of ancestors
*/
ancestorLevel?: number;
/**
* Optional parameter to limit the depth of descendants
*/
descendantLevel?: number;
}
/**
* Analyzer tree that represent a summary view of analyzer. It shows current process, and its parent and child processes
*/
export const AnalyzerTree: React.FC<AnalyzerTreeProps> = ({
statsNodes,
loading,
error,
childCountLimit = 3,
ancestorLevel = 1,
descendantLevel = 1,
}) => {
const { eventId, indexName, scopeId } = useRightPanelContext();
const { openLeftPanel } = useExpandableFlyoutContext();
const items = useMemo(
() => getTreeNodes(statsNodes ?? [], childCountLimit, ancestorLevel, descendantLevel),
[statsNodes, childCountLimit, ancestorLevel, descendantLevel]
);
const goToAnalyserTab = useCallback(() => {
openLeftPanel({
id: LeftPanelKey,
path: {
tab: LeftPanelVisualizeTab,
subTab: ANALYZE_GRAPH_ID,
},
params: {
id: eventId,
indexName,
scopeId,
},
});
}, [eventId, openLeftPanel, indexName, scopeId]);
if (items && items.length !== 0) {
return (
<ExpandablePanel
header={{
title: ANALYZER_PREVIEW_TITLE,
callback: goToAnalyserTab,
iconType: 'arrowStart',
}}
content={{ loading, error }}
data-test-subj={ANALYZER_PREVIEW_TEST_ID}
>
<EuiTreeView
items={items}
display="compressed"
aria-label={ANALYZER_PREVIEW_TITLE}
showExpansionArrows
/>
</ExpandablePanel>
);
}
return null;
};
AnalyzerTree.displayName = 'AnalyzerTree';

View file

@ -68,6 +68,7 @@ const RELATED_ALERTS_BY_SESSION_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID(
const RELATED_CASES_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID( const RELATED_CASES_TEST_ID = SUMMARY_ROW_VALUE_TEST_ID(
INSIGHTS_CORRELATIONS_RELATED_CASES_TEST_ID INSIGHTS_CORRELATIONS_RELATED_CASES_TEST_ID
); );
const CORRELATIONS_ERROR_TEST_ID = `${INSIGHTS_CORRELATIONS_TEST_ID}Error`;
const panelContextValue = { const panelContextValue = {
eventId: 'event id', eventId: 'event id',
@ -139,7 +140,7 @@ describe('<CorrelationsOverview />', () => {
expect(getByTestId(RELATED_CASES_TEST_ID)).toBeInTheDocument(); expect(getByTestId(RELATED_CASES_TEST_ID)).toBeInTheDocument();
}); });
it('should hide rows if show values are false', () => { it('should hide rows and show error message if show values are false', () => {
jest jest
.mocked(useShowRelatedAlertsByAncestry) .mocked(useShowRelatedAlertsByAncestry)
.mockReturnValue({ show: false, documentId: 'documentId', indices: ['index1'] }); .mockReturnValue({ show: false, documentId: 'documentId', indices: ['index1'] });
@ -151,11 +152,12 @@ describe('<CorrelationsOverview />', () => {
.mockReturnValue({ show: false, entityId: 'entityId' }); .mockReturnValue({ show: false, entityId: 'entityId' });
jest.mocked(useShowRelatedCases).mockReturnValue(false); jest.mocked(useShowRelatedCases).mockReturnValue(false);
const { queryByTestId } = render(renderCorrelationsOverview(panelContextValue)); const { getByTestId, queryByTestId } = render(renderCorrelationsOverview(panelContextValue));
expect(queryByTestId(RELATED_ALERTS_BY_ANCESTRY_TEST_ID)).not.toBeInTheDocument(); expect(queryByTestId(RELATED_ALERTS_BY_ANCESTRY_TEST_ID)).not.toBeInTheDocument();
expect(queryByTestId(RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID)).not.toBeInTheDocument(); expect(queryByTestId(RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID)).not.toBeInTheDocument();
expect(queryByTestId(RELATED_ALERTS_BY_SESSION_TEST_ID)).not.toBeInTheDocument(); expect(queryByTestId(RELATED_ALERTS_BY_SESSION_TEST_ID)).not.toBeInTheDocument();
expect(queryByTestId(RELATED_CASES_TEST_ID)).not.toBeInTheDocument(); expect(queryByTestId(RELATED_CASES_TEST_ID)).not.toBeInTheDocument();
expect(getByTestId(CORRELATIONS_ERROR_TEST_ID)).toBeInTheDocument();
}); });
it('should hide rows if values are null', () => { it('should hide rows if values are null', () => {

View file

@ -19,7 +19,7 @@ import { RelatedCases } from './related_cases';
import { useShowRelatedCases } from '../../shared/hooks/use_show_related_cases'; import { useShowRelatedCases } from '../../shared/hooks/use_show_related_cases';
import { INSIGHTS_CORRELATIONS_TEST_ID } from './test_ids'; import { INSIGHTS_CORRELATIONS_TEST_ID } from './test_ids';
import { useRightPanelContext } from '../context'; import { useRightPanelContext } from '../context';
import { CORRELATIONS_TITLE } from './translations'; import { CORRELATIONS_ERROR, CORRELATIONS_TITLE } from './translations';
import { LeftPanelKey, LeftPanelInsightsTab } from '../../left'; import { LeftPanelKey, LeftPanelInsightsTab } from '../../left';
import { CORRELATIONS_TAB_ID } from '../../left/components/correlations_details'; import { CORRELATIONS_TAB_ID } from '../../left/components/correlations_details';
@ -69,6 +69,9 @@ export const CorrelationsOverview: React.FC = () => {
const { show: showAlertsBySession, entityId } = useShowRelatedAlertsBySession({ getFieldsData }); const { show: showAlertsBySession, entityId } = useShowRelatedAlertsBySession({ getFieldsData });
const showCases = useShowRelatedCases(); const showCases = useShowRelatedCases();
const canShowAtLeastOneInsight =
showAlertsByAncestry || showSameSourceAlerts || showAlertsBySession || showCases;
return ( return (
<ExpandablePanel <ExpandablePanel
header={{ header={{
@ -78,18 +81,22 @@ export const CorrelationsOverview: React.FC = () => {
}} }}
data-test-subj={INSIGHTS_CORRELATIONS_TEST_ID} data-test-subj={INSIGHTS_CORRELATIONS_TEST_ID}
> >
<EuiFlexGroup direction="column" gutterSize="none"> {canShowAtLeastOneInsight ? (
{showAlertsByAncestry && documentId && indices && ( <EuiFlexGroup direction="column" gutterSize="none">
<RelatedAlertsByAncestry documentId={documentId} indices={indices} scopeId={scopeId} /> {showAlertsByAncestry && documentId && indices && (
)} <RelatedAlertsByAncestry documentId={documentId} indices={indices} scopeId={scopeId} />
{showSameSourceAlerts && originalEventId && ( )}
<RelatedAlertsBySameSourceEvent originalEventId={originalEventId} scopeId={scopeId} /> {showSameSourceAlerts && originalEventId && (
)} <RelatedAlertsBySameSourceEvent originalEventId={originalEventId} scopeId={scopeId} />
{showAlertsBySession && entityId && ( )}
<RelatedAlertsBySession entityId={entityId} scopeId={scopeId} /> {showAlertsBySession && entityId && (
)} <RelatedAlertsBySession entityId={entityId} scopeId={scopeId} />
{showCases && <RelatedCases eventId={eventId} />} )}
</EuiFlexGroup> {showCases && <RelatedCases eventId={eventId} />}
</EuiFlexGroup>
) : (
<div data-test-subj={`${INSIGHTS_CORRELATIONS_TEST_ID}Error`}>{CORRELATIONS_ERROR}</div>
)}
</ExpandablePanel> </ExpandablePanel>
); );
}; };

View file

@ -12,25 +12,9 @@ import { TestProviders } from '../../../common/mock';
import React from 'react'; import React from 'react';
import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context';
import { RightPanelContext } from '../context'; import { RightPanelContext } from '../context';
import { SESSION_PREVIEW_TEST_ID } from './test_ids';
import { LeftPanelKey, LeftPanelVisualizeTab } from '../../left';
import { SESSION_VIEW_ID } from '../../left/components/session_view';
import {
EXPANDABLE_PANEL_CONTENT_TEST_ID,
EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID,
EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID,
EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID,
EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID,
} from '../../shared/components/test_ids';
jest.mock('../hooks/use_process_data'); jest.mock('../hooks/use_process_data');
const TOGGLE_ICON_TEST_ID = EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID(SESSION_PREVIEW_TEST_ID);
const TITLE_LINK_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(SESSION_PREVIEW_TEST_ID);
const TITLE_ICON_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID(SESSION_PREVIEW_TEST_ID);
const TITLE_TEXT_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(SESSION_PREVIEW_TEST_ID);
const CONTENT_TEST_ID = EXPANDABLE_PANEL_CONTENT_TEST_ID(SESSION_PREVIEW_TEST_ID);
const flyoutContextValue = { const flyoutContextValue = {
openLeftPanel: jest.fn(), openLeftPanel: jest.fn(),
} as unknown as ExpandableFlyoutContext; } as unknown as ExpandableFlyoutContext;
@ -58,26 +42,6 @@ describe('SessionPreview', () => {
jest.resetAllMocks(); jest.resetAllMocks();
}); });
it('should render wrapper component', () => {
jest.mocked(useProcessData).mockReturnValue({
processName: 'process1',
userName: 'user1',
startAt: '2022-01-01T00:00:00.000Z',
ruleName: 'rule1',
ruleId: 'id',
workdir: '/path/to/workdir',
command: 'command1',
});
renderSessionPreview();
expect(screen.queryByTestId(TOGGLE_ICON_TEST_ID)).not.toBeInTheDocument();
expect(screen.getByTestId(TITLE_LINK_TEST_ID)).toBeInTheDocument();
expect(screen.getByTestId(TITLE_ICON_TEST_ID)).toBeInTheDocument();
expect(screen.getByTestId(CONTENT_TEST_ID)).toBeInTheDocument();
expect(screen.queryByTestId(TITLE_TEXT_TEST_ID)).not.toBeInTheDocument();
});
it('renders session preview with all data', () => { it('renders session preview with all data', () => {
jest.mocked(useProcessData).mockReturnValue({ jest.mocked(useProcessData).mockReturnValue({
processName: 'process1', processName: 'process1',
@ -95,7 +59,7 @@ describe('SessionPreview', () => {
expect(screen.getByText('started')).toBeInTheDocument(); expect(screen.getByText('started')).toBeInTheDocument();
expect(screen.getByText('process1')).toBeInTheDocument(); expect(screen.getByText('process1')).toBeInTheDocument();
expect(screen.getByText('at')).toBeInTheDocument(); expect(screen.getByText('at')).toBeInTheDocument();
expect(screen.getByText('2022-01-01T00:00:00Z')).toBeInTheDocument(); expect(screen.getByText('Jan 1, 2022 @ 00:00:00.000')).toBeInTheDocument();
expect(screen.getByText('with rule')).toBeInTheDocument(); expect(screen.getByText('with rule')).toBeInTheDocument();
expect(screen.getByText('rule1')).toBeInTheDocument(); expect(screen.getByText('rule1')).toBeInTheDocument();
expect(screen.getByText('by')).toBeInTheDocument(); expect(screen.getByText('by')).toBeInTheDocument();
@ -122,29 +86,4 @@ describe('SessionPreview', () => {
expect(screen.queryByText('with rule')).not.toBeInTheDocument(); expect(screen.queryByText('with rule')).not.toBeInTheDocument();
expect(screen.queryByText('by')).not.toBeInTheDocument(); expect(screen.queryByText('by')).not.toBeInTheDocument();
}); });
it('should navigate to left section Visualize tab when clicking on title', () => {
jest.mocked(useProcessData).mockReturnValue({
processName: 'process1',
userName: 'user1',
startAt: '2022-01-01T00:00:00.000Z',
ruleName: 'rule1',
ruleId: 'id',
workdir: '/path/to/workdir',
command: 'command1',
});
const { getByTestId } = renderSessionPreview();
getByTestId(TITLE_LINK_TEST_ID).click();
expect(flyoutContextValue.openLeftPanel).toHaveBeenCalledWith({
id: LeftPanelKey,
path: { tab: LeftPanelVisualizeTab, subTab: SESSION_VIEW_ID },
params: {
id: panelContextValue.eventId,
indexName: panelContextValue.indexName,
scopeId: panelContextValue.scopeId,
},
});
});
}); });

View file

@ -6,24 +6,19 @@
*/ */
import { EuiCode, EuiIcon, useEuiTheme } from '@elastic/eui'; import { EuiCode, EuiIcon, useEuiTheme } from '@elastic/eui';
import React, { useMemo, type FC, useCallback } from 'react'; import React, { useMemo, type FC } from 'react';
import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; import { SESSION_PREVIEW_TEST_ID } from './test_ids';
import { ExpandablePanel } from '../../shared/components/expandable_panel';
import { SIGNAL_RULE_NAME_FIELD_NAME } from '../../../timelines/components/timeline/body/renderers/constants';
import { useRightPanelContext } from '../context'; import { useRightPanelContext } from '../context';
import { SIGNAL_RULE_NAME_FIELD_NAME } from '../../../timelines/components/timeline/body/renderers/constants';
import { PreferenceFormattedDate } from '../../../common/components/formatted_date'; import { PreferenceFormattedDate } from '../../../common/components/formatted_date';
import { useProcessData } from '../hooks/use_process_data'; import { useProcessData } from '../hooks/use_process_data';
import { SESSION_PREVIEW_TEST_ID } from './test_ids';
import { import {
SESSION_PREVIEW_COMMAND_TEXT, SESSION_PREVIEW_COMMAND_TEXT,
SESSION_PREVIEW_PROCESS_TEXT, SESSION_PREVIEW_PROCESS_TEXT,
SESSION_PREVIEW_RULE_TEXT, SESSION_PREVIEW_RULE_TEXT,
SESSION_PREVIEW_TIME_TEXT, SESSION_PREVIEW_TIME_TEXT,
SESSION_PREVIEW_TITLE,
} from './translations'; } from './translations';
import { LeftPanelKey, LeftPanelVisualizeTab } from '../../left';
import { RenderRuleName } from '../../../timelines/components/timeline/body/renderers/formatted_field_helpers'; import { RenderRuleName } from '../../../timelines/components/timeline/body/renderers/formatted_field_helpers';
import { SESSION_VIEW_ID } from '../../left/components/session_view';
/** /**
* One-off helper to make sure that inline values are rendered consistently * One-off helper to make sure that inline values are rendered consistently
@ -42,26 +37,10 @@ const ValueContainer: FC<{ text?: string }> = ({ text, children }) => (
); );
/** /**
* Renders session preview under visualistions section in the flyout right EuiPanel * Renders session preview under Visualizations section in the flyout right EuiPanel
*/ */
export const SessionPreview: FC = () => { export const SessionPreview: FC = () => {
const { eventId, indexName, scopeId } = useRightPanelContext(); const { eventId, scopeId } = useRightPanelContext();
const { openLeftPanel } = useExpandableFlyoutContext();
const goToSessionViewTab = useCallback(() => {
openLeftPanel({
id: LeftPanelKey,
path: {
tab: LeftPanelVisualizeTab,
subTab: SESSION_VIEW_ID,
},
params: {
id: eventId,
indexName,
scopeId,
},
});
}, [eventId, openLeftPanel, indexName, scopeId]);
const { processName, userName, startAt, ruleName, ruleId, workdir, command } = useProcessData(); const { processName, userName, startAt, ruleName, ruleId, workdir, command } = useProcessData();
const { euiTheme } = useEuiTheme(); const { euiTheme } = useEuiTheme();
@ -124,25 +103,16 @@ export const SessionPreview: FC = () => {
}, [command, workdir]); }, [command, workdir]);
return ( return (
<ExpandablePanel <div data-test-subj={SESSION_PREVIEW_TEST_ID}>
header={{ <ValueContainer>
title: SESSION_PREVIEW_TITLE, <EuiIcon type="user" />
iconType: 'arrowStart', &nbsp;
callback: goToSessionViewTab, <span style={emphasisStyles}>{userName}</span>
}} </ValueContainer>
data-test-subj={SESSION_PREVIEW_TEST_ID} {processNameFragment}
> {timeFragment}
<div> {ruleFragment}
<ValueContainer> {commandFragment}
<EuiIcon type="user" /> </div>
&nbsp;
<span style={emphasisStyles}>{userName}</span>
</ValueContainer>
{processNameFragment}
{timeFragment}
{ruleFragment}
{commandFragment}
</div>
</ExpandablePanel>
); );
}; };

View file

@ -0,0 +1,110 @@
/*
* 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 { render, screen } from '@testing-library/react';
import { TestProviders } from '../../../common/mock';
import React from 'react';
import { RightPanelContext } from '../context';
import { SessionPreviewContainer } from './session_preview_container';
import { useSessionPreview } from '../hooks/use_session_preview';
import { useLicense } from '../../../common/hooks/use_license';
import { SESSION_PREVIEW_TEST_ID } from './test_ids';
import {
EXPANDABLE_PANEL_CONTENT_TEST_ID,
EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID,
EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID,
EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID,
EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID,
} from '../../shared/components/test_ids';
import { mockGetFieldsData } from '../mocks/mock_context';
jest.mock('../hooks/use_session_preview');
jest.mock('../../../common/hooks/use_license');
const panelContextValue = {
dataAsNestedObject: null,
getFieldsData: mockGetFieldsData,
} as unknown as RightPanelContext;
const sessionViewConfig = {
index: {},
sessionEntityId: 'sessionEntityId',
sessionStartTime: 'sessionStartTime',
};
const TEST_ID = SESSION_PREVIEW_TEST_ID;
const ERROR_TEST_ID = `${SESSION_PREVIEW_TEST_ID}Error`;
const UPSELL_TEST_ID = `${SESSION_PREVIEW_TEST_ID}UpSell`;
const renderSessionPreview = () =>
render(
<TestProviders>
<RightPanelContext.Provider value={panelContextValue}>
<SessionPreviewContainer />
</RightPanelContext.Provider>
</TestProviders>
);
describe('SessionPreviewContainer', () => {
afterEach(() => {
jest.resetAllMocks();
});
it('should render component and link in header', () => {
(useSessionPreview as jest.Mock).mockReturnValue(sessionViewConfig);
(useLicense as jest.Mock).mockReturnValue({ isEnterprise: () => true });
const { getByTestId, queryByTestId } = renderSessionPreview();
expect(getByTestId(TEST_ID)).toBeInTheDocument();
expect(queryByTestId(ERROR_TEST_ID)).not.toBeInTheDocument();
expect(queryByTestId(UPSELL_TEST_ID)).not.toBeInTheDocument();
expect(
getByTestId(EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(SESSION_PREVIEW_TEST_ID))
).toBeInTheDocument();
expect(
screen.queryByTestId(EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID(SESSION_PREVIEW_TEST_ID))
).not.toBeInTheDocument();
expect(
screen.getByTestId(EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID(SESSION_PREVIEW_TEST_ID))
).toBeInTheDocument();
expect(
screen.getByTestId(EXPANDABLE_PANEL_CONTENT_TEST_ID(SESSION_PREVIEW_TEST_ID))
).toBeInTheDocument();
expect(
screen.queryByTestId(EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(SESSION_PREVIEW_TEST_ID))
).not.toBeInTheDocument();
});
it('should render error message and text in header if no sessionConfig', () => {
(useSessionPreview as jest.Mock).mockReturnValue(null);
(useLicense as jest.Mock).mockReturnValue({ isEnterprise: () => true });
const { getByTestId, queryByTestId } = renderSessionPreview();
expect(queryByTestId(TEST_ID)).not.toBeInTheDocument();
expect(getByTestId(ERROR_TEST_ID)).toBeInTheDocument();
expect(queryByTestId(UPSELL_TEST_ID)).not.toBeInTheDocument();
expect(
getByTestId(EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(SESSION_PREVIEW_TEST_ID))
).toBeInTheDocument();
});
it('should render error message and text in header if no correct license', () => {
(useSessionPreview as jest.Mock).mockReturnValue(sessionViewConfig);
(useLicense as jest.Mock).mockReturnValue({ isEnterprise: () => false });
const { getByTestId, queryByTestId } = renderSessionPreview();
expect(queryByTestId(TEST_ID)).not.toBeInTheDocument();
expect(queryByTestId(ERROR_TEST_ID)).not.toBeInTheDocument();
expect(getByTestId(UPSELL_TEST_ID)).toBeInTheDocument();
expect(
getByTestId(EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(SESSION_PREVIEW_TEST_ID))
).toBeInTheDocument();
});
});

View file

@ -0,0 +1,86 @@
/*
* 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, { type FC, useCallback } from 'react';
import { TimelineTabs } from '@kbn/securitysolution-data-table';
import { useDispatch } from 'react-redux';
import { useLicense } from '../../../common/hooks/use_license';
import { SessionPreview } from './session_preview';
import { useSessionPreview } from '../hooks/use_session_preview';
import { useInvestigateInTimeline } from '../../../detections/components/alerts_table/timeline_actions/use_investigate_in_timeline';
import { useRightPanelContext } from '../context';
import { ALERTS_ACTIONS } from '../../../common/lib/apm/user_actions';
import { ExpandablePanel } from '../../shared/components/expandable_panel';
import { SESSION_PREVIEW_TEST_ID } from './test_ids';
import {
SESSION_PREVIEW_ERROR,
SESSION_PREVIEW_TITLE,
SESSION_PREVIEW_UPSELL,
} from './translations';
import { useStartTransaction } from '../../../common/lib/apm/use_start_transaction';
import { setActiveTabTimeline } from '../../../timelines/store/timeline/actions';
import { getScopedActions } from '../../../helpers';
const timelineId = 'timeline-1';
/**
* Checks if the SessionView component is available, if so render it or else render an error message
*/
export const SessionPreviewContainer: FC = () => {
const { dataAsNestedObject, getFieldsData } = useRightPanelContext();
// decide whether to show the session view or not
const sessionViewConfig = useSessionPreview({ getFieldsData });
const isEnterprisePlus = useLicense().isEnterprise();
const isEnabled = sessionViewConfig && isEnterprisePlus;
const dispatch = useDispatch();
const { startTransaction } = useStartTransaction();
const scopedActions = getScopedActions(timelineId);
const { investigateInTimelineAlertClick } = useInvestigateInTimeline({
ecsRowData: dataAsNestedObject,
});
const goToSessionViewTab = useCallback(() => {
// open timeline
investigateInTimelineAlertClick();
// open session view tab
startTransaction({ name: ALERTS_ACTIONS.OPEN_SESSION_VIEW });
if (sessionViewConfig !== null) {
dispatch(setActiveTabTimeline({ id: timelineId, activeTab: TimelineTabs.session }));
if (scopedActions) {
dispatch(scopedActions.updateSessionViewConfig({ id: timelineId, sessionViewConfig }));
}
}
}, [
dispatch,
investigateInTimelineAlertClick,
scopedActions,
sessionViewConfig,
startTransaction,
]);
const noSessionMessage = !isEnterprisePlus ? (
<div data-test-subj={`${SESSION_PREVIEW_TEST_ID}UpSell`}>{SESSION_PREVIEW_UPSELL}</div>
) : !sessionViewConfig ? (
<div data-test-subj={`${SESSION_PREVIEW_TEST_ID}Error`}>{SESSION_PREVIEW_ERROR}</div>
) : null;
return (
<ExpandablePanel
header={{
title: SESSION_PREVIEW_TITLE,
iconType: 'timeline',
...(isEnabled && { callback: goToSessionViewTab }),
}}
data-test-subj={SESSION_PREVIEW_TEST_ID}
>
{isEnabled ? <SessionPreview /> : noSessionMessage}
</ExpandablePanel>
);
};

View file

@ -80,8 +80,6 @@ export const SUMMARY_ROW_VALUE_TEST_ID = (dataTestSubj: string) => `${dataTestSu
/* Insights Entities */ /* Insights Entities */
export const INSIGHTS_ENTITIES_TEST_ID = 'securitySolutionDocumentDetailsFlyoutInsightsEntities'; export const INSIGHTS_ENTITIES_TEST_ID = 'securitySolutionDocumentDetailsFlyoutInsightsEntities';
export const TECHNICAL_PREVIEW_ICON_TEST_ID =
'securitySolutionDocumentDetailsFlyoutTechnicalPreviewIcon';
export const ENTITIES_USER_OVERVIEW_TEST_ID = export const ENTITIES_USER_OVERVIEW_TEST_ID =
'securitySolutionDocumentDetailsFlyoutEntitiesUserOverview'; 'securitySolutionDocumentDetailsFlyoutEntitiesUserOverview';
export const ENTITIES_USER_OVERVIEW_LINK_TEST_ID = `${ENTITIES_USER_OVERVIEW_TEST_ID}Link`; export const ENTITIES_USER_OVERVIEW_LINK_TEST_ID = `${ENTITIES_USER_OVERVIEW_TEST_ID}Link`;
@ -126,13 +124,7 @@ export const INSIGHTS_PREVALENCE_ROW_TEST_ID =
export const VISUALIZATIONS_SECTION_TEST_ID = 'securitySolutionDocumentDetailsVisualizationsTitle'; export const VISUALIZATIONS_SECTION_TEST_ID = 'securitySolutionDocumentDetailsVisualizationsTitle';
export const VISUALIZATIONS_SECTION_HEADER_TEST_ID = export const VISUALIZATIONS_SECTION_HEADER_TEST_ID =
'securitySolutionDocumentDetailsVisualizationsTitleHeader'; 'securitySolutionDocumentDetailsVisualizationsTitleHeader';
export const ANALYZER_PREVIEW_TEST_ID = 'securitySolutionDocumentDetailsAnalyzerPreview';
/* Visualizations analyzer preview */
export const ANALYZER_PREVIEW_TEST_ID = 'securitySolutionDocumentDetailsAnalayzerPreview';
/* Visualizations session preview */
export const SESSION_PREVIEW_TEST_ID = 'securitySolutionDocumentDetailsSessionPreview'; export const SESSION_PREVIEW_TEST_ID = 'securitySolutionDocumentDetailsSessionPreview';
/* Response section */ /* Response section */

View file

@ -145,6 +145,13 @@ export const CORRELATIONS_TITLE = i18n.translate(
{ defaultMessage: 'Correlations' } { defaultMessage: 'Correlations' }
); );
export const CORRELATIONS_ERROR = i18n.translate(
'xpack.securitySolution.flyout.documentDetails.correlations.error',
{
defaultMessage: 'No correlations data available',
}
);
export const PREVALENCE_TITLE = i18n.translate( export const PREVALENCE_TITLE = i18n.translate(
'xpack.securitySolution.flyout.documentDetails.prevalenceTitle', 'xpack.securitySolution.flyout.documentDetails.prevalenceTitle',
{ defaultMessage: 'Prevalence' } { defaultMessage: 'Prevalence' }
@ -195,6 +202,13 @@ export const ANALYZER_PREVIEW_TITLE = i18n.translate(
{ defaultMessage: 'Analyzer preview' } { defaultMessage: 'Analyzer preview' }
); );
export const ANALYZER_PREVIEW_ERROR = i18n.translate(
'xpack.securitySolution.flyout.documentDetails.analyzerPreview.error',
{
defaultMessage: 'No analyzer graph data available',
}
);
export const SHARE = i18n.translate('xpack.securitySolution.flyout.documentDetails.share', { export const SHARE = i18n.translate('xpack.securitySolution.flyout.documentDetails.share', {
defaultMessage: 'Share Alert', defaultMessage: 'Share Alert',
}); });
@ -213,6 +227,21 @@ export const SESSION_PREVIEW_TITLE = i18n.translate(
} }
); );
export const SESSION_PREVIEW_UPSELL = i18n.translate(
'xpack.securitySolution.flyout.documentDetails.sessionPreview.upsell',
{
defaultMessage:
'Session preview is disabled because your license does not support it. Please upgrade your license.',
}
);
export const SESSION_PREVIEW_ERROR = i18n.translate(
'xpack.securitySolution.flyout.documentDetails.sessionPreview.error',
{
defaultMessage: 'No session view data available',
}
);
export const SESSION_PREVIEW_PROCESS_TEXT = i18n.translate( export const SESSION_PREVIEW_PROCESS_TEXT = i18n.translate(
'xpack.securitySolution.flyout.documentDetails.sessionPreview.processText', 'xpack.securitySolution.flyout.documentDetails.sessionPreview.processText',
{ {

View file

@ -7,13 +7,13 @@
import React from 'react'; import React from 'react';
import { EuiSpacer } from '@elastic/eui'; import { EuiSpacer } from '@elastic/eui';
import { AnalyzerPreviewContainer } from './analyzer_preview_container';
import { SessionPreviewContainer } from './session_preview_container';
import { ExpandableSection } from './expandable_section'; import { ExpandableSection } from './expandable_section';
import { VISUALIZATIONS_SECTION_TEST_ID } from './test_ids'; import { VISUALIZATIONS_SECTION_TEST_ID } from './test_ids';
import { VISUALIZATIONS_TITLE } from './translations'; import { VISUALIZATIONS_TITLE } from './translations';
import { AnalyzerPreview } from './analyzer_preview';
import { SessionPreview } from './session_preview';
export interface VisualizatioinsSectionProps { export interface VisualizationsSectionProps {
/** /**
* Boolean to allow the component to be expanded or collapsed on first render * Boolean to allow the component to be expanded or collapsed on first render
*/ */
@ -23,7 +23,7 @@ export interface VisualizatioinsSectionProps {
/** /**
* Visualizations section in overview. It contains analyzer preview and session view preview. * Visualizations section in overview. It contains analyzer preview and session view preview.
*/ */
export const VisualizationsSection: React.FC<VisualizatioinsSectionProps> = ({ export const VisualizationsSection: React.FC<VisualizationsSectionProps> = ({
expanded = false, expanded = false,
}) => { }) => {
return ( return (
@ -32,11 +32,11 @@ export const VisualizationsSection: React.FC<VisualizatioinsSectionProps> = ({
title={VISUALIZATIONS_TITLE} title={VISUALIZATIONS_TITLE}
data-test-subj={VISUALIZATIONS_SECTION_TEST_ID} data-test-subj={VISUALIZATIONS_SECTION_TEST_ID}
> >
<SessionPreview /> <SessionPreviewContainer />
<EuiSpacer /> <EuiSpacer />
<AnalyzerPreview /> <AnalyzerPreviewContainer />
</ExpandableSection> </ExpandableSection>
); );
}; };

View file

@ -0,0 +1,44 @@
/*
* 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 { RenderHookResult } from '@testing-library/react-hooks';
import { renderHook } from '@testing-library/react-hooks';
import type { UseSessionPreviewParams } from './use_session_preview';
import { useSessionPreview } from './use_session_preview';
import type { SessionViewConfig } from '@kbn/securitysolution-data-table/common/types';
import type { GetFieldsData } from '../../../common/hooks/use_get_fields_data';
describe('useSessionPreview', () => {
let hookResult: RenderHookResult<UseSessionPreviewParams, SessionViewConfig | null>;
it(`should return a session view config object`, () => {
const getFieldsData: GetFieldsData = (field: string) => field;
hookResult = renderHook((props: UseSessionPreviewParams) => useSessionPreview(props), {
initialProps: { getFieldsData },
});
expect(hookResult.result.current).toEqual({
index: 'kibana.alert.ancestors.index',
investigatedAlertId: '_id',
jumpToCursor: 'kibana.alert.original_time',
jumpToEntityId: 'process.entity_id',
sessionEntityId: 'process.entry_leader.entity_id',
sessionStartTime: 'process.entry_leader.start',
});
});
it(`should return null if data isn't ready for session view`, () => {
const getFieldsData: GetFieldsData = (field: string) => '';
hookResult = renderHook((props: UseSessionPreviewParams) => useSessionPreview(props), {
initialProps: { getFieldsData },
});
expect(hookResult.result.current).toEqual(null);
});
});

View file

@ -0,0 +1,46 @@
/*
* 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 { SessionViewConfig } from '@kbn/securitysolution-data-table/common/types';
import type { GetFieldsData } from '../../../common/hooks/use_get_fields_data';
import { getField } from '../../shared/utils';
export interface UseSessionPreviewParams {
/**
* Retrieves searchHit values for the provided field
*/
getFieldsData: GetFieldsData;
}
/**
* Hook that returns the session view configuration if the session view is available for the alert
*/
export const useSessionPreview = ({
getFieldsData,
}: UseSessionPreviewParams): SessionViewConfig | null => {
const _id = getField(getFieldsData('_id'));
const index =
getField(getFieldsData('kibana.alert.ancestors.index')) || getField(getFieldsData('_index'));
const entryLeaderEntityId = getField(getFieldsData('process.entry_leader.entity_id'));
const entryLeaderStart = getField(getFieldsData('process.entry_leader.start'));
const entityId = getField(getFieldsData('process.entity_id'));
const time =
getField(getFieldsData('kibana.alert.original_time')) || getField(getFieldsData('timestamp'));
if (!index || !entryLeaderEntityId || !entryLeaderStart) {
return null;
}
return {
index,
sessionEntityId: entryLeaderEntityId,
sessionStartTime: entryLeaderStart,
...(entityId && { jumpToEntityId: entityId }),
...(time && { jumpToCursor: time }),
...(_id && { investigatedAlertId: _id }),
};
};

View file

@ -26,7 +26,8 @@ import { getNewRule } from '../../../../objects/rule';
import { ALERTS_URL } from '../../../../urls/navigation'; import { ALERTS_URL } from '../../../../urls/navigation';
import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule';
describe( // TODO enable once the visualize tabs are back
describe.skip(
'Alert details expandable flyout left panel analyzer graph', 'Alert details expandable flyout left panel analyzer graph',
{ tags: [tag.ESS, tag.BROKEN_IN_SERVERLESS] }, { tags: [tag.ESS, tag.BROKEN_IN_SERVERLESS] },
() => { () => {

View file

@ -42,7 +42,7 @@ describe(
openEntitiesTab(); openEntitiesTab();
}); });
it('should display analyzer graph and node list under Insights Entities', () => { it('should display host details and user details under Insights Entities', () => {
cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB) cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB)
.should('be.visible') .should('be.visible')
.and('have.text', 'Insights'); .and('have.text', 'Insights');

View file

@ -23,7 +23,8 @@ import { getNewRule } from '../../../../objects/rule';
import { ALERTS_URL } from '../../../../urls/navigation'; import { ALERTS_URL } from '../../../../urls/navigation';
import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule';
describe( // TODO enable once the visualize tabs are back
describe.skip(
'Alert details expandable flyout left panel session view', 'Alert details expandable flyout left panel session view',
{ tags: [tag.ESS, tag.BROKEN_IN_SERVERLESS] }, { tags: [tag.ESS, tag.BROKEN_IN_SERVERLESS] },
() => { () => {

View file

@ -16,7 +16,7 @@ import {
import { import {
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ABOUT_SECTION_CONTENT, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ABOUT_SECTION_CONTENT,
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ABOUT_SECTION_HEADER, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ABOUT_SECTION_HEADER,
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ANALYZER_PREVIEW_CONTENT, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ANALYZER_PREVIEW_CONTAINER,
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_DETAILS, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_DETAILS,
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_TITLE, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_TITLE,
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_OPEN_RULE_PREVIEW_BUTTON, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_OPEN_RULE_PREVIEW_BUTTON,
@ -42,7 +42,7 @@ import {
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_TITLE, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_TITLE,
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_DETAILS, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_DETAILS,
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_TITLE, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_TITLE,
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_SESSION_PREVIEW_CONTENT, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_SESSION_PREVIEW_CONTAINER,
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_TABLE_FIELD_CELL, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_TABLE_FIELD_CELL,
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_TABLE_VALUE_CELL, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_TABLE_VALUE_CELL,
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_RESPONSE_SECTION_EMPTY_RESPONSE, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_RESPONSE_SECTION_EMPTY_RESPONSE,
@ -142,13 +142,15 @@ describe(
cy.log('analyzer graph preview'); cy.log('analyzer graph preview');
cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ANALYZER_PREVIEW_CONTENT).scrollIntoView(); cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ANALYZER_PREVIEW_CONTAINER).scrollIntoView();
cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ANALYZER_PREVIEW_CONTENT).should('be.visible'); cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ANALYZER_PREVIEW_CONTAINER).should(
'be.visible'
);
cy.log('session view preview'); cy.log('session view preview');
cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_SESSION_PREVIEW_CONTENT).scrollIntoView(); cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_SESSION_PREVIEW_CONTAINER).scrollIntoView();
cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_SESSION_PREVIEW_CONTENT).should('be.visible'); cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_SESSION_PREVIEW_CONTAINER).should('be.visible');
}); });
}); });

View file

@ -32,15 +32,15 @@ import {
INSIGHTS_THREAT_INTELLIGENCE_TEST_ID, INSIGHTS_THREAT_INTELLIGENCE_TEST_ID,
INSIGHTS_CORRELATIONS_TEST_ID, INSIGHTS_CORRELATIONS_TEST_ID,
INSIGHTS_PREVALENCE_TEST_ID, INSIGHTS_PREVALENCE_TEST_ID,
ANALYZER_PREVIEW_TEST_ID,
SUMMARY_ROW_VALUE_TEST_ID, SUMMARY_ROW_VALUE_TEST_ID,
INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID, INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID,
INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID, INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID,
INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID, INSIGHTS_CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID,
INSIGHTS_CORRELATIONS_RELATED_CASES_TEST_ID, INSIGHTS_CORRELATIONS_RELATED_CASES_TEST_ID,
INSIGHTS_ENTITIES_TEST_ID, INSIGHTS_ENTITIES_TEST_ID,
SESSION_PREVIEW_TEST_ID,
REASON_DETAILS_PREVIEW_BUTTON_TEST_ID, REASON_DETAILS_PREVIEW_BUTTON_TEST_ID,
ANALYZER_PREVIEW_TEST_ID,
SESSION_PREVIEW_TEST_ID,
} from '@kbn/security-solution-plugin/public/flyout/right/components/test_ids'; } from '@kbn/security-solution-plugin/public/flyout/right/components/test_ids';
import { getDataTestSubjectSelector } from '../../helpers/common'; import { getDataTestSubjectSelector } from '../../helpers/common';
@ -154,9 +154,9 @@ export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_VALUES =
export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_VISUALIZATIONS_SECTION_HEADER = export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_VISUALIZATIONS_SECTION_HEADER =
getDataTestSubjectSelector(VISUALIZATIONS_SECTION_HEADER_TEST_ID); getDataTestSubjectSelector(VISUALIZATIONS_SECTION_HEADER_TEST_ID);
export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ANALYZER_PREVIEW_CONTENT = export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ANALYZER_PREVIEW_CONTAINER =
getDataTestSubjectSelector(EXPANDABLE_PANEL_CONTENT_TEST_ID(ANALYZER_PREVIEW_TEST_ID)); getDataTestSubjectSelector(EXPANDABLE_PANEL_CONTENT_TEST_ID(ANALYZER_PREVIEW_TEST_ID));
export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_SESSION_PREVIEW_CONTENT = export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_SESSION_PREVIEW_CONTAINER =
getDataTestSubjectSelector(EXPANDABLE_PANEL_CONTENT_TEST_ID(SESSION_PREVIEW_TEST_ID)); getDataTestSubjectSelector(EXPANDABLE_PANEL_CONTENT_TEST_ID(SESSION_PREVIEW_TEST_ID));
/* Response section */ /* Response section */