foreman_puppet 11.0.0 → 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 (34) 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/services/concerns/foreman_puppet/extensions/bulk_hosts_manager.rb +18 -0
  5. data/config/api_routes.rb +1 -0
  6. data/db/seeds.d/111_puppet_proxy_feature.rb +1 -1
  7. data/lib/foreman_puppet/register.rb +1 -0
  8. data/lib/foreman_puppet/version.rb +1 -1
  9. data/test/controllers/foreman_puppet/api/v2/hosts_bulk_actions_controller_test.rb +93 -0
  10. data/test/services/foreman_puppet/bulk_hosts_manager_test.rb +45 -0
  11. data/webpack/__mocks__/foremanReact/redux/API/index.js +8 -0
  12. data/webpack/global_index.js +18 -1
  13. data/webpack/src/Extends/Hosts/ActionsBar/index.js +20 -0
  14. data/webpack/src/Extends/Hosts/BulkActions/BulkChangeProxyCommon/__tests__/actions.test.js +0 -7
  15. data/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetEnvironment/BulkChangePuppetEnvironmentModal.js +239 -0
  16. data/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetEnvironment/__tests__/BulkChangePuppetEnvironmentModal.test.js +155 -0
  17. data/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetEnvironment/__tests__/actions.test.js +49 -0
  18. data/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetEnvironment/__tests__/index.test.js +66 -0
  19. data/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetEnvironment/actions.js +37 -0
  20. data/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetEnvironment/index.js +30 -0
  21. data/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetProxy/__tests__/index.test.js +160 -47
  22. data/webpack/src/Extends/Hosts/BulkActions/BulkRemoveProxyCommon/__tests__/actions.test.js +0 -6
  23. data/webpack/src/Extends/Hosts/BulkActions/BulkRemovePuppetCAProxy/__tests__/index.test.js +136 -39
  24. data/webpack/src/Extends/Hosts/BulkActions/BulkRemovePuppetEnvironment/BulkRemovePuppetEnvironmentModal.js +148 -0
  25. data/webpack/src/Extends/Hosts/BulkActions/BulkRemovePuppetEnvironment/__tests__/BulkRemovePuppetEnvironmentModal.test.js +80 -0
  26. data/webpack/src/Extends/Hosts/BulkActions/BulkRemovePuppetEnvironment/__tests__/index.test.js +66 -0
  27. data/webpack/src/Extends/Hosts/BulkActions/BulkRemovePuppetEnvironment/index.js +30 -0
  28. data/webpack/src/Extends/Hosts/BulkActions/BulkRemovePuppetProxy/__tests__/index.test.js +123 -34
  29. data/webpack/src/Router/routes.fixtures.js +13 -0
  30. data/webpack/src/Router/routes.test.js +68 -8
  31. data/webpack/src/foreman_puppet_host_form.test.js +3 -1
  32. data/webpack/test_setup.js +1 -0
  33. metadata +14 -3
  34. data/webpack/src/Router/__snapshots__/routes.test.js.snap +0 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5a50beabd870a36bed2dbf49653e80e1766178fe79922553a227ce8ea54563c2
4
- data.tar.gz: d68b2c95b690b4d5b9e38100c14d06ea0911de97587660708a0ab0b68e325e45
3
+ metadata.gz: 5aa351556f85774a9796437e9ee6fd0671f46db4bb3519aa6a94c1691c90d6ca
4
+ data.tar.gz: a9507d1ae1d1961d4256eba509e88e72f5ee1124d2d2532751f6f3ff7b2e2ac2
5
5
  SHA512:
6
- metadata.gz: d26f5e28b2c205a3484811497f0e332fef3d0ec1afede88e2ab81716038d1a103b9d8a43c1ce65c6cd4b7e77d6723ae8d0eef2fb69e3a0fd415e3b1ded2e604f
7
- data.tar.gz: 37ecf68758a85dedc51f3856156bce673675b9d22fc9acec8c75848a7d819a91c022cfd1da4b08c4106b8d46e6591e00b16d1cb97f386a90c2172560c008529a
6
+ metadata.gz: 31593435b997b9202741f97a709aa40bbd57ed82f6c09aeadbe8e83432d0273b0f2523aebe0a13cf03f3e534592418091f21da8eb768cad3dfc7f0a68c1c3277
7
+ data.tar.gz: 4625c5cfbefe7af792e45ffad193ca1f0867aac29ac3596c003072aec34c366d3ade137244a861802ddab8d75c45651343c76901d871bfd0b145509ea4a67df1
@@ -126,7 +126,7 @@ module ForemanPuppet
126
126
 
