Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security Solution] Expandably flyout - Enable expandable flyout for generic events #176332

Merged
merged 7 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ export const allowedExperimentalValues = Object.freeze({
* Enables expandable flyout in create rule page, alert preview
*/
expandableFlyoutInCreateRuleEnabled: true,
/**
* Enables expandable flyout for event type documents
*/
expandableEventFlyoutEnabled: false,
/*
* Enables new Set of filters on the Alerts page.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,28 @@ import type { VFC } from 'react';
import React, { useMemo } from 'react';
import { css } from '@emotion/react';
import type { LeftPanelPaths } from '.';
import { tabs } from './tabs';
import type { LeftPanelTabType } from './tabs';
import { FlyoutBody } from '../../shared/components/flyout_body';

export interface PanelContentProps {
/**
* Id of the tab selected in the parent component to display its content
*/
selectedTabId: LeftPanelPaths;
/**
* Tabs display at the top of left panel
*/
tabs: LeftPanelTabType[];
}

/**
* Document details expandable flyout left section. Appears after the user clicks on the expand details button in the right section.
* 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, tabs }) => {
const selectedTabContent = useMemo(() => {
return tabs.filter((tab) => tab.visible).find((tab) => tab.id === selectedTabId)?.content;
}, [selectedTabId]);
return tabs.find((tab) => tab.id === selectedTabId)?.content;
}, [selectedTabId, tabs]);

return (
<FlyoutBody
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ import type { VFC } from 'react';
import React, { memo } from 'react';
import { css } from '@emotion/react';
import type { LeftPanelPaths } from '.';
import { tabs } from './tabs';
import { FlyoutHeader } from '../../shared/components/flyout_header';
import type { LeftPanelTabType } from './tabs';
import { getField } from '../shared/utils';
import { EventKind } from '../shared/constants/event_kinds';
import { useLeftPanelContext } from './context';

export interface PanelHeaderProps {
/**
Expand All @@ -23,17 +26,23 @@ export interface PanelHeaderProps {
* @param selected
*/
setSelectedTabId: (selected: LeftPanelPaths) => void;
/**
* Tabs display at the top of left panel
*/
tabs: LeftPanelTabType[];
}

