foreman_openscap 5.1.0 → 5.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/app/graphql/mutations/oval_policies/create.rb +33 -0
  3. data/app/helpers/policies_helper.rb +1 -1
  4. data/app/models/concerns/foreman_openscap/oval_facet_hostgroup_extensions.rb +1 -0
  5. data/app/models/concerns/foreman_openscap/policy_common.rb +1 -1
  6. data/app/services/foreman_openscap/oval/configure.rb +16 -13
  7. data/app/services/foreman_openscap/oval/setup_check.rb +1 -1
  8. data/lib/foreman_openscap/engine.rb +1 -0
  9. data/lib/foreman_openscap/version.rb +1 -1
  10. data/webpack/components/EditableInput.js +16 -10
  11. data/webpack/components/IndexTable/index.js +7 -2
  12. data/webpack/components/LinkButton.js +14 -2
  13. data/webpack/components/withLoading.js +3 -1
  14. data/webpack/graphql/mutations/createOvalPolicy.gql +22 -0
  15. data/webpack/graphql/queries/ovalPolicy.gql +3 -0
  16. data/webpack/helpers/formFieldsHelper.js +51 -1
  17. data/webpack/helpers/globalIdHelper.js +4 -2
  18. data/webpack/helpers/pathsHelper.js +5 -3
  19. data/webpack/helpers/toastsHelper.js +3 -0
  20. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.fixtures.js +6 -1
  21. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesTable.js +18 -1
  22. data/webpack/routes/OvalPolicies/OvalPoliciesNew/HostgroupSelect.js +135 -0
  23. data/webpack/routes/OvalPolicies/OvalPoliciesNew/NewOvalPolicyForm.js +119 -0
  24. data/webpack/routes/OvalPolicies/OvalPoliciesNew/NewOvalPolicyFormHelpers.js +107 -0
  25. data/webpack/routes/OvalPolicies/OvalPoliciesNew/OvalPoliciesNew.js +32 -0
  26. data/webpack/routes/OvalPolicies/OvalPoliciesNew/__tests__/OvalPoliciesNew.fixtures.js +147 -0
  27. data/webpack/routes/OvalPolicies/OvalPoliciesNew/__tests__/OvalPoliciesNew.test.js +172 -0
  28. data/webpack/routes/OvalPolicies/OvalPoliciesNew/index.js +11 -0
  29. data/webpack/routes/OvalPolicies/OvalPoliciesShow/CvesTable.js +2 -2
  30. data/webpack/routes/OvalPolicies/OvalPoliciesShow/DetailsTab.js +2 -0
  31. data/webpack/routes/OvalPolicies/OvalPoliciesShow/OvalPoliciesShowHelper.js +4 -3
  32. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesEdit.test.js +27 -0
  33. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.fixtures.js +11 -1
  34. data/webpack/routes/routes.js +7 -0
  35. data/webpack/testHelper.js +22 -0
  36. metadata +39 -42
  37. data/locale/de/foreman_openscap.edit.po +0 -0
  38. data/locale/en_GB/foreman_openscap.edit.po +0 -0
  39. data/locale/es/foreman_openscap.edit.po +0 -0
  40. data/locale/fr/foreman_openscap.edit.po +0 -0
  41. data/locale/gl/foreman_openscap.edit.po +0 -0
  42. data/locale/it/foreman_openscap.edit.po +0 -0
  43. data/locale/ja/foreman_openscap.edit.po +0 -0
  44. data/locale/ko/foreman_openscap.edit.po +0 -0
  45. data/locale/pt_BR/foreman_openscap.edit.po +0 -0
  46. data/locale/ru/foreman_openscap.edit.po +0 -0
  47. data/locale/sv_SE/foreman_openscap.edit.po +0 -0
  48. data/locale/zh_CN/foreman_openscap.edit.po +0 -0
  49. data/locale/zh_TW/foreman_openscap.edit.po +0 -0