127
127
  def get_environment_for(host, id)
128
128
  if id == 'inherit' && host.hostgroup.present?
129
- host.hostgroup.environment
129
+ host.hostgroup.puppet&.environment
130
130
  else
131
131
  ForemanPuppet::Environment.find_by(id: id)
132
132
  end
@@ -3,7 +3,9 @@ module ForemanPuppet
3
3
  module V2
4
4
  class HostsBulkActionsController < ::ForemanPuppet::Api::V2::PuppetBaseController
5
5
  include ::Api::V2::BulkHostsExtension
6
- before_action :find_editable_hosts, only: %i[change_puppet_proxy remove_puppet_proxy]
6
+ before_action :find_editable_hosts, only: %i[change_puppet_environment change_puppet_proxy remove_puppet_proxy]
7
+ before_action :find_environment, only: %i[change_puppet_environment]
8
+ before_action :validate_environment_taxonomies, only: %i[change_puppet_environment]
7
9
  before_action :find_smart_proxy, only: %i[change_puppet_proxy]
8
10
 
9
11
  def_param_group :bulk_params do
@@ -17,6 +19,27 @@ module ForemanPuppet
17
19
  end
18
20
  end
19
21
 
22
+ api :PUT, '/hosts/bulk/change_puppet_environment', N_('Change Puppet environment')
23
+ param_group :bulk_params
24
+ param :environment_id, String, required: false, desc: N_('ID of the Puppet environment to set for the selected hosts')
25
+ def change_puppet_environment
26
+ error_hosts = ::BulkHostsManager.new(hosts: @hosts).change_puppet_environment(resolved_environment)
27
+
28
+ process_bulk_response(
29
+ error_hosts,
30
+ success_message: format(n_(
31
+ 'Updated host: changed environment',
32
+ 'Updated hosts: changed environment',
33
+ @hosts.count
34
+ )),
35
+ error_message: format(n_(
36
+ 'Failed to change environment for %{count} host',
37
+ 'Failed to change environment for %{count} hosts',
38
+ error_hosts.count
39
+ ), count: error_hosts.count)
40
+ )
41
+ end
42
+
20
43
  api :PUT, '/hosts/bulk/change_puppet_proxy', N_('Change Puppet (CA) Proxy')
21
44
  param_group :bulk_params
22
45
  param :proxy_id, :number, required: true, desc: N_('ID of the Puppet proxy to reassign the hosts to')
@@ -65,6 +88,14 @@ module ForemanPuppet
65
88
  end
66
89
 
67
90
  def process_bulk_puppet_proxy_response(error_hosts, success_message:, error_message:)
91
+ process_bulk_response(
92
+ error_hosts,
93
+ success_message: success_message,
94
+ error_message: error_message
95
+ )
96
+ end
97
+
98
+ def process_bulk_response(error_hosts, success_message:, error_message:)
68
99
  if error_hosts.empty?
69
100
  process_response(true, { message: success_message })
70
101
  else
@@ -93,6 +124,49 @@ module ForemanPuppet
93
124
  Foreman::Cast.to_bool(params[:ca_proxy])
94
125
  end
95
126
 
127
+ def find_environment
128
+ return true if environment_value == 'inherit' || environment_value.blank?
129
+
130
+ @environment = ForemanPuppet::Environment.find_by(id: environment_value)
131
+ return true if @environment.present?
132
+
133
+ render json: {
134
+ error: {
135
+ message: format(_('A Puppet environment with id %{id} could not be found.'), id: environment_value),
136
+ },
137
+ }, status: :unprocessable_entity
138
+ false
139
+ end
140
+
141
+ def validate_environment_taxonomies
142
+ return true if resolved_environment.blank? || resolved_environment == 'inherit'
143
+
144
+ invalid_host_ids = @hosts.reject do |host|
145
+ ForemanPuppet::Environment.with_taxonomy_scope(host.organization, host.location)
146
+ .exists?(id: resolved_environment.id)
147
+ end.map(&:id)
148
+
149
+ return true if invalid_host_ids.empty?
150
+
151
+ render_error(:bulk_hosts_error, status: :unprocessable_entity,
152
+ locals: {
153
+ message: _('Selected Puppet environment is not assigned to the proper organization and/or location for all hosts.'),
154
+ failed_host_ids: invalid_host_ids,
155
+ })
156
+ false
157
+ end
158
+
159
+ def environment_value
160
+ params[:environment_id]
161
+ end
162
+
163
+ def resolved_environment
164
+ return 'inherit' if environment_value == 'inherit'
165
+ return nil if environment_value.blank?
166
+
167
+ @environment
168
+ end
169
+
96
170
  def proxy_type
