From c842db549ac55238499d63033f046d744ee18ba1 Mon Sep 17 00:00:00 2001 From: Rickyanto Ang Date: Thu, 21 Nov 2024 12:33:30 -0800 Subject: [PATCH] [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 --- .../src/hooks/use_has_misconfigurations.ts | 29 ++++ .../src/hooks/use_has_vulnerabilities.ts | 39 +++++ .../components/alerts/alerts_preview.test.tsx | 6 +- .../components/alerts/alerts_preview.tsx | 134 ++--------------- .../alerts_findings_details_table.tsx | 12 +- .../csp_details/insights_tab_csp.tsx | 8 +- ...isconfiguration_findings_details_table.tsx | 6 +- ...vulnerabilities_findings_details_table.tsx | 6 +- .../components/entity_insight.tsx | 104 ++++---------- .../misconfiguration_preview.test.tsx | 2 +- .../misconfiguration_preview.tsx | 135 +++--------------- .../vulnerabilities_preview.test.tsx | 2 +- .../vulnerabilities_preview.tsx | 73 ++-------- .../hooks/use_entity_insight.ts | 89 ++++++++++++ .../hooks/use_non_closed_alerts.ts | 47 ++++++ .../hooks/use_risk_score_data.ts | 45 ++++++ .../entity_details_flyout/index.tsx | 2 +- .../entity_details/host_right/content.tsx | 2 +- .../entity_details/host_right/index.tsx | 46 ++---- .../entity_details/user_right/content.tsx | 2 +- .../entity_details/user_right/index.tsx | 34 +---- 21 files changed, 360 insertions(+), 463 deletions(-) create mode 100644 x-pack/packages/kbn-cloud-security-posture/public/src/hooks/use_has_misconfigurations.ts create mode 100644 x-pack/packages/kbn-cloud-security-posture/public/src/hooks/use_has_vulnerabilities.ts create mode 100644 x-pack/plugins/security_solution/public/cloud_security_posture/hooks/use_entity_insight.ts create mode 100644 x-pack/plugins/security_solution/public/cloud_security_posture/hooks/use_non_closed_alerts.ts create mode 100644 x-pack/plugins/security_solution/public/cloud_security_posture/hooks/use_risk_score_data.ts diff --git a/x-pack/packages/kbn-cloud-security-posture/public/src/hooks/use_has_misconfigurations.ts b/x-pack/packages/kbn-cloud-security-posture/public/src/hooks/use_has_misconfigurations.ts new file mode 100644 index 0000000000000..9b126332b567b --- /dev/null +++ b/x-pack/packages/kbn-cloud-security-posture/public/src/hooks/use_has_misconfigurations.ts @@ -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, + }; +}; diff --git a/x-pack/packages/kbn-cloud-security-posture/public/src/hooks/use_has_vulnerabilities.ts b/x-pack/packages/kbn-cloud-security-posture/public/src/hooks/use_has_vulnerabilities.ts new file mode 100644 index 0000000000000..336892ee888af --- /dev/null +++ b/x-pack/packages/kbn-cloud-security-posture/public/src/hooks/use_has_vulnerabilities.ts @@ -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 }; +}; diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/alerts/alerts_preview.test.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/alerts/alerts_preview.test.tsx index fff9450b6a1cb..594629e9f9e27 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/alerts/alerts_preview.test.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/alerts/alerts_preview.test.tsx @@ -58,7 +58,7 @@ describe('AlertsPreview', () => { it('renders', () => { const { getByTestId } = render( - + ); @@ -68,7 +68,7 @@ describe('AlertsPreview', () => { it('renders correct alerts number', () => { const { getByTestId } = render( - + ); @@ -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( - + ); diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/alerts/alerts_preview.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/alerts/alerts_preview.tsx index a5f08527cdc77..8edd0b7981936 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/alerts/alerts_preview.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/alerts/alerts_preview.tsx @@ -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 diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/alerts_findings_details_table.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/alerts_findings_details_table.tsx index 966de68e3497f..6567fb41a93f4 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/alerts_findings_details_table.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/alerts_findings_details_table.tsx @@ -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 ( diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/insights_tab_csp.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/insights_tab_csp.tsx index 2e7b4171fd023..84e3ee4faee99 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/insights_tab_csp.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/insights_tab_csp.tsx @@ -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( /> {activeInsightsId === CspInsightLeftPanelSubTab.MISCONFIGURATIONS ? ( - + ) : activeInsightsId === CspInsightLeftPanelSubTab.VULNERABILITIES ? ( - + ) : ( - + )} ); diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/misconfiguration_findings_details_table.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/misconfiguration_findings_details_table.tsx index 69912c58e4e15..00430c2b87262 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/misconfiguration_findings_details_table.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/misconfiguration_findings_details_table.tsx @@ -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( { diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/vulnerabilities_findings_details_table.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/vulnerabilities_findings_details_table.tsx index 82c5f91bf4250..155946a791f79 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/vulnerabilities_findings_details_table.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/vulnerabilities_findings_details_table.tsx @@ -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 { diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/entity_insight.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/entity_insight.tsx index 7139994f7e972..eec66e765371b 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/entity_insight.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/entity_insight.tsx @@ -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 = ({ - 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 passedFindings = dataMisconfiguration?.count.passed || 0; - const failedFindings = dataMisconfiguration?.count.failed || 0; - - 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 { hasMisconfigurationFindings: showMisconfigurationsPreview } = useHasMisconfigurations( + field, + value + ); - const { signalIndexName } = useSignalIndex(); + const { hasVulnerabilitiesFindings } = useHasVulnerabilities(field, value); - 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( <> @@ -105,34 +64,23 @@ export const EntityInsight = ({ ); } - if (hasMisconfigurationFindings) + if (showMisconfigurationsPreview) insightContent.push( <> - 0} - isPreviewMode={isPreviewMode} - /> + ); - if (isVulnerabilitiesFindingForHost && hasVulnerabilitiesFindings) + if (showVulnerabilitiesPreview) insightContent.push( <> - 0} - /> + ); return ( <> - {(insightContent.length > 0 || - hasMisconfigurationFindings || - (isVulnerabilitiesFindingForHost && hasVulnerabilitiesFindings)) && ( + {insightContent.length > 0 && ( <> { it('renders', () => { const { getByTestId } = render( - + ); diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx index 42a5906ce4e36..c7c1889a5838b 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx @@ -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 diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.test.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.test.tsx index cc71d1be2158d..14a6366fd4baa 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.test.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.test.tsx @@ -37,7 +37,7 @@ describe('VulnerabilitiesPreview', () => { it('renders', () => { const { getByTestId } = render( - + ); diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.tsx index c4335d921e371..5a5b638abafa3 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.tsx @@ -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 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 { goToEntityInsightTab } = useNavigateEntityInsight({ + field, + value, + queryIdExtension: 'VULNERABILITIES_PREVIEW', + subTab: CspInsightLeftPanelSubTab.VULNERABILITIES, }); - 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 diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/hooks/use_entity_insight.ts b/x-pack/plugins/security_solution/public/cloud_security_posture/hooks/use_entity_insight.ts new file mode 100644 index 0000000000000..fc35474ffdef0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/hooks/use_entity_insight.ts @@ -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 }; +}; diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/hooks/use_non_closed_alerts.ts b/x-pack/plugins/security_solution/public/cloud_security_posture/hooks/use_non_closed_alerts.ts new file mode 100644 index 0000000000000..598f78cd68402 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/hooks/use_non_closed_alerts.ts @@ -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 }; +}; diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/hooks/use_risk_score_data.ts b/x-pack/plugins/security_solution/public/cloud_security_posture/hooks/use_risk_score_data.ts new file mode 100644 index 0000000000000..b100fe30b105b --- /dev/null +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/hooks/use_risk_score_data.ts @@ -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 }; +}; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_details_flyout/index.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_details_flyout/index.tsx index 6f728d7653783..1073be375b093 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_details_flyout/index.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_details_flyout/index.tsx @@ -44,6 +44,6 @@ export const getInsightsInputTab = ({ defaultMessage="Insights" /> ), - content: , + content: , }; }; diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/host_right/content.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/host_right/content.tsx index 2e7c14fc38027..9c2ce61dea7fc 100644 --- a/x-pack/plugins/security_solution/public/flyout/entity_details/host_right/content.tsx +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/host_right/content.tsx @@ -64,7 +64,7 @@ export const HostPanelContent = ({ entity={{ name: hostName, type: 'host' }} onChange={onAssetCriticalityChange} /> - + { @@ -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 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', hostName), - sort: [], - enabled: true, - pageSize: 1, - }); + const { hasMisconfigurationFindings } = useHasMisconfigurations('host.name', hostName); - const hasVulnerabilitiesFindings = sum(Object.values(vulnerabilitiesData?.count || {})) > 0; + const { hasVulnerabilitiesFindings } = useHasVulnerabilities('host.name', hostName); - 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, diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/content.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/content.tsx index 0dbc1faa5cb42..08295038a1bd8 100644 --- a/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/content.tsx +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/content.tsx @@ -73,7 +73,7 @@ export const UserPanelContent = ({ entity={{ name: userName, type: 'user' }} onChange={onAssetCriticalityChange} /> - + 0 || failedFindings > 0; - - const { signalIndexName } = useSignalIndex(); + const { hasMisconfigurationFindings } = useHasMisconfigurations('user.name', userName); - 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,