Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Cloud Security] [Posture Dashboard] Update links to the findings page with groupBy option #176463

Merged
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
11 changes: 10 additions & 1 deletion x-pack/plugins/cloud_security_posture/common/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,15 @@ const CLOUD_PROVIDER_NAMES = {
GCP: 'Google Cloud Platform',
};

export const CLOUD_PROVIDERS = {
AWS: 'aws',
AZURE: 'azure',
GCP: 'gcp',
};

/**
* Returns the cloud provider name or benchmark applicable name for the given benchmark id
*/
export const getBenchmarkApplicableTo = (benchmarkId: BenchmarksCisId) => {
switch (benchmarkId) {
case 'cis_k8s':
Expand All @@ -205,7 +214,7 @@ export const getBenchmarkApplicableTo = (benchmarkId: BenchmarksCisId) => {
case 'cis_aws':
return CLOUD_PROVIDER_NAMES.AWS;
case 'cis_eks':
return 'Amazon Elastic Kubernetes Service';
return 'Amazon Elastic Kubernetes Service (EKS)';
case 'cis_gcp':
return CLOUD_PROVIDER_NAMES.GCP;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,10 @@ export const DETECTION_ENGINE_RULES_KEY = 'detection_engine_rules';
export const DETECTION_ENGINE_ALERTS_KEY = 'detection_engine_alerts';

export const DEFAULT_GROUPING_TABLE_HEIGHT = 512;

export const FINDINGS_GROUPING_OPTIONS = {
RESOURCE_NAME: 'resource.name',
RULE_NAME: 'rule.name',
CLOUD_ACCOUNT_NAME: 'cloud.account.name',
ORCHESTRATOR_CLUSTER_NAME: 'orchestrator.cluster.name',
};
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const useNavigate = (pathname: string, dataViewId = SECURITY_DEFAULT_DATA_VIEW_I
const { services } = useKibana();

return useCallback(
(filterParams: NavFilter = {}) => {
(filterParams: NavFilter = {}, groupBy?: string[]) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

might be to read and use if this function will receive an object, not urgent since it will require some refactoring

const filters = Object.entries(filterParams).map(([key, filterValue]) =>
createFilter(key, filterValue, dataViewId)
);
Expand All @@ -68,10 +68,11 @@ const useNavigate = (pathname: string, dataViewId = SECURITY_DEFAULT_DATA_VIEW_I
// Set query language from user's preference
query: services.data.query.queryString.getDefaultQuery(),
filters,
...(groupBy && { groupBy }),
}),
});
},
[pathname, history, services.data.query.queryString, dataViewId]
[history, pathname, services.data.query.queryString, dataViewId]
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* 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 React from 'react';
import { render, fireEvent } from '@testing-library/react';
import { AccountsEvaluatedWidget } from './accounts_evaluated_widget';
import { BenchmarkData } from '../../common/types_old';
import { TestProvider } from '../test/test_provider';

const mockNavToFindings = jest.fn();
jest.mock('../common/hooks/use_navigate_findings', () => ({
useNavigateFindings: () => mockNavToFindings,
}));

describe('AccountsEvaluatedWidget', () => {
const benchmarkAssets = [
{ meta: { benchmarkId: 'cis_aws', assetCount: 10 } },
{ meta: { benchmarkId: 'cis_k8s', assetCount: 20 } },
] as BenchmarkData[];

it('renders the component with benchmark data correctly', () => {
const { getByText } = render(
<TestProvider>
<AccountsEvaluatedWidget benchmarkAssets={benchmarkAssets} benchmarkAbbreviateAbove={999} />
</TestProvider>
);

expect(getByText('10')).toBeInTheDocument();
expect(getByText('20')).toBeInTheDocument();
});

it('calls navToFindingsByCloudProvider when a benchmark with provider is clicked', () => {
const { getByText } = render(
<TestProvider>
<AccountsEvaluatedWidget benchmarkAssets={benchmarkAssets} benchmarkAbbreviateAbove={999} />
</TestProvider>
);

fireEvent.click(getByText('10'));

expect(mockNavToFindings).toHaveBeenCalledWith(
{
'cloud.provider': 'aws',
},
['cloud.account.name']
);
});

it('calls navToFindingsByCisBenchmark when a benchmark with benchmarkId is clicked', () => {
const { getByText } = render(
<TestProvider>
<AccountsEvaluatedWidget benchmarkAssets={benchmarkAssets} benchmarkAbbreviateAbove={999} />
</TestProvider>
);

fireEvent.click(getByText('20'));

expect(mockNavToFindings).toHaveBeenCalledWith(
{
'rule.benchmark.id': 'cis_k8s',
},
['orchestrator.cluster.name']
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,40 @@
import React from 'react';
import { EuiFlexGroup, EuiFlexItem, useEuiTheme } from '@elastic/eui';
import { css } from '@emotion/react';
import { CLOUD_PROVIDERS, getBenchmarkApplicableTo } from '../../common/utils/helpers';
import { CIS_AWS, CIS_GCP, CIS_AZURE, CIS_K8S, CIS_EKS } from '../../common/constants';
import { CISBenchmarkIcon } from './cis_benchmark_icon';
import { CompactFormattedNumber } from './compact_formatted_number';
import { useNavigateFindings } from '../common/hooks/use_navigate_findings';
import { BenchmarkData } from '../../common/types_old';
import { FINDINGS_GROUPING_OPTIONS } from '../common/constants';

// order in array will determine order of appearance in the dashboard
const benchmarks = [
{
type: CIS_AWS,
name: 'Amazon Web Services (AWS)',
provider: 'aws',
name: getBenchmarkApplicableTo(CIS_AWS),
provider: CLOUD_PROVIDERS.AWS,
},
{
type: CIS_GCP,
name: 'Google Cloud Platform (GCP)',
provider: 'gcp',
name: getBenchmarkApplicableTo(CIS_GCP),
provider: CLOUD_PROVIDERS.GCP,
},
{
type: CIS_AZURE,
name: 'Azure',
provider: 'azure',
name: getBenchmarkApplicableTo(CIS_AZURE),
provider: CLOUD_PROVIDERS.AZURE,
},
{
type: CIS_K8S,
name: 'Kubernetes',
benchmarkId: 'cis_k8s',
name: getBenchmarkApplicableTo(CIS_K8S),
benchmarkId: CIS_K8S,
},
{
type: CIS_EKS,
name: 'Amazon Elastic Kubernetes Service (EKS)',
benchmarkId: 'cis_eks',
name: getBenchmarkApplicableTo(CIS_EKS),
benchmarkId: CIS_EKS,
Comment on lines 21 to +43
Copy link
Contributor

Choose a reason for hiding this comment

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

i see that its not new, but can we change it so all of those uses benchmarkId to keep it consistent? not urgent

},
];

Expand All @@ -59,11 +61,13 @@ export const AccountsEvaluatedWidget = ({
const navToFindings = useNavigateFindings();

const navToFindingsByCloudProvider = (provider: string) => {
navToFindings({ 'cloud.provider': provider });
navToFindings({ 'cloud.provider': provider }, [FINDINGS_GROUPING_OPTIONS.CLOUD_ACCOUNT_NAME]);
};

const navToFindingsByCisBenchmark = (cisBenchmark: string) => {
navToFindings({ 'rule.benchmark.id': cisBenchmark });
navToFindings({ 'rule.benchmark.id': cisBenchmark }, [
FINDINGS_GROUPING_OPTIONS.ORCHESTRATOR_CLUSTER_NAME,
]);
};

const benchmarkElements = benchmarks.map((benchmark) => {
Expand All @@ -75,10 +79,10 @@ export const AccountsEvaluatedWidget = ({
key={benchmark.type}
onClick={() => {
if (benchmark.provider) {
navToFindingsByCloudProvider(benchmark.provider);
return navToFindingsByCloudProvider(benchmark.provider);
}
if (benchmark.benchmarkId) {
navToFindingsByCisBenchmark(benchmark.benchmarkId);
return navToFindingsByCisBenchmark(benchmark.benchmarkId);
Comment on lines +82 to +85
Copy link
Contributor

Choose a reason for hiding this comment

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

why do we need to return here? no one takes in the value and the functions themselves do not return anything. i think this is redundant.

}
}}
css={css`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,32 @@ import {
import { FormattedMessage } from '@kbn/i18n-react';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { FINDINGS_GROUPING_OPTIONS } from '../../../common/constants';
import { getBenchmarkIdQuery } from './benchmarks_section';
import { BenchmarkData } from '../../../../common/types_old';
import { useNavigateFindings } from '../../../common/hooks/use_navigate_findings';
import { CISBenchmarkIcon } from '../../../components/cis_benchmark_icon';
import cisLogoIcon from '../../../assets/icons/cis_logo.svg';

interface BenchmarkInfo {
name: string;
assetType: string;
handleClick: () => void;
}

export const BenchmarkDetailsBox = ({ benchmark }: { benchmark: BenchmarkData }) => {
const navToFindings = useNavigateFindings();

const handleBenchmarkClick = () => {
return navToFindings(getBenchmarkIdQuery(benchmark));
};
const handleClickCloudProvider = () =>
navToFindings(getBenchmarkIdQuery(benchmark), [FINDINGS_GROUPING_OPTIONS.CLOUD_ACCOUNT_NAME]);

const handleClickCluster = () =>
navToFindings(getBenchmarkIdQuery(benchmark), [
FINDINGS_GROUPING_OPTIONS.ORCHESTRATOR_CLUSTER_NAME,
]);

const getBenchmarkInfo = (
benchmarkId: string,
cloudAssetCount: number
): { name: string; assetType: string } => {
const benchmarks: Record<string, { name: string; assetType: string }> = {
const getBenchmarkInfo = (benchmarkId: string, cloudAssetCount: number): BenchmarkInfo => {
const benchmarks: Record<string, BenchmarkInfo> = {
cis_gcp: {
name: i18n.translate(
'xpack.csp.dashboard.benchmarkSection.benchmarkName.cisGcpBenchmarkName',
Expand All @@ -48,6 +57,7 @@ export const BenchmarkDetailsBox = ({ benchmark }: { benchmark: BenchmarkData })
values: { count: cloudAssetCount },
}
),
handleClick: handleClickCloudProvider,
},
cis_aws: {
name: i18n.translate(
Expand All @@ -63,6 +73,7 @@ export const BenchmarkDetailsBox = ({ benchmark }: { benchmark: BenchmarkData })
values: { count: cloudAssetCount },
}
),
handleClick: handleClickCloudProvider,
},
cis_azure: {
name: i18n.translate(
Expand All @@ -78,6 +89,7 @@ export const BenchmarkDetailsBox = ({ benchmark }: { benchmark: BenchmarkData })
values: { count: cloudAssetCount },
}
),
handleClick: handleClickCloudProvider,
},
cis_k8s: {
name: i18n.translate(
Expand All @@ -93,6 +105,7 @@ export const BenchmarkDetailsBox = ({ benchmark }: { benchmark: BenchmarkData })
values: { count: cloudAssetCount },
}
),
handleClick: handleClickCluster,
},
cis_eks: {
name: i18n.translate(
Expand All @@ -108,6 +121,7 @@ export const BenchmarkDetailsBox = ({ benchmark }: { benchmark: BenchmarkData })
values: { count: cloudAssetCount },
}
),
handleClick: handleClickCluster,
},
};
return benchmarks[benchmarkId];
Expand Down Expand Up @@ -149,14 +163,14 @@ export const BenchmarkDetailsBox = ({ benchmark }: { benchmark: BenchmarkData })
</EuiText>
}
>
<EuiLink onClick={handleBenchmarkClick} color="text">
<EuiLink onClick={benchmarkInfo.handleClick} color="text">
<EuiTitle css={{ fontSize: 20 }}>
<h5>{benchmarkInfo.name}</h5>
</EuiTitle>
</EuiLink>
</EuiToolTip>

<EuiLink onClick={handleBenchmarkClick} color="text">
<EuiLink onClick={benchmarkInfo.handleClick} color="text">
<EuiText size="xs">{benchmarkInfo.assetType}</EuiText>
</EuiLink>
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import { i18n } from '@kbn/i18n';
import { GroupOption } from '@kbn/securitysolution-grouping';
import { FINDINGS_GROUPING_OPTIONS } from '../../../common/constants';
import { FindingsBaseURLQuery } from '../../../common/types';
import { CloudSecurityDefaultColumn } from '../../../components/cloud_security_data_table';

Expand All @@ -16,13 +17,6 @@ export const FINDINGS_UNIT = (totalCount: number) =>
defaultMessage: `{totalCount, plural, =1 {finding} other {findings}}`,
});

export const GROUPING_OPTIONS = {
RESOURCE_NAME: 'resource.name',
RULE_NAME: 'rule.name',
CLOUD_ACCOUNT_NAME: 'cloud.account.name',
ORCHESTRATOR_CLUSTER_NAME: 'orchestrator.cluster.name',
};

export const NULL_GROUPING_UNIT = i18n.translate('xpack.csp.findings.grouping.nullGroupUnit', {
defaultMessage: 'findings',
});
Expand Down Expand Up @@ -51,25 +45,25 @@ export const defaultGroupingOptions: GroupOption[] = [
label: i18n.translate('xpack.csp.findings.latestFindings.groupByResource', {
defaultMessage: 'Resource',
}),
key: GROUPING_OPTIONS.RESOURCE_NAME,
key: FINDINGS_GROUPING_OPTIONS.RESOURCE_NAME,
},
{
label: i18n.translate('xpack.csp.findings.latestFindings.groupByRuleName', {
defaultMessage: 'Rule name',
}),
key: GROUPING_OPTIONS.RULE_NAME,
key: FINDINGS_GROUPING_OPTIONS.RULE_NAME,
},
{
label: i18n.translate('xpack.csp.findings.latestFindings.groupByCloudAccount', {
defaultMessage: 'Cloud account',
}),
key: GROUPING_OPTIONS.CLOUD_ACCOUNT_NAME,
key: FINDINGS_GROUPING_OPTIONS.CLOUD_ACCOUNT_NAME,
},
{
label: i18n.translate('xpack.csp.findings.latestFindings.groupByKubernetesCluster', {
defaultMessage: 'Kubernetes cluster',
}),
key: GROUPING_OPTIONS.ORCHESTRATOR_CLUSTER_NAME,
key: FINDINGS_GROUPING_OPTIONS.ORCHESTRATOR_CLUSTER_NAME,
},
];

Expand Down
Loading