97
171
  ca_proxy? ? _('Puppet CA proxy') : _('Puppet proxy')
98
172
  end
@@ -3,6 +3,24 @@ module ForemanPuppet
3
3
  module BulkHostsManager
4
4
  extend ActiveSupport::Concern
5
5
 
6
+ def change_puppet_environment(environment)
7
+ error_hosts = []
8
+ @hosts.each do |host|
9
+ puppet = host.puppet || host.build_puppet
10
+ puppet.environment = if environment == 'inherit'
11
+ host.hostgroup&.puppet&.environment
12
+ else
13
+ environment
14
+ end
15
+ host.save(validate: false)
16
+ rescue StandardError => e
17
+ message = format(_('Failed to set Puppet environment for %{host}.'), host: host)
18
+ Foreman::Logging.exception(message, e)
19
+ error_hosts << host.id
20
+ end
21
+ error_hosts
22
+ end
23
+
6
24
  def change_puppet_proxy(proxy, is_ca_proxy)
7
25
  error_hosts = []
8
26
  @hosts.each do |host|
data/config/api_routes.rb CHANGED
@@ -4,6 +4,7 @@ Foreman::Application.routes.draw do
4
4
  scope '(:apiv)', module: :v2, defaults: { apiv: 'v2' }, apiv: /v1|v2/, constraints: ApiConstraints.new(version: 2, default: true) do
5
5
  match 'hosts/bulk/change_puppet_proxy', to: 'hosts_bulk_actions#change_puppet_proxy', via: [:put]
6
6
  match 'hosts/bulk/remove_puppet_proxy', to: 'hosts_bulk_actions#remove_puppet_proxy', via: [:put]
7
+ match 'hosts/bulk/change_puppet_environment', to: 'hosts_bulk_actions#change_puppet_environment', via: [:put]
7
8
  end
8
9
  end
9
10
  end
@@ -4,5 +4,5 @@ proxy_features = %w[Puppet]
4
4
 
5
5
  proxy_features.each do |f_name|
6
6
  f = Feature.where(name: f_name).first_or_create
7
- raise "Unable to create proxy feature: #{format_errors f}" if f.nil? || f.errors.any?
7
+ raise "Unable to create proxy feature: #{SeedHelper.format_errors(f)}" if f.nil? || f.errors.any?
8
8
  end
@@ -62,6 +62,7 @@ Foreman::Plugin.register :foreman_puppet do
62
62
  p.actions << 'hosts/update_multiple_environment'
63
63
  p.actions << 'hosts/select_multiple_puppet_proxy'
64
64
  p.actions << 'hosts/update_multiple_puppet_proxy'
65
+ p.actions << 'foreman_puppet/api/v2/hosts_bulk_actions/change_puppet_environment'
65
66
  p.actions << 'foreman_puppet/api/v2/hosts_bulk_actions/change_puppet_proxy'
66
67
  p.actions << 'foreman_puppet/api/v2/hosts_bulk_actions/remove_puppet_proxy'
67
68
  end
@@ -1,3 +1,3 @@
1
1
  module ForemanPuppet
2
- VERSION = '11.0.0'.freeze
2
+ VERSION = '11.1.0'.freeze
3
3
  end
@@ -16,8 +16,101 @@ module ForemanPuppet
16
16
  organization: host.organization,
17
17
  location: host.location)
18
18
  end
19
+ let(:environment) { FactoryBot.create(:environment, organizations: [host.organization], locations: [host.location]) }
20
+ let(:hostgroup_environment) { FactoryBot.create(:environment, organizations: [host.organization], locations: [host.location]) }
21
+ let(:hostgroup) do
22
+ FactoryBot.create(:hostgroup, :with_puppet_enc,
23
+ environment: hostgroup_environment,
24
+ organizations: [host.organization],
25
+ locations: [host.location])
26
+ end
19
27
  let(:proxy) { FactoryBot.create(:puppet_and_ca_smart_proxy, organizations: [host.organization], locations: [host.location]) }
20
28
 
