Skip to content

Commit f5ad084

Browse files
fix: hide remove role button for users without permissions
1 parent 2b3b480 commit f5ad084

4 files changed

Lines changed: 67 additions & 10 deletions

File tree

src/authz-module/audit-user/index.test.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ jest.mock('@edx/frontend-component-header', () => ({
3030
jest.mock('@src/data/hooks', () => ({
3131
...jest.requireActual('@src/data/hooks'),
3232
useUserAccount: jest.fn(),
33+
useValidateUserPermissions: jest.fn().mockReturnValue({
34+
data: [{ allowed: true }],
35+
isLoading: false,
36+
}),
3337
}));
3438

3539
// Mock the useRevokeUserRoles hook

src/authz-module/components/TableCells.test.tsx

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { initializeMockApp } from '@edx/frontend-platform/testing';
33
import { renderWrapper } from '@src/setupTest';
44
import userEvent from '@testing-library/user-event';
55
import { DataTableContext } from '@openedx/paragon';
6+
import { useValidateUserPermissions } from '@src/data/hooks';
67
import {
78
NameCell,
89
ViewActionCell,
@@ -14,9 +15,9 @@ import {
1415
createActionsCell,
1516
} from './TableCells';
1617

17-
// TODO: remove console.log mocks and implement actual logic for these cells, then update tests accordingly
18-
// Mock console.log for TODO functions
19-
jest.spyOn(console, 'log').mockImplementation(() => {});
18+
jest.mock('@src/data/hooks', () => ({
19+
useValidateUserPermissions: jest.fn(),
20+
}));
2021

2122
const mockNavigate = jest.fn();
2223

@@ -494,6 +495,12 @@ describe('TableCells Components', () => {
494495
};
495496

496497
beforeEach(() => {
498+
(useValidateUserPermissions as jest.Mock).mockReturnValue({
499+
isLoading: false,
500+
data: [
501+
{ allowed: true },
502+
],
503+
});
497504
jest.clearAllMocks();
498505
});
499506

@@ -552,6 +559,40 @@ describe('TableCells Components', () => {
552559
await user.hover(infoIcon);
553560
expect(screen.getByText(/Please go to Django Admin to manage it/i)).toBeInTheDocument();
554561
});
562+
563+
it('renders a disabled button when user does not have permission', async () => {
564+
(useValidateUserPermissions as jest.Mock).mockReturnValue({
565+
isLoading: false,
566+
data: [
567+
{ allowed: false },
568+
],
569+
});
570+
const CustomActionsCell = createActionsCell({
571+
onClickDeleteButton: mockOnClickDeleteButton,
572+
isUserAuthenticatedPage: false,
573+
});
574+
renderWrapper(<CustomActionsCell row={baseRow} column={{ id: 'actions' }} />);
575+
576+
const deleteButton = screen.queryByRole('button', { name: /delete role action/i });
577+
expect(deleteButton).toBeDisabled();
578+
});
579+
580+
it('renders no button when permissions is loading', async () => {
581+
(useValidateUserPermissions as jest.Mock).mockReturnValue({
582+
data: [
583+
{ allowed: false },
584+
],
585+
isLoading: true,
586+
});
587+
const CustomActionsCell = createActionsCell({
588+
onClickDeleteButton: mockOnClickDeleteButton,
589+
isUserAuthenticatedPage: false,
590+
});
591+
renderWrapper(<CustomActionsCell row={baseRow} column={{ id: 'actions' }} />);
592+
593+
const deleteButton = screen.queryByRole('button', { name: /delete role action/i });
594+
expect(deleteButton).not.toBeInTheDocument();
595+
});
555596
});
556597

557598
describe('ViewAllPermissionsCell', () => {

src/authz-module/components/TableCells.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ import {
1010
} from '@src/types';
1111
import { useNavigate } from 'react-router-dom';
1212
import { useContext, useMemo } from 'react';
13-
import { ADMIN_ROLES, DJANGO_MANAGED_ROLES, MAP_ROLE_KEY_TO_LABEL } from '@src/authz-module/constants';
13+
import {
14+
ADMIN_ROLES, CONTENT_COURSE_PERMISSIONS, CONTENT_LIBRARY_PERMISSIONS, DJANGO_MANAGED_ROLES, MAP_ROLE_KEY_TO_LABEL,
15+
} from '@src/authz-module/constants';
1416
import {
1517
Icon, IconButton, OverlayTrigger, Tooltip, DataTableContext,
1618
} from '@openedx/paragon';
19+
import { useValidateUserPermissions } from '@src/data/hooks';
1720
import { RESOURCE_ICONS } from './constants';
1821
import messages from './messages';
1922
import ViewMoreLink from './ViewMoreLink';
@@ -162,7 +165,13 @@ const ViewAllPermissionsCell = ({ row }: CellProps) => {
162165

163166
const ActionsCell = ({ row, onClickDeleteButton, isUserAuthenticatedPage }: ActionsCellProps) => {
164167
const { formatMessage } = useIntl();
165-
const { role } = row.original;
168+
const { role, scope } = row.original;
169+
const action = role.startsWith('lib') ? CONTENT_LIBRARY_PERMISSIONS.MANAGE_LIBRARY_TEAM : CONTENT_COURSE_PERMISSIONS.MANAGE_COURSE_TEAM;
170+
const permission = {
171+
action,
172+
scope,
173+
};
174+
const { data: hasPermission, isLoading } = useValidateUserPermissions([permission]);
166175

167176
const handleDelete = () => {
168177
const roleToDelete = {
@@ -204,8 +213,10 @@ const ActionsCell = ({ row, onClickDeleteButton, isUserAuthenticatedPage }: Acti
204213
);
205214
}
206215

216+
if (isLoading) { return null; }
217+
207218
return (
208-
<IconButton variant="danger" onClick={handleDelete} alt={formatMessage(messages['authz.user.table.delete.action.alt'])} src={Delete} />
219+
<IconButton disabled={!hasPermission[0]?.allowed} variant="danger" onClick={handleDelete} alt={formatMessage(messages['authz.user.table.delete.action.alt'])} src={Delete} />
209220
);
210221
};
211222

src/authz-module/data/hooks.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,18 +129,19 @@ export const useValidateUsers = () => useMutation({
129129
*/
130130
export const useRevokeUserRoles = () => {
131131
const queryClient = useQueryClient();
132-
const { querySettings: defaultQuerySettings } = useQuerySettings();
133132
return useMutation({
134133
mutationFn: async ({ data }: {
135134
data: RevokeUserRolesRequest
136135
}) => revokeUserRoles(data),
137-
onSettled: (_data, _error, { data: { scope, users, querySettings } }) => {
136+
onSettled: (_data, _error, { data: { scope } }) => {
138137
queryClient.invalidateQueries({ queryKey: authzQueryKeys.teamMembersAll(scope) });
139138
queryClient.invalidateQueries({ queryKey: authzQueryKeys.permissionsByRole(scope) });
140139
queryClient.invalidateQueries({
141-
queryKey: authzQueryKeys.userRoles(users, querySettings),
140+
predicate: (query) => query.queryKey.includes('userRoles'),
141+
});
142+
queryClient.invalidateQueries({
143+
predicate: (query) => query.queryKey.includes('allRoleAssignments'),
142144
});
143-
queryClient.invalidateQueries({ queryKey: authzQueryKeys.allRoleAssignments(defaultQuerySettings) });
144145
},
145146
});
146147
};

0 commit comments

Comments
 (0)