foreman_openscap 5.0.0 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/app/graphql/mutations/oval_contents/delete.rb +9 -0
  3. data/app/graphql/mutations/oval_policies/delete.rb +9 -0
  4. data/app/graphql/mutations/oval_policies/update.rb +15 -0
  5. data/app/graphql/types/oval_check.rb +11 -0
  6. data/app/graphql/types/oval_content.rb +2 -0
  7. data/app/graphql/types/oval_policy.rb +3 -0
  8. data/app/models/concerns/foreman_openscap/host_extensions.rb +0 -6
  9. data/app/models/concerns/foreman_openscap/oval_facet_hostgroup_extensions.rb +15 -0
  10. data/app/models/foreman_openscap/oval_content.rb +2 -0
  11. data/app/services/foreman_openscap/oval/configure.rb +1 -1
  12. data/app/services/foreman_openscap/oval/setup.rb +5 -5
  13. data/app/services/foreman_openscap/oval/setup_check.rb +5 -2
  14. data/db/migrate/20210819143316_drop_unused_tables.rb +6 -0
  15. data/lib/foreman_openscap/engine.rb +6 -1
  16. data/lib/foreman_openscap/version.rb +1 -1
  17. data/package.json +3 -6
  18. data/test/graphql/mutations/oval_policies/delete_mutation_test.rb +63 -0
  19. data/test/graphql/queries/oval_content_query_test.rb +29 -0
  20. data/test/unit/services/hostgroup_overrider_test.rb +1 -1
  21. data/test/unit/services/oval/setup_check_test.rb +37 -0
  22. data/webpack/components/ConfirmModal.js +63 -0
  23. data/webpack/components/ConfirmModal.scss +3 -0
  24. data/webpack/components/EditableInput.js +157 -0
  25. data/webpack/components/EditableInput.scss +3 -0
  26. data/webpack/components/EmptyState.js +4 -1
  27. data/webpack/components/IndexLayout.js +11 -4
  28. data/webpack/components/IndexTable/index.js +17 -17
  29. data/webpack/components/LinkButton.js +26 -0
  30. data/webpack/components/withDeleteModal.js +51 -0
  31. data/webpack/components/withLoading.js +21 -3
  32. data/webpack/graphql/mutations/deleteOvalContent.gql +9 -0
  33. data/webpack/graphql/mutations/deleteOvalPolicy.gql +9 -0
  34. data/webpack/graphql/mutations/updateOvalPolicy.gql +14 -0
  35. data/webpack/graphql/queries/hostgroups.gql +14 -0
  36. data/webpack/graphql/queries/ovalContent.gql +8 -0
  37. data/webpack/graphql/queries/ovalContents.gql +3 -0
  38. data/webpack/graphql/queries/ovalPolicies.gql +3 -0
  39. data/webpack/helpers/formFieldsHelper.js +63 -0
  40. data/webpack/helpers/mutationHelper.js +68 -0
  41. data/webpack/helpers/pathsHelper.js +5 -0
  42. data/webpack/helpers/toastHelper.js +3 -0
  43. data/webpack/routes/OvalContents/OvalContentsIndex/OvalContentsIndex.js +25 -0
  44. data/webpack/routes/OvalContents/OvalContentsIndex/OvalContentsTable.js +41 -4
  45. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsDestroy.fixtures.js +105 -0
  46. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsDestroy.test.js +124 -0
  47. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.fixtures.js +61 -59
  48. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.test.js +29 -8
  49. data/webpack/routes/OvalContents/OvalContentsIndex/index.js +7 -1
  50. data/webpack/routes/OvalContents/OvalContentsNew/OvalContentsNew.js +138 -0
  51. data/webpack/routes/OvalContents/OvalContentsNew/OvalContentsNew.scss +3 -0
  52. data/webpack/routes/OvalContents/OvalContentsNew/OvalContentsNewHelper.js +73 -0
  53. data/webpack/routes/OvalContents/OvalContentsNew/__tests__/OvalContentsNew.test.js +104 -0
  54. data/webpack/routes/OvalContents/OvalContentsNew/index.js +13 -0
  55. data/webpack/routes/OvalContents/OvalContentsShow/OvalContentsShow.js +62 -0
  56. data/webpack/routes/OvalContents/OvalContentsShow/OvalContentsShow.test.js +45 -0
  57. data/webpack/routes/OvalContents/OvalContentsShow/OvalContentsShowHelper.js +0 -0
  58. data/webpack/routes/OvalContents/OvalContentsShow/index.js +35 -0
  59. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesIndex.js +17 -2
  60. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesTable.js +16 -3
  61. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesDestroy.fixtures.js +101 -0
  62. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesDestroy.test.js +117 -0
  63. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesIndex.fixtures.js +57 -41
  64. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesIndex.test.js +14 -2
  65. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/index.js +7 -1
  66. data/webpack/routes/OvalPolicies/OvalPoliciesShow/DetailsTab.js +85 -0
  67. data/webpack/routes/OvalPolicies/OvalPoliciesShow/HostgroupsTab.js +49 -0
  68. data/webpack/routes/OvalPolicies/OvalPoliciesShow/HostgroupsTable.js +38 -0
  69. data/webpack/routes/OvalPolicies/OvalPoliciesShow/OvalPoliciesShow.js +15 -11
  70. data/webpack/routes/OvalPolicies/OvalPoliciesShow/OvalPoliciesShowHelper.js +77 -0
  71. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesEdit.fixtures.js +48 -0
  72. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesEdit.test.js +175 -0
  73. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.fixtures.js +28 -1
  74. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.test.js +47 -4
  75. data/webpack/routes/OvalPolicies/OvalPoliciesShow/index.js +3 -0
  76. data/webpack/routes/routes.js +14 -0
  77. data/webpack/testHelper.js +9 -1
  78. metadata +46 -3