/**
* Header at the top of the left section.
* Displays the investigation and insights tabs (visualize is hidden for 8.9).
* Displays the insights, investigation and response tabs (visualize is hidden for 8.9+).
*/
export const PanelHeader: VFC<PanelHeaderProps> = memo(({ selectedTabId, setSelectedTabId }) => {
const onSelectedTabChanged = (id: LeftPanelPaths) => setSelectedTabId(id);
const renderTabs = tabs
.filter((tab) => tab.visible)
.map((tab, index) => (
export const PanelHeader: VFC<PanelHeaderProps> = memo(
({ selectedTabId, setSelectedTabId, tabs }) => {
const { getFieldsData } = useLeftPanelContext();
const isEventKindSignal = getField(getFieldsData('event.kind')) === EventKind.signal;

const onSelectedTabChanged = (id: LeftPanelPaths) => setSelectedTabId(id);
const renderTabs = tabs.map((tab, index) => (
<EuiTab
onClick={() => onSelectedTabChanged(tab.id)}
isSelected={tab.id === selectedTabId}
Expand All @@ -44,19 +53,20 @@ export const PanelHeader: VFC<PanelHeaderProps> = memo(({ selectedTabId, setSele
</EuiTab>
));

return (
<FlyoutHeader
css={css`
background-color: ${useEuiBackgroundColor('subdued')};
padding-bottom: 0 !important;
border-block-end: none !important;
`}
>
<EuiTabs size="l" expand>
{renderTabs}
</EuiTabs>
</FlyoutHeader>
);
});
return (
<FlyoutHeader
css={css`
background-color: ${useEuiBackgroundColor('subdued')};
padding-bottom: 0 !important;
border-block-end: none !important;
`}
>
<EuiTabs size="l" expand={isEventKindSignal}>
{renderTabs}
</EuiTabs>
</FlyoutHeader>
);
}
);

PanelHeader.displayName = 'PanelHeader';
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import type { FlyoutPanelProps, PanelPath } from '@kbn/expandable-flyout';
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
import { PanelHeader } from './header';
import { PanelContent } from './content';
import type { LeftPanelTabsType } from './tabs';
import { tabs } from './tabs';
import type { LeftPanelTabType } from './tabs';
import * as tabs from './tabs';
import { getField } from '../shared/utils';
import { EventKind } from '../shared/constants/event_kinds';
import { useLeftPanelContext } from './context';

export type LeftPanelPaths = 'visualize' | 'insights' | 'investigation' | 'response';
Expand All @@ -34,16 +36,24 @@ export interface LeftPanelProps extends FlyoutPanelProps {

export const LeftPanel: FC<Partial<LeftPanelProps>> = memo(({ path }) => {
const { openLeftPanel } = useExpandableFlyoutApi();
const { eventId, indexName, scopeId } = useLeftPanelContext();
const { eventId, indexName, scopeId, getFieldsData } = useLeftPanelContext();
const eventKind = getField(getFieldsData('event.kind'));

const tabsDisplayed = useMemo(
() =>
eventKind === EventKind.signal
? [tabs.insightsTab, tabs.investigationTab, tabs.responseTab]
: [tabs.insightsTab],
[eventKind]
);

const selectedTabId = useMemo(() => {
const visibleTabs = tabs.filter((tab) => tab.visible);
const defaultTab = visibleTabs[0].id;
const defaultTab = tabsDisplayed[0].id;
if (!path) return defaultTab;
return visibleTabs.map((tab) => tab.id).find((tabId) => tabId === path.tab) ?? defaultTab;
}, [path]);
return tabsDisplayed.map((tab) => tab.id).find((tabId) => tabId === path.tab) ?? defaultTab;
}, [path, tabsDisplayed]);

const setSelectedTabId = (tabId: LeftPanelTabsType[number]['id']) => {
const setSelectedTabId = (tabId: LeftPanelTabType['id']) => {
openLeftPanel({
id: DocumentDetailsLeftPanelKey,
path: {
Expand All @@ -59,8 +69,12 @@ export const LeftPanel: FC<Partial<LeftPanelProps>> = memo(({ path }) => {

return (
<>
<PanelHeader selectedTabId={selectedTabId} setSelectedTabId={setSelectedTabId} />
<PanelContent selectedTabId={selectedTabId} />
<PanelHeader
selectedTabId={selectedTabId}
setSelectedTabId={setSelectedTabId}
tabs={tabsDisplayed}
/>
<PanelContent selectedTabId={selectedTabId} tabs={tabsDisplayed} />
</>
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,61 +20,57 @@ import {
} from './test_ids';
import { ResponseTab } from './tabs/response_tab';

export type LeftPanelTabsType = Array<{
export interface LeftPanelTabType {
id: LeftPanelPaths;
'data-test-subj': string;
name: ReactElement;
content: React.ReactElement;
visible: boolean;
}>;
}

export const tabs: LeftPanelTabsType = [
{
id: 'visualize',
'data-test-subj': VISUALIZE_TAB_TEST_ID,
name: (
<FormattedMessage
id="xpack.securitySolution.flyout.left.visualize.tabLabel"
defaultMessage="Visualize"
/>
),
content: <VisualizeTab />,
visible: false,
},
{
id: 'insights',
'data-test-subj': INSIGHTS_TAB_TEST_ID,
name: (
<FormattedMessage
id="xpack.securitySolution.flyout.left.insights.tabLabel"
defaultMessage="Insights"
/>
),
content: <InsightsTab />,
visible: true,
},
{
id: 'investigation',
'data-test-subj': INVESTIGATION_TAB_TEST_ID,
name: (
<FormattedMessage
id="xpack.securitySolution.flyout.left.investigations.tabLabel"
defaultMessage="Investigation"
/>
),
content: <InvestigationTab />,
visible: true,
},
{
id: 'response',
'data-test-subj': RESPONSE_TAB_TEST_ID,
name: (
<FormattedMessage
id="xpack.securitySolution.flyout.left.response.tabLabel"
defaultMessage="Response"
/>
),
content: <ResponseTab />,
visible: true,
},
];
export const visualizeTab: LeftPanelTabType = {
id: 'visualize',
'data-test-subj': VISUALIZE_TAB_TEST_ID,
name: (
<FormattedMessage
id="xpack.securitySolution.flyout.left.visualize.tabLabel"
defaultMessage="Visualize"
/>
),
content: <VisualizeTab />,
};

export const insightsTab: LeftPanelTabType = {
id: 'insights',
'data-test-subj': INSIGHTS_TAB_TEST_ID,
name: (
<FormattedMessage
id="xpack.securitySolution.flyout.left.insights.tabLabel"
defaultMessage="Insights"
/>
),
content: <InsightsTab />,
};

export const investigationTab: LeftPanelTabType = {
id: 'investigation',
'data-test-subj': INVESTIGATION_TAB_TEST_ID,
name: (
<FormattedMessage
id="xpack.securitySolution.flyout.left.investigations.tabLabel"
defaultMessage="Investigation"
/>
),
content: <InvestigationTab />,
};

export const responseTab: LeftPanelTabType = {
id: 'response',
'data-test-subj': RESPONSE_TAB_TEST_ID,
name: (
<FormattedMessage
id="xpack.securitySolution.flyout.left.response.tabLabel"
defaultMessage="Response"
/>
),
content: <ResponseTab />,
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
* 2.0.
*/

import React, { memo, useCallback } from 'react';

import React, { memo, useCallback, useMemo } from 'react';
import { EuiButtonGroup, EuiSpacer } from '@elastic/eui';
import type { EuiButtonGroupOptionProps } from '@elastic/eui/src/components/button/button_group/button_group';
import { i18n } from '@kbn/i18n';
Expand All @@ -28,6 +27,8 @@ import {
} from '../components/threat_intelligence_details';
import { PREVALENCE_TAB_ID, PrevalenceDetails } from '../components/prevalence_details';
import { CORRELATIONS_TAB_ID, CorrelationsDetails } from '../components/correlations_details';
import { getField } from '../../shared/utils';
import { EventKind } from '../../shared/constants/event_kinds';

const insightsButtons: EuiButtonGroupOptionProps[] = [
{
Expand Down Expand Up @@ -76,11 +77,25 @@ const insightsButtons: EuiButtonGroupOptionProps[] = [
* Insights view displayed in the document details expandable flyout left section
*/
export const InsightsTab: React.FC = memo(() => {
const { eventId, indexName, scopeId } = useLeftPanelContext();
const { eventId, indexName, scopeId, getFieldsData } = useLeftPanelContext();
const isEventKindSignal = getField(getFieldsData('event.kind')) === EventKind.signal;
const { openLeftPanel } = useExpandableFlyoutApi();
const panels = useExpandableFlyoutState();
const activeInsightsId = panels.left?.path?.subTab ?? ENTITIES_TAB_ID;

// insight tabs based on whether document is alert or non-alert
// alert: entities, threat intelligence, prevalence, correlations
// non-alert: entities, prevalence
const buttonGroup = useMemo(
() =>
isEventKindSignal
? insightsButtons
: insightsButtons.filter(
(tab) => tab.id === ENTITIES_TAB_ID || tab.id === PREVALENCE_TAB_ID
),
[isEventKindSignal]
);

const onChangeCompressed = useCallback(
(optionId: string) => {
openLeftPanel({
Expand All @@ -103,19 +118,17 @@ export const InsightsTab: React.FC = memo(() => {
<>
<EuiButtonGroup
color="primary"
name="coarsness"
legend={i18n.translate(
'xpack.securitySolution.flyout.left.insights.buttonGroupLegendLabel',
{
defaultMessage: 'Insights options',
}
{ defaultMessage: 'Insights options' }
)}
options={insightsButtons}
options={buttonGroup}
idSelected={activeInsightsId}
onChange={onChangeCompressed}
buttonSize="compressed"
isFullWidth
data-test-subj={INSIGHTS_TAB_BUTTON_GROUP_TEST_ID}
style={!isEventKindSignal ? { maxWidth: 300 } : undefined}
PhilippeOberti marked this conversation as resolved.
Show resolved Hide resolved
/>
<EuiSpacer size="m" />
{activeInsightsId === ENTITIES_TAB_ID && <EntitiesDetails />}
Expand Down
Loading