diff --git a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/use_cloud_posture_data_table.ts b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/use_cloud_posture_data_table.ts
index 03517383ecc3f..ae8ddb48488c1 100644
--- a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/use_cloud_posture_data_table.ts
+++ b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/use_cloud_posture_data_table.ts
@@ -137,7 +137,7 @@ export const useCloudPostureDataTable = ({
return {
setUrlQuery,
sort: urlQuery.sort,
- filters: urlQuery.filters,
+ filters: urlQuery.filters || [],
query: baseEsQuery.query
? baseEsQuery.query
: {
diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.handlers.mock.ts b/x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.handlers.mock.ts
index 38e4edf46f77a..c5fb197583dd9 100644
--- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.handlers.mock.ts
+++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.handlers.mock.ts
@@ -11,6 +11,15 @@ import { isArray } from 'lodash';
import { http, HttpResponse } from 'msw';
import { v4 as uuidV4 } from 'uuid';
+export const generateMultipleCspFindings = (
+ option: { count: number; failedCount?: number } = { count: 1, failedCount: 0 }
+) => {
+ const failedCount = option.failedCount || 0;
+ return Array.from({ length: option?.count }, (_, i) => {
+ return generateCspFinding(i.toString(), i < failedCount ? 'failed' : 'passed');
+ });
+};
+
export const generateCspFinding = (
id: string,
evaluation: 'failed' | 'passed' = 'passed'
@@ -211,25 +220,36 @@ export const bsearchFindingsHandler = (findings: CspFinding[]) =>
filter[0]?.bool?.should?.[0]?.term?.['rule.section']?.value !== undefined;
if (hasRuleSectionQuerySearchTerm) {
- const filteredFindingJson = findings.filter((finding) => {
+ const filteredFindings = findings.filter((finding) => {
const termValue = (filter[0].bool?.should as estypes.QueryDslQueryContainer[])?.[0]?.term?.[
'rule.section'
]?.value;
return finding.rule.section === termValue;
});
- return HttpResponse.json(getFindingsBsearchResponse(filteredFindingJson));
+ return HttpResponse.json(getFindingsBsearchResponse(filteredFindings));
}
const hasRuleSectionFilter =
isArray(filter) && filter?.[0]?.match_phrase?.['rule.section'] !== undefined;
if (hasRuleSectionFilter) {
- const filteredFindingJson = findings.filter((finding) => {
+ const filteredFindings = findings.filter((finding) => {
return finding.rule.section === filter?.[0]?.match_phrase?.['rule.section'];
});
- return HttpResponse.json(getFindingsBsearchResponse(filteredFindingJson));
+ return HttpResponse.json(getFindingsBsearchResponse(filteredFindings));
+ }
+
+ const hasResultEvaluationFilter =
+ isArray(filter) && filter?.[0]?.match_phrase?.['result.evaluation'] !== undefined;
+
+ if (hasResultEvaluationFilter) {
+ const filteredFindings = findings.filter((finding) => {
+ return finding.result.evaluation === filter?.[0]?.match_phrase?.['result.evaluation'];
+ });
+
+ return HttpResponse.json(getFindingsBsearchResponse(filteredFindings));
}
return HttpResponse.json(getFindingsBsearchResponse(findings));
diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.test.tsx b/x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.test.tsx
index 324eddd1fd8fc..9ff1b40d49c70 100644
--- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.test.tsx
+++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.test.tsx
@@ -22,6 +22,7 @@ import * as statusHandlers from '../../../server/routes/status/status.handlers.m
import {
bsearchFindingsHandler,
generateCspFinding,
+ generateMultipleCspFindings,
rulesGetStatesHandler,
} from './configurations.handlers.mock';
@@ -247,4 +248,109 @@ describe('', () => {
expect(screen.getByText(finding2.resource.name)).toBeInTheDocument();
});
});
+
+ describe('DistributionBar', () => {
+ it('renders the distribution bar', async () => {
+ server.use(statusHandlers.indexedHandler);
+ server.use(
+ bsearchFindingsHandler(
+ generateMultipleCspFindings({
+ count: 10,
+ failedCount: 3,
+ })
+ )
+ );
+
+ renderFindingsPage();
+
+ // Loading while checking the status API
+ expect(screen.getByText(/loading/i)).toBeInTheDocument();
+
+ await waitFor(() => expect(screen.getByText(/10 findings/i)).toBeInTheDocument());
+
+ screen.getByRole('button', {
+ name: /passed findings: 7/i,
+ });
+ screen.getByRole('button', {
+ name: /failed findings: 3/i,
+ });
+
+ // Assert that the distribution bar has the correct percentages rendered
+ expect(screen.getByTestId('distribution_bar_passed')).toHaveStyle('flex: 7');
+ expect(screen.getByTestId('distribution_bar_failed')).toHaveStyle('flex: 3');
+ });
+
+ it('filters by passed findings when clicking on the passed findings button', async () => {
+ server.use(statusHandlers.indexedHandler);
+ server.use(
+ bsearchFindingsHandler(
+ generateMultipleCspFindings({
+ count: 2,
+ failedCount: 1,
+ })
+ )
+ );
+
+ renderFindingsPage();
+
+ // Loading while checking the status API
+ expect(screen.getByText(/loading/i)).toBeInTheDocument();
+
+ await waitFor(() => expect(screen.getByText(/2 findings/i)).toBeInTheDocument());
+
+ const passedFindingsButton = screen.getByRole('button', {
+ name: /passed findings: 1/i,
+ });
+ userEvent.click(passedFindingsButton);
+
+ await waitFor(() => expect(screen.getByText(/1 findings/i)).toBeInTheDocument());
+
+ screen.getByRole('button', {
+ name: /passed findings: 1/i,
+ });
+ screen.getByRole('button', {
+ name: /failed findings: 0/i,
+ });
+
+ // Assert that the distribution bar has the correct percentages rendered
+ expect(screen.getByTestId('distribution_bar_passed')).toHaveStyle('flex: 1');
+ expect(screen.getByTestId('distribution_bar_failed')).toHaveStyle('flex: 0');
+ }, 10000);
+ it('filters by failed findings when clicking on the failed findings button', async () => {
+ server.use(statusHandlers.indexedHandler);
+ server.use(
+ bsearchFindingsHandler(
+ generateMultipleCspFindings({
+ count: 2,
+ failedCount: 1,
+ })
+ )
+ );
+
+ renderFindingsPage();
+
+ // Loading while checking the status API
+ expect(screen.getByText(/loading/i)).toBeInTheDocument();
+
+ await waitFor(() => expect(screen.getByText(/2 findings/i)).toBeInTheDocument());
+
+ const failedFindingsButton = screen.getByRole('button', {
+ name: /failed findings: 1/i,
+ });
+ userEvent.click(failedFindingsButton);
+
+ await waitFor(() => expect(screen.getByText(/1 findings/i)).toBeInTheDocument());
+
+ screen.getByRole('button', {
+ name: /passed findings: 0/i,
+ });
+ screen.getByRole('button', {
+ name: /failed findings: 1/i,
+ });
+
+ // Assert that the distribution bar has the correct percentages rendered
+ expect(screen.getByTestId('distribution_bar_passed')).toHaveStyle('flex: 0');
+ expect(screen.getByTestId('distribution_bar_failed')).toHaveStyle('flex: 1');
+ }, 10000);
+ });
});
diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/layout/findings_distribution_bar.tsx b/x-pack/plugins/cloud_security_posture/public/pages/configurations/layout/findings_distribution_bar.tsx
index 35f6fd008d4b9..56ca9687551d8 100644
--- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/layout/findings_distribution_bar.tsx
+++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/layout/findings_distribution_bar.tsx
@@ -11,7 +11,6 @@ import {
EuiBadge,
EuiSpacer,
EuiFlexGroup,
- EuiFlexItem,
useEuiTheme,
EuiTextColor,
} from '@elastic/eui';
@@ -28,6 +27,14 @@ interface Props {
distributionOnClick: (evaluation: Evaluation) => void;
}
+const I18N_PASSED_FINDINGS = i18n.translate('xpack.csp.findings.distributionBar.totalPassedLabel', {
+ defaultMessage: 'Passed Findings',
+});
+
+const I18N_FAILED_FINDINGS = i18n.translate('xpack.csp.findings.distributionBar.totalFailedLabel', {
+ defaultMessage: 'Failed Findings',
+});
+
export const CurrentPageOfTotal = ({
pageEnd,
pageStart,
@@ -60,42 +67,21 @@ export const FindingsDistributionBar = (props: Props) => (
);
-
-const Counters = (props: Props) => (
-
-
-
-
-
-
-
-);
-
-const PassedFailedCounters = ({ passed, failed }: Pick) => {
+const Counters = ({ passed, failed }: Pick) => {
const { euiTheme } = useEuiTheme();
+
return (
-
-
-
-
+ {I18N_PASSED_FINDINGS}
+ {getAbbreviatedNumber(passed)}
+ {I18N_FAILED_FINDINGS}
+ {getAbbreviatedNumber(failed)}
+
);
};
@@ -121,6 +107,7 @@ const DistributionBar: React.FC> = ({
distributionOnClick(RULE_PASSED);
}}
data-test-subj="distribution_bar_passed"
+ aria-label={`${I18N_PASSED_FINDINGS}: ${passed}`}
/>
> = ({
distributionOnClick(RULE_FAILED);
}}
data-test-subj="distribution_bar_failed"
+ aria-label={`${I18N_FAILED_FINDINGS}: ${failed}`}
/>
);
@@ -144,25 +132,18 @@ const DistributionBarPart = ({
color: string;
distributionOnClick: () => void;
['data-test-subj']: string;
+ ['aria-label']: string;
}) => (
);
-
-const Counter = ({ label, value, color }: { label: string; value: number; color: string }) => (
-
-
- {label}
-
-
- {getAbbreviatedNumber(value)}
-
-
-);
diff --git a/x-pack/test/cloud_security_posture_functional/pages/findings.ts b/x-pack/test/cloud_security_posture_functional/pages/findings.ts
index 76ea64f6e6195..16e63fac3491e 100644
--- a/x-pack/test/cloud_security_posture_functional/pages/findings.ts
+++ b/x-pack/test/cloud_security_posture_functional/pages/findings.ts
@@ -17,7 +17,6 @@ import type { FtrProviderContext } from '../ftr_provider_context';
// eslint-disable-next-line import/no-default-export
export default function ({ getPageObjects, getService }: FtrProviderContext) {
- const filterBar = getService('filterBar');
const testSubjects = getService('testSubjects');
const retry = getService('retry');
const supertest = getService('supertest');
@@ -189,19 +188,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
});
});
- describe('DistributionBar', () => {
- (['passed', 'failed'] as const).forEach((type) => {
- it(`filters by ${type} findings`, async () => {
- await distributionBar.filterBy(type);
-
- const items = data.filter(({ result }) => result.evaluation === type);
- expect(await latestFindingsTable.getFindingsCount(type)).to.eql(items.length);
-
- await filterBar.removeFilter('result.evaluation');
- });
- });
- });
-
describe('Findings - Fields selector', () => {
const CSP_FIELDS_SELECTOR_MODAL = 'cloudSecurityFieldsSelectorModal';
const CSP_FIELDS_SELECTOR_OPEN_BUTTON = 'cloudSecurityFieldsSelectorOpenButton';