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.
- checksums.yaml +4 -4
- data/app/graphql/mutations/oval_contents/delete.rb +9 -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/models/concerns/foreman_openscap/host_extensions.rb +0 -6
- data/app/models/concerns/foreman_openscap/oval_facet_hostgroup_extensions.rb +15 -0
- data/app/models/foreman_openscap/oval_content.rb +2 -0
- data/app/services/foreman_openscap/oval/configure.rb +1 -1
- data/app/services/foreman_openscap/oval/setup.rb +5 -5
- data/app/services/foreman_openscap/oval/setup_check.rb +5 -2
- data/db/migrate/20210819143316_drop_unused_tables.rb +6 -0
- data/lib/foreman_openscap/engine.rb +6 -1
- data/lib/foreman_openscap/version.rb +1 -1
- data/package.json +3 -6
- 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/unit/services/hostgroup_overrider_test.rb +1 -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 +157 -0
- data/webpack/components/EditableInput.scss +3 -0
- data/webpack/components/EmptyState.js +4 -1
- data/webpack/components/IndexLayout.js +11 -4
- data/webpack/components/IndexTable/index.js +17 -17
- data/webpack/components/LinkButton.js +26 -0
- data/webpack/components/withDeleteModal.js +51 -0
- data/webpack/components/withLoading.js +21 -3
- 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/hostgroups.gql +14 -0
- data/webpack/graphql/queries/ovalContent.gql +8 -0
- data/webpack/graphql/queries/ovalContents.gql +3 -0
- data/webpack/graphql/queries/ovalPolicies.gql +3 -0
- data/webpack/helpers/formFieldsHelper.js +63 -0
- data/webpack/helpers/mutationHelper.js +68 -0
- data/webpack/helpers/pathsHelper.js +5 -0
- data/webpack/helpers/toastHelper.js +3 -0
- data/webpack/routes/OvalContents/OvalContentsIndex/OvalContentsIndex.js +25 -0
- data/webpack/routes/OvalContents/OvalContentsIndex/OvalContentsTable.js +41 -4
- 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 +61 -59
- data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.test.js +29 -8
- data/webpack/routes/OvalContents/OvalContentsIndex/index.js +7 -1
- data/webpack/routes/OvalContents/OvalContentsNew/OvalContentsNew.js +138 -0
- data/webpack/routes/OvalContents/OvalContentsNew/OvalContentsNew.scss +3 -0
- data/webpack/routes/OvalContents/OvalContentsNew/OvalContentsNewHelper.js +73 -0
- data/webpack/routes/OvalContents/OvalContentsNew/__tests__/OvalContentsNew.test.js +104 -0
- data/webpack/routes/OvalContents/OvalContentsNew/index.js +13 -0
- data/webpack/routes/OvalContents/OvalContentsShow/OvalContentsShow.js +62 -0
- data/webpack/routes/OvalContents/OvalContentsShow/OvalContentsShow.test.js +45 -0
- data/webpack/routes/OvalContents/OvalContentsShow/OvalContentsShowHelper.js +0 -0
- data/webpack/routes/OvalContents/OvalContentsShow/index.js +35 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesIndex.js +17 -2
- data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesTable.js +16 -3
- 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 +57 -41
- data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesIndex.test.js +14 -2
- data/webpack/routes/OvalPolicies/OvalPoliciesIndex/index.js +7 -1
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/DetailsTab.js +85 -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 +77 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesEdit.fixtures.js +48 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesEdit.test.js +175 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.fixtures.js +28 -1
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.test.js +47 -4
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/index.js +3 -0
- data/webpack/routes/routes.js +14 -0
- data/webpack/testHelper.js +9 -1
- 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
|
-
|
51
|
+
policiesData,
|
69
52
|
{ currentUser: admin }
|
70
53
|
);
|
71
54
|
export const pageParamsMocks = policiesMockFactory(
|
72
55
|
{ first: 10, last: 5 },
|
73
|
-
|
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
|
-
{
|
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
|
-
|
93
|
+
policiesData,
|
89
94
|
{ currentUser: viewer }
|
90
95
|
);
|
91
96
|
export const unauthorizedMocks = policiesMockFactory(
|
92
97
|
{ first: 20, last: 20 },
|
93
|
-
|
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 '../
|
26
|
+
import OvalPoliciesIndex from '../index';
|
26
27
|
import { ovalPoliciesPath } from '../../../../helpers/pathsHelper';
|
27
28
|
|
28
|
-
const TestComponent = withRouter(
|
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 =>
|
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 {
|
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
|
-
<
|
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
|
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
|
+
);
|