[Cloud Security] Refactor Contextual Flyout (#200291)

## Summary

This PR is for reducing code duplication by Encapsulating Hooks,
Functions, constants that are used multiple times in a same manner
accross multiple files

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Maxim Kholod <maxim.kholod@elastic.co>
This commit is contained in:
Rickyanto Ang 2024-11-21 12:33:30 -08:00 committed by GitHub
parent 1fea109ecc
commit c842db549a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 360 additions and 463 deletions

View file

@ -0,0 +1,29 @@
/*
* 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 { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common';
import { useMisconfigurationPreview } from './use_misconfiguration_preview';
export const useHasMisconfigurations = (field: 'host.name' | 'user.name', value: string) => {
const { data } = useMisconfigurationPreview({
query: buildEntityFlyoutPreviewQuery(field, value),
sort: [],
enabled: true,
pageSize: 1,
});
const passedFindings = data?.count.passed || 0;
const failedFindings = data?.count.failed || 0;
const hasMisconfigurationFindings = passedFindings > 0 || failedFindings > 0;
return {
passedFindings,
failedFindings,
hasMisconfigurationFindings,
};
};

View file

@ -0,0 +1,39 @@
/*
* 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 { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common';
import { useVulnerabilitiesPreview } from './use_vulnerabilities_preview';
import { hasVulnerabilitiesData } from '../utils/vulnerability_helpers';
export const useHasVulnerabilities = (field: 'host.name' | 'user.name', value: string) => {
const { data: vulnerabilitiesData } = useVulnerabilitiesPreview({
query: buildEntityFlyoutPreviewQuery(field, value),
sort: [],
enabled: true,
pageSize: 1,
});
const {
CRITICAL = 0,
HIGH = 0,
MEDIUM = 0,
LOW = 0,
NONE = 0,
} = vulnerabilitiesData?.count || {};
const counts = {
critical: CRITICAL,
high: HIGH,
medium: MEDIUM,
low: LOW,
none: NONE,
};
const hasVulnerabilitiesFindings = hasVulnerabilitiesData(counts);
return { counts, hasVulnerabilitiesFindings };
};

View file

@ -58,7 +58,7 @@ describe('AlertsPreview', () => {
it('renders', () => {
const { getByTestId } = render(
<TestProviders>
<AlertsPreview alertsData={mockAlertsData} name="host1" fieldName="host.name" />
<AlertsPreview alertsData={mockAlertsData} value="host1" field="host.name" />
</TestProviders>
);
@ -68,7 +68,7 @@ describe('AlertsPreview', () => {
it('renders correct alerts number', () => {
const { getByTestId } = render(
<TestProviders>
<AlertsPreview alertsData={mockAlertsData} name="host1" fieldName="host.name" />
<AlertsPreview alertsData={mockAlertsData} value="host1" field="host.name" />
</TestProviders>
);
@ -78,7 +78,7 @@ 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} name="host1" fieldName="host.name" />
<AlertsPreview alertsData={mockAlertsData} value="host1" field="host.name" />
</TestProviders>
);

View file

@ -5,40 +5,21 @@
* 2.0.
*/
import React, { useCallback, useMemo } from 'react';
import React, { useMemo } from 'react';
import { capitalize } from 'lodash';
import type { EuiThemeComputed } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, EuiTitle, useEuiTheme } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { DistributionBar } from '@kbn/security-solution-distribution-bar';
import {
buildEntityFlyoutPreviewQuery,
getAbbreviatedNumber,
} from '@kbn/cloud-security-posture-common';
import { hasVulnerabilitiesData } from '@kbn/cloud-security-posture';
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 { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
import { getAbbreviatedNumber } from '@kbn/cloud-security-posture-common';
import type {
AlertsByStatus,
ParsedAlertsData,
} 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 type { HostRiskScore, UserRiskScore } from '../../../../common/search_strategy';
import {
buildHostNamesFilter,
buildUserNamesFilter,
RiskScoreEntity,
} from '../../../../common/search_strategy';
import { useRiskScore } from '../../../entity_analytics/api/hooks/use_risk_score';
import { FIRST_RECORD_PAGINATION } from '../../../entity_analytics/common';
import { HostDetailsPanelKey } from '../../../flyout/entity_details/host_details_left';
import {
EntityDetailsLeftPanelTab,
CspInsightLeftPanelSubTab,
} from '../../../flyout/entity_details/shared/components/left_panel/left_panel_header';
import { UserDetailsPanelKey } from '../../../flyout/entity_details/user_details_left';
import { CspInsightLeftPanelSubTab } from '../../../flyout/entity_details/shared/components/left_panel/left_panel_header';
import { useNavigateEntityInsight } from '../../hooks/use_entity_insight';
const AlertsCount = ({
alertsTotal,
@ -77,13 +58,13 @@ const AlertsCount = ({
export const AlertsPreview = ({
alertsData,
fieldName,
name,
field,
value,
isPreviewMode,
}: {
alertsData: ParsedAlertsData;
fieldName: string;
name: string;
field: 'host.name' | 'user.name';
value: string;
isPreviewMode?: boolean;
}) => {
const { euiTheme } = useEuiTheme();
@ -107,101 +88,14 @@ export const AlertsPreview = ({
const totalAlertsCount = alertStats.reduce((total, item) => total + item.count, 0);
const { data } = useMisconfigurationPreview({
query: buildEntityFlyoutPreviewQuery(fieldName, name),
sort: [],
enabled: true,
pageSize: 1,
ignore_unavailable: true,
});
const isUsingHostName = fieldName === 'host.name';
const passedFindings = data?.count.passed || 0;
const failedFindings = data?.count.failed || 0;
const hasMisconfigurationFindings = passedFindings > 0 || failedFindings > 0;
const { data: vulnerabilitiesData } = useVulnerabilitiesPreview({
query: buildEntityFlyoutPreviewQuery('host.name', name),
sort: [],
enabled: true,
pageSize: 1,
});
const {
CRITICAL = 0,
HIGH = 0,
MEDIUM = 0,
LOW = 0,
NONE = 0,
} = vulnerabilitiesData?.count || {};
const hasVulnerabilitiesFindings = hasVulnerabilitiesData({
critical: CRITICAL,
high: HIGH,
medium: MEDIUM,
low: LOW,
none: NONE,
});
const buildFilterQuery = useMemo(
() => (isUsingHostName ? buildHostNamesFilter([name]) : buildUserNamesFilter([name])),
[isUsingHostName, name]
);
const riskScoreState = useRiskScore({
riskEntity: isUsingHostName ? RiskScoreEntity.host : RiskScoreEntity.user,
filterQuery: buildFilterQuery,
onlyLatest: false,
pagination: FIRST_RECORD_PAGINATION,
});
const { data: hostRisk } = riskScoreState;
const riskData = hostRisk?.[0];
const isRiskScoreExist = isUsingHostName
? !!(riskData as HostRiskScore)?.host.risk
: !!(riskData as UserRiskScore)?.user.risk;
const hasNonClosedAlerts = totalAlertsCount > 0;
const { openLeftPanel } = useExpandableFlyoutApi();
const goToEntityInsightTab = useCallback(() => {
openLeftPanel({
id: isUsingHostName ? HostDetailsPanelKey : UserDetailsPanelKey,
params: isUsingHostName
? {
name,
isRiskScoreExist,
hasMisconfigurationFindings,
hasVulnerabilitiesFindings,
hasNonClosedAlerts,
path: {
tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS,
subTab: CspInsightLeftPanelSubTab.ALERTS,
},
}
: {
user: { name },
isRiskScoreExist,
hasMisconfigurationFindings,
hasNonClosedAlerts,
path: {
tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS,
subTab: CspInsightLeftPanelSubTab.ALERTS,
},
},
});
}, [
hasMisconfigurationFindings,
hasNonClosedAlerts,
hasVulnerabilitiesFindings,
isRiskScoreExist,
isUsingHostName,
name,
openLeftPanel,
]);
const { goToEntityInsightTab } = useNavigateEntityInsight({
field,
value,
queryIdExtension: 'ALERTS_PREVIEW',
subTab: CspInsightLeftPanelSubTab.ALERTS,
});
const link = useMemo(
() =>
!isPreviewMode

View file

@ -60,7 +60,7 @@ interface AlertsDetailsFields {
}
export const AlertsDetailsTable = memo(
({ fieldName, queryName }: { fieldName: 'host.name' | 'user.name'; queryName: string }) => {
({ field, value }: { field: 'host.name' | 'user.name'; value: string }) => {
useEffect(() => {
uiMetricService.trackUiMetric(
METRIC_TYPE.COUNT,
@ -90,7 +90,7 @@ export const AlertsDetailsTable = memo(
const { to, from } = useGlobalTime();
const { signalIndexName } = useSignalIndex();
const { data } = useQueryAlerts({
query: buildEntityAlertsQuery(fieldName, to, from, queryName, 500),
query: buildEntityAlertsQuery(field, to, from, value, 500),
queryName: ALERTS_QUERY_NAMES.BY_RULE_BY_STATUS,
indexName: signalIndexName,
});
@ -216,11 +216,11 @@ export const AlertsDetailsTable = memo(
[
{
title:
fieldName === 'host.name'
field === 'host.name'
? OPEN_IN_ALERTS_TITLE_HOSTNAME
: OPEN_IN_ALERTS_TITLE_USERNAME,
selectedOptions: [queryName],
fieldName,
selectedOptions: [value],
fieldName: field,
},
{
title: OPEN_IN_ALERTS_TITLE_STATUS,
@ -230,7 +230,7 @@ export const AlertsDetailsTable = memo(
],
true
),
[fieldName, openAlertsPageWithFilters, queryName]
[field, openAlertsPageWithFilters, value]
);
return (

View file

@ -42,7 +42,7 @@ function isCspFlyoutPanelProps(
}
export const InsightsTabCsp = memo(
({ name, fieldName }: { name: string; fieldName: 'host.name' | 'user.name' }) => {
({ value, field }: { value: string; field: 'host.name' | 'user.name' }) => {
const panels = useExpandableFlyoutState();
let hasMisconfigurationFindings = false;
@ -150,11 +150,11 @@ export const InsightsTabCsp = memo(
/>
<EuiSpacer size="xl" />
{activeInsightsId === CspInsightLeftPanelSubTab.MISCONFIGURATIONS ? (
<MisconfigurationFindingsDetailsTable fieldName={fieldName} queryName={name} />
<MisconfigurationFindingsDetailsTable field={field} value={value} />
) : activeInsightsId === CspInsightLeftPanelSubTab.VULNERABILITIES ? (
<VulnerabilitiesFindingsDetailsTable queryName={name} />
<VulnerabilitiesFindingsDetailsTable value={value} />
) : (
<AlertsDetailsTable fieldName={fieldName} queryName={name} />
<AlertsDetailsTable field={field} value={value} />
)}
</>
);

View file

@ -59,7 +59,7 @@ const getFindingsStats = (passedFindingsStats: number, failedFindingsStats: numb
* Insights view displayed in the document details expandable flyout left section
*/
export const MisconfigurationFindingsDetailsTable = memo(
({ fieldName, queryName }: { fieldName: 'host.name' | 'user.name'; queryName: string }) => {
({ field, value }: { field: 'host.name' | 'user.name'; value: string }) => {
useEffect(() => {
uiMetricService.trackUiMetric(
METRIC_TYPE.COUNT,
@ -68,7 +68,7 @@ export const MisconfigurationFindingsDetailsTable = memo(
}, []);
const { data } = useMisconfigurationFindings({
query: buildEntityFlyoutPreviewQuery(fieldName, queryName),
query: buildEntityFlyoutPreviewQuery(field, value),
sort: [],
enabled: true,
pageSize: 1,
@ -183,7 +183,7 @@ export const MisconfigurationFindingsDetailsTable = memo(
<EuiPanel hasShadow={false}>
<SecuritySolutionLinkAnchor
deepLinkId={SecurityPageName.cloudSecurityPostureFindings}
path={`${getFindingsPageUrl(queryName, fieldName)}`}
path={`${getFindingsPageUrl(value, field)}`}
target={'_blank'}
external={false}
onClick={() => {

View file

@ -44,7 +44,7 @@ interface VulnerabilitiesPackage extends Vulnerability {
};
}
export const VulnerabilitiesFindingsDetailsTable = memo(({ queryName }: { queryName: string }) => {
export const VulnerabilitiesFindingsDetailsTable = memo(({ value }: { value: string }) => {
useEffect(() => {
uiMetricService.trackUiMetric(
METRIC_TYPE.COUNT,
@ -53,7 +53,7 @@ export const VulnerabilitiesFindingsDetailsTable = memo(({ queryName }: { queryN
}, []);
const { data } = useVulnerabilitiesFindings({
query: buildEntityFlyoutPreviewQuery('host.name', queryName),
query: buildEntityFlyoutPreviewQuery('host.name', value),
sort: [],
enabled: true,
pageSize: 1,
@ -204,7 +204,7 @@ export const VulnerabilitiesFindingsDetailsTable = memo(({ queryName }: { queryN
<EuiPanel hasShadow={false}>
<SecuritySolutionLinkAnchor
deepLinkId={SecurityPageName.cloudSecurityPostureFindings}
path={`${getVulnerabilityUrl(queryName, 'host.name')}`}
path={`${getVulnerabilityUrl(value, 'host.name')}`}
target={'_blank'}
external={false}
onClick={() => {

View file

@ -7,97 +7,56 @@
import { EuiAccordion, EuiHorizontalRule, EuiSpacer, EuiTitle, useEuiTheme } from '@elastic/eui';
import React, { useMemo } from 'react';
import React from 'react';
import { css } from '@emotion/react';
import { FormattedMessage } from '@kbn/i18n-react';
import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview';
import { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common';
import { useVulnerabilitiesPreview } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview';
import { hasVulnerabilitiesData } from '@kbn/cloud-security-posture';
import { FILTER_CLOSED } from '../../../common/types';
import { useHasVulnerabilities } from '@kbn/cloud-security-posture/src/hooks/use_has_vulnerabilities';
import { useHasMisconfigurations } from '@kbn/cloud-security-posture/src/hooks/use_has_misconfigurations';
import { MisconfigurationsPreview } from './misconfiguration/misconfiguration_preview';
import { VulnerabilitiesPreview } from './vulnerabilities/vulnerabilities_preview';
import { AlertsPreview } from './alerts/alerts_preview';
import { useGlobalTime } from '../../common/containers/use_global_time';
import type { ParsedAlertsData } from '../../overview/components/detection_response/alerts_by_status/types';
import { DETECTION_RESPONSE_ALERTS_BY_STATUS_ID } from '../../overview/components/detection_response/alerts_by_status/types';
import { useAlertsByStatus } from '../../overview/components/detection_response/alerts_by_status/use_alerts_by_status';
import { useSignalIndex } from '../../detections/containers/detection_engine/alerts/use_signal_index';
import { useNonClosedAlerts } from '../hooks/use_non_closed_alerts';
export const EntityInsight = <T,>({
name,
fieldName,
value,
field,
isPreviewMode,
}: {
name: string;
fieldName: 'host.name' | 'user.name';
value: string;
field: 'host.name' | 'user.name';
isPreviewMode?: boolean;
}) => {
const { euiTheme } = useEuiTheme();
const insightContent: React.ReactElement[] = [];
const { data: dataMisconfiguration } = useMisconfigurationPreview({
query: buildEntityFlyoutPreviewQuery(fieldName, name),
sort: [],
enabled: true,
pageSize: 1,
});
const { hasMisconfigurationFindings: showMisconfigurationsPreview } = useHasMisconfigurations(
field,
value
);
const passedFindings = dataMisconfiguration?.count.passed || 0;
const failedFindings = dataMisconfiguration?.count.failed || 0;
const { hasVulnerabilitiesFindings } = useHasVulnerabilities(field, value);
const hasMisconfigurationFindings = passedFindings > 0 || failedFindings > 0;
const { data } = useVulnerabilitiesPreview({
query: buildEntityFlyoutPreviewQuery(fieldName, name),
sort: [],
enabled: true,
pageSize: 1,
});
const { CRITICAL = 0, HIGH = 0, MEDIUM = 0, LOW = 0, NONE = 0 } = data?.count || {};
const hasVulnerabilitiesFindings = hasVulnerabilitiesData({
critical: CRITICAL,
high: HIGH,
medium: MEDIUM,
low: LOW,
none: NONE,
});
const isVulnerabilitiesFindingForHost = hasVulnerabilitiesFindings && fieldName === 'host.name';
const { signalIndexName } = useSignalIndex();
const entityFilter = useMemo(() => ({ field: fieldName, value: name }), [fieldName, name]);
const showVulnerabilitiesPreview = hasVulnerabilitiesFindings && field === 'host.name';
const { to, from } = useGlobalTime();
const { items: alertsData } = useAlertsByStatus({
entityFilter,
signalIndexName,
queryId: DETECTION_RESPONSE_ALERTS_BY_STATUS_ID,
const { hasNonClosedAlerts: showAlertsPreview, filteredAlertsData } = useNonClosedAlerts({
field,
value,
to,
from,
queryId: DETECTION_RESPONSE_ALERTS_BY_STATUS_ID,
});
const filteredAlertsData: ParsedAlertsData = alertsData
? Object.fromEntries(Object.entries(alertsData).filter(([key]) => key !== FILTER_CLOSED))
: {};
const alertsOpenCount = filteredAlertsData?.open?.total || 0;
const alertsAcknowledgedCount = filteredAlertsData?.acknowledged?.total || 0;
const alertsCount = alertsOpenCount + alertsAcknowledgedCount;
if (alertsCount > 0) {
if (showAlertsPreview) {
insightContent.push(
<>
<AlertsPreview
alertsData={filteredAlertsData}
fieldName={fieldName}
name={name}
field={field}
value={value}
isPreviewMode={isPreviewMode}
/>
<EuiSpacer size="s" />
@ -105,34 +64,23 @@ export const EntityInsight = <T,>({
);
}
if (hasMisconfigurationFindings)
if (showMisconfigurationsPreview)
insightContent.push(
<>
<MisconfigurationsPreview
name={name}
fieldName={fieldName}
hasNonClosedAlerts={alertsCount > 0}
isPreviewMode={isPreviewMode}
/>
<MisconfigurationsPreview value={value} field={field} isPreviewMode={isPreviewMode} />
<EuiSpacer size="s" />
</>
);
if (isVulnerabilitiesFindingForHost && hasVulnerabilitiesFindings)
if (showVulnerabilitiesPreview)
insightContent.push(
<>
<VulnerabilitiesPreview
name={name}
isPreviewMode={isPreviewMode}
hasNonClosedAlerts={alertsCount > 0}
/>
<VulnerabilitiesPreview value={value} field={field} isPreviewMode={isPreviewMode} />
<EuiSpacer size="s" />
</>
);
return (
<>
{(insightContent.length > 0 ||
hasMisconfigurationFindings ||
(isVulnerabilitiesFindingForHost && hasVulnerabilitiesFindings)) && (
{insightContent.length > 0 && (
<>
<EuiAccordion
initialIsOpen={true}

View file

@ -37,7 +37,7 @@ describe('MisconfigurationsPreview', () => {
it('renders', () => {
const { getByTestId } = render(
<TestProviders>
<MisconfigurationsPreview name="host1" fieldName="host.name" />
<MisconfigurationsPreview value="host1" field="host.name" />
</TestProviders>
);

View file

@ -5,39 +5,23 @@
* 2.0.
*/
import React, { useCallback, useEffect, useMemo } from 'react';
import React, { 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';
import { FormattedMessage } from '@kbn/i18n-react';
import { DistributionBar } from '@kbn/security-solution-distribution-bar';
import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview';
import { useHasMisconfigurations } from '@kbn/cloud-security-posture/src/hooks/use_has_misconfigurations';
import { i18n } from '@kbn/i18n';
import { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common';
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
import { useVulnerabilitiesPreview } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview';
import { hasVulnerabilitiesData, statusColors } from '@kbn/cloud-security-posture';
import { statusColors } from '@kbn/cloud-security-posture';
import { METRIC_TYPE } from '@kbn/analytics';
import {
ENTITY_FLYOUT_WITH_MISCONFIGURATION_VISIT,
uiMetricService,
} from '@kbn/cloud-security-posture-common/utils/ui_metrics';
import { ExpandablePanel } from '../../../flyout/shared/components/expandable_panel';
import {
CspInsightLeftPanelSubTab,
EntityDetailsLeftPanelTab,
} from '../../../flyout/entity_details/shared/components/left_panel/left_panel_header';
import { UserDetailsPanelKey } from '../../../flyout/entity_details/user_details_left';
import { HostDetailsPanelKey } from '../../../flyout/entity_details/host_details_left';
import { useRiskScore } from '../../../entity_analytics/api/hooks/use_risk_score';
import { RiskScoreEntity } from '../../../../common/entity_analytics/risk_engine';
import type { HostRiskScore, UserRiskScore } from '../../../../common/search_strategy';
import { buildHostNamesFilter, buildUserNamesFilter } from '../../../../common/search_strategy';
const FIRST_RECORD_PAGINATION = {
cursorStart: 0,
querySize: 1,
};
import { CspInsightLeftPanelSubTab } from '../../../flyout/entity_details/shared/components/left_panel/left_panel_header';
import { useNavigateEntityInsight } from '../../hooks/use_entity_insight';
export const getFindingsStats = (passedFindingsStats: number, failedFindingsStats: number) => {
if (passedFindingsStats === 0 && failedFindingsStats === 0) return [];
@ -101,113 +85,30 @@ const MisconfigurationPreviewScore = ({
};
export const MisconfigurationsPreview = ({
name,
fieldName,
hasNonClosedAlerts = false,
value,
field,
isPreviewMode,
}: {
name: string;
fieldName: 'host.name' | 'user.name';
hasNonClosedAlerts?: boolean;
value: string;
field: 'host.name' | 'user.name';
isPreviewMode?: boolean;
}) => {
const { data } = useMisconfigurationPreview({
query: buildEntityFlyoutPreviewQuery(fieldName, name),
sort: [],
enabled: true,
pageSize: 1,
ignore_unavailable: true,
});
const isUsingHostName = fieldName === 'host.name';
const passedFindings = data?.count.passed || 0;
const failedFindings = data?.count.failed || 0;
const { hasMisconfigurationFindings, passedFindings, failedFindings } = useHasMisconfigurations(
field,
value
);
useEffect(() => {
uiMetricService.trackUiMetric(METRIC_TYPE.CLICK, ENTITY_FLYOUT_WITH_MISCONFIGURATION_VISIT);
}, []);
const { euiTheme } = useEuiTheme();
const hasMisconfigurationFindings = passedFindings > 0 || failedFindings > 0;
const { data: vulnerabilitiesData } = useVulnerabilitiesPreview({
query: buildEntityFlyoutPreviewQuery('host.name', name),
sort: [],
enabled: true,
pageSize: 1,
const { goToEntityInsightTab } = useNavigateEntityInsight({
field,
value,
queryIdExtension: 'MISCONFIGURATION_PREVIEW',
subTab: CspInsightLeftPanelSubTab.MISCONFIGURATIONS,
});
const {
CRITICAL = 0,
HIGH = 0,
MEDIUM = 0,
LOW = 0,
NONE = 0,
} = vulnerabilitiesData?.count || {};
const hasVulnerabilitiesFindings = hasVulnerabilitiesData({
critical: CRITICAL,
high: HIGH,
medium: MEDIUM,
low: LOW,
none: NONE,
});
const buildFilterQuery = useMemo(
() => (isUsingHostName ? buildHostNamesFilter([name]) : buildUserNamesFilter([name])),
[isUsingHostName, name]
);
const riskScoreState = useRiskScore({
riskEntity: isUsingHostName ? RiskScoreEntity.host : RiskScoreEntity.user,
filterQuery: buildFilterQuery,
onlyLatest: false,
pagination: FIRST_RECORD_PAGINATION,
});
const { data: hostRisk } = riskScoreState;
const riskData = hostRisk?.[0];
const isRiskScoreExist = isUsingHostName
? !!(riskData as HostRiskScore)?.host.risk
: !!(riskData as UserRiskScore)?.user.risk;
const { openLeftPanel } = useExpandableFlyoutApi();
const goToEntityInsightTab = useCallback(() => {
openLeftPanel({
id: isUsingHostName ? HostDetailsPanelKey : UserDetailsPanelKey,
params: isUsingHostName
? {
name,
isRiskScoreExist,
hasMisconfigurationFindings,
hasVulnerabilitiesFindings,
hasNonClosedAlerts,
path: {
tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS,
subTab: CspInsightLeftPanelSubTab.MISCONFIGURATIONS,
},
}
: {
user: { name },
isRiskScoreExist,
hasMisconfigurationFindings,
hasNonClosedAlerts,
path: {
tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS,
subTab: CspInsightLeftPanelSubTab.MISCONFIGURATIONS,
},
},
});
}, [
hasMisconfigurationFindings,
hasNonClosedAlerts,
hasVulnerabilitiesFindings,
isRiskScoreExist,
isUsingHostName,
name,
openLeftPanel,
]);
const link = useMemo(
() =>
!isPreviewMode

View file

@ -37,7 +37,7 @@ describe('VulnerabilitiesPreview', () => {
it('renders', () => {
const { getByTestId } = render(
<TestProviders>
<VulnerabilitiesPreview name="host1" />
<VulnerabilitiesPreview value="host1" field="host.name" />
</TestProviders>
);

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React, { useCallback, useEffect, useMemo } from 'react';
import React, { 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';
@ -17,24 +17,14 @@ import {
getAbbreviatedNumber,
} from '@kbn/cloud-security-posture-common';
import { getVulnerabilityStats, hasVulnerabilitiesData } from '@kbn/cloud-security-posture';
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview';
import {
ENTITY_FLYOUT_WITH_VULNERABILITY_PREVIEW,
uiMetricService,
} from '@kbn/cloud-security-posture-common/utils/ui_metrics';
import { METRIC_TYPE } from '@kbn/analytics';
import { ExpandablePanel } from '../../../flyout/shared/components/expandable_panel';
import { EntityDetailsLeftPanelTab } from '../../../flyout/entity_details/shared/components/left_panel/left_panel_header';
import { HostDetailsPanelKey } from '../../../flyout/entity_details/host_details_left';
import { useRiskScore } from '../../../entity_analytics/api/hooks/use_risk_score';
import { RiskScoreEntity } from '../../../../common/entity_analytics/risk_engine';
import { buildHostNamesFilter } from '../../../../common/search_strategy';
const FIRST_RECORD_PAGINATION = {
cursorStart: 0,
querySize: 1,
};
import { CspInsightLeftPanelSubTab } from '../../../flyout/entity_details/shared/components/left_panel/left_panel_header';
import { useNavigateEntityInsight } from '../../hooks/use_entity_insight';
const VulnerabilitiesCount = ({
vulnerabilitiesTotal,
@ -70,20 +60,20 @@ const VulnerabilitiesCount = ({
};
export const VulnerabilitiesPreview = ({
name,
value,
field,
isPreviewMode,
hasNonClosedAlerts = false,
}: {
name: string;
value: string;
field: 'host.name' | 'user.name';
isPreviewMode?: boolean;
hasNonClosedAlerts?: boolean;
}) => {
useEffect(() => {
uiMetricService.trackUiMetric(METRIC_TYPE.CLICK, ENTITY_FLYOUT_WITH_VULNERABILITY_PREVIEW);
}, []);
const { data } = useVulnerabilitiesPreview({
query: buildEntityFlyoutPreviewQuery('host.name', name),
query: buildEntityFlyoutPreviewQuery(field, value),
sort: [],
enabled: true,
pageSize: 1,
@ -103,49 +93,12 @@ export const VulnerabilitiesPreview = ({
const { euiTheme } = useEuiTheme();
const { data: dataMisconfiguration } = useMisconfigurationPreview({
query: buildEntityFlyoutPreviewQuery('host.name', name),
sort: [],
enabled: true,
pageSize: 1,
const { goToEntityInsightTab } = useNavigateEntityInsight({
field,
value,
queryIdExtension: 'VULNERABILITIES_PREVIEW',
subTab: CspInsightLeftPanelSubTab.VULNERABILITIES,
});
const passedFindings = dataMisconfiguration?.count.passed || 0;
const failedFindings = dataMisconfiguration?.count.failed || 0;
const hasMisconfigurationFindings = passedFindings > 0 || failedFindings > 0;
const buildFilterQuery = useMemo(() => buildHostNamesFilter([name]), [name]);
const riskScoreState = useRiskScore({
riskEntity: RiskScoreEntity.host,
filterQuery: buildFilterQuery,
onlyLatest: false,
pagination: FIRST_RECORD_PAGINATION,
});
const { data: hostRisk } = riskScoreState;
const riskData = hostRisk?.[0];
const isRiskScoreExist = riskData?.host.risk;
const { openLeftPanel } = useExpandableFlyoutApi();
const goToEntityInsightTab = useCallback(() => {
openLeftPanel({
id: HostDetailsPanelKey,
params: {
name,
isRiskScoreExist,
hasMisconfigurationFindings,
hasVulnerabilitiesFindings,
hasNonClosedAlerts,
path: { tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS, subTab: 'vulnerabilitiesTabId' },
},
});
}, [
hasMisconfigurationFindings,
hasNonClosedAlerts,
hasVulnerabilitiesFindings,
isRiskScoreExist,
name,
openLeftPanel,
]);
const link = useMemo(
() =>
!isPreviewMode

View file

@ -0,0 +1,89 @@
/*
* 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 { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
import { useCallback } from 'react';
import { useHasVulnerabilities } from '@kbn/cloud-security-posture/src/hooks/use_has_vulnerabilities';
import { useHasMisconfigurations } from '@kbn/cloud-security-posture/src/hooks/use_has_misconfigurations';
import { UserDetailsPanelKey } from '../../flyout/entity_details/user_details_left';
import { HostDetailsPanelKey } from '../../flyout/entity_details/host_details_left';
import { EntityDetailsLeftPanelTab } from '../../flyout/entity_details/shared/components/left_panel/left_panel_header';
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 './use_non_closed_alerts';
import { useHasRiskScore } from './use_risk_score_data';
export const useNavigateEntityInsight = ({
field,
value,
subTab,
queryIdExtension,
}: {
field: 'host.name' | 'user.name';
value: string;
subTab: string;
queryIdExtension: string;
}) => {
const isHostNameField = field === 'host.name';
const { to, from } = useGlobalTime();
const { hasNonClosedAlerts } = useNonClosedAlerts({
field,
value,
to,
from,
queryId: `${DETECTION_RESPONSE_ALERTS_BY_STATUS_ID}${queryIdExtension}`,
});
const { hasVulnerabilitiesFindings } = useHasVulnerabilities(field, value);
const { hasRiskScore } = useHasRiskScore({
field,
value,
});
const { hasMisconfigurationFindings } = useHasMisconfigurations(field, value);
const { openLeftPanel } = useExpandableFlyoutApi();
const goToEntityInsightTab = useCallback(() => {
openLeftPanel({
id: isHostNameField ? HostDetailsPanelKey : UserDetailsPanelKey,
params: isHostNameField
? {
name: value,
isRiskScoreExist: hasRiskScore,
hasMisconfigurationFindings,
hasVulnerabilitiesFindings,
hasNonClosedAlerts,
path: {
tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS,
subTab,
},
}
: {
user: { name: value },
isRiskScoreExist: hasRiskScore,
hasMisconfigurationFindings,
hasNonClosedAlerts,
path: {
tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS,
subTab,
},
},
});
}, [
openLeftPanel,
isHostNameField,
value,
hasRiskScore,
hasMisconfigurationFindings,
hasVulnerabilitiesFindings,
hasNonClosedAlerts,
subTab,
]);
return { goToEntityInsightTab };
};

View file

@ -0,0 +1,47 @@
/*
* 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 { useMemo } from 'react';
import { FILTER_CLOSED } from '@kbn/securitysolution-data-table/common/types';
import { useSignalIndex } from '../../detections/containers/detection_engine/alerts/use_signal_index';
import { useAlertsByStatus } from '../../overview/components/detection_response/alerts_by_status/use_alerts_by_status';
import type { ParsedAlertsData } from '../../overview/components/detection_response/alerts_by_status/types';
export const useNonClosedAlerts = ({
field,
value,
to,
from,
queryId,
}: {
field: 'host.name' | 'user.name';
value: string;
to: string;
from: string;
queryId: string;
}) => {
const { signalIndexName } = useSignalIndex();
const entityFilter = useMemo(() => ({ field, value }), [field, value]);
const { items: alertsData } = useAlertsByStatus({
entityFilter,
signalIndexName,
queryId,
to,
from,
});
const filteredAlertsData: ParsedAlertsData = alertsData
? Object.fromEntries(Object.entries(alertsData).filter(([key]) => key !== FILTER_CLOSED))
: {};
const hasNonClosedAlerts =
(filteredAlertsData?.acknowledged?.total || 0) + (filteredAlertsData?.open?.total || 0) > 0;
return { hasNonClosedAlerts, filteredAlertsData };
};

View file

@ -0,0 +1,45 @@
/*
* 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 { useMemo } from 'react';
import {
RiskScoreEntity,
type HostRiskScore,
type UserRiskScore,
buildHostNamesFilter,
buildUserNamesFilter,
} from '../../../common/search_strategy';
import { useRiskScore } from '../../entity_analytics/api/hooks/use_risk_score';
import { FIRST_RECORD_PAGINATION } from '../../entity_analytics/common';
export const useHasRiskScore = ({
field,
value,
}: {
field: 'host.name' | 'user.name';
value: string;
}) => {
const isHostNameField = field === 'host.name';
const buildFilterQuery = useMemo(
() => (isHostNameField ? buildHostNamesFilter([value]) : buildUserNamesFilter([value])),
[isHostNameField, value]
);
const { data } = useRiskScore({
riskEntity: isHostNameField ? RiskScoreEntity.host : RiskScoreEntity.user,
filterQuery: buildFilterQuery,
onlyLatest: false,
pagination: FIRST_RECORD_PAGINATION,
});
const riskData = data?.[0];
const hasRiskScore = isHostNameField
? !!(riskData as HostRiskScore)?.host.risk
: !!(riskData as UserRiskScore)?.user.risk;
return { hasRiskScore };
};

View file

@ -44,6 +44,6 @@ export const getInsightsInputTab = ({
defaultMessage="Insights"
/>
),
content: <InsightsTabCsp name={name} fieldName={fieldName} />,
content: <InsightsTabCsp value={name} field={fieldName} />,
};
};

View file

@ -64,7 +64,7 @@ export const HostPanelContent = ({
entity={{ name: hostName, type: 'host' }}
onChange={onAssetCriticalityChange}
/>
<EntityInsight name={hostName} fieldName={'host.name'} isPreviewMode={isPreviewMode} />
<EntityInsight value={hostName} field={'host.name'} isPreviewMode={isPreviewMode} />
<ObservedEntity
observedData={observedHost}
contextID={contextID}

View file

@ -9,12 +9,10 @@ import React, { useCallback, useMemo } from 'react';
import type { FlyoutPanelProps } from '@kbn/expandable-flyout';
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
import { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common';
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 { sum } from 'lodash';
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 { DETECTION_RESPONSE_ALERTS_BY_STATUS_ID } from '../../../overview/components/detection_response/alerts_by_status/types';
import { useAlertsByStatus } from '../../../overview/components/detection_response/alerts_by_status/use_alerts_by_status';
import { useRefetchQueryById } from '../../../entity_analytics/api/hooks/use_refetch_query_by_id';
import { RISK_INPUTS_TAB_QUERY_ID } from '../../../entity_analytics/components/entity_details_flyout/tabs/risk_inputs/risk_inputs_tab';
import type { Refetch } from '../../../common/types';
@ -37,7 +35,6 @@ import { useObservedHost } from './hooks/use_observed_host';
import { HostDetailsPanelKey } from '../host_details_left';
import { EntityDetailsLeftPanelTab } from '../shared/components/left_panel/left_panel_header';
import { HostPreviewPanelFooter } from '../host_preview/footer';
import { useSignalIndex } from '../../../detections/containers/detection_engine/alerts/use_signal_index';
import { EntityEventTypes } from '../../../common/lib/telemetry';
export interface HostPanelProps extends Record<string, unknown> {
@ -101,43 +98,18 @@ export const HostPanel = ({
{ onSuccess: refetchRiskScore }
);
const { data } = useMisconfigurationPreview({
query: buildEntityFlyoutPreviewQuery('host.name', hostName),
sort: [],
enabled: true,
pageSize: 1,
ignore_unavailable: true,
});
const { hasMisconfigurationFindings } = useHasMisconfigurations('host.name', hostName);
const passedFindings = data?.count.passed || 0;
const failedFindings = data?.count.failed || 0;
const { hasVulnerabilitiesFindings } = useHasVulnerabilities('host.name', hostName);
const hasMisconfigurationFindings = passedFindings > 0 || failedFindings > 0;
const { data: vulnerabilitiesData } = useVulnerabilitiesPreview({
query: buildEntityFlyoutPreviewQuery('host.name', hostName),
sort: [],
enabled: true,
pageSize: 1,
});
const hasVulnerabilitiesFindings = sum(Object.values(vulnerabilitiesData?.count || {})) > 0;
const { signalIndexName } = useSignalIndex();
const entityFilter = useMemo(() => ({ field: 'host.name', value: hostName }), [hostName]);
const { items: alertsData } = useAlertsByStatus({
entityFilter,
signalIndexName,
queryId: `${DETECTION_RESPONSE_ALERTS_BY_STATUS_ID}HOST_NAME_RIGHT`,
const { hasNonClosedAlerts } = useNonClosedAlerts({
field: 'host.name',
value: hostName,
to,
from,
queryId: `${DETECTION_RESPONSE_ALERTS_BY_STATUS_ID}HOST_NAME_RIGHT`,
});
const hasNonClosedAlerts =
(alertsData?.acknowledged?.total || 0) + (alertsData?.open?.total || 0) > 0;
useQueryInspector({
deleteQuery,
inspect: inspectRiskScore,

View file

@ -73,7 +73,7 @@ export const UserPanelContent = ({
entity={{ name: userName, type: 'user' }}
onChange={onAssetCriticalityChange}
/>
<EntityInsight name={userName} fieldName={'user.name'} isPreviewMode={isPreviewMode} />
<EntityInsight value={userName} field={'user.name'} isPreviewMode={isPreviewMode} />
<ObservedEntity
observedData={observedUser}
contextID={contextID}

View file

@ -8,8 +8,8 @@
import React, { useCallback, useMemo } from 'react';
import type { FlyoutPanelProps } from '@kbn/expandable-flyout';
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview';
import { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common';
import { useHasMisconfigurations } from '@kbn/cloud-security-posture/src/hooks/use_has_misconfigurations';
import { useNonClosedAlerts } from '../../../cloud_security_posture/hooks/use_non_closed_alerts';
import { useRefetchQueryById } from '../../../entity_analytics/api/hooks/use_refetch_query_by_id';
import type { Refetch } from '../../../common/types';
import { RISK_INPUTS_TAB_QUERY_ID } from '../../../entity_analytics/components/entity_details_flyout/tabs/risk_inputs/risk_inputs_tab';
@ -33,8 +33,6 @@ import { UserDetailsPanelKey } from '../user_details_left';
import { useObservedUser } from './hooks/use_observed_user';
import { EntityDetailsLeftPanelTab } from '../shared/components/left_panel/left_panel_header';
import { UserPreviewPanelFooter } from '../user_preview/footer';
import { useSignalIndex } from '../../../detections/containers/detection_engine/alerts/use_signal_index';
import { useAlertsByStatus } from '../../../overview/components/detection_response/alerts_by_status/use_alerts_by_status';
import { DETECTION_RESPONSE_ALERTS_BY_STATUS_ID } from '../../../overview/components/detection_response/alerts_by_status/types';
import { EntityEventTypes } from '../../../common/lib/telemetry';
@ -102,34 +100,16 @@ export const UserPanel = ({
{ onSuccess: refetchRiskScore }
);
const { data } = useMisconfigurationPreview({
query: buildEntityFlyoutPreviewQuery('user.name', userName),
sort: [],
enabled: true,
pageSize: 1,
ignore_unavailable: true,
});
const { hasMisconfigurationFindings } = useHasMisconfigurations('user.name', userName);
const passedFindings = data?.count.passed || 0;
const failedFindings = data?.count.failed || 0;
const hasMisconfigurationFindings = passedFindings > 0 || failedFindings > 0;
const { signalIndexName } = useSignalIndex();
const entityFilter = useMemo(() => ({ field: 'user.name', value: userName }), [userName]);
const { items: alertsData } = useAlertsByStatus({
entityFilter,
signalIndexName,
queryId: `${DETECTION_RESPONSE_ALERTS_BY_STATUS_ID}USER_NAME_RIGHT`,
const { hasNonClosedAlerts } = useNonClosedAlerts({
field: 'user.name',
value: userName,
to,
from,
queryId: `${DETECTION_RESPONSE_ALERTS_BY_STATUS_ID}USER_NAME_RIGHT`,
});
const hasNonClosedAlerts =
(alertsData?.acknowledged?.total || 0) + (alertsData?.open?.total || 0) > 0;
useQueryInspector({
deleteQuery,
inspect,