From 84bf3890741260352ef18244bd746886da3fc8df Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 3 Dec 2024 07:30:12 +1100 Subject: [PATCH] [8.x] [Cloud Security] Filters for Contextual Flyout Datagrid (#201708) (#202556) # Backport This will backport the following commits from `main` to `8.x`: - [[Cloud Security] Filters for Contextual Flyout Datagrid (#201708)](https://github.com/elastic/kibana/pull/201708) ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) Co-authored-by: Rickyanto Ang --- .../common/constants.ts | 5 + .../common/index.ts | 4 +- .../common/utils/helpers.test.ts | 186 ++++++++++++++++-- .../common/utils/helpers.ts | 74 +++++-- .../src/constants/component_constants.ts | 3 + .../src/hooks/use_has_misconfigurations.ts | 4 +- .../src/hooks/use_has_vulnerabilities.ts | 4 +- .../public/src/types.ts | 2 +- .../src/utils/vulnerability_helpers.test.ts | 47 ++++- .../public/src/utils/vulnerability_helpers.ts | 47 ++++- .../distribution_bar/src/distribution_bar.tsx | 29 ++- .../alerts_findings_details_table.tsx | 56 ++++-- ...isconfiguration_findings_details_table.tsx | 48 ++++- ...vulnerabilities_findings_details_table.tsx | 37 ++-- .../vulnerabilities_preview.tsx | 4 +- .../components/misconfiguration_insight.tsx | 4 +- .../components/vulnerabilities_insight.tsx | 4 +- 17 files changed, 480 insertions(+), 78 deletions(-) diff --git a/x-pack/packages/kbn-cloud-security-posture/common/constants.ts b/x-pack/packages/kbn-cloud-security-posture/common/constants.ts index a24d676dc6f88..9112dba4b9a4f 100644 --- a/x-pack/packages/kbn-cloud-security-posture/common/constants.ts +++ b/x-pack/packages/kbn-cloud-security-posture/common/constants.ts @@ -47,3 +47,8 @@ export const VULNERABILITIES_SEVERITY: Record = { CRITICAL: 'CRITICAL', UNKNOWN: 'UNKNOWN', }; + +export const MISCONFIGURATION_STATUS: Record = { + PASSED: 'passed', + FAILED: 'failed', +}; diff --git a/x-pack/packages/kbn-cloud-security-posture/common/index.ts b/x-pack/packages/kbn-cloud-security-posture/common/index.ts index d5ee781c39b20..4065f7ca331a9 100644 --- a/x-pack/packages/kbn-cloud-security-posture/common/index.ts +++ b/x-pack/packages/kbn-cloud-security-posture/common/index.ts @@ -28,7 +28,9 @@ export * from './constants'; export { extractErrorMessage, buildMutedRulesFilter, - buildEntityFlyoutPreviewQuery, + buildGenericEntityFlyoutPreviewQuery, + buildMisconfigurationEntityFlyoutPreviewQuery, + buildVulnerabilityEntityFlyoutPreviewQuery, } from './utils/helpers'; export { getAbbreviatedNumber } from './utils/get_abbreviated_number'; export { UiMetricService } from './utils/ui_metrics'; diff --git a/x-pack/packages/kbn-cloud-security-posture/common/utils/helpers.test.ts b/x-pack/packages/kbn-cloud-security-posture/common/utils/helpers.test.ts index 04cb76f4441c5..6b85d2ead28fe 100644 --- a/x-pack/packages/kbn-cloud-security-posture/common/utils/helpers.test.ts +++ b/x-pack/packages/kbn-cloud-security-posture/common/utils/helpers.test.ts @@ -9,8 +9,10 @@ import { extractErrorMessage, defaultErrorMessage, buildMutedRulesFilter, - buildEntityFlyoutPreviewQuery, buildEntityAlertsQuery, + buildGenericEntityFlyoutPreviewQuery, + buildMisconfigurationEntityFlyoutPreviewQuery, + buildVulnerabilityEntityFlyoutPreviewQuery, } from './helpers'; const fallbackMessage = 'thisIsAFallBackMessage'; @@ -145,7 +147,7 @@ describe('test helper methods', () => { }); }); - describe('buildEntityFlyoutPreviewQueryTest', () => { + describe('buildGenericEntityFlyoutPreviewQuery', () => { it('should return the correct query when given field and query', () => { const field = 'host.name'; const query = 'exampleHost'; @@ -162,10 +164,10 @@ describe('test helper methods', () => { }, }; - expect(buildEntityFlyoutPreviewQuery(field, query)).toEqual(expectedQuery); + expect(buildGenericEntityFlyoutPreviewQuery(field, query)).toEqual(expectedQuery); }); - it('should return the correct query when given field and empty query', () => { + it('should return the correct query when given field and empty query and empty status', () => { const field = 'host.name'; const expectedQuery = { bool: { @@ -180,12 +182,143 @@ describe('test helper methods', () => { }, }; - expect(buildEntityFlyoutPreviewQuery(field)).toEqual(expectedQuery); + expect(buildGenericEntityFlyoutPreviewQuery(field)).toEqual(expectedQuery); + }); + + it('should return the correct query when given field and queryValue and status but empty queryField', () => { + const field = 'host.name'; + const query = 'exampleHost'; + const status = 'pass'; + const expectedQuery = { + bool: { + filter: [ + { + bool: { + should: [{ term: { 'host.name': 'exampleHost' } }], + minimum_should_match: 1, + }, + }, + ], + }, + }; + + expect(buildGenericEntityFlyoutPreviewQuery(field, query, status)).toEqual(expectedQuery); + }); + + it('should return the correct query when given field and queryValue and queryField but empty status', () => { + const field = 'host.name'; + const query = 'exampleHost'; + const emptyStatus = undefined; + const queryField = 'some.field'; + const expectedQuery = { + bool: { + filter: [ + { + bool: { + should: [{ term: { 'host.name': 'exampleHost' } }], + minimum_should_match: 1, + }, + }, + ], + }, + }; + + expect(buildGenericEntityFlyoutPreviewQuery(field, query, emptyStatus, queryField)).toEqual( + expectedQuery + ); + }); + + it('should return the correct query when given all the parameters', () => { + const field = 'host.name'; + const query = 'exampleHost'; + const emptyStatus = 'some.status'; + const queryField = 'some.field'; + const expectedQuery = { + bool: { + filter: [ + { + bool: { + should: [{ term: { 'host.name': 'exampleHost' } }], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [{ term: { 'some.field': 'some.status' } }], + minimum_should_match: 1, + }, + }, + ], + }, + }; + + expect(buildGenericEntityFlyoutPreviewQuery(field, query, emptyStatus, queryField)).toEqual( + expectedQuery + ); + }); + }); + + describe('buildMisconfigurationEntityFlyoutPreviewQuery', () => { + it('should return the correct query when given field, queryValue, status and queryType Misconfiguration', () => { + const field = 'host.name'; + const queryValue = 'exampleHost'; + const status = 'pass'; + const expectedQuery = { + bool: { + filter: [ + { + bool: { + should: [{ term: { 'host.name': 'exampleHost' } }], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [{ term: { 'result.evaluation': 'pass' } }], + minimum_should_match: 1, + }, + }, + ], + }, + }; + + expect(buildMisconfigurationEntityFlyoutPreviewQuery(field, queryValue, status)).toEqual( + expectedQuery + ); + }); + }); + describe('buildVulnerabilityEntityFlyoutPreviewQuery', () => { + it('should return the correct query when given field, queryValue, status and queryType Vulnerability', () => { + const field = 'host.name'; + const queryValue = 'exampleHost'; + const status = 'low'; + const expectedQuery = { + bool: { + filter: [ + { + bool: { + should: [{ term: { 'host.name': 'exampleHost' } }], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [{ term: { 'vulnerability.severity': 'low' } }], + minimum_should_match: 1, + }, + }, + ], + }, + }; + + expect(buildVulnerabilityEntityFlyoutPreviewQuery(field, queryValue, status)).toEqual( + expectedQuery + ); }); }); describe('buildEntityAlertsQuery', () => { - const getExpectedAlertsQuery = (size?: number) => { + const getExpectedAlertsQuery = (size?: number, severity?: string) => { return { size: size || 0, _source: false, @@ -202,20 +335,30 @@ describe('test helper methods', () => { filter: [ { bool: { - must: [], - filter: [ + should: [ { - match_phrase: { - 'host.name': { - query: 'exampleHost', - }, + term: { + 'host.name': 'exampleHost', }, }, ], - should: [], - must_not: [], + minimum_should_match: 1, }, }, + severity + ? { + bool: { + should: [ + { + term: { + 'kibana.alert.severity': 'low', + }, + }, + ], + minimum_should_match: 1, + }, + } + : undefined, { range: { '@timestamp': { @@ -229,7 +372,7 @@ describe('test helper methods', () => { 'kibana.alert.workflow_status': ['open', 'acknowledged'], }, }, - ], + ].filter(Boolean), }, }, }; @@ -256,5 +399,18 @@ describe('test helper methods', () => { expect(buildEntityAlertsQuery(field, to, from, query)).toEqual(getExpectedAlertsQuery(size)); }); + + it('should return the correct query when given severity query', () => { + const field = 'host.name'; + const query = 'exampleHost'; + const to = 'Tomorrow'; + const from = 'Today'; + const size = undefined; + const severity = 'low'; + + expect(buildEntityAlertsQuery(field, to, from, query, size, severity)).toEqual( + getExpectedAlertsQuery(size, 'low') + ); + }); }); }); diff --git a/x-pack/packages/kbn-cloud-security-posture/common/utils/helpers.ts b/x-pack/packages/kbn-cloud-security-posture/common/utils/helpers.ts index bd531fa63804f..ac2e27b70878f 100644 --- a/x-pack/packages/kbn-cloud-security-posture/common/utils/helpers.ts +++ b/x-pack/packages/kbn-cloud-security-posture/common/utils/helpers.ts @@ -43,7 +43,12 @@ export const buildMutedRulesFilter = ( return mutedRulesFilterQuery; }; -export const buildEntityFlyoutPreviewQuery = (field: string, queryValue?: string) => { +export const buildGenericEntityFlyoutPreviewQuery = ( + field: string, + queryValue?: string, + status?: string, + queryField?: string +) => { return { bool: { filter: [ @@ -59,17 +64,52 @@ export const buildEntityFlyoutPreviewQuery = (field: string, queryValue?: string minimum_should_match: 1, }, }, - ], + status && queryField + ? { + bool: { + should: [ + { + term: { + [queryField]: status, + }, + }, + ], + minimum_should_match: 1, + }, + } + : undefined, + ].filter(Boolean), }, }; }; +// Higher-order function for Misconfiguration +export const buildMisconfigurationEntityFlyoutPreviewQuery = ( + field: string, + queryValue?: string, + status?: string +) => { + const queryField = 'result.evaluation'; + return buildGenericEntityFlyoutPreviewQuery(field, queryValue, status, queryField); +}; + +// Higher-order function for Vulnerability +export const buildVulnerabilityEntityFlyoutPreviewQuery = ( + field: string, + queryValue?: string, + status?: string +) => { + const queryField = 'vulnerability.severity'; + return buildGenericEntityFlyoutPreviewQuery(field, queryValue, status, queryField); +}; + export const buildEntityAlertsQuery = ( field: string, to: string, from: string, queryValue?: string, - size?: number + size?: number, + severity?: string ) => { return { size: size || 0, @@ -87,20 +127,30 @@ export const buildEntityAlertsQuery = ( filter: [ { bool: { - must: [], - filter: [ + should: [ { - match_phrase: { - [field]: { - query: queryValue, - }, + term: { + [field]: `${queryValue || ''}`, }, }, ], - should: [], - must_not: [], + minimum_should_match: 1, }, }, + severity + ? { + bool: { + should: [ + { + term: { + 'kibana.alert.severity': severity, + }, + }, + ], + minimum_should_match: 1, + }, + } + : undefined, { range: { '@timestamp': { @@ -114,7 +164,7 @@ export const buildEntityAlertsQuery = ( 'kibana.alert.workflow_status': ['open', 'acknowledged'], }, }, - ], + ].filter(Boolean), }, }, }; diff --git a/x-pack/packages/kbn-cloud-security-posture/public/src/constants/component_constants.ts b/x-pack/packages/kbn-cloud-security-posture/public/src/constants/component_constants.ts index d4d436e981cc4..3e22fdea80666 100644 --- a/x-pack/packages/kbn-cloud-security-posture/public/src/constants/component_constants.ts +++ b/x-pack/packages/kbn-cloud-security-posture/public/src/constants/component_constants.ts @@ -11,3 +11,6 @@ export const statusColors = { failed: euiThemeVars.euiColorVis9, unknown: euiThemeVars.euiColorLightShade, }; + +export const HOST_NAME = 'host.name'; +export const USER_NAME = 'user.name'; 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 index 9b126332b567b..c8e36898fed16 100644 --- 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 @@ -5,12 +5,12 @@ * 2.0. */ -import { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common'; +import { buildGenericEntityFlyoutPreviewQuery } 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), + query: buildGenericEntityFlyoutPreviewQuery(field, value), sort: [], enabled: true, pageSize: 1, 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 index 336892ee888af..ae7e951ce9313 100644 --- 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 @@ -5,13 +5,13 @@ * 2.0. */ -import { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common'; +import { buildGenericEntityFlyoutPreviewQuery } 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), + query: buildGenericEntityFlyoutPreviewQuery(field, value), sort: [], enabled: true, pageSize: 1, diff --git a/x-pack/packages/kbn-cloud-security-posture/public/src/types.ts b/x-pack/packages/kbn-cloud-security-posture/public/src/types.ts index 493e58519bd91..2c7be37de50dd 100644 --- a/x-pack/packages/kbn-cloud-security-posture/public/src/types.ts +++ b/x-pack/packages/kbn-cloud-security-posture/public/src/types.ts @@ -58,7 +58,7 @@ export interface CspClientPluginStartDeps { export interface CspBaseEsQuery { query?: { bool: { - filter: estypes.QueryDslQueryContainer[]; + filter: Array | undefined; }; }; } diff --git a/x-pack/packages/kbn-cloud-security-posture/public/src/utils/vulnerability_helpers.test.ts b/x-pack/packages/kbn-cloud-security-posture/public/src/utils/vulnerability_helpers.test.ts index 898f9990a1a96..e2503b7a38a2c 100644 --- a/x-pack/packages/kbn-cloud-security-posture/public/src/utils/vulnerability_helpers.test.ts +++ b/x-pack/packages/kbn-cloud-security-posture/public/src/utils/vulnerability_helpers.test.ts @@ -10,6 +10,7 @@ import { getVulnerabilityStats } from './vulnerability_helpers'; import { i18n } from '@kbn/i18n'; describe('getVulnerabilitiesAggregationCount', () => { + const mockFilterFunction = jest.fn(); it('should return empty array when all severity count is 0', () => { const result = getVulnerabilityStats({ critical: 0, high: 0, medium: 0, low: 0, none: 0 }); expect(result).toEqual([]); @@ -17,8 +18,12 @@ describe('getVulnerabilitiesAggregationCount', () => { it('should return stats for low, medium, high, and critical vulnerabilities', () => { const result = getVulnerabilityStats({ critical: 1, high: 2, medium: 3, low: 4, none: 5 }); + const resultWithoutFunctions = result.map((item) => { + const { filter, reset, ...rest } = item; + return rest; + }); - expect(result).toEqual([ + expect(resultWithoutFunctions).toEqual([ { key: i18n.translate( 'xpack.securitySolution.flyout.right.insights.vulnerabilities.noneVulnerabilitiesText', @@ -28,6 +33,7 @@ describe('getVulnerabilitiesAggregationCount', () => { ), count: 5, color: '#aaa', + isCurrentFilter: false, }, { key: i18n.translate( @@ -38,6 +44,7 @@ describe('getVulnerabilitiesAggregationCount', () => { ), count: 4, color: euiThemeVars.euiColorVis0, + isCurrentFilter: false, }, { key: i18n.translate( @@ -48,6 +55,7 @@ describe('getVulnerabilitiesAggregationCount', () => { ), count: 3, color: euiThemeVars.euiColorVis5_behindText, + isCurrentFilter: false, }, { key: i18n.translate( @@ -58,6 +66,7 @@ describe('getVulnerabilitiesAggregationCount', () => { ), count: 2, color: euiThemeVars.euiColorVis9_behindText, + isCurrentFilter: false, }, { key: i18n.translate( @@ -68,7 +77,43 @@ describe('getVulnerabilitiesAggregationCount', () => { ), count: 1, color: euiThemeVars.euiColorDanger, + isCurrentFilter: false, }, ]); }); + + it('should return correct stats with correct onClick functions', () => { + const result = getVulnerabilityStats( + { critical: 1, high: 2, medium: 3, low: 4, none: 5 }, + mockFilterFunction + ); + const event = { stopPropagation: jest.fn() } as unknown as React.MouseEvent< + SVGElement, + MouseEvent + >; + result[1].filter?.(); + expect(mockFilterFunction).toHaveBeenCalledWith('LOW'); + result[1].reset?.(event); + expect(mockFilterFunction).toHaveBeenCalledWith(''); + }); + + it('should identify correct currentFilter', () => { + const currentFilter = 'LOW'; + const result = getVulnerabilityStats( + { critical: 1, high: 2, medium: 3, low: 4, none: 5 }, + mockFilterFunction, + currentFilter + ); + + // Make sure that Low is set to Current Filter + expect(result[1].isCurrentFilter).toBe(true); + expect(result[1].key).toBe('Low'); + + // Make sure only Low is set as Current Filter and no other severity + result.forEach((item) => { + if (item.key !== 'Low') { + expect(item.isCurrentFilter).toBe(false); + } + }); + }); }); diff --git a/x-pack/packages/kbn-cloud-security-posture/public/src/utils/vulnerability_helpers.ts b/x-pack/packages/kbn-cloud-security-posture/public/src/utils/vulnerability_helpers.ts index c8782daf35308..e1bec795ad444 100644 --- a/x-pack/packages/kbn-cloud-security-posture/public/src/utils/vulnerability_helpers.ts +++ b/x-pack/packages/kbn-cloud-security-posture/public/src/utils/vulnerability_helpers.ts @@ -14,6 +14,9 @@ interface VulnerabilitiesDistributionBarProps { key: string; count: number; color: string; + isCurrentFilter?: boolean; + filter?: () => void; + reset?: (event: any) => void; } interface VulnerabilityCounts { @@ -30,7 +33,9 @@ export const hasVulnerabilitiesData = (counts: VulnerabilityCounts): boolean => }; export const getVulnerabilityStats = ( - counts: VulnerabilityCounts + counts: VulnerabilityCounts, + filterFunction?: (filter: string) => void, + currentFilter?: string ): VulnerabilitiesDistributionBarProps[] => { const vulnerabilityStats: VulnerabilitiesDistributionBarProps[] = []; @@ -50,6 +55,14 @@ export const getVulnerabilityStats = ( ), count: counts.none, color: getSeverityStatusColor(VULNERABILITIES_SEVERITY.UNKNOWN), + filter: () => { + filterFunction?.(VULNERABILITIES_SEVERITY.UNKNOWN); + }, + isCurrentFilter: currentFilter === VULNERABILITIES_SEVERITY.UNKNOWN, + reset: (event: React.MouseEvent) => { + filterFunction?.(''); + event?.stopPropagation(); + }, }); if (counts.low > 0) vulnerabilityStats.push({ @@ -61,6 +74,14 @@ export const getVulnerabilityStats = ( ), count: counts.low, color: getSeverityStatusColor(VULNERABILITIES_SEVERITY.LOW), + filter: () => { + filterFunction?.(VULNERABILITIES_SEVERITY.LOW); + }, + isCurrentFilter: currentFilter === VULNERABILITIES_SEVERITY.LOW, + reset: (event: React.MouseEvent) => { + filterFunction?.(''); + event?.stopPropagation(); + }, }); if (counts.medium > 0) @@ -73,6 +94,14 @@ export const getVulnerabilityStats = ( ), count: counts.medium, color: getSeverityStatusColor(VULNERABILITIES_SEVERITY.MEDIUM), + filter: () => { + filterFunction?.(VULNERABILITIES_SEVERITY.MEDIUM); + }, + isCurrentFilter: currentFilter === VULNERABILITIES_SEVERITY.MEDIUM, + reset: (event: React.MouseEvent) => { + filterFunction?.(''); + event?.stopPropagation(); + }, }); if (counts.high > 0) vulnerabilityStats.push({ @@ -84,6 +113,14 @@ export const getVulnerabilityStats = ( ), count: counts.high, color: getSeverityStatusColor(VULNERABILITIES_SEVERITY.HIGH), + filter: () => { + filterFunction?.(VULNERABILITIES_SEVERITY.HIGH); + }, + isCurrentFilter: currentFilter === VULNERABILITIES_SEVERITY.HIGH, + reset: (event: React.MouseEvent) => { + filterFunction?.(''); + event?.stopPropagation(); + }, }); if (counts.critical > 0) vulnerabilityStats.push({ @@ -95,6 +132,14 @@ export const getVulnerabilityStats = ( ), count: counts.critical, color: getSeverityStatusColor(VULNERABILITIES_SEVERITY.CRITICAL), + filter: () => { + filterFunction?.(VULNERABILITIES_SEVERITY.CRITICAL); + }, + isCurrentFilter: currentFilter === VULNERABILITIES_SEVERITY.CRITICAL, + reset: (event: React.MouseEvent) => { + filterFunction?.(''); + event?.stopPropagation(); + }, }); return vulnerabilityStats; diff --git a/x-pack/packages/security-solution/distribution_bar/src/distribution_bar.tsx b/x-pack/packages/security-solution/distribution_bar/src/distribution_bar.tsx index c4395ba1d13d1..0942cace23267 100644 --- a/x-pack/packages/security-solution/distribution_bar/src/distribution_bar.tsx +++ b/x-pack/packages/security-solution/distribution_bar/src/distribution_bar.tsx @@ -12,7 +12,15 @@ import { css } from '@emotion/react'; /** DistributionBar component props */ export interface DistributionBarProps { /** distribution data points */ - stats: Array<{ key: string; count: number; color: string; label?: React.ReactNode }>; + stats: Array<{ + key: string; + count: number; + color: string; + label?: React.ReactNode; + isCurrentFilter?: boolean; + filter?: () => void; + reset?: (event: React.MouseEvent) => void; + }>; /** hide the label above the bar at first render */ hideLastTooltip?: boolean; /** data-test-subj used for querying the component in tests */ @@ -156,7 +164,19 @@ export const DistributionBar: React.FC = React.memo(functi const prettyNumber = numeral(stat.count).format('0,0a'); return ( -
+
{ + if (event.key === 'Enter' || event.key === ' ') { + stat.filter?.(); + } + }} + tabIndex={0} + role="button" + >
= React.memo(functi {stat.label ? stat.label : stat.key} + {stat.isCurrentFilter ? ( + + + + ) : undefined} 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 6567fb41a93f4..2b1a2002667c2 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 @@ -19,6 +19,8 @@ import { METRIC_TYPE } from '@kbn/analytics'; import { buildEntityAlertsQuery } from '@kbn/cloud-security-posture-common/utils/helpers'; import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; import { TableId } from '@kbn/securitysolution-data-table'; +import type { AlertsByStatus } 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 { OPEN_IN_ALERTS_TITLE_HOSTNAME, OPEN_IN_ALERTS_TITLE_STATUS, @@ -34,6 +36,7 @@ import { getSeverityColor } from '../../../detections/components/alerts_kpis/sev import { SeverityBadge } from '../../../common/components/severity_badge'; import { ALERT_PREVIEW_BANNER } from '../../../flyout/document_details/preview/constants'; import { FILTER_OPEN, FILTER_ACKNOWLEDGED } from '../../../../common/types'; +import { useNonClosedAlerts } from '../../hooks/use_non_closed_alerts'; type AlertSeverity = 'low' | 'medium' | 'high' | 'critical'; @@ -68,6 +71,8 @@ export const AlertsDetailsTable = memo( ); }, []); + const [currentFilter, setCurrentFilter] = useState(''); + const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(10); @@ -89,12 +94,46 @@ export const AlertsDetailsTable = memo( const { to, from } = useGlobalTime(); const { signalIndexName } = useSignalIndex(); - const { data } = useQueryAlerts({ - query: buildEntityAlertsQuery(field, to, from, value, 500), + const { data, setQuery } = useQueryAlerts({ + query: buildEntityAlertsQuery(field, to, from, value, 500, ''), queryName: ALERTS_QUERY_NAMES.BY_RULE_BY_STATUS, indexName: signalIndexName, }); + const { filteredAlertsData: alertsData } = useNonClosedAlerts({ + field, + value, + to, + from, + queryId: `${DETECTION_RESPONSE_ALERTS_BY_STATUS_ID}`, + }); + + const severityMap = new Map(); + (Object.keys(alertsData || {}) as AlertsByStatus[]).forEach((status) => { + if (alertsData?.[status]?.severities) { + alertsData?.[status]?.severities.forEach((severity) => { + const currentSeverity = severityMap.get(severity.key) || 0; + severityMap.set(severity.key, currentSeverity + severity.value); + }); + } + }); + + const alertStats = Array.from(severityMap, ([key, count]) => ({ + key: capitalize(key), + count, + color: getSeverityColor(key), + filter: () => { + setCurrentFilter(key); + setQuery(buildEntityAlertsQuery(field, to, from, value, 500, key)); + }, + isCurrentFilter: currentFilter === key, + reset: (event: React.MouseEvent) => { + setCurrentFilter(''); + setQuery(buildEntityAlertsQuery(field, to, from, value, 500, '')); + event?.stopPropagation(); + }, + })); + const alertDataResults = (data?.hits?.hits as AlertsDetailsFields[])?.map( (item: AlertsDetailsFields) => { return { @@ -108,19 +147,6 @@ export const AlertsDetailsTable = memo( } ); - const severitiesMap = alertDataResults?.map((item) => item.severity) || []; - - const alertStats = Object.entries( - severitiesMap.reduce((acc: Record, item) => { - acc[item] = (acc[item] || 0) + 1; - return acc; - }, {}) - ).map(([key, count]) => ({ - key: capitalize(key), - count, - color: getSeverityColor(key), - })); - const { pageOfItems, totalItemCount } = alertsPagination(alertDataResults || []); const pagination = { 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 00430c2b87262..61c89e196a183 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 @@ -10,8 +10,12 @@ import type { Criteria, EuiBasicTableColumn } from '@elastic/eui'; import { EuiSpacer, EuiPanel, EuiText, EuiBasicTable, EuiIcon } from '@elastic/eui'; import { useMisconfigurationFindings } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_findings'; import { i18n } from '@kbn/i18n'; -import type { CspFinding, CspFindingResult } from '@kbn/cloud-security-posture-common'; -import { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common'; +import { + MISCONFIGURATION_STATUS, + type CspFinding, + type CspFindingResult, + buildMisconfigurationEntityFlyoutPreviewQuery, +} from '@kbn/cloud-security-posture-common'; import { euiThemeVars } from '@kbn/ui-theme'; import { DistributionBar } from '@kbn/security-solution-distribution-bar'; import type { CspBenchmarkRuleMetadata } from '@kbn/cloud-security-posture-common/schema/rules/latest'; @@ -25,11 +29,17 @@ import { import { METRIC_TYPE } from '@kbn/analytics'; import { useGetNavigationUrlParams } from '@kbn/cloud-security-posture/src/hooks/use_get_navigation_url_params'; import { SecurityPageName } from '@kbn/deeplinks-security'; +import { useHasMisconfigurations } from '@kbn/cloud-security-posture/src/hooks/use_has_misconfigurations'; import { SecuritySolutionLinkAnchor } from '../../../common/components/links'; type MisconfigurationFindingDetailFields = Pick; -const getFindingsStats = (passedFindingsStats: number, failedFindingsStats: number) => { +const getFindingsStats = ( + passedFindingsStats: number, + failedFindingsStats: number, + filterFunction: (filter: string) => void, + currentFilter: string +) => { if (passedFindingsStats === 0 && failedFindingsStats === 0) return []; return [ { @@ -41,6 +51,14 @@ const getFindingsStats = (passedFindingsStats: number, failedFindingsStats: numb ), count: passedFindingsStats, color: euiThemeVars.euiColorSuccess, + filter: () => { + filterFunction(MISCONFIGURATION_STATUS.PASSED); + }, + isCurrentFilter: currentFilter === MISCONFIGURATION_STATUS.PASSED, + reset: (event: React.MouseEvent) => { + filterFunction(''); + event?.stopPropagation(); + }, }, { key: i18n.translate( @@ -51,6 +69,14 @@ const getFindingsStats = (passedFindingsStats: number, failedFindingsStats: numb ), count: failedFindingsStats, color: euiThemeVars.euiColorVis9, + filter: () => { + filterFunction(MISCONFIGURATION_STATUS.FAILED); + }, + isCurrentFilter: currentFilter === MISCONFIGURATION_STATUS.FAILED, + reset: (event: React.MouseEvent) => { + filterFunction(''); + event?.stopPropagation(); + }, }, ]; }; @@ -67,15 +93,16 @@ export const MisconfigurationFindingsDetailsTable = memo( ); }, []); + const [currentFilter, setCurrentFilter] = useState(''); + const { data } = useMisconfigurationFindings({ - query: buildEntityFlyoutPreviewQuery(field, value), + query: buildMisconfigurationEntityFlyoutPreviewQuery(field, value, currentFilter), sort: [], enabled: true, pageSize: 1, }); - const passedFindings = data?.count.passed || 0; - const failedFindings = data?.count.failed || 0; + const { passedFindings, failedFindings } = useHasMisconfigurations(field, value); const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(10); @@ -129,6 +156,13 @@ export const MisconfigurationFindingsDetailsTable = memo( const linkWidth = 40; const resultWidth = 74; + const misconfgurationStats = getFindingsStats( + passedFindings, + failedFindings, + setCurrentFilter, + currentFilter + ); + const columns: Array> = [ { field: 'rule', @@ -202,7 +236,7 @@ export const MisconfigurationFindingsDetailsTable = memo( - + (''); + const { data } = useVulnerabilitiesFindings({ - query: buildEntityFlyoutPreviewQuery('host.name', value), + query: buildVulnerabilityEntityFlyoutPreviewQuery('host.name', value, currentFilter), sort: [], enabled: true, pageSize: 1, }); - const { CRITICAL = 0, HIGH = 0, MEDIUM = 0, LOW = 0, NONE = 0 } = data?.count || {}; + const { counts } = useHasVulnerabilities('host.name', value); + + const { critical = 0, high = 0, medium = 0, low = 0, none = 0 } = counts || {}; const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(10); @@ -120,6 +127,18 @@ export const VulnerabilitiesFindingsDetailsTable = memo(({ value }: { value: str ); }; + const vulnerabilityStats = getVulnerabilityStats( + { + critical, + high, + medium, + low, + none, + }, + setCurrentFilter, + currentFilter + ); + const columns: Array> = [ { field: 'vulnerability', @@ -220,15 +239,7 @@ export const VulnerabilitiesFindingsDetailsTable = memo(({ value }: { value: str - + = const { scopeId, isPreview } = useDocumentDetailsContext(); const { euiTheme } = useEuiTheme(); const { data } = useMisconfigurationPreview({ - query: buildEntityFlyoutPreviewQuery(fieldName, name), + query: buildGenericEntityFlyoutPreviewQuery(fieldName, name), sort: [], enabled: true, pageSize: 1, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/vulnerabilities_insight.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/vulnerabilities_insight.tsx index 1dab4660194b9..8991124a77e24 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/vulnerabilities_insight.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/vulnerabilities_insight.tsx @@ -10,7 +10,7 @@ import { EuiFlexItem, type EuiFlexGroupProps, useEuiTheme } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { css } from '@emotion/css'; import { useVulnerabilitiesPreview } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview'; -import { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common'; +import { buildGenericEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common'; import { getVulnerabilityStats, hasVulnerabilitiesData } from '@kbn/cloud-security-posture'; import { uiMetricService, @@ -53,7 +53,7 @@ export const VulnerabilitiesInsight: React.FC = ({ const { scopeId, isPreview } = useDocumentDetailsContext(); const { euiTheme } = useEuiTheme(); const { data } = useVulnerabilitiesPreview({ - query: buildEntityFlyoutPreviewQuery('host.name', hostName), + query: buildGenericEntityFlyoutPreviewQuery('host.name', hostName), sort: [], enabled: true, pageSize: 1,