diff --git a/x-pack/platform/packages/shared/kbn-cloud-security-posture/common/utils/helpers.test.ts b/x-pack/platform/packages/shared/kbn-cloud-security-posture/common/utils/helpers.test.ts index 6b85d2ead28fe..edcef278ab593 100644 --- a/x-pack/platform/packages/shared/kbn-cloud-security-posture/common/utils/helpers.test.ts +++ b/x-pack/platform/packages/shared/kbn-cloud-security-posture/common/utils/helpers.test.ts @@ -318,8 +318,18 @@ describe('test helper methods', () => { }); describe('buildEntityAlertsQuery', () => { - const getExpectedAlertsQuery = (size?: number, severity?: string) => { + const field: 'host.name' | 'user.name' = 'host.name'; + const query = 'exampleHost'; + const to = 'Tomorrow'; + const from = 'Today'; + const getExpectedAlertsQuery = ( + size?: number, + severity?: string, + sortField?: string, + sortDirection?: 'asc' | 'desc' + ) => { return { + sort: sortField ? [{ [sortField]: sortDirection }] : [], size: size || 0, _source: false, fields: [ @@ -379,37 +389,64 @@ describe('test helper methods', () => { }; it('should return the correct query when given all params', () => { - const field = 'host.name'; - const query = 'exampleHost'; - const to = 'Tomorrow'; - const from = 'Today'; const size = 100; + const testObjectParams = { + field, + to, + from, + queryValue: query, + size, + }; - expect(buildEntityAlertsQuery(field, to, from, query, size)).toEqual( - getExpectedAlertsQuery(size) - ); + expect(buildEntityAlertsQuery(testObjectParams)).toEqual(getExpectedAlertsQuery(size)); }); it('should return the correct query when not given size', () => { - const field = 'host.name'; - const query = 'exampleHost'; - const to = 'Tomorrow'; - const from = 'Today'; const size = undefined; + const testObjectParams = { + field, + to, + from, + queryValue: query, + size, + }; - expect(buildEntityAlertsQuery(field, to, from, query)).toEqual(getExpectedAlertsQuery(size)); + expect(buildEntityAlertsQuery(testObjectParams)).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'; + const testObjectParams = { + field, + to, + from, + queryValue: query, + size, + severity, + }; + + expect(buildEntityAlertsQuery(testObjectParams)).toEqual(getExpectedAlertsQuery(size, 'low')); + }); + + it('should return the correct query when given sort parameter', () => { + const size = undefined; + const severity = 'low'; + const sortField = 'sort.field'; + const sortDirection = 'asc'; + const testObjectParams = { + field, + to, + from, + queryValue: query, + size, + severity, + sortField, + sortDirection, + }; - expect(buildEntityAlertsQuery(field, to, from, query, size, severity)).toEqual( - getExpectedAlertsQuery(size, 'low') + expect(buildEntityAlertsQuery(testObjectParams)).toEqual( + getExpectedAlertsQuery(size, 'low', sortField, sortDirection) ); }); }); diff --git a/x-pack/platform/packages/shared/kbn-cloud-security-posture/common/utils/helpers.ts b/x-pack/platform/packages/shared/kbn-cloud-security-posture/common/utils/helpers.ts index 8335fea705069..4283436418eab 100644 --- a/x-pack/platform/packages/shared/kbn-cloud-security-posture/common/utils/helpers.ts +++ b/x-pack/platform/packages/shared/kbn-cloud-security-posture/common/utils/helpers.ts @@ -9,6 +9,17 @@ import { QueryDslQueryContainer } from '@kbn/data-views-plugin/common/types'; import { i18n } from '@kbn/i18n'; import type { CspBenchmarkRulesStates } from '../schema/rules/latest'; +interface BuildEntityAlertsQueryParams { + field: 'user.name' | 'host.name'; + to: string; + from: string; + queryValue?: string; + size?: number; + severity?: string; + sortField?: string; + sortDirection?: string; +} + export const defaultErrorMessage = i18n.translate( 'sharedPlatformPackages.csp.common.utils.helpers.unknownError', { @@ -106,17 +117,20 @@ export const buildVulnerabilityEntityFlyoutPreviewQuery = ( return buildGenericEntityFlyoutPreviewQuery(field, queryValue, status, queryField); }; -export const buildEntityAlertsQuery = ( - field: string, - to: string, - from: string, - queryValue?: string, - size?: number, - severity?: string -) => { +export const buildEntityAlertsQuery = ({ + field, + to, + from, + queryValue = '', + size = 0, + severity, + sortField, + sortDirection, +}: BuildEntityAlertsQueryParams) => { return { size: size || 0, _source: false, + sort: sortField ? [{ [sortField]: sortDirection }] : [], fields: [ '_id', '_index', diff --git a/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/hooks/use_misconfiguration_findings.ts b/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/hooks/use_misconfiguration_findings.ts index d07ac51cc6075..e77a9d61cb686 100644 --- a/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/hooks/use_misconfiguration_findings.ts +++ b/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/hooks/use_misconfiguration_findings.ts @@ -23,6 +23,18 @@ import { getMisconfigurationAggregationCount, } from '../utils/hooks_utils'; +export enum MISCONFIGURATION { + RESULT_EVALUATION = 'result.evaluation', + RULE_NAME = 'rule.name', +} +export interface MisconfigurationFindingTableDetailsFields { + [MISCONFIGURATION.RESULT_EVALUATION]: string; + [MISCONFIGURATION.RULE_NAME]: string; +} + +export type MisconfigurationFindingDetailFields = Pick & + MisconfigurationFindingTableDetailsFields; + export const useMisconfigurationFindings = (options: UseCspOptions) => { const { data, @@ -50,10 +62,11 @@ export const useMisconfigurationFindings = (options: UseCspOptions) => { return { count: getMisconfigurationAggregationCount(aggregations?.count.buckets), rows: hits.hits.map((finding) => ({ - result: finding._source?.result, rule: finding?._source?.rule, resource: finding?._source?.resource, - })) as Array>, + [MISCONFIGURATION.RULE_NAME]: finding?._source?.rule?.name, + [MISCONFIGURATION.RESULT_EVALUATION]: finding._source?.result?.evaluation, + })) as MisconfigurationFindingDetailFields[], }; }, { diff --git a/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/hooks/use_vulnerabilities_findings.ts b/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/hooks/use_vulnerabilities_findings.ts index d8e44ad5c4328..cf39aa295dffc 100644 --- a/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/hooks/use_vulnerabilities_findings.ts +++ b/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/hooks/use_vulnerabilities_findings.ts @@ -14,17 +14,43 @@ import { AggregationsMultiBucketAggregateBase, AggregationsStringRareTermsBucketKeys, } from '@elastic/elasticsearch/lib/api/types'; -import type { CspVulnerabilityFinding } from '@kbn/cloud-security-posture-common/schema/vulnerabilities/latest'; +import type { + CspVulnerabilityFinding, + Vulnerability, +} from '@kbn/cloud-security-posture-common/schema/vulnerabilities/latest'; import type { CoreStart } from '@kbn/core/public'; import type { CspClientPluginStartDeps, UseCspOptions } from '../types'; import { showErrorToast } from '../..'; import { getVulnerabilitiesAggregationCount, getVulnerabilitiesQuery } from '../utils/hooks_utils'; +export enum VULNERABILITY { + ID = 'vulnerability.id', + SEVERITY = 'vulnerability.severity', + PACKAGE_NAME = 'vulnerability.package.name', +} + type LatestFindingsRequest = IKibanaSearchRequest; type LatestFindingsResponse = IKibanaSearchResponse< SearchResponse >; +export interface VulnerabilitiesPackage extends Vulnerability { + package: { + name: string; + version: string; + }; +} + +export interface VulnerabilitiesFindingTableDetailsFields { + [VULNERABILITY.ID]: string; + [VULNERABILITY.SEVERITY]: string; + [VULNERABILITY.PACKAGE_NAME]: string; +} + +export type VulnerabilitiesFindingDetailFields = Pick & + Pick & + VulnerabilitiesFindingTableDetailsFields; + interface FindingsAggs { count: AggregationsMultiBucketAggregateBase; } @@ -56,7 +82,11 @@ export const useVulnerabilitiesFindings = (options: UseCspOptions) => { rows: hits.hits.map((finding) => ({ vulnerability: finding._source?.vulnerability, resource: finding._source?.resource, - })) as Array>, + score: finding._source?.vulnerability?.score, + [VULNERABILITY.ID]: finding._source?.vulnerability?.id, + [VULNERABILITY.SEVERITY]: finding._source?.vulnerability?.severity, + [VULNERABILITY.PACKAGE_NAME]: finding._source?.package?.name, + })) as VulnerabilitiesFindingDetailFields[], }; }, { diff --git a/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/types.ts b/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/types.ts index 03fa60479806b..738df0265a9c9 100644 --- a/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/types.ts +++ b/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/types.ts @@ -64,7 +64,9 @@ export interface CspBaseEsQuery { } export interface UseCspOptions extends CspBaseEsQuery { - sort: string[][]; + sort: Array<{ + [key: string]: string; + }>; enabled: boolean; pageSize: number; ignore_unavailable?: boolean; diff --git a/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/utils/hooks_utils.ts b/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/utils/hooks_utils.ts index 88416477021ca..324549098ed17 100644 --- a/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/utils/hooks_utils.ts +++ b/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/utils/hooks_utils.ts @@ -73,12 +73,11 @@ export const getMisconfigurationAggregationCount = ( }; export const buildMisconfigurationsFindingsQuery = ( - { query }: UseCspOptions, + { query, sort }: UseCspOptions, rulesStates: CspBenchmarkRulesStates, isPreview = false ) => { const mutedRulesFilterQuery = buildMutedRulesFilter(rulesStates); - return { index: CDR_MISCONFIGURATIONS_INDEX_PATTERN, size: isPreview ? 0 : 500, @@ -86,6 +85,7 @@ export const buildMisconfigurationsFindingsQuery = ( ignore_unavailable: true, query: buildMisconfigurationsFindingsQueryWithFilters(query, mutedRulesFilterQuery), _source: MISCONFIGURATIONS_SOURCE_FIELDS, + sort, }; }; @@ -163,12 +163,13 @@ export const getFindingsCountAggQueryVulnerabilities = () => ({ }, }); -export const getVulnerabilitiesQuery = ({ query }: UseCspOptions, isPreview = false) => ({ +export const getVulnerabilitiesQuery = ({ query, sort }: UseCspOptions, isPreview = false) => ({ index: CDR_VULNERABILITIES_INDEX_PATTERN, size: isPreview ? 0 : 500, aggs: getFindingsCountAggQueryVulnerabilities(), ignore_unavailable: true, query: buildVulnerabilityFindingsQueryWithFilters(query), + sort, }); const buildVulnerabilityFindingsQueryWithFilters = (query: UseCspOptions['query']) => { diff --git a/x-pack/solutions/security/plugins/security_solution/public/cloud_security_posture/components/csp_details/alerts_findings_details_table.tsx b/x-pack/solutions/security/plugins/security_solution/public/cloud_security_posture/components/csp_details/alerts_findings_details_table.tsx index 2b1a2002667c2..78c5604615903 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/cloud_security_posture/components/csp_details/alerts_findings_details_table.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/cloud_security_posture/components/csp_details/alerts_findings_details_table.tsx @@ -7,7 +7,7 @@ import React, { memo, useCallback, useEffect, useState } from 'react'; import { capitalize } from 'lodash'; -import type { Criteria, EuiBasicTableColumn } from '@elastic/eui'; +import type { Criteria, EuiBasicTableColumn, EuiTableSortingType } from '@elastic/eui'; import { EuiSpacer, EuiPanel, EuiText, EuiBasicTable, EuiIcon, EuiLink } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { DistributionBar } from '@kbn/security-solution-distribution-bar'; @@ -38,24 +38,35 @@ import { ALERT_PREVIEW_BANNER } from '../../../flyout/document_details/preview/c import { FILTER_OPEN, FILTER_ACKNOWLEDGED } from '../../../../common/types'; import { useNonClosedAlerts } from '../../hooks/use_non_closed_alerts'; +enum KIBANA_ALERTS { + SEVERITY = 'kibana.alert.severity', + RULE_NAME = 'kibana.alert.rule.name', + WORKFLOW_STATUS = 'kibana.alert.workflow_status', +} + type AlertSeverity = 'low' | 'medium' | 'high' | 'critical'; +type AlertsSortFieldType = + | 'id' + | 'index' + | KIBANA_ALERTS.SEVERITY + | KIBANA_ALERTS.WORKFLOW_STATUS + | KIBANA_ALERTS.RULE_NAME; + interface ResultAlertsField { _id: string[]; _index: string[]; - 'kibana.alert.rule.uuid': string[]; - 'kibana.alert.severity': AlertSeverity[]; - 'kibana.alert.rule.name': string[]; - 'kibana.alert.workflow_status': string[]; + [KIBANA_ALERTS.SEVERITY]: AlertSeverity[]; + [KIBANA_ALERTS.RULE_NAME]: string[]; + [KIBANA_ALERTS.WORKFLOW_STATUS]: string[]; } interface ContextualFlyoutAlertsField { id: string; index: string; - ruleUuid: string; - ruleName: string; - severity: AlertSeverity; - status: string; + [KIBANA_ALERTS.SEVERITY]: AlertSeverity; + [KIBANA_ALERTS.RULE_NAME]: string; + [KIBANA_ALERTS.WORKFLOW_STATUS]: string; } interface AlertsDetailsFields { @@ -76,6 +87,16 @@ export const AlertsDetailsTable = memo( const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(10); + const [sortField, setSortField] = useState(KIBANA_ALERTS.SEVERITY); + const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc'); + + const sorting: EuiTableSortingType = { + sort: { + field: sortField, + direction: sortDirection, + }, + }; + const alertsPagination = (alerts: ContextualFlyoutAlertsField[]) => { let pageOfItems; @@ -95,7 +116,16 @@ export const AlertsDetailsTable = memo( const { to, from } = useGlobalTime(); const { signalIndexName } = useSignalIndex(); const { data, setQuery } = useQueryAlerts({ - query: buildEntityAlertsQuery(field, to, from, value, 500, ''), + query: buildEntityAlertsQuery({ + field, + to, + from, + queryValue: value, + size: 500, + severity: '', + sortField, + sortDirection, + }), queryName: ALERTS_QUERY_NAMES.BY_RULE_BY_STATUS, indexName: signalIndexName, }); @@ -124,12 +154,32 @@ export const AlertsDetailsTable = memo( color: getSeverityColor(key), filter: () => { setCurrentFilter(key); - setQuery(buildEntityAlertsQuery(field, to, from, value, 500, key)); + setQuery( + buildEntityAlertsQuery({ + field, + to, + from, + queryValue: value, + size: 500, + severity: key, + sortField, + sortDirection, + }) + ); }, isCurrentFilter: currentFilter === key, reset: (event: React.MouseEvent) => { setCurrentFilter(''); - setQuery(buildEntityAlertsQuery(field, to, from, value, 500, '')); + setQuery( + buildEntityAlertsQuery({ + field, + to, + from, + queryValue: value, + size: 500, + severity: '', + }) + ); event?.stopPropagation(); }, })); @@ -139,10 +189,9 @@ export const AlertsDetailsTable = memo( return { id: item.fields?._id?.[0], index: item.fields?._index?.[0], - ruleName: item.fields?.['kibana.alert.rule.name']?.[0], - ruleUuid: item.fields?.['kibana.alert.rule.uuid']?.[0], - severity: item.fields?.['kibana.alert.severity']?.[0], - status: item.fields?.['kibana.alert.workflow_status']?.[0], + [KIBANA_ALERTS.RULE_NAME]: item.fields?.[KIBANA_ALERTS.RULE_NAME]?.[0], + [KIBANA_ALERTS.SEVERITY]: item.fields?.[KIBANA_ALERTS.SEVERITY]?.[0], + [KIBANA_ALERTS.WORKFLOW_STATUS]: item.fields?.[KIBANA_ALERTS.WORKFLOW_STATUS]?.[0], }; } ); @@ -156,13 +205,34 @@ export const AlertsDetailsTable = memo( pageSizeOptions: [10, 25, 100], }; - const onTableChange = ({ page }: Criteria) => { - if (page) { - const { index, size } = page; - setPageIndex(index); - setPageSize(size); - } - }; + const onTableChange = useCallback( + ({ page, sort }: Criteria) => { + if (page) { + const { index, size } = page; + setPageIndex(index); + setPageSize(size); + } + + if (sort) { + const { field: fieldSort, direction } = sort; + setSortField(fieldSort); + setSortDirection(direction); + setQuery( + buildEntityAlertsQuery({ + field, + to, + from, + queryValue: value, + size: 500, + severity: currentFilter, + sortField: fieldSort, + sortDirection: direction, + }) + ); + } + }, + [currentFilter, field, from, setQuery, to, value] + ); const { openPreviewPanel } = useExpandableFlyoutApi(); @@ -196,7 +266,7 @@ export const AlertsDetailsTable = memo( ), }, { - field: 'ruleName', + field: KIBANA_ALERTS.RULE_NAME, render: (ruleName: string) => {ruleName}, name: i18n.translate( 'xpack.securitySolution.flyout.left.insights.alerts.table.ruleNameColumnName', @@ -205,9 +275,10 @@ export const AlertsDetailsTable = memo( } ), width: '55%', + sortable: true, }, { - field: 'severity', + field: KIBANA_ALERTS.SEVERITY, render: (severity: AlertSeverity) => ( @@ -220,9 +291,10 @@ export const AlertsDetailsTable = memo( } ), width: '20%', + sortable: true, }, { - field: 'status', + field: KIBANA_ALERTS.WORKFLOW_STATUS, render: (status: string) => {capitalize(status)}, name: i18n.translate( 'xpack.securitySolution.flyout.left.insights.alerts.table.statusColumnName', @@ -231,6 +303,7 @@ export const AlertsDetailsTable = memo( } ), width: '20%', + sortable: true, }, ]; @@ -281,6 +354,7 @@ export const AlertsDetailsTable = memo( pagination={pagination} onChange={onTableChange} data-test-subj={'securitySolutionFlyoutMisconfigurationFindingsTable'} + sorting={sorting} /> diff --git a/x-pack/solutions/security/plugins/security_solution/public/cloud_security_posture/components/csp_details/misconfiguration_findings_details_table.tsx b/x-pack/solutions/security/plugins/security_solution/public/cloud_security_posture/components/csp_details/misconfiguration_findings_details_table.tsx index 61c89e196a183..c03f7585cfc72 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/cloud_security_posture/components/csp_details/misconfiguration_findings_details_table.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/cloud_security_posture/components/csp_details/misconfiguration_findings_details_table.tsx @@ -5,15 +5,18 @@ * 2.0. */ -import React, { memo, useEffect, useState } from 'react'; -import type { Criteria, EuiBasicTableColumn } from '@elastic/eui'; +import React, { memo, useCallback, useEffect, useState } from 'react'; +import type { Criteria, EuiBasicTableColumn, EuiTableSortingType } 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 type { MisconfigurationFindingDetailFields } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_findings'; +import { + useMisconfigurationFindings, + MISCONFIGURATION, +} from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_findings'; import { i18n } from '@kbn/i18n'; +import type { CspFindingResult } 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'; @@ -32,7 +35,11 @@ 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; +type MisconfigurationSortFieldType = + | MISCONFIGURATION.RESULT_EVALUATION + | MISCONFIGURATION.RULE_NAME + | 'resource' + | 'rule'; const getFindingsStats = ( passedFindingsStats: number, @@ -95,9 +102,17 @@ export const MisconfigurationFindingsDetailsTable = memo( const [currentFilter, setCurrentFilter] = useState(''); + const [sortField, setSortField] = useState( + MISCONFIGURATION.RESULT_EVALUATION + ); + const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc'); + + const sortFieldDirection: { [key: string]: string } = {}; + sortFieldDirection[sortField] = sortDirection; + const { data } = useMisconfigurationFindings({ query: buildMisconfigurationEntityFlyoutPreviewQuery(field, value, currentFilter), - sort: [], + sort: [sortFieldDirection], enabled: true, pageSize: 1, }); @@ -107,6 +122,13 @@ export const MisconfigurationFindingsDetailsTable = memo( const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(10); + const sorting: EuiTableSortingType = { + sort: { + field: sortField, + direction: sortDirection, + }, + }; + const findingsPagination = (findings: MisconfigurationFindingDetailFields[]) => { let pageOfItems; @@ -134,14 +156,21 @@ export const MisconfigurationFindingsDetailsTable = memo( totalItemCount, pageSizeOptions: [10, 25, 100], }; - - const onTableChange = ({ page }: Criteria) => { - if (page) { - const { index, size } = page; - setPageIndex(index); - setPageSize(size); - } - }; + const onTableChange = useCallback( + ({ page, sort }: Criteria) => { + if (page) { + const { index, size } = page; + setPageIndex(index); + setPageSize(size); + } + if (sort) { + const { field: fieldSort, direction } = sort; + setSortField(fieldSort); + setSortDirection(direction); + } + }, + [] + ); const getNavUrlParams = useGetNavigationUrlParams(); @@ -189,8 +218,10 @@ export const MisconfigurationFindingsDetailsTable = memo( ), }, { - field: 'result', - render: (result: CspFindingResult) => , + field: MISCONFIGURATION.RESULT_EVALUATION, + render: (result: CspFindingResult['evaluation'] | undefined) => ( + + ), name: i18n.translate( 'xpack.securitySolution.flyout.left.insights.misconfigurations.table.resultColumnName', { @@ -198,10 +229,13 @@ export const MisconfigurationFindingsDetailsTable = memo( } ), width: `${resultWidth}px`, + sortable: true, }, { - field: 'rule', - render: (rule: CspBenchmarkRuleMetadata) => {rule?.name}, + field: MISCONFIGURATION.RULE_NAME, + render: (ruleName: CspBenchmarkRuleMetadata['name']) => ( + {ruleName} + ), name: i18n.translate( 'xpack.securitySolution.flyout.left.insights.misconfigurations.table.ruleColumnName', { @@ -209,6 +243,7 @@ export const MisconfigurationFindingsDetailsTable = memo( } ), width: `calc(100% - ${linkWidth + resultWidth}px)`, + sortable: true, }, ]; @@ -245,6 +280,7 @@ export const MisconfigurationFindingsDetailsTable = memo( pagination={pagination} onChange={onTableChange} data-test-subj={'securitySolutionFlyoutMisconfigurationFindingsTable'} + sorting={sorting} /> diff --git a/x-pack/solutions/security/plugins/security_solution/public/cloud_security_posture/components/csp_details/vulnerabilities_findings_details_table.tsx b/x-pack/solutions/security/plugins/security_solution/public/cloud_security_posture/components/csp_details/vulnerabilities_findings_details_table.tsx index 84f85af509b85..5afc639624eda 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/cloud_security_posture/components/csp_details/vulnerabilities_findings_details_table.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/cloud_security_posture/components/csp_details/vulnerabilities_findings_details_table.tsx @@ -6,7 +6,7 @@ */ import React, { memo, useEffect, useState } from 'react'; -import type { Criteria, EuiBasicTableColumn } from '@elastic/eui'; +import type { Criteria, EuiBasicTableColumn, EuiTableSortingType } from '@elastic/eui'; import { EuiSpacer, EuiPanel, EuiText, EuiBasicTable, EuiIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { @@ -14,11 +14,14 @@ import { type VulnSeverity, } from '@kbn/cloud-security-posture-common'; import { DistributionBar } from '@kbn/security-solution-distribution-bar'; -import { useVulnerabilitiesFindings } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_findings'; import type { - CspVulnerabilityFinding, - Vulnerability, -} from '@kbn/cloud-security-posture-common/schema/vulnerabilities/csp_vulnerability_finding'; + VulnerabilitiesFindingDetailFields, + VulnerabilitiesPackage, +} from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_findings'; +import { + useVulnerabilitiesFindings, + VULNERABILITY, +} from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_findings'; import { getVulnerabilityStats, CVSScoreBadge, @@ -35,17 +38,13 @@ import { useGetNavigationUrlParams } from '@kbn/cloud-security-posture/src/hooks import { useHasVulnerabilities } from '@kbn/cloud-security-posture/src/hooks/use_has_vulnerabilities'; import { SecuritySolutionLinkAnchor } from '../../../common/components/links'; -type VulnerabilitiesFindingDetailFields = Pick< - CspVulnerabilityFinding, - 'vulnerability' | 'resource' ->; - -interface VulnerabilitiesPackage extends Vulnerability { - package: { - name: string; - version: string; - }; -} +type VulnerabilitySortFieldType = + | 'score' + | 'vulnerability' + | 'resource' + | VULNERABILITY.SEVERITY + | VULNERABILITY.ID + | VULNERABILITY.PACKAGE_NAME; export const VulnerabilitiesFindingsDetailsTable = memo(({ value }: { value: string }) => { useEffect(() => { @@ -56,10 +55,23 @@ export const VulnerabilitiesFindingsDetailsTable = memo(({ value }: { value: str }, []); const [currentFilter, setCurrentFilter] = useState(''); + const [sortField, setSortField] = useState(VULNERABILITY.SEVERITY); + const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc'); + + const sortFieldDirection: { [key: string]: string } = {}; + sortFieldDirection[sortField === 'score' ? 'vulnerability.score.base' : sortField] = + sortDirection; + + const sorting: EuiTableSortingType = { + sort: { + field: sortField, + direction: sortDirection, + }, + }; const { data } = useVulnerabilitiesFindings({ query: buildVulnerabilityEntityFlyoutPreviewQuery('host.name', value, currentFilter), - sort: [], + sort: [sortFieldDirection], enabled: true, pageSize: 1, }); @@ -96,12 +108,17 @@ export const VulnerabilitiesFindingsDetailsTable = memo(({ value }: { value: str pageSizeOptions: [10, 25, 100], }; - const onTableChange = ({ page }: Criteria) => { + const onTableChange = ({ page, sort }: Criteria) => { if (page) { const { index, size } = page; setPageIndex(index); setPageSize(size); } + if (sort) { + const { field: fieldSort, direction } = sort; + setSortField(fieldSort); + setSortDirection(direction); + } }; const getNavUrlParams = useGetNavigationUrlParams(); @@ -164,22 +181,20 @@ export const VulnerabilitiesFindingsDetailsTable = memo(({ value }: { value: str ), }, { - field: 'vulnerability', - render: (vulnerability: Vulnerability) => {vulnerability?.id}, + field: VULNERABILITY.ID, + render: (id: string) => {id}, name: i18n.translate( 'xpack.securitySolution.flyout.left.insights.vulnerability.table.resultColumnName', { defaultMessage: 'Vulnerability' } ), width: '20%', + sortable: true, }, { - field: 'vulnerability', - render: (vulnerability: Vulnerability) => ( + field: 'score', + render: (score: { version?: string; base?: number }) => ( - + ), name: i18n.translate( @@ -187,15 +202,14 @@ export const VulnerabilitiesFindingsDetailsTable = memo(({ value }: { value: str { defaultMessage: 'CVSS' } ), width: '15%', + sortable: true, }, { - field: 'vulnerability', - render: (vulnerability: Vulnerability) => ( + field: VULNERABILITY.SEVERITY, + render: (severity: string) => ( <> - + ), @@ -204,17 +218,17 @@ export const VulnerabilitiesFindingsDetailsTable = memo(({ value }: { value: str { defaultMessage: 'Severity' } ), width: '20%', + sortable: true, }, { - field: 'vulnerability', - render: (vulnerability: VulnerabilitiesPackage) => ( - {vulnerability?.package?.name} - ), + field: VULNERABILITY.PACKAGE_NAME, + render: (packageName: string) => {packageName}, name: i18n.translate( 'xpack.securitySolution.flyout.left.insights.vulnerability.table.ruleColumnName', { defaultMessage: 'Package' } ), width: '40%', + sortable: true, }, ]; @@ -248,6 +262,7 @@ export const VulnerabilitiesFindingsDetailsTable = memo(({ value }: { value: str pagination={pagination} onChange={onTableChange} data-test-subj={'securitySolutionFlyoutVulnerabilitiesFindingsTable'} + sorting={sorting} />