foreman_puppet 10.1.3 → 11.1.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/foreman_puppet/extensions/hosts_controller_extensions.rb +1 -1
  3. data/app/controllers/foreman_puppet/api/v2/hosts_bulk_actions_controller.rb +75 -1
  4. data/app/models/concerns/foreman_puppet/extensions/host.rb +2 -2
  5. data/app/models/concerns/foreman_puppet/extensions/host_common.rb +35 -0
  6. data/app/models/concerns/foreman_puppet/extensions/hostgroup.rb +10 -3
  7. data/app/models/foreman_puppet/config_group.rb +6 -0
  8. data/app/models/foreman_puppet/puppetclass.rb +14 -0
  9. data/app/services/concerns/foreman_puppet/extensions/bulk_hosts_manager.rb +18 -0
  10. data/config/api_routes.rb +1 -0
  11. data/db/seeds.d/111_puppet_proxy_feature.rb +1 -1
  12. data/lib/foreman_puppet/register.rb +2 -1
  13. data/lib/foreman_puppet/version.rb +1 -1
  14. data/test/controllers/foreman_puppet/api/v2/hosts_bulk_actions_controller_test.rb +93 -0
  15. data/test/models/foreman_puppet/host_test.rb +66 -0
  16. data/test/models/foreman_puppet/hostgroup_test.rb +32 -0
  17. data/test/services/foreman_puppet/bulk_hosts_manager_test.rb +45 -0
  18. data/webpack/__mocks__/foremanReact/redux/API/index.js +8 -0
  19. data/webpack/global_index.js +18 -1
  20. data/webpack/src/Extends/Hosts/ActionsBar/index.js +20 -0
  21. data/webpack/src/Extends/Hosts/BulkActions/BulkChangeProxyCommon/__tests__/actions.test.js +0 -7
  22. data/webpack/src/Extends/Hosts/BulkActions/BulkChangeProxyCommon/index.js +9 -13
  23. data/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetCAProxy/__tests__/index.test.js +3 -0
  24. data/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetCAProxy/index.js +2 -0
  25. data/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetEnvironment/BulkChangePuppetEnvironmentModal.js +239 -0
  26. data/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetEnvironment/__tests__/BulkChangePuppetEnvironmentModal.test.js +155 -0
  27. data/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetEnvironment/__tests__/actions.test.js +49 -0
  28. data/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetEnvironment/__tests__/index.test.js +66 -0
  29. data/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetEnvironment/actions.js +37 -0
  30. data/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetEnvironment/index.js +30 -0
  31. data/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetProxy/__tests__/index.test.js +160 -44
  32. data/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetProxy/index.js +2 -0
  33. data/webpack/src/Extends/Hosts/BulkActions/BulkRemoveProxyCommon/__tests__/actions.test.js +0 -6
  34. data/webpack/src/Extends/Hosts/BulkActions/BulkRemoveProxyCommon/index.js +8 -13
  35. data/webpack/src/Extends/Hosts/BulkActions/BulkRemovePuppetCAProxy/__tests__/index.test.js +138 -38
  36. data/webpack/src/Extends/Hosts/BulkActions/BulkRemovePuppetCAProxy/index.js +2 -0
  37. data/webpack/src/Extends/Hosts/BulkActions/BulkRemovePuppetEnvironment/BulkRemovePuppetEnvironmentModal.js +148 -0
  38. data/webpack/src/Extends/Hosts/BulkActions/BulkRemovePuppetEnvironment/__tests__/BulkRemovePuppetEnvironmentModal.test.js +80 -0
  39. data/webpack/src/Extends/Hosts/BulkActions/BulkRemovePuppetEnvironment/__tests__/index.test.js +66 -0
  40. data/webpack/src/Extends/Hosts/BulkActions/BulkRemovePuppetEnvironment/index.js +30 -0
  41. data/webpack/src/Extends/Hosts/BulkActions/BulkRemovePuppetProxy/__tests__/index.test.js +125 -33
  42. data/webpack/src/Extends/Hosts/BulkActions/BulkRemovePuppetProxy/index.js +2 -0
  43. data/webpack/src/Router/routes.fixtures.js +13 -0
  44. data/webpack/src/Router/routes.test.js +68 -8
  45. data/webpack/src/foreman_puppet_host_form.test.js +3 -1
  46. data/webpack/test_setup.js +1 -0
  47. metadata +14 -3
  48. data/webpack/src/Router/__snapshots__/routes.test.js.snap +0 -3
