From 6592dd23663bf22b9eb8f8839363c02591834841 Mon Sep 17 00:00:00 2001 From: Nadja Heitmann Date: Mon, 22 Jun 2026 07:40:03 +0000 Subject: [PATCH] Fixes #39437 - Apply taxonomies to UI bulk actions Before this patch, the Foreman UI did not send any information about taxonomies on the 'All Hosts' page for bulk actions. This could cause bulk actions to be applied to different hosts than the ones that were actually selected in the UI because the session in the backend would calculate the hosts independent from UI input. Assisted-by: OpenAI Codex (cherry picked from commit 34ee2c5b7d1810ced6607b14bd37d3d02b12a87d) --- .../api/v2/hosts_bulk_actions_controller.rb | 1 - .../concerns/api/v2/bulk_hosts_extension.rb | 10 +++++-- .../v2/hosts_bulk_actions_controller_test.rb | 28 +++++++++++++++++++ .../assignTaxonomy/BulkAssignTaxonomyModal.js | 17 +++++++---- .../BulkActions/assignTaxonomy/index.js | 24 ++++++++++++---- .../buildHosts/BulkBuildHostModal.js | 18 ++++++++---- .../BulkActions/buildHosts/index.js | 11 ++++++-- .../HostsIndex/BulkActions/bulkDelete.js | 9 +++++- .../changeOwner/BulkChangeOwnerModal.js | 18 ++++++++---- .../BulkActions/changeOwner/index.js | 4 +++ .../disassociate/BulkDisassociateModal.js | 19 +++++++++---- .../BulkActions/disassociate/index.js | 4 +++ .../HostsIndex/BulkActions/helpers.js | 22 +++++++++++++++ .../powerState/BulkPowerStateModal.js | 18 ++++++++---- .../BulkActions/powerState/index.js | 11 ++++++-- .../BulkReassignHostgroupModal.js | 18 ++++++++---- .../BulkActions/reassignHostGroup/index.js | 11 ++++++-- .../react_app/components/HostsIndex/index.js | 8 ++++++ .../components/HostsIndex/index.test.js | 2 ++ 19 files changed, 199 insertions(+), 54 deletions(-) diff --git a/app/controllers/api/v2/hosts_bulk_actions_controller.rb b/app/controllers/api/v2/hosts_bulk_actions_controller.rb index 4c15d89e43..d076ce67ab 100644 --- a/app/controllers/api/v2/hosts_bulk_actions_controller.rb +++ b/app/controllers/api/v2/hosts_bulk_actions_controller.rb @@ -9,7 +9,6 @@ class HostsBulkActionsController < V2::BaseController before_action :validate_power_action, :only => [:change_power_state] def_param_group :bulk_host_ids do - param :organization_id, :number, :required => true, :desc => N_("ID of the organization") param :included, Hash, :desc => N_("Hosts to include in the action"), :required => true, :action_aware => true do param :search, String, :required => false, :desc => N_("Search string describing which hosts to perform the action on") param :ids, Array, :required => false, :desc => N_("List of host ids to perform the action on") diff --git a/app/controllers/concerns/api/v2/bulk_hosts_extension.rb b/app/controllers/concerns/api/v2/bulk_hosts_extension.rb index b6d720964d..49e53b2b77 100644 --- a/app/controllers/concerns/api/v2/bulk_hosts_extension.rb +++ b/app/controllers/concerns/api/v2/bulk_hosts_extension.rb @@ -1,9 +1,10 @@ module Api::V2::BulkHostsExtension extend ActiveSupport::Concern - def bulk_hosts_relation(permission, org) + def bulk_hosts_relation(permission, org, location) relation = ::Host::Managed.authorized(permission) relation = relation.where(organization: org) if org + relation = relation.where(location: location) if location relation end @@ -18,7 +19,8 @@ def find_bulk_hosts(permission, bulk_params, restrict_to = nil) end find_organization - @hosts = bulk_hosts_relation(permission, @organization) + find_location + @hosts = bulk_hosts_relation(permission, @organization, @location) if bulk_params[:included][:ids].present? @hosts = @hosts.where(id: bulk_params[:included][:ids]) @@ -42,4 +44,8 @@ def find_bulk_hosts(permission, bulk_params, restrict_to = nil) def find_organization @organization ||= Organization.find_by_id(params[:organization_id]) end + + def find_location + @location ||= Location.find_by_id(params[:location_id]) + end end diff --git a/test/controllers/api/v2/hosts_bulk_actions_controller_test.rb b/test/controllers/api/v2/hosts_bulk_actions_controller_test.rb index 0823d2180a..7c9214d81b 100644 --- a/test/controllers/api/v2/hosts_bulk_actions_controller_test.rb +++ b/test/controllers/api/v2/hosts_bulk_actions_controller_test.rb @@ -17,6 +17,7 @@ def setup def valid_bulk_params(host_ids = @host_ids) { :organization_id => @organization.id, + :location_id => @location.id, :included => { :ids => host_ids, }, @@ -107,6 +108,33 @@ def valid_power_params(host_ids = @host_ids, action = 'start') end end + test "should scope searched hosts by organization and location" do + other_location = FactoryBot.create(:location) + other_host = FactoryBot.create(:host, :managed, :organization => @organization, :location => other_location) + + put :change_owner, params: { + :organization_id => @organization.id, + :location_id => @location.id, + :included => { + :search => 'name ~ *', + }, + :excluded => { + :ids => [], + }, + :owner_id => @user.id_and_type, + } + + assert_response :success + + [@host1, @host2, @host3].each do |host| + host.reload + assert_equal @user.id_and_type, host.is_owned_by + end + + other_host.reload + refute_equal @user.id_and_type, other_host.is_owned_by + end + context "change_power_state" do test "successfully changes power state for all hosts" do Host.any_instance.stubs(:supports_power?).returns(true) diff --git a/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/assignTaxonomy/BulkAssignTaxonomyModal.js b/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/assignTaxonomy/BulkAssignTaxonomyModal.js index 5bc8273709..3ae3450866 100644 --- a/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/assignTaxonomy/BulkAssignTaxonomyModal.js +++ b/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/assignTaxonomy/BulkAssignTaxonomyModal.js @@ -38,6 +38,7 @@ import { API_REQUEST_KEY, } from '../../../../routes/Hosts/constants'; import TaxonomySelect from './TaxonomySelect'; +import { buildBulkRequestBody } from '../helpers'; export const BulkAssignOrganizationModal = props => ( @@ -52,6 +53,8 @@ const BulkAssignTaxonomyModal = ({ selectAllHostsMode, selectedCount, fetchBulkParams, + organizationId, + locationId, modalType, }) => { const org = modalType === MODAL_TYPES.ORGANIZATION; @@ -136,13 +139,13 @@ const BulkAssignTaxonomyModal = ({ }; const handleSave = () => { - const requestBody = { - included: { - search: fetchBulkParams(), - }, + const requestBody = buildBulkRequestBody({ + fetchBulkParams, + organizationId, + locationId, id: taxId, mismatch_setting: fixRadioChecked, - }; + }); org ? dispatch( @@ -243,10 +246,14 @@ BulkAssignTaxonomyModal.propTypes = { selectedCount: PropTypes.number.isRequired, selectAllHostsMode: PropTypes.bool.isRequired, fetchBulkParams: PropTypes.func.isRequired, + organizationId: PropTypes.number, + locationId: PropTypes.number, modalType: PropTypes.string.isRequired, }; BulkAssignTaxonomyModal.defaultProps = { isOpen: false, closeModal: () => {}, + organizationId: undefined, + locationId: undefined, }; diff --git a/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/assignTaxonomy/index.js b/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/assignTaxonomy/index.js index 61d2fdfc5d..4375e6b447 100644 --- a/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/assignTaxonomy/index.js +++ b/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/assignTaxonomy/index.js @@ -7,15 +7,21 @@ import { } from './BulkAssignTaxonomyModal'; export const BulkAssignOrganizationModalScene = ({ isOpen, closeModal }) => { - const { selectAllHostsMode, selectedCount, fetchBulkParams } = useContext( - ForemanActionsBarContext - ); + const { + selectAllHostsMode, + selectedCount, + fetchBulkParams, + organizationId, + locationId, + } = useContext(ForemanActionsBarContext); return ( @@ -23,15 +29,21 @@ export const BulkAssignOrganizationModalScene = ({ isOpen, closeModal }) => { }; export const BulkAssignLocationModalScene = ({ isOpen, closeModal }) => { - const { selectAllHostsMode, selectedCount, fetchBulkParams } = useContext( - ForemanActionsBarContext - ); + const { + selectAllHostsMode, + selectedCount, + fetchBulkParams, + organizationId, + locationId, + } = useContext(ForemanActionsBarContext); return ( diff --git a/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/buildHosts/BulkBuildHostModal.js b/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/buildHosts/BulkBuildHostModal.js index e492e0b0fc..eea9df3c7e 100644 --- a/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/buildHosts/BulkBuildHostModal.js +++ b/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/buildHosts/BulkBuildHostModal.js @@ -12,7 +12,7 @@ import { } from '@patternfly/react-core'; import { addToast } from '../../../ToastsList/slice'; import { translate as __ } from '../../../../common/I18n'; -import { failedHostsToastParams } from '../helpers'; +import { buildBulkRequestBody, failedHostsToastParams } from '../helpers'; import { STATUS } from '../../../../constants'; import { selectAPIStatus } from '../../../../redux/API/APISelectors'; import { bulkBuildHosts, HOST_BUILD_KEY } from './actions'; @@ -22,6 +22,8 @@ const BulkBuildHostModal = ({ closeModal, selectedCount, fetchBulkParams, + organizationId, + locationId, }) => { const dispatch = useDispatch(); const [buildRadioChecked, setBuildRadioChecked] = useState(true); @@ -44,13 +46,13 @@ const BulkBuildHostModal = ({ ); }; const handleSave = () => { - const requestBody = { - included: { - search: fetchBulkParams(), - }, + const requestBody = buildBulkRequestBody({ + fetchBulkParams, + organizationId, + locationId, reboot: rebootChecked, rebuild_configuration: !buildRadioChecked, - }; + }); dispatch(bulkBuildHosts(requestBody, handleModalClose, handleError)); }; @@ -156,11 +158,15 @@ BulkBuildHostModal.propTypes = { closeModal: PropTypes.func, selectedCount: PropTypes.number.isRequired, fetchBulkParams: PropTypes.func.isRequired, + organizationId: PropTypes.number, + locationId: PropTypes.number, }; BulkBuildHostModal.defaultProps = { isOpen: false, closeModal: () => {}, + organizationId: undefined, + locationId: undefined, }; export default BulkBuildHostModal; diff --git a/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/buildHosts/index.js b/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/buildHosts/index.js index f7e413862e..b143c30973 100644 --- a/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/buildHosts/index.js +++ b/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/buildHosts/index.js @@ -4,14 +4,19 @@ import { ForemanActionsBarContext } from '../../../../components/HostDetails/Act import BulkBuildHostModal from './BulkBuildHostModal'; const BulkBuildHostModalScene = ({ isOpen, closeModal }) => { - const { selectedCount, fetchBulkParams } = useContext( - ForemanActionsBarContext - ); + const { + selectedCount, + fetchBulkParams, + organizationId, + locationId, + } = useContext(ForemanActionsBarContext); return ( diff --git a/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/bulkDelete.js b/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/bulkDelete.js index be546dc337..909b35079a 100644 --- a/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/bulkDelete.js +++ b/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/bulkDelete.js @@ -4,16 +4,23 @@ import { visit, foremanUrl } from '../../../common/helpers'; import { sprintf, translate as __ } from '../../../common/I18n'; import { openConfirmModal } from '../../ConfirmModal'; import { APIActions } from '../../../redux/API'; +import { bulkActionTaxonomyParams } from './helpers'; import './bulkDeleteModal.scss'; export const bulkDeleteHosts = ({ bulkParams, + organizationId, + locationId, selectedCount, destroyVmOnHostDelete, }) => dispatch => { const successToast = () => sprintf(__('%s hosts deleted'), selectedCount); const errorToast = ({ message }) => message; - const url = foremanUrl(`/api/v2/hosts/bulk?search=${bulkParams}`); + const queryParams = new URLSearchParams({ + search: bulkParams, + ...bulkActionTaxonomyParams({ organizationId, locationId }), + }); + const url = foremanUrl(`/api/v2/hosts/bulk?${queryParams.toString()}`); // TODO: Replace with a checkbox instead of a global setting for cascade host destroy const cascadeMessage = () => diff --git a/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/changeOwner/BulkChangeOwnerModal.js b/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/changeOwner/BulkChangeOwnerModal.js index 34e7d9b456..d48e9d9e0c 100644 --- a/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/changeOwner/BulkChangeOwnerModal.js +++ b/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/changeOwner/BulkChangeOwnerModal.js @@ -34,7 +34,7 @@ import { HOSTS_API_PATH, API_REQUEST_KEY, } from '../../../../routes/Hosts/constants'; -import { failedHostsToastParams } from '../helpers'; +import { buildBulkRequestBody, failedHostsToastParams } from '../helpers'; const BulkChangeOwnerModal = ({ isOpen, @@ -42,6 +42,8 @@ const BulkChangeOwnerModal = ({ selectAllHostsMode, selectedCount, fetchBulkParams, + organizationId, + locationId, }) => { const dispatch = useDispatch(); const [ownerId, setOwnerId] = useState(''); @@ -131,12 +133,12 @@ const BulkChangeOwnerModal = ({ }; const handleConfirm = () => { - const requestBody = { - included: { - search: fetchBulkParams(), - }, + const requestBody = buildBulkRequestBody({ + fetchBulkParams, + organizationId, + locationId, owner_id: ownerId, - }; + }); dispatch(bulkChangeOwner(requestBody, handleSuccess, handleError)); }; @@ -250,11 +252,15 @@ BulkChangeOwnerModal.propTypes = { fetchBulkParams: PropTypes.func.isRequired, selectedCount: PropTypes.number.isRequired, selectAllHostsMode: PropTypes.bool.isRequired, + organizationId: PropTypes.number, + locationId: PropTypes.number, }; BulkChangeOwnerModal.defaultProps = { isOpen: false, closeModal: () => {}, + organizationId: undefined, + locationId: undefined, }; export default BulkChangeOwnerModal; diff --git a/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/changeOwner/index.js b/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/changeOwner/index.js index fd554d8662..454e56ca50 100644 --- a/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/changeOwner/index.js +++ b/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/changeOwner/index.js @@ -9,6 +9,8 @@ const BulkChangeOwnerModalScene = ({ isOpen, closeModal }) => { selectedCount, selectedResults, fetchBulkParams, + organizationId, + locationId, } = useContext(ForemanActionsBarContext); return ( { selectedCount={selectedCount} selectedResults={selectedResults} fetchBulkParams={fetchBulkParams} + organizationId={organizationId} + locationId={locationId} isOpen={isOpen} closeModal={closeModal} /> diff --git a/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/disassociate/BulkDisassociateModal.js b/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/disassociate/BulkDisassociateModal.js index 17a51be23d..d8b7ec3300 100644 --- a/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/disassociate/BulkDisassociateModal.js +++ b/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/disassociate/BulkDisassociateModal.js @@ -18,7 +18,7 @@ import { HOSTS_API_PATH, API_REQUEST_KEY, } from '../../../../routes/Hosts/constants'; -import { failedHostsToastParams } from '../helpers'; +import { buildBulkRequestBody, failedHostsToastParams } from '../helpers'; const BulkDisassociateModal = ({ isOpen, @@ -27,6 +27,8 @@ const BulkDisassociateModal = ({ selectedCount, selectedResults, fetchBulkParams, + organizationId, + locationId, }) => { const dispatch = useDispatch(); const hostsWithComputeResource = selectedResults?.filter( @@ -91,11 +93,12 @@ const BulkDisassociateModal = ({ const queryString = selectedResultsEmpty ? fetchBulkParams() : `id ^ (${hostsWithComputeResource.map(h => h.id).join(',')})`; - const requestBody = { - included: { - search: queryString, - }, - }; + const requestBody = buildBulkRequestBody({ + fetchBulkParams, + organizationId, + locationId, + includedSearch: queryString, + }); dispatch(bulkDisassociate(requestBody, handleSuccess, handleError)); }; @@ -188,12 +191,16 @@ BulkDisassociateModal.propTypes = { fetchBulkParams: PropTypes.func.isRequired, selectedCount: PropTypes.number.isRequired, selectAllHostsMode: PropTypes.bool.isRequired, + organizationId: PropTypes.number, + locationId: PropTypes.number, }; BulkDisassociateModal.defaultProps = { isOpen: false, closeModal: () => {}, selectedResults: [], + organizationId: undefined, + locationId: undefined, }; export default BulkDisassociateModal; diff --git a/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/disassociate/index.js b/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/disassociate/index.js index 45438eb4f2..169eb72f36 100644 --- a/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/disassociate/index.js +++ b/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/disassociate/index.js @@ -9,6 +9,8 @@ const BulkDisassociateModalScene = ({ isOpen, closeModal }) => { selectedCount, selectedResults, fetchBulkParams, + organizationId, + locationId, } = useContext(ForemanActionsBarContext); return ( { selectedCount={selectedCount} selectedResults={selectedResults} fetchBulkParams={fetchBulkParams} + organizationId={organizationId} + locationId={locationId} isOpen={isOpen} closeModal={closeModal} /> diff --git a/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/helpers.js b/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/helpers.js index 675c0c5bfb..e51c1ba8cc 100644 --- a/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/helpers.js +++ b/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/helpers.js @@ -8,6 +8,28 @@ export const searchLink = ({ query, message, baseUrl }) => ({ href: urlWithSearch(baseUrl, query), }); +export const bulkActionTaxonomyParams = ({ + organizationId, + locationId, +} = {}) => ({ + ...(organizationId != null ? { organization_id: organizationId } : {}), + ...(locationId != null ? { location_id: locationId } : {}), +}); + +export const buildBulkRequestBody = ({ + fetchBulkParams, + organizationId, + locationId, + includedSearch, + ...params +}) => ({ + included: { + search: includedSearch || fetchBulkParams(), + }, + ...bulkActionTaxonomyParams({ organizationId, locationId }), + ...params, +}); + export const failedHostsToastParams = ({ message, failed_host_ids: failedHostIds, diff --git a/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/powerState/BulkPowerStateModal.js b/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/powerState/BulkPowerStateModal.js index a21ab5bfcd..8cb93e5b10 100644 --- a/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/powerState/BulkPowerStateModal.js +++ b/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/powerState/BulkPowerStateModal.js @@ -19,7 +19,7 @@ import './BulkPowerStateModal.scss'; import { HostsPowerRefreshContext } from '../../HostsPowerRefreshContext'; import { POWER_STATES, BULK_POWER_STATE_KEY } from './constants'; import { bulkChangePowerState } from './actions'; -import { failedHostsToastParams } from '../helpers'; +import { buildBulkRequestBody, failedHostsToastParams } from '../helpers'; import { addToast } from '../../../ToastsList/slice'; import { HOSTS_API_PATH, @@ -31,6 +31,8 @@ import { APIActions } from '../../../../redux/API'; const BulkPowerStateModal = ({ selectedHostsCount, fetchBulkParams, + organizationId, + locationId, isOpen, closeModal, }) => { @@ -102,12 +104,12 @@ const BulkPowerStateModal = ({ const handleSubmit = () => { setIsLoading(true); - const payload = { - included: { - search: fetchBulkParams(), - }, + const payload = buildBulkRequestBody({ + fetchBulkParams, + organizationId, + locationId, power: selectedPowerState, - }; + }); dispatch(bulkChangePowerState(payload, handleSuccess, handleError)); }; @@ -206,12 +208,16 @@ const BulkPowerStateModal = ({ BulkPowerStateModal.propTypes = { selectedHostsCount: PropTypes.number, fetchBulkParams: PropTypes.func.isRequired, + organizationId: PropTypes.number, + locationId: PropTypes.number, isOpen: PropTypes.bool, closeModal: PropTypes.func, }; BulkPowerStateModal.defaultProps = { selectedHostsCount: 0, + organizationId: undefined, + locationId: undefined, isOpen: false, closeModal: () => {}, }; diff --git a/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/powerState/index.js b/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/powerState/index.js index 80dcf1ba13..f67e800a9e 100644 --- a/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/powerState/index.js +++ b/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/powerState/index.js @@ -4,13 +4,18 @@ import { ForemanActionsBarContext } from '../../../../components/HostDetails/Act import BulkPowerStateModal from './BulkPowerStateModal'; const BulkPowerStateModalScene = ({ isOpen, closeModal }) => { - const { fetchBulkParams, selectedCount = 0 } = useContext( - ForemanActionsBarContext - ); + const { + fetchBulkParams, + selectedCount = 0, + organizationId, + locationId, + } = useContext(ForemanActionsBarContext); return ( diff --git a/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/reassignHostGroup/BulkReassignHostgroupModal.js b/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/reassignHostGroup/BulkReassignHostgroupModal.js index 91176bccee..95af58429c 100644 --- a/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/reassignHostGroup/BulkReassignHostgroupModal.js +++ b/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/reassignHostGroup/BulkReassignHostgroupModal.js @@ -11,7 +11,7 @@ import { } from '@patternfly/react-core'; import { addToast } from '../../../ToastsList/slice'; import { translate as __ } from '../../../../common/I18n'; -import { failedHostsToastParams } from '../helpers'; +import { buildBulkRequestBody, failedHostsToastParams } from '../helpers'; import { STATUS } from '../../../../constants'; import { selectAPIStatus, @@ -76,6 +76,8 @@ const BulkReassignHostgroupModal = ({ closeModal, selectedCount, fetchBulkParams, + organizationId, + locationId, }) => { const dispatch = useDispatch(); const [hostgroupId, setHostgroupId] = useState(''); @@ -150,12 +152,12 @@ const BulkReassignHostgroupModal = ({ handleModalClose(); }; const handleSave = () => { - const requestBody = { - included: { - search: fetchBulkParams(), - }, + const requestBody = buildBulkRequestBody({ + fetchBulkParams, + organizationId, + locationId, hostgroup_id: hostgroupId, - }; + }); dispatch(bulkReassignHostgroups(requestBody, handleSuccess, handleError)); }; @@ -281,11 +283,15 @@ BulkReassignHostgroupModal.propTypes = { closeModal: PropTypes.func, selectedCount: PropTypes.number.isRequired, fetchBulkParams: PropTypes.func.isRequired, + organizationId: PropTypes.number, + locationId: PropTypes.number, }; BulkReassignHostgroupModal.defaultProps = { isOpen: false, closeModal: () => {}, + organizationId: undefined, + locationId: undefined, }; export default BulkReassignHostgroupModal; diff --git a/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/reassignHostGroup/index.js b/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/reassignHostGroup/index.js index 4712eaad4d..d7a4669a73 100644 --- a/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/reassignHostGroup/index.js +++ b/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/reassignHostGroup/index.js @@ -4,14 +4,19 @@ import { ForemanActionsBarContext } from '../../../../components/HostDetails/Act import BulkReassignHostgroupModal from './BulkReassignHostgroupModal'; const BulkReassignHostgroupModalScene = ({ isOpen, closeModal }) => { - const { selectedCount, fetchBulkParams } = useContext( - ForemanActionsBarContext - ); + const { + selectedCount, + fetchBulkParams, + organizationId, + locationId, + } = useContext(ForemanActionsBarContext); return ( diff --git a/webpack/assets/javascripts/react_app/components/HostsIndex/index.js b/webpack/assets/javascripts/react_app/components/HostsIndex/index.js index 3852ec6691..bd2f3847cd 100644 --- a/webpack/assets/javascripts/react_app/components/HostsIndex/index.js +++ b/webpack/assets/javascripts/react_app/components/HostsIndex/index.js @@ -45,6 +45,8 @@ import { useForemanSettings, useForemanHostsPageUrl, useForemanContext, + useForemanOrganization, + useForemanLocation, } from '../../Root/Context/ForemanContext'; import { bulkDeleteHosts } from './BulkActions/bulkDelete'; import { @@ -102,6 +104,8 @@ const HostsIndex = () => { defaultParams, }); const contextData = useForemanContext(); + const currentOrganization = useForemanOrganization(); + const currentLocation = useForemanLocation(); const { response: { @@ -216,6 +220,8 @@ const HostsIndex = () => { dispatch( bulkDeleteHosts({ bulkParams, + organizationId: currentOrganization?.id, + locationId: currentLocation?.id, selectedCount, destroyVmOnHostDelete, }) @@ -553,6 +559,8 @@ const HostsIndex = () => { selectedCount, selectedResults, fetchBulkParams, + organizationId: currentOrganization?.id, + locationId: currentLocation?.id, }} > ({ })), useForemanHostsPageUrl: jest.fn(() => '/hosts'), useForemanContext: jest.fn(() => ({})), + useForemanOrganization: jest.fn(() => undefined), + useForemanLocation: jest.fn(() => undefined), })); jest.mock('../common/Slot', () => ({