29
+ def put_change_puppet_environment(params:, session: nil)
30
+ put :change_puppet_environment, params: params, session: session
31
+ end
32
+
33
+ test 'changes puppet environment for selected hosts' do
34
+ put_change_puppet_environment(params: bulk_params.merge(environment_id: environment.id))
35
+
36
+ assert_response :success
37
+ assert_equal environment.id, host2.reload.puppet.environment_id
38
+ assert_equal environment.id, host.reload.puppet.environment_id
39
+ end
40
+
41
+ test 'inherits puppet environment from hostgroups for selected hosts' do
42
+ host.update!(hostgroup: hostgroup)
43
+ host2.update!(hostgroup: hostgroup)
44
+
45
+ put_change_puppet_environment(params: bulk_params.merge(environment_id: 'inherit'))
46
+
47
+ assert_response :success
48
+ assert_equal hostgroup_environment.id, host2.reload.puppet.environment_id
49
+ assert_equal hostgroup_environment.id, host.reload.puppet.environment_id
50
+ end
51
+
52
+ test 'clears puppet environment for selected hosts' do
53
+ host.puppet.update!(environment: environment)
54
+ host2.puppet.update!(environment: environment)
55
+
56
+ put_change_puppet_environment(
57
+ params: bulk_params.merge(environment_id: nil),
58
+ session: set_session_user
59
+ )
60
+
61
+ assert_response :success
62
+ assert_nil host.reload.puppet.environment
63
+ assert_nil host2.reload.puppet.environment
64
+ end
65
+
66
+ test 'returns error when puppet environment is missing' do
67
+ missing_environment_id = 999_999
68
+
69
+ put_change_puppet_environment(
70
+ params: bulk_params.merge(environment_id: missing_environment_id),
71
+ session: set_session_user
72
+ )
73
+
74
+ assert_response :unprocessable_entity
75
+ response = JSON.parse(@response.body)
76
+ assert_equal "A Puppet environment with id #{missing_environment_id} could not be found.",
77
+ response.dig('error', 'message')
78
+ end
79
+
80
+ test 'returns error when changing puppet environment fails for some hosts' do
81
+ ::BulkHostsManager.any_instance.expects(:change_puppet_environment)
82
+ .with(environment)
83
+ .returns([host2.id])
84
+
85
+ put_change_puppet_environment(
86
+ params: bulk_params.merge(environment_id: environment.id),
87
+ session: set_session_user
88
+ )
89
+
90
+ assert_response :unprocessable_entity
91
+ response = JSON.parse(@response.body)
92
+ assert_equal 'Failed to change environment for 1 host',
93
+ response.dig('error', 'message')
94
+ assert_equal [host2.id], response.dig('error', 'failed_host_ids')
95
+ end
96
+
97
+ test 'returns error when puppet environment is outside host taxonomies' do
98
+ invalid_environment = FactoryBot.create(:environment,
99
+ organizations: [host.organization],
100
+ locations: [FactoryBot.create(:location)])
101
+
102
+ put_change_puppet_environment(
103
+ params: bulk_params.merge(environment_id: invalid_environment.id),
104
+ session: set_session_user
105
+ )
106
+
107
+ assert_response :unprocessable_entity
108
+ response = JSON.parse(@response.body)
109
+ assert_equal 'Selected Puppet environment is not assigned to the proper organization and/or location for all hosts.',
110
+ response.dig('error', 'message')
111
+ assert_equal [host.id, host2.id].sort, response.dig('error', 'failed_host_ids').sort
112
+ end
113
+
21
114
  test 'changes puppet proxy for selected hosts' do
22
115
  put :change_puppet_proxy,
23
116
  params: bulk_params.merge(proxy_id: proxy.id, ca_proxy: false)
@@ -4,6 +4,51 @@ class BulkHostsManagerTest < ActiveSupport::TestCase
4
4
  let(:hosts) { FactoryBot.create_list(:host, 2, :with_puppet_enc) }
5
5
  let(:manager) { ::BulkHostsManager.new(hosts: hosts) }
6
6
  let(:proxy) { FactoryBot.create(:puppet_smart_proxy) }
