Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions x-pack/plugins/cloud_security_posture/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ export const CLOUDBEAT_VULN_MGMT_GCP = 'cloudbeat/vuln_mgmt_gcp';
export const CLOUDBEAT_VULN_MGMT_AZURE = 'cloudbeat/vuln_mgmt_azure';
export const CIS_AWS = 'cis_aws';
export const CIS_GCP = 'cis_gcp';
export const CIS_K8S = 'cis_k8s';
export const CIS_EKS = 'cis_eks';
export const CIS_AZURE = 'cis_azure';
export const KSPM_POLICY_TEMPLATE = 'kspm';
export const CSPM_POLICY_TEMPLATE = 'cspm';
export const VULN_MGMT_POLICY_TEMPLATE = 'vuln_mgmt';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,43 @@
* 2.0.
*/
import React from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { CIS_AWS, CIS_GCP } from '../../common/constants';
import { EuiFlexGroup, EuiFlexItem, useEuiTheme } from '@elastic/eui';
import { css } from '@emotion/react';
import { CIS_AWS, CIS_GCP, CIS_AZURE, CIS_K8S, CIS_EKS } from '../../common/constants';
import { Cluster } from '../../common/types';
import { CISBenchmarkIcon } from './cis_benchmark_icon';
import { CompactFormattedNumber } from './compact_formatted_number';
import { useNavigateFindings } from '../common/hooks/use_navigate_findings';

// order in array will determine order of appearance in the dashboard
const benchmarks = [
{
type: CIS_AWS,
name: 'Amazon Web Services (AWS)',
provider: 'aws',
},
{
type: CIS_GCP,
name: 'Google Cloud Platform (GCP)',
provider: 'gcp',
},
{
type: CIS_AZURE,
name: 'Azure',
provider: 'azure',
},
{
type: CIS_K8S,
name: 'Kubernetes',
benchmarkId: 'cis_k8s',
},
{
type: CIS_EKS,
name: 'Amazon Elastic Kubernetes Service (EKS)',
benchmarkId: 'cis_eks',
},
];

