Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/manager/.changeset/pr-13330-fixed-1769594156861.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Fixed
---

IAM: bugs related to resseting to prev page ([#13330](https://github.com/linode/manager/pull/13330))
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ export const AssignedEntitiesTable = ({ username }: Props) => {
// If we just deleted the last one on a page, reset to the previous page.
const removedLastOnPage =
filteredAndSortedRoles.length % pagination.pageSize === 1;
if (removedLastOnPage) {
if (removedLastOnPage && pagination.page > 1) {
pagination.handlePageChange(pagination.page - 1);
}
};
Expand Down Expand Up @@ -373,13 +373,15 @@ export const AssignedEntitiesTable = ({ username }: Props) => {
</Table>
<ChangeRoleForEntityDrawer
mode={drawerMode}
onClose={() => handleDialogClose(drawerMode)}
onClose={() => setIsChangeRoleForEntityDrawerOpen(false)}
onSuccess={() => handleDialogClose()}
open={isChangeRoleForEntityDrawerOpen}
role={selectedRole}
username={username}
/>
<RemoveAssignmentConfirmationDialog
onClose={() => handleDialogClose()}
onClose={() => setIsRemoveAssignmentDialogOpen(false)}
onSuccess={() => handleDialogClose()}
open={isRemoveAssignmentDialogOpen}
role={selectedRole}
username={username}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import type { ExtendedEntityRole } from '../utilities';
interface Props {
mode: DrawerModes;
onClose: () => void;
onSuccess?: () => void;
open: boolean;
role: EntitiesRole | undefined;
username?: string;
Expand All @@ -47,6 +48,7 @@ interface Props {
export const ChangeRoleForEntityDrawer = ({
mode,
onClose,
onSuccess,
open,
role,
username,
Expand Down Expand Up @@ -159,6 +161,7 @@ export const ChangeRoleForEntityDrawer = ({
variant: 'success',
});

onSuccess?.();
handleClose();
} catch (errors) {
for (const error of errors) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,20 +167,49 @@ export const AssignedRolesTable = () => {
};

/**
* Closes the appropriate assignment-related dialog and adjusts pagination if needed.
*
* @param drawerMode Optional mode indicating which dialog should be closed.
* Closes the Unassign role dialog and adjusts pagination if needed.
*/
const handleDialogClose = (drawerMode?: DrawerModes) => {
if (drawerMode && drawerMode === 'change-role') {
setIsChangeRoleDrawerOpen(false);
} else {
setIsUnassignRoleDialogOpen(false);
const handleUnassignRoleDialogClose = () => {
setIsUnassignRoleDialogOpen(false);

if (hadOneRoleOnPage && pagination.page > 1) {
pagination.handlePageChange(pagination.page - 1);
}
};

/**
* Closes the Change role dialog and adjusts pagination if needed.
*/
const handleChangeRoleDialogClose = (newTotalRolesCount: number) => {
setIsChangeRoleDrawerOpen(false);

// Check if the change decreased the total roles by exactly one.
// This occurs when switching to a role the user already had,
// causing the two roles to merge into a single entry.
const decreasedByOne =
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why we need it:

user has linode_admin and linode_viewer. Change role action can result in 2 different outcomes:

  1. change role to a new one
    ex: changing from linode_viewer to linode_contributor
    The total number of roles does not change (one role is replaced by another)
  2. change role to an existing one
    ex: changing from linode_viewer to linode_admin(when the user already has linode_admin)
    The total number of roles decreases by one, because two roles are merged into a single role entry

So we should reset to the previous page only when the total number of roles decreases

filteredAndSortedRoles.length - newTotalRolesCount === 1;

if (hadOneRoleOnPage && decreasedByOne && pagination.page > 1) {
pagination.handlePageChange(pagination.page - 1);
}
// If we just deleted the last one on a page, reset to the previous page.
const removedLastOnPage =
filteredAndSortedRoles.length % pagination.pageSize === 1;
if (removedLastOnPage) {
};

/**
* Closes the Remove Assignment Confirmation Dialog and adjusts pagination if needed.
* @param selectedRole Role that was affected by the removal.
*/
const handleRemoveAssignmentDialogClose = (
selectedRole?: ExtendedRoleView
) => {
setIsRemoveAssignmentDialogOpen(false);

// If we just deleted the last role with only one entity on a page, reset to the previous page.
if (
selectedRole &&
selectedRole.entity_ids?.length === 1 &&
hadOneRoleOnPage &&
pagination.page > 1
) {
pagination.handlePageChange(pagination.page - 1);
}
};
Expand Down Expand Up @@ -346,6 +375,13 @@ export const AssignedRolesTable = () => {
return filteredAndSortedRoles.length;
}, [filteredAndSortedRoles]);

// Detect when the current page has exactly one role.
// If a delete or unassign removes it, the page becomes empty,
// so navigate back one page to avoid an empty view.
const hadOneRoleOnPage = React.useMemo(() => {
return filteredAndSortedRoles.length % pagination.pageSize === 1;
}, [filteredAndSortedRoles.length, pagination.pageSize]);

if (accountPermissionsLoading || entitiesLoading || assignedRolesLoading) {
return <CircleProgress />;
}
Expand Down Expand Up @@ -461,12 +497,16 @@ export const AssignedRolesTable = () => {
/>
<ChangeRoleDrawer
mode={drawerMode}
onClose={() => handleDialogClose(drawerMode)}
onClose={() => setIsChangeRoleDrawerOpen(false)}
onSuccess={(newTotalRolesCount: number) =>
handleChangeRoleDialogClose(newTotalRolesCount)
}
open={isChangeRoleDrawerOpen}
role={selectedRole}
/>
<UnassignRoleConfirmationDialog
onClose={() => handleDialogClose()}
onClose={() => setIsUnassignRoleDialogOpen(false)}
onSuccess={() => handleUnassignRoleDialogClose()}
open={isUnassignRoleDialogOpen}
role={selectedRole}
/>
Expand All @@ -477,6 +517,7 @@ export const AssignedRolesTable = () => {
/>
<RemoveAssignmentConfirmationDialog
onClose={() => setIsRemoveAssignmentDialogOpen(false)}
onSuccess={() => handleRemoveAssignmentDialogClose(selectedRole)}
open={isRemoveAssignmentDialogOpen}
role={selectedRoleDetails}
username={username}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,26 @@ import {
isAccountRole,
isEntityRole,
} from '../utilities';
import { combineRoles } from './utils';

import type { DrawerModes, EntitiesOption, ExtendedRoleView } from '../types';
import type { RolesType } from '../utilities';

interface Props {
mode: DrawerModes;
onClose: () => void;
onSuccess?: (nextTotalCount: number) => void;
open: boolean;
role: ExtendedRoleView | undefined;
}

export const ChangeRoleDrawer = ({ mode, onClose, open, role }: Props) => {
export const ChangeRoleDrawer = ({
mode,
onClose,
onSuccess,
open,
role,
}: Props) => {
const theme = useTheme();
const { username } = useParams({ strict: false });
const { data: accountRoles, isLoading: accountPermissionsLoading } =
Expand Down Expand Up @@ -147,6 +155,12 @@ export const ChangeRoleDrawer = ({ mode, onClose, open, role }: Props) => {

await mutationFn(updatedUserRoles);

// Calculate the next total roles count after the change.
// Passed to the parent to determine pagination updates
// (e.g., navigate back if the current page becomes empty).
const nextTotalRolesCount = combineRoles(updatedUserRoles).length;

onSuccess?.(nextTotalRolesCount);
handleClose();
} catch (errors) {
setError('root', {
Expand Down