7
+ let(:environment) { FactoryBot.create(:environment, organizations: [hosts.first.organization], locations: [hosts.first.location]) }
8
+
9
+ test 'changes puppet environment for hosts' do
10
+ manager.change_puppet_environment(environment)
11
+
12
+ hosts.each do |host|
13
+ assert_equal environment.id, host.reload.puppet.environment_id
14
+ end
15
+ end
16
+
17
+ test 'inherits puppet environment from hostgroup' do
18
+ organization = FactoryBot.create(:organization)
19
+ location = FactoryBot.create(:location)
20
+ inherited_environment = FactoryBot.create(:environment, organizations: [organization], locations: [location])
21
+ initial_environment = FactoryBot.create(:environment, organizations: [organization], locations: [location])
22
+ hostgroup = FactoryBot.create(:hostgroup, :with_puppet_enc,
23
+ environment: inherited_environment,
24
+ organizations: [organization],
25
+ locations: [location])
26
+ inherited_hosts = FactoryBot.create_list(:host, 2, :with_puppet_enc,
27
+ environment: initial_environment,
28
+ hostgroup: hostgroup,
29
+ organization: organization,
30
+ location: location)
31
+
32
+ inherited_hosts.each do |host|
33
+ assert_not_equal inherited_environment.id, host.reload.puppet.environment_id
34
+ end
35
+
36
+ ::BulkHostsManager.new(hosts: inherited_hosts).change_puppet_environment('inherit')
37
+
38
+ inherited_hosts.each do |host|
39
+ assert_equal inherited_environment.id, host.reload.puppet.environment_id
40
+ end
41
+ end
42
+
43
+ test 'clears puppet environment when environment is nil' do
44
+ hosts.each { |host| host.puppet.update!(environment: environment) }
45
+
46
+ manager.change_puppet_environment(nil)
47
+
48
+ hosts.each do |host|
49
+ assert_nil host.reload.puppet.environment
50
+ end
51
+ end
7
52
 
8
53
  test 'changes puppet proxy for hosts' do
9
54
  manager.change_puppet_proxy(proxy, false)
@@ -6,5 +6,13 @@ export const API = {
6
6
  patch: jest.fn(),
7
7
  };
8
8
 
9
+ export const APIActions = {
10
+ get: jest.fn(),
11
+ post: jest.fn(),
12
+ put: jest.fn(),
13
+ patch: jest.fn(),
14
+ delete: jest.fn(),
15
+ };
16
+
9
17
  export const get = data => ({ type: 'get-some-type', ...data });
10
18
  export const post = data => ({ type: 'post-some-type', ...data });
@@ -7,12 +7,15 @@ import reducers from './src/reducers';
7
7
  import { registerFills } from './src/Extends/Fills';
8
8
  import { registerLegacy } from './legacy';
9
9
  import HostsIndexActionsBar from './src/Extends/Hosts/ActionsBar';
10
+ import BulkChangePuppetEnvironment from './src/Extends/Hosts/BulkActions/BulkChangePuppetEnvironment';
11
+ import BulkRemovePuppetEnvironment from './src/Extends/Hosts/BulkActions/BulkRemovePuppetEnvironment';
10
12
  import BulkChangePuppetProxy from './src/Extends/Hosts/BulkActions/BulkChangePuppetProxy';
11
13
  import BulkChangePuppetCAProxy from './src/Extends/Hosts/BulkActions/BulkChangePuppetCAProxy';
12
14
  import BulkRemovePuppetProxy from './src/Extends/Hosts/BulkActions/BulkRemovePuppetProxy';
13
15
  import BulkRemovePuppetCAProxy from './src/Extends/Hosts/BulkActions/BulkRemovePuppetCAProxy';
14
16
 
15
17
  const GLOBAL_FILL_PRIORITY = 100;
18
+ const PUPPET_ENV_COLUMN_WEIGHT = 2700;
16
19
 
17
20
  // register reducers
18
21
  registerReducer('puppet', reducers);
@@ -28,7 +31,7 @@ const puppetHostsIndexColumns = [
28
31
  title: __('Puppet env'),
29
32
  isSorted: true,
30
33
  wrapper: hostDetails => hostDetails.environment_name,
31
- weight: 2700,
34
+ weight: PUPPET_ENV_COLUMN_WEIGHT,
32
35
  },
33
36
  ];
34
37
 
@@ -45,6 +48,20 @@ addGlobalFill(
45
48
  GLOBAL_FILL_PRIORITY
46
49
  );
47
50
 
51
+ addGlobalFill(
52
+ '_all-hosts-modals',
53
+ 'BulkChangePuppetEnvironment',
54
+ <BulkChangePuppetEnvironment key="bulk-change-puppet-environment" />,
55
+ GLOBAL_FILL_PRIORITY
56
+ );
57
+
58
+ addGlobalFill(
59
+ '_all-hosts-modals',
60
+ 'BulkRemovePuppetEnvironment',
61
+ <BulkRemovePuppetEnvironment key="bulk-remove-puppet-environment" />,
62
+ GLOBAL_FILL_PRIORITY
63
+ );
64
+
48
65
  addGlobalFill(
49
66
  '_all-hosts-modals',
50
67
  'BulkChangePuppetProxy',
@@ -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');
@@ -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;