foreman_ansible 6.4.1 → 7.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (137) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/ansible_inventories_controller.rb +1 -1
  3. data/app/graphql/mutations/ansible_variable_overrides/create.rb +26 -0
  4. data/app/graphql/mutations/ansible_variable_overrides/delete.rb +38 -0
  5. data/app/graphql/mutations/ansible_variable_overrides/update.rb +26 -0
  6. data/app/graphql/mutations/hosts/assign_ansible_roles.rb +37 -0
  7. data/app/graphql/presenters/ansible_role_presenter.rb +12 -0
  8. data/app/graphql/presenters/overriden_ansible_variable_presenter.rb +19 -0
  9. data/app/graphql/types/ansible_role.rb +9 -0
  10. data/app/graphql/types/ansible_variable.rb +23 -0
  11. data/app/graphql/types/ansible_variable_override.rb +9 -0
  12. data/app/graphql/types/inherited_ansible_role.rb +13 -0
  13. data/app/graphql/types/overriden_ansible_variable.rb +27 -0
  14. data/app/helpers/foreman_ansible/ansible_roles_data_preparations.rb +22 -22
  15. data/app/models/concerns/foreman_ansible/host_managed_extensions.rb +23 -4
  16. data/app/models/concerns/foreman_ansible/hostgroup_extensions.rb +2 -1
  17. data/app/models/foreman_ansible/ansible_provider.rb +7 -5
  18. data/app/services/foreman_ansible/ansible_report_importer.rb +2 -2
  19. data/app/services/foreman_ansible/inventory_creator.rb +1 -1
  20. data/app/services/foreman_ansible/override_resolver.rb +22 -0
  21. data/app/views/api/v2/ansible_override_values/index.json.rabl +3 -0
  22. data/app/views/api/v2/ansible_variables/show.json.rabl +1 -1
  23. data/app/views/foreman_ansible/ansible_roles/_hostgroup_ansible_roles_button.erb +3 -0
  24. data/app/views/foreman_ansible/job_templates/convert_to_rhel.erb +6 -2
  25. data/app/views/foreman_ansible/job_templates/run_openscap_scans_-_ansible_default.erb +20 -0
  26. data/config/routes.rb +3 -0
  27. data/db/migrate/20210818083407_fix_ansible_setting_category_to_dsl.rb +5 -0
  28. data/lib/foreman_ansible/engine.rb +0 -17
  29. data/lib/foreman_ansible/register.rb +115 -4
  30. data/lib/foreman_ansible/version.rb +1 -1
  31. data/package.json +4 -2
  32. data/test/functional/api/v2/ansible_inventories_controller_test.rb +1 -2
  33. data/test/graphql/mutations/hosts/assign_ansible_roles_mutation_test.rb +96 -0
  34. data/test/graphql/queries/ansible_roles_query_test.rb +35 -0
  35. data/test/unit/ansible_provider_test.rb +3 -6
  36. data/test/unit/concerns/host_managed_extensions_test.rb +8 -0
  37. data/test/unit/concerns/hostgroup_extensions_test.rb +6 -0
  38. data/test/unit/hostgroup_ansible_role_test.rb +13 -0
  39. data/test/unit/services/override_resolver_test.rb +34 -0
  40. data/webpack/components/AnsibleHostDetail/AnsibleHostDetail.js +51 -27
  41. data/webpack/components/AnsibleHostDetail/AnsibleHostDetail.test.js +12 -6
  42. data/webpack/components/AnsibleHostDetail/components/AnsibleHostInventory/AnsibleHostInventory.js +22 -0
  43. data/webpack/components/AnsibleHostDetail/components/AnsibleHostInventory/AnsibleHostInventory.scss +4 -0
  44. data/webpack/components/AnsibleHostDetail/components/AnsibleHostInventory/AnsibleHostInventory.test.js +104 -0
  45. data/webpack/components/AnsibleHostDetail/components/AnsibleHostInventory/index.js +38 -0
  46. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/AnsibleVariableOverrides.scss +3 -0
  47. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/AnsibleVariableOverridesTable.js +238 -0
  48. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/AnsibleVariableOverridesTableHelper.js +111 -0
  49. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableAction.js +161 -0
  50. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableAction.scss +7 -0
  51. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableActionHelper.js +49 -0
  52. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableValue.js +70 -0
  53. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableValueHelper.js +35 -0
  54. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverrides.fixtures.js +429 -0
  55. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverrides.test.js +71 -0
  56. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverridesDelete.test.js +74 -0
  57. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverridesUpdate.test.js +188 -0
  58. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/index.js +58 -0
  59. data/webpack/components/AnsibleHostDetail/components/JobsTab/JobsTabHelper.js +79 -0
  60. data/webpack/components/AnsibleHostDetail/components/JobsTab/NewRecurringJobHelper.js +106 -0
  61. data/webpack/components/AnsibleHostDetail/components/JobsTab/NewRecurringJobModal.js +129 -0
  62. data/webpack/components/AnsibleHostDetail/components/JobsTab/NewRecurringJobModal.scss +7 -0
  63. data/webpack/components/AnsibleHostDetail/components/JobsTab/PreviousJobsTable.js +103 -0
  64. data/webpack/components/AnsibleHostDetail/components/JobsTab/RecurringJobsTable.js +96 -0
  65. data/webpack/components/AnsibleHostDetail/components/JobsTab/__test__/JobsTab.fixtures.js +184 -0
  66. data/webpack/components/AnsibleHostDetail/components/JobsTab/__test__/JobsTab.test.js +195 -0
  67. data/webpack/components/AnsibleHostDetail/components/JobsTab/index.js +88 -0
  68. data/webpack/components/AnsibleHostDetail/components/RolesTab/AllRolesModal/AllRolesTable.js +89 -0
  69. data/webpack/components/AnsibleHostDetail/components/RolesTab/AllRolesModal/index.js +80 -0
  70. data/webpack/components/AnsibleHostDetail/components/RolesTab/EditRolesModal/EditRolesForm.js +90 -0
  71. data/webpack/components/AnsibleHostDetail/components/RolesTab/EditRolesModal/EditRolesModal.scss +3 -0
  72. data/webpack/components/AnsibleHostDetail/components/RolesTab/EditRolesModal/EditRolesModalHelper.js +40 -0
  73. data/webpack/components/AnsibleHostDetail/components/RolesTab/EditRolesModal/index.js +82 -0
  74. data/webpack/components/AnsibleHostDetail/components/RolesTab/RolesTable.js +129 -0
  75. data/webpack/components/AnsibleHostDetail/components/RolesTab/__test__/EditRoles.test.js +85 -0
  76. data/webpack/components/AnsibleHostDetail/components/RolesTab/__test__/RolesTab.fixtures.js +180 -0
  77. data/webpack/components/AnsibleHostDetail/components/RolesTab/__test__/RolesTab.test.js +75 -0
  78. data/webpack/components/AnsibleHostDetail/components/RolesTab/index.js +51 -0
  79. data/webpack/components/AnsibleHostDetail/components/SecondaryTabRoutes.js +60 -0
  80. data/webpack/components/AnsibleHostDetail/components/TabLayout.js +12 -0
  81. data/webpack/components/AnsibleHostDetail/constants.js +9 -0
  82. data/webpack/components/AnsibleHostDetail/helpers.js +4 -0
  83. data/webpack/components/AnsibleRolesAndVariables/__test__/AnsibleRolesAndVariablesImport.test.js +15 -10
  84. data/webpack/components/AnsibleRolesSwitcher/components/AnsibleRole.js +29 -0
  85. data/webpack/components/AnsibleRolesSwitcher/components/AnsibleRole.test.js +3 -0
  86. data/webpack/components/AnsibleRolesSwitcher/components/AvailableRolesList.js +2 -1
  87. data/webpack/components/AnsibleRolesSwitcher/components/__snapshots__/AnsibleRole.test.js.snap +3 -3
  88. data/webpack/components/AnsibleRolesSwitcher/components/__snapshots__/AvailableRolesList.test.js.snap +4 -0
  89. data/webpack/components/DualList/DualList.scss +3 -0
  90. data/webpack/components/DualList/ListControls.js +65 -0
  91. data/webpack/components/DualList/ListHeader.js +16 -0
  92. data/webpack/components/DualList/ListItem.js +69 -0
  93. data/webpack/components/DualList/ListPane.js +95 -0
  94. data/webpack/components/DualList/SelectedStatus.js +21 -0
  95. data/webpack/components/DualList/index.js +103 -0
  96. data/webpack/components/ErrorState.js +16 -0
  97. data/webpack/components/withLoading.js +135 -0
  98. data/webpack/components/withPagination.js +0 -0
  99. data/webpack/formHelper.js +131 -0
  100. data/webpack/globalIdHelper.js +13 -0
  101. data/webpack/global_index.js +7 -1
  102. data/webpack/graphql/mutations/assignAnsibleRoles.gql +17 -0
  103. data/webpack/graphql/mutations/cancelRecurringLogic.gql +12 -0
  104. data/webpack/graphql/mutations/createAnsibleVariableOverride.gql +28 -0
  105. data/webpack/graphql/mutations/createJobInvocation.gql +11 -0
  106. data/webpack/graphql/mutations/deleteAnsibleVariableOverride.gql +17 -0
  107. data/webpack/graphql/mutations/updateAnsibleVariableOverride.gql +29 -0
  108. data/webpack/graphql/queries/allAnsibleRoles.gql +13 -0
  109. data/webpack/graphql/queries/ansibleRoles.gql +13 -0
  110. data/webpack/graphql/queries/currentUserAttributes.gql +11 -0
  111. data/webpack/graphql/queries/hostAnsibleRoles.gql +17 -0
  112. data/webpack/graphql/queries/hostAvailableAnsibleRoles.gql +11 -0
  113. data/webpack/graphql/queries/hostVariableOverrides.gql +39 -0
  114. data/webpack/graphql/queries/recurringJobs.gql +28 -0
  115. data/webpack/helpers/pageParamsHelper.js +40 -0
  116. data/webpack/helpers/paginationHelper.js +9 -0
  117. data/webpack/permissionsHelper.js +58 -0
  118. data/webpack/routes/HostgroupJobs/__test__/HostgroupJobs.fixtures.js +63 -0
  119. data/webpack/routes/HostgroupJobs/__test__/HostgroupJobs.test.js +112 -0
  120. data/webpack/routes/HostgroupJobs/index.js +26 -0
  121. data/webpack/routes/routes.js +10 -0
  122. data/webpack/testHelper.js +165 -0
  123. data/webpack/toastHelper.js +4 -0
  124. metadata +127 -54
  125. data/app/assets/images/foreman_ansible/Ansible.png +0 -0
  126. data/app/models/foreman_ansible/fact_name.rb +0 -16
  127. data/app/models/setting/ansible.rb +0 -106
  128. data/app/services/foreman_ansible/fact_importer.rb +0 -99
  129. data/app/services/foreman_ansible/fact_parser.rb +0 -126
  130. data/app/services/foreman_ansible/fact_sparser.rb +0 -37
  131. data/app/services/foreman_ansible/operating_system_parser.rb +0 -102
  132. data/app/services/foreman_ansible/structured_fact_importer.rb +0 -25
  133. data/test/unit/services/fact_importer_test.rb +0 -52
  134. data/test/unit/services/fact_parser_test.rb +0 -281
  135. data/test/unit/services/fact_sparser_test.rb +0 -24
  136. data/test/unit/services/structured_fact_importer_test.rb +0 -30
  137. data/webpack/components/AnsibleRolesAndVariables/__test__/__snapshots__/AnsibleRolesAndVariablesImport.test.js.snap +0 -177