@@ -19,42 +19,6 @@ export const pageParamsHistoryMock = {
19
19
  push: pushMock,
20
20
  };
21
21
 
22
- const policiesMocks = {
23
- totalCount: 2,
24
- nodes: [
25
- {
26
- __typename: 'ForemanOpenscap::OvalPolicy',
27
- id: 'abc',
28
- name: 'first policy',
29
- ovalContent: { name: 'first content' },
30
- },
31
- {
32
- __typename: 'ForemanOpenscap::OvalPolicy',
33
- id: 'xyz',
34
- name: 'second policy',
35
- ovalContent: { name: 'second content' },
36
- },
37
- ],
38
- };
39
-
40
- const pagedPoliciesMocks = {
41
- totalCount: 7,
42
- nodes: [
43
- {
44
- __typename: 'ForemanOpenscap::OvalPolicy',
45
- id: 'xyz',
46
- name: 'sixth policy',
47
- ovalContent: { name: 'sixth content' },
48
- },
49
- {
50
- __typename: 'ForemanOpenscap::OvalPolicy',
51
- id: 'abc',
52
- name: 'seventh policy',
53
- ovalContent: { name: 'seventh content' },
54
- },
55
- ],
56
- };
57
-
58
22
  const viewer = userFactory('viewer', [
59
23
  {
60
24
  __typename: 'Permission',
@@ -63,16 +27,54 @@ const viewer = userFactory('viewer', [
63
27
  },
64
28
  ]);
65
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
+
66
49
  export const mocks = policiesMockFactory(
67
50
  { first: 20, last: 20 },
68
- policiesMocks,
51
+ policiesData,
69
52
  { currentUser: admin }
70
53
  );
71
54
  export const pageParamsMocks = policiesMockFactory(
72
55
  { first: 10, last: 5 },
73
- pagedPoliciesMocks,
56
+ {
57
+ totalCount: 7,
58
+ nodes: [
59
+ {
60
+ __typename: 'ForemanOpenscap::OvalPolicy',
61
+ id: 'MDE6Rm9yZW1hbk9wZW5zY2FwOjpPdmFsUG9saWN5LTQx',
62
+ name: 'sixth policy',
63
+ meta: { canDestroy: true },
64
+ ovalContent: { name: 'sixth content' },
65
+ },
66
+ {
67
+ __typename: 'ForemanOpenscap::OvalPolicy',
68
+ id: 'MDE6Rm9yZW1hbk9wZW5zY2FwOjpPdmFsUG9saWN5LTQy',
69
+ name: 'seventh policy',
70
+ meta: { canDestroy: true },
71
+ ovalContent: { name: 'seventh content' },
72
+ },
73
+ ],
74
+ },
74
75
  { currentUser: admin }
75
76
  );
77
+
76
78
  export const emptyMocks = policiesMockFactory(
77
79
  { first: 20, last: 20 },
78
80
  { totalCount: 0, nodes: [] },
@@ -81,15 +83,29 @@ export const emptyMocks = policiesMockFactory(
81
83
  export const errorMocks = policiesMockFactory(
82
84
  { first: 20, last: 20 },
83
85
  { totalCount: 0, nodes: [] },
84
- { errors: [{ message: 'Something very bad happened.' }], currentUser: admin }
86
+ {
87
+ errors: [{ message: 'Something very bad happened.', path: 'base' }],
88
+ currentUser: admin,
89
+ }
85
90
  );
86
91
  export const viewerMocks = policiesMockFactory(
87
92
  { first: 20, last: 20 },
88
- policiesMocks,
93
+ policiesData,
89
94
  { currentUser: viewer }
90
95
  );
91
96
  export const unauthorizedMocks = policiesMockFactory(
92
97
  { first: 20, last: 20 },
93
- policiesMocks,
98
+ policiesData,
94
99
  { currentUser: intruder }
95
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 }
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';
@@ -22,10 +23,12 @@ import {
22
23
  unauthorizedMocks,
23
24
  } from './OvalPoliciesIndex.fixtures';
24
25
 
25
- import OvalPoliciesIndex from '../OvalPoliciesIndex';
26
+ import OvalPoliciesIndex from '../index';
26
27
  import { ovalPoliciesPath } from '../../../../helpers/pathsHelper';
27
28
 
28
- const TestComponent = withRouter(withMockedProvider(OvalPoliciesIndex));
29
+ const TestComponent = withRouter(
30
+ withRedux(withMockedProvider(OvalPoliciesIndex))
31
+ );
29
32
 
30
33
  describe('OvalPoliciesIndex', () => {
31
34
  it('should load page', async () => {
@@ -42,6 +45,15 @@ describe('OvalPoliciesIndex', () => {
42
45
  expect(within(pageItems).getByText(/1 - 2/)).toBeInTheDocument();
43
46
  expect(within(pageItems).getByText('of')).toBeInTheDocument();
44
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
+ );
45
57
  });
46
58
  it('should load page with page params', async () => {
47
59
  const { container } = render(
@@ -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 => <OvalPoliciesIndex {...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,85 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { useMutation } from '@apollo/client';
4
+
5
+ import {
6
+ TextList,
7
+ TextContent,
8
+ TextArea,
9
+ TextListItem,
10
+ TextListVariants,
11
+ TextListItemVariants,
12
+ TextInput,
13
+ } from '@patternfly/react-core';
14
+
15
+ import { translate as __ } from 'foremanReact/common/I18n';
16
+
17
+ import EditableInput from '../../../components/EditableInput';
18
+
19
+ import { onAttrUpdate, policySchedule } from './OvalPoliciesShowHelper';
20
+ import updateOvalPolicyMutation from '../../../graphql/mutations/updateOvalPolicy.gql';
21
+
22
+ const DetailsTab = props => {
23
+ const { policy, showToast } = props;
24
+
25
+ const [callMutation] = useMutation(updateOvalPolicyMutation);
26
+
27
+ return (
28
+ <TextContent className="pf-u-pt-md">
29
+ <TextList component={TextListVariants.dl}>
30
+ <TextListItem component={TextListItemVariants.dt}>
31
+ {__('Name')}
32
+ </TextListItem>
33
+ <TextListItem
34
+ aria-label="label text value"
35
+ component={TextListItemVariants.dd}
36
+ className="foreman-spaced-list"
37
+ >
38
+ <EditableInput
39
+ value={policy.name}
40
+ onConfirm={onAttrUpdate('name', policy, callMutation, showToast)}
41
+ component={TextInput}
42
+ attrName="name"
43
+ />
44
+ </TextListItem>
45
+ <TextListItem component={TextListItemVariants.dt}>
46
+ {__('Period')}
47
+ </TextListItem>
48
+ <TextListItem
49
+ aria-label="label text value"
50
+ component={TextListItemVariants.dd}
51
+ className="foreman-spaced-list"
52
+ >
53
+ {policySchedule(policy)}
54
+ </TextListItem>
55
+ <TextListItem component={TextListItemVariants.dt}>
56
+ {__('Description')}
57
+ </TextListItem>
58
+ <TextListItem
59
+ aria-label="label text value"
60
+ component={TextListItemVariants.dd}
61
+ className="foreman-spaced-list"
62
+ >
63
+ <EditableInput
64
+ value={policy.description}
65
+ onConfirm={onAttrUpdate(
66
+ 'description',
67
+ policy,
68
+ callMutation,
69
+ showToast
70
+ )}
71
+ component={TextArea}
72
+ attrName="description"
73
+ />
74
+ </TextListItem>
75
+ </TextList>
76
+ </TextContent>
77
+ );
78
+ };
79
+
80
+ DetailsTab.propTypes = {
81
+ policy: PropTypes.object.isRequired,
82
+ showToast: PropTypes.func.isRequired,
83
+ };
84
+
85
+ export default DetailsTab;
@@ -0,0 +1,49 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { translate as __ } from 'foremanReact/common/I18n';
4
+
5
+ import { useQuery } from '@apollo/client';
6
+
7
+ import HostgroupsTable from './HostgroupsTable';
8
+
9
+ import hostgroups from '../../../graphql/queries/hostgroups.gql';
10
+ import {
11
+ useParamsToVars,
12
+ useCurrentPagination,
13
+ } from '../../../helpers/pageParamsHelper';
14
+
15
+ const HostgroupsTab = props => {
16
+ const useFetchFn = componentProps =>
17
+ useQuery(hostgroups, {
18
+ variables: {
19
+ search: `oval_policy_id = ${componentProps.match.params.id}`,
20
+ ...useParamsToVars(componentProps.history),
21
+ },
22
+ });
23
+
24
+ const renameData = data => ({
25
+ hostgroups: data.hostgroups.nodes,
26
+ totalCount: data.hostgroups.totalCount,
27
+ });
28
+
29
+ const pagination = useCurrentPagination(props.history);
30
+
31
+ return (
32
+ <HostgroupsTable
33
+ {...props}
34
+ fetchFn={useFetchFn}
35
+ renameData={renameData}
36
+ resultPath="hostgroups.nodes"
37
+ pagination={pagination}
38
+ emptyStateTitle={__('No Hostgroups found.')}
39
+ permissions={['view_hostgroups']}
40
+ />
41
+ );
42
+ };
43
+
44
+ HostgroupsTab.propTypes = {
45
+ match: PropTypes.object.isRequired,
46
+ history: PropTypes.object.isRequired,
47
+ };
48
+
49
+ export default HostgroupsTab;
@@ -0,0 +1,38 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { translate as __ } from 'foremanReact/common/I18n';
4
+
5
+ import withLoading from '../../../components/withLoading';
6
+ import IndexTable from '../../../components/IndexTable';
7
+
8
+ const CvesTable = props => {
9
+ const columns = [{ title: __('Name') }];
10
+
11
+ const rows = props.hostgroups.map(hostgroup => ({
12
+ cells: [{ title: hostgroup.name }],
13
+ hostgroup,
14
+ }));
15
+
16
+ const actions = [];
17
+
18
+ return (
19
+ <IndexTable
20
+ columns={columns}
21
+ rows={rows}
22
+ actions={actions}
23
+ pagination={props.pagination}
24
+ totalCount={props.totalCount}
25
+ history={props.history}
26
+ ariaTableLabel={__('Table of hostgroups for OVAL policy')}
27
+ />
28
+ );
29
+ };
30
+
31
+ CvesTable.propTypes = {
32
+ hostgroups: PropTypes.array.isRequired,
33
+ pagination: PropTypes.object.isRequired,
34
+ totalCount: PropTypes.number.isRequired,
35
+ history: PropTypes.object.isRequired,
36
+ };
37
+
38
+ export default withLoading(CvesTable);
@@ -7,7 +7,6 @@ import {
7
7
  Button,
8
8
  Grid,
9
9
  GridItem,
10
- TextContent,
11
10
  Text,
12
11
  TextVariants,
13
12
  Tabs,
@@ -16,10 +15,11 @@ import {
16
15
  } from '@patternfly/react-core';
17
16
 
18
17
  import withLoading from '../../../components/withLoading';
19
-
20
18
  import CvesTab from './CvesTab';
19
+ import HostgroupsTab from './HostgroupsTab';
20
+ import DetailsTab from './DetailsTab';
21
21
 
22
- import { policySchedule, newJobFormPath } from './OvalPoliciesShowHelper';
22
+ import { newJobFormPath } from './OvalPoliciesShowHelper';
23
23
  import { resolvePath } from '../../../helpers/pathsHelper';
24
24
 
25
25
  const OvalPoliciesShow = props => {
@@ -50,18 +50,22 @@ const OvalPoliciesShow = props => {
50
50
  <Tabs mountOnEnter activeKey={activeTab} onSelect={handleTabSelect}>
51
51
  <Tab
52
52
  eventKey="details"
53
- title={<TabTitleText>Details</TabTitleText>}
53
+ title={<TabTitleText>{__('Details')}</TabTitleText>}
54
54
  >
55
- <TextContent className="pf-u-pt-md">
56
- <Text component={TextVariants.h3}>Period</Text>
57
- <Text component={TextVariants.p}>{policySchedule(policy)}</Text>
58
- <Text component={TextVariants.h3}>Description</Text>
59
- <Text component={TextVariants.p}>{policy.description}</Text>
60
- </TextContent>
55
+ <DetailsTab {...props} />
61
56
  </Tab>
62
- <Tab eventKey="cves" title={<TabTitleText>CVEs</TabTitleText>}>
57
+ <Tab
58
+ eventKey="cves"
59
+ title={<TabTitleText>{__('CVEs')}</TabTitleText>}
60
+ >
63
61
  <CvesTab {...props} />
64
62
  </Tab>
63
+ <Tab
64
+ eventKey="hostgroups"
65
+ title={<TabTitleText>{__('Hostgroups')}</TabTitleText>}
66
+ >
67
+ <HostgroupsTab {...props} />
68
+ </Tab>
65
69
  </Tabs>
66
70
  </GridItem>
67
71
  </Grid>
@@ -1,3 +1,5 @@
1
+ import { translate as __, sprintf } from 'foremanReact/common/I18n';
2
+
1
3
  import { decodeId } from '../../../helpers/globalIdHelper';
2
4
  import { addSearch } from '../../../helpers/pageParamsHelper';
3
5
  import { newJobPath } from '../../../helpers/pathsHelper';
@@ -37,3 +39,78 @@ export const newJobFormPath = (policy, policyId) =>
37
39
  host_ids: targetingScopedSearchQuery(policy),
38
40
  'inputs[oval_policies]': policyId,
39
41
  });
42
+
43
+ const policyToAttrs = (policy, attrs) =>
44
+ Object.entries(policy).reduce((memo, [key, value]) => {
45
+ if (attrs.includes(key)) {
46
+ memo[key] = value;
47
+ }
48
+ return memo;
49
+ }, {});
50
+
51
+ const onUpdateSuccess = (
52
+ closeEditable,
53
+ stopSubmitting,
54
+ showToast,
55
+ attr,
56
+ onValidationError
57
+ ) => result => {
58
+ const { errors } = result.data.updateOvalPolicy;
59
+ if (Array.isArray(errors) && errors.length > 0) {
60
+ stopSubmitting();
61
+ if (
62
+ errors.length === 1 &&
63
+ errors[0].path.join(' ') === `attributes ${attr}`
64
+ ) {
65
+ onValidationError(errors[0].message);
66
+ } else {
67
+ showToast({
68
+ type: 'error',
69
+ message: formatError(joinErrors(errors)),
70
+ });
71
+ }
72
+ } else {
73
+ closeEditable();
74
+ showToast({
75
+ type: 'success',
76
+ message: __('OVAL policy was successfully updated.'),
77
+ });
78
+ }
79
+ };
80
+
81
+ const formatError = error =>
82
+ sprintf(
83
+ __('There was a following error when updating OVAL policy: %s'),
84
+ error
85
+ );
86
+
87
+ const joinErrors = errors => errors.map(err => err.message).join(', ');
88
+
89
+ const onUpdateError = (showToast, stopSubmitting) => error => {
90
+ stopSubmitting();
91
+ showToast({ type: 'error', message: formatError(error.message) });
92
+ };
93
+
94
+ export const onAttrUpdate = (attr, policy, callMutation, showToast) => (
95
+ newValue,
96
+ closeEditable,
97
+ stopSubmitting,
98
+ onValidationError
99
+ ) => {
100
+ const vars = policyToAttrs(policy, ['id', 'name', 'description', 'cronLine']);
101
+ vars[attr] = newValue;
102
+ return (
103
+ callMutation({ variables: vars })
104
+ // eslint-disable-next-line promise/prefer-await-to-then
105
+ .then(
106
+ onUpdateSuccess(
107
+ closeEditable,
108
+ stopSubmitting,
109
+ showToast,
110
+ attr,
111
+ onValidationError
112
+ )
113
+ )
114
+ .catch(onUpdateError(showToast, stopSubmitting))
115
+ );
116
+ };
@@ -0,0 +1,48 @@
1
+ import { mockFactory } from '../../../../testHelper';
2
+ import updateOvalPolicyMutation from '../../../../graphql/mutations/updateOvalPolicy.gql';
3
+ import { ovalPolicy } from './OvalPoliciesShow.fixtures';
4
+
5
+ const updateOvalPolicyMockFactory = mockFactory(
6
+ 'updateOvalPolicy',
7
+ updateOvalPolicyMutation
8
+ );
9
+
10
+ export const updatedName = 'updated policy name';
11
+
12
+ const variables = {
13
+ id: ovalPolicy.id,
14
+ name: updatedName,
15
+ cronLine: ovalPolicy.cronLine,
16
+ description: ovalPolicy.description,
17
+ };
18
+ const responsePolicy = {
19
+ ovalPolicy: {
20
+ __typename: 'ForemanOpenscap::OvalPolicy',
21
+ id: ovalPolicy.id,
22
+ name: updatedName,
23
+ description: ovalPolicy.description,
24
+ cronLine: ovalPolicy.cronLine,
25
+ },
26
+ errors: [],
27
+ };
28
+
29
+ export const policyUpdateMock = updateOvalPolicyMockFactory(
30
+ variables,
31
+ responsePolicy
32
+ );
33
+
34
+ export const policyUpdateErrorMock = updateOvalPolicyMockFactory(
35
+ variables,
36
+ responsePolicy,
37
+ { errors: [{ message: 'This is an unexpected failure.' }] }
38
+ );
39
+
40
+ export const policyUpdateValidationMock = updateOvalPolicyMockFactory(
41
+ variables,
42
+ {
43
+ ovalPolicy,
44
+ errors: [
45
+ { path: ['attributes', 'name'], message: 'has already been taken' },
46
+ ],
47
+ }
48
+ );