foreman_openscap 4.3.2 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/controllers/api/v2/compliance/arf_reports_controller.rb +0 -6
- data/app/controllers/api/v2/compliance/oval_policies_controller.rb +1 -1
- data/app/graphql/mutations/oval_contents/delete.rb +9 -0
- data/app/graphql/mutations/oval_policies/create.rb +33 -0
- data/app/graphql/mutations/oval_policies/delete.rb +9 -0
- data/app/graphql/mutations/oval_policies/update.rb +15 -0
- data/app/graphql/types/oval_check.rb +11 -0
- data/app/graphql/types/oval_content.rb +2 -0
- data/app/graphql/types/oval_policy.rb +3 -0
- data/app/helpers/arf_report_dashboard_helper.rb +2 -4
- data/app/helpers/compliance_hosts_helper.rb +1 -1
- data/app/helpers/policies_helper.rb +2 -2
- data/app/models/concerns/foreman_openscap/data_stream_content.rb +1 -1
- data/app/models/concerns/foreman_openscap/host_extensions.rb +0 -6
- data/app/models/concerns/foreman_openscap/oval_facet_hostgroup_extensions.rb +16 -0
- data/app/models/foreman_openscap/arf_report.rb +1 -1
- data/app/models/foreman_openscap/oval_content.rb +2 -0
- data/app/services/foreman_openscap/client_config/base.rb +1 -0
- data/app/services/foreman_openscap/client_config/puppet.rb +6 -2
- data/app/services/foreman_openscap/oval/configure.rb +16 -13
- data/app/services/foreman_openscap/oval/setup.rb +5 -5
- data/app/services/foreman_openscap/oval/setup_check.rb +5 -2
- data/app/views/api/v2/compliance/oval_contents/destroy.json.rabl +3 -0
- data/app/views/arf_reports/_metrics.html.erb +4 -4
- data/app/views/compliance_hosts/show.html.erb +4 -6
- data/app/views/dashboard/_compliance_reports_breakdown_widget.html.erb +4 -3
- data/app/views/policy_dashboard/_policy_chart_widget.html.erb +3 -2
- data/db/migrate/20200117135424_migrate_port_overrides_to_int.rb +2 -1
- data/db/migrate/20201202110213_update_puppet_port_param_type.rb +2 -1
- data/db/migrate/20210819143316_drop_unused_tables.rb +6 -0
- data/lib/foreman_openscap/engine.rb +8 -9
- data/lib/foreman_openscap/version.rb +1 -1
- data/package.json +3 -6
- data/test/functional/api/v2/compliance/oval_reports_controller_test.rb +1 -1
- data/test/functional/api/v2/compliance/policies_controller_test.rb +2 -0
- data/test/graphql/mutations/oval_policies/delete_mutation_test.rb +63 -0
- data/test/graphql/queries/oval_content_query_test.rb +29 -0
- data/test/helpers/arf_report_dashboard_helper_test.rb +9 -10
- data/test/helpers/policy_dashboard_helper_test.rb +1 -1
- data/test/test_plugin_helper.rb +9 -4
- data/test/unit/policy_test.rb +1 -1
- data/test/unit/services/config_name_service_test.rb +1 -0
- data/test/unit/services/hostgroup_overrider_test.rb +2 -1
- data/test/unit/services/lookup_key_overrider_test.rb +4 -1
- data/test/unit/services/oval/setup_check_test.rb +37 -0
- data/webpack/components/ConfirmModal.js +63 -0
- data/webpack/components/ConfirmModal.scss +3 -0
- data/webpack/components/EditableInput.js +163 -0
- data/webpack/components/EditableInput.scss +3 -0
- data/webpack/components/EmptyState.js +12 -3
- data/webpack/components/IndexLayout.js +11 -4
- data/webpack/components/IndexTable/index.js +21 -16
- data/webpack/components/LinkButton.js +38 -0
- data/webpack/components/withDeleteModal.js +51 -0
- data/webpack/components/withLoading.js +44 -5
- data/webpack/graphql/mutations/createOvalPolicy.gql +22 -0
- data/webpack/graphql/mutations/deleteOvalContent.gql +9 -0
- data/webpack/graphql/mutations/deleteOvalPolicy.gql +9 -0
- data/webpack/graphql/mutations/updateOvalPolicy.gql +14 -0
- data/webpack/graphql/queries/currentUserAttributes.gql +11 -0
- data/webpack/graphql/queries/cves.gql +5 -0
- data/webpack/graphql/queries/hostgroups.gql +14 -0
- data/webpack/graphql/queries/ovalContent.gql +8 -0
- data/webpack/graphql/queries/ovalContents.gql +8 -0
- data/webpack/graphql/queries/ovalPolicies.gql +8 -0
- data/webpack/graphql/queries/ovalPolicy.gql +8 -0
- data/webpack/helpers/formFieldsHelper.js +113 -0
- data/webpack/helpers/globalIdHelper.js +4 -2
- data/webpack/helpers/mutationHelper.js +68 -0
- data/webpack/helpers/pathsHelper.js +10 -3
- data/webpack/helpers/permissionsHelper.js +42 -0
- data/webpack/helpers/toastHelper.js +3 -0
- data/webpack/helpers/toastsHelper.js +3 -0
- data/webpack/routes/OvalContents/OvalContentsIndex/OvalContentsIndex.js +26 -0
- data/webpack/routes/OvalContents/OvalContentsIndex/OvalContentsTable.js +50 -5
- data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsDestroy.fixtures.js +105 -0
- data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsDestroy.test.js +124 -0
- data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.fixtures.js +98 -77
- data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.test.js +53 -6
- data/webpack/routes/OvalContents/OvalContentsIndex/index.js +7 -1
- data/webpack/routes/OvalContents/OvalContentsNew/OvalContentsNew.js +138 -0
- data/webpack/routes/OvalContents/OvalContentsNew/OvalContentsNew.scss +3 -0
- data/webpack/routes/OvalContents/OvalContentsNew/OvalContentsNewHelper.js +73 -0
- data/webpack/routes/OvalContents/OvalContentsNew/__tests__/OvalContentsNew.test.js +104 -0
- data/webpack/routes/OvalContents/OvalContentsNew/index.js +13 -0
- data/webpack/routes/OvalContents/OvalContentsShow/OvalContentsShow.js +62 -0
- data/webpack/routes/OvalContents/OvalContentsShow/OvalContentsShow.test.js +45 -0
- data/webpack/routes/OvalContents/OvalContentsShow/OvalContentsShowHelper.js +0 -0
- data/webpack/routes/OvalContents/OvalContentsShow/index.js +35 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesIndex.js +18 -2
- data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesTable.js +34 -4
- data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesDestroy.fixtures.js +101 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesDestroy.test.js +117 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesIndex.fixtures.js +71 -21
- data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesIndex.test.js +34 -2
- data/webpack/routes/OvalPolicies/OvalPoliciesIndex/index.js +7 -1
- data/webpack/routes/OvalPolicies/OvalPoliciesNew/HostgroupSelect.js +135 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesNew/NewOvalPolicyForm.js +119 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesNew/NewOvalPolicyFormHelpers.js +107 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesNew/OvalPoliciesNew.js +32 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesNew/__tests__/OvalPoliciesNew.fixtures.js +147 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesNew/__tests__/OvalPoliciesNew.test.js +172 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesNew/index.js +11 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/CvesTab.js +1 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/CvesTable.js +2 -2
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/DetailsTab.js +87 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/HostgroupsTab.js +49 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/HostgroupsTable.js +38 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/OvalPoliciesShow.js +15 -11
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/OvalPoliciesShowHelper.js +80 -2
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesEdit.fixtures.js +48 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesEdit.test.js +202 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.fixtures.js +50 -4
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.test.js +64 -4
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/index.js +4 -0
- data/webpack/routes/routes.js +21 -0
- data/webpack/testHelper.js +64 -2
- metadata +63 -7
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
|
+
import { Button } from '@patternfly/react-core';
|
|
4
|
+
|
|
3
5
|
import { translate as __ } from 'foremanReact/common/I18n';
|
|
4
6
|
|
|
5
7
|
import IndexTable from '../../../components/IndexTable';
|
|
6
8
|
import withLoading from '../../../components/withLoading';
|
|
9
|
+
import withDeleteModal from '../../../components/withDeleteModal';
|
|
7
10
|
|
|
8
11
|
import { linkCell } from '../../../helpers/tableHelper';
|
|
9
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
ovalPoliciesPath,
|
|
14
|
+
modelPath,
|
|
15
|
+
ovalPoliciesNewPath,
|
|
16
|
+
} from '../../../helpers/pathsHelper';
|
|
10
17
|
|
|
11
18
|
const OvalPoliciesTable = props => {
|
|
12
19
|
const columns = [{ title: __('Name') }, { title: __('OVAL Content') }];
|
|
@@ -19,17 +26,39 @@ const OvalPoliciesTable = props => {
|
|
|
19
26
|
policy,
|
|
20
27
|
}));
|
|
21
28
|
|
|
22
|
-
const
|
|
29
|
+
const actionResolver = (rowData, rest) => {
|
|
30
|
+
const actions = [];
|
|
31
|
+
if (rowData.policy.meta.canDestroy) {
|
|
32
|
+
actions.push({
|
|
33
|
+
title: __('Delete OVAL Policy'),
|
|
34
|
+
onClick: (event, rowId, rData, extra) => {
|
|
35
|
+
props.toggleModal(rData.policy);
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return actions;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const createBtn = (
|
|
43
|
+
<Button
|
|
44
|
+
onClick={() => props.history.push(ovalPoliciesNewPath)}
|
|
45
|
+
variant="primary"
|
|
46
|
+
aria-label="create_oval_policy"
|
|
47
|
+
>
|
|
48
|
+
{__('Create OVAL Policy')}
|
|
49
|
+
</Button>
|
|
50
|
+
);
|
|
23
51
|
|
|
24
52
|
return (
|
|
25
53
|
<IndexTable
|
|
26
54
|
columns={columns}
|
|
27
55
|
rows={rows}
|
|
28
|
-
|
|
56
|
+
actionResolver={actionResolver}
|
|
29
57
|
pagination={props.pagination}
|
|
30
58
|
totalCount={props.totalCount}
|
|
31
59
|
history={props.history}
|
|
32
60
|
ariaTableLabel={__('OVAL Policies Table')}
|
|
61
|
+
toolbarBtns={createBtn}
|
|
33
62
|
/>
|
|
34
63
|
);
|
|
35
64
|
};
|
|
@@ -39,6 +68,7 @@ OvalPoliciesTable.propTypes = {
|
|
|
39
68
|
pagination: PropTypes.object.isRequired,
|
|
40
69
|
totalCount: PropTypes.number.isRequired,
|
|
41
70
|
history: PropTypes.object.isRequired,
|
|
71
|
+
toggleModal: PropTypes.func.isRequired,
|
|
42
72
|
};
|
|
43
73
|
|
|
44
|
-
export default withLoading(OvalPoliciesTable);
|
|
74
|
+
export default withLoading(withDeleteModal(OvalPoliciesTable));
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import policiesQuery from '../../../../graphql/queries/ovalPolicies.gql';
|
|
2
|
+
import deleteOvalPolicy from '../../../../graphql/mutations/deleteOvalPolicy.gql';
|
|
3
|
+
|
|
4
|
+
import { admin } from '../../../../testHelper';
|
|
5
|
+
|
|
6
|
+
export const firstCall = {
|
|
7
|
+
data: {
|
|
8
|
+
ovalPolicies: {
|
|
9
|
+
totalCount: 5,
|
|
10
|
+
nodes: [
|
|
11
|
+
{
|
|
12
|
+
__typename: 'ForemanOpenscap::OvalPolicy',
|
|
13
|
+
id: 'MDE6Rm9yZW1hbk9wZW5zY2FwOjpPdmFsUG9saWN5LTQz',
|
|
14
|
+
name: 'first policy',
|
|
15
|
+
meta: { canDestroy: true },
|
|
16
|
+
ovalContent: { name: 'foo' },
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
__typename: 'ForemanOpenscap::OvalPolicy',
|
|
20
|
+
id: 'MDE6Rm9yZW1hbk9wZW5zY2FwOjpPdmFsUG9saWN5LTQ0',
|
|
21
|
+
name: 'second policy',
|
|
22
|
+
meta: { canDestroy: true },
|
|
23
|
+
ovalContent: { name: 'foo' },
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
currentUser: admin,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const secondCall = {
|
|
32
|
+
data: {
|
|
33
|
+
ovalPolicies: {
|
|
34
|
+
totalCount: 4,
|
|
35
|
+
nodes: [
|
|
36
|
+
{
|
|
37
|
+
id: 'MDE6Rm9yZW1hbk9wZW5zY2FwOjpPdmFsUG9saWN5LTQ0',
|
|
38
|
+
name: 'second policy',
|
|
39
|
+
meta: { canDestroy: true },
|
|
40
|
+
ovalContent: { name: 'foo' },
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: 'MDE6Rm9yZW1hbk9wZW5zY2FwOjpPdmFsUG9saWN5LTQ1',
|
|
44
|
+
name: 'third policy',
|
|
45
|
+
meta: { canDestroy: true },
|
|
46
|
+
ovalContent: { name: 'foo' },
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
currentUser: admin,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const deleteMockFactory = (first, second, errors = null) => {
|
|
55
|
+
let called = false;
|
|
56
|
+
|
|
57
|
+
const deleteMocks = [
|
|
58
|
+
{
|
|
59
|
+
request: {
|
|
60
|
+
query: deleteOvalPolicy,
|
|
61
|
+
variables: {
|
|
62
|
+
id: 'MDE6Rm9yZW1hbk9wZW5zY2FwOjpPdmFsUG9saWN5LTQz',
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
result: {
|
|
66
|
+
data: {
|
|
67
|
+
deleteOvalPolicy: {
|
|
68
|
+
__typename: 'ForemanOpenscap::OvalPolicy',
|
|
69
|
+
id: 'MDE6Rm9yZW1hbk9wZW5zY2FwOjpPdmFsUG9saWN5LTQz',
|
|
70
|
+
errors,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
request: {
|
|
77
|
+
query: policiesQuery,
|
|
78
|
+
variables: {
|
|
79
|
+
first: 2,
|
|
80
|
+
last: 2,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
newData: () => {
|
|
84
|
+
if (called && !errors) {
|
|
85
|
+
return second;
|
|
86
|
+
} else if (called && errors) {
|
|
87
|
+
return first;
|
|
88
|
+
}
|
|
89
|
+
called = true;
|
|
90
|
+
return first;
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
return deleteMocks;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const pageParamsHistoryMock = {
|
|
98
|
+
location: {
|
|
99
|
+
search: '?page=1&perPage=2',
|
|
100
|
+
},
|
|
101
|
+
};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
|
+
import userEvent from '@testing-library/user-event';
|
|
5
|
+
|
|
6
|
+
import OvalPoliciesIndex from '../OvalPoliciesIndex';
|
|
7
|
+
import {
|
|
8
|
+
withRouter,
|
|
9
|
+
withRedux,
|
|
10
|
+
withMockedProvider,
|
|
11
|
+
tick,
|
|
12
|
+
historyMock,
|
|
13
|
+
} from '../../../../testHelper';
|
|
14
|
+
import { mocks, noDeleteMocks } from './OvalPoliciesIndex.fixtures';
|
|
15
|
+
import {
|
|
16
|
+
firstCall,
|
|
17
|
+
secondCall,
|
|
18
|
+
deleteMockFactory,
|
|
19
|
+
pageParamsHistoryMock,
|
|
20
|
+
} from './OvalPoliciesDestroy.fixtures';
|
|
21
|
+
|
|
22
|
+
const TestComponent = withRouter(
|
|
23
|
+
withRedux(withMockedProvider(OvalPoliciesIndex))
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
describe('OvalPoliciesIndex', () => {
|
|
27
|
+
it('should open and close delete modal', async () => {
|
|
28
|
+
render(
|
|
29
|
+
<TestComponent
|
|
30
|
+
history={historyMock}
|
|
31
|
+
mocks={mocks}
|
|
32
|
+
showToast={jest.fn()}
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
35
|
+
await waitFor(tick);
|
|
36
|
+
expect(screen.getByText('first policy')).toBeInTheDocument();
|
|
37
|
+
userEvent.click(screen.getAllByRole('button', { name: 'Actions' })[0]);
|
|
38
|
+
userEvent.click(screen.getByText('Delete OVAL Policy'));
|
|
39
|
+
await waitFor(tick);
|
|
40
|
+
expect(
|
|
41
|
+
screen.getByText('Are you sure you want to delete first policy?')
|
|
42
|
+
).toBeInTheDocument();
|
|
43
|
+
userEvent.click(screen.getByText('Cancel'));
|
|
44
|
+
await waitFor(tick);
|
|
45
|
+
expect(
|
|
46
|
+
screen.queryByText('Are you sure you want to delete first policy?')
|
|
47
|
+
).not.toBeInTheDocument();
|
|
48
|
+
expect(screen.getByText('first policy')).toBeInTheDocument();
|
|
49
|
+
});
|
|
50
|
+
it('should delete OVAL policy', async () => {
|
|
51
|
+
const showToast = jest.fn();
|
|
52
|
+
render(
|
|
53
|
+
<TestComponent
|
|
54
|
+
history={pageParamsHistoryMock}
|
|
55
|
+
mocks={deleteMockFactory(firstCall, secondCall)}
|
|
56
|
+
showToast={showToast}
|
|
57
|
+
/>
|
|
58
|
+
);
|
|
59
|
+
await waitFor(tick);
|
|
60
|
+
expect(screen.getByText('first policy')).toBeInTheDocument();
|
|
61
|
+
expect(screen.queryByText('third policy')).not.toBeInTheDocument();
|
|
62
|
+
userEvent.click(screen.getAllByRole('button', { name: 'Actions' })[0]);
|
|
63
|
+
userEvent.click(screen.getByText('Delete OVAL Policy'));
|
|
64
|
+
await waitFor(tick);
|
|
65
|
+
userEvent.click(screen.getByText('Confirm'));
|
|
66
|
+
await waitFor(tick);
|
|
67
|
+
expect(showToast).toHaveBeenCalledWith({
|
|
68
|
+
type: 'success',
|
|
69
|
+
message: 'OVAL policy was successfully deleted.',
|
|
70
|
+
});
|
|
71
|
+
await waitFor(tick);
|
|
72
|
+
expect(screen.queryByText('first policy')).not.toBeInTheDocument();
|
|
73
|
+
expect(screen.getByText('third policy')).toBeInTheDocument();
|
|
74
|
+
});
|
|
75
|
+
it('should show error when deleting OVAL policy fails', async () => {
|
|
76
|
+
const showToast = jest.fn();
|
|
77
|
+
render(
|
|
78
|
+
<TestComponent
|
|
79
|
+
history={pageParamsHistoryMock}
|
|
80
|
+
mocks={deleteMockFactory(firstCall, secondCall, [
|
|
81
|
+
{ message: 'cannot do it', path: 'base' },
|
|
82
|
+
{ message: 'will not do it', path: 'base' },
|
|
83
|
+
])}
|
|
84
|
+
showToast={showToast}
|
|
85
|
+
/>
|
|
86
|
+
);
|
|
87
|
+
await waitFor(tick);
|
|
88
|
+
expect(screen.getByText('first policy')).toBeInTheDocument();
|
|
89
|
+
expect(screen.queryByText('third policy')).not.toBeInTheDocument();
|
|
90
|
+
userEvent.click(screen.getAllByRole('button', { name: 'Actions' })[0]);
|
|
91
|
+
userEvent.click(screen.getByText('Delete OVAL Policy'));
|
|
92
|
+
await waitFor(tick);
|
|
93
|
+
userEvent.click(screen.getByText('Confirm'));
|
|
94
|
+
await waitFor(tick);
|
|
95
|
+
expect(showToast).toHaveBeenCalledWith({
|
|
96
|
+
type: 'error',
|
|
97
|
+
message:
|
|
98
|
+
'There was a following error when deleting OVAL policy: cannot do it, will not do it',
|
|
99
|
+
});
|
|
100
|
+
expect(screen.getByText('first policy')).toBeInTheDocument();
|
|
101
|
+
expect(screen.queryByText('third policy')).not.toBeInTheDocument();
|
|
102
|
+
});
|
|
103
|
+
it('should not show delete button when user does not have delete permissions', async () => {
|
|
104
|
+
render(
|
|
105
|
+
<TestComponent
|
|
106
|
+
history={historyMock}
|
|
107
|
+
mocks={noDeleteMocks}
|
|
108
|
+
showToast={jest.fn()}
|
|
109
|
+
/>
|
|
110
|
+
);
|
|
111
|
+
await waitFor(tick);
|
|
112
|
+
expect(screen.getByText('first policy')).toBeInTheDocument();
|
|
113
|
+
expect(
|
|
114
|
+
screen.queryByRole('button', { name: 'Actions' })
|
|
115
|
+
).not.toBeInTheDocument();
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import policiesQuery from '../../../../graphql/queries/ovalPolicies.gql';
|
|
2
2
|
import { ovalPoliciesPath } from '../../../../helpers/pathsHelper';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
mockFactory,
|
|
5
|
+
admin,
|
|
6
|
+
intruder,
|
|
7
|
+
userFactory,
|
|
8
|
+
} from '../../../../testHelper';
|
|
4
9
|
|
|
5
10
|
const policiesMockFactory = mockFactory('ovalPolicies', policiesQuery);
|
|
6
11
|
|
|
@@ -14,23 +19,37 @@ export const pageParamsHistoryMock = {
|
|
|
14
19
|
push: pushMock,
|
|
15
20
|
};
|
|
16
21
|
|
|
22
|
+
const viewer = userFactory('viewer', [
|
|
23
|
+
{
|
|
24
|
+
__typename: 'Permission',
|
|
25
|
+
id: 'MDE6UGVybWlzc2lvbi0yOTY=',
|
|
26
|
+
name: 'view_oval_policies',
|
|
27
|
+
},
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
const firstPolicy = (meta = { canDestroy: true }) => ({
|
|
31
|
+
__typename: 'ForemanOpenscap::OvalPolicy',
|
|
32
|
+
id: 'MDE6Rm9yZW1hbk9wZW5zY2FwOjpPdmFsUG9saWN5LTE=',
|
|
33
|
+
name: 'first policy',
|
|
34
|
+
meta,
|
|
35
|
+
ovalContent: { name: 'first content' },
|
|
36
|
+
});
|
|
37
|
+
const secondPolicy = (meta = { canDestroy: true }) => ({
|
|
38
|
+
__typename: 'ForemanOpenscap::OvalPolicy',
|
|
39
|
+
id: 'MDE6Rm9yZW1hbk9wZW5zY2FwOjpPdmFsUG9saWN5LTQw',
|
|
40
|
+
name: 'second policy',
|
|
41
|
+
meta,
|
|
42
|
+
ovalContent: { name: 'second content' },
|
|
43
|
+
});
|
|
44
|
+
const policiesData = {
|
|
45
|
+
totalCount: 2,
|
|
46
|
+
nodes: [firstPolicy(), secondPolicy()],
|
|
47
|
+
};
|
|
48
|
+
|
|
17
49
|
export const mocks = policiesMockFactory(
|
|
18
50
|
{ first: 20, last: 20 },
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
nodes: [
|
|
22
|
-
{
|
|
23
|
-
id: 'abc',
|
|
24
|
-
name: 'first policy',
|
|
25
|
-
ovalContent: { name: 'first content' },
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
id: 'xyz',
|
|
29
|
-
name: 'second policy',
|
|
30
|
-
ovalContent: { name: 'second content' },
|
|
31
|
-
},
|
|
32
|
-
],
|
|
33
|
-
}
|
|
51
|
+
policiesData,
|
|
52
|
+
{ currentUser: admin }
|
|
34
53
|
);
|
|
35
54
|
export const pageParamsMocks = policiesMockFactory(
|
|
36
55
|
{ first: 10, last: 5 },
|
|
@@ -38,24 +57,55 @@ export const pageParamsMocks = policiesMockFactory(
|
|
|
38
57
|
totalCount: 7,
|
|
39
58
|
nodes: [
|
|
40
59
|
{
|
|
41
|
-
|
|
60
|
+
__typename: 'ForemanOpenscap::OvalPolicy',
|
|
61
|
+
id: 'MDE6Rm9yZW1hbk9wZW5zY2FwOjpPdmFsUG9saWN5LTQx',
|
|
42
62
|
name: 'sixth policy',
|
|
63
|
+
meta: { canDestroy: true },
|
|
43
64
|
ovalContent: { name: 'sixth content' },
|
|
44
65
|
},
|
|
45
66
|
{
|
|
46
|
-
|
|
67
|
+
__typename: 'ForemanOpenscap::OvalPolicy',
|
|
68
|
+
id: 'MDE6Rm9yZW1hbk9wZW5zY2FwOjpPdmFsUG9saWN5LTQy',
|
|
47
69
|
name: 'seventh policy',
|
|
70
|
+
meta: { canDestroy: true },
|
|
48
71
|
ovalContent: { name: 'seventh content' },
|
|
49
72
|
},
|
|
50
73
|
],
|
|
51
|
-
}
|
|
74
|
+
},
|
|
75
|
+
{ currentUser: admin }
|
|
52
76
|
);
|
|
77
|
+
|
|
53
78
|
export const emptyMocks = policiesMockFactory(
|
|
54
79
|
{ first: 20, last: 20 },
|
|
55
|
-
{ totalCount: 0, nodes: [] }
|
|
80
|
+
{ totalCount: 0, nodes: [] },
|
|
81
|
+
{ currentUser: admin }
|
|
56
82
|
);
|
|
57
83
|
export const errorMocks = policiesMockFactory(
|
|
58
84
|
{ first: 20, last: 20 },
|
|
59
85
|
{ totalCount: 0, nodes: [] },
|
|
60
|
-
|
|
86
|
+
{
|
|
87
|
+
errors: [{ message: 'Something very bad happened.', path: 'base' }],
|
|
88
|
+
currentUser: admin,
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
export const viewerMocks = policiesMockFactory(
|
|
92
|
+
{ first: 20, last: 20 },
|
|
93
|
+
policiesData,
|
|
94
|
+
{ currentUser: viewer }
|
|
95
|
+
);
|
|
96
|
+
export const unauthorizedMocks = policiesMockFactory(
|
|
97
|
+
{ first: 20, last: 20 },
|
|
98
|
+
policiesData,
|
|
99
|
+
{ currentUser: intruder }
|
|
100
|
+
);
|
|
101
|
+
export const noDeleteMocks = policiesMockFactory(
|
|
102
|
+
{ first: 20, last: 20 },
|
|
103
|
+
{
|
|
104
|
+
totalCount: 2,
|
|
105
|
+
nodes: [
|
|
106
|
+
firstPolicy({ canDestroy: false }),
|
|
107
|
+
secondPolicy({ canDestroy: false }),
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
{ currentUser: admin }
|
|
61
111
|
);
|
|
@@ -7,6 +7,7 @@ import '@testing-library/jest-dom';
|
|
|
7
7
|
import {
|
|
8
8
|
withMockedProvider,
|
|
9
9
|
withRouter,
|
|
10
|
+
withRedux,
|
|
10
11
|
tick,
|
|
11
12
|
historyMock,
|
|
12
13
|
} from '../../../../testHelper';
|
|
@@ -18,12 +19,16 @@ import {
|
|
|
18
19
|
pageParamsHistoryMock,
|
|
19
20
|
emptyMocks,
|
|
20
21
|
errorMocks,
|
|
22
|
+
viewerMocks,
|
|
23
|
+
unauthorizedMocks,
|
|
21
24
|
} from './OvalPoliciesIndex.fixtures';
|
|
22
25
|
|
|
23
|
-
import OvalPoliciesIndex from '../
|
|
26
|
+
import OvalPoliciesIndex from '../index';
|
|
24
27
|
import { ovalPoliciesPath } from '../../../../helpers/pathsHelper';
|
|
25
28
|
|
|
26
|
-
const TestComponent = withRouter(
|
|
29
|
+
const TestComponent = withRouter(
|
|
30
|
+
withRedux(withMockedProvider(OvalPoliciesIndex))
|
|
31
|
+
);
|
|
27
32
|
|
|
28
33
|
describe('OvalPoliciesIndex', () => {
|
|
29
34
|
it('should load page', async () => {
|
|
@@ -40,6 +45,15 @@ describe('OvalPoliciesIndex', () => {
|
|
|
40
45
|
expect(within(pageItems).getByText(/1 - 2/)).toBeInTheDocument();
|
|
41
46
|
expect(within(pageItems).getByText('of')).toBeInTheDocument();
|
|
42
47
|
expect(within(pageItems).getByText('2')).toBeInTheDocument();
|
|
48
|
+
|
|
49
|
+
expect(screen.getByText('first policy').closest('a')).toHaveAttribute(
|
|
50
|
+
'href',
|
|
51
|
+
'/experimental/compliance/oval_policies/1'
|
|
52
|
+
);
|
|
53
|
+
expect(screen.getByText('second policy').closest('a')).toHaveAttribute(
|
|
54
|
+
'href',
|
|
55
|
+
'/experimental/compliance/oval_policies/40'
|
|
56
|
+
);
|
|
43
57
|
});
|
|
44
58
|
it('should load page with page params', async () => {
|
|
45
59
|
const { container } = render(
|
|
@@ -75,4 +89,22 @@ describe('OvalPoliciesIndex', () => {
|
|
|
75
89
|
).toBeInTheDocument();
|
|
76
90
|
expect(screen.getByText('Error!')).toBeInTheDocument();
|
|
77
91
|
});
|
|
92
|
+
it('should load page for user with permissions', async () => {
|
|
93
|
+
render(<TestComponent history={historyMock} mocks={viewerMocks} />);
|
|
94
|
+
await waitFor(tick);
|
|
95
|
+
expect(screen.queryByText('Loading')).not.toBeInTheDocument();
|
|
96
|
+
expect(screen.getByText('first policy')).toBeInTheDocument();
|
|
97
|
+
});
|
|
98
|
+
it('should not load page for user without permissions', async () => {
|
|
99
|
+
render(<TestComponent history={historyMock} mocks={unauthorizedMocks} />);
|
|
100
|
+
await waitFor(tick);
|
|
101
|
+
expect(screen.queryByText('Loading')).not.toBeInTheDocument();
|
|
102
|
+
expect(screen.queryByText('first policy')).not.toBeInTheDocument();
|
|
103
|
+
expect(
|
|
104
|
+
screen.getByText(
|
|
105
|
+
'You are not authorized to view the page. Request the following permissions from administrator: view_oval_policies.'
|
|
106
|
+
)
|
|
107
|
+
).toBeInTheDocument();
|
|
108
|
+
expect(screen.getByText('Permission denied')).toBeInTheDocument();
|
|
109
|
+
});
|
|
78
110
|
});
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { useDispatch } from 'react-redux';
|
|
3
|
+
import { showToast } from '../../../helpers/toastHelper';
|
|
2
4
|
|
|
3
5
|
import OvalPoliciesIndex from './OvalPoliciesIndex';
|
|
4
6
|
|
|
5
|
-
const WrappedOvalPoliciesIndex = props =>
|
|
7
|
+
const WrappedOvalPoliciesIndex = props => {
|
|
8
|
+
const dispatch = useDispatch();
|
|
9
|
+
|
|
10
|
+
return <OvalPoliciesIndex {...props} showToast={showToast(dispatch)} />;
|
|
11
|
+
};
|
|
6
12
|
|
|
7
13
|
export default WrappedOvalPoliciesIndex;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { useLazyQuery } from '@apollo/client';
|
|
4
|
+
import { translate as __, sprintf } from 'foremanReact/common/I18n';
|
|
5
|
+
import {
|
|
6
|
+
Select,
|
|
7
|
+
SelectOption,
|
|
8
|
+
SelectVariant,
|
|
9
|
+
FormGroup,
|
|
10
|
+
} from '@patternfly/react-core';
|
|
11
|
+
import { ExclamationCircleIcon } from '@patternfly/react-icons';
|
|
12
|
+
import hostgroupsQuery from '../../../graphql/queries/hostgroups.gql';
|
|
13
|
+
|
|
14
|
+
const HostgroupSelect = ({
|
|
15
|
+
selected,
|
|
16
|
+
setSelected,
|
|
17
|
+
hgsError,
|
|
18
|
+
showError,
|
|
19
|
+
setShowError,
|
|
20
|
+
}) => {
|
|
21
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
22
|
+
|
|
23
|
+
const [typingTimeout, setTypingTimeout] = useState(null);
|
|
24
|
+
|
|
25
|
+
const [fetchHostgroups, { loading, data, error }] = useLazyQuery(
|
|
26
|
+
hostgroupsQuery
|
|
27
|
+
);
|
|
28
|
+
const results = data?.hostgroups?.nodes ? data.hostgroups.nodes : [];
|
|
29
|
+
|
|
30
|
+
const onSelect = (event, selection) => {
|
|
31
|
+
if (selected.find(item => item.name === selection)) {
|
|
32
|
+
setSelected(selected.filter(item => item.name !== selection));
|
|
33
|
+
} else {
|
|
34
|
+
const hg = results.find(item => item.name === selection);
|
|
35
|
+
setSelected([...selected, hg]);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const onClear = () => {
|
|
40
|
+
if (showError) {
|
|
41
|
+
setShowError(false);
|
|
42
|
+
}
|
|
43
|
+
setSelected([]);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const onInputChange = value => {
|
|
47
|
+
if (showError) {
|
|
48
|
+
setShowError(false);
|
|
49
|
+
}
|
|
50
|
+
if (typingTimeout) {
|
|
51
|
+
clearTimeout(typingTimeout);
|
|
52
|
+
}
|
|
53
|
+
const variables = { search: `name ~ ${value}` };
|
|
54
|
+
setTypingTimeout(setTimeout(() => fetchHostgroups({ variables }), 500));
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const shouldValidate = (err, shouldShowError) => {
|
|
58
|
+
if (shouldShowError) {
|
|
59
|
+
return err ? 'error' : 'success';
|
|
60
|
+
}
|
|
61
|
+
return 'noval';
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const prepareOptions = fetchedResults => {
|
|
65
|
+
if (loading) {
|
|
66
|
+
return [
|
|
67
|
+
<SelectOption isDisabled key={0}>
|
|
68
|
+
{__('Loading...')}
|
|
69
|
+
</SelectOption>,
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (error) {
|
|
74
|
+
return [
|
|
75
|
+
<SelectOption isDisabled key={0}>
|
|
76
|
+
{sprintf('Failed to fetch hostgroups, cause: %s', error.message)}
|
|
77
|
+
</SelectOption>,
|
|
78
|
+
];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (fetchedResults.length > 20) {
|
|
82
|
+
return [
|
|
83
|
+
<SelectOption isDisabled key={0}>
|
|
84
|
+
{sprintf(
|
|
85
|
+
'You have %s hostgroups to display. Please refine your search.',
|
|
86
|
+
fetchedResults.length
|
|
87
|
+
)}
|
|
88
|
+
</SelectOption>,
|
|
89
|
+
];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return fetchedResults.map((hg, idx) => (
|
|
93
|
+
<SelectOption key={hg.id} value={hg.name} />
|
|
94
|
+
));
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<FormGroup
|
|
99
|
+
label={__('Hostgroups')}
|
|
100
|
+
helperTextInvalidIcon={<ExclamationCircleIcon />}
|
|
101
|
+
helperTextInvalid={showError && hgsError}
|
|
102
|
+
validated={shouldValidate(hgsError, showError)}
|
|
103
|
+
>
|
|
104
|
+
<Select
|
|
105
|
+
variant={SelectVariant.typeaheadMulti}
|
|
106
|
+
typeAheadAriaLabel="Select a hostgroup"
|
|
107
|
+
placeholderText="Type a hostroup name..."
|
|
108
|
+
onToggle={() => setIsOpen(!isOpen)}
|
|
109
|
+
onSelect={onSelect}
|
|
110
|
+
onClear={onClear}
|
|
111
|
+
selections={selected.map(item => item.name)}
|
|
112
|
+
isOpen={isOpen}
|
|
113
|
+
onTypeaheadInputChanged={onInputChange}
|
|
114
|
+
validated={shouldValidate(hgsError, showError)}
|
|
115
|
+
>
|
|
116
|
+
{prepareOptions(results)}
|
|
117
|
+
</Select>
|
|
118
|
+
</FormGroup>
|
|
119
|
+
);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
HostgroupSelect.propTypes = {
|
|
123
|
+
selected: PropTypes.array,
|
|
124
|
+
setSelected: PropTypes.func.isRequired,
|
|
125
|
+
hgsError: PropTypes.string,
|
|
126
|
+
showError: PropTypes.bool.isRequired,
|
|
127
|
+
setShowError: PropTypes.func.isRequired,
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
HostgroupSelect.defaultProps = {
|
|
131
|
+
selected: [],
|
|
132
|
+
hgsError: '',
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export default HostgroupSelect;
|