diff --git a/src/components/InventoryGroups/Modals/RemoveHostsFromGroupModal.js b/src/components/InventoryGroups/Modals/RemoveHostsFromGroupModal.js index 5f59a6aaa..17ccf827f 100644 --- a/src/components/InventoryGroups/Modals/RemoveHostsFromGroupModal.js +++ b/src/components/InventoryGroups/Modals/RemoveHostsFromGroupModal.js @@ -7,27 +7,56 @@ import { removeHostsFromGroup } from '../utils/api'; import componentTypes from '@data-driven-forms/react-form-renderer/component-types'; import { Text } from '@patternfly/react-core'; -const schema = (groupName, hosts) => ({ - fields: [ - { - component: componentTypes.PLAIN_TEXT, - name: 'warning-message', - label: - hosts.length === 1 ? ( - - {hosts[0].display_name} will no longer be part of{' '} - {groupName} and its configuration will be impacted. - - ) : ( - - {hosts.length} systems will no longer be part of{' '} - {groupName} and their configuration will be - impacted. - - ), - }, - ], -}); +const schema = (hosts) => { + const hostsInGroup = hosts.filter(({ groups }) => groups.length > 0); // selection can contain ungroupped hosts + const groupName = hostsInGroup[0].groups[0].name; + + return { + fields: [ + { + component: componentTypes.PLAIN_TEXT, + name: 'warning-message', + label: + hostsInGroup.length === 1 ? ( + + {hostsInGroup[0].display_name} will no longer be + part of {groupName} and its configuration will be + impacted. + + ) : ( + + {hostsInGroup.length} systems will no longer be + part of {groupName} and their configuration will + be impacted. + + ), + }, + ], + }; +}; + +const statusMessages = (hosts) => { + const hostsInGroup = hosts.filter(({ groups }) => groups.length > 0); + const groupName = hostsInGroup[0].groups[0].name; + + return hostsInGroup.length === 1 + ? { + onSuccess: { + title: `1 system removed from ${groupName}`, + }, + onError: { + title: `Failed to remove 1 system from ${groupName}`, + }, + } + : { + onSuccess: { + title: `${hostsInGroup.length} systems removed from ${groupName}`, + }, + onError: { + title: `Failed to remove ${hostsInGroup.length} systems from ${groupName}`, + }, + }; +}; const RemoveHostsFromGroupModal = ({ isModalOpen, @@ -37,23 +66,9 @@ const RemoveHostsFromGroupModal = ({ reloadTimeout, }) => { const dispatch = useDispatch(); - // the current iteration of groups feature a host can be in at maximum one group - const { name: groupName, id: groupId } = hosts[0].groups[0]; - - const handleRemoveHosts = () => { - const statusMessages = { - onSuccess: { - title: `${hosts.length} ${ - hosts.length > 1 ? 'systems' : 'system' - } removed from ${groupName}`, - }, - onError: { - title: `Failed to remove ${hosts.length} ${ - hosts.length > 1 ? 'systems' : 'system' - } from ${groupName}`, - }, - }; + const groupId = hosts.find(({ groups }) => groups.length > 0).groups[0].id; + const handleRemoveHosts = () => apiWithToast( dispatch, async () => @@ -61,9 +76,8 @@ const RemoveHostsFromGroupModal = ({ groupId, hosts.map(({ id }) => id) ), - statusMessages + statusMessages(hosts) ); - }; return ( { .to.be.true; }); - it('the fourth host is not in a group', () => { + it('the fourth and fifth hosts are not in a group', () => { expect(hostsFixtures.results[3].groups.length === 0).to.be.true; + expect(hostsFixtures.results[4].groups.length === 0).to.be.true; }); }); @@ -387,8 +388,57 @@ describe('inventory table', () => { .contains('Remove from group') .shouldHaveAriaDisabled(); }); - }); - // TODO: test group bulk actions once granular RBAC is implemented there too + it('can bulk remove from the permitted group', () => { + cy.get(ROW).find('[type="checkbox"]').eq(0).click(); + // TODO: implement ouia selector for this component + cy.get( + '.ins-c-primary-toolbar__actions [aria-label="Actions"]' + ).click(); + cy.get(DROPDOWN_ITEM) + .contains('Remove from group') + .shouldHaveAriaEnabled(); + cy.get(ROW).find('[type="checkbox"]').eq(1).click(); + // TODO: implement ouia selector for this component + cy.get( + '.ins-c-primary-toolbar__actions [aria-label="Actions"]' + ).click(); + cy.get(DROPDOWN_ITEM) + .contains('Remove from group') + .shouldHaveAriaEnabled(); + }); + + it('can bulk remove from group together with ungroupped hosts', () => { + cy.get(ROW).find('[type="checkbox"]').eq(0).click(); + cy.get(ROW).find('[type="checkbox"]').eq(3).click(); + // TODO: implement ouia selector for this component + cy.get( + '.ins-c-primary-toolbar__actions [aria-label="Actions"]' + ).click(); + cy.get(DROPDOWN_ITEM) + .contains('Remove from group') + .shouldHaveAriaEnabled(); + }); + + it('can bulk add hosts to the permitted group', () => { + cy.get(ROW).find('[type="checkbox"]').eq(3).click(); + cy.get(ROW).find('[type="checkbox"]').eq(4).click(); + // TODO: implement ouia selector for this component + cy.get( + '.ins-c-primary-toolbar__actions [aria-label="Actions"]' + ).click(); + cy.get(DROPDOWN_ITEM).contains('Add to group').shouldHaveAriaEnabled(); + }); + + it('cannot bulk add to group if groupped hosts selected', () => { + cy.get(ROW).find('[type="checkbox"]').eq(0).click(); + cy.get(ROW).find('[type="checkbox"]').eq(3).click(); + // TODO: implement ouia selector for this component + cy.get( + '.ins-c-primary-toolbar__actions [aria-label="Actions"]' + ).click(); + cy.get(DROPDOWN_ITEM).contains('Add to group').shouldHaveAriaDisabled(); + }); + }); }); }); diff --git a/src/routes/InventoryTable.js b/src/routes/InventoryTable.js index 2b7acbab6..ed4c80207 100644 --- a/src/routes/InventoryTable.js +++ b/src/routes/InventoryTable.js @@ -50,6 +50,7 @@ import { ActionButton, ActionDropdownItem, } from '../components/InventoryTable/ActionWithRBAC'; +import uniq from 'lodash/uniq'; const mapTags = ({ category, values }) => values.map( @@ -272,17 +273,16 @@ const Inventory = ({ const calculateSelected = () => (selected ? selected.size : 0); const isBulkRemoveFromGroupsEnabled = () => { - if (calculateSelected() > 0) { - const selectedHosts = Array.from(selected.values()); - - return selectedHosts.every( - ({ groups }) => - groups.length !== 0 && - groups[0].name === selectedHosts[0].groups[0].name - ); - } - - return false; + return ( + calculateSelected() > 0 && + Array.from(selected.values()).some(({ groups }) => groups.length > 0) && + uniq( + // can remove from at maximum one group at a time + Array.from(selected.values()) + .filter(({ groups }) => groups.length > 0) + .map(({ groups }) => groups[0].name) + ).length === 1 + ); }; const isBulkAddHostsToGroupsEnabled = () => { @@ -448,6 +448,7 @@ const Inventory = ({ setCurrentSystem(Array.from(selected.values())); setAddHostGroupModalOpen(true); }} + ignoreResourceDefinitions > Add to group @@ -462,15 +463,29 @@ const Inventory = ({ label: ( + groups?.[0]?.id !== undefined + ? REQUIRED_PERMISSIONS_TO_MODIFY_GROUP( + groups[0].id + ) + : null + ) + .filter(Boolean) // don't check ungroupped hosts + : [] + } isAriaDisabled={!isBulkRemoveFromGroupsEnabled()} noAccessTooltip={NO_MODIFY_GROUPS_TOOLTIP_MESSAGE} onClick={() => { setCurrentSystem(Array.from(selected.values())); setRemoveHostsFromGroupModalOpen(true); }} + {...(selected === undefined // when nothing is selected, no access must be checked + ? { override: true } + : {})} + checkAll > Remove from group