foreman_openscap 4.3.2 → 5.2.0

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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/compliance/arf_reports_controller.rb +0 -6
  3. data/app/controllers/api/v2/compliance/oval_policies_controller.rb +1 -1
  4. data/app/graphql/mutations/oval_contents/delete.rb +9 -0
  5. data/app/graphql/mutations/oval_policies/create.rb +33 -0
  6. data/app/graphql/mutations/oval_policies/delete.rb +9 -0
  7. data/app/graphql/mutations/oval_policies/update.rb +15 -0
  8. data/app/graphql/types/oval_check.rb +11 -0
  9. data/app/graphql/types/oval_content.rb +2 -0
  10. data/app/graphql/types/oval_policy.rb +3 -0
  11. data/app/helpers/arf_report_dashboard_helper.rb +2 -4
  12. data/app/helpers/compliance_hosts_helper.rb +1 -1
  13. data/app/helpers/policies_helper.rb +2 -2
  14. data/app/models/concerns/foreman_openscap/data_stream_content.rb +1 -1
  15. data/app/models/concerns/foreman_openscap/host_extensions.rb +0 -6
  16. data/app/models/concerns/foreman_openscap/oval_facet_hostgroup_extensions.rb +16 -0
  17. data/app/models/foreman_openscap/arf_report.rb +1 -1
  18. data/app/models/foreman_openscap/oval_content.rb +2 -0
  19. data/app/services/foreman_openscap/client_config/base.rb +1 -0
  20. data/app/services/foreman_openscap/client_config/puppet.rb +6 -2
  21. data/app/services/foreman_openscap/oval/configure.rb +16 -13
  22. data/app/services/foreman_openscap/oval/setup.rb +5 -5
  23. data/app/services/foreman_openscap/oval/setup_check.rb +5 -2
  24. data/app/views/api/v2/compliance/oval_contents/destroy.json.rabl +3 -0
  25. data/app/views/arf_reports/_metrics.html.erb +4 -4
  26. data/app/views/compliance_hosts/show.html.erb +4 -6
  27. data/app/views/dashboard/_compliance_reports_breakdown_widget.html.erb +4 -3
  28. data/app/views/policy_dashboard/_policy_chart_widget.html.erb +3 -2
  29. data/db/migrate/20200117135424_migrate_port_overrides_to_int.rb +2 -1
  30. data/db/migrate/20201202110213_update_puppet_port_param_type.rb +2 -1
  31. data/db/migrate/20210819143316_drop_unused_tables.rb +6 -0
  32. data/lib/foreman_openscap/engine.rb +8 -9
  33. data/lib/foreman_openscap/version.rb +1 -1
  34. data/package.json +3 -6
  35. data/test/functional/api/v2/compliance/oval_reports_controller_test.rb +1 -1
  36. data/test/functional/api/v2/compliance/policies_controller_test.rb +2 -0
  37. data/test/graphql/mutations/oval_policies/delete_mutation_test.rb +63 -0
  38. data/test/graphql/queries/oval_content_query_test.rb +29 -0
  39. data/test/helpers/arf_report_dashboard_helper_test.rb +9 -10
  40. data/test/helpers/policy_dashboard_helper_test.rb +1 -1
  41. data/test/test_plugin_helper.rb +9 -4
  42. data/test/unit/policy_test.rb +1 -1
  43. data/test/unit/services/config_name_service_test.rb +1 -0
  44. data/test/unit/services/hostgroup_overrider_test.rb +2 -1
  45. data/test/unit/services/lookup_key_overrider_test.rb +4 -1
  46. data/test/unit/services/oval/setup_check_test.rb +37 -0
  47. data/webpack/components/ConfirmModal.js +63 -0
  48. data/webpack/components/ConfirmModal.scss +3 -0
  49. data/webpack/components/EditableInput.js +163 -0
  50. data/webpack/components/EditableInput.scss +3 -0
  51. data/webpack/components/EmptyState.js +12 -3
  52. data/webpack/components/IndexLayout.js +11 -4
  53. data/webpack/components/IndexTable/index.js +21 -16
  54. data/webpack/components/LinkButton.js +38 -0
  55. data/webpack/components/withDeleteModal.js +51 -0
  56. data/webpack/components/withLoading.js +44 -5
  57. data/webpack/graphql/mutations/createOvalPolicy.gql +22 -0
  58. data/webpack/graphql/mutations/deleteOvalContent.gql +9 -0
  59. data/webpack/graphql/mutations/deleteOvalPolicy.gql +9 -0
  60. data/webpack/graphql/mutations/updateOvalPolicy.gql +14 -0
  61. data/webpack/graphql/queries/currentUserAttributes.gql +11 -0
  62. data/webpack/graphql/queries/cves.gql +5 -0
  63. data/webpack/graphql/queries/hostgroups.gql +14 -0
  64. data/webpack/graphql/queries/ovalContent.gql +8 -0
  65. data/webpack/graphql/queries/ovalContents.gql +8 -0
  66. data/webpack/graphql/queries/ovalPolicies.gql +8 -0
  67. data/webpack/graphql/queries/ovalPolicy.gql +8 -0
  68. data/webpack/helpers/formFieldsHelper.js +113 -0
  69. data/webpack/helpers/globalIdHelper.js +4 -2
  70. data/webpack/helpers/mutationHelper.js +68 -0
  71. data/webpack/helpers/pathsHelper.js +10 -3
  72. data/webpack/helpers/permissionsHelper.js +42 -0
  73. data/webpack/helpers/toastHelper.js +3 -0
  74. data/webpack/helpers/toastsHelper.js +3 -0
  75. data/webpack/routes/OvalContents/OvalContentsIndex/OvalContentsIndex.js +26 -0
  76. data/webpack/routes/OvalContents/OvalContentsIndex/OvalContentsTable.js +50 -5
  77. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsDestroy.fixtures.js +105 -0
  78. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsDestroy.test.js +124 -0
  79. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.fixtures.js +98 -77
  80. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.test.js +53 -6
  81. data/webpack/routes/OvalContents/OvalContentsIndex/index.js +7 -1
  82. data/webpack/routes/OvalContents/OvalContentsNew/OvalContentsNew.js +138 -0
  83. data/webpack/routes/OvalContents/OvalContentsNew/OvalContentsNew.scss +3 -0
  84. data/webpack/routes/OvalContents/OvalContentsNew/OvalContentsNewHelper.js +73 -0
  85. data/webpack/routes/OvalContents/OvalContentsNew/__tests__/OvalContentsNew.test.js +104 -0
  86. data/webpack/routes/OvalContents/OvalContentsNew/index.js +13 -0
  87. data/webpack/routes/OvalContents/OvalContentsShow/OvalContentsShow.js +62 -0
  88. data/webpack/routes/OvalContents/OvalContentsShow/OvalContentsShow.test.js +45 -0
  89. data/webpack/routes/OvalContents/OvalContentsShow/OvalContentsShowHelper.js +0 -0
  90. data/webpack/routes/OvalContents/OvalContentsShow/index.js +35 -0
  91. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesIndex.js +18 -2
  92. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesTable.js +34 -4
  93. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesDestroy.fixtures.js +101 -0
  94. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesDestroy.test.js +117 -0
  95. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesIndex.fixtures.js +71 -21
  96. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesIndex.test.js +34 -2
  97. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/index.js +7 -1
  98. data/webpack/routes/OvalPolicies/OvalPoliciesNew/HostgroupSelect.js +135 -0
  99. data/webpack/routes/OvalPolicies/OvalPoliciesNew/NewOvalPolicyForm.js +119 -0
  100. data/webpack/routes/OvalPolicies/OvalPoliciesNew/NewOvalPolicyFormHelpers.js +107 -0
  101. data/webpack/routes/OvalPolicies/OvalPoliciesNew/OvalPoliciesNew.js +32 -0
  102. data/webpack/routes/OvalPolicies/OvalPoliciesNew/__tests__/OvalPoliciesNew.fixtures.js +147 -0
  103. data/webpack/routes/OvalPolicies/OvalPoliciesNew/__tests__/OvalPoliciesNew.test.js +172 -0
  104. data/webpack/routes/OvalPolicies/OvalPoliciesNew/index.js +11 -0
  105. data/webpack/routes/OvalPolicies/OvalPoliciesShow/CvesTab.js +1 -0
  106. data/webpack/routes/OvalPolicies/OvalPoliciesShow/CvesTable.js +2 -2
  107. data/webpack/routes/OvalPolicies/OvalPoliciesShow/DetailsTab.js +87 -0
  108. data/webpack/routes/OvalPolicies/OvalPoliciesShow/HostgroupsTab.js +49 -0
  109. data/webpack/routes/OvalPolicies/OvalPoliciesShow/HostgroupsTable.js +38 -0
  110. data/webpack/routes/OvalPolicies/OvalPoliciesShow/OvalPoliciesShow.js +15 -11
  111. data/webpack/routes/OvalPolicies/OvalPoliciesShow/OvalPoliciesShowHelper.js +80 -2
  112. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesEdit.fixtures.js +48 -0
  113. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesEdit.test.js +202 -0
  114. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.fixtures.js +50 -4
  115. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.test.js +64 -4
  116. data/webpack/routes/OvalPolicies/OvalPoliciesShow/index.js +4 -0
  117. data/webpack/routes/routes.js +21 -0
  118. data/webpack/testHelper.js +64 -2
  119. metadata +63 -7