@@ -0,0 +1,89 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { translate as __ } from 'foremanReact/common/I18n';
4
+ import { usePaginationOptions } from 'foremanReact/components/Pagination/PaginationHooks';
5
+
6
+ import {
7
+ TableComposable,
8
+ Thead,
9
+ Tbody,
10
+ Tr,
11
+ Th,
12
+ Td,
13
+ } from '@patternfly/react-table';
14
+
15
+ import { Flex, FlexItem, Pagination } from '@patternfly/react-core';
16
+ import withLoading from '../../../../withLoading';
17
+ import {
18
+ preparePerPageOptions,
19
+ refreshPage,
20
+ } from '../../../../../helpers/paginationHelper';
21
+
22
+ const AllRolesTable = ({
23
+ allAnsibleRoles,
24
+ totalCount,
25
+ pagination,
26
+ history,
27
+ }) => {
28
+ const columns = [__('Name'), __('Source')];
29
+
30
+ const handlePerPageSelected = (event, allPerPage) => {
31
+ refreshPage(history, { allPage: 1, allPerPage });
32
+ };
33
+
34
+ const handlePageSelected = (event, allPage) => {
35
+ refreshPage(history, { ...pagination, allPage });
36
+ };
37
+
38
+ const perPageOptions = preparePerPageOptions(usePaginationOptions());
39
+
40
+ return (
41
+ <React.Fragment>
42
+ <Flex className="pf-u-pt-md">
43
+ <FlexItem align={{ default: 'alignRight' }}>
44
+ <Pagination
45
+ itemCount={totalCount}
46
+ page={pagination.allPage}
47
+ perPage={pagination.allPerPage}
48
+ onSetPage={handlePageSelected}
49
+ onPerPageSelect={handlePerPageSelected}
50
+ perPageOptions={perPageOptions}
51
+ variant="top"
52
+ />
53
+ </FlexItem>
54
+ </Flex>
55
+ <TableComposable variant="compact">
56
+ <Thead>
57
+ <Tr>
58
+ <Th />
59
+ {columns.map(col => (
60
+ <Th key={`${col}-all`}>{col}</Th>
61
+ ))}
62
+ </Tr>
63
+ </Thead>
64
+ <Tbody>
65
+ {allAnsibleRoles.map(role => (
66
+ <Tr key={`${role.id}-all`} id={role.id}>
67
+ <Td />
68
+ <Td>{role.name}</Td>
69
+ <Td>
70
+ {role.inherited
71
+ ? __('Inherited from Hostgroup')
72
+ : __('Directly assigned to Host')}
73
+ </Td>
74
+ </Tr>
75
+ ))}
76
+ </Tbody>
77
+ </TableComposable>
78
+ </React.Fragment>
79
+ );
80
+ };
81
+
82
+ AllRolesTable.propTypes = {
83
+ allAnsibleRoles: PropTypes.array.isRequired,
84
+ totalCount: PropTypes.number.isRequired,
85
+ pagination: PropTypes.object.isRequired,
86
+ history: PropTypes.object.isRequired,
87
+ };
88
+
89
+ export default withLoading(AllRolesTable);
@@ -0,0 +1,80 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { useQuery } from '@apollo/client';
4
+ import { translate as __ } from 'foremanReact/common/I18n';
5
+
6
+ import { Modal, Button, ModalVariant } from '@patternfly/react-core';
7
+
8
+ import allAnsibleRolesQuery from '../../../../../graphql/queries/allAnsibleRoles.gql';
9
+ import AllRolesTable from './AllRolesTable';
10
+
11
+ import {
12
+ useParamsToVars,
13
+ useCurrentPagination,
14
+ } from '../../../../../helpers/pageParamsHelper';
15
+
16
+ const AllRolesModal = ({ hostGlobalId, onClose, history }) => {
17
+ const baseModalProps = {
18
+ variant: ModalVariant.large,
19
+ isOpen: true,
20
+ className: 'foreman-modal',
21
+ showClose: false,
22
+ title: __('All Ansible Roles'),
23
+ disableFocusTrap: true,
24
+ };
25
+
26
+ const paginationKeys = { page: 'allPage', perPage: 'allPerPage' };
27
+
28
+ const actions = [
29
+ <Button variant="link" onClick={onClose} key="close">
30
+ {__('Close')}
31
+ </Button>,
32
+ ];
33
+
34
+ const wrapper = child => (
35
+ <Modal {...baseModalProps} actions={actions}>
36
+ {child}
37
+ </Modal>
38
+ );
39
+
40
+ const loadingWrapper = child => <Modal {...baseModalProps}>{child}</Modal>;
41
+
42
+ const useFetchFn = () =>
43
+ useQuery(allAnsibleRolesQuery, {
44
+ variables: {
45
+ id: hostGlobalId,
46
+ ...useParamsToVars(history, paginationKeys),
47
+ },
48
+ fetchPolicy: 'network-only',
49
+ });
50
+
51
+ const renameData = data => ({
52
+ allAnsibleRoles: data.host.allAnsibleRoles.nodes,
53
+ totalCount: data.host.allAnsibleRoles.totalCount,
54
+ });
55
+
56
+ const pagination = useCurrentPagination(history, paginationKeys);
57
+
58
+ return (
59
+ <AllRolesTable
60
+ wrapper={wrapper}
61
+ loadingWrapper={loadingWrapper}
62
+ emptyWrapper={loadingWrapper}
63
+ fetchFn={useFetchFn}
64
+ renameData={renameData}
65
+ renamedDataPath="allAnsibleRoles"
66
+ hostGlobalId={hostGlobalId}
67
+ emptyStateTitle={__('No Ansible roles assigned')}
68
+ history={history}
69
+ pagination={pagination}
70
+ />
71
+ );
72
+ };
73
+
74
+ AllRolesModal.propTypes = {
75
+ hostGlobalId: PropTypes.string.isRequired,
76
+ onClose: PropTypes.func.isRequired,
77
+ history: PropTypes.object.isRequired,
78
+ };
79
+
80
+ export default AllRolesModal;
@@ -0,0 +1,90 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { translate as __ } from 'foremanReact/common/I18n';
3
+ import PropTypes from 'prop-types';
4
+
5
+ import { useMutation } from '@apollo/client';
6
+
7
+ import { Button, Modal, Spinner } from '@patternfly/react-core';
8
+ import { encodeId } from '../../../../../globalIdHelper';
9
+ import assignAnsibleRoles from '../../../../../graphql/mutations/assignAnsibleRoles.gql';
10
+ import withLoading from '../../../../withLoading';
11
+ import { onCompleted, onError, roleNamesToIds } from './EditRolesModalHelper';
12
+ import DualList from '../../../../DualList';
13
+
14
+ const EditRolesForm = props => {
15
+ const {
16
+ assignedRoles,
17
+ availableRoles,
18
+ closeModal,
19
+ hostId,
20
+ baseModalProps,
21
+ actions,
22
+ } = props;
23
+
24
+ const [formState, setFormState] = useState({
25
+ availableOptions: [],
26
+ chosenOptions: [],
27
+ });
28
+
29
+ useEffect(() => {
30
+ setFormState({
31
+ availableOptions: availableRoles.map(item => item.name),
32
+ chosenOptions: assignedRoles.map(item => item.name) || [],
33
+ });
34
+ }, [availableRoles, assignedRoles]);
35
+
36
+ const [callMutation, { loading }] = useMutation(assignAnsibleRoles, {
37
+ onCompleted: onCompleted(closeModal),
38
+ onError,
39
+ });
40
+
41
+ const allRoles = (availableRoles || []).concat(assignedRoles || []);
42
+
43
+ const variables = {
44
+ id: encodeId('Host', hostId),
45
+ ansibleRoleIds: roleNamesToIds(allRoles, formState.chosenOptions),
46
+ };
47
+
48
+ const formActions = [
49
+ <Button
50
+ key="confirm"
51
+ variant="primary"
52
+ onClick={() => callMutation({ variables })}
53
+ isDisabled={loading}
54
+ aria-label="submit ansible roles"
55
+ >
56
+ {__('Confirm')}
57
+ </Button>,
58
+ ...actions,
59
+ ];
60
+
61
+ if (loading) {
62
+ formActions.push(<Spinner key="spinner" size="lg" />);
63
+ }
64
+
65
+ return (
66
+ <Modal {...baseModalProps} actions={formActions}>
67
+ <DualList
68
+ availableOptions={formState.availableOptions}
69
+ chosenOptions={formState.chosenOptions}
70
+ onListChange={(newAvailable, newChosen) =>
71
+ setFormState({
72
+ availableOptions: newAvailable,
73
+ chosenOptions: newChosen,
74
+ })
75
+ }
76
+ />
77
+ </Modal>
78
+ );
79
+ };
80
+
81
+ EditRolesForm.propTypes = {
82
+ closeModal: PropTypes.func.isRequired,
83
+ assignedRoles: PropTypes.array.isRequired,
84
+ availableRoles: PropTypes.array.isRequired,
85
+ actions: PropTypes.array.isRequired,
86
+ hostId: PropTypes.number.isRequired,
87
+ baseModalProps: PropTypes.object.isRequired,
88
+ };
89
+
90
+ export default withLoading(EditRolesForm);
@@ -0,0 +1,40 @@
1
+ import { sprintf, translate as __ } from 'foremanReact/common/I18n';
2
+ import { decodeModelId } from '../../../../../globalIdHelper';
3
+ import { showToast } from '../../../../../toastHelper';
4
+
5
+ export const roleNamesToIds = (roles, names) =>
6
+ names.reduce((memo, name) => {
7
+ const role = roles.find(item => item.name === name);
8
+ if (role) {
9
+ memo.push(decodeModelId(role));
10
+ }
11
+ return memo;
12
+ }, []);
13
+
14
+ const joinErrors = errors => errors.map(err => err.message).join(', ');
15
+
16
+ const formatError = error =>
17
+ sprintf(
18
+ __('There was a following error when assigning Ansible Roles: %s'),
19
+ error
20
+ );
21
+
22
+ export const onCompleted = closeModal => data => {
23
+ const { errors } = data.assignAnsibleRoles;
24
+ if (Array.isArray(errors) && errors.length > 0) {
25
+ showToast({
26
+ type: 'error',
27
+ message: formatError(joinErrors(errors)),
28
+ });
29
+ } else {
30
+ closeModal();
31
+ showToast({
32
+ type: 'success',
33
+ message: __('Ansible Roles were successfully assigned.'),
34
+ });
35
+ }
36
+ };
37
+
38
+ export const onError = error => {
39
+ showToast({ type: 'error', message: formatError(error) });
40
+ };
@@ -0,0 +1,82 @@
1
+ import React from 'react';
2
+ import { translate as __ } from 'foremanReact/common/I18n';
3
+ import PropTypes from 'prop-types';
4
+
5
+ import { Modal, Button, ModalVariant } from '@patternfly/react-core';
6
+ import { useQuery } from '@apollo/client';
7
+
8
+ import EditRolesForm from './EditRolesForm';
9
+
10
+ import availableAnsibleRoles from '../../../../../graphql/queries/hostAvailableAnsibleRoles.gql';
11
+ import { encodeId } from '../../../../../globalIdHelper';
12
+
13
+ import './EditRolesModal.scss';
14
+
15
+ const EditRolesModal = ({
16
+ assignedRoles,
17
+ isOpen,
18
+ closeModal,
19
+ hostId,
20
+ canEditHost,
21
+ }) => {
22
+ const baseModalProps = {
23
+ variant: ModalVariant.large,
24
+ isOpen,
25
+ className: 'foreman-modal',
26
+ showClose: false,
27
+ title: __('Edit Ansible Roles'),
28
+ disableFocusTrap: true,
29
+ };
30
+
31
+ const actions = [
32
+ <Button variant="link" onClick={event => closeModal()} key="close">
33
+ {__('Close')}
34
+ </Button>,
35
+ ];
36
+
37
+ const emptyWrapper = child => (
38
+ <Modal {...baseModalProps} actions={actions}>
39
+ {child}
40
+ </Modal>
41
+ );
42
+
43
+ const loadingWrapper = child => <Modal {...baseModalProps}>{child}</Modal>;
44
+
45
+ const variables = {
46
+ id: encodeId('Host', hostId),
47
+ };
48
+
49
+ const useFetchFn = () =>
50
+ useQuery(availableAnsibleRoles, { variables, fetchPolicy: 'network-only' });
51
+
52
+ const renameData = data => ({
53
+ availableRoles: data.host.availableAnsibleRoles.nodes,
54
+ });
55
+
56
+ return (
57
+ <EditRolesForm
58
+ emptyWrapper={emptyWrapper}
59
+ loadingWrapper={loadingWrapper}
60
+ actions={actions}
61
+ baseModalProps={baseModalProps}
62
+ fetchFn={useFetchFn}
63
+ renameData={renameData}
64
+ renamedDataPath="availableRoles"
65
+ assignedRoles={assignedRoles}
66
+ closeModal={closeModal}
67
+ hostId={hostId}
68
+ allowed={canEditHost}
69
+ requiredPermissions={['edit_hosts']}
70
+ />
71
+ );
72
+ };
73
+
74
+ EditRolesModal.propTypes = {
75
+ assignedRoles: PropTypes.array.isRequired,
76
+ isOpen: PropTypes.bool.isRequired,
77
+ closeModal: PropTypes.func.isRequired,
78
+ hostId: PropTypes.number.isRequired,
79
+ canEditHost: PropTypes.bool.isRequired,
80
+ };
81
+
82
+ export default EditRolesModal;
@@ -0,0 +1,129 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { translate as __ } from 'foremanReact/common/I18n';
4
+ import { Route, Link } from 'react-router-dom';
5
+ import { usePaginationOptions } from 'foremanReact/components/Pagination/PaginationHooks';
6
+
7
+ import {
8
+ TableComposable,
9
+ Thead,
10
+ Tbody,
11
+ Tr,
12
+ Th,
13
+ Td,
14
+ } from '@patternfly/react-table';
15
+ import { Flex, FlexItem, Button, Pagination } from '@patternfly/react-core';
16
+
17
+ import EditRolesModal from './EditRolesModal';
18
+
19
+ import withLoading from '../../../withLoading';
20
+ import AllRolesModal from './AllRolesModal';
21
+ import {
22
+ preparePerPageOptions,
23
+ refreshPage,
24
+ } from '../../../../helpers/paginationHelper';
25
+
26
+ const RolesTable = ({
27
+ totalCount,
28
+ pagination,
29
+ history,
30
+ ansibleRoles,
31
+ hostId,
32
+ hostGlobalId,
33
+ canEditHost,
34
+ }) => {
35
+ const columns = [__('Name')];
36
+
37
+ const handlePerPageSelected = (event, perPage) => {
38
+ refreshPage(history, { page: 1, perPage });
39
+ };
40
+
41
+ const handlePageSelected = (event, page) => {
42
+ refreshPage(history, { ...pagination, page });
43
+ };
44
+
45
+ const perPageOptions = preparePerPageOptions(usePaginationOptions());
46
+
47
+ const editBtn = canEditHost ? (
48
+ <FlexItem>
49
+ <Link to="/Ansible/roles/edit">
50
+ <Button aria-label="edit ansible roles">
51
+ {__('Edit Ansible Roles')}
52
+ </Button>
53
+ </Link>
54
+ </FlexItem>
55
+ ) : null;
56
+
57
+ return (
58
+ <React.Fragment>
59
+ <Flex>
60
+ <FlexItem>
61
+ <h3>
62
+ <span>{__('Ansible roles assigned directly to host')}</span>
63
+ <span>{' - '}</span>
64
+ <Link to="/Ansible/roles/all">{__('view all assigned roles')}</Link>
65
+ </h3>
66
+ </FlexItem>
67
+ </Flex>
68
+ <Flex>
69
+ <FlexItem>{editBtn}</FlexItem>
70
+ <FlexItem align={{ default: 'alignRight' }}>
71
+ <Pagination
72
+ itemCount={totalCount}
73
+ page={pagination.page}
74
+ perPage={pagination.perPage}
75
+ onSetPage={handlePageSelected}
76
+ onPerPageSelect={handlePerPageSelected}
77
+ perPageOptions={perPageOptions}
78
+ variant="top"
79
+ />
80
+ </FlexItem>
81
+ </Flex>
82
+ <TableComposable variant="compact">
83
+ <Thead>
84
+ <Tr>
85
+ {columns.map(col => (
86
+ <Th key={col}>{col}</Th>
87
+ ))}
88
+ </Tr>
89
+ </Thead>
90
+ <Tbody>
91
+ {ansibleRoles.map(role => (
92
+ <Tr key={role.id}>
93
+ <Td>{role.name}</Td>
94
+ </Tr>
95
+ ))}
96
+ </Tbody>
97
+ </TableComposable>
98
+ <Route path="/Ansible/roles/edit">
99
+ <EditRolesModal
100
+ closeModal={() => history.goBack()}
101
+ isOpen
102
+ assignedRoles={ansibleRoles}
103
+ hostId={hostId}
104
+ canEditHost={canEditHost}
105
+ />
106
+ </Route>
107
+ <Route path="/Ansible/roles/all">
108
+ <AllRolesModal
109
+ onClose={() => history.push('/Ansible/roles')}
110
+ isOpen
111
+ hostGlobalId={hostGlobalId}
112
+ history={history}
113
+ />
114
+ </Route>
115
+ </React.Fragment>
116
+ );
117
+ };
118
+
119
+ RolesTable.propTypes = {
120
+ ansibleRoles: PropTypes.array.isRequired,
121
+ hostId: PropTypes.number.isRequired,
122
+ hostGlobalId: PropTypes.string.isRequired,
123
+ history: PropTypes.object.isRequired,
124
+ pagination: PropTypes.object.isRequired,
125
+ totalCount: PropTypes.number.isRequired,
126
+ canEditHost: PropTypes.bool.isRequired,
127
+ };
128
+
129
+ export default withLoading(RolesTable);
@@ -0,0 +1,85 @@
1
+ import React from 'react';
2
+ import { render, screen, waitFor } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+ import userEvent from '@testing-library/user-event';
5
+
6
+ import RolesTab from '../';
7
+
8
+ import * as toasts from '../../../../../toastHelper';
9
+
10
+ import {
11
+ tick,
12
+ withMockedProvider,
13
+ withRedux,
14
+ withReactRouter,
15
+ } from '../../../../../testHelper';
16
+ import {
17
+ mocks,
18
+ hostId,
19
+ editModalOpenMocks,
20
+ assignRolesSuccessMock,
21
+ assignRolesErrorMock,
22
+ } from './RolesTab.fixtures';
23
+
24
+ const TestComponent = withReactRouter(withRedux(withMockedProvider(RolesTab)));
25
+
26
+ describe('assigning Ansible roles', () => {
27
+ it('should assign Ansible roles', async () => {
28
+ const showToast = jest.fn();
29
+ jest.spyOn(toasts, 'showToast').mockImplementation(showToast);
30
+
31
+ render(
32
+ <TestComponent
33
+ hostId={hostId}
34
+ mocks={mocks.concat(editModalOpenMocks).concat(assignRolesSuccessMock)}
35
+ canEditHost
36
+ />
37
+ );
38
+ await waitFor(tick);
39
+ userEvent.click(screen.getByRole('button', { name: 'edit ansible roles' }));
40
+ await waitFor(tick);
41
+ await waitFor(tick);
42
+ expect(screen.getByText('Available options')).toBeInTheDocument();
43
+ userEvent.click(screen.getAllByText('another.role')[1]);
44
+ userEvent.click(screen.getByRole('button', { name: 'Remove selected' }));
45
+ userEvent.click(screen.getByText('geerlingguy.ceylon'));
46
+ userEvent.click(screen.getByRole('button', { name: 'Add selected' }));
47
+ userEvent.click(
48
+ screen.getByRole('button', { name: 'submit ansible roles' })
49
+ );
50
+ await waitFor(tick);
51
+ expect(showToast).toHaveBeenCalledWith({
52
+ type: 'success',
53
+ message: 'Ansible Roles were successfully assigned.',
54
+ });
55
+ }, 8000);
56
+ it('should show errors', async () => {
57
+ const showToast = jest.fn();
58
+ jest.spyOn(toasts, 'showToast').mockImplementation(showToast);
59
+
60
+ render(
61
+ <TestComponent
62
+ hostId={hostId}
63
+ mocks={mocks.concat(editModalOpenMocks).concat(assignRolesErrorMock)}
64
+ canEditHost
65
+ />
66
+ );
67
+ await waitFor(tick);
68
+ userEvent.click(screen.getByRole('button', { name: 'edit ansible roles' }));
69
+ await waitFor(tick);
70
+ expect(screen.getByText('Available options')).toBeInTheDocument();
71
+ userEvent.click(screen.getAllByText('another.role')[1]);
72
+ userEvent.click(screen.getByRole('button', { name: 'Remove selected' }));
73
+ userEvent.click(screen.getByText('geerlingguy.ceylon'));
74
+ userEvent.click(screen.getByRole('button', { name: 'Add selected' }));
75
+ userEvent.click(
76
+ screen.getByRole('button', { name: 'submit ansible roles' })
77
+ );
78
+ await waitFor(tick);
79
+ expect(showToast).toHaveBeenCalledWith({
80
+ type: 'error',
81
+ message:
82
+ 'There was a following error when assigning Ansible Roles: is invalid',
83
+ });
84
+ }, 8000);
85
+ });