mirror of
https://github.com/elastic/kibana.git
synced 2025-04-25 02:09:32 -04:00
[Security Solution] expandable flyout - hide visualize tab in left section and open session view and analyzer in timeline (#164111)
This commit is contained in:
parent
0759c869d2
commit
acedd23097
30 changed files with 684 additions and 539 deletions
|
@ -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', () => {
|
||||||
|
|
|
@ -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} />}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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',
|
||||||
});
|
});
|
||||||
|
|
|
@ -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>;
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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']) => {
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -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();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
});
|
||||||
|
});
|
|
@ -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';
|
|
@ -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} />);
|
|
||||||
};
|
|
|
@ -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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -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';
|
|
|
@ -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', () => {
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -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',
|
|
||||||
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>
|
||||||
|
|
||||||
<span style={emphasisStyles}>{userName}</span>
|
|
||||||
</ValueContainer>
|
|
||||||
{processNameFragment}
|
|
||||||
{timeFragment}
|
|
||||||
{ruleFragment}
|
|
||||||
{commandFragment}
|
|
||||||
</div>
|
|
||||||
</ExpandablePanel>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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();
|
||||||
|
});
|
||||||
|
});
|
|
@ -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>
|
||||||
|
);
|
||||||
|
};
|
|
@ -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 */
|
||||||
|
|
|
@ -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',
|
||||||
{
|
{
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
|
@ -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 }),
|
||||||
|
};
|
||||||
|
};
|
|
@ -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] },
|
||||||
() => {
|
() => {
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -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] },
|
||||||
() => {
|
() => {
|
||||||
|
|
|
@ -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');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue