Skip to content

Commit

Permalink
[Security Solution] Update preview navigation in entity details flyout (
Browse files Browse the repository at this point in the history
elastic#204513)

## Summary

This PR enables navigation when an user is viewing a host or user
preview. The main navigation logic has been extracted to 2 hooks
`useNavigateToHostDetails` and `useNavigateToUserDetails`

To see the new navigation, enable feature flag
`newExpandableFlyoutNavigationEnabled`

**How navigation used to work:**

- When an entity details flyout is open, some sections have navigation
links to open the details panel in the left (i.e. risk contribution,
alert insights etc.)
- However, these navigations were disabled in a preview mode, and user
is limited to stay in the original flyout context

**Preview navigations now available**
- When the feature flag is on, navigation links is available
- For example, an user is on a host preview, and click on `alert`, a new
flyout will be opened (for the new host context) and the alert insights
details section will be available.
  • Loading branch information
christineweng authored Dec 19, 2024
1 parent a21fb6c commit 715c59d
Show file tree
Hide file tree
Showing 25 changed files with 872 additions and 173 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@ import React from 'react';
import { render } from '@testing-library/react';
import { AlertsPreview } from './alerts_preview';
import { TestProviders } from '../../../common/mock/test_providers';
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
import type { ParsedAlertsData } from '../../../overview/components/detection_response/alerts_by_status/types';
import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview';
import { useVulnerabilitiesPreview } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview';
import { useRiskScore } from '../../../entity_analytics/api/hooks/use_risk_score';

const mockAlertsData: ParsedAlertsData = {
open: {
Expand All @@ -35,18 +33,14 @@ const mockAlertsData: ParsedAlertsData = {
// Mock hooks
jest.mock('@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview');
jest.mock('@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview');
jest.mock('../../../entity_analytics/api/hooks/use_risk_score');
jest.mock('@kbn/expandable-flyout');

describe('AlertsPreview', () => {
const mockOpenLeftPanel = jest.fn();

beforeEach(() => {
(useExpandableFlyoutApi as jest.Mock).mockReturnValue({ openLeftPanel: mockOpenLeftPanel });
(useVulnerabilitiesPreview as jest.Mock).mockReturnValue({
data: { count: { CRITICAL: 0, HIGH: 1, MEDIUM: 1, LOW: 0, UNKNOWN: 0 } },
});
(useRiskScore as jest.Mock).mockReturnValue({ data: [{ host: { risk: 75 } }] });
(useMisconfigurationPreview as jest.Mock).mockReturnValue({
data: { count: { passed: 1, failed: 1 } },
});
Expand All @@ -58,7 +52,11 @@ describe('AlertsPreview', () => {
it('renders', () => {
const { getByTestId } = render(
<TestProviders>
<AlertsPreview alertsData={mockAlertsData} value="host1" field="host.name" />
<AlertsPreview
alertsData={mockAlertsData}
isLinkEnabled={true}
openDetailsPanel={mockOpenLeftPanel}
/>
</TestProviders>
);

Expand All @@ -68,7 +66,11 @@ describe('AlertsPreview', () => {
it('renders correct alerts number', () => {
const { getByTestId } = render(
<TestProviders>
<AlertsPreview alertsData={mockAlertsData} value="host1" field="host.name" />
<AlertsPreview
alertsData={mockAlertsData}
isLinkEnabled={true}
openDetailsPanel={mockOpenLeftPanel}
/>
</TestProviders>
);

Expand All @@ -78,7 +80,11 @@ describe('AlertsPreview', () => {
it('should render the correct number of distribution bar section based on the number of severities', () => {
const { queryAllByTestId } = render(
<TestProviders>
<AlertsPreview alertsData={mockAlertsData} value="host1" field="host.name" />
<AlertsPreview
alertsData={mockAlertsData}
isLinkEnabled={true}
openDetailsPanel={mockOpenLeftPanel}
/>
</TestProviders>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import React, { useMemo } from 'react';
import React, { useCallback, useMemo } from 'react';
import { capitalize } from 'lodash';
import type { EuiThemeComputed } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, EuiTitle, useEuiTheme } from '@elastic/eui';
Expand All @@ -18,8 +18,11 @@ import type {
} from '../../../overview/components/detection_response/alerts_by_status/types';
import { ExpandablePanel } from '../../../flyout/shared/components/expandable_panel';
import { getSeverityColor } from '../../../detections/components/alerts_kpis/severity_level_panel/helpers';
import { CspInsightLeftPanelSubTab } from '../../../flyout/entity_details/shared/components/left_panel/left_panel_header';
import { useNavigateEntityInsight } from '../../hooks/use_entity_insight';
import type { EntityDetailsPath } from '../../../flyout/entity_details/shared/components/left_panel/left_panel_header';
import {
CspInsightLeftPanelSubTab,
EntityDetailsLeftPanelTab,
} from '../../../flyout/entity_details/shared/components/left_panel/left_panel_header';

const AlertsCount = ({
alertsTotal,
Expand Down Expand Up @@ -58,14 +61,14 @@ const AlertsCount = ({

export const AlertsPreview = ({
alertsData,
field,
value,
isPreviewMode,
openDetailsPanel,
isLinkEnabled,
}: {
alertsData: ParsedAlertsData;
field: 'host.name' | 'user.name';
value: string;
isPreviewMode?: boolean;
openDetailsPanel: (path: EntityDetailsPath) => void;
isLinkEnabled: boolean;
}) => {
const { euiTheme } = useEuiTheme();

Expand All @@ -90,15 +93,16 @@ export const AlertsPreview = ({

const hasNonClosedAlerts = totalAlertsCount > 0;

const { goToEntityInsightTab } = useNavigateEntityInsight({
field,
value,
queryIdExtension: isPreviewMode ? 'ALERTS_PREVIEW_TRUE' : 'ALERTS_PREVIEW_FALSE',
subTab: CspInsightLeftPanelSubTab.ALERTS,
});
const goToEntityInsightTab = useCallback(() => {
openDetailsPanel({
tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS,
subTab: CspInsightLeftPanelSubTab.ALERTS,
});
}, [openDetailsPanel]);

const link = useMemo(
() =>
!isPreviewMode
isLinkEnabled
? {
callback: goToEntityInsightTab,
tooltip: (
Expand All @@ -109,7 +113,7 @@ export const AlertsPreview = ({
),
}
: undefined,
[isPreviewMode, goToEntityInsightTab]
[isLinkEnabled, goToEntityInsightTab]
);
return (
<ExpandablePanel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,20 @@ import { AlertsPreview } from './alerts/alerts_preview';
import { useGlobalTime } from '../../common/containers/use_global_time';
import { DETECTION_RESPONSE_ALERTS_BY_STATUS_ID } from '../../overview/components/detection_response/alerts_by_status/types';
import { useNonClosedAlerts } from '../hooks/use_non_closed_alerts';
import type { EntityDetailsPath } from '../../flyout/entity_details/shared/components/left_panel/left_panel_header';

export const EntityInsight = <T,>({
value,
field,
isPreviewMode,
isLinkEnabled,
openDetailsPanel,
}: {
value: string;
field: 'host.name' | 'user.name';
isPreviewMode?: boolean;
isLinkEnabled: boolean;
openDetailsPanel: (path: EntityDetailsPath) => void;
}) => {
const { euiTheme } = useEuiTheme();
const insightContent: React.ReactElement[] = [];
Expand Down Expand Up @@ -55,9 +60,9 @@ export const EntityInsight = <T,>({
<>
<AlertsPreview
alertsData={filteredAlertsData}
field={field}
value={value}
isPreviewMode={isPreviewMode}
isLinkEnabled={isLinkEnabled}
openDetailsPanel={openDetailsPanel}
/>
<EuiSpacer size="s" />
</>
Expand All @@ -67,14 +72,26 @@ export const EntityInsight = <T,>({
if (showMisconfigurationsPreview)
insightContent.push(
<>
<MisconfigurationsPreview value={value} field={field} isPreviewMode={isPreviewMode} />
<MisconfigurationsPreview
value={value}
field={field}
isPreviewMode={isPreviewMode}
isLinkEnabled={isLinkEnabled}
openDetailsPanel={openDetailsPanel}
/>
<EuiSpacer size="s" />
</>
);
if (showVulnerabilitiesPreview)
insightContent.push(
<>
<VulnerabilitiesPreview value={value} field={field} isPreviewMode={isPreviewMode} />
<VulnerabilitiesPreview
value={value}
field={field}
isPreviewMode={isPreviewMode}
isLinkEnabled={isLinkEnabled}
openDetailsPanel={openDetailsPanel}
/>
<EuiSpacer size="s" />
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,19 @@ import { render } from '@testing-library/react';
import { MisconfigurationsPreview } from './misconfiguration_preview';
import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview';
import { useVulnerabilitiesPreview } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview';
import { useRiskScore } from '../../../entity_analytics/api/hooks/use_risk_score';
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
import { TestProviders } from '../../../common/mock/test_providers';

// Mock hooks
jest.mock('@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview');
jest.mock('@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview');
jest.mock('../../../entity_analytics/api/hooks/use_risk_score');
jest.mock('@kbn/expandable-flyout');

describe('MisconfigurationsPreview', () => {
const mockOpenLeftPanel = jest.fn();

beforeEach(() => {
(useExpandableFlyoutApi as jest.Mock).mockReturnValue({ openLeftPanel: mockOpenLeftPanel });
(useVulnerabilitiesPreview as jest.Mock).mockReturnValue({
data: { count: { CRITICAL: 0, HIGH: 1, MEDIUM: 1, LOW: 0, UNKNOWN: 0 } },
});
(useRiskScore as jest.Mock).mockReturnValue({ data: [{ host: { risk: 75 } }] });
(useMisconfigurationPreview as jest.Mock).mockReturnValue({
data: { count: { passed: 1, failed: 1 } },
});
Expand All @@ -37,7 +31,12 @@ describe('MisconfigurationsPreview', () => {
it('renders', () => {
const { getByTestId } = render(
<TestProviders>
<MisconfigurationsPreview value="host1" field="host.name" />
<MisconfigurationsPreview
value="host1"
field="host.name"
isLinkEnabled={true}
openDetailsPanel={mockOpenLeftPanel}
/>
</TestProviders>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import React, { useEffect, useMemo } from 'react';
import React, { useCallback, useEffect, useMemo } from 'react';
import { css } from '@emotion/react';
import type { EuiThemeComputed } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, useEuiTheme, EuiTitle } from '@elastic/eui';
Expand All @@ -20,8 +20,11 @@ import {
uiMetricService,
} from '@kbn/cloud-security-posture-common/utils/ui_metrics';
import { ExpandablePanel } from '../../../flyout/shared/components/expandable_panel';
import { CspInsightLeftPanelSubTab } from '../../../flyout/entity_details/shared/components/left_panel/left_panel_header';
import { useNavigateEntityInsight } from '../../hooks/use_entity_insight';
import type { EntityDetailsPath } from '../../../flyout/entity_details/shared/components/left_panel/left_panel_header';
import {
CspInsightLeftPanelSubTab,
EntityDetailsLeftPanelTab,
} from '../../../flyout/entity_details/shared/components/left_panel/left_panel_header';

export const getFindingsStats = (passedFindingsStats: number, failedFindingsStats: number) => {
if (passedFindingsStats === 0 && failedFindingsStats === 0) return [];
Expand Down Expand Up @@ -88,10 +91,14 @@ export const MisconfigurationsPreview = ({
value,
field,
isPreviewMode,
isLinkEnabled,
openDetailsPanel,
}: {
value: string;
field: 'host.name' | 'user.name';
isPreviewMode?: boolean;
isLinkEnabled: boolean;
openDetailsPanel: (path: EntityDetailsPath) => void;
}) => {
const { hasMisconfigurationFindings, passedFindings, failedFindings } = useHasMisconfigurations(
field,
Expand All @@ -103,15 +110,16 @@ export const MisconfigurationsPreview = ({
}, []);
const { euiTheme } = useEuiTheme();

const { goToEntityInsightTab } = useNavigateEntityInsight({
field,
value,
queryIdExtension: 'MISCONFIGURATION_PREVIEW',
subTab: CspInsightLeftPanelSubTab.MISCONFIGURATIONS,
});
const goToEntityInsightTab = useCallback(() => {
openDetailsPanel({
tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS,
subTab: CspInsightLeftPanelSubTab.MISCONFIGURATIONS,
});
}, [openDetailsPanel]);

const link = useMemo(
() =>
!isPreviewMode
isLinkEnabled
? {
callback: goToEntityInsightTab,
tooltip: (
Expand All @@ -122,7 +130,7 @@ export const MisconfigurationsPreview = ({
),
}
: undefined,
[isPreviewMode, goToEntityInsightTab]
[isLinkEnabled, goToEntityInsightTab]
);
return (
<ExpandablePanel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,19 @@ import { render } from '@testing-library/react';
import { VulnerabilitiesPreview } from './vulnerabilities_preview';
import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview';
import { useVulnerabilitiesPreview } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview';
import { useRiskScore } from '../../../entity_analytics/api/hooks/use_risk_score';
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
import { TestProviders } from '../../../common/mock/test_providers';

// Mock hooks
jest.mock('@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview');
jest.mock('@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview');
jest.mock('../../../entity_analytics/api/hooks/use_risk_score');
jest.mock('@kbn/expandable-flyout');

describe('VulnerabilitiesPreview', () => {
const mockOpenLeftPanel = jest.fn();

beforeEach(() => {
(useExpandableFlyoutApi as jest.Mock).mockReturnValue({ openLeftPanel: mockOpenLeftPanel });
(useVulnerabilitiesPreview as jest.Mock).mockReturnValue({
data: { count: { CRITICAL: 0, HIGH: 1, MEDIUM: 1, LOW: 0, UNKNOWN: 0 } },
});
(useRiskScore as jest.Mock).mockReturnValue({ data: [{ host: { risk: 75 } }] });
(useMisconfigurationPreview as jest.Mock).mockReturnValue({
data: { count: { passed: 1, failed: 1 } },
});
Expand All @@ -37,7 +31,12 @@ describe('VulnerabilitiesPreview', () => {
it('renders', () => {
const { getByTestId } = render(
<TestProviders>
<VulnerabilitiesPreview value="host1" field="host.name" />
<VulnerabilitiesPreview
value="host1"
field="host.name"
isLinkEnabled={true}
openDetailsPanel={mockOpenLeftPanel}
/>
</TestProviders>
);

Expand Down
Loading

0 comments on commit 715c59d

Please sign in to comment.