Skip to content

Commit 0d6cf4b

Browse files
authored
feat: add PolicyReportsPage status and severity filter (#63)
Updates the PolicyReportsPage component to now render 1 big table showing all failed policies. It's now also possible to filter policies by status and severity --------- Signed-off-by: Jonas Beck <dev@jonasbeck.dk>
1 parent 5560993 commit 0d6cf4b

File tree

13 files changed

+198
-37
lines changed

13 files changed

+198
-37
lines changed

.changeset/crazy-dingos-add.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@kyverno/backstage-plugin-policy-reporter': minor
3+
---
4+
5+
Add Status and Severity filter to `PolicyReportsPage` component and updates the UI to now be 1 big table that by default show all failing policies

.changeset/true-seals-fall.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@kyverno/backstage-plugin-policy-reporter': patch
3+
---
4+
5+
Update the `PolicyReportsPage` component's exported name to match the documentation. It was previously set to `PolicyReporterPage` by mistake and has now been corrected to `PolicyReportsPage`.

packages/app/src/App.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { AppRouter, FlatRoutes } from '@backstage/core-app-api';
2020
import { CatalogGraphPage } from '@backstage/plugin-catalog-graph';
2121
import { RequirePermission } from '@backstage/plugin-permission-react';
2222
import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha';
23-
import { PolicyReporterPage } from '@kyverno/backstage-plugin-policy-reporter';
23+
import { PolicyReportsPage } from '@kyverno/backstage-plugin-policy-reporter';
2424

2525
const app = createApp({
2626
apis,
@@ -44,7 +44,7 @@ const routes = (
4444
>
4545
{entityPage}
4646
</Route>
47-
<Route path="/kyverno" element={<PolicyReporterPage />} />
47+
<Route path="/kyverno" element={<PolicyReportsPage />} />
4848
<Route path="/settings" element={<UserSettingsPage />} />
4949
<Route path="/catalog-graph" element={<CatalogGraphPage />} />
5050
</FlatRoutes>

plugins/policy-reporter/src/components/PolicyReportsPage/PolicyReportsPage.test.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,6 @@ describe('EntityKyvernoPolicyReportsContent component', () => {
5454

5555
// Assert
5656
expect(extension.getByText('Policy Reports')).toBeTruthy();
57-
expect(extension.getByText('Failing Policy Results')).toBeTruthy();
58-
expect(extension.getByText('Passing Policy Results')).toBeTruthy();
59-
expect(extension.getByText('Skipped Policy Results')).toBeTruthy();
57+
expect(extension.getByText('Policy Results')).toBeTruthy();
6058
});
6159
});

plugins/policy-reporter/src/components/PolicyReportsPage/PolicyReportsPage.tsx

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ import { useEnvironments } from '../../hooks/useEnvironments';
99
import { SelectEnvironment } from '../SelectEnvironment';
1010
import { Grid } from '@material-ui/core';
1111
import { PolicyReportsTable } from '../PolicyReportsTable';
12+
import { useState } from 'react';
13+
import {
14+
Severity,
15+
Status,
16+
} from '@kyverno/backstage-plugin-policy-reporter-common';
17+
import { SelectStatus } from '../SelectStatus';
18+
import { SelectSeverity } from '../SelectSeverity';
1219

1320
export interface PolicyReportsPageProps {
1421
title?: string;
@@ -28,6 +35,9 @@ export const PolicyReportsPage = ({
2835
currentEnvironment,
2936
} = useEnvironments();
3037

38+
const [status, setStatus] = useState<Status[]>(['fail']);
39+
const [severity, setSeverity] = useState<Severity[]>([]);
40+
3141
// Fetching environments
3242
if (environmentsLoading) return <Progress />;
3343

@@ -39,6 +49,11 @@ export const PolicyReportsPage = ({
3949
<Header title={title} subtitle={subtitle} />
4050
<Content>
4151
<ContentHeader>
52+
<SelectStatus currentStatus={status} setStatus={setStatus} />
53+
<SelectSeverity
54+
currentSeverity={severity}
55+
setSeverity={setSeverity}
56+
/>
4257
<SelectEnvironment
4358
environments={environments}
4459
currentEnvironment={currentEnvironment}
@@ -50,33 +65,13 @@ export const PolicyReportsPage = ({
5065
<PolicyReportsTable
5166
currentEnvironment={currentEnvironment}
5267
filter={{
53-
status: ['fail', 'warn', 'error'],
54-
}}
55-
title="Failing Policy Results"
56-
emptyContentText="No failing policies"
57-
policyDocumentationUrl={policyDocumentationUrl}
58-
/>
59-
</Grid>
60-
<Grid item xs={12}>
61-
<PolicyReportsTable
62-
currentEnvironment={currentEnvironment}
63-
filter={{
64-
status: ['pass'],
65-
}}
66-
title="Passing Policy Results"
67-
emptyContentText="No passing policies"
68-
policyDocumentationUrl={policyDocumentationUrl}
69-
/>
70-
</Grid>
71-
<Grid item xs={12}>
72-
<PolicyReportsTable
73-
currentEnvironment={currentEnvironment}
74-
filter={{
75-
status: ['skip'],
68+
status: status,
69+
severities: severity,
7670
}}
77-
title="Skipped Policy Results"
78-
emptyContentText="No skipped policies"
71+
title="Policy Results"
72+
emptyContentText="No policies found"
7973
policyDocumentationUrl={policyDocumentationUrl}
74+
pagination={{ offset: 25 }}
8075
/>
8176
</Grid>
8277
</Grid>

plugins/policy-reporter/src/components/PolicyReportsTable/PolicyReportsTable.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { useMemo, useState } from 'react';
77
import {
88
Filter,
99
ListResult,
10+
Pagination,
1011
} from '@kyverno/backstage-plugin-policy-reporter-common';
1112
import { Drawer, makeStyles } from '@material-ui/core';
1213
import Chip from '@material-ui/core/Chip';
@@ -24,6 +25,8 @@ interface PolicyReportsTableProps {
2425
emptyContentText: string;
2526
policyDocumentationUrl?: string;
2627
enableSearch?: boolean;
28+
pagination?: Partial<Pagination>;
29+
pageSizeOptions?: number[];
2730
}
2831

2932
export const PolicyReportsTable = ({
@@ -33,6 +36,8 @@ export const PolicyReportsTable = ({
3336
filter,
3437
policyDocumentationUrl,
3538
enableSearch,
39+
pagination,
40+
pageSizeOptions,
3641
}: PolicyReportsTableProps) => {
3742
const useStyles = makeStyles(theme => ({
3843
empty: {
@@ -119,10 +124,11 @@ export const PolicyReportsTable = ({
119124
policies,
120125
policiesError,
121126
currentPage,
127+
currentOffset,
122128
setCurrentPage,
123129
setCurrentOffset,
124130
initialLoading,
125-
} = usePaginatedPolicies(currentEnvironment, mergedFilter);
131+
} = usePaginatedPolicies(currentEnvironment, mergedFilter, pagination);
126132

127133
if (policiesError) return <ResponseErrorPanel error={policiesError} />;
128134

@@ -152,6 +158,8 @@ export const PolicyReportsTable = ({
152158
sorting: true,
153159
padding: 'dense',
154160
search: enableSearch,
161+
pageSize: currentOffset,
162+
pageSizeOptions: pageSizeOptions,
155163
}}
156164
onRowClick={(
157165
event?: React.MouseEvent<Element, MouseEvent>,
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import MenuItem from '@material-ui/core/MenuItem';
2+
import InputLabel from '@material-ui/core/InputLabel';
3+
import FormControl from '@material-ui/core/FormControl';
4+
import Select from '@material-ui/core/Select';
5+
import { Severity } from '@kyverno/backstage-plugin-policy-reporter-common';
6+
import { makeStyles } from '@material-ui/core/styles';
7+
import { Checkbox, ListItemText } from '@material-ui/core';
8+
9+
// This could be moved into the common package if needed in multiple places
10+
const SEVERITY_VALUES: Severity[] = [
11+
'unknown',
12+
'low',
13+
'medium',
14+
'high',
15+
'critical',
16+
'info',
17+
];
18+
19+
const useStyles = makeStyles({
20+
formControl: {
21+
margin: 8,
22+
minWidth: 150,
23+
},
24+
});
25+
26+
export type SelectSeverityProps = {
27+
currentSeverity: Severity[];
28+
setSeverity: (Status: Severity[]) => void;
29+
};
30+
31+
export const SelectSeverity = ({
32+
currentSeverity: currentSeverity,
33+
setSeverity: setSeverity,
34+
}: SelectSeverityProps) => {
35+
const classes = useStyles();
36+
37+
const handleChange = (event: React.ChangeEvent<{ value: unknown }>) => {
38+
setSeverity(event.target.value as Severity[]);
39+
};
40+
41+
return (
42+
<FormControl className={classes.formControl}>
43+
<InputLabel id="select-severity-label" shrink>
44+
Severity
45+
</InputLabel>
46+
<Select
47+
labelId="select-severity-label"
48+
id="select-severity"
49+
multiple
50+
displayEmpty
51+
value={currentSeverity}
52+
renderValue={selected => {
53+
if ((selected as Severity[]).length === 0) {
54+
return 'All';
55+
}
56+
57+
return (selected as Severity[]).join(', ');
58+
}}
59+
onChange={handleChange}
60+
>
61+
{SEVERITY_VALUES.map(severity => (
62+
<MenuItem key={severity} value={severity}>
63+
<Checkbox checked={currentSeverity.includes(severity)} />
64+
<ListItemText primary={severity} />
65+
</MenuItem>
66+
))}
67+
</Select>
68+
</FormControl>
69+
);
70+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { SelectSeverity, type SelectSeverityProps } from './SelectSeverity';
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import MenuItem from '@material-ui/core/MenuItem';
2+
import InputLabel from '@material-ui/core/InputLabel';
3+
import FormControl from '@material-ui/core/FormControl';
4+
import Select from '@material-ui/core/Select';
5+
import { Status } from '@kyverno/backstage-plugin-policy-reporter-common';
6+
import { makeStyles } from '@material-ui/core/styles';
7+
import { Checkbox, ListItemText } from '@material-ui/core';
8+
9+
// This could be moved into the common package if needed in multiple places
10+
const STATUS_VALUES: Status[] = [
11+
'fail',
12+
'skip',
13+
'pass',
14+
'warn',
15+
'error',
16+
'summary',
17+
];
18+
19+
const useStyles = makeStyles({
20+
formControl: {
21+
margin: 8,
22+
minWidth: 150,
23+
},
24+
});
25+
26+
export type SelectStatusProps = {
27+
currentStatus: Status[];
28+
setStatus: (Status: Status[]) => void;
29+
};
30+
31+
export const SelectStatus = ({
32+
currentStatus,
33+
setStatus,
34+
}: SelectStatusProps) => {
35+
const classes = useStyles();
36+
37+
const handleChange = (event: React.ChangeEvent<{ value: unknown }>) => {
38+
setStatus(event.target.value as Status[]);
39+
};
40+
41+
return (
42+
<FormControl className={classes.formControl}>
43+
<InputLabel id="select-status-label" shrink>
44+
Status
45+
</InputLabel>
46+
<Select
47+
labelId="select-status-label"
48+
id="select-status"
49+
multiple
50+
displayEmpty
51+
value={currentStatus}
52+
renderValue={selected => {
53+
if ((selected as Status[]).length === 0) {
54+
return 'All';
55+
}
56+
57+
return (selected as Status[]).join(', ');
58+
}}
59+
onChange={handleChange}
60+
>
61+
{STATUS_VALUES.map(status => (
62+
<MenuItem key={status} value={status}>
63+
<Checkbox checked={currentStatus.includes(status)} />
64+
<ListItemText primary={status} />
65+
</MenuItem>
66+
))}
67+
</Select>
68+
</FormControl>
69+
);
70+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { SelectStatus, type SelectStatusProps } from './SelectStatus';

0 commit comments

Comments
 (0)