@@ -6,13 +6,21 @@ import { usePaginationOptions } from 'foremanReact/components/Pagination/Paginat
6
6
 
7
7
  import { preparePerPageOptions, refreshPage } from './IndexTableHelper';
8
8
 
9
- const IndexTable = props => {
9
+ const IndexTable = ({
10
+ history,
11
+ pagination,
12
+ totalCount,
13
+ toolbarBtns,
14
+ ariaTableLabel,
15
+ columns,
16
+ ...rest
17
+ }) => {
10
18
  const handlePerPageSelected = (event, perPage) => {
11
- refreshPage(props.history, { page: 1, perPage });
19
+ refreshPage(history, { page: 1, perPage });
12
20
  };
13
21
 
14
22
  const handlePageSelected = (event, page) => {
15
- refreshPage(props.history, { ...props.pagination, page });
23
+ refreshPage(history, { ...pagination, page });
16
24
  };
17
25
 
18
26
  const perPageOptions = preparePerPageOptions(usePaginationOptions());
@@ -20,12 +28,12 @@ const IndexTable = props => {
20
28
  return (
21
29
  <React.Fragment>
22
30
  <Flex className="pf-u-pt-md">
23
- <FlexItem>{props.toolbarBtns}</FlexItem>
31
+ <FlexItem>{toolbarBtns}</FlexItem>
24
32
  <FlexItem align={{ default: 'alignRight' }}>
25
33
  <Pagination
26
- itemCount={props.totalCount}
27
- page={props.pagination.page}
28
- perPage={props.pagination.perPage}
34
+ itemCount={totalCount}
35
+ page={pagination.page}
36
+ perPage={pagination.perPage}
29
37
  onSetPage={handlePageSelected}
30
38
  onPerPageSelect={handlePerPageSelected}
31
39
  perPageOptions={perPageOptions}
@@ -34,10 +42,10 @@ const IndexTable = props => {
34
42
  </FlexItem>
35
43
  </Flex>
36
44
  <Table
37
- aria-label={props.ariaTableLabel}
38
- cells={props.columns}
39
- rows={props.rows}
40
- actions={props.actions}
45
+ aria-label={ariaTableLabel}
46
+ cells={columns}
47
+ {...rest}
48
+ variant="compact"
41
49
  >
42
50
  <TableHeader />
43
51
  <TableBody />
@@ -49,17 +57,14 @@ const IndexTable = props => {
49
57
  IndexTable.propTypes = {
50
58
  history: PropTypes.object.isRequired,
51
59
  pagination: PropTypes.object.isRequired,
52
- toolbarBtns: PropTypes.array,
60
+ toolbarBtns: PropTypes.node,
53
61
  totalCount: PropTypes.number.isRequired,
54
62
  ariaTableLabel: PropTypes.string.isRequired,
55
63
  columns: PropTypes.array.isRequired,
56
- rows: PropTypes.array.isRequired,
57
- actions: PropTypes.array,
58
64
  };
59
65
 
60
66
  IndexTable.defaultProps = {
61
- toolbarBtns: [],
62
- actions: [],
67
+ toolbarBtns: null,
63
68
  };
64
69
 
65
70
  export default IndexTable;
@@ -0,0 +1,38 @@
1
+ import React from 'react';
2
+ import { Link } from 'react-router-dom';
3
+ import { Button } from '@patternfly/react-core';
4
+ import PropTypes from 'prop-types';
5
+
6
+ const LinkButton = ({
7
+ path,
8
+ btnVariant,
9
+ btnText,
10
+ isDisabled,
11
+ btnAriaLabel,
12
+ }) => (
13
+ <Link to={path}>
14
+ <Button
15
+ variant={btnVariant}
16
+ isDisabled={isDisabled}
17
+ aria-label={btnAriaLabel}
18
+ >
19
+ {btnText}
20
+ </Button>
21
+ </Link>
22
+ );
23
+
24
+ LinkButton.propTypes = {
25
+ path: PropTypes.string.isRequired,
26
+ btnText: PropTypes.string.isRequired,
27
+ btnVariant: PropTypes.string,
28
+ isDisabled: PropTypes.bool,
29
+ btnAriaLabel: PropTypes.string,
30
+ };
31
+
32
+ LinkButton.defaultProps = {
33
+ btnVariant: 'primary',
34
+ isDisabled: false,
35
+ btnAriaLabel: null,
36
+ };
37
+
38
+ export default LinkButton;
@@ -0,0 +1,51 @@
1
+ import React, { useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { translate as __, sprintf } from 'foremanReact/common/I18n';
4
+ import ConfirmModal from './ConfirmModal';
5
+
6
+ const withDelete = Component => {
7
+ const Subcomponent = ({
8
+ confirmDeleteTitle,
9
+ submitDelete,
10
+ prepareMutation,
11
+ ...rest
12
+ }) => {
13
+ const [toDelete, setToDelete] = useState(null);
14
+
15
+ const toggleModal = (item = null) => {
16
+ setToDelete(item);
17
+ };
18
+
19
+ return (
20
+ <React.Fragment>
21
+ <Component {...rest} toggleModal={toggleModal} />
22
+ <ConfirmModal
23
+ title={confirmDeleteTitle}
24
+ text={
25
+ toDelete
26
+ ? sprintf(
27
+ __('Are you sure you want to delete %s?'),
28
+ toDelete.name
29
+ )
30
+ : ''
31
+ }
32
+ onClose={toggleModal}
33
+ isOpen={!!toDelete}
34
+ onConfirm={submitDelete}
35
+ prepareMutation={() => prepareMutation(toggleModal)}
36
+ record={toDelete}
37
+ />
38
+ </React.Fragment>
39
+ );
40
+ };
41
+
42
+ Subcomponent.propTypes = {
43
+ confirmDeleteTitle: PropTypes.string.isRequired,
44
+ submitDelete: PropTypes.func.isRequired,
45
+ prepareMutation: PropTypes.func.isRequired,
46
+ };
47
+
48
+ return Subcomponent;
49
+ };
50
+
51
+ export default withDelete;
@@ -1,12 +1,15 @@
1
- import React from 'react';
1
+ import React, { useEffect } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { translate as __ } from 'foremanReact/common/I18n';
4
-
5
4
  import Loading from 'foremanReact/components/Loading';
5
+ import {
6
+ permissionCheck,
7
+ permissionDeniedMsg,
8
+ } from '../helpers/permissionsHelper';
9
+
6
10
  import EmptyState from './EmptyState';
7
11
 
8
12
  const errorStateTitle = __('Error!');
9
- const emptyStateBody = '';
10
13
 
11
14
  const pluckData = (data, path) => {
12
15
  const split = path.split('.');
@@ -24,9 +27,19 @@ const withLoading = Component => {
24
27
  resultPath,
25
28
  renameData,
26
29
  emptyStateTitle,
30
+ emptyStateBody,
31
+ permissions,
32
+ primaryButton,
33
+ shouldRefetch,
27
34
  ...rest
28
35
  }) => {
29
- const { loading, error, data } = fetchFn(rest);
36
+ const { loading, error, data, refetch } = fetchFn(rest);
37
+
38
+ useEffect(() => {
39
+ if (shouldRefetch) {
40
+ refetch();
41
+ }
42
+ }, [shouldRefetch, refetch]);
30
43
 
31
44
  if (loading) {
32
45
  return <Loading />;
@@ -42,10 +55,28 @@ const withLoading = Component => {
42
55
  );
43
56
  }
44
57
 
58
+ const check = permissionCheck(data.currentUser, permissions);
59
+
60
+ if (!check.allowed) {
61
+ return (
62
+ <EmptyState
63
+ lock
64
+ title={__('Permission denied')}
65
+ body={permissionDeniedMsg(check.permissions.map(item => item.name))}
66
+ />
67
+ );
68
+ }
69
+
45
70
  const result = pluckData(data, resultPath);
46
71
 
47
72
  if ((Array.isArray(result) && result.length === 0) || !result) {
48
- return <EmptyState title={emptyStateTitle} body={emptyStateBody} />;
73
+ return (
74
+ <EmptyState
75
+ title={emptyStateTitle}
76
+ body={emptyStateBody}
77
+ primaryButton={primaryButton}
78
+ />
79
+ );
49
80
  }
50
81
 
51
82
  return <Component {...rest} {...renameData(data)} />;
@@ -56,10 +87,18 @@ const withLoading = Component => {
56
87
  resultPath: PropTypes.string.isRequired,
57
88
  renameData: PropTypes.func,
58
89
  emptyStateTitle: PropTypes.string.isRequired,
90
+ emptyStateBody: PropTypes.string,
91
+ permissions: PropTypes.array,
92
+ primaryButton: PropTypes.node,
93
+ shouldRefetch: PropTypes.bool,
59
94
  };
60
95
 
61
96
  Subcomponent.defaultProps = {
62
97
  renameData: data => data,
98
+ permissions: [],
99
+ primaryButton: null,
100
+ shouldRefetch: false,
101
+ emptyStateBody: '',
63
102
  };
64
103
 
65
104
  return Subcomponent;
@@ -0,0 +1,22 @@
1
+ mutation CreateOvalPolicy($name: String!, $period: String!, $cronLine: String, $ovalContentId: Int!, $hostgroupIds: [Int!]) {
2
+ createOvalPolicy(input: {name: $name, period: $period, cronLine: $cronLine, ovalContentId: $ovalContentId, hostgroupIds: $hostgroupIds}) {
3
+ ovalPolicy {
4
+ name
5
+ id
6
+ period
7
+ cronLine
8
+ hostgroups {
9
+ nodes {
10
+ name
11
+ id
12
+ }
13
+ }
14
+ }
15
+ checkCollection {
16
+ id
17
+ errors
18
+ failMsg
19
+ result
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,9 @@
1
+ mutation DeleteOvalContent($id: ID!) {
2
+ deleteOvalContent(input: { id: $id }) {
3
+ id
4
+ errors {
5
+ path
6
+ message
7
+ }
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ mutation DeleteOvalPolicy($id: ID!) {
2
+ deleteOvalPolicy(input: { id: $id }) {
3
+ id
4
+ errors {
5
+ path
6
+ message
7
+ }
8
+ }
9
+ }
@@ -0,0 +1,14 @@
1
+ mutation UpdateOvalPolicy($id: ID!, $name: String, $description: String, $cronLine: String) {
2
+ updateOvalPolicy(input:{ id:$id, name:$name, description: $description, cronLine: $cronLine }) {
3
+ ovalPolicy {
4
+ id
5
+ name
6
+ description
7
+ cronLine
8
+ }
9
+ errors {
10
+ path
11
+ message
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,11 @@
1
+ fragment CurrentUserAttributes on User {
2
+ id
3
+ login
4
+ admin
5
+ permissions {
6
+ nodes {
7
+ id
8
+ name
9
+ }
10
+ }
11
+ }
@@ -1,3 +1,5 @@
1
+ #import "./currentUserAttributes.gql"
2
+
1
3
  query($search: String, $first: Int, $last: Int) {
2
4
  cves(search: $search, first: $first, last: $last) {
3
5
  totalCount
@@ -15,4 +17,7 @@ query($search: String, $first: Int, $last: Int) {
15
17
  }
16
18
  }
17
19
  }
20
+ currentUser {
21
+ ...CurrentUserAttributes
22
+ }
18
23
  }
@@ -0,0 +1,14 @@
1
+ #import "./currentUserAttributes.gql"
2
+
3
+ query($search: String, $first: Int, $last: Int) {
4
+ hostgroups(search: $search, first: $first, last: $last) {
5
+ totalCount
6
+ nodes {
7
+ id
8
+ name
9
+ }
10
+ }
11
+ currentUser {
12
+ ...CurrentUserAttributes
13
+ }
14
+ }
@@ -0,0 +1,8 @@
1
+ query($id: String!) {
2
+ ovalContent(id: $id) {
3
+ id
4
+ name
5
+ url
6
+ originalFilename
7
+ }
8
+ }
@@ -1,3 +1,5 @@
1
+ #import "./currentUserAttributes.gql"
2
+
1
3
  query($first: Int, $last: Int) {
2
4
  ovalContents(first: $first, last: $last) {
3
5
  totalCount
@@ -6,6 +8,12 @@ query($first: Int, $last: Int) {
6
8
  name
7
9
  url
8
10
  originalFilename
11
+ meta {
12
+ canDestroy
13
+ }
9
14
  }
10
15
  }
16
+ currentUser {
17
+ ...CurrentUserAttributes
18
+ }
11
19
  }
@@ -1,12 +1,20 @@
1
+ #import "./currentUserAttributes.gql"
2
+
1
3
  query($first: Int, $last: Int) {
2
4
  ovalPolicies(first: $first, last: $last) {
3
5
  totalCount
4
6
  nodes {
5
7
  id
6
8
  name
9
+ meta {
10
+ canDestroy
11
+ }
7
12
  ovalContent {
8
13
  name
9
14
  }
10
15
  }
11
16
  }
17
+ currentUser {
18
+ ...CurrentUserAttributes
19
+ }
12
20
  }
@@ -1,3 +1,5 @@
1
+ #import "./currentUserAttributes.gql"
2
+
1
3
  query($id: String!) {
2
4
  ovalPolicy(id: $id) {
3
5
  id
@@ -7,6 +9,9 @@ query($id: String!) {
7
9
  weekday
8
10
  dayOfMonth
9
11
  description
12
+ meta {
13
+ canEdit
14
+ }
10
15
  hostgroups {
11
16
  nodes {
12
17
  id
@@ -18,4 +23,7 @@ query($id: String!) {
18
23
  }
19
24
  }
20
25
  }
26
+ currentUser {
27
+ ...CurrentUserAttributes
28
+ }
21
29
  }
@@ -0,0 +1,113 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ import {
5
+ FormGroup,
6
+ TextInput,
7
+ TextArea,
8
+ FormSelect,
9
+ FormSelectOption,
10
+ } from '@patternfly/react-core';
11
+ import { ExclamationCircleIcon } from '@patternfly/react-icons';
12
+
13
+ export const SelectField = props => {
14
+ const { selectItems, field, form } = props;
15
+ const fieldProps = wrapFieldProps(field);
16
+
17
+ const valid = shouldValidate(form, field.name);
18
+
19
+ return (
20
+ <FormGroup
21
+ label={props.label}
22
+ isRequired={props.isRequired}
23
+ helperTextInvalid={form.errors[field.name]}
24
+ helperTextInvalidIcon={<ExclamationCircleIcon />}
25
+ validated={valid}
26
+ >
27
+ <FormSelect
28
+ {...fieldProps}
29
+ className="without_select2"
30
+ aria-label={fieldProps.name}
31
+ validated={valid}
32
+ isDisabled={form.isSubmitting}
33
+ >
34
+ <FormSelectOption key={0} value="" label={props.blankLabel} />
35
+ {selectItems.map((item, idx) => (
36
+ <FormSelectOption key={idx + 1} value={item.id} label={item.name} />
37
+ ))}
38
+ </FormSelect>
39
+ </FormGroup>
40
+ );
41
+ };
42
+
43
+ SelectField.propTypes = {
44
+ selectItems: PropTypes.array,
45
+ label: PropTypes.string.isRequired,
46
+ isRequired: PropTypes.bool,
47
+ field: PropTypes.object.isRequired,
48
+ form: PropTypes.object.isRequired,
49
+ blankLabel: PropTypes.string.isRequired,
50
+ };
51
+ SelectField.defaultProps = {
52
+ selectItems: [],
53
+ isRequired: false,
54
+ };
55
+
56
+ const wrapFieldProps = fieldProps => {
57
+ const { onChange } = fieldProps;
58
+ // modify onChange args to correctly wire formik with pf4 input handlers
59
+ const wrappedOnChange = (value, event) => {
60
+ onChange(event);
61
+ };
62
+
63
+ return { ...fieldProps, onChange: wrappedOnChange };
64
+ };
65
+
66
+ const shouldValidate = (form, fieldName) => {
67
+ if (form.touched[fieldName]) {
68
+ return form.errors[fieldName] ? 'error' : 'success';
69
+ }
70
+
71
+ return 'noval';
72
+ };
73
+
74
+ const fieldWithHandlers = Component => {
75
+ const Subcomponent = ({ label, form, field, isRequired, ...rest }) => {
76
+ const fieldProps = wrapFieldProps(field);
77
+ const valid = shouldValidate(form, field.name);
78
+
79
+ return (
80
+ <FormGroup
81
+ label={label}
82
+ isRequired={isRequired}
83
+ helperTextInvalid={form.errors[field.name]}
84
+ helperTextInvalidIcon={<ExclamationCircleIcon />}
85
+ validated={valid}
86
+ >
87
+ <Component
88
+ aria-label={fieldProps.name}
89
+ {...fieldProps}
90
+ {...rest}
91
+ validated={valid}
92
+ isDisabled={form.isSubmitting}
93
+ />
94
+ </FormGroup>
95
+ );
96
+ };
97
+
98
+ Subcomponent.propTypes = {
99
+ form: PropTypes.object.isRequired,
100
+ field: PropTypes.object.isRequired,
101
+ label: PropTypes.string.isRequired,
102
+ isRequired: PropTypes.bool,
103
+ };
104
+
105
+ Subcomponent.defaultProps = {
106
+ isRequired: false,
107
+ };
108
+
109
+ return Subcomponent;
110
+ };
111
+
112
+ export const TextField = fieldWithHandlers(TextInput);
113
+ export const TextAreaField = fieldWithHandlers(TextArea);
@@ -4,8 +4,10 @@ const idSeparator = '-';
4
4
  const versionSeparator = ':';
5
5
  const defaultVersion = '01';
6
6
 
7
- export const decodeId = model => {
8
- const split = atob(model.id).split(idSeparator);
7
+ export const decodeModelId = model => decodeId(model.id);
8
+
9
+ export const decodeId = globalId => {
10
+ const split = atob(globalId).split(idSeparator);
9
11
  return parseInt(last(split), 10);
10
12
  };
11
13
 
@@ -0,0 +1,68 @@
1
+ import { useMutation } from '@apollo/client';
2
+ import { translate as __, sprintf } from 'foremanReact/common/I18n';
3
+
4
+ import { useCurrentPagination, pageToVars } from './pageParamsHelper';
5
+
6
+ const formatError = (error, name) =>
7
+ sprintf(__('There was a following error when deleting %(name)s: %(error)s'), {
8
+ name,
9
+ error,
10
+ });
11
+
12
+ const joinErrors = errors => errors.map(err => err.message).join(', ');
13
+
14
+ const onError = (showToast, resourceName) => error => {
15
+ showToast({ type: 'error', message: formatError(error, resourceName) });
16
+ };
17
+
18
+ const onCompleted = (
19
+ toggleModal,
20
+ showToast,
21
+ mutationName,
22
+ successMsg,
23
+ resourceName
24
+ ) => data => {
25
+ toggleModal();
26
+ const { errors } = data[mutationName];
27
+ if (Array.isArray(errors) && errors.length > 0) {
28
+ showToast({
29
+ type: 'error',
30
+ message: formatError(joinErrors(errors), resourceName),
31
+ });
32
+ } else {
33
+ showToast({
34
+ type: 'success',
35
+ message: successMsg,
36
+ });
37
+ }
38
+ };
39
+
40
+ export const prepareMutation = (
41
+ history,
42
+ showToast,
43
+ refetchQuery,
44
+ mutationName,
45
+ successMsg,
46
+ mutation,
47
+ resourceName
48
+ ) => toggleModal => {
49
+ const pagination = pageToVars(useCurrentPagination(history));
50
+
51
+ const options = {
52
+ refetchQueries: [{ query: refetchQuery, variables: pagination }],
53
+ onCompleted: onCompleted(
54
+ toggleModal,
55
+ showToast,
56
+ mutationName,
57
+ successMsg,
58
+ resourceName
59
+ ),
60
+ onError: onError(showToast, resourceName),
61
+ };
62
+
63
+ return useMutation(mutation, options);
64
+ };
65
+
66
+ export const submitDelete = (mutation, id) => {
67
+ mutation({ variables: { id } });
68
+ };
@@ -1,10 +1,12 @@
1
- import { decodeId } from './globalIdHelper';
1
+ import { decodeModelId } from './globalIdHelper';
2
2
 
3
3
  const experimental = path => `/experimental${path}`;
4
4
 
5
5
  const showPath = path => `${path}/:id`;
6
+ const newPath = path => `${path}/new`;
6
7
 
7
- export const modelPath = (basePath, model) => `${basePath}/${decodeId(model)}`;
8
+ export const modelPath = (basePath, model) =>
9
+ `${basePath}/${decodeModelId(model)}`;
8
10
 
9
11
  // react-router uses path-to-regexp, should we use it as well in a future?
10
12
  // https://github.com/pillarjs/path-to-regexp/tree/v1.7.0#compile-reverse-path-to-regexp
@@ -14,9 +16,14 @@ export const resolvePath = (path, params) =>
14
16
  path
15
17
  );
16
18
 
19
+ export const ovalContentsApiPath = '/api/v2/compliance/oval_contents';
20
+
17
21
  export const ovalContentsPath = experimental('/compliance/oval_contents');
22
+ export const ovalContentsShowPath = showPath(ovalContentsPath);
23
+ export const ovalContentsNewPath = newPath(ovalContentsPath);
18
24
  export const ovalPoliciesPath = experimental('/compliance/oval_policies');
19
25
  export const ovalPoliciesShowPath = `${showPath(ovalPoliciesPath)}/:tab?`;
26
+ export const ovalPoliciesNewPath = newPath(ovalPoliciesPath);
20
27
  export const hostsPath = '/hosts';
21
- export const newJobPath = '/job_invocations/new';
28
+ export const newJobPath = newPath('/job_invocations');
22
29
  export const hostsShowPath = showPath(hostsPath);