export const AccountsEvaluatedWidget = ({
clusters,
benchmarkAbbreviateAbove = 999,
Expand All @@ -20,6 +50,8 @@ export const AccountsEvaluatedWidget = ({
/** numbers higher than the value of this field will be abbreviated using compact notation and have a tooltip displaying the full value */
benchmarkAbbreviateAbove?: number;
}) => {
const { euiTheme } = useEuiTheme();

const filterClustersById = (benchmarkId: string) => {
return clusters?.filter((obj) => obj?.meta.benchmark.id === benchmarkId) || [];
};
Expand All @@ -30,56 +62,52 @@ export const AccountsEvaluatedWidget = ({
navToFindings({ 'cloud.provider': provider });
};

const cisAwsClusterAmount = filterClustersById(CIS_AWS).length;
const cisGcpClusterAmount = filterClustersById(CIS_GCP).length;
const navToFindingsByCisBenchmark = (cisBenchmark: string) => {
navToFindings({ 'rule.benchmark.id': cisBenchmark });
};

const benchmarkElements = benchmarks.map((benchmark) => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

think it's a good candidate to wrap with useMemo()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while possible, it also means wrapping all the hooks in usecallbacks and for such small component all those memos are not worth it in my opinion

const clusterAmount = filterClustersById(benchmark.type).length;

return (
clusterAmount > 0 && (
<EuiFlexItem
key={benchmark.type}
onClick={() => {
if (benchmark.provider) {
navToFindingsByCloudProvider(benchmark.provider);
}
if (benchmark.benchmarkId) {
navToFindingsByCisBenchmark(benchmark.benchmarkId);
}
}}
css={css`
transition: ${euiTheme.animation.normal} ease-in;
border-bottom: ${euiTheme.border.thick};
border-color: transparent;

const cisAwsBenchmarkName = 'Amazon Web Services (AWS)';
const cisGcpBenchmarkName = 'Google Cloud Platform (GCP)';
:hover {
cursor: pointer;
border-color: ${euiTheme.colors.darkestShade};
Comment on lines +89 to +91
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice addition!

}
`}
>
<EuiFlexGroup gutterSize="xs" alignItems="center">
<EuiFlexItem>
<CISBenchmarkIcon type={benchmark.type} name={benchmark.name} size={'l'} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<CompactFormattedNumber
number={clusterAmount}
abbreviateAbove={benchmarkAbbreviateAbove}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
)
);
});

return (
<>
<EuiFlexGroup gutterSize="m">
{cisAwsClusterAmount > 0 && (
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="xs" alignItems="center">
<EuiFlexItem>
<CISBenchmarkIcon type={CIS_AWS} name={cisAwsBenchmarkName} size={'l'} />
</EuiFlexItem>
<EuiFlexItem
grow={false}
onClick={() => {
navToFindingsByCloudProvider('aws');
}}
>
<CompactFormattedNumber
number={cisAwsClusterAmount}
abbreviateAbove={benchmarkAbbreviateAbove}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
)}
{cisGcpClusterAmount > 0 && (
<EuiFlexItem>
<EuiFlexGroup gutterSize="xs" alignItems="center">
<EuiFlexItem>
<CISBenchmarkIcon type={CIS_GCP} name={cisGcpBenchmarkName} size={'l'} />
</EuiFlexItem>
<EuiFlexItem
grow={false}
onClick={() => {
navToFindingsByCloudProvider('gcp');
}}
>
<CompactFormattedNumber
number={cisGcpClusterAmount}
abbreviateAbove={benchmarkAbbreviateAbove}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
)}
</EuiFlexGroup>
</>
);
// Render the benchmark elements
return <EuiFlexGroup gutterSize="m">{benchmarkElements}</EuiFlexGroup>;
};
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ const getBenchmarkIdIconType = (props: Props): string => {
switch (props.type) {
case 'cis_eks':
return cisEksIcon;
case 'cis_azure':
return 'logoAzure';
case 'cis_aws':
return 'logoAWS';
case 'cis_gcp':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@
* 2.0.
*/

import React, { MouseEventHandler } from 'react';
import { css } from '@emotion/react';
import { EuiIcon, EuiPanel, EuiStat, useEuiTheme } from '@elastic/eui';
import React, { ReactNode } from 'react';
import { EuiPanel, EuiStat, useEuiTheme, EuiHorizontalRule } from '@elastic/eui';
import type { EuiStatProps } from '@elastic/eui';

export interface CspCounterCardProps {
id: string;
onClick?: MouseEventHandler<HTMLButtonElement>;
button?: ReactNode;
title: EuiStatProps['title'];
titleColor?: EuiStatProps['titleColor'];
description: EuiStatProps['description'];
Expand All @@ -22,25 +21,10 @@ export const CspCounterCard = (counter: CspCounterCardProps) => {
const { euiTheme } = useEuiTheme();

return (
<EuiPanel
hasBorder
onClick={counter.onClick}
paddingSize="m"
css={css`
position: relative;
display: flex;
align-items: center;

:hover .euiIcon {
color: ${euiTheme.colors.primary};
transition: ${euiTheme.animation.normal};
}
`}
data-test-subj={counter.id}
>
<EuiPanel hasBorder paddingSize="m" data-test-subj={counter.id}>
<EuiStat
css={{
height: '100%',
height: '60%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-around',
Expand All @@ -55,17 +39,8 @@ export const CspCounterCard = (counter: CspCounterCardProps) => {
descriptionElement="h6"
description={counter.description}
/>
{counter.onClick && (
<EuiIcon
type={'pivot'}
css={css`
color: ${euiTheme.colors.lightShade};
position: absolute;
top: ${euiTheme.size.s};
right: ${euiTheme.size.s};
`}
/>
)}
<EuiHorizontalRule margin="xs" />
{counter.button}
</EuiPanel>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,7 @@ describe('<CloudSummarySection />', () => {
renderCloudSummarySection();

expectIdsInDoc({
be: [
DASHBOARD_COUNTER_CARDS.CLUSTERS_EVALUATED,
DASHBOARD_COUNTER_CARDS.RESOURCES_EVALUATED,
DASHBOARD_COUNTER_CARDS.FAILING_FINDINGS,
],
be: [DASHBOARD_COUNTER_CARDS.CLUSTERS_EVALUATED, DASHBOARD_COUNTER_CARDS.RESOURCES_EVALUATED],
});
});

Expand All @@ -46,7 +42,6 @@ describe('<CloudSummarySection />', () => {
expect(screen.getByTestId(DASHBOARD_COUNTER_CARDS.RESOURCES_EVALUATED)).toHaveTextContent(
'162'
);
expect(screen.getByTestId(DASHBOARD_COUNTER_CARDS.FAILING_FINDINGS)).toHaveTextContent('17');
});

it('renders counters value in compact abbreviation if its above one million', () => {
Expand All @@ -55,12 +50,5 @@ describe('<CloudSummarySection />', () => {
expect(screen.getByTestId(DASHBOARD_COUNTER_CARDS.RESOURCES_EVALUATED)).toHaveTextContent(
'999,999'
);
expect(screen.getByTestId(DASHBOARD_COUNTER_CARDS.FAILING_FINDINGS)).toHaveTextContent('1M');
});

it('renders N/A as an empty state', () => {
renderCloudSummarySection({ stats: { totalFailed: undefined } });

expect(screen.getByTestId(DASHBOARD_COUNTER_CARDS.FAILING_FINDINGS)).toHaveTextContent('N/A');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
*/

import React, { useMemo } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiFlexItemProps } from '@elastic/eui';
import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiFlexItemProps } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { css } from '@emotion/react';
import { statusColors } from '../../../common/constants';
import { useCspIntegrationLink } from '../../../common/navigation/use_csp_integration_link';
import { DASHBOARD_COUNTER_CARDS, DASHBOARD_SUMMARY_CONTAINER } from '../test_subjects';
import { CspCounterCard, CspCounterCardProps } from '../../../components/csp_counter_card';
import { CompactFormattedNumber } from '../../../components/compact_formatted_number';
Expand Down Expand Up @@ -56,6 +56,8 @@ export const SummarySection = ({
}) => {
const navToFindings = useNavigateFindings();
const navToFindingsByResource = useNavigateFindingsByResource();
const cspmIntegrationLink = useCspIntegrationLink(CSPM_POLICY_TEMPLATE);
const kspmIntegrationLink = useCspIntegrationLink(KSPM_POLICY_TEMPLATE);

const handleEvalCounterClick = (evaluation: Evaluation) => {
navToFindings({ 'result.evaluation': evaluation, ...getPolicyTemplateQuery(dashboardType) });
Expand Down Expand Up @@ -87,12 +89,26 @@ export const SummarySection = ({
'xpack.csp.dashboard.summarySection.counterCard.accountsEvaluatedDescription',
{ defaultMessage: 'Accounts Evaluated' }
),
title:
dashboardType === KSPM_POLICY_TEMPLATE ? (
<CompactFormattedNumber number={complianceData.clusters.length} />
) : (
<AccountsEvaluatedWidget clusters={complianceData.clusters} />
),
title: <AccountsEvaluatedWidget clusters={complianceData.clusters} />,
button: (
<EuiButtonEmpty
iconType="listAdd"
target="_blank"
href={
dashboardType === KSPM_POLICY_TEMPLATE ? kspmIntegrationLink : cspmIntegrationLink
}
>
{dashboardType === KSPM_POLICY_TEMPLATE
? i18n.translate(
'xpack.csp.dashboard.summarySection.counterCard.clustersEvaluatedButtonTitle',
{ defaultMessage: 'Enroll more clusters' }
)
: i18n.translate(
'xpack.csp.dashboard.summarySection.counterCard.accountsEvaluatedButtonTitle',
{ defaultMessage: 'Enroll more accounts' }
)}
</EuiButtonEmpty>
Comment on lines +94 to +110
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels this could be better if splitting into two buttons

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah i agree but its pretty small so i prefer to just merge for now

),
},
{
id: DASHBOARD_COUNTER_CARDS.RESOURCES_EVALUATED,
Expand All @@ -101,32 +117,27 @@ export const SummarySection = ({
{ defaultMessage: 'Resources Evaluated' }
),
title: <CompactFormattedNumber number={complianceData.stats.resourcesEvaluated || 0} />,
onClick: () => {
navToFindingsByResource(getPolicyTemplateQuery(dashboardType));
},
},
{
id: DASHBOARD_COUNTER_CARDS.FAILING_FINDINGS,
description: i18n.translate(
'xpack.csp.dashboard.summarySection.counterCard.failingFindingsDescription',
{ defaultMessage: 'Failing Findings' }
button: (
<EuiButtonEmpty
iconType="search"
onClick={() => {
navToFindingsByResource(getPolicyTemplateQuery(dashboardType));
}}
>
{i18n.translate(
'xpack.csp.dashboard.summarySection.counterCard.resourcesEvaluatedButtonTitle',
{ defaultMessage: 'View all resources' }
)}
</EuiButtonEmpty>
),
title: <CompactFormattedNumber number={complianceData.stats.totalFailed} />,
titleColor: complianceData.stats.totalFailed > 0 ? statusColors.failed : 'text',
onClick: () => {
navToFindings({
'result.evaluation': RULE_FAILED,
...getPolicyTemplateQuery(dashboardType),
});
},
},
],
[
complianceData.clusters,
complianceData.stats.resourcesEvaluated,
complianceData.stats.totalFailed,
cspmIntegrationLink,
dashboardType,
navToFindings,
kspmIntegrationLink,
navToFindingsByResource,
]
);
Expand Down
1 change: 0 additions & 1 deletion x-pack/plugins/translations/translations/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -11377,7 +11377,6 @@
"xpack.csp.dashboard.summarySection.complianceByCisSectionPanelTitle": "Conformité par section CIS",
"xpack.csp.dashboard.summarySection.counterCard.accountsEvaluatedDescription": "Comptes évalués",
"xpack.csp.dashboard.summarySection.counterCard.clustersEvaluatedDescription": "Clusters évalués",
"xpack.csp.dashboard.summarySection.counterCard.failingFindingsDescription": "Résultats en échec",
"xpack.csp.dashboard.summarySection.counterCard.resourcesEvaluatedDescription": "Ressources évaluées",
"xpack.csp.dashboardTabs.cloudTab.tabTitle": "Cloud",
"xpack.csp.dashboardTabs.kubernetesTab.tabTitle": "Kubernetes",
Expand Down
1 change: 0 additions & 1 deletion x-pack/plugins/translations/translations/ja-JP.json
Original file line number Diff line number Diff line change
Expand Up @@ -11392,7 +11392,6 @@
"xpack.csp.dashboard.summarySection.complianceByCisSectionPanelTitle": "CISセクション別のコンプライアンス",
"xpack.csp.dashboard.summarySection.counterCard.accountsEvaluatedDescription": "評価されたアカウント",
"xpack.csp.dashboard.summarySection.counterCard.clustersEvaluatedDescription": "評価されたクラスター",
"xpack.csp.dashboard.summarySection.counterCard.failingFindingsDescription": "失敗した調査結果",
"xpack.csp.dashboard.summarySection.counterCard.resourcesEvaluatedDescription": "評価されたリソース",
"xpack.csp.dashboardTabs.cloudTab.tabTitle": "クラウド",
"xpack.csp.dashboardTabs.kubernetesTab.tabTitle": "Kubernetes",
Expand Down
Loading