foreman_openscap 4.3.2 → 5.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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/host_extensions.rb +0 -6
- data/app/models/concerns/foreman_openscap/oval_facet_hostgroup_extensions.rb +16 -0
- data/app/models/concerns/foreman_openscap/policy_common.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 +6 -7
- 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/{locale/de/foreman_openscap.edit.po → 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 +80 -37
- data/locale/en_GB/foreman_openscap.edit.po +0 -0
- data/locale/es/foreman_openscap.edit.po +0 -0
- data/locale/fr/foreman_openscap.edit.po +0 -0
- data/locale/gl/foreman_openscap.edit.po +0 -0
- data/locale/it/foreman_openscap.edit.po +0 -0
- data/locale/ja/foreman_openscap.edit.po +0 -0
- data/locale/ko/foreman_openscap.edit.po +0 -0
- data/locale/pt_BR/foreman_openscap.edit.po +0 -0
- data/locale/ru/foreman_openscap.edit.po +0 -0
- data/locale/sv_SE/foreman_openscap.edit.po +0 -0
- data/locale/zh_CN/foreman_openscap.edit.po +0 -0
- data/locale/zh_TW/foreman_openscap.edit.po +0 -0
@@ -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;
|