@@ -24,6 +24,26 @@ const HostActionsBar = () => {
24
24
  <Menu ouiaId="content-flyout-menu" onSelect={() => setMenuOpen(false)}>
25
25
  <MenuContent>
26
26
  <MenuList>
27
+ <MenuItem
28
+ itemId="bulk-change-puppet-environment-menu-item"
29
+ key="bulk-change-puppet-environment-menu-item"
30
+ onClick={() =>
31
+ handleOpenBulkModal('bulk-change-puppet-environment')
32
+ }
33
+ isDisabled={selectedCount === 0}
34
+ >
35
+ {__('Change Puppet environment')}
36
+ </MenuItem>
37
+ <MenuItem
38
+ itemId="bulk-remove-puppet-environment-menu-item"
39
+ key="bulk-remove-puppet-environment-menu-item"
40
+ onClick={() =>
41
+ handleOpenBulkModal('bulk-remove-puppet-environment')
42
+ }
43
+ isDisabled={selectedCount === 0}
44
+ >
45
+ {__('Remove Puppet environment')}
46
+ </MenuItem>
27
47
  <MenuItem
28
48
  itemId="bulk-change-puppet-proxy-menu-item"
29
49
  key="bulk-change-puppet-proxy-menu-item"
@@ -9,13 +9,6 @@ import {
9
9
  BULK_CHANGE_PUPPET_PROXY_KEY,
10
10
  } from '../actions';
11
11
 
12
- jest.mock('foremanReact/redux/API', () => ({
13
- APIActions: {
14
- get: jest.fn(),
15
- put: jest.fn(),
16
- },
17
- }));
18
-
19
12
  describe('BulkChangeProxyCommon actions', () => {
20
13
  const smartProxiesUrl = foremanUrl('/api/smart_proxies');
21
14
  const bulkChangeUrl = foremanUrl('/api/v2/hosts/bulk/change_puppet_proxy');
@@ -15,17 +15,11 @@ import {
15
15
  } from '@patternfly/react-core';
16
16
  import { addToast } from 'foremanReact/components/ToastsList/slice';
17
17
  import { sprintf, translate as __ } from 'foremanReact/common/I18n';
18
- import { foremanUrl } from 'foremanReact/common/helpers';
19
- import { APIActions } from 'foremanReact/redux/API';
20
18
  import { STATUS } from 'foremanReact/constants';
21
19
  import {
22
20
  selectAPIStatus,
23
21
  selectAPIResponse,
24
22
  } from 'foremanReact/redux/API/APISelectors';
25
- import {
26
- HOSTS_API_PATH,
27
- API_REQUEST_KEY,
28
- } from 'foremanReact/routes/Hosts/constants';
29
23
  import {
30
24
  fetchSmartProxies,
31
25
  SMART_PROXY_KEY,
@@ -47,6 +41,7 @@ const BulkChangeProxyCommon = ({
47
41
  allHostsMessage,
48
42
  someHostsMessage,
49
43
  isCAProxy,
44
+ onSuccess: onSuccessCallback,
50
45
  }) => {
51
46
  const dispatch = useDispatch();
52
47
  const [smartProxyId, setSmartProxyId] = useState('');
@@ -82,6 +77,7 @@ const BulkChangeProxyCommon = ({
82
77
 
83
78
  const toggle = toggleRef => (
84
79
  <MenuToggle
80
+ ouiaId="bulk-change-proxy-common-select-toggle"
85
81
  ref={toggleRef}
86
82
  onClick={onToggleClick}
87
83
  isExpanded={smartProxySelectOpen}
@@ -112,13 +108,11 @@ const BulkChangeProxyCommon = ({
112
108
  message: response.data.message,
113
109
  })
114
110
  );
115
- dispatch(
116
- APIActions.get({
117
- key: API_REQUEST_KEY,
118
- url: foremanUrl(HOSTS_API_PATH),
119
- })
120
- );
121
- handleModalClose();
111
+ try {
112
+ if (onSuccessCallback) onSuccessCallback();
113
+ } finally {
114
+ handleModalClose();
115
+ }
122
116
  };
123
117
 
124
118
  const handleConfirm = () => {
@@ -242,11 +236,13 @@ BulkChangeProxyCommon.propTypes = {
242
236
  allHostsMessage: PropTypes.string.isRequired,
243
237
  someHostsMessage: PropTypes.string.isRequired,
244
238
  isCAProxy: PropTypes.bool.isRequired,
239
+ onSuccess: PropTypes.func,
245
240
  };
246
241
 
247
242
  BulkChangeProxyCommon.defaultProps = {
248
243
  isOpen: false,
249
244
  closeModal: () => {},
245
+ onSuccess: undefined,
250
246
  };
251
247
 
252
248
  export default BulkChangeProxyCommon;
@@ -18,11 +18,13 @@ jest.mock('../../BulkChangeProxyCommon', () => ({
18
18
 
19
19
  describe('BulkChangePuppetCAProxyScene', () => {
20
20
  const fetchBulkParams = jest.fn();
21
+ const refreshTableData = jest.fn();
21
22
  const contextValue = {
22
23
  selectAllHostsMode: false,
23
24
  selectedCount: 2,
24
25
  selectedResults: [1, 2],
25
26
  fetchBulkParams,
27
+ refreshTableData,
26
28
  };
27
29
 
28
30
  beforeEach(() => {
@@ -51,6 +53,7 @@ describe('BulkChangePuppetCAProxyScene', () => {
51
53
  selectAllHostsMode: false,
52
54
  isOpen: true,
53
55
  closeModal: expect.any(Function),
56
+ onSuccess: refreshTableData,
54
57
  selectMessage: 'Select a Puppet CA Proxy',
55
58
  handleErrorMessage: 'Failed to change Puppet CA Proxy',
56
59
  changeMessage: 'Change Puppet CA Proxy',
@@ -11,6 +11,7 @@ const BulkChangePuppetCAProxyScene = () => {
11
11
  selectedCount,
12
12
  selectedResults,
13
13
  fetchBulkParams,
14
+ refreshTableData,
14
15
  } = useContext(ForemanActionsBarContext);
15
16
  const { isOpen, close: closeModal } = useBulkModalOpen(
16
17
  'bulk-change-puppet-ca-proxy'
@@ -24,6 +25,7 @@ const BulkChangePuppetCAProxyScene = () => {
24
25
  selectAllHostsMode={selectAllHostsMode}
25
26
  isOpen={isOpen}
26
27
  closeModal={closeModal}
28
+ onSuccess={refreshTableData}
27
29
  selectMessage={__('Select a Puppet CA Proxy')}
28
30
  handleErrorMessage={__('Failed to change Puppet CA Proxy')}
29
31
  changeMessage={__('Change Puppet CA Proxy')}
@@ -0,0 +1,239 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { FormattedMessage } from 'react-intl';
4
+ import { useDispatch, useSelector } from 'react-redux';
5
+ import {
6
+ Modal,
7
+ Button,
8
+ TextContent,
9
+ Text,
10
+ Select,
11
+ SelectOption,
12
+ SelectList,
13
+ MenuToggle,
14
+ } from '@patternfly/react-core';
15
+ import { translate as __ } from 'foremanReact/common/I18n';
16
+ import { addToast } from 'foremanReact/components/ToastsList/slice';
17
+ import { useForemanOrganization } from 'foremanReact/Root/Context/ForemanContext';
18
+ import {
19
+ selectAPIStatus,
20
+ selectAPIResponse,
21
+ } from 'foremanReact/redux/API/APISelectors';
22
+ import { STATUS } from 'foremanReact/constants';
23
+ import { bulkActionErrorToastParams } from '../toastHelpers';
24
+ import {
25
+ fetchEnvironments,
26
+ bulkChangePuppetEnvironment,
27
+ PUPPET_ENVIRONMENTS_KEY,
28
+ BULK_CHANGE_PUPPET_ENVIRONMENT_KEY,
29
+ INHERIT_ENVIRONMENT,
30
+ } from './actions';
31
+
32
+ const BulkChangePuppetEnvironmentModal = ({
33
+ isOpen,
34
+ closeModal,
35
+ selectAllHostsMode,
36
+ selectedCount,
37
+ fetchBulkParams,
38
+ onSuccess: onSuccessCallback,
39
+ }) => {
40
+ const dispatch = useDispatch();
41
+ const [environmentId, setEnvironmentId] = useState(null);
42
+ const [environmentSelectOpen, setEnvironmentSelectOpen] = useState(false);
43
+ const currentOrganization = useForemanOrganization();
44
+
45
+ useEffect(() => {
46
+ dispatch(fetchEnvironments(currentOrganization?.id));
47
+ }, [dispatch, currentOrganization?.id]);
48
+
49
+ const environments = useSelector(state =>
50
+ selectAPIResponse(state, PUPPET_ENVIRONMENTS_KEY)
51
+ );
52
+ const environmentsStatus = useSelector(state =>
53
+ selectAPIStatus(state, PUPPET_ENVIRONMENTS_KEY)
54
+ );
55
+
56
+ const onToggleClick = () => {
57
+ setEnvironmentSelectOpen(!environmentSelectOpen);
58
+ };
59
+
60
+ const handleEnvironmentSelect = (event, selection) => {
61
+ setEnvironmentId(selection);
62
+ setEnvironmentSelectOpen(false);
63
+ };
64
+
65
+ const getEnvironmentLabel = value => {
66
+ if (value === INHERIT_ENVIRONMENT) return __('*Inherit from host group*');
67
+
68
+ const selectedEnvironment = environments?.results?.find(
69
+ environment => `${environment.id}` === value
70
+ );
71
+ return selectedEnvironment?.name || __('Select an Environment');
72
+ };
73
+
74
+ const toggle = toggleRef => (
75
+ <MenuToggle
76
+ ouiaId="bulk-change-env-common-select-toggle"
77
+ ref={toggleRef}
78
+ onClick={onToggleClick}
79
+ isExpanded={environmentSelectOpen}
80
+ style={{ width: '500px' }}
81
+ >
82
+ {environmentId
83
+ ? getEnvironmentLabel(environmentId)
84
+ : __('Select an Environment')}
85
+ </MenuToggle>
86
+ );
87
+
88
+ const handleModalClose = () => {
89
+ setEnvironmentId(null);
90
+ closeModal();
91
+ };
92
+
93
+ const handleError = response => {
94
+ handleModalClose();
95
+ dispatch(
96
+ addToast(
97
+ bulkActionErrorToastParams(
98
+ response,
99
+ __('Failed to change Puppet Environment'),
100
+ BULK_CHANGE_PUPPET_ENVIRONMENT_KEY
101
+ )
102
+ )
103
+ );
104
+ };
105
+
106
+ const handleSuccess = response => {
107
+ dispatch(
108
+ addToast({
109
+ type: 'success',
110
+ message: response.data.message,
111
+ })
112
+ );
113
+ try {
114
+ if (onSuccessCallback) onSuccessCallback();
115
+ } finally {
116
+ handleModalClose();
117
+ }
118
+ };
119
+
120
+ const handleConfirm = () => {
121
+ const requestBody = {
122
+ included: {
123
+ search: fetchBulkParams(),
124
+ },
125
+ environment_id: environmentId,
126
+ organization_id: currentOrganization?.id,
127
+ };
128
+
129
+ dispatch(
130
+ bulkChangePuppetEnvironment(requestBody, handleSuccess, handleError)
131
+ );
132
+ };
133
+
134
+ const modalActions = [
135
+ <Button
136
+ key="add"
137
+ ouiaId="bulk-change-environment-modal-add-button"
138
+ variant="primary"
139
+ onClick={handleConfirm}
140
+ isDisabled={environmentId === null}
141
+ isLoading={environmentsStatus === STATUS.PENDING}
142
+ >
143
+ {__('Change Puppet Environment')}
144
+ </Button>,
145
+ <Button
146
+ key="cancel"
147
+ ouiaId="bulk-change-environment-modal-cancel-button"
148
+ variant="link"
149
+ onClick={handleModalClose}
150
+ >
151
+ {__('Cancel')}
152
+ </Button>,
153
+ ];
154
+
155
+ return (
156
+ <Modal
157
+ isOpen={isOpen}
158
+ onClose={handleModalClose}
159
+ onEscapePress={handleModalClose}
160
+ title={__('Change Puppet Environment')}
161
+ width="50%"
162
+ position="top"
163
+ actions={modalActions}
164
+ id="bulk-change-puppet-environment"
165
+ key="bulk-change-puppet-environment"
166
+ ouiaId="bulk-change-puppet-environment"
167
+ >
168
+ <TextContent>
169
+ <Text ouiaId="bulk-change-puppet-environment-options">
170
+ {selectAllHostsMode ? (
171
+ <FormattedMessage
172
+ id="bulk-change-puppet-environment-warning-message-all"
173
+ defaultMessage={__(
174
+ 'Changing the Puppet environment will affect {boldCount} selected hosts. Some hosts may already have been associated with the selected environment.'
175
+ )}
176
+ values={{
177
+ boldCount: <strong>{__('All')}</strong>,
178
+ }}
179
+ />
180
+ ) : (
181
+ <FormattedMessage
182
+ id="bulk-change-puppet-environment-warning-message"
183
+ defaultMessage={__(
184
+ 'Changing the Puppet environment will affect {boldCount} selected {count, plural, one {host} other {hosts}}. Some hosts may already have been associated with the selected environment.'
185
+ )}
186
+ values={{
187
+ count: selectedCount,
188
+ boldCount: <strong>{selectedCount}</strong>,
189
+ }}
190
+ />
191
+ )}
192
+ </Text>
193
+ </TextContent>
194
+ {environmentsStatus === STATUS.RESOLVED && (
195
+ <Select
196
+ id="bulk-change-puppet-environment-select"
197
+ isOpen={environmentSelectOpen}
198
+ selected={environmentId}
199
+ onSelect={handleEnvironmentSelect}
200
+ onOpenChange={isSelectOpen => setEnvironmentSelectOpen(isSelectOpen)}
201
+ toggle={toggle}
202
+ shouldFocusToggleOnSelect
203
+ ouiaId="bulk-change-puppet-environment-select"
204
+ >
205
+ <SelectList>
206
+ <SelectOption value={INHERIT_ENVIRONMENT}>
207
+ {__('*Inherit from host group*')}
208
+ </SelectOption>
209
+ {environments?.results?.map(environment => (
210
+ <SelectOption
211
+ key={`${environment.id}`}
212
+ value={`${environment.id}`}
213
+ >
214
+ {environment.name}
215
+ </SelectOption>
216
+ ))}
217
+ </SelectList>
218
+ </Select>
219
+ )}
220
+ </Modal>
221
+ );
222
+ };
223
+
224
+ BulkChangePuppetEnvironmentModal.propTypes = {
225
+ isOpen: PropTypes.bool,
226
+ closeModal: PropTypes.func,
227
+ fetchBulkParams: PropTypes.func.isRequired,
228
+ onSuccess: PropTypes.func,
229
+ selectedCount: PropTypes.number.isRequired,
230
+ selectAllHostsMode: PropTypes.bool.isRequired,
231
+ };
232
+
233
+ BulkChangePuppetEnvironmentModal.defaultProps = {
234
+ isOpen: false,
235
+ closeModal: () => {},
236
+ onSuccess: undefined,
237
+ };
238
+
239
+ export default BulkChangePuppetEnvironmentModal;
@@ -0,0 +1,155 @@
1
+ import React from 'react';
2
+ import { render, screen, fireEvent } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+ import { IntlProvider } from 'react-intl';
5
+ import { useDispatch, useSelector } from 'react-redux';
6
+
7
+ import BulkChangePuppetEnvironmentModal from '../BulkChangePuppetEnvironmentModal';
8
+ import { fetchEnvironments, bulkChangePuppetEnvironment } from '../actions';
9
+ import {
10
+ selectAPIResponse,
11
+ selectAPIStatus,
12
+ } from 'foremanReact/redux/API/APISelectors';
13
+
14
+ jest.mock('@patternfly/react-core', () => {
15
+ const React = require('react');
16
+ const actual = jest.requireActual('@patternfly/react-core');
17
+
18
+ const MenuToggle = React.forwardRef(({ children, onClick }, ref) => (
19
+ <button type="button" ref={ref} onClick={onClick}>
20
+ {children}
21
+ </button>
22
+ ));
23
+
24
+ const SelectOption = ({ children, value, onSelect }) => (
25
+ <button type="button" onClick={event => onSelect(event, value)}>
26
+ {children}
27
+ </button>
28
+ );
29
+
30
+ const SelectList = ({ children, onSelect }) => (
31
+ <div>
32
+ {React.Children.map(children, child =>
33
+ React.isValidElement(child)
34
+ ? React.cloneElement(child, { onSelect })
35
+ : child
36
+ )}
37
+ </div>
38
+ );
39
+
40
+ const Select = ({ children, isOpen, toggle, onSelect }) => (
41
+ <div>
42
+ {toggle()}
43
+ {isOpen &&
44
+ React.Children.map(children, child =>
45
+ React.isValidElement(child)
46
+ ? React.cloneElement(child, { onSelect })
47
+ : child
48
+ )}
49
+ </div>
50
+ );
51
+
52
+ return {
53
+ ...actual,
54
+ MenuToggle,
55
+ Select,
56
+ SelectList,
57
+ SelectOption,
58
+ };
59
+ });
60
+
61
+ jest.mock('react-redux', () => ({
62
+ ...jest.requireActual('react-redux'),
63
+ useDispatch: jest.fn(),
64
+ useSelector: jest.fn(),
65
+ }));
66
+
67
+ jest.mock('foremanReact/Root/Context/ForemanContext', () => ({
68
+ useForemanOrganization: jest.fn(() => ({ id: 23 })),
69
+ }));
70
+
71
+ jest.mock('foremanReact/redux/API/APISelectors', () => ({
72
+ selectAPIResponse: jest.fn(),
73
+ selectAPIStatus: jest.fn(),
74
+ }));
75
+
76
+ jest.mock('../actions', () => ({
77
+ fetchEnvironments: jest.fn(() => ({ type: 'FETCH_ENVIRONMENTS' })),
78
+ bulkChangePuppetEnvironment: jest.fn(() => ({
79
+ type: 'BULK_CHANGE_PUPPET_ENVIRONMENT',
80
+ })),
81
+ PUPPET_ENVIRONMENTS_KEY: 'PUPPET_ENVIRONMENTS_KEY',
82
+ BULK_CHANGE_PUPPET_ENVIRONMENT_KEY: 'BULK_CHANGE_PUPPET_ENVIRONMENT',
83
+ INHERIT_ENVIRONMENT: 'inherit',
84
+ }));
85
+
86
+ describe('BulkChangePuppetEnvironmentModal', () => {
87
+ const dispatch = jest.fn();
88
+ const fetchBulkParams = jest.fn(
89
+ () => 'organization = "Default Organization"'
90
+ );
91
+ const environmentsResponse = {
92
+ results: [{ id: 1, name: 'production' }],
93
+ };
94
+
95
+ const renderComponent = () =>
96
+ render(
97
+ <IntlProvider locale="en">
98
+ <BulkChangePuppetEnvironmentModal
99
+ isOpen
100
+ closeModal={jest.fn()}
101
+ fetchBulkParams={fetchBulkParams}
102
+ selectedCount={2}
103
+ selectAllHostsMode={false}
104
+ />
105
+ </IntlProvider>
106
+ );
107
+
108
+ beforeEach(() => {
109
+ jest.clearAllMocks();
110
+ useDispatch.mockReturnValue(dispatch);
111
+ selectAPIResponse.mockReturnValue(environmentsResponse);
112
+ selectAPIStatus.mockReturnValue('RESOLVED');
113
+ useSelector.mockImplementation(selector => selector({}));
114
+ });
115
+
116
+ it('renders the environment options', () => {
117
+ renderComponent();
118
+
119
+ expect(fetchEnvironments).toHaveBeenCalledWith(23);
120
+ expect(
121
+ screen.getAllByText('Change Puppet Environment')[0]
122
+ ).toBeInTheDocument();
123
+ expect(
124
+ screen.getByRole('button', { name: 'Select an Environment' })
125
+ ).toBeInTheDocument();
126
+ expect(
127
+ screen.getByText(/Changing the Puppet environment will affect/)
128
+ ).toBeInTheDocument();
129
+ });
130
+
131
+ it('submits organization scope with the bulk change request', () => {
132
+ renderComponent();
133
+
134
+ fireEvent.click(
135
+ screen.getByRole('button', { name: 'Select an Environment' })
136
+ );
137
+ expect(screen.getByText('*Inherit from host group*')).toBeInTheDocument();
138
+ fireEvent.click(screen.getByText('production'));
139
+ fireEvent.click(
140
+ screen.getByRole('button', { name: 'Change Puppet Environment' })
141
+ );
142
+
143
+ expect(bulkChangePuppetEnvironment).toHaveBeenCalledWith(
144
+ {
145
+ included: {
146
+ search: 'organization = "Default Organization"',
147
+ },
148
+ environment_id: '1',
149
+ organization_id: 23,
150
+ },
151
+ expect.any(Function),
152
+ expect.any(Function)
153
+ );
154
+ });
155
+ });
@@ -0,0 +1,49 @@
1
+ import { APIActions } from 'foremanReact/redux/API';
2
+ import {
3
+ fetchEnvironments,
4
+ PUPPET_ENVIRONMENTS_KEY,
5
+ bulkChangePuppetEnvironment,
6
+ BULK_CHANGE_PUPPET_ENVIRONMENT_KEY,
7
+ } from '../actions';
8
+
9
+ jest.mock('foremanReact/redux/API', () => ({
10
+ APIActions: {
11
+ get: jest.fn(),
12
+ put: jest.fn(),
13
+ },
14
+ }));
15
+
16
+ describe('BulkChangePuppetEnvironment actions', () => {
17
+ const environmentsUrl = '/foreman_puppet/api/v2/environments';
18
+ const bulkChangeUrl = '/api/v2/hosts/bulk/change_puppet_environment';
19
+
20
+ beforeEach(() => {
21
+ jest.clearAllMocks();
22
+ });
23
+
24
+ it('fetches environments', () => {
25
+ fetchEnvironments(23);
26
+
27
+ expect(APIActions.get).toHaveBeenCalledWith({
28
+ key: PUPPET_ENVIRONMENTS_KEY,
29
+ url: environmentsUrl,
30
+ params: { per_page: 'all', organization_id: 23 },
31
+ });
32
+ });
33
+
34
+ it('calls bulk change puppet environment endpoint', () => {
35
+ const params = { included: { ids: [1] }, environment_id: '1' };
36
+ const handleSuccess = jest.fn();
37
+ const handleError = jest.fn();
38
+
39
+ bulkChangePuppetEnvironment(params, handleSuccess, handleError);
40
+
41
+ expect(APIActions.put).toHaveBeenCalledWith({
42
+ key: BULK_CHANGE_PUPPET_ENVIRONMENT_KEY,
43
+ url: bulkChangeUrl,
44
+ handleSuccess,
45
+ handleError,
46
+ params,
47
+ });
48
+ });
49
+ });
@@ -0,0 +1,66 @@
1
+ import React from 'react';
2
+ import renderer, { act } from 'react-test-renderer';
3
+
4
+ import { useBulkModalOpen } from 'foremanReact/common/BulkModalStateHelper';
5
+ import { ForemanActionsBarContext } from 'foremanReact/components/HostDetails/ActionsBar';
6
+
7
+ import BulkChangePuppetEnvironmentScene from '../index';
8
+ import BulkChangePuppetEnvironmentModal from '../BulkChangePuppetEnvironmentModal';
9
+
10
+ jest.mock('foremanReact/common/BulkModalStateHelper', () => ({
11
+ useBulkModalOpen: jest.fn(),
12
+ }));
13
+
14
+ jest.mock('../BulkChangePuppetEnvironmentModal', () => ({
15
+ __esModule: true,
16
+ default: jest.fn(() => null),
17
+ }));
18
+
19
+ describe('BulkChangePuppetEnvironmentScene', () => {
20
+ const fetchBulkParams = jest.fn();
21
+ const refreshTableData = jest.fn();
22
+ const contextValue = {
23
+ selectAllHostsMode: false,
24
+ selectedCount: 2,
25
+ selectedResults: [1, 2],
26
+ fetchBulkParams,
27
+ refreshTableData,
28
+ };
29
+
30
+ beforeEach(() => {
31
+ jest.clearAllMocks();
32
+ useBulkModalOpen.mockReturnValue({
33
+ isOpen: true,
34
+ close: jest.fn(),
35
+ });
36
+ });
37
+
38
+ it('opens with bulk modal state and passes expected props', () => {
39
+ let component;
40
+ act(() => {
41
+ component = renderer.create(
42
+ <ForemanActionsBarContext.Provider value={contextValue}>
43
+ <BulkChangePuppetEnvironmentScene />
44
+ </ForemanActionsBarContext.Provider>
45
+ );
46
+ });
47
+
48
+ const componentType =
49
+ BulkChangePuppetEnvironmentModal.default ||
50
+ BulkChangePuppetEnvironmentModal;
51
+ const { props } = component.root.findByType(componentType);
52
+
53
+ expect(props).toEqual(
54
+ expect.objectContaining({
55
+ fetchBulkParams,
56
+ selectedCount: 2,
57
+ selectAllHostsMode: false,
58
+ isOpen: true,
59
+ closeModal: expect.any(Function),
60
+ onSuccess: refreshTableData,
61
+ })
62
+ );
63
+
64
+ component.unmount();
65
+ });
66
+ });
@@ -0,0 +1,37 @@
1
+ import { APIActions } from 'foremanReact/redux/API';
2
+ import { foremanUrl } from 'foremanReact/common/helpers';
3
+
4
+ export const PUPPET_ENVIRONMENTS_KEY = 'PUPPET_ENVIRONMENTS_KEY';
5
+ export const BULK_CHANGE_PUPPET_ENVIRONMENT_KEY =
6
+ 'BULK_CHANGE_PUPPET_ENVIRONMENT';
7
+
8
+ export const INHERIT_ENVIRONMENT = 'inherit';
9
+
10
+ export const fetchEnvironments = organizationId => {
11
+ const url = foremanUrl('/foreman_puppet/api/v2/environments');
12
+ return APIActions.get({
13
+ key: PUPPET_ENVIRONMENTS_KEY,
14
+ url,
15
+ params: {
16
+ per_page: 'all',
17
+ ...(organizationId ? { organization_id: organizationId } : {}),
18
+ },
19
+ });
20
+ };
21
+
22
+ export const bulkChangePuppetEnvironment = (
23
+ params,
24
+ handleSuccess,
25
+ handleError
26
+ ) => {
27
+ const url = foremanUrl('/api/v2/hosts/bulk/change_puppet_environment');
28
+ return APIActions.put({
29
+ key: BULK_CHANGE_PUPPET_ENVIRONMENT_KEY,
30
+ url,
31
+ handleSuccess,
32
+ handleError,
33
+ params,
34
+ });
35
+ };
36
+
37
+ export default fetchEnvironments;