Merge pull request #12123 from marshmalien/12109-fix-user-role-association

Fix user role association in access modal
This commit is contained in:
Sarah Akus
2022-04-27 19:23:53 -04:00
committed by GitHub
2 changed files with 59 additions and 69 deletions

View File

@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { TeamsAPI, UsersAPI } from 'api'; import { TeamsAPI, UsersAPI } from 'api';
import useSelected from 'hooks/useSelected';
import SelectableCard from '../SelectableCard'; import SelectableCard from '../SelectableCard';
import Wizard from '../Wizard'; import Wizard from '../Wizard';
import SelectResourceStep from './SelectResourceStep'; import SelectResourceStep from './SelectResourceStep';
@@ -71,51 +72,31 @@ const teamSortColumns = [
function AddResourceRole({ onSave, onClose, roles, resource, onError }) { function AddResourceRole({ onSave, onClose, roles, resource, onError }) {
const history = useHistory(); const history = useHistory();
const [selectedResource, setSelectedResource] = useState(null); const {
const [selectedResourceRows, setSelectedResourceRows] = useState([]); selected: resourcesSelected,
const [selectedRoleRows, setSelectedRoleRows] = useState([]); handleSelect: handleResourceSelect,
clearSelected: clearResources,
} = useSelected([]);
const {
selected: rolesSelected,
handleSelect: handleRoleSelect,
clearSelected: clearRoles,
} = useSelected([]);
const [resourceType, setResourceType] = useState(null);
const [currentStepId, setCurrentStepId] = useState(1); const [currentStepId, setCurrentStepId] = useState(1);
const [maxEnabledStep, setMaxEnabledStep] = useState(1); const [maxEnabledStep, setMaxEnabledStep] = useState(1);
const handleResourceCheckboxClick = (user) => {
const selectedIndex = selectedResourceRows.findIndex(
(selectedRow) => selectedRow.id === user.id
);
if (selectedIndex > -1) {
selectedResourceRows.splice(selectedIndex, 1);
if (selectedResourceRows.length === 0) {
setMaxEnabledStep(currentStepId);
}
setSelectedRoleRows(selectedResourceRows);
} else {
setSelectedResourceRows([...selectedResourceRows, user]);
}
};
useEffect(() => { useEffect(() => {
if (currentStepId === 1 && maxEnabledStep > 1) { if (currentStepId === 1 && maxEnabledStep > 1) {
history.push(history.location.pathname); history.push(history.location.pathname);
} }
}, [currentStepId, history, maxEnabledStep]); }, [currentStepId, history, maxEnabledStep]);
const handleRoleCheckboxClick = (role) => { const handleResourceTypeSelect = (type) => {
const selectedIndex = selectedRoleRows.findIndex( setResourceType(type);
(selectedRow) => selectedRow.id === role.id clearResources();
); clearRoles();
if (selectedIndex > -1) {
setSelectedRoleRows(
selectedRoleRows.filter((r, index) => index !== selectedIndex)
);
} else {
setSelectedRoleRows([...selectedRoleRows, role]);
}
};
const handleResourceSelect = (resourceType) => {
setSelectedResource(resourceType);
setSelectedResourceRows([]);
setSelectedRoleRows([]);
}; };
const handleWizardNext = (step) => { const handleWizardNext = (step) => {
@@ -131,20 +112,20 @@ function AddResourceRole({ onSave, onClose, roles, resource, onError }) {
try { try {
const roleRequests = []; const roleRequests = [];
for (let i = 0; i < selectedResourceRows.length; i++) { for (let i = 0; i < resourcesSelected.length; i++) {
for (let j = 0; j < selectedRoleRows.length; j++) { for (let j = 0; j < rolesSelected.length; j++) {
if (selectedResource === 'users') { if (resourceType === 'users') {
roleRequests.push( roleRequests.push(
UsersAPI.associateRole( UsersAPI.associateRole(
selectedResourceRows[i].id, resourcesSelected[i].id,
selectedRoleRows[j].id rolesSelected[j].id
) )
); );
} else if (selectedResource === 'teams') { } else if (resourceType === 'teams') {
roleRequests.push( roleRequests.push(
TeamsAPI.associateRole( TeamsAPI.associateRole(
selectedResourceRows[i].id, resourcesSelected[i].id,
selectedRoleRows[j].id rolesSelected[j].id
) )
); );
} }
@@ -162,7 +143,7 @@ function AddResourceRole({ onSave, onClose, roles, resource, onError }) {
// Object roles can be user only, so we remove them when // Object roles can be user only, so we remove them when
// showing role choices for team access // showing role choices for team access
const selectableRoles = { ...roles }; const selectableRoles = { ...roles };
if (selectedResource === 'teams') { if (resourceType === 'teams') {
Object.keys(roles).forEach((key) => { Object.keys(roles).forEach((key) => {
if (selectableRoles[key].user_only) { if (selectableRoles[key].user_only) {
delete selectableRoles[key]; delete selectableRoles[key];
@@ -172,7 +153,7 @@ function AddResourceRole({ onSave, onClose, roles, resource, onError }) {
let wizardTitle = ''; let wizardTitle = '';
switch (selectedResource) { switch (resourceType) {
case 'users': case 'users':
wizardTitle = t`Add User Roles`; wizardTitle = t`Add User Roles`;
break; break;
@@ -193,60 +174,60 @@ function AddResourceRole({ onSave, onClose, roles, resource, onError }) {
{t`Choose the type of resource that will be receiving new roles. For example, if you'd like to add new roles to a set of users please choose Users and click Next. You'll be able to select the specific resources in the next step.`} {t`Choose the type of resource that will be receiving new roles. For example, if you'd like to add new roles to a set of users please choose Users and click Next. You'll be able to select the specific resources in the next step.`}
</div> </div>
<SelectableCard <SelectableCard
isSelected={selectedResource === 'users'} isSelected={resourceType === 'users'}
label={t`Users`} label={t`Users`}
ariaLabel={t`Users`} ariaLabel={t`Users`}
dataCy="add-role-users" dataCy="add-role-users"
onClick={() => handleResourceSelect('users')} onClick={() => handleResourceTypeSelect('users')}
/> />
{resource?.type === 'team' || {resource?.type === 'team' ||
(resource?.type === 'credential' && (resource?.type === 'credential' &&
!resource?.organization) ? null : ( !resource?.organization) ? null : (
<SelectableCard <SelectableCard
isSelected={selectedResource === 'teams'} isSelected={resourceType === 'teams'}
label={t`Teams`} label={t`Teams`}
ariaLabel={t`Teams`} ariaLabel={t`Teams`}
dataCy="add-role-teams" dataCy="add-role-teams"
onClick={() => handleResourceSelect('teams')} onClick={() => handleResourceTypeSelect('teams')}
/> />
)} )}
</div> </div>
), ),
nextButtonText: t`Next`, nextButtonText: t`Next`,
enableNext: selectedResource !== null, enableNext: resourceType !== null,
}, },
{ {
id: 2, id: 2,
name: t`Select Items from List`, name: t`Select Items from List`,
component: ( component: (
<> <>
{selectedResource === 'users' && ( {resourceType === 'users' && (
<SelectResourceStep <SelectResourceStep
searchColumns={userSearchColumns} searchColumns={userSearchColumns}
sortColumns={userSortColumns} sortColumns={userSortColumns}
displayKey="username" displayKey="username"
onRowClick={handleResourceCheckboxClick} onRowClick={handleResourceSelect}
fetchItems={readUsers} fetchItems={readUsers}
fetchOptions={readUsersOptions} fetchOptions={readUsersOptions}
selectedLabel={t`Selected`} selectedLabel={t`Selected`}
selectedResourceRows={selectedResourceRows} selectedResourceRows={resourcesSelected}
sortedColumnKey="username" sortedColumnKey="username"
/> />
)} )}
{selectedResource === 'teams' && ( {resourceType === 'teams' && (
<SelectResourceStep <SelectResourceStep
searchColumns={teamSearchColumns} searchColumns={teamSearchColumns}
sortColumns={teamSortColumns} sortColumns={teamSortColumns}
onRowClick={handleResourceCheckboxClick} onRowClick={handleResourceSelect}
fetchItems={readTeams} fetchItems={readTeams}
fetchOptions={readTeamsOptions} fetchOptions={readTeamsOptions}
selectedLabel={t`Selected`} selectedLabel={t`Selected`}
selectedResourceRows={selectedResourceRows} selectedResourceRows={resourcesSelected}
/> />
)} )}
</> </>
), ),
enableNext: selectedResourceRows.length > 0, enableNext: resourcesSelected.length > 0,
nextButtonText: t`Next`, nextButtonText: t`Next`,
canJumpTo: maxEnabledStep >= 2, canJumpTo: maxEnabledStep >= 2,
}, },
@@ -255,16 +236,16 @@ function AddResourceRole({ onSave, onClose, roles, resource, onError }) {
name: t`Select Roles to Apply`, name: t`Select Roles to Apply`,
component: ( component: (
<SelectRoleStep <SelectRoleStep
onRolesClick={handleRoleCheckboxClick} onRolesClick={handleRoleSelect}
roles={selectableRoles} roles={selectableRoles}
selectedListKey={selectedResource === 'users' ? 'username' : 'name'} selectedListKey={resourceType === 'users' ? 'username' : 'name'}
selectedListLabel={t`Selected`} selectedListLabel={t`Selected`}
selectedResourceRows={selectedResourceRows} selectedResourceRows={resourcesSelected}
selectedRoleRows={selectedRoleRows} selectedRoleRows={rolesSelected}
/> />
), ),
nextButtonText: t`Save`, nextButtonText: t`Save`,
enableNext: selectedRoleRows.length > 0, enableNext: rolesSelected.length > 0,
canJumpTo: maxEnabledStep >= 3, canJumpTo: maxEnabledStep >= 3,
}, },
]; ];

View File

@@ -42,6 +42,7 @@ describe('<_AddResourceRole />', () => {
results: [ results: [
{ id: 1, username: 'foo', url: '' }, { id: 1, username: 'foo', url: '' },
{ id: 2, username: 'bar', url: '' }, { id: 2, username: 'bar', url: '' },
{ id: 3, username: 'baz', url: '' },
], ],
}, },
}); });
@@ -95,14 +96,20 @@ describe('<_AddResourceRole />', () => {
// Step 2 // Step 2
await waitForElement(wrapper, 'EmptyStateBody', (el) => el.length === 0); await waitForElement(wrapper, 'EmptyStateBody', (el) => el.length === 0);
expect(wrapper.find('Chip').length).toBe(0); expect(wrapper.find('Chip').length).toBe(0);
act(() => wrapper.find('CheckboxListItem[name="foo"]').invoke('onSelect')(true);
wrapper.find('CheckboxListItem[name="foo"]').invoke('onSelect')(true) wrapper.find('CheckboxListItem[name="bar"]').invoke('onSelect')(true);
); wrapper.find('CheckboxListItem[name="baz"]').invoke('onSelect')(true);
wrapper.update(); wrapper.find('CheckboxListItem[name="baz"]').invoke('onSelect')(false);
expect( expect(
wrapper.find('CheckboxListItem[name="foo"]').prop('isSelected') wrapper.find('CheckboxListItem[name="foo"]').prop('isSelected')
).toBe(true); ).toBe(true);
expect(wrapper.find('Chip').length).toBe(1); expect(
wrapper.find('CheckboxListItem[name="bar"]').prop('isSelected')
).toBe(true);
expect(
wrapper.find('CheckboxListItem[name="baz"]').prop('isSelected')
).toBe(false);
expect(wrapper.find('Chip').length).toBe(2);
act(() => wrapper.find('Button[type="submit"]').prop('onClick')()); act(() => wrapper.find('Button[type="submit"]').prop('onClick')());
wrapper.update(); wrapper.update();
@@ -120,6 +127,8 @@ describe('<_AddResourceRole />', () => {
wrapper.find('Button[type="submit"]').prop('onClick')() wrapper.find('Button[type="submit"]').prop('onClick')()
); );
expect(UsersAPI.associateRole).toBeCalledWith(1, 1); expect(UsersAPI.associateRole).toBeCalledWith(1, 1);
expect(UsersAPI.associateRole).toBeCalledWith(2, 1);
expect(UsersAPI.associateRole).toBeCalledTimes(2);
}); });
test('should call on error properly', async () => { test('should call on error properly', async () => {
@@ -189,7 +198,7 @@ describe('<_AddResourceRole />', () => {
expect(onError).toBeCalled(); expect(onError).toBeCalled();
}); });
test('should should update history properly', async () => { test('should update history properly', async () => {
let wrapper; let wrapper;
const history = createMemoryHistory({ const history = createMemoryHistory({
initialEntries: ['organizations/2/access?resource.order_by=-username'], initialEntries: ['organizations/2/access?resource.order_by=-username'],