foreman_openscap 5.1.0 → 5.1.1

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.
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
  });