Skip to content

Commit

Permalink
[CloudSecurity] Converting Findings DistributionBar FTR into integrat…
Browse files Browse the repository at this point in the history
…ion test (elastic#186938)

## Summary

It closes elastic#176700

This PR converts the DistributionBar FTR test on the Findings page into
an integration test using MSW. It also closes #176700as it was once
triggering an error in the past

Also, it adds the following changes:
- Added a `generateMultipleCspFindings` helper to help with the writing
of future tests and generating batch data.
- Removed DistributionBar FTR test
- Removed the extra layer of sub-components on the DistributionBar
component to be simpler and added an aria-label on the distribution bar
buttons.

## Screenshots


![image](https://github.com/elastic/kibana/assets/19270322/ee4abc0e-1f60-46d0-afe7-48bce93bf24a)



![image](https://github.com/elastic/kibana/assets/19270322/bf443121-eb14-4ae5-b9aa-dea662410da4)
  • Loading branch information
opauloh authored Jul 3, 2024
1 parent 5e353a3 commit 7ae1f7a
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export const useCloudPostureDataTable = ({
return {
setUrlQuery,
sort: urlQuery.sort,
filters: urlQuery.filters,
filters: urlQuery.filters || [],
query: baseEsQuery.query
? baseEsQuery.query
: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import * as statusHandlers from '../../../server/routes/status/status.handlers.m
import {
bsearchFindingsHandler,
generateCspFinding,
generateMultipleCspFindings,
rulesGetStatesHandler,
} from './configurations.handlers.mock';

Expand Down Expand Up @@ -247,4 +248,109 @@ describe('<Findings />', () => {
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);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
EuiBadge,
EuiSpacer,
EuiFlexGroup,
EuiFlexItem,
useEuiTheme,
EuiTextColor,
} from '@elastic/eui';
Expand All @@ -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,
Expand Down Expand Up @@ -60,42 +67,21 @@ export const FindingsDistributionBar = (props: Props) => (
<DistributionBar {...props} />
</div>
);

const Counters = (props: Props) => (
<EuiFlexGroup justifyContent="flexEnd">
<EuiFlexItem>
<EuiFlexGroup justifyContent="flexEnd">
<PassedFailedCounters {...props} />
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
);

const PassedFailedCounters = ({ passed, failed }: Pick<Props, 'passed' | 'failed'>) => {
const Counters = ({ passed, failed }: Pick<Props, 'passed' | 'failed'>) => {
const { euiTheme } = useEuiTheme();

return (
<div
<EuiFlexGroup
justifyContent="flexEnd"
css={css`
display: grid;
grid-template-columns: auto auto;
grid-column-gap: ${euiTheme.size.m};
gap: ${euiTheme.size.m};
`}
>
<Counter
label={i18n.translate('xpack.csp.findings.distributionBar.totalPassedLabel', {
defaultMessage: 'Passed Findings',
})}
color={statusColors.passed}
value={passed}
/>
<Counter
label={i18n.translate('xpack.csp.findings.distributionBar.totalFailedLabel', {
defaultMessage: 'Failed Findings',
})}
color={statusColors.failed}
value={failed}
/>
</div>
<EuiHealth color={statusColors.passed}>{I18N_PASSED_FINDINGS}</EuiHealth>
<EuiBadge>{getAbbreviatedNumber(passed)}</EuiBadge>
<EuiHealth color={statusColors.failed}>{I18N_FAILED_FINDINGS}</EuiHealth>
<EuiBadge>{getAbbreviatedNumber(failed)}</EuiBadge>
</EuiFlexGroup>
);
};

Expand All @@ -121,6 +107,7 @@ const DistributionBar: React.FC<Omit<Props, 'pageEnd' | 'pageStart'>> = ({
distributionOnClick(RULE_PASSED);
}}
data-test-subj="distribution_bar_passed"
aria-label={`${I18N_PASSED_FINDINGS}: ${passed}`}
/>
<DistributionBarPart
value={failed}
Expand All @@ -129,6 +116,7 @@ const DistributionBar: React.FC<Omit<Props, 'pageEnd' | 'pageStart'>> = ({
distributionOnClick(RULE_FAILED);
}}
data-test-subj="distribution_bar_failed"
aria-label={`${I18N_FAILED_FINDINGS}: ${failed}`}
/>
</EuiFlexGroup>
);
Expand All @@ -144,25 +132,18 @@ const DistributionBarPart = ({
color: string;
distributionOnClick: () => void;
['data-test-subj']: string;
['aria-label']: string;
}) => (
<button
data-test-subj={rest['data-test-subj']}
aria-label={rest['aria-label']}
onClick={distributionOnClick}
css={css`
flex: ${value};
background: ${color};
height: 100%;
`}
css={{
background: color,
height: '100%',
}}
style={{
flex: value,
}}
/>
);

const Counter = ({ label, value, color }: { label: string; value: number; color: string }) => (
<EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexItem grow={1}>
<EuiHealth color={color}>{label}</EuiHealth>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiBadge>{getAbbreviatedNumber(value)}</EuiBadge>
</EuiFlexItem>
</EuiFlexGroup>
);
14 changes: 0 additions & 14 deletions x-pack/test/cloud_security_posture_functional/pages/findings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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';
Expand Down

0 comments on commit 7ae1f7a

Please sign in to comment.