diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/left/components/host_details.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/left/components/host_details.tsx index d0450418536f9..cdc956595b4eb 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/left/components/host_details.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/left/components/host_details.tsx @@ -29,13 +29,16 @@ import { MISCONFIGURATION_INSIGHT_HOST_DETAILS, VULNERABILITIES_INSIGHT_HOST_DETAILS, } from '@kbn/cloud-security-posture-common/utils/ui_metrics'; +import { useHasMisconfigurations } from '@kbn/cloud-security-posture/src/hooks/use_has_misconfigurations'; +import { useHasVulnerabilities } from '@kbn/cloud-security-posture/src/hooks/use_has_vulnerabilities'; +import { useNonClosedAlerts } from '../../../../cloud_security_posture/hooks/use_non_closed_alerts'; import { ExpandablePanel } from '../../../shared/components/expandable_panel'; import type { RelatedUser } from '../../../../../common/search_strategy/security_solution/related_entities/related_users'; import type { RiskSeverity } from '../../../../../common/search_strategy'; import { HostOverview } from '../../../../overview/components/host_overview'; import { AnomalyTableProvider } from '../../../../common/components/ml/anomaly/anomaly_table_provider'; import { InspectButton, InspectButtonContainer } from '../../../../common/components/inspect'; -import { EntityType } from '../../../../../common/entity_analytics/types'; +import { EntityIdentifierFields, EntityType } from '../../../../../common/entity_analytics/types'; import { RiskScoreLevel } from '../../../../entity_analytics/components/severity/common'; import { DefaultFieldRenderer } from '../../../../timelines/components/field_renderers/default_renderer'; import { InputsModelId } from '../../../../common/store/inputs/constants'; @@ -74,9 +77,13 @@ import { MisconfigurationsInsight } from '../../shared/components/misconfigurati import { VulnerabilitiesInsight } from '../../shared/components/vulnerabilities_insight'; import { AlertCountInsight } from '../../shared/components/alert_count_insight'; import { DocumentEventTypes } from '../../../../common/lib/telemetry'; +import { useNavigateToHostDetails } from '../../../entity_details/host_right/hooks/use_navigate_to_host_details'; +import { useRiskScore } from '../../../../entity_analytics/api/hooks/use_risk_score'; +import { buildHostNamesFilter } from '../../../../../common/search_strategy'; const HOST_DETAILS_ID = 'entities-hosts-details'; const RELATED_USERS_ID = 'entities-hosts-related-users'; +const HOST_DETAILS_INSIGHTS_ID = 'host-details-insights'; const HostOverviewManage = manageQuery(HostOverview); const RelatedUsersManage = manageQuery(InspectButtonContainer); @@ -113,6 +120,14 @@ export const HostDetails: React.FC = ({ hostName, timestamp, s const { openPreviewPanel } = useExpandableFlyoutApi(); + const timerange = useMemo( + () => ({ + from, + to, + }), + [from, to] + ); + const narrowDateRange = useCallback( (score, interval) => { const fromTo = scoreIntervalToDateTime(score, interval); @@ -151,6 +166,40 @@ export const HostDetails: React.FC = ({ hostName, timestamp, s skip: selectedPatterns.length === 0, }); + const filterQuery = useMemo( + () => (hostName ? buildHostNamesFilter([hostName]) : undefined), + [hostName] + ); + const { data: hostRisk } = useRiskScore({ + filterQuery, + riskEntity: EntityType.host, + skip: hostName == null, + timerange, + }); + const hostRiskData = hostRisk && hostRisk.length > 0 ? hostRisk[0] : undefined; + const isRiskScoreExist = !!hostRiskData?.host.risk; + + const { hasNonClosedAlerts } = useNonClosedAlerts({ + field: EntityIdentifierFields.hostName, + value: hostName, + to, + from, + queryId: 'HostEntityOverview', + }); + const { hasMisconfigurationFindings } = useHasMisconfigurations('host.name', hostName); + const { hasVulnerabilitiesFindings } = useHasVulnerabilities('host.name', hostName); + + const { openDetailsPanel } = useNavigateToHostDetails({ + hostName, + scopeId, + isRiskScoreExist, + hasMisconfigurationFindings, + hasVulnerabilitiesFindings, + hasNonClosedAlerts, + isPreviewMode: true, // setting to true to always open a new host flyout + contextID: HOST_DETAILS_INSIGHTS_ID, + }); + const { loading: isRelatedUsersLoading, inspect: inspectRelatedUsers, @@ -342,18 +391,21 @@ export const HostDetails: React.FC = ({ hostName, timestamp, s fieldName={'host.name'} name={hostName} direction="column" + openDetailsPanel={openDetailsPanel} data-test-subj={HOST_DETAILS_ALERT_COUNT_TEST_ID} /> diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/left/components/user_details.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/left/components/user_details.tsx index 0e8c454c48807..b1565a232d9d9 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/left/components/user_details.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/left/components/user_details.tsx @@ -26,13 +26,15 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; import { MISCONFIGURATION_INSIGHT_USER_DETAILS } from '@kbn/cloud-security-posture-common/utils/ui_metrics'; +import { useHasMisconfigurations } from '@kbn/cloud-security-posture/src/hooks/use_has_misconfigurations'; +import { useNonClosedAlerts } from '../../../../cloud_security_posture/hooks/use_non_closed_alerts'; import { ExpandablePanel } from '../../../shared/components/expandable_panel'; import type { RelatedHost } from '../../../../../common/search_strategy/security_solution/related_entities/related_hosts'; import type { RiskSeverity } from '../../../../../common/search_strategy'; import { UserOverview } from '../../../../overview/components/user_overview'; import { AnomalyTableProvider } from '../../../../common/components/ml/anomaly/anomaly_table_provider'; import { InspectButton, InspectButtonContainer } from '../../../../common/components/inspect'; -import { EntityType } from '../../../../../common/entity_analytics/types'; +import { EntityIdentifierFields, EntityType } from '../../../../../common/entity_analytics/types'; import { RiskScoreLevel } from '../../../../entity_analytics/components/severity/common'; import { DefaultFieldRenderer } from '../../../../timelines/components/field_renderers/default_renderer'; import { CellActions } from '../../shared/components/cell_actions'; @@ -69,9 +71,13 @@ import type { NarrowDateRange } from '../../../../common/components/ml/types'; import { MisconfigurationsInsight } from '../../shared/components/misconfiguration_insight'; import { AlertCountInsight } from '../../shared/components/alert_count_insight'; import { DocumentEventTypes } from '../../../../common/lib/telemetry'; +import { useNavigateToUserDetails } from '../../../entity_details/user_right/hooks/use_navigate_to_user_details'; +import { useRiskScore } from '../../../../entity_analytics/api/hooks/use_risk_score'; +import { buildUserNamesFilter } from '../../../../../common/search_strategy'; const USER_DETAILS_ID = 'entities-users-details'; const RELATED_HOSTS_ID = 'entities-users-related-hosts'; +const USER_DETAILS_INSIGHTS_ID = 'user-details-insights'; const UserOverviewManage = manageQuery(UserOverview); const RelatedHostsManage = manageQuery(InspectButtonContainer); @@ -109,6 +115,14 @@ export const UserDetails: React.FC = ({ userName, timestamp, s const { openPreviewPanel } = useExpandableFlyoutApi(); + const timerange = useMemo( + () => ({ + from, + to, + }), + [from, to] + ); + const narrowDateRange = useCallback( (score, interval) => { const fromTo = scoreIntervalToDateTime(score, interval); @@ -138,6 +152,41 @@ export const UserDetails: React.FC = ({ userName, timestamp, s }); }, [openPreviewPanel, userName, scopeId, telemetry]); + const filterQuery = useMemo( + () => (userName ? buildUserNamesFilter([userName]) : undefined), + [userName] + ); + + const { data: userRisk } = useRiskScore({ + filterQuery, + riskEntity: EntityType.user, + timerange, + }); + const userRiskData = userRisk && userRisk.length > 0 ? userRisk[0] : undefined; + const isRiskScoreExist = !!userRiskData?.user.risk; + + const { hasMisconfigurationFindings } = useHasMisconfigurations( + EntityIdentifierFields.userName, + userName + ); + const { hasNonClosedAlerts } = useNonClosedAlerts({ + field: EntityIdentifierFields.userName, + value: userName, + to, + from, + queryId: USER_DETAILS_INSIGHTS_ID, + }); + + const { openDetailsPanel } = useNavigateToUserDetails({ + userName, + scopeId, + isRiskScoreExist, + hasMisconfigurationFindings, + hasNonClosedAlerts, + isPreviewMode: true, // setting to true to always open a new user flyout + contextID: USER_DETAILS_INSIGHTS_ID, + }); + const [isUserLoading, { inspect, userDetails, refetch }] = useObservedUserDetails({ id: userDetailsQueryId, startDate: from, @@ -339,12 +388,14 @@ export const UserDetails: React.FC = ({ userName, timestamp, s fieldName={'user.name'} name={userName} direction="column" + openDetailsPanel={openDetailsPanel} data-test-subj={USER_DETAILS_ALERT_COUNT_TEST_ID} /> diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/components/host_entity_overview.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/components/host_entity_overview.tsx index 3aeb910114b3e..a0b9e8906e955 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/components/host_entity_overview.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/components/host_entity_overview.tsx @@ -22,6 +22,9 @@ import { MISCONFIGURATION_INSIGHT_HOST_ENTITY_OVERVIEW, VULNERABILITIES_INSIGHT_HOST_ENTITY_OVERVIEW, } from '@kbn/cloud-security-posture-common/utils/ui_metrics'; +import { useHasMisconfigurations } from '@kbn/cloud-security-posture/src/hooks/use_has_misconfigurations'; +import { useHasVulnerabilities } from '@kbn/cloud-security-posture/src/hooks/use_has_vulnerabilities'; +import { useNonClosedAlerts } from '../../../../cloud_security_posture/hooks/use_non_closed_alerts'; import { buildHostNamesFilter } from '../../../../../common/search_strategy'; import { HOST_NAME_FIELD_NAME } from '../../../../timelines/components/timeline/body/renderers/constants'; import { useRiskScore } from '../../../../entity_analytics/api/hooks/use_risk_score'; @@ -31,7 +34,7 @@ import { FirstLastSeen, FirstLastSeenType, } from '../../../../common/components/first_last_seen/first_last_seen'; -import { EntityType } from '../../../../../common/entity_analytics/types'; +import { EntityIdentifierFields, EntityType } from '../../../../../common/entity_analytics/types'; import { getEmptyTagValue } from '../../../../common/components/empty_value'; import { DescriptionListStyled } from '../../../../common/components/page'; import { OverviewDescriptionList } from '../../../../common/components/overview_description_list'; @@ -62,8 +65,10 @@ import { PreviewLink } from '../../../shared/components/preview_link'; import { MisconfigurationsInsight } from '../../shared/components/misconfiguration_insight'; import { VulnerabilitiesInsight } from '../../shared/components/vulnerabilities_insight'; import { AlertCountInsight } from '../../shared/components/alert_count_insight'; +import { useNavigateToHostDetails } from '../../../entity_details/host_right/hooks/use_navigate_to_host_details'; const HOST_ICON = 'storage'; +const HOST_ENTITY_OVERVIEW_ID = 'host-entity-overview'; export interface HostEntityOverviewProps { /** @@ -111,6 +116,8 @@ export const HostEntityOverview: React.FC = ({ hostName skip: hostName == null, timerange, }); + const hostRiskData = hostRisk && hostRisk.length > 0 ? hostRisk[0] : undefined; + const isRiskScoreExist = !!hostRiskData?.host.risk; const [isHostDetailsLoading, { hostDetails }] = useHostDetails({ hostName, @@ -159,9 +166,8 @@ export const HostEntityOverview: React.FC = ({ hostName const { euiTheme } = useEuiTheme(); const xsFontSize = useEuiFontSize('xs').fontSize; - const [hostRiskLevel] = useMemo(() => { - const hostRiskData = hostRisk && hostRisk.length > 0 ? hostRisk[0] : undefined; - return [ + const [hostRiskLevel] = useMemo( + () => [ { title: ( @@ -181,8 +187,30 @@ export const HostEntityOverview: React.FC = ({ hostName ), }, - ]; - }, [hostRisk]); + ], + [hostRiskData] + ); + + const { hasNonClosedAlerts } = useNonClosedAlerts({ + field: EntityIdentifierFields.hostName, + value: hostName, + to, + from, + queryId: HOST_ENTITY_OVERVIEW_ID, + }); + const { hasMisconfigurationFindings } = useHasMisconfigurations('host.name', hostName); + const { hasVulnerabilitiesFindings } = useHasVulnerabilities('host.name', hostName); + + const { openDetailsPanel } = useNavigateToHostDetails({ + hostName, + scopeId, + isRiskScoreExist, + hasMisconfigurationFindings, + hasVulnerabilitiesFindings, + hasNonClosedAlerts, + isPreviewMode: true, // setting to true to always open a new host flyout + contextID: 'HostEntityOverview', + }); return ( = ({ hostName diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/components/user_entity_overview.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/components/user_entity_overview.tsx index fb09f70f17664..1370aaca6b2d1 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/components/user_entity_overview.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/right/components/user_entity_overview.tsx @@ -19,6 +19,8 @@ import { css } from '@emotion/react'; import { getOr } from 'lodash/fp'; import { i18n } from '@kbn/i18n'; import { MISCONFIGURATION_INSIGHT_USER_ENTITY_OVERVIEW } from '@kbn/cloud-security-posture-common/utils/ui_metrics'; +import { useHasMisconfigurations } from '@kbn/cloud-security-posture/src/hooks/use_has_misconfigurations'; +import { useNonClosedAlerts } from '../../../../cloud_security_posture/hooks/use_non_closed_alerts'; import { buildUserNamesFilter } from '../../../../../common/search_strategy'; import { useDocumentDetailsContext } from '../../shared/context'; import type { DescriptionList } from '../../../../../common/utility_types'; @@ -29,7 +31,7 @@ import { FirstLastSeen, FirstLastSeenType, } from '../../../../common/components/first_last_seen/first_last_seen'; -import { EntityType } from '../../../../../common/entity_analytics/types'; +import { EntityIdentifierFields, EntityType } from '../../../../../common/entity_analytics/types'; import { getEmptyTagValue } from '../../../../common/components/empty_value'; import { DescriptionListStyled } from '../../../../common/components/page'; import { OverviewDescriptionList } from '../../../../common/components/overview_description_list'; @@ -57,8 +59,10 @@ import { RiskScoreDocTooltip } from '../../../../overview/components/common'; import { PreviewLink } from '../../../shared/components/preview_link'; import { MisconfigurationsInsight } from '../../shared/components/misconfiguration_insight'; import { AlertCountInsight } from '../../shared/components/alert_count_insight'; +import { useNavigateToUserDetails } from '../../../entity_details/user_right/hooks/use_navigate_to_user_details'; const USER_ICON = 'user'; +const USER_ENTITY_OVERVIEW_ID = 'user-entity-overview'; export interface UserEntityOverviewProps { /** @@ -111,6 +115,30 @@ export const UserEntityOverview: React.FC = ({ userName riskEntity: EntityType.user, timerange, }); + const userRiskData = userRisk && userRisk.length > 0 ? userRisk[0] : undefined; + const isRiskScoreExist = !!userRiskData?.user.risk; + + const { hasMisconfigurationFindings } = useHasMisconfigurations( + EntityIdentifierFields.userName, + userName + ); + const { hasNonClosedAlerts } = useNonClosedAlerts({ + field: EntityIdentifierFields.userName, + value: userName, + to, + from, + queryId: USER_ENTITY_OVERVIEW_ID, + }); + + const { openDetailsPanel } = useNavigateToUserDetails({ + userName, + scopeId, + isRiskScoreExist, + hasMisconfigurationFindings, + hasNonClosedAlerts, + isPreviewMode: true, // setting to true to always open a new user flyout + contextID: 'UserEntityOverview', + }); const userDomainValue = useMemo( () => getField(getOr([], 'user.domain', userDetails)), @@ -152,10 +180,8 @@ export const UserEntityOverview: React.FC = ({ userName const { euiTheme } = useEuiTheme(); const xsFontSize = useEuiFontSize('xs').fontSize; - const [userRiskLevel] = useMemo(() => { - const userRiskData = userRisk && userRisk.length > 0 ? userRisk[0] : undefined; - - return [ + const [userRiskLevel] = useMemo( + () => [ { title: ( @@ -175,8 +201,9 @@ export const UserEntityOverview: React.FC = ({ userName ), }, - ]; - }, [userRisk]); + ], + [userRiskData] + ); return ( = ({ userName diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/components/alert_count_insight.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/components/alert_count_insight.test.tsx index 023715e2680a1..e00d0ebd31b4f 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/components/alert_count_insight.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/components/alert_count_insight.test.tsx @@ -15,13 +15,16 @@ import { useEuiTheme } from '@elastic/eui'; import { INSIGHTS_ALERTS_COUNT_INVESTIGATE_IN_TIMELINE_BUTTON_TEST_ID, INSIGHTS_ALERTS_COUNT_TEXT_TEST_ID, + INSIGHTS_ALERTS_COUNT_NAVIGATION_BUTTON_TEST_ID, } from './test_ids'; import { useUserPrivileges } from '../../../../common/components/user_privileges'; import { useSignalIndex } from '../../../../detections/containers/detection_engine/alerts/use_signal_index'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; jest.mock('../../../../common/lib/kibana'); jest.mock('../../../../detections/containers/detection_engine/alerts/use_signal_index'); jest.mock('../../../../common/components/user_privileges'); +jest.mock('../../../../common/hooks/use_experimental_features'); jest.mock('react-router-dom', () => { const actual = jest.requireActual('react-router-dom'); @@ -65,10 +68,17 @@ const mockAlertData: ParsedAlertsData = { }, }; +const openDetailsPanel = jest.fn(); + const renderAlertCountInsight = () => { return render( - + ); }; @@ -77,6 +87,7 @@ describe('AlertCountInsight', () => { beforeEach(() => { (useSignalIndex as jest.Mock).mockReturnValue({ signalIndexName: '' }); (useUserPrivileges as jest.Mock).mockReturnValue({ timelinePrivileges: { read: true } }); + (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(false); }); it('renders', () => { @@ -96,6 +107,17 @@ describe('AlertCountInsight', () => { expect(queryByTestId(INSIGHTS_ALERTS_COUNT_TEXT_TEST_ID)).not.toBeInTheDocument(); }); + it('open entity details panel when clicking on the count if new navigation is enabled', () => { + (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); + (useAlertsByStatus as jest.Mock).mockReturnValue({ + isLoading: false, + items: mockAlertData, + }); + const { getByTestId } = renderAlertCountInsight(); + getByTestId(INSIGHTS_ALERTS_COUNT_NAVIGATION_BUTTON_TEST_ID).click(); + expect(openDetailsPanel).toHaveBeenCalled(); + }); + it('renders the count as text instead of button', () => { (useUserPrivileges as jest.Mock).mockReturnValue({ timelinePrivileges: { read: false } }); (useAlertsByStatus as jest.Mock).mockReturnValue({ diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/components/alert_count_insight.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/components/alert_count_insight.tsx index 58c6e78b74bda..2fb94b02b33c9 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/components/alert_count_insight.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/components/alert_count_insight.tsx @@ -14,6 +14,8 @@ import { EuiText, type EuiFlexGroupProps, useEuiTheme, + EuiLink, + EuiToolTip, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { InsightDistributionBar } from './insight_distribution_bar'; @@ -37,7 +39,14 @@ import { useUserPrivileges } from '../../../../common/components/user_privileges import { INSIGHTS_ALERTS_COUNT_INVESTIGATE_IN_TIMELINE_BUTTON_TEST_ID, INSIGHTS_ALERTS_COUNT_TEXT_TEST_ID, + INSIGHTS_ALERTS_COUNT_NAVIGATION_BUTTON_TEST_ID, } from './test_ids'; +import type { EntityDetailsPath } from '../../../entity_details/shared/components/left_panel/left_panel_header'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; +import { + CspInsightLeftPanelSubTab, + EntityDetailsLeftPanelTab, +} from '../../../entity_details/shared/components/left_panel/left_panel_header'; interface AlertCountInsightProps { /** @@ -56,6 +65,10 @@ interface AlertCountInsightProps { * The data-test-subj to use for the component. */ ['data-test-subj']?: string; + /** + * The function to open the details panel. + */ + openDetailsPanel: (path: EntityDetailsPath) => void; } /** @@ -95,6 +108,7 @@ export const AlertCountInsight: React.FC = ({ name, fieldName, direction, + openDetailsPanel, 'data-test-subj': dataTestSubj, }) => { const { euiTheme } = useEuiTheme(); @@ -102,6 +116,9 @@ export const AlertCountInsight: React.FC = ({ timelinePrivileges: { read: canUseTimeline }, } = useUserPrivileges(); + const isNewNavigationEnabled = useIsExperimentalFeatureEnabled( + 'newExpandableFlyoutNavigationEnabled' + ); const entityFilter = useMemo(() => ({ field: fieldName, value: name }), [fieldName, name]); const { to, from } = useGlobalTime(); const { signalIndexName } = useSignalIndex(); @@ -139,10 +156,35 @@ export const AlertCountInsight: React.FC = ({ [fieldName, name] ); - // renders either a button to open timeline or just plain text depending on the user's timeline privileges + // renders either a button to go to host alert details, open timeline or just plain text depending on the user's timeline privileges const alertCount = useMemo(() => { const formattedAlertCount = ; + if (isNewNavigationEnabled) { + return ( + + } + > + + openDetailsPanel({ + tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS, + subTab: CspInsightLeftPanelSubTab.ALERTS, + }) + } + > + {formattedAlertCount} + + + ); + } + if (!canUseTimeline) { return ( @@ -160,7 +202,7 @@ export const AlertCountInsight: React.FC = ({ {formattedAlertCount} ); - }, [canUseTimeline, dataProviders, totalAlertCount]); + }, [canUseTimeline, dataProviders, totalAlertCount, isNewNavigationEnabled, openDetailsPanel]); if (!isLoading && totalAlertCount === 0) return null; diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/components/misconfiguration_insight.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/components/misconfiguration_insight.test.tsx index 8976a01eedbc4..854a8e2aec0db 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/components/misconfiguration_insight.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/components/misconfiguration_insight.test.tsx @@ -18,19 +18,28 @@ import { HostPreviewPanelKey } from '../../../entity_details/host_right'; import { HOST_PREVIEW_BANNER } from '../../right/components/host_entity_overview'; import { UserPreviewPanelKey } from '../../../entity_details/user_right'; import { USER_PREVIEW_BANNER } from '../../right/components/user_entity_overview'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; jest.mock('@kbn/expandable-flyout'); jest.mock('@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview'); +jest.mock('../../../../common/hooks/use_experimental_features'); const hostName = 'test host'; const userName = 'test user'; const testId = 'test'; +const openDetailsPanel = jest.fn(); + const renderMisconfigurationsInsight = (fieldName: 'host.name' | 'user.name', value: string) => { return render( - + ); @@ -39,6 +48,7 @@ const renderMisconfigurationsInsight = (fieldName: 'host.name' | 'user.name', va describe('MisconfigurationsInsight', () => { beforeEach(() => { jest.mocked(useExpandableFlyoutApi).mockReturnValue(mockFlyoutApi); + (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(false); }); it('renders', () => { @@ -50,6 +60,16 @@ describe('MisconfigurationsInsight', () => { expect(getByTestId(`${testId}-distribution-bar`)).toBeInTheDocument(); }); + it('open entity details panel when clicking on the count if new navigation is enabled', () => { + (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); + (useMisconfigurationPreview as jest.Mock).mockReturnValue({ + data: { count: { passed: 1, failed: 2 } }, + }); + const { getByTestId } = renderMisconfigurationsInsight('host.name', hostName); + getByTestId(`${testId}-count`).click(); + expect(openDetailsPanel).toHaveBeenCalled(); + }); + it('renders null if no misconfiguration data found', () => { (useMisconfigurationPreview as jest.Mock).mockReturnValue({}); const { container } = renderMisconfigurationsInsight('host.name', hostName); diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/components/misconfiguration_insight.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/components/misconfiguration_insight.tsx index fcaec7b218c59..8bf6f587c4912 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/components/misconfiguration_insight.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/components/misconfiguration_insight.tsx @@ -6,7 +6,14 @@ */ import React, { useEffect, useMemo } from 'react'; -import { EuiFlexItem, type EuiFlexGroupProps, useEuiTheme, useGeneratedHtmlId } from '@elastic/eui'; +import { + EuiFlexItem, + type EuiFlexGroupProps, + useEuiTheme, + useGeneratedHtmlId, + EuiLink, + EuiToolTip, +} from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { css } from '@emotion/react'; import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview'; @@ -21,6 +28,12 @@ import { getFindingsStats } from '../../../../cloud_security_posture/components/ import { FormattedCount } from '../../../../common/components/formatted_number'; import { PreviewLink } from '../../../shared/components/preview_link'; import { useDocumentDetailsContext } from '../context'; +import type { EntityDetailsPath } from '../../../entity_details/shared/components/left_panel/left_panel_header'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; +import { + CspInsightLeftPanelSubTab, + EntityDetailsLeftPanelTab, +} from '../../../entity_details/shared/components/left_panel/left_panel_header'; interface MisconfigurationsInsightProps { /** @@ -43,6 +56,10 @@ interface MisconfigurationsInsightProps { * used to track the instance of this component, prefer kebab-case */ telemetryKey?: CloudSecurityUiCounters; + /** + * The function to open the details panel. + */ + openDetailsPanel: (path: EntityDetailsPath) => void; } /* @@ -54,6 +71,7 @@ export const MisconfigurationsInsight: React.FC = direction, 'data-test-subj': dataTestSubj, telemetryKey, + openDetailsPanel, }) => { const renderingId = useGeneratedHtmlId(); const { scopeId, isPreview } = useDocumentDetailsContext(); @@ -65,6 +83,10 @@ export const MisconfigurationsInsight: React.FC = pageSize: 1, }); + const isNewNavigationEnabled = useIsExperimentalFeatureEnabled( + 'newExpandableFlyoutNavigationEnabled' + ); + useEffect(() => { if (telemetryKey) { uiMetricService.trackUiMetric(METRIC_TYPE.COUNT, telemetryKey); @@ -92,18 +114,51 @@ export const MisconfigurationsInsight: React.FC = margin-bottom: ${euiTheme.size.xs}; `} > - - - + {isNewNavigationEnabled ? ( + + } + > + + openDetailsPanel({ + tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS, + subTab: CspInsightLeftPanelSubTab.MISCONFIGURATIONS, + }) + } + > + + + + ) : ( + + + + )} ), - [totalFindings, fieldName, name, scopeId, isPreview, dataTestSubj, euiTheme.size] + [ + totalFindings, + fieldName, + name, + scopeId, + isPreview, + dataTestSubj, + euiTheme.size, + isNewNavigationEnabled, + openDetailsPanel, + ] ); if (!hasMisconfigurationFindings) return null; diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/components/test_ids.ts b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/components/test_ids.ts index 8e54fb31c408e..918c3f9f8c295 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/components/test_ids.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/components/test_ids.ts @@ -14,5 +14,7 @@ const INSIGHTS_TEST_ID = `${PREFIX}Insights` as const; export const INSIGHTS_ALERTS_COUNT_TEXT_TEST_ID = `${INSIGHTS_TEST_ID}AlertsCount` as const; export const INSIGHTS_ALERTS_COUNT_INVESTIGATE_IN_TIMELINE_BUTTON_TEST_ID = `${INSIGHTS_TEST_ID}AlertsCountInvestigateInTimelineButton` as const; +export const INSIGHTS_ALERTS_COUNT_NAVIGATION_BUTTON_TEST_ID = + `${INSIGHTS_TEST_ID}AlertsCountNavigationButton` as const; export const FLYOUT_FOOTER_DROPDOWN_BUTTON_TEST_ID = `${PREFIX}FooterDropdownButton` as const; diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/components/vulnerabilities_insight.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/components/vulnerabilities_insight.test.tsx index cfac8703fbc89..ddccb4a4e62a9 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/components/vulnerabilities_insight.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/components/vulnerabilities_insight.test.tsx @@ -16,18 +16,25 @@ import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; import { mockContextValue } from '../mocks/mock_context'; import { HostPreviewPanelKey } from '../../../entity_details/host_right'; import { HOST_PREVIEW_BANNER } from '../../right/components/host_entity_overview'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; jest.mock('@kbn/expandable-flyout'); jest.mock('@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview'); +jest.mock('../../../../common/hooks/use_experimental_features'); const hostName = 'test host'; const testId = 'test'; +const openDetailsPanel = jest.fn(); const renderVulnerabilitiesInsight = () => { return render( - + ); @@ -48,7 +55,7 @@ describe('VulnerabilitiesInsight', () => { expect(getByTestId(`${testId}-distribution-bar`)).toBeInTheDocument(); }); - it('opens host preview when click on count badge', () => { + it('opens host preview when click on count badge if new navigation is disabled', () => { (useVulnerabilitiesPreview as jest.Mock).mockReturnValue({ data: { count: { CRITICAL: 1, HIGH: 2, MEDIUM: 1, LOW: 2, NONE: 2 } }, }); @@ -66,6 +73,16 @@ describe('VulnerabilitiesInsight', () => { }); }); + it('open entity details panel when clicking on the count if new navigation is enabled', () => { + (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); + (useVulnerabilitiesPreview as jest.Mock).mockReturnValue({ + data: { count: { CRITICAL: 1, HIGH: 2, MEDIUM: 1, LOW: 2, NONE: 2 } }, + }); + const { getByTestId } = renderVulnerabilitiesInsight(); + getByTestId(`${testId}-count`).click(); + expect(openDetailsPanel).toHaveBeenCalled(); + }); + it('renders null when data is not available', () => { (useVulnerabilitiesPreview as jest.Mock).mockReturnValue({}); diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/components/vulnerabilities_insight.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/components/vulnerabilities_insight.tsx index f1904823c5193..68bdd303ef2dd 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/components/vulnerabilities_insight.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/shared/components/vulnerabilities_insight.tsx @@ -6,7 +6,14 @@ */ import React, { useEffect, useMemo } from 'react'; -import { EuiFlexItem, type EuiFlexGroupProps, useEuiTheme, useGeneratedHtmlId } from '@elastic/eui'; +import { + EuiFlexItem, + type EuiFlexGroupProps, + useEuiTheme, + useGeneratedHtmlId, + EuiLink, + EuiToolTip, +} from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { css } from '@emotion/react'; import { useVulnerabilitiesPreview } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview'; @@ -22,6 +29,12 @@ import { InsightDistributionBar } from './insight_distribution_bar'; import { FormattedCount } from '../../../../common/components/formatted_number'; import { PreviewLink } from '../../../shared/components/preview_link'; import { useDocumentDetailsContext } from '../context'; +import { + EntityDetailsLeftPanelTab, + CspInsightLeftPanelSubTab, +} from '../../../entity_details/shared/components/left_panel/left_panel_header'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; +import type { EntityDetailsPath } from '../../../entity_details/shared/components/left_panel/left_panel_header'; interface VulnerabilitiesInsightProps { /** @@ -40,6 +53,10 @@ interface VulnerabilitiesInsightProps { * used to track the instance of this component, prefer kebab-case */ telemetryKey?: CloudSecurityUiCounters; + /** + * The function to open the details panel. + */ + openDetailsPanel: (path: EntityDetailsPath) => void; } /* @@ -50,6 +67,7 @@ export const VulnerabilitiesInsight: React.FC = ({ direction, 'data-test-subj': dataTestSubj, telemetryKey, + openDetailsPanel, }) => { const renderingId = useGeneratedHtmlId(); const { scopeId, isPreview } = useDocumentDetailsContext(); @@ -62,6 +80,10 @@ export const VulnerabilitiesInsight: React.FC = ({ pageSize: 1, }); + const isNewNavigationEnabled = useIsExperimentalFeatureEnabled( + 'newExpandableFlyoutNavigationEnabled' + ); + useEffect(() => { if (telemetryKey) { uiMetricService.trackUiMetric(METRIC_TYPE.COUNT, telemetryKey); @@ -109,18 +131,50 @@ export const VulnerabilitiesInsight: React.FC = ({ margin-bottom: ${euiTheme.size.xs}; `} > - - - + {isNewNavigationEnabled ? ( + + } + > + + openDetailsPanel({ + tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS, + subTab: CspInsightLeftPanelSubTab.VULNERABILITIES, + }) + } + > + + + + ) : ( + + + + )} ), - [totalVulnerabilities, hostName, scopeId, isPreview, dataTestSubj, euiTheme.size] + [ + totalVulnerabilities, + hostName, + scopeId, + isPreview, + dataTestSubj, + euiTheme.size, + isNewNavigationEnabled, + openDetailsPanel, + ] ); if (!hasVulnerabilitiesFindings) return null;