@@ -0,0 +1,119 @@
1
+ import React, { useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { Formik, Field as FormikField } from 'formik';
4
+ import { useMutation } from '@apollo/client';
5
+ import { translate as __ } from 'foremanReact/common/I18n';
6
+ import { Button, Form as PfForm, ActionGroup } from '@patternfly/react-core';
7
+
8
+ import createOvalPolicy from '../../../graphql/mutations/createOvalPolicy.gql';
9
+
10
+ import {
11
+ TextField,
12
+ TextAreaField,
13
+ SelectField,
14
+ } from '../../../helpers/formFieldsHelper';
15
+ import HostgroupSelect from './HostgroupSelect';
16
+ import withLoading from '../../../components/withLoading';
17
+
18
+ import { ovalPoliciesPath } from '../../../helpers/pathsHelper';
19
+ import LinkButton from '../../../components/LinkButton';
20
+
21
+ import {
22
+ createValidationSchema,
23
+ onSubmit,
24
+ initialValues,
25
+ } from './NewOvalPolicyFormHelpers';
26
+
27
+ const NewOvalPolicyForm = ({ history, showToast, ovalContents }) => {
28
+ const [callMutation] = useMutation(createOvalPolicy);
29
+
30
+ const [assignedHgs, setAssignedHgs] = useState([]);
31
+ const [hgsShowError, setHgsShowError] = useState(false);
32
+ const [hgsError, setHgsError] = useState('');
33
+
34
+ const onHgsError = error => {
35
+ setHgsShowError(true);
36
+ setHgsError(error);
37
+ };
38
+
39
+ return (
40
+ <Formik
41
+ onSubmit={onSubmit(
42
+ history,
43
+ showToast,
44
+ callMutation,
45
+ assignedHgs,
46
+ onHgsError
47
+ )}
48
+ initialValues={initialValues}
49
+ validationSchema={createValidationSchema()}
50
+ >
51
+ {formProps => (
52
+ <PfForm>
53
+ <FormikField
54
+ name="name"
55
+ component={TextField}
56
+ label={__('Name')}
57
+ isRequired
58
+ />
59
+ <FormikField
60
+ name="description"
61
+ component={TextAreaField}
62
+ label={__('Description')}
63
+ />
64
+ <FormikField
65
+ name="cronLine"
66
+ component={TextField}
67
+ label={__('Schedule')}
68
+ isRequired
69
+ />
70
+ <FormikField
71
+ name="ovalContentId"
72
+ component={SelectField}
73
+ selectItems={ovalContents}
74
+ label={__('OVAL Content')}
75
+ isRequired
76
+ blankLabel={__('Choose OVAL Content')}
77
+ />
78
+ <HostgroupSelect
79
+ selected={assignedHgs}
80
+ setSelected={setAssignedHgs}
81
+ showError={hgsShowError}
82
+ setShowError={setHgsShowError}
83
+ hgsError={hgsError}
84
+ isDisabled={formProps.isSubmitting}
85
+ />
86
+ <ActionGroup>
87
+ <Button
88
+ variant="primary"
89
+ onClick={formProps.handleSubmit}
90
+ isDisabled={
91
+ !formProps.isValid ||
92
+ formProps.isSubmitting ||
93
+ (hgsShowError && hgsError)
94
+ }
95
+ aria-label="submit"
96
+ >
97
+ {__('Submit')}
98
+ </Button>
99
+ <LinkButton
100
+ path={ovalPoliciesPath}
101
+ btnVariant="link"
102
+ btnText={__('Cancel')}
103
+ btnAriaLabel="cancel"
104
+ isDisabled={formProps.isSubmitting}
105
+ />
106
+ </ActionGroup>
107
+ </PfForm>
108
+ )}
109
+ </Formik>
110
+ );
111
+ };
112
+
113
+ NewOvalPolicyForm.propTypes = {
114
+ history: PropTypes.object.isRequired,
115
+ showToast: PropTypes.func.isRequired,
116
+ ovalContents: PropTypes.array.isRequired,
117
+ };
118
+
119
+ export default withLoading(NewOvalPolicyForm);
@@ -0,0 +1,107 @@
1
+ import * as Yup from 'yup';
2
+ import { translate as __ } from 'foremanReact/common/I18n';
3
+
4
+ import { ovalPoliciesPath } from '../../../helpers/pathsHelper';
5
+ import { decodeId, decodeModelId } from '../../../helpers/globalIdHelper';
6
+
7
+ export const createValidationSchema = () => {
8
+ const cantBeBlank = __("can't be blank");
9
+
10
+ return Yup.object().shape({
11
+ name: Yup.string().required(cantBeBlank),
12
+ ovalContentId: Yup.string().required(cantBeBlank),
13
+ cronLine: Yup.string().test(
14
+ 'is-cron',
15
+ __('is not a valid cronline'),
16
+ value => value && value.trim().split(' ').length === 5
17
+ ),
18
+ });
19
+ };
20
+
21
+ const partitionById = (array, name) => {
22
+ const res = array.reduce(
23
+ (memo, item) => {
24
+ if (item.id === name) {
25
+ memo.left.push(item);
26
+ } else {
27
+ memo.right.push(item);
28
+ }
29
+ return memo;
30
+ },
31
+ { left: [], right: [] }
32
+ );
33
+ return [res.left, res.right];
34
+ };
35
+
36
+ const checksToMessage = checks =>
37
+ checks.reduce((memo, check) => [...memo, check.failMsg], []).join(' ');
38
+
39
+ export const onSubmit = (
40
+ history,
41
+ showToast,
42
+ callMutation,
43
+ assignedHgs,
44
+ setHgsError
45
+ ) => (values, actions) => {
46
+ const onCompleted = response => {
47
+ const failedChecks = response.data.createOvalPolicy.checkCollection.filter(
48
+ check => check.result === 'fail'
49
+ );
50
+ if (failedChecks.length === 0) {
51
+ history.push(ovalPoliciesPath);
52
+ showToast({
53
+ type: 'success',
54
+ message: 'OVAL Policy succesfully created.',
55
+ });
56
+ } else {
57
+ actions.setSubmitting(false);
58
+
59
+ const [validationChecks, withoutValidationChecks] = partitionById(
60
+ failedChecks,
61
+ 'oval_policy_errors'
62
+ );
63
+
64
+ const [hgChecks, remainingChecks] = partitionById(
65
+ withoutValidationChecks,
66
+ 'hostgroups_without_proxy'
67
+ );
68
+ if (validationChecks.length === 1) {
69
+ actions.setErrors(validationChecks[0].errors);
70
+ }
71
+ if (hgChecks.length > 0) {
72
+ setHgsError(checksToMessage(hgChecks));
73
+ }
74
+ if (remainingChecks.length > 0) {
75
+ showToast({
76
+ type: 'error',
77
+ message: checksToMessage(remainingChecks),
78
+ });
79
+ }
80
+ }
81
+ };
82
+
83
+ const onError = response => {
84
+ showToast({
85
+ type: 'error',
86
+ message: `Failed to create OVAL Policy: ${response.error}`,
87
+ });
88
+ actions.setSubmitting(false);
89
+ };
90
+
91
+ const hostgroupIds = assignedHgs.map(decodeModelId);
92
+ const variables = {
93
+ ...values,
94
+ ovalContentId: decodeId(values.ovalContentId),
95
+ period: 'custom',
96
+ hostgroupIds,
97
+ };
98
+ // eslint-disable-next-line promise/prefer-await-to-then
99
+ callMutation({ variables }).then(onCompleted, onError);
100
+ };
101
+
102
+ export const initialValues = {
103
+ name: '',
104
+ description: '',
105
+ ovalContentId: '',
106
+ cronLine: '',
107
+ };
@@ -0,0 +1,32 @@
1
+ import React from 'react';
2
+ import { useQuery } from '@apollo/client';
3
+ import { translate as __ } from 'foremanReact/common/I18n';
4
+ import IndexLayout from '../../../components/IndexLayout';
5
+
6
+ import ovalContentsQuery from '../../../graphql/queries/ovalContents.gql';
7
+ import NewOvalPolicyForm from './NewOvalPolicyForm';
8
+
9
+ const OvalPoliciesNew = props => {
10
+ const useFetchFn = () => useQuery(ovalContentsQuery);
11
+
12
+ const renameData = data => ({
13
+ ovalContents: data.ovalContents.nodes,
14
+ });
15
+
16
+ return (
17
+ <IndexLayout pageTitle={__('Create OVAL Policy')}>
18
+ <NewOvalPolicyForm
19
+ fetchFn={useFetchFn}
20
+ renameData={renameData}
21
+ resultPath="ovalContents.nodes"
22
+ emptyStateTitle={__('No OVAL Content found')}
23
+ emptyStateBody={__(
24
+ 'OVAL Content is required to create OVAL Policy. Please create one before proceeding.'
25
+ )}
26
+ {...props}
27
+ />
28
+ </IndexLayout>
29
+ );
30
+ };
31
+
32
+ export default OvalPoliciesNew;
@@ -0,0 +1,147 @@
1
+ import createOvalPolicy from '../../../../graphql/mutations/createOvalPolicy.gql';
2
+ import hostgroupsQuery from '../../../../graphql/queries/hostgroups.gql';
3
+
4
+ import { mockFactory, admin } from '../../../../testHelper';
5
+ import { decodeId } from '../../../../helpers/globalIdHelper';
6
+ import { ovalContents } from '../../../OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.fixtures';
7
+
8
+ export const newPolicyName = 'test policy';
9
+ export const newPolicyDescription = 'random description';
10
+ export const newPolicyCronline = '5 5 5 5 5';
11
+ export const newPolicyContentName = ovalContents.nodes[1].name;
12
+ export const newPolicyContentId = ovalContents.nodes[1].id;
13
+ const hostgroupId = 3;
14
+
15
+ const createPolicyMockFactory = mockFactory(
16
+ 'createOvalPolicy',
17
+ createOvalPolicy
18
+ );
19
+ const hostgroupsMockFactory = mockFactory('hostgroups', hostgroupsQuery);
20
+
21
+ const foremanAnsiblePresent = {
22
+ id: 'foreman_ansible_present',
23
+ errors: null,
24
+ failMsg: null,
25
+ result: 'pass',
26
+ };
27
+ const rolePresent = {
28
+ id: 'foreman_scap_client_role_present',
29
+ errors: null,
30
+ failMsg: null,
31
+ result: 'pass',
32
+ };
33
+ const roleVarsPresent = {
34
+ id: 'foreman_scap_client_vars_present',
35
+ errors: null,
36
+ failMsg: null,
37
+ result: 'pass',
38
+ };
39
+ const serverVarOverriden = {
40
+ id: 'foreman_scap_client_server_overriden',
41
+ errors: null,
42
+ failMsg: null,
43
+ result: 'pass',
44
+ };
45
+ const portVarOverriden = {
46
+ id: 'foreman_scap_client_port_overriden',
47
+ errors: null,
48
+ failMsg: null,
49
+ result: 'pass',
50
+ };
51
+ const policiesVarOverriden = {
52
+ id: 'foreman_scap_client_policies_overriden',
53
+ errors: null,
54
+ failMsg: null,
55
+ result: 'pass',
56
+ };
57
+ const policyErrors = {
58
+ id: 'oval_policy_errors',
59
+ errors: { name: 'has already been taken' },
60
+ failMsg: null,
61
+ result: 'fail',
62
+ };
63
+ export const hgWithoutProxy = {
64
+ id: 'hostgroups_without_proxy',
65
+ errors: null,
66
+ failMsg: 'Assign openscap_proxy to first hostgroup before proceeding.',
67
+ result: 'fail',
68
+ };
69
+ export const roleAbsent = {
70
+ id: 'foreman_scap_client_role_present',
71
+ errors: null,
72
+ failMsg:
73
+ 'theforeman.foreman_scap_client Ansible Role not found, please import it before running this action again.',
74
+ result: 'fail',
75
+ };
76
+
77
+ const varChecks = [
78
+ roleVarsPresent,
79
+ serverVarOverriden,
80
+ portVarOverriden,
81
+ policiesVarOverriden,
82
+ ];
83
+ const checkCollectionPass = [foremanAnsiblePresent, rolePresent, ...varChecks];
84
+ const checkCollectionPreconditionFail = [
85
+ foremanAnsiblePresent,
86
+ roleAbsent,
87
+ ...varChecks.map(check => ({ ...check, result: 'skip' })),
88
+ ];
89
+ const ovalPolicy = {
90
+ name: newPolicyName,
91
+ id: 'MDE6Rm9yZW1hbk9wZW5zY2FwOjpPdmFsUG9saWN5LTcw',
92
+ period: 'custom',
93
+ cronLine: newPolicyCronline,
94
+ hostgroups: {
95
+ nodes: [],
96
+ },
97
+ };
98
+
99
+ const policyCreateSuccess = {
100
+ checkCollection: checkCollectionPass,
101
+ ovalPolicy,
102
+ };
103
+
104
+ const baseVariables = {
105
+ name: newPolicyName,
106
+ description: '',
107
+ ovalContentId: decodeId(newPolicyContentId),
108
+ cronLine: newPolicyCronline,
109
+ hostgroupIds: [],
110
+ period: 'custom',
111
+ };
112
+
113
+ export const firstHg = {
114
+ id: 'MDE6SG9zdGdyb3VwLTM=',
115
+ name: 'first hostgroup',
116
+ };
117
+
118
+ const successVariables = {
119
+ ...baseVariables,
120
+ description: newPolicyDescription,
121
+ };
122
+
123
+ export const policySuccessMock = createPolicyMockFactory(
124
+ successVariables,
125
+ policyCreateSuccess
126
+ );
127
+
128
+ export const policyValidationMock = createPolicyMockFactory(baseVariables, {
129
+ checkCollection: [...checkCollectionPass, policyErrors],
130
+ ovalPolicy,
131
+ });
132
+
133
+ export const policyPreconditionMock = createPolicyMockFactory(baseVariables, {
134
+ checkCollection: checkCollectionPreconditionFail,
135
+ ovalPolicy,
136
+ });
137
+
138
+ export const policyInvalidHgMock = createPolicyMockFactory(
139
+ { ...baseVariables, hostgroupIds: [hostgroupId] },
140
+ { checkCollection: [...checkCollectionPass, hgWithoutProxy], ovalPolicy }
141
+ );
142
+
143
+ export const hostgroupsMock = hostgroupsMockFactory(
144
+ { search: `name ~ first` },
145
+ { totalCount: 2, nodes: [firstHg] },
146
+ { currentUser: admin }
147
+ );
@@ -0,0 +1,172 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+ import userEvent from '@testing-library/user-event';
5
+
6
+ import OvalPoliciesNew from '../';
7
+ import { ovalPoliciesPath } from '../../../../helpers/pathsHelper';
8
+
9
+ import { unpagedMocks as ovalContentMocks } from '../../../OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.fixtures';
10
+
11
+ import {
12
+ withMockedProvider,
13
+ wait,
14
+ withRouter,
15
+ withRedux,
16
+ } from '../../../../testHelper';
17
+
18
+ import {
19
+ newPolicyName,
20
+ newPolicyDescription,
21
+ newPolicyCronline,
22
+ newPolicyContentName,
23
+ policySuccessMock,
24
+ policyValidationMock,
25
+ policyPreconditionMock,
26
+ policyInvalidHgMock,
27
+ hostgroupsMock,
28
+ firstHg,
29
+ roleAbsent as roleAbsentCheck,
30
+ hgWithoutProxy as withoutProxyCheck,
31
+ } from './OvalPoliciesNew.fixtures';
32
+
33
+ import * as toasts from '../../../../helpers/toastsHelper';
34
+
35
+ const TestComponent = withRouter(
36
+ withRedux(withMockedProvider(OvalPoliciesNew))
37
+ );
38
+
39
+ describe('OvalPoliciesNew', () => {
40
+ it('should create new OVAL policy', async () => {
41
+ const showToast = jest.fn();
42
+ jest.spyOn(toasts, 'showToast').mockImplementation(() => showToast);
43
+ const pushMock = jest.fn();
44
+
45
+ render(
46
+ <TestComponent
47
+ mocks={ovalContentMocks.concat(policySuccessMock)}
48
+ history={{
49
+ push: pushMock,
50
+ }}
51
+ />
52
+ );
53
+ expect(screen.getByText('Loading')).toBeInTheDocument();
54
+ await wait();
55
+ const submitBtn = screen.getByRole('button', { name: 'submit' });
56
+ expect(submitBtn).toBeDisabled();
57
+ userEvent.type(screen.getByLabelText(/name/), newPolicyName);
58
+ await wait();
59
+ expect(submitBtn).toBeDisabled();
60
+ userEvent.type(screen.getByLabelText(/cronLine/), 'foo');
61
+ userEvent.type(screen.getByLabelText(/description/), newPolicyDescription);
62
+ userEvent.selectOptions(
63
+ screen.getByLabelText(/ovalContentId/),
64
+ newPolicyContentName
65
+ );
66
+ await wait();
67
+ expect(screen.getByText('is not a valid cronline')).toBeInTheDocument();
68
+ expect(submitBtn).toBeDisabled();
69
+ userEvent.clear(screen.getByLabelText(/cronLine/));
70
+ userEvent.type(screen.getByLabelText(/cronLine/), newPolicyCronline);
71
+ await wait();
72
+ expect(
73
+ screen.queryByText('is not a valid cronline')
74
+ ).not.toBeInTheDocument();
75
+ expect(submitBtn).not.toBeDisabled();
76
+ userEvent.click(submitBtn);
77
+ await wait(2);
78
+ expect(pushMock).toHaveBeenCalledWith(ovalPoliciesPath);
79
+ expect(showToast).toHaveBeenCalledWith({
80
+ type: 'success',
81
+ message: 'OVAL Policy succesfully created.',
82
+ });
83
+ });
84
+ it('should not create new policy on validation error', async () => {
85
+ const showToast = jest.fn();
86
+ jest.spyOn(toasts, 'showToast').mockImplementation(() => showToast);
87
+ const pushMock = jest.fn();
88
+
89
+ render(
90
+ <TestComponent
91
+ mocks={ovalContentMocks.concat(policyValidationMock)}
92
+ history={{
93
+ push: pushMock,
94
+ }}
95
+ />
96
+ );
97
+ await wait();
98
+ userEvent.type(screen.getByLabelText(/name/), newPolicyName);
99
+ userEvent.type(screen.getByLabelText(/cronLine/), newPolicyCronline);
100
+ userEvent.selectOptions(
101
+ screen.getByLabelText(/ovalContentId/),
102
+ newPolicyContentName
103
+ );
104
+ await wait();
105
+ userEvent.click(screen.getByRole('button', { name: 'submit' }));
106
+ await wait(2);
107
+ expect(pushMock).not.toHaveBeenCalled();
108
+ expect(showToast).not.toHaveBeenCalled();
109
+ expect(screen.getByText('has already been taken')).toBeInTheDocument();
110
+ });
111
+ it('should not create policy on preconditions error', async () => {
112
+ const showToast = jest.fn();
113
+ jest.spyOn(toasts, 'showToast').mockImplementation(() => showToast);
114
+ const pushMock = jest.fn();
115
+
116
+ render(
117
+ <TestComponent
118
+ mocks={ovalContentMocks.concat(policyPreconditionMock)}
119
+ history={{
120
+ push: pushMock,
121
+ }}
122
+ />
123
+ );
124
+ await wait();
125
+ userEvent.type(screen.getByLabelText(/name/), newPolicyName);
126
+ userEvent.type(screen.getByLabelText(/cronLine/), newPolicyCronline);
127
+ userEvent.selectOptions(
128
+ screen.getByLabelText(/ovalContentId/),
129
+ newPolicyContentName
130
+ );
131
+ await wait();
132
+ userEvent.click(screen.getByRole('button', { name: 'submit' }));
133
+ await wait(2);
134
+ await wait();
135
+ expect(pushMock).not.toHaveBeenCalled();
136
+ expect(showToast).toHaveBeenCalledWith({
137
+ type: 'error',
138
+ message: roleAbsentCheck.failMsg,
139
+ });
140
+ });
141
+ it('should show hostgroup errros', async () => {
142
+ const showToast = jest.fn();
143
+ jest.spyOn(toasts, 'showToast').mockImplementation(() => showToast);
144
+ const pushMock = jest.fn();
145
+
146
+ render(
147
+ <TestComponent
148
+ mocks={ovalContentMocks
149
+ .concat(policyInvalidHgMock)
150
+ .concat(hostgroupsMock)}
151
+ history={{
152
+ push: pushMock,
153
+ }}
154
+ />
155
+ );
156
+ await wait();
157
+ userEvent.type(screen.getByLabelText(/name/), newPolicyName);
158
+ userEvent.type(screen.getByLabelText(/cronLine/), newPolicyCronline);
159
+ userEvent.selectOptions(
160
+ screen.getByLabelText(/ovalContentId/),
161
+ newPolicyContentName
162
+ );
163
+ userEvent.type(screen.getByLabelText(/hostgroup/), 'first');
164
+ await wait(500);
165
+ userEvent.click(screen.getByText(firstHg.name));
166
+ await wait();
167
+ userEvent.click(screen.getByRole('button', { name: 'submit' }));
168
+ await wait(2);
169
+ expect(pushMock).not.toHaveBeenCalled();
170
+ expect(screen.getByText(withoutProxyCheck.failMsg)).toBeInTheDocument();
171
+ });
172
+ });
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import { useDispatch } from 'react-redux';
3
+
4
+ import { showToast } from '../../../helpers/toastsHelper';
5
+ import OvalPoliciesNew from './OvalPoliciesNew';
6
+
7
+ const WrappedOvalPoliciesNew = props => (
8
+ <OvalPoliciesNew {...props} showToast={showToast(useDispatch())} />
9
+ );
10
+
11
+ export default WrappedOvalPoliciesNew;
@@ -4,7 +4,7 @@ import { translate as __ } from 'foremanReact/common/I18n';
4
4
 
5
5
  import { linkCell } from '../../../helpers/tableHelper';
6
6
  import { hostsPath } from '../../../helpers/pathsHelper';
7
- import { decodeId } from '../../../helpers/globalIdHelper';
7
+ import { decodeModelId } from '../../../helpers/globalIdHelper';
8
8
  import { addSearch } from '../../../helpers/pageParamsHelper';
9
9
 
10
10
  import withLoading from '../../../components/withLoading';
@@ -25,7 +25,7 @@ const CvesTable = props => {
25
25
 
26
26
  const hostCount = cve =>
27
27
  linkCell(
28
- addSearch(hostsPath, { search: `cve_id = ${decodeId(cve)}` }),
28
+ addSearch(hostsPath, { search: `cve_id = ${decodeModelId(cve)}` }),
29
29
  cve.hosts.nodes.length
30
30
  );
31
31
 
@@ -40,6 +40,7 @@ const DetailsTab = props => {
40
40
  onConfirm={onAttrUpdate('name', policy, callMutation, showToast)}
41
41
  component={TextInput}
42
42
  attrName="name"
43
+ allowed={policy.meta.canEdit}
43
44
  />
44
45
  </TextListItem>
45
46
  <TextListItem component={TextListItemVariants.dt}>
@@ -70,6 +71,7 @@ const DetailsTab = props => {
70
71
  )}
71
72
  component={TextArea}
72
73
  attrName="description"
74
+ allowed={policy.meta.canEdit}
73
75
  />
74
76
  </TextListItem>
75
77
  </TextList>
@@ -1,6 +1,5 @@
1
1
  import { translate as __, sprintf } from 'foremanReact/common/I18n';
2
-
3
- import { decodeId } from '../../../helpers/globalIdHelper';
2
+ import { decodeModelId } from '../../../helpers/globalIdHelper';
4
3
  import { addSearch } from '../../../helpers/pageParamsHelper';
5
4
  import { newJobPath } from '../../../helpers/pathsHelper';
6
5
 
@@ -19,7 +18,9 @@ export const policySchedule = policy => {
19
18
 
20
19
  const targetingScopedSearchQuery = policy => {
21
20
  const hgIds = policy.hostgroups.nodes.reduce((memo, hg) => {
22
- const ids = [decodeId(hg)].concat(hg.descendants.nodes.map(decodeId));
21
+ const ids = [decodeModelId(hg)].concat(
22
+ hg.descendants.nodes.map(decodeModelId)
23
+ );
23
24
  return ids.reduce(
24
25
  (acc, id) => (acc.includes(id) ? acc : [...acc, id]),
25
26
  memo
@@ -9,6 +9,7 @@ import {
9
9
  historyMock,
10
10
  ovalPolicyId,
11
11
  policyDetailMock,
12
+ policyEditPermissionsMock,
12
13
  ovalPolicy,
13
14
  } from './OvalPoliciesShow.fixtures';
14
15
  import {
@@ -171,5 +172,31 @@ describe('OvalPoliciesShow', () => {
171
172
  ).not.toBeInTheDocument();
172
173
  expect(screen.getByText(ovalPolicy.name)).toBeInTheDocument();
173
174
  expect(screen.getByText('has already been taken')).toBeInTheDocument();
175
+ userEvent.click(
176
+ screen.getByRole('button', { name: 'cancel editing name' })
177
+ );
178
+ userEvent.click(editBtn);
179
+ expect(
180
+ screen.queryByText('has already been taken')
181
+ ).not.toBeInTheDocument();
182
+ });
183
+ it('should not show edit btns when user is not allowed to edit', async () => {
184
+ render(
185
+ <TestComponent
186
+ history={historyMock}
187
+ match={{
188
+ params: { id: ovalPolicyId, tab: 'details' },
189
+ path: ovalPoliciesShowPath,
190
+ }}
191
+ mocks={policyEditPermissionsMock}
192
+ />
193
+ );
194
+ await waitFor(tick);
195
+ expect(
196
+ screen.queryByRole('button', { name: 'edit name' })
197
+ ).not.toBeInTheDocument();
198
+ expect(
199
+ screen.queryByRole('button', { name: 'edit description' })
200
+ ).not.toBeInTheDocument();
174
201
  });
175
202
  });