diff --git a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_benchmark_dynamic_values.test.ts b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_benchmark_dynamic_values.test.ts new file mode 100644 index 0000000000000..6207885b60ab0 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_benchmark_dynamic_values.test.ts @@ -0,0 +1,100 @@ +/* + * 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 { useBenchmarkDynamicValues } from './use_benchmark_dynamic_values'; +import { renderHook } from '@testing-library/react-hooks/dom'; +import { useCspIntegrationLink } from '../navigation/use_csp_integration_link'; +import { BenchmarksCisId } from '../../../common/types/benchmarks/v2'; + +jest.mock('../navigation/use_csp_integration_link'); + +describe('useBenchmarkDynamicValues', () => { + const setupMocks = (cspmIntegrationLink: string, kspmIntegrationLink: string) => { + (useCspIntegrationLink as jest.Mock) + .mockReturnValueOnce(cspmIntegrationLink) + .mockReturnValueOnce(kspmIntegrationLink); + }; + + it('should return the correct dynamic benchmark values for each provided benchmark ID', () => { + setupMocks('cspm-integration-link', 'kspm-integration-link'); + const { result } = renderHook(() => useBenchmarkDynamicValues()); + + const benchmarkValuesCisAws = result.current.getBenchmarkDynamicValues('cis_aws', 3); + expect(benchmarkValuesCisAws).toEqual({ + integrationType: 'CSPM', + integrationName: 'AWS', + resourceName: 'Accounts', + resourceCountLabel: 'accounts', + integrationLink: 'cspm-integration-link', + learnMoreLink: 'https://ela.st/cspm-get-started', + }); + + const benchmarkValuesCisGcp = result.current.getBenchmarkDynamicValues('cis_gcp', 0); + expect(benchmarkValuesCisGcp).toEqual({ + integrationType: 'CSPM', + integrationName: 'GCP', + resourceName: 'Projects', + resourceCountLabel: 'projects', + integrationLink: 'cspm-integration-link', + learnMoreLink: 'https://ela.st/cspm-get-started', + }); + + const benchmarkValuesCisAzure = result.current.getBenchmarkDynamicValues('cis_azure', 1); + expect(benchmarkValuesCisAzure).toEqual({ + integrationType: 'CSPM', + integrationName: 'Azure', + resourceName: 'Subscriptions', + resourceCountLabel: 'subscription', + integrationLink: 'cspm-integration-link', + learnMoreLink: 'https://ela.st/cspm-get-started', + }); + + const benchmarkValuesCisK8s = result.current.getBenchmarkDynamicValues('cis_k8s', 0); + expect(benchmarkValuesCisK8s).toEqual({ + integrationType: 'KSPM', + integrationName: 'Kubernetes', + resourceName: 'Clusters', + resourceCountLabel: 'clusters', + integrationLink: 'kspm-integration-link', + learnMoreLink: 'https://ela.st/kspm-get-started', + }); + + const benchmarkValuesCisEks = result.current.getBenchmarkDynamicValues('cis_eks'); + expect(benchmarkValuesCisEks).toEqual({ + integrationType: 'KSPM', + integrationName: 'EKS', + resourceName: 'Clusters', + resourceCountLabel: 'clusters', + integrationLink: 'kspm-integration-link', + learnMoreLink: 'https://ela.st/kspm-get-started', + }); + + const benchmarkValuesInvalid = result.current.getBenchmarkDynamicValues( + 'invalid_benchmark' as BenchmarksCisId + ); + expect(benchmarkValuesInvalid).toEqual({}); + }); + + it('should return the correct resource plurals based on the provided resource count', () => { + const { result } = renderHook(() => useBenchmarkDynamicValues()); + + const benchmarkValuesCisAws = result.current.getBenchmarkDynamicValues('cis_aws', 3); + expect(benchmarkValuesCisAws.resourceCountLabel).toBe('accounts'); + + const benchmarkValuesCisGcp = result.current.getBenchmarkDynamicValues('cis_gcp', 0); + expect(benchmarkValuesCisGcp.resourceCountLabel).toBe('projects'); + + const benchmarkValuesCisAzure = result.current.getBenchmarkDynamicValues('cis_azure', 1); + expect(benchmarkValuesCisAzure.resourceCountLabel).toBe('subscription'); + + const benchmarkValuesCisK8s = result.current.getBenchmarkDynamicValues('cis_k8s', 0); + expect(benchmarkValuesCisK8s.resourceCountLabel).toBe('clusters'); + + const benchmarkValuesCisEks = result.current.getBenchmarkDynamicValues('cis_eks'); + expect(benchmarkValuesCisEks.resourceCountLabel).toBe('clusters'); + }); +}); diff --git a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_benchmark_dynamic_values.ts b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_benchmark_dynamic_values.ts new file mode 100644 index 0000000000000..5fe3f8f69050a --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_benchmark_dynamic_values.ts @@ -0,0 +1,149 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { useCspIntegrationLink } from '../navigation/use_csp_integration_link'; +import { CSPM_POLICY_TEMPLATE, KSPM_POLICY_TEMPLATE } from '../../../common/constants'; +import { BenchmarksCisId } from '../../../common/types/benchmarks/v2'; + +type BenchmarkDynamicNames = + | { + integrationType: 'CSPM'; + integrationName: 'AWS'; + resourceName: 'Accounts'; + } + | { + integrationType: 'CSPM'; + integrationName: 'GCP'; + resourceName: 'Projects'; + } + | { + integrationType: 'CSPM'; + integrationName: 'Azure'; + resourceName: 'Subscriptions'; + } + | { + integrationType: 'KSPM'; + integrationName: 'Kubernetes'; + resourceName: 'Clusters'; + } + | { + integrationType: 'KSPM'; + integrationName: 'EKS'; + resourceName: 'Clusters'; + }; + +export type BenchmarkDynamicValues = BenchmarkDynamicNames & { + resourceCountLabel: string; + integrationLink: string; + learnMoreLink: string; +}; + +export type GetBenchmarkDynamicValues = ( + benchmarkId: BenchmarksCisId, + resourceCount?: number +) => BenchmarkDynamicValues; + +export const useBenchmarkDynamicValues = () => { + const cspmIntegrationLink = useCspIntegrationLink(CSPM_POLICY_TEMPLATE) || ''; + const kspmIntegrationLink = useCspIntegrationLink(KSPM_POLICY_TEMPLATE) || ''; + + /** + * Retrieves dynamic benchmark values based on the provided benchmark ID and resource count. + * + * @param {BenchmarksCisId} benchmarkId - The benchmark ID. + * @param {number} [resourceCount] - The count of resources (optional). + * @returns {BenchmarkDynamicValues} The dynamic benchmark values including integration details, + * resource name, resource count label in plurals/singular, integration link, and learn more link. + * + * @example + * const benchmarkValues = getBenchmarkDynamicValues('cis_aws', 3); + * // Returns: + * // { + * // integrationType: 'CSPM', + * // integrationName: 'AWS', + * // resourceName: 'Accounts', + * // resourceCountLabel: 'accounts', + * // integrationLink: 'cspm-integration-link', + * // learnMoreLink: 'https://ela.st/cspm-get-started' + * // } + */ + const getBenchmarkDynamicValues: GetBenchmarkDynamicValues = ( + benchmarkId: BenchmarksCisId, + resourceCount?: number + ): BenchmarkDynamicValues => { + switch (benchmarkId) { + case 'cis_aws': + return { + integrationType: 'CSPM', + integrationName: 'AWS', + resourceName: 'Accounts', + resourceCountLabel: i18n.translate('xpack.csp.benchmarkDynamicValues.AwsAccountPlural', { + defaultMessage: '{resourceCount, plural, one {account} other {accounts}}', + values: { resourceCount: resourceCount || 0 }, + }), + integrationLink: cspmIntegrationLink, + learnMoreLink: 'https://ela.st/cspm-get-started', + }; + case 'cis_gcp': + return { + integrationType: 'CSPM', + integrationName: 'GCP', + resourceName: 'Projects', + resourceCountLabel: i18n.translate('xpack.csp.benchmarkDynamicValues.GcpAccountPlural', { + defaultMessage: '{resourceCount, plural, one {project} other {projects}}', + values: { resourceCount: resourceCount || 0 }, + }), + integrationLink: cspmIntegrationLink, + learnMoreLink: 'https://ela.st/cspm-get-started', + }; + case 'cis_azure': + return { + integrationType: 'CSPM', + integrationName: 'Azure', + resourceName: 'Subscriptions', + resourceCountLabel: i18n.translate( + 'xpack.csp.benchmarkDynamicValues.AzureAccountPlural', + { + defaultMessage: '{resourceCount, plural, one {subscription} other {subscriptions}}', + values: { resourceCount: resourceCount || 0 }, + } + ), + integrationLink: cspmIntegrationLink, + learnMoreLink: 'https://ela.st/cspm-get-started', + }; + case 'cis_k8s': + return { + integrationType: 'KSPM', + integrationName: 'Kubernetes', + resourceName: 'Clusters', + resourceCountLabel: i18n.translate('xpack.csp.benchmarkDynamicValues.K8sAccountPlural', { + defaultMessage: '{resourceCount, plural, one {cluster} other {clusters}}', + values: { resourceCount: resourceCount || 0 }, + }), + integrationLink: kspmIntegrationLink, + learnMoreLink: 'https://ela.st/kspm-get-started', + }; + case 'cis_eks': + return { + integrationType: 'KSPM', + integrationName: 'EKS', + resourceName: 'Clusters', + resourceCountLabel: i18n.translate('xpack.csp.benchmarkDynamicValues.EksAccountPlural', { + defaultMessage: '{resourceCount, plural, one {cluster} other {clusters}}', + values: { resourceCount: resourceCount || 0 }, + }), + integrationLink: kspmIntegrationLink, + learnMoreLink: 'https://ela.st/kspm-get-started', + }; + default: + return {} as BenchmarkDynamicValues; + } + }; + + return { getBenchmarkDynamicValues }; +}; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks_table.tsx b/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks_table.tsx index 25690410a9780..32cb0c6f11f98 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks_table.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks_table.tsx @@ -15,12 +15,14 @@ import { EuiFlexGroup, EuiFlexItem, EuiLink, + EuiButtonEmpty, } from '@elastic/eui'; import React, { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { generatePath } from 'react-router-dom'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { FINDINGS_GROUPING_OPTIONS } from '../../common/constants'; +import { useNavigateFindings } from '../../common/hooks/use_navigate_findings'; import type { BenchmarkScore, Benchmark, BenchmarksCisId } from '../../../common/types/latest'; import * as TEST_SUBJ from './test_subjects'; import { isCommonError } from '../../components/cloud_posture_page'; @@ -29,6 +31,11 @@ import { ComplianceScoreBar } from '../../components/compliance_score_bar'; import { getBenchmarkCisName, getBenchmarkApplicableTo } from '../../../common/utils/helpers'; import { CISBenchmarkIcon } from '../../components/cis_benchmark_icon'; import { benchmarksNavigation } from '../../common/navigation/constants'; +import { + GetBenchmarkDynamicValues, + useBenchmarkDynamicValues, +} from '../../common/hooks/use_benchmark_dynamic_values'; +import { useKibana } from '../../common/hooks/use_kibana'; export const ERROR_STATE_TEST_SUBJECT = 'benchmark_page_error'; @@ -62,51 +69,6 @@ const BenchmarkButtonLink = ({ ); }; -export const getBenchmarkPlurals = (benchmarkId: string, accountEvaluation: number) => { - switch (benchmarkId) { - case 'cis_k8s': - return ( - - ); - case 'cis_azure': - return ( - - ); - case 'cis_aws': - return ( - - ); - case 'cis_eks': - return ( - - ); - case 'cis_gcp': - return ( - - ); - } -}; - const ErrorMessageComponent = (error: { error: unknown }) => ( ( ); -const BENCHMARKS_TABLE_COLUMNS: Array> = [ +const getBenchmarkTableColumns = ( + getBenchmarkDynamicValues: GetBenchmarkDynamicValues, + navToFindings: any +): Array> => [ { field: 'id', name: i18n.translate('xpack.csp.benchmarks.benchmarksTable.integrationBenchmarkCisName', { @@ -198,7 +163,40 @@ const BENCHMARKS_TABLE_COLUMNS: Array> = [ width: '17.5%', 'data-test-subj': TEST_SUBJ.BENCHMARKS_TABLE_COLUMNS.EVALUATED, render: (benchmarkEvaluation: Benchmark['evaluation'], benchmark: Benchmark) => { - return getBenchmarkPlurals(benchmark.id, benchmarkEvaluation); + const { resourceCountLabel, integrationLink } = getBenchmarkDynamicValues( + benchmark.id, + benchmarkEvaluation + ); + + if (benchmarkEvaluation === 0) { + return ( + + {i18n.translate('xpack.csp.benchmarks.benchmarksTable.addIntegrationTitle', { + defaultMessage: 'Add {resourceCountLabel}', + values: { resourceCountLabel }, + })} + + ); + } + + const isKspmBenchmark = ['cis_k8s', 'cis_eks'].includes(benchmark.id); + const groupByField = isKspmBenchmark + ? FINDINGS_GROUPING_OPTIONS.ORCHESTRATOR_CLUSTER_NAME + : FINDINGS_GROUPING_OPTIONS.CLOUD_ACCOUNT_NAME; + + return ( + { + navToFindings({ 'rule.benchmark.id': benchmark.id }, [groupByField]); + }} + > + {i18n.translate('xpack.csp.benchmarks.benchmarksTable.accountsCountTitle', { + defaultMessage: '{benchmarkEvaluation} {resourceCountLabel}', + values: { benchmarkEvaluation, resourceCountLabel }, + })} + + ); }, }, { @@ -215,6 +213,7 @@ const BENCHMARKS_TABLE_COLUMNS: Array> = [ return ( ); + return ( { + const { getBenchmarkDynamicValues } = useBenchmarkDynamicValues(); + const navToFindings = useNavigateFindings(); + const pagination: Pagination = { pageIndex: Math.max(pageIndex - 1, 0), pageSize, @@ -261,7 +263,7 @@ export const BenchmarksTable = ({ [item.id, item.version].join('/')} pagination={pagination} onChange={onChange} diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_counters.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_counters.tsx index 9c311406fe172..ec8d4a653c222 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_counters.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_counters.tsx @@ -18,19 +18,13 @@ import { i18n } from '@kbn/i18n'; import { useParams } from 'react-router-dom'; import { Chart, Partition, PartitionLayout, Settings } from '@elastic/charts'; import { FormattedMessage } from '@kbn/i18n-react'; +import { useBenchmarkDynamicValues } from '../../common/hooks/use_benchmark_dynamic_values'; import { getPostureScorePercentage } from '../compliance_dashboard/compliance_charts/compliance_score_chart'; import { RULE_COUNTERS_TEST_SUBJ } from './test_subjects'; import noDataIllustration from '../../assets/illustrations/no_data_illustration.svg'; -import { BenchmarksCisId } from '../../../common/types/benchmarks/v2'; -import { useCspIntegrationLink } from '../../common/navigation/use_csp_integration_link'; import { useNavigateFindings } from '../../common/hooks/use_navigate_findings'; import { cloudPosturePages } from '../../common/navigation/constants'; -import { - CSPM_POLICY_TEMPLATE, - KSPM_POLICY_TEMPLATE, - RULE_FAILED, - RULE_PASSED, -} from '../../../common/constants'; +import { RULE_FAILED, RULE_PASSED } from '../../../common/constants'; import { statusColors } from '../../common/constants'; import { useCspBenchmarkIntegrationsV2 } from '../benchmarks/use_csp_benchmark_integrations'; import { CspCounterCard } from '../../components/csp_counter_card'; @@ -98,11 +92,10 @@ export const RulesCounters = ({ setEnabledDisabledItemsFilter: (filterState: string) => void; }) => { const { http } = useKibana().services; + const { getBenchmarkDynamicValues } = useBenchmarkDynamicValues(); const rulesPageParams = useParams<{ benchmarkId: string; benchmarkVersion: string }>(); const getBenchmarks = useCspBenchmarkIntegrationsV2(); const navToFindings = useNavigateFindings(); - const cspmIntegrationLink = useCspIntegrationLink(CSPM_POLICY_TEMPLATE) || ''; - const kspmIntegrationLink = useCspIntegrationLink(KSPM_POLICY_TEMPLATE) || ''; const benchmarkRulesStats = getBenchmarks.data?.items.find( (benchmark) => @@ -114,52 +107,7 @@ export const RulesCounters = ({ return <>; } - const benchmarkDynamicValues: Record< - BenchmarksCisId, - { - integrationType: string; - integrationName: string; - resourceName: string; - integrationLink: string; - learnMoreLink: string; - } - > = { - cis_aws: { - integrationType: 'CSPM', - integrationName: 'AWS', - resourceName: 'Accounts', - integrationLink: cspmIntegrationLink, - learnMoreLink: 'https://ela.st/cspm-get-started', - }, - cis_gcp: { - integrationType: 'CSPM', - integrationName: 'GCP', - resourceName: 'Projects', - integrationLink: cspmIntegrationLink, - learnMoreLink: 'https://ela.st/cspm-get-started', - }, - cis_azure: { - integrationType: 'CSPM', - integrationName: 'Azure', - resourceName: 'Subscriptions', - integrationLink: cspmIntegrationLink, - learnMoreLink: 'https://ela.st/cspm-get-started', - }, - cis_k8s: { - integrationType: 'KSPM', - integrationName: 'Kubernetes', - resourceName: 'Clusters', - integrationLink: kspmIntegrationLink, - learnMoreLink: 'https://ela.st/kspm-get-started', - }, - cis_eks: { - integrationType: 'KSPM', - integrationName: 'EKS', - resourceName: 'Clusters', - integrationLink: kspmIntegrationLink, - learnMoreLink: 'https://ela.st/kspm-get-started', - }, - }; + const benchmarkValues = getBenchmarkDynamicValues(benchmarkRulesStats.id); if (benchmarkRulesStats.score.totalFindings === 0) { return ( @@ -182,10 +130,8 @@ export const RulesCounters = ({ id="xpack.csp.rulesPage.rulesCounterEmptyState.emptyStateTitle" defaultMessage="Add {integrationResourceName} to get started" values={{ - integrationResourceName: `${ - benchmarkDynamicValues[benchmarkRulesStats.id].integrationName - } - ${benchmarkDynamicValues[benchmarkRulesStats.id].resourceName}`, + integrationResourceName: `${benchmarkValues.integrationName} + ${benchmarkValues.resourceName}`, }} /> @@ -196,32 +142,23 @@ export const RulesCounters = ({ id="xpack.csp.rulesPage.rulesCounterEmptyState.emptyStateDescription" defaultMessage="Add your {resourceName} in {integrationType} to begin detecing misconfigurations" values={{ - resourceName: - benchmarkDynamicValues[benchmarkRulesStats.id].resourceName.toLowerCase(), - integrationType: benchmarkDynamicValues[benchmarkRulesStats.id].integrationType, + resourceName: benchmarkValues.resourceName.toLowerCase(), + integrationType: benchmarkValues.integrationType, }} />

} actions={[ - + , - + {i18n.translate('xpack.csp.rulesCounters.accountsEvaluatedButton', { defaultMessage: 'Add more {resourceName}', values: { - resourceName: - benchmarkDynamicValues[benchmarkRulesStats.id].resourceName.toLowerCase(), + resourceName: benchmarkValues.resourceName.toLowerCase(), }, })} diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index de90e90f58e0a..5c830f3ff1702 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -42811,4 +42811,4 @@ "xpack.serverlessObservability.nav.projectSettings": "Paramètres de projet", "xpack.serverlessObservability.nav.visualizations": "Visualisations" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index deb0b7bceb6d1..6f31a8d2dd91a 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -42803,4 +42803,4 @@ "xpack.serverlessObservability.nav.projectSettings": "プロジェクト設定", "xpack.serverlessObservability.nav.visualizations": "ビジュアライゼーション" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 2393a8e4391db..85e80203cc509 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -42783,4 +42783,4 @@ "xpack.serverlessObservability.nav.projectSettings": "项目设置", "xpack.serverlessObservability.nav.visualizations": "可视化" } -} \ No newline at end of file +}