From ef68de0d65011e144e5018abfc1dcfdbeb689337 Mon Sep 17 00:00:00 2001 From: jacobo-dominguez-wgu Date: Sat, 21 Mar 2026 10:03:36 -0600 Subject: [PATCH 01/11] feat: roles table for audit user page --- src/authz-module/audit-user/CustomCells.tsx | 44 +++++++++++++++++++ src/authz-module/audit-user/messages.ts | 15 +++++++ src/authz-module/audit-user/utils.ts | 7 +++ .../components/TableFooter/TableFooter.tsx | 7 +-- src/authz-module/components/messages.ts | 15 ------- src/authz-module/index.tsx | 4 ++ 6 files changed, 74 insertions(+), 18 deletions(-) create mode 100644 src/authz-module/audit-user/CustomCells.tsx create mode 100644 src/authz-module/audit-user/utils.ts diff --git a/src/authz-module/audit-user/CustomCells.tsx b/src/authz-module/audit-user/CustomCells.tsx new file mode 100644 index 00000000..c8b896c2 --- /dev/null +++ b/src/authz-module/audit-user/CustomCells.tsx @@ -0,0 +1,44 @@ +import { useIntl } from '@edx/frontend-platform/i18n'; +import ViewMoreLink from '@src/authz-module/components/ViewMoreLink'; +import { Delete, ExpandMore } from '@openedx/paragon/icons'; +import { IconButton } from '@openedx/paragon'; +import { TableCellValue, UserRole } from 'types'; +import messages from './messages'; +import { getPermissionsCountByRole } from './utils'; + +type CellProps = TableCellValue; + +export const ViewAllPermissionsCell = ({ row }: CellProps) => { + const { formatMessage } = useIntl(); + return ( + console.log('View more clicked for row:', row)} + iconSrc={ExpandMore} + /> + ); +}; + +export const ActionsCell = ({ row }: CellProps) => { + const { formatMessage } = useIntl(); + const handleDelete = () => { + // TODO: Implement delete functionality + console.log('Delete clicked for row:', row); + }; + + return ( + + ); +}; + +export const PermissionsCell = ({ row }: CellProps) => { + const { formatMessage } = useIntl(); + // TODO handle permissions length per role + const count = getPermissionsCountByRole(row.original.role); + return ( + + {formatMessage(messages['authz.user.table.permissions.available.count'], { count })} + + ); +}; diff --git a/src/authz-module/audit-user/messages.ts b/src/authz-module/audit-user/messages.ts index dbf9d7b3..dd3747fe 100644 --- a/src/authz-module/audit-user/messages.ts +++ b/src/authz-module/audit-user/messages.ts @@ -27,6 +27,21 @@ const messages = defineMessages( defaultMessage: 'Actions', description: 'Header for the actions column in the user table', }, + 'authz.user.table.view_all_permissions.link.text': { + id: 'authz.user.table.view_all_permissions.link.text', + defaultMessage: 'View all permissions', + description: 'Text for the link to view all permissions in the user table', + }, + 'authz.user.table.delete.action.alt': { + id: 'authz.user.table.delete.action.alt', + defaultMessage: 'Delete role action', + description: 'Alt description for delete button', + }, + 'authz.user.table.permissions.available.count': { + id: 'authz.user.table.permissions.available.count', + defaultMessage: '{count, plural, one {# permission available} other {# permissions available}}', + description: 'Text showing the number of permissions available, with proper pluralization', + }, }, ); diff --git a/src/authz-module/audit-user/utils.ts b/src/authz-module/audit-user/utils.ts new file mode 100644 index 00000000..d215d02e --- /dev/null +++ b/src/authz-module/audit-user/utils.ts @@ -0,0 +1,7 @@ +export const getPermissionsCountByRole = (role: string) => { +/* + const roleData = permissionsList.find(item => item.role === role); + return roleData ? roleData.permissions.length : 0; + */ + return Math.floor(Math.random() * 50); +}; diff --git a/src/authz-module/components/TableFooter/TableFooter.tsx b/src/authz-module/components/TableFooter/TableFooter.tsx index 26b3a974..30bc2fc9 100644 --- a/src/authz-module/components/TableFooter/TableFooter.tsx +++ b/src/authz-module/components/TableFooter/TableFooter.tsx @@ -6,14 +6,15 @@ import messages from '../messages'; const Footer = () => { const { formatMessage } = useIntl(); const { - pageCount, gotoPage, state, itemCount, rows, + pageCount, gotoPage, state, itemCount, // @ts-ignore-next-line - Paragon's DataTableContext is not typed } = useContext(DataTableContext); - const { pageIndex } = state; + const { pageIndex, pageSize } = state; + return ( - {formatMessage(messages['authz.table.footer.items.showing.text'], { pageSize: rows.length, itemCount })} + {formatMessage(messages['authz.table.footer.items.showing.text'], { pageSize, itemCount })} ( element={} /> } /> + } + /> From 2011551d248bb5fe52cb658a20cc680fd3c628a2 Mon Sep 17 00:00:00 2001 From: jacobo-dominguez-wgu Date: Mon, 13 Apr 2026 21:23:28 -0600 Subject: [PATCH 02/11] feat: integrating api for permissions assignments on audit user page table --- src/authz-module/audit-user/CustomCells.tsx | 44 ------------- src/authz-module/audit-user/messages.ts | 15 ----- src/authz-module/audit-user/utils.ts | 7 --- .../components/TableFooter/TableFooter.tsx | 7 +-- src/authz-module/components/messages.ts | 62 +++++++++---------- 5 files changed, 34 insertions(+), 101 deletions(-) delete mode 100644 src/authz-module/audit-user/CustomCells.tsx delete mode 100644 src/authz-module/audit-user/utils.ts diff --git a/src/authz-module/audit-user/CustomCells.tsx b/src/authz-module/audit-user/CustomCells.tsx deleted file mode 100644 index c8b896c2..00000000 --- a/src/authz-module/audit-user/CustomCells.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { useIntl } from '@edx/frontend-platform/i18n'; -import ViewMoreLink from '@src/authz-module/components/ViewMoreLink'; -import { Delete, ExpandMore } from '@openedx/paragon/icons'; -import { IconButton } from '@openedx/paragon'; -import { TableCellValue, UserRole } from 'types'; -import messages from './messages'; -import { getPermissionsCountByRole } from './utils'; - -type CellProps = TableCellValue; - -export const ViewAllPermissionsCell = ({ row }: CellProps) => { - const { formatMessage } = useIntl(); - return ( - console.log('View more clicked for row:', row)} - iconSrc={ExpandMore} - /> - ); -}; - -export const ActionsCell = ({ row }: CellProps) => { - const { formatMessage } = useIntl(); - const handleDelete = () => { - // TODO: Implement delete functionality - console.log('Delete clicked for row:', row); - }; - - return ( - - ); -}; - -export const PermissionsCell = ({ row }: CellProps) => { - const { formatMessage } = useIntl(); - // TODO handle permissions length per role - const count = getPermissionsCountByRole(row.original.role); - return ( - - {formatMessage(messages['authz.user.table.permissions.available.count'], { count })} - - ); -}; diff --git a/src/authz-module/audit-user/messages.ts b/src/authz-module/audit-user/messages.ts index dd3747fe..dbf9d7b3 100644 --- a/src/authz-module/audit-user/messages.ts +++ b/src/authz-module/audit-user/messages.ts @@ -27,21 +27,6 @@ const messages = defineMessages( defaultMessage: 'Actions', description: 'Header for the actions column in the user table', }, - 'authz.user.table.view_all_permissions.link.text': { - id: 'authz.user.table.view_all_permissions.link.text', - defaultMessage: 'View all permissions', - description: 'Text for the link to view all permissions in the user table', - }, - 'authz.user.table.delete.action.alt': { - id: 'authz.user.table.delete.action.alt', - defaultMessage: 'Delete role action', - description: 'Alt description for delete button', - }, - 'authz.user.table.permissions.available.count': { - id: 'authz.user.table.permissions.available.count', - defaultMessage: '{count, plural, one {# permission available} other {# permissions available}}', - description: 'Text showing the number of permissions available, with proper pluralization', - }, }, ); diff --git a/src/authz-module/audit-user/utils.ts b/src/authz-module/audit-user/utils.ts deleted file mode 100644 index d215d02e..00000000 --- a/src/authz-module/audit-user/utils.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const getPermissionsCountByRole = (role: string) => { -/* - const roleData = permissionsList.find(item => item.role === role); - return roleData ? roleData.permissions.length : 0; - */ - return Math.floor(Math.random() * 50); -}; diff --git a/src/authz-module/components/TableFooter/TableFooter.tsx b/src/authz-module/components/TableFooter/TableFooter.tsx index 30bc2fc9..26b3a974 100644 --- a/src/authz-module/components/TableFooter/TableFooter.tsx +++ b/src/authz-module/components/TableFooter/TableFooter.tsx @@ -6,15 +6,14 @@ import messages from '../messages'; const Footer = () => { const { formatMessage } = useIntl(); const { - pageCount, gotoPage, state, itemCount, + pageCount, gotoPage, state, itemCount, rows, // @ts-ignore-next-line - Paragon's DataTableContext is not typed } = useContext(DataTableContext); - const { pageIndex, pageSize } = state; - + const { pageIndex } = state; return ( - {formatMessage(messages['authz.table.footer.items.showing.text'], { pageSize, itemCount })} + {formatMessage(messages['authz.table.footer.items.showing.text'], { pageSize: rows.length, itemCount })} Date: Mon, 13 Apr 2026 22:26:14 -0600 Subject: [PATCH 03/11] test: adding unit test for audit user page components --- src/authz-module/constants.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/authz-module/constants.ts b/src/authz-module/constants.ts index 997b1366..ca58a6d5 100644 --- a/src/authz-module/constants.ts +++ b/src/authz-module/constants.ts @@ -122,6 +122,8 @@ export const MAX_TABLE_FILTERS_APPLIED = 10; export const AUTHZ_HOME_PATH = '/authz'; +export const AUTHZ_HOME_PATH = '/authz'; + export const MAP_ROLE_KEY_TO_LABEL: Record = { library_admin: 'Library Admin', library_author: 'Library Author', From a1bbb46258a903f61afe683a327a9f69338452c0 Mon Sep 17 00:00:00 2001 From: jacobo-dominguez-wgu Date: Sat, 21 Mar 2026 10:03:36 -0600 Subject: [PATCH 04/11] feat: roles table for audit user page --- src/authz-module/audit-user/CustomCells.tsx | 44 +++++++++++++++++++++ src/authz-module/audit-user/messages.ts | 15 +++++++ src/authz-module/audit-user/utils.ts | 7 ++++ 3 files changed, 66 insertions(+) create mode 100644 src/authz-module/audit-user/CustomCells.tsx create mode 100644 src/authz-module/audit-user/utils.ts diff --git a/src/authz-module/audit-user/CustomCells.tsx b/src/authz-module/audit-user/CustomCells.tsx new file mode 100644 index 00000000..c8b896c2 --- /dev/null +++ b/src/authz-module/audit-user/CustomCells.tsx @@ -0,0 +1,44 @@ +import { useIntl } from '@edx/frontend-platform/i18n'; +import ViewMoreLink from '@src/authz-module/components/ViewMoreLink'; +import { Delete, ExpandMore } from '@openedx/paragon/icons'; +import { IconButton } from '@openedx/paragon'; +import { TableCellValue, UserRole } from 'types'; +import messages from './messages'; +import { getPermissionsCountByRole } from './utils'; + +type CellProps = TableCellValue; + +export const ViewAllPermissionsCell = ({ row }: CellProps) => { + const { formatMessage } = useIntl(); + return ( + console.log('View more clicked for row:', row)} + iconSrc={ExpandMore} + /> + ); +}; + +export const ActionsCell = ({ row }: CellProps) => { + const { formatMessage } = useIntl(); + const handleDelete = () => { + // TODO: Implement delete functionality + console.log('Delete clicked for row:', row); + }; + + return ( + + ); +}; + +export const PermissionsCell = ({ row }: CellProps) => { + const { formatMessage } = useIntl(); + // TODO handle permissions length per role + const count = getPermissionsCountByRole(row.original.role); + return ( + + {formatMessage(messages['authz.user.table.permissions.available.count'], { count })} + + ); +}; diff --git a/src/authz-module/audit-user/messages.ts b/src/authz-module/audit-user/messages.ts index dbf9d7b3..dd3747fe 100644 --- a/src/authz-module/audit-user/messages.ts +++ b/src/authz-module/audit-user/messages.ts @@ -27,6 +27,21 @@ const messages = defineMessages( defaultMessage: 'Actions', description: 'Header for the actions column in the user table', }, + 'authz.user.table.view_all_permissions.link.text': { + id: 'authz.user.table.view_all_permissions.link.text', + defaultMessage: 'View all permissions', + description: 'Text for the link to view all permissions in the user table', + }, + 'authz.user.table.delete.action.alt': { + id: 'authz.user.table.delete.action.alt', + defaultMessage: 'Delete role action', + description: 'Alt description for delete button', + }, + 'authz.user.table.permissions.available.count': { + id: 'authz.user.table.permissions.available.count', + defaultMessage: '{count, plural, one {# permission available} other {# permissions available}}', + description: 'Text showing the number of permissions available, with proper pluralization', + }, }, ); diff --git a/src/authz-module/audit-user/utils.ts b/src/authz-module/audit-user/utils.ts new file mode 100644 index 00000000..d215d02e --- /dev/null +++ b/src/authz-module/audit-user/utils.ts @@ -0,0 +1,7 @@ +export const getPermissionsCountByRole = (role: string) => { +/* + const roleData = permissionsList.find(item => item.role === role); + return roleData ? roleData.permissions.length : 0; + */ + return Math.floor(Math.random() * 50); +}; From 7d5a0ade46149c24895c9a86c66fff60da425b50 Mon Sep 17 00:00:00 2001 From: jacobo-dominguez-wgu Date: Sat, 21 Mar 2026 10:03:36 -0600 Subject: [PATCH 05/11] feat: roles table for audit user page --- .../components/ProtectedRoute.tsx | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/authz-module/components/ProtectedRoute.tsx diff --git a/src/authz-module/components/ProtectedRoute.tsx b/src/authz-module/components/ProtectedRoute.tsx new file mode 100644 index 00000000..7db6a64d --- /dev/null +++ b/src/authz-module/components/ProtectedRoute.tsx @@ -0,0 +1,46 @@ +// src/components/ProtectedRoute.tsx +import { ReactElement } from 'react'; +import { useValidateUserPermissions } from '@src/data/hooks'; +import LoadingPage from 'components/LoadingPage'; +import { CustomErrors } from 'constants'; +import { CONTENT_COURSE_PERMISSIONS, CONTENT_LIBRARY_PERMISSIONS } from 'authz-module/constants'; + +const REQUIRED_USER_PERMISSIONS = [ + CONTENT_LIBRARY_PERMISSIONS.VIEW_LIBRARY_TEAM, + CONTENT_LIBRARY_PERMISSIONS.MANAGE_LIBRARY_TEAM, + CONTENT_COURSE_PERMISSIONS.VIEW_COURSE_TEAM, + CONTENT_COURSE_PERMISSIONS.MANAGE_COURSE_TEAM, +]; + +type ProtectedRouteProps = { + children: ReactElement; + fallback?: ReactElement; +}; + +export const ProtectedRoute = ({ + children, + fallback, +}: ProtectedRouteProps) => { + // TODO: which scope? + const requiredPermissions = REQUIRED_USER_PERMISSIONS.map(action => ({ action, scope: '*' })); + const { data: permissions, isLoading, isError } = useValidateUserPermissions(requiredPermissions); + + if (isLoading) { + return ; + } + + if (isError && fallback) { + return fallback; + } + if (isError) { + throw new Error(CustomErrors.SERVER_ERROR); + } + + const hasAccess = permissions.some(permission => permission.allowed); + + if (!hasAccess) { + throw new Error(CustomErrors.NO_ACCESS); + } + + return children; +}; From 893b4ea0172bd101ed30f7a493b830e80b2cd9d8 Mon Sep 17 00:00:00 2001 From: jacobo-dominguez-wgu Date: Mon, 23 Mar 2026 08:37:14 -0600 Subject: [PATCH 06/11] feat: delete role functionality added to user table --- src/authz-module/audit-user/CustomCells.tsx | 44 ------- src/authz-module/audit-user/index.tsx | 111 +++++++++++++++++- src/authz-module/audit-user/utils.ts | 7 -- .../components/ConfirmDeletionModal.tsx | 73 ++++++++++++ .../components/ProtectedRoute.tsx | 46 -------- src/authz-module/components/TableCells.tsx | 82 ++++++++++--- src/authz-module/components/messages.ts | 40 +++++++ src/authz-module/constants.ts | 3 +- src/authz-module/data/api.ts | 1 + src/authz-module/data/hooks.ts | 5 +- src/authz-module/messages.ts | 15 ++- src/types.ts | 2 + 12 files changed, 302 insertions(+), 127 deletions(-) delete mode 100644 src/authz-module/audit-user/CustomCells.tsx delete mode 100644 src/authz-module/audit-user/utils.ts create mode 100644 src/authz-module/components/ConfirmDeletionModal.tsx delete mode 100644 src/authz-module/components/ProtectedRoute.tsx diff --git a/src/authz-module/audit-user/CustomCells.tsx b/src/authz-module/audit-user/CustomCells.tsx deleted file mode 100644 index c8b896c2..00000000 --- a/src/authz-module/audit-user/CustomCells.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { useIntl } from '@edx/frontend-platform/i18n'; -import ViewMoreLink from '@src/authz-module/components/ViewMoreLink'; -import { Delete, ExpandMore } from '@openedx/paragon/icons'; -import { IconButton } from '@openedx/paragon'; -import { TableCellValue, UserRole } from 'types'; -import messages from './messages'; -import { getPermissionsCountByRole } from './utils'; - -type CellProps = TableCellValue; - -export const ViewAllPermissionsCell = ({ row }: CellProps) => { - const { formatMessage } = useIntl(); - return ( - console.log('View more clicked for row:', row)} - iconSrc={ExpandMore} - /> - ); -}; - -export const ActionsCell = ({ row }: CellProps) => { - const { formatMessage } = useIntl(); - const handleDelete = () => { - // TODO: Implement delete functionality - console.log('Delete clicked for row:', row); - }; - - return ( - - ); -}; - -export const PermissionsCell = ({ row }: CellProps) => { - const { formatMessage } = useIntl(); - // TODO handle permissions length per role - const count = getPermissionsCountByRole(row.original.role); - return ( - - {formatMessage(messages['authz.user.table.permissions.available.count'], { count })} - - ); -}; diff --git a/src/authz-module/audit-user/index.tsx b/src/authz-module/audit-user/index.tsx index 042d5b3b..762d5bbf 100644 --- a/src/authz-module/audit-user/index.tsx +++ b/src/authz-module/audit-user/index.tsx @@ -1,5 +1,10 @@ -import { useEffect, useMemo } from 'react'; +import { + useCallback, + useContext, useEffect, useMemo, useState, +} from 'react'; import { useIntl } from '@edx/frontend-platform/i18n'; +import { AppContext } from '@edx/frontend-platform/react'; +import type { AppContextType } from '@edx/frontend-platform/react'; import debounce from 'lodash.debounce'; import { Container, DataTable, @@ -12,21 +17,32 @@ import { useUserAccount } from '@src/data/hooks'; import baseMessages from '@src/authz-module/messages'; import AddRoleButton from '@src/authz-module/components/AddRoleButton'; import { - OrgCell, RoleCell, ScopeCell, PermissionsCell, ViewAllPermissionsCell, ActionsCell, + OrgCell, RoleCell, ScopeCell, PermissionsCell, ViewAllPermissionsCell, + createActionsCell, } from '@src/authz-module/components/TableCells'; import { useQuerySettings } from '@src/authz-module/hooks/useQuerySettings'; -import { useUserAssignedRoles } from '@src/authz-module/data/hooks'; +import { useRevokeUserRoles, useUserAssignedRoles } from '@src/authz-module/data/hooks'; +import { Role } from 'types'; +import { useToastManager } from 'authz-module/libraries-manager/ToastManagerContext'; import messages from './messages'; +import ConfirmDeletionModal from '../components/ConfirmDeletionModal'; const AuditUserPage = () => { const { formatMessage } = useIntl(); const { username } = useParams(); + const { authenticatedUser } = useContext(AppContext) as AppContextType; const navigate = useNavigate(); const { isLoading: isLoadingUser, data: user, isError: isErrorUser, error: errorUser, } = useUserAccount(username); const { querySettings, handleTableFetch } = useQuerySettings(); const { isLoading: isLoadingUserAssignments, data: { results: userAssignments, count } = { results: [], count: 0 } } = useUserAssignedRoles(username ?? '', querySettings); + const [roleToDelete, setRoleToDelete] = useState(null); + const [showConfirmDeletionModal, setShowConfirmDeletionModal] = useState(false); + const { + showToast, showErrorToast, Bold, Br, + } = useToastManager(); + const { mutate: revokeUserRoles, isPending: isRevokingUserRolePending } = useRevokeUserRoles(); const fetchData = useMemo(() => debounce(handleTableFetch, 500), [handleTableFetch]); @@ -41,12 +57,20 @@ const AuditUserPage = () => { useEffect(() => () => fetchData.cancel(), [fetchData]); + const handleShowConfirmDeletionModal = useCallback((role: Role) => { + if (isRevokingUserRolePending) { return; } + + setRoleToDelete(role); + setShowConfirmDeletionModal(true); + }, [isRevokingUserRolePending]); + const navLinks = useMemo(() => [ { label: formatMessage(baseMessages['authz.management.home.nav.link']), to: AUTHZ_HOME_PATH, }, ], [formatMessage]); + const additionalColumns = useMemo(() => [ { id: 'view_permissions', @@ -56,9 +80,13 @@ const AuditUserPage = () => { { id: 'action', Header: formatMessage(messages['authz.user.table.action.column.header']), - Cell: ActionsCell, + Cell: createActionsCell({ + onClickDeleteButton: handleShowConfirmDeletionModal, + isUserAuthenticatedPage: username === authenticatedUser.username, + }), }, - ], [formatMessage]); + ], [authenticatedUser.username, formatMessage, handleShowConfirmDeletionModal, username]); + const columns = useMemo(() => [ { Header: formatMessage(messages['authz.user.table.role.column.header']), @@ -83,10 +111,83 @@ const AuditUserPage = () => { disableSortBy: true, }, ], [formatMessage]); + const pageCount = Math.ceil(count / TABLE_DEFAULT_PAGE_SIZE); + const handleCloseConfirmDeletionModal = () => { + setRoleToDelete(null); + setShowConfirmDeletionModal(false); + }; + + const handleRevokeUserRole = () => { + if (!user || !roleToDelete) { return; } + + const data = { + users: user.username, + role: roleToDelete.role, + scope: roleToDelete.scope, + }; + + const runRevokeRole = (variables) => { + const variablesData = { + data: { + ...variables.data, + querySettings, + }, + + }; + revokeUserRoles(variablesData, { + onSuccess: (response) => { + const { errors } = response; + + if (errors.length) { + showToast({ + type: 'error', + message: formatMessage( + baseMessages['authz.team.toast.default.error.message'], + { Bold, Br }, + ), + }); + // authzQueryKeys.userRoles(username, querySettings), + return; + } + + const remainingRolesCount = count ? count - 1 : 0; + showToast({ + message: formatMessage( + baseMessages['authz.team.remove.user.toast.success.description'], + { + role: roleToDelete.name, + rolesCount: remainingRolesCount, + }, + ), + type: 'success', + }); + handleCloseConfirmDeletionModal(); + }, + onError: (error, retryVariables) => { + showErrorToast(error, () => runRevokeRole(retryVariables)); + }, + }); + }; + + runRevokeRole({ data }); + }; + return (
+ { -/* - const roleData = permissionsList.find(item => item.role === role); - return roleData ? roleData.permissions.length : 0; - */ - return Math.floor(Math.random() * 50); -}; diff --git a/src/authz-module/components/ConfirmDeletionModal.tsx b/src/authz-module/components/ConfirmDeletionModal.tsx new file mode 100644 index 00000000..152082ca --- /dev/null +++ b/src/authz-module/components/ConfirmDeletionModal.tsx @@ -0,0 +1,73 @@ +import { + ActionRow, AlertModal, Icon, ModalDialog, Stack, + StatefulButton, +} from '@openedx/paragon'; +import { useIntl } from '@edx/frontend-platform/i18n'; + +import { SpinnerSimple } from '@openedx/paragon/icons'; +import messages from './messages'; + +interface ConfirmDeletionModalProps { + isOpen: boolean; + close: () => void; + onSave: () => void; + isDeleting?: boolean; + context: { + userName: string; + scope: string; + role: string; + rolesCount: number; + } +} + +const ConfirmDeletionModal = ({ + isOpen, close, onSave, isDeleting, context, +}: ConfirmDeletionModalProps) => { + const intl = useIntl(); + return ( + + + {intl.formatMessage(messages['authz.manage.cancel.button'])} + + , + }} + state={isDeleting ? 'pending' : 'default'} + onClick={() => onSave()} + disabledStates={['pending']} + /> + + )} + isOverflowVisible={false} + > + +

{intl.formatMessage(messages['authz.team.remove.user.modal.body.1'], { + userName: context.userName, + scope: context.scope, + role: context.role, + })} +

+ {context.rolesCount === 1 && ( +

{intl.formatMessage(messages['authz.team.remove.user.modal.body.2'])}

+ )} +

{intl.formatMessage(messages['authz.team.remove.user.modal.body.3'])}

+
+ +
+ ); +}; + +export default ConfirmDeletionModal; diff --git a/src/authz-module/components/ProtectedRoute.tsx b/src/authz-module/components/ProtectedRoute.tsx deleted file mode 100644 index 7db6a64d..00000000 --- a/src/authz-module/components/ProtectedRoute.tsx +++ /dev/null @@ -1,46 +0,0 @@ -// src/components/ProtectedRoute.tsx -import { ReactElement } from 'react'; -import { useValidateUserPermissions } from '@src/data/hooks'; -import LoadingPage from 'components/LoadingPage'; -import { CustomErrors } from 'constants'; -import { CONTENT_COURSE_PERMISSIONS, CONTENT_LIBRARY_PERMISSIONS } from 'authz-module/constants'; - -const REQUIRED_USER_PERMISSIONS = [ - CONTENT_LIBRARY_PERMISSIONS.VIEW_LIBRARY_TEAM, - CONTENT_LIBRARY_PERMISSIONS.MANAGE_LIBRARY_TEAM, - CONTENT_COURSE_PERMISSIONS.VIEW_COURSE_TEAM, - CONTENT_COURSE_PERMISSIONS.MANAGE_COURSE_TEAM, -]; - -type ProtectedRouteProps = { - children: ReactElement; - fallback?: ReactElement; -}; - -export const ProtectedRoute = ({ - children, - fallback, -}: ProtectedRouteProps) => { - // TODO: which scope? - const requiredPermissions = REQUIRED_USER_PERMISSIONS.map(action => ({ action, scope: '*' })); - const { data: permissions, isLoading, isError } = useValidateUserPermissions(requiredPermissions); - - if (isLoading) { - return ; - } - - if (isError && fallback) { - return fallback; - } - if (isError) { - throw new Error(CustomErrors.SERVER_ERROR); - } - - const hasAccess = permissions.some(permission => permission.allowed); - - if (!hasAccess) { - throw new Error(CustomErrors.NO_ACCESS); - } - - return children; -}; diff --git a/src/authz-module/components/TableCells.tsx b/src/authz-module/components/TableCells.tsx index d15cb78f..4d5fb927 100644 --- a/src/authz-module/components/TableCells.tsx +++ b/src/authz-module/components/TableCells.tsx @@ -1,14 +1,19 @@ import { useIntl } from '@edx/frontend-platform/i18n'; -import { Icon, IconButton } from '@openedx/paragon'; import { AppContext } from '@edx/frontend-platform/react'; import { RemoveRedEye, Delete, ExpandMore, + Info, } from '@openedx/paragon/icons'; -import { TableCellValue, AppContextType, UserRole } from '@src/types'; +import { + TableCellValue, AppContextType, UserRole, Role, +} from '@src/types'; import { useNavigate } from 'react-router-dom'; import { useContext, useMemo } from 'react'; -import { DJANGO_MANAGED_ROLES, MAP_ROLE_KEY_TO_LABEL } from '@src/authz-module/constants'; +import { ADMIN_ROLES, DJANGO_MANAGED_ROLES, MAP_ROLE_KEY_TO_LABEL } from '@src/authz-module/constants'; +import { + Icon, IconButton, OverlayTrigger, Tooltip, +} from '@openedx/paragon'; import { RESOURCE_ICONS } from './constants'; import messages from './messages'; import ViewMoreLink from './ViewMoreLink'; @@ -22,6 +27,10 @@ type ExtendedCellProps = CellPropsWithValue & { getCellProps: (props?: Record) => Record; }; }; +type ActionsCellProps = CellProps & { + onClickDeleteButton: (role: Role) => void; + isUserAuthenticatedPage: boolean; +}; const NameCell = ({ row }: CellProps) => { const intl = useIntl(); @@ -109,19 +118,6 @@ const PermissionsCell = ({ row }: CellProps) => { ); }; -const ActionsCell = ({ row }: CellProps) => { - const { formatMessage } = useIntl(); - const handleDelete = () => { - // TODO: Implement delete functionality - // eslint-disable-next-line no-console - console.log('Delete clicked for row:', row); - }; - - return ( - - ); -}; - const ViewAllPermissionsCell = ({ row }: CellProps) => { const { formatMessage } = useIntl(); return ( @@ -135,6 +131,58 @@ const ViewAllPermissionsCell = ({ row }: CellProps) => { ); }; +const ActionsCell = ({ row, onClickDeleteButton, isUserAuthenticatedPage }: ActionsCellProps) => { + const { formatMessage } = useIntl(); + const { role } = row.original; + + const handleDelete = () => { + const roleToDelete = { + role, + scope: row.original.scope, + } as Role; + onClickDeleteButton(roleToDelete); + }; + + if (DJANGO_MANAGED_ROLES.includes(role)) { + return ( + + {formatMessage(messages['authz.user.table.delete.action.djangorole.tooltip'])} + + )} + > + + + ); + } + + if (ADMIN_ROLES.includes(role) && isUserAuthenticatedPage) { + return ( + + ); + } + + return ( + + ); +}; + +const createActionsCell = (extraProps) => function customActionsCell(cellProps) { + return ; +}; + export { NameCell, ViewActionCell, @@ -142,6 +190,6 @@ export { OrgCell, ScopeCell, PermissionsCell, - ActionsCell, ViewAllPermissionsCell, + createActionsCell, }; diff --git a/src/authz-module/components/messages.ts b/src/authz-module/components/messages.ts index bb566079..d2cbe5eb 100644 --- a/src/authz-module/components/messages.ts +++ b/src/authz-module/components/messages.ts @@ -117,6 +117,46 @@ const messages = defineMessages({ defaultMessage: 'Search to show more', description: 'Message displayed when there are more results available than currently shown', }, + 'authz.team.remove.user.modal.title': { + id: 'authz.team.remove.user.modal.title', + defaultMessage: 'Remove role?', + description: 'AuthZ team management remove user modal title', + }, + 'authz.manage.cancel.button': { + id: 'authz.manage.cancel.button', + defaultMessage: 'Cancel', + description: 'AuthZ cancel button title', + }, + 'authz.manage.remove.button': { + id: 'authz.manage.remove.button', + defaultMessage: 'Remove', + description: 'AuthZ remove button title', + }, + 'authz.manage.removing.button': { + id: 'authz.manage.removing.button', + defaultMessage: 'Removing...', + description: 'AuthZ removing button title', + }, + 'authz.team.remove.user.modal.body.1': { + id: 'authz.team.remove.user.modal.body.1', + defaultMessage: 'Are you sure you want to remove the {role} role from the user “{userName}” in the scope {scope}?', + description: 'AuthZ team management remove user modal body', + }, + 'authz.team.remove.user.modal.body.2': { + id: 'authz.team.remove.user.modal.body.2', + defaultMessage: "This is the user's only role in this scope. Removing it will revoke their access completely, and they will no longer appear in the scope's member list.", + description: 'AuthZ team management remove user modal body', + }, + 'authz.team.remove.user.modal.body.3': { + id: 'authz.team.remove.user.modal.body.3', + defaultMessage: 'Are you sure you want to proceed?', + description: 'AuthZ team management remove user modal body', + }, + 'authz.user.table.delete.action.djangorole.tooltip': { + id: 'authz.user.table.delete.action.djangorole.tooltip', + defaultMessage: 'You can’t remove this role here. Please go to Django Admin to manage it.', + description: 'Tooltip for delete button when hovering over Django roles', + }, }); export default messages; diff --git a/src/authz-module/constants.ts b/src/authz-module/constants.ts index ca58a6d5..f94c2c56 100644 --- a/src/authz-module/constants.ts +++ b/src/authz-module/constants.ts @@ -122,8 +122,6 @@ export const MAX_TABLE_FILTERS_APPLIED = 10; export const AUTHZ_HOME_PATH = '/authz'; -export const AUTHZ_HOME_PATH = '/authz'; - export const MAP_ROLE_KEY_TO_LABEL: Record = { library_admin: 'Library Admin', library_author: 'Library Author', @@ -142,3 +140,4 @@ export const DJANGO_MANAGED_ROLES = ['django.superuser', 'django.globalstaff']; export const TABLE_DEFAULT_PAGE_SIZE = 10; export const DEFAULT_FILTER_PAGE_SIZE = 5; +export const ADMIN_ROLES = ['course_admin', 'library_admin']; diff --git a/src/authz-module/data/api.ts b/src/authz-module/data/api.ts index b9db8bbb..6677a618 100644 --- a/src/authz-module/data/api.ts +++ b/src/authz-module/data/api.ts @@ -33,6 +33,7 @@ export type RevokeUserRolesRequest = { users: string; role: string; scope: string; + querySettings?: QuerySettings; }; export interface DeleteRevokeUserRolesResponse { diff --git a/src/authz-module/data/hooks.ts b/src/authz-module/data/hooks.ts index cfbc71ba..c478b444 100644 --- a/src/authz-module/data/hooks.ts +++ b/src/authz-module/data/hooks.ts @@ -111,9 +111,12 @@ export const useRevokeUserRoles = () => { mutationFn: async ({ data }: { data: RevokeUserRolesRequest }) => revokeUserRoles(data), - onSettled: (_data, _error, { data: { scope } }) => { + onSettled: (_data, _error, { data: { scope, users, querySettings } }) => { queryClient.invalidateQueries({ queryKey: authzQueryKeys.teamMembersAll(scope) }); queryClient.invalidateQueries({ queryKey: authzQueryKeys.permissionsByRole(scope) }); + queryClient.invalidateQueries({ + queryKey: authzQueryKeys.userRoles(users, querySettings), + }); }, }); }; diff --git a/src/authz-module/messages.ts b/src/authz-module/messages.ts index 48d0a3c2..286b5f40 100644 --- a/src/authz-module/messages.ts +++ b/src/authz-module/messages.ts @@ -7,16 +7,21 @@ const messages = defineMessages( defaultMessage: 'Roles and Permissions Management', description: 'Text for the roles and permissions management home page title navigation link', }, - 'authz.management.specific.user.nav.link': { - id: 'authz.management.specific.user.nav.link', - defaultMessage: 'Specific User', - description: 'Text for the specific user page navigation link', - }, 'authz.management.assign.role.title': { id: 'authz.management.assign.role.title', defaultMessage: 'Assign Role', description: 'Text for the assign role button', }, + 'authz.team.toast.default.error.message': { + id: 'authz.team.toast.default.error.message', + defaultMessage: 'Something went wrong on our end.

Please try again later.', + description: 'Libraries default error message', + }, + 'authz.team.remove.user.toast.success.description': { + id: 'authz.team.remove.user.toast.success.description', + defaultMessage: 'The {role} role has been successfully removed.{rolesCount, plural, =0 { The user no longer has access to this library and has been removed from the member list.} other {}}', + description: 'Libraries team management remove user toast success', + }, }, ); diff --git a/src/types.ts b/src/types.ts index 1ce1c194..baf50b07 100644 --- a/src/types.ts +++ b/src/types.ts @@ -32,7 +32,9 @@ export interface RoleMetadata { name: string; description: string; } +// TODO: remove unnecessary fields when libraries gets removed export interface Role extends RoleMetadata { + scope: string; userCount: number; permissions: string[]; disabled?: boolean; From a5c61b0809eb7c4a3574beb485d8eb2cd6354a59 Mon Sep 17 00:00:00 2001 From: jacobo-dominguez-wgu Date: Tue, 14 Apr 2026 12:39:01 -0600 Subject: [PATCH 07/11] refactor: for toast manager context and some messages ids and descriptions --- src/authz-module/audit-user/index.tsx | 2 +- .../context}/ToastManagerContext.test.tsx | 0 .../context}/ToastManagerContext.tsx | 16 +++++----- src/authz-module/index.tsx | 2 +- .../LibrariesTeamManager.test.tsx | 2 +- .../LibrariesUserManager.test.tsx | 2 +- .../LibrariesUserManager.tsx | 2 +- .../AddNewTeamMemberTrigger.test.tsx | 2 +- .../AddNewTeamMemberTrigger.tsx | 2 +- .../AssignNewRoleTrigger.test.tsx | 2 +- .../AssignNewRoleTrigger.tsx | 2 +- .../components/TeamTable/index.test.tsx | 2 +- .../components/TeamTable/index.tsx | 2 +- .../libraries-manager/messages.ts | 28 +++++++++--------- src/authz-module/messages.ts | 29 +++++++++++++++++-- .../roles-permissions/libraries/messages.ts | 28 +++++++++--------- 16 files changed, 74 insertions(+), 49 deletions(-) rename src/authz-module/{libraries-manager => data/context}/ToastManagerContext.test.tsx (100%) rename src/authz-module/{libraries-manager => data/context}/ToastManagerContext.tsx (85%) diff --git a/src/authz-module/audit-user/index.tsx b/src/authz-module/audit-user/index.tsx index 762d5bbf..d64964a7 100644 --- a/src/authz-module/audit-user/index.tsx +++ b/src/authz-module/audit-user/index.tsx @@ -23,7 +23,7 @@ import { import { useQuerySettings } from '@src/authz-module/hooks/useQuerySettings'; import { useRevokeUserRoles, useUserAssignedRoles } from '@src/authz-module/data/hooks'; import { Role } from 'types'; -import { useToastManager } from 'authz-module/libraries-manager/ToastManagerContext'; +import { useToastManager } from '@src/authz-module/data/context/ToastManagerContext'; import messages from './messages'; import ConfirmDeletionModal from '../components/ConfirmDeletionModal'; diff --git a/src/authz-module/libraries-manager/ToastManagerContext.test.tsx b/src/authz-module/data/context/ToastManagerContext.test.tsx similarity index 100% rename from src/authz-module/libraries-manager/ToastManagerContext.test.tsx rename to src/authz-module/data/context/ToastManagerContext.test.tsx diff --git a/src/authz-module/libraries-manager/ToastManagerContext.tsx b/src/authz-module/data/context/ToastManagerContext.tsx similarity index 85% rename from src/authz-module/libraries-manager/ToastManagerContext.tsx rename to src/authz-module/data/context/ToastManagerContext.tsx index 6871f2fd..04b76c7c 100644 --- a/src/authz-module/libraries-manager/ToastManagerContext.tsx +++ b/src/authz-module/data/context/ToastManagerContext.tsx @@ -4,20 +4,20 @@ import { import { logError } from '@edx/frontend-platform/logging'; import { useIntl } from '@edx/frontend-platform/i18n'; import { Toast } from '@openedx/paragon'; -import messages from './messages'; -import { DEFAULT_TOAST_DELAY, RETRY_TOAST_DELAY } from '../constants'; +import messages from '@src/authz-module/messages'; +import { DEFAULT_TOAST_DELAY, RETRY_TOAST_DELAY } from '../../constants'; type ToastType = 'success' | 'error' | 'error-retry'; export const ERROR_TOAST_MAP: Record = { // Transient (retryable) server errors - 500: { type: 'error-retry', messageId: 'library.authz.team.toast.500.error.message' }, - 502: { type: 'error-retry', messageId: 'library.authz.team.toast.502.error.message' }, - 503: { type: 'error-retry', messageId: 'library.authz.team.toast.503.error.message' }, - 408: { type: 'error-retry', messageId: 'library.authz.team.toast.408.error.message' }, + 500: { type: 'error-retry', messageId: 'authz.team.toast.500.error.message' }, + 502: { type: 'error-retry', messageId: 'authz.team.toast.502.error.message' }, + 503: { type: 'error-retry', messageId: 'authz.team.toast.503.error.message' }, + 408: { type: 'error-retry', messageId: 'authz.team.toast.408.error.message' }, // Generic fallback error - DEFAULT: { type: 'error-retry', messageId: 'library.authz.team.toast.default.error.message' }, + DEFAULT: { type: 'error-retry', messageId: 'authz.team.toast.default.error.message' }, }; export interface AppToast { @@ -108,7 +108,7 @@ export const ToastManagerProvider = ({ children }: ToastManagerProviderProps) => discardToast(toast.id); toast.onRetry?.(); }, - label: intl.formatMessage(messages['library.authz.team.toast.retry.label']), + label: intl.formatMessage(messages['authz.team.toast.retry.label']), } : undefined} > {toast.message} diff --git a/src/authz-module/index.tsx b/src/authz-module/index.tsx index 147b14ad..d60e53f1 100644 --- a/src/authz-module/index.tsx +++ b/src/authz-module/index.tsx @@ -5,7 +5,7 @@ import { QueryErrorResetBoundary } from '@tanstack/react-query'; import LoadingPage from '@src/components/LoadingPage'; import LibrariesErrorFallback from '@src/authz-module/libraries-manager/ErrorPage'; import { CustomErrors } from '@src/constants'; -import { ToastManagerProvider } from './libraries-manager/ToastManagerContext'; +import { ToastManagerProvider } from './data/context/ToastManagerContext'; import { LibrariesUserManager, LibrariesLayout, LibrariesTeamManager } from './libraries-manager'; import AuthzHome from './authz-home'; import AuditUserPage from './audit-user'; diff --git a/src/authz-module/libraries-manager/LibrariesTeamManager.test.tsx b/src/authz-module/libraries-manager/LibrariesTeamManager.test.tsx index 959c044e..205ed9bd 100644 --- a/src/authz-module/libraries-manager/LibrariesTeamManager.test.tsx +++ b/src/authz-module/libraries-manager/LibrariesTeamManager.test.tsx @@ -5,7 +5,7 @@ import { initializeMockApp } from '@edx/frontend-platform/testing'; import { useLibrary } from '@src/authz-module/data/hooks'; import { useLibraryAuthZ } from './context'; import LibrariesTeamManager from './LibrariesTeamManager'; -import { ToastManagerProvider } from './ToastManagerContext'; +import { ToastManagerProvider } from '../data/context/ToastManagerContext'; import { CONTENT_LIBRARY_PERMISSIONS } from '../constants'; jest.mock('./context', () => { diff --git a/src/authz-module/libraries-manager/LibrariesUserManager.test.tsx b/src/authz-module/libraries-manager/LibrariesUserManager.test.tsx index 77ea2ca9..2a125883 100644 --- a/src/authz-module/libraries-manager/LibrariesUserManager.test.tsx +++ b/src/authz-module/libraries-manager/LibrariesUserManager.test.tsx @@ -5,7 +5,7 @@ import { renderWrapper } from '@src/setupTest'; import LibrariesUserManager from './LibrariesUserManager'; import { useLibraryAuthZ } from './context'; import { useLibrary, useTeamMembers, useRevokeUserRoles } from '../data/hooks'; -import { ToastManagerProvider } from './ToastManagerContext'; +import { ToastManagerProvider } from '../data/context/ToastManagerContext'; jest.mock('@edx/frontend-platform/logging', () => ({ logError: jest.fn(), diff --git a/src/authz-module/libraries-manager/LibrariesUserManager.tsx b/src/authz-module/libraries-manager/LibrariesUserManager.tsx index 7f495d42..3d4c1cd0 100644 --- a/src/authz-module/libraries-manager/LibrariesUserManager.tsx +++ b/src/authz-module/libraries-manager/LibrariesUserManager.tsx @@ -4,7 +4,7 @@ import { useIntl } from '@edx/frontend-platform/i18n'; import { Container, Skeleton } from '@openedx/paragon'; import { ROUTES } from '@src/authz-module/constants'; import { Role } from 'types'; -import { useToastManager } from '@src/authz-module/libraries-manager/ToastManagerContext'; +import { useToastManager } from '@src/authz-module/data/context/ToastManagerContext'; import AuthZLayout from '../components/AuthZLayout'; import { useLibraryAuthZ } from './context'; import RoleCard from '../components/RoleCard'; diff --git a/src/authz-module/libraries-manager/components/AddNewTeamMemberModal/AddNewTeamMemberTrigger.test.tsx b/src/authz-module/libraries-manager/components/AddNewTeamMemberModal/AddNewTeamMemberTrigger.test.tsx index 727d6a1f..306373f2 100644 --- a/src/authz-module/libraries-manager/components/AddNewTeamMemberModal/AddNewTeamMemberTrigger.test.tsx +++ b/src/authz-module/libraries-manager/components/AddNewTeamMemberModal/AddNewTeamMemberTrigger.test.tsx @@ -3,7 +3,7 @@ import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { renderWrapper } from '@src/setupTest'; import { useAssignTeamMembersRole } from '@src/authz-module/data/hooks'; -import { ToastManagerProvider } from '@src/authz-module/libraries-manager/ToastManagerContext'; +import { ToastManagerProvider } from 'authz-module/data/context/ToastManagerContext'; import AddNewTeamMemberTrigger from './AddNewTeamMemberTrigger'; jest.mock('@edx/frontend-platform/logging'); diff --git a/src/authz-module/libraries-manager/components/AddNewTeamMemberModal/AddNewTeamMemberTrigger.tsx b/src/authz-module/libraries-manager/components/AddNewTeamMemberModal/AddNewTeamMemberTrigger.tsx index e21da8d9..99d23bbf 100644 --- a/src/authz-module/libraries-manager/components/AddNewTeamMemberModal/AddNewTeamMemberTrigger.tsx +++ b/src/authz-module/libraries-manager/components/AddNewTeamMemberModal/AddNewTeamMemberTrigger.tsx @@ -6,7 +6,7 @@ import { Plus } from '@openedx/paragon/icons'; import { PutAssignTeamMembersRoleResponse } from '@src/authz-module/data/api'; import { useAssignTeamMembersRole } from '@src/authz-module/data/hooks'; import { RoleOperationErrorStatus, DEFAULT_TOAST_DELAY } from '@src/authz-module/constants'; -import { AppToast, useToastManager } from '@src/authz-module/libraries-manager/ToastManagerContext'; +import { AppToast, useToastManager } from '@src/authz-module/data/context/ToastManagerContext'; import baseMessages from '@src/authz-module/messages'; import AddNewTeamMemberModal from './AddNewTeamMemberModal'; import messages from './messages'; diff --git a/src/authz-module/libraries-manager/components/AssignNewRoleModal/AssignNewRoleTrigger.test.tsx b/src/authz-module/libraries-manager/components/AssignNewRoleModal/AssignNewRoleTrigger.test.tsx index a8d10830..b6aafa0f 100644 --- a/src/authz-module/libraries-manager/components/AssignNewRoleModal/AssignNewRoleTrigger.test.tsx +++ b/src/authz-module/libraries-manager/components/AssignNewRoleModal/AssignNewRoleTrigger.test.tsx @@ -3,7 +3,7 @@ import userEvent from '@testing-library/user-event'; import { renderWrapper } from '@src/setupTest'; import { useLibraryAuthZ } from '@src/authz-module/libraries-manager/context'; import { useAssignTeamMembersRole } from '@src/authz-module/data/hooks'; -import { ToastManagerProvider } from '@src/authz-module/libraries-manager/ToastManagerContext'; +import { ToastManagerProvider } from 'authz-module/data/context/ToastManagerContext'; import AssignNewRoleTrigger from './AssignNewRoleTrigger'; jest.mock('@edx/frontend-platform/logging'); diff --git a/src/authz-module/libraries-manager/components/AssignNewRoleModal/AssignNewRoleTrigger.tsx b/src/authz-module/libraries-manager/components/AssignNewRoleModal/AssignNewRoleTrigger.tsx index dc04c642..88023730 100644 --- a/src/authz-module/libraries-manager/components/AssignNewRoleModal/AssignNewRoleTrigger.tsx +++ b/src/authz-module/libraries-manager/components/AssignNewRoleModal/AssignNewRoleTrigger.tsx @@ -5,7 +5,7 @@ import { Plus } from '@openedx/paragon/icons'; import { useLibraryAuthZ } from '@src/authz-module/libraries-manager/context'; import { useAssignTeamMembersRole } from '@src/authz-module/data/hooks'; -import { useToastManager } from '@src/authz-module/libraries-manager/ToastManagerContext'; +import { useToastManager } from '@src/authz-module/data/context/ToastManagerContext'; import AssignNewRoleModal from './AssignNewRoleModal'; import messages from '../messages'; diff --git a/src/authz-module/libraries-manager/components/TeamTable/index.test.tsx b/src/authz-module/libraries-manager/components/TeamTable/index.test.tsx index f1229274..3fe89eea 100644 --- a/src/authz-module/libraries-manager/components/TeamTable/index.test.tsx +++ b/src/authz-module/libraries-manager/components/TeamTable/index.test.tsx @@ -3,7 +3,7 @@ import userEvent from '@testing-library/user-event'; import { renderWrapper } from '@src/setupTest'; import { useTeamMembers } from '@src/authz-module/data/hooks'; import { useLibraryAuthZ } from '@src/authz-module/libraries-manager/context'; -import { ToastManagerProvider } from '@src/authz-module/libraries-manager/ToastManagerContext'; +import { ToastManagerProvider } from 'authz-module/data/context/ToastManagerContext'; import { CONTENT_LIBRARY_PERMISSIONS } from '@src/authz-module/constants'; import TeamTable from './index'; diff --git a/src/authz-module/libraries-manager/components/TeamTable/index.tsx b/src/authz-module/libraries-manager/components/TeamTable/index.tsx index 77efa3ad..ec0a0246 100644 --- a/src/authz-module/libraries-manager/components/TeamTable/index.tsx +++ b/src/authz-module/libraries-manager/components/TeamTable/index.tsx @@ -10,8 +10,8 @@ import { import { useTeamMembers } from '@src/authz-module/data/hooks'; import { useLibraryAuthZ } from '@src/authz-module/libraries-manager/context'; -import { useToastManager } from '@src/authz-module/libraries-manager/ToastManagerContext'; import { SKELETON_ROWS, TABLE_DEFAULT_PAGE_SIZE } from '@src/authz-module/constants'; +import { useToastManager } from '@src/authz-module/data/context/ToastManagerContext'; import { useQuerySettings } from '@src/authz-module/hooks/useQuerySettings'; import TableControlBar from './components/TableControlBar'; import messages from './messages'; diff --git a/src/authz-module/libraries-manager/messages.ts b/src/authz-module/libraries-manager/messages.ts index bbb2ad41..27f65ceb 100644 --- a/src/authz-module/libraries-manager/messages.ts +++ b/src/authz-module/libraries-manager/messages.ts @@ -81,28 +81,28 @@ const messages = defineMessages({ defaultMessage: 'Something went wrong on our end.

Please try again later.', description: 'Libraries default error message', }, - 'library.authz.team.toast.500.error.message': { - id: 'library.authz.team.toast.500.error.message', + 'authz.team.toast.500.error.message': { + id: 'authz.team.toast.500.error.message', defaultMessage: 'We\'re experiencing technical difficulties.

Please try again later.', - description: 'Libraries internal server error message', + description: 'Internal server error message', }, - 'library.authz.team.toast.502.error.message': { - id: 'library.authz.team.toast.502.error.message', + 'authz.team.toast.502.error.message': { + id: 'authz.team.toast.502.error.message', defaultMessage: 'We\'re having trouble connecting to our services.

Please try again later.', - description: 'Libraries bad gateway error message', + description: 'Bad gateway error message', }, - 'library.authz.team.toast.503.error.message': { - id: 'library.authz.team.toast.503.error.message', + 'authz.team.toast.503.error.message': { + id: 'authz.team.toast.503.error.message', defaultMessage: 'The service is temporarily unavailable.

Please try again in a few moments.', - description: 'Libraries service temporary unavailable message', + description: 'Service temporarily unavailable message', }, - 'library.authz.team.toast.408.error.message': { - id: 'library.authz.team.toast.408.error.message', + 'authz.team.toast.408.error.message': { + id: 'authz.team.toast.408.error.message', defaultMessage: 'The request took too long.

Please check your connection and try again.', - description: 'Libraries request timeout message', + description: 'Request timeout message', }, - 'library.authz.team.toast.retry.label': { - id: 'library.authz.team.toast.retry.label', + 'authz.team.toast.retry.label': { + id: 'authz.team.toast.retry.label', defaultMessage: 'Retry', description: 'Label for retry button.', }, diff --git a/src/authz-module/messages.ts b/src/authz-module/messages.ts index 286b5f40..3b3916f6 100644 --- a/src/authz-module/messages.ts +++ b/src/authz-module/messages.ts @@ -15,12 +15,37 @@ const messages = defineMessages( 'authz.team.toast.default.error.message': { id: 'authz.team.toast.default.error.message', defaultMessage: 'Something went wrong on our end.

Please try again later.', - description: 'Libraries default error message', + description: 'Default error message', }, 'authz.team.remove.user.toast.success.description': { id: 'authz.team.remove.user.toast.success.description', defaultMessage: 'The {role} role has been successfully removed.{rolesCount, plural, =0 { The user no longer has access to this library and has been removed from the member list.} other {}}', - description: 'Libraries team management remove user toast success', + description: 'Team management remove user toast success', + }, + 'authz.team.toast.500.error.message': { + id: 'authz.team.toast.500.error.message', + defaultMessage: 'We\'re experiencing technical difficulties.

Please try again later.', + description: 'Internal server error message', + }, + 'authz.team.toast.502.error.message': { + id: 'authz.team.toast.502.error.message', + defaultMessage: 'We\'re having trouble connecting to our services.

Please try again later.', + description: 'Bad gateway error message', + }, + 'authz.team.toast.503.error.message': { + id: 'authz.team.toast.503.error.message', + defaultMessage: 'The service is temporarily unavailable.

Please try again in a few moments.', + description: 'Service temporarily unavailable message', + }, + 'authz.team.toast.408.error.message': { + id: 'authz.team.toast.408.error.message', + defaultMessage: 'The request took too long.

Please check your connection and try again.', + description: 'Request timeout message', + }, + 'authz.team.toast.retry.label': { + id: 'authz.team.toast.retry.label', + defaultMessage: 'Retry', + description: 'Label for retry button.', }, }, ); diff --git a/src/authz-module/roles-permissions/libraries/messages.ts b/src/authz-module/roles-permissions/libraries/messages.ts index bada55fd..edbfae66 100644 --- a/src/authz-module/roles-permissions/libraries/messages.ts +++ b/src/authz-module/roles-permissions/libraries/messages.ts @@ -61,28 +61,28 @@ const messages = defineMessages({ defaultMessage: 'Something went wrong on our end.

Please try again later.', description: 'Libraries default error message', }, - 'library.authz.team.toast.500.error.message': { - id: 'library.authz.team.toast.500.error.message', + 'authz.team.toast.500.error.message': { + id: 'authz.team.toast.500.error.message', defaultMessage: 'We\'re experiencing technical difficulties.

Please try again later.', - description: 'Libraries internal server error message', + description: 'Internal server error message', }, - 'library.authz.team.toast.502.error.message': { - id: 'library.authz.team.toast.502.error.message', + 'authz.team.toast.502.error.message': { + id: 'authz.team.toast.502.error.message', defaultMessage: 'We\'re having trouble connecting to our services.

Please try again later.', - description: 'Libraries bad gateway error message', + description: 'Bad gateway error message', }, - 'library.authz.team.toast.503.error.message': { - id: 'library.authz.team.toast.503.error.message', + 'authz.team.toast.503.error.message': { + id: 'authz.team.toast.503.error.message', defaultMessage: 'The service is temporarily unavailable.

Please try again in a few moments.', - description: 'Libraries service temporary unavailable message', + description: 'Service temporarily unavailable message', }, - 'library.authz.team.toast.408.error.message': { - id: 'library.authz.team.toast.408.error.message', + 'authz.team.toast.408.error.message': { + id: 'authz.team.toast.408.error.message', defaultMessage: 'The request took too long.

Please check your connection and try again.', - description: 'Libraries request timeout message', + description: 'Request timeout message', }, - 'library.authz.team.toast.retry.label': { - id: 'library.authz.team.toast.retry.label', + 'authz.team.toast.retry.label': { + id: 'authz.team.toast.retry.label', defaultMessage: 'Retry', description: 'Label for retry button.', }, From f99b9773edc21e92bfedf79c76bee6298584c524 Mon Sep 17 00:00:00 2001 From: jacobo-dominguez-wgu Date: Tue, 14 Apr 2026 15:22:16 -0600 Subject: [PATCH 08/11] test: for role deletion on roles table audit user page --- src/authz-module/audit-user/index.test.tsx | 145 ++++++++++++++++-- .../components/TableCells.test.tsx | 89 ++++++----- .../AddNewTeamMemberTrigger.test.tsx | 2 +- .../AssignNewRoleTrigger.test.tsx | 2 +- .../components/TeamTable/index.test.tsx | 2 +- 5 files changed, 192 insertions(+), 48 deletions(-) diff --git a/src/authz-module/audit-user/index.test.tsx b/src/authz-module/audit-user/index.test.tsx index 63fe18f6..18f06a0e 100644 --- a/src/authz-module/audit-user/index.test.tsx +++ b/src/authz-module/audit-user/index.test.tsx @@ -1,9 +1,11 @@ import { render, screen, waitFor } from '@testing-library/react'; +import { AppContext } from '@edx/frontend-platform/react'; import userEvent from '@testing-library/user-event'; import { MemoryRouter, Route, Routes } from 'react-router-dom'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { IntlProvider } from '@edx/frontend-platform/i18n'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { ToastManagerProvider } from '@src/authz-module/data/context/ToastManagerContext'; import AuditUserPage from './index'; jest.mock('@edx/frontend-platform/auth', () => ({ @@ -40,17 +42,32 @@ const renderWithRouter = (route = '/audit/johndoe') => { }, }); + const mockAppContext = { + authenticatedUser: { + username: 'testuser', + email: 'testuser@example.com', + }, + config: { + // @ts-ignore + ...process.env, + }, + }; + return render( - - - - - } /> - Home Page
} /> - - - - , + + + + + + + } /> + Home Page} /> + + + + + + , ); }; @@ -59,6 +76,11 @@ describe('AuditUserPage', () => { jest.clearAllMocks(); }); + beforeAll(() => { + // @ts-ignore + global.logError = jest.fn(); + }); + it('renders user info and table when data is loaded', async () => { (getAuthenticatedHttpClient as jest.Mock).mockReturnValue({ get: jest @@ -185,4 +207,107 @@ describe('AuditUserPage', () => { expect(screen.getByText(mockUser.username, { selector: 'li[aria-current="page"]' })).toBeInTheDocument(); }); }); + + it('opens and closes the ConfirmDeletionModal when delete is clicked and cancel is pressed', async () => { + (getAuthenticatedHttpClient as jest.Mock).mockReturnValue({ + get: jest + .fn() + .mockResolvedValueOnce({ data: mockUser }) + .mockResolvedValueOnce({ data: mockAssignments }), + }); + + renderWithRouter(); + + await waitFor(() => { + expect(screen.getByRole('button', { name: /delete role action/i })).toBeInTheDocument(); + }); + + const user = userEvent.setup(); + const deleteButton = screen.getByRole('button', { name: /delete role action/i }); + await user.click(deleteButton); + + await waitFor(() => { + expect(screen.getByRole('dialog')).toBeInTheDocument(); + expect(screen.getByText(/remove role\?/i)).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument(); + }); + + const cancelButton = screen.getByRole('button', { name: /cancel/i }); + await user.click(cancelButton); + + await waitFor(() => { + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + }); + }); + + it('calls onSave when confirming deletion in ConfirmDeletionModal', async () => { + (getAuthenticatedHttpClient as jest.Mock).mockReturnValue({ + get: jest + .fn() + .mockResolvedValueOnce({ data: mockUser }) + .mockResolvedValueOnce({ data: mockAssignments }), + delete: jest.fn().mockResolvedValue({ data: { errors: [] } }), + }); + + renderWithRouter(); + + await waitFor(() => { + expect(screen.getByRole('button', { name: /delete role action/i })).toBeInTheDocument(); + }); + + const user = userEvent.setup(); + const deleteButton = screen.getByRole('button', { name: /delete role action/i }); + await user.click(deleteButton); + + await waitFor(() => { + expect(screen.getByRole('dialog')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /remove/i })).toBeInTheDocument(); + }); + + const removeButton = screen.getByRole('button', { name: /remove/i }); + await user.click(removeButton); + + await waitFor(() => { + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + expect(screen.getByText(/role has been successfully removed/i)).toBeInTheDocument(); + }); + }); + + it('shows the extra warning when rolesCount is 1', async () => { + (getAuthenticatedHttpClient as jest.Mock).mockReturnValue({ + get: jest + .fn() + .mockResolvedValueOnce({ data: mockUser }) + .mockResolvedValueOnce({ + data: { + count: 1, + results: [ + { + id: '1', + role: 'library_admin', + org: 'Test Org', + scope: 'lib:test', + permissionCount: 5, + }, + ], + next: null, + previous: null, + }, + }), + }); + + renderWithRouter(); + + await waitFor(() => { + expect(screen.getByRole('button', { name: /delete role action/i })).toBeInTheDocument(); + }); + + const user = userEvent.setup(); + const deleteButton = screen.getByRole('button', { name: /delete role action/i }); + await user.click(deleteButton); + + await waitFor(() => { + expect(screen.getByText(/this is the user's only role/i)).toBeInTheDocument(); + }); + }); }); diff --git a/src/authz-module/components/TableCells.test.tsx b/src/authz-module/components/TableCells.test.tsx index 062b8593..02ace29f 100644 --- a/src/authz-module/components/TableCells.test.tsx +++ b/src/authz-module/components/TableCells.test.tsx @@ -9,8 +9,8 @@ import { OrgCell, ScopeCell, PermissionsCell, - ActionsCell, ViewAllPermissionsCell, + createActionsCell, } from './TableCells'; // TODO: remove console.log mocks and implement actual logic for these cells, then update tests accordingly @@ -481,56 +481,75 @@ describe('TableCells Components', () => { }); }); - describe('ActionsCell', () => { - const mockRow = { + describe('createActionsCell', () => { + const mockOnClickDeleteButton = jest.fn(); + const baseRow = { original: { - role: 'library_admin', id: '123', org: 'Test Org', scope: 'Test Scope', permissionCount: 1, + role: 'library_admin', + org: 'Test Org', + scope: 'Test Scope', + permissionCount: 1, }, }; - it('renders a delete button', () => { - const props = { - row: mockRow, - column: { id: 'actions' }, - }; + beforeEach(() => { + jest.clearAllMocks(); + }); - renderWrapper(); + it('renders a delete button and calls onClickDeleteButton when clicked', async () => { + const user = userEvent.setup(); + const CustomActionsCell = createActionsCell({ + onClickDeleteButton: mockOnClickDeleteButton, + isUserAuthenticatedPage: false, + }); + renderWrapper(); const deleteButton = screen.getByRole('button', { name: /delete role action/i }); expect(deleteButton).toBeInTheDocument(); + + await user.click(deleteButton); + expect(mockOnClickDeleteButton).toHaveBeenCalledWith({ role: 'library_admin', scope: 'Test Scope' }); }); - it('calls handleDelete when delete button is clicked', async () => { - const user = userEvent.setup(); - const props = { - row: mockRow, - column: { id: 'actions' }, + it('renders a disabled button for admin roles when isUserAuthenticatedPage is true', () => { + const adminRow = { + original: { + role: 'course_admin', + org: 'Test Org', + scope: 'Test Scope', + permissionCount: 1, + }, }; + const CustomActionsCell = createActionsCell({ + onClickDeleteButton: mockOnClickDeleteButton, + isUserAuthenticatedPage: true, + }); + renderWrapper(); - renderWrapper(); - - const deleteButton = screen.getByRole('button', { name: /delete role action/i }); - await user.click(deleteButton); - // TODO: replace console.log with actual delete logic and update this test accordingly - // eslint-disable-next-line no-console - expect(console.log).toHaveBeenCalledWith('Delete clicked for row:', mockRow); + const button = screen.getByRole('button', { name: /delete role action/i }); + expect(button).toBeDisabled(); }); - it('handles keyboard interaction for delete button', async () => { - const user = userEvent.setup(); - const props = { - row: mockRow, - column: { id: 'actions' }, + it('renders info icon with tooltip for Django managed roles', async () => { + const djangoRow = { + original: { + role: 'django.superuser', + org: 'Test Org', + scope: 'Test Scope', + permissionCount: 1, + }, }; + const user = userEvent.setup(); + const CustomActionsCell = createActionsCell({ + onClickDeleteButton: mockOnClickDeleteButton, + isUserAuthenticatedPage: true, + }); + renderWrapper(); - renderWrapper(); - - const deleteButton = screen.getByRole('button', { name: /delete role action/i }); - deleteButton.focus(); - await user.keyboard('{Enter}'); - // TODO: replace console.log with actual delete logic and update this test accordingly - // eslint-disable-next-line no-console - expect(console.log).toHaveBeenCalledWith('Delete clicked for row:', mockRow); + const infoIcon = screen.getByRole('img', { hidden: true }); + expect(infoIcon).toBeInTheDocument(); + await user.hover(infoIcon); + expect(screen.getByText(/Please go to Django Admin to manage it/i)).toBeInTheDocument(); }); }); diff --git a/src/authz-module/libraries-manager/components/AddNewTeamMemberModal/AddNewTeamMemberTrigger.test.tsx b/src/authz-module/libraries-manager/components/AddNewTeamMemberModal/AddNewTeamMemberTrigger.test.tsx index 306373f2..70456581 100644 --- a/src/authz-module/libraries-manager/components/AddNewTeamMemberModal/AddNewTeamMemberTrigger.test.tsx +++ b/src/authz-module/libraries-manager/components/AddNewTeamMemberModal/AddNewTeamMemberTrigger.test.tsx @@ -3,7 +3,7 @@ import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { renderWrapper } from '@src/setupTest'; import { useAssignTeamMembersRole } from '@src/authz-module/data/hooks'; -import { ToastManagerProvider } from 'authz-module/data/context/ToastManagerContext'; +import { ToastManagerProvider } from '@src/authz-module/data/context/ToastManagerContext'; import AddNewTeamMemberTrigger from './AddNewTeamMemberTrigger'; jest.mock('@edx/frontend-platform/logging'); diff --git a/src/authz-module/libraries-manager/components/AssignNewRoleModal/AssignNewRoleTrigger.test.tsx b/src/authz-module/libraries-manager/components/AssignNewRoleModal/AssignNewRoleTrigger.test.tsx index b6aafa0f..c63f4832 100644 --- a/src/authz-module/libraries-manager/components/AssignNewRoleModal/AssignNewRoleTrigger.test.tsx +++ b/src/authz-module/libraries-manager/components/AssignNewRoleModal/AssignNewRoleTrigger.test.tsx @@ -3,7 +3,7 @@ import userEvent from '@testing-library/user-event'; import { renderWrapper } from '@src/setupTest'; import { useLibraryAuthZ } from '@src/authz-module/libraries-manager/context'; import { useAssignTeamMembersRole } from '@src/authz-module/data/hooks'; -import { ToastManagerProvider } from 'authz-module/data/context/ToastManagerContext'; +import { ToastManagerProvider } from '@src/authz-module/data/context/ToastManagerContext'; import AssignNewRoleTrigger from './AssignNewRoleTrigger'; jest.mock('@edx/frontend-platform/logging'); diff --git a/src/authz-module/libraries-manager/components/TeamTable/index.test.tsx b/src/authz-module/libraries-manager/components/TeamTable/index.test.tsx index 3fe89eea..3d809e58 100644 --- a/src/authz-module/libraries-manager/components/TeamTable/index.test.tsx +++ b/src/authz-module/libraries-manager/components/TeamTable/index.test.tsx @@ -3,7 +3,7 @@ import userEvent from '@testing-library/user-event'; import { renderWrapper } from '@src/setupTest'; import { useTeamMembers } from '@src/authz-module/data/hooks'; import { useLibraryAuthZ } from '@src/authz-module/libraries-manager/context'; -import { ToastManagerProvider } from 'authz-module/data/context/ToastManagerContext'; +import { ToastManagerProvider } from '@src/authz-module/data/context/ToastManagerContext'; import { CONTENT_LIBRARY_PERMISSIONS } from '@src/authz-module/constants'; import TeamTable from './index'; From e9e0bfcb66738f63c063bf34cad80b113dc3e945 Mon Sep 17 00:00:00 2001 From: jacobo-dominguez-wgu Date: Sat, 18 Apr 2026 17:45:27 -0700 Subject: [PATCH 09/11] fix: minor issues after conflict solving --- src/authz-module/authz-home/index.test.tsx | 2 +- src/authz-module/data/api.ts | 14 +++++++------- src/authz-module/data/hooks.ts | 8 +++++--- .../team-members/TeamMembersTable.test.tsx | 2 +- src/authz-module/team-members/TeamMembersTable.tsx | 2 +- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/authz-module/authz-home/index.test.tsx b/src/authz-module/authz-home/index.test.tsx index 818984da..5544143d 100644 --- a/src/authz-module/authz-home/index.test.tsx +++ b/src/authz-module/authz-home/index.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { screen } from '@testing-library/react'; import { useAllRoleAssignments, useOrgs, useScopes } from '@src/authz-module/data/hooks'; -import { ToastManagerProvider } from '@src/authz-module/libraries-manager/ToastManagerContext'; +import { ToastManagerProvider } from '@src/authz-module/data/context/ToastManagerContext'; import { renderWithAllProviders } from '@src/setupTest'; import userEvent from '@testing-library/user-event'; import AuthzHome from './index'; diff --git a/src/authz-module/data/api.ts b/src/authz-module/data/api.ts index 6677a618..a2c1d477 100644 --- a/src/authz-module/data/api.ts +++ b/src/authz-module/data/api.ts @@ -200,22 +200,22 @@ export const getScopes = async (search?: string, page?: number, pageSize?: numbe return camelCaseObject(data); }; -export const getUserAssignedRoles = async (username: string, querySettings: QuerySettings) +export const getUserAssignedRoles = async (username?: string, querySettings?: QuerySettings) : Promise => { const url = new URL(getApiUrl(`/api/authz/v1/users/${username}/assignments/`)); - if (querySettings.roles) { + if (querySettings?.roles) { url.searchParams.set('roles', querySettings.roles); } - if (querySettings.search) { + if (querySettings?.search) { url.searchParams.set('search', querySettings.search); } - if (querySettings.sortBy && querySettings.order) { + if (querySettings?.sortBy && querySettings?.order) { url.searchParams.set('sort_by', querySettings.sortBy); - url.searchParams.set('order', querySettings.order); + url.searchParams.set('order', querySettings?.order || ''); } - url.searchParams.set('page_size', querySettings.pageSize.toString()); - url.searchParams.set('page', (querySettings.pageIndex + 1).toString()); + url.searchParams.set('page_size', querySettings?.pageSize?.toString() || ''); + url.searchParams.set('page', ((querySettings?.pageIndex ?? 0) + 1).toString()); const { data } = await getAuthenticatedHttpClient().get(url); return camelCaseObject(data); diff --git a/src/authz-module/data/hooks.ts b/src/authz-module/data/hooks.ts index c478b444..f811bc53 100644 --- a/src/authz-module/data/hooks.ts +++ b/src/authz-module/data/hooks.ts @@ -21,7 +21,7 @@ const authzQueryKeys = { allRoleAssignments: (querySettings?: QuerySettings) => [...authzQueryKeys.all, 'allRoleAssignments', querySettings] as const, orgs: (search?: string, page?: number, pageSize?: number) => [...authzQueryKeys.all, 'organizations', search, page, pageSize] as const, scopes: (search?: string, page?: number, pageSize?: number) => [...authzQueryKeys.all, 'scopes', search, page, pageSize] as const, - userRoles: (username: string, querySettings?: QuerySettings) => [...authzQueryKeys.all, 'userRoles', username, querySettings] as const, + userRoles: (username?: string, querySettings?: QuerySettings) => [...authzQueryKeys.all, 'userRoles', username, querySettings] as const, }; /** @@ -184,10 +184,12 @@ export const useScopes = (search?: string, page?: number, pageSize?: number) => * ``` */ export const useUserAssignedRoles = ( - username: string, - querySettings: QuerySettings, + username?: string, + querySettings?: QuerySettings, ) => useQuery({ queryKey: authzQueryKeys.userRoles(username, querySettings), queryFn: () => getUserAssignedRoles(username, querySettings), staleTime: 1000 * 60 * 30, // refetch after 30 minutes + enabled: !!username, + refetchOnWindowFocus: false, }); diff --git a/src/authz-module/team-members/TeamMembersTable.test.tsx b/src/authz-module/team-members/TeamMembersTable.test.tsx index 7619b88c..b6f91819 100644 --- a/src/authz-module/team-members/TeamMembersTable.test.tsx +++ b/src/authz-module/team-members/TeamMembersTable.test.tsx @@ -3,7 +3,7 @@ import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { renderWithAllProviders } from '@src/setupTest'; import { useAllRoleAssignments, useOrgs, useScopes } from '@src/authz-module/data/hooks'; -import { ToastManagerProvider } from '@src/authz-module/libraries-manager/ToastManagerContext'; +import { ToastManagerProvider } from '@src/authz-module/data/context/ToastManagerContext'; import TeamMembersTable from './TeamMembersTable'; const mockedAllRoleAssignments = { diff --git a/src/authz-module/team-members/TeamMembersTable.tsx b/src/authz-module/team-members/TeamMembersTable.tsx index efa930fe..d5f25b57 100644 --- a/src/authz-module/team-members/TeamMembersTable.tsx +++ b/src/authz-module/team-members/TeamMembersTable.tsx @@ -6,7 +6,7 @@ import { TextFilter, } from '@openedx/paragon'; -import { useToastManager } from '@src/authz-module/libraries-manager/ToastManagerContext'; +import { useToastManager } from '@src/authz-module/data/context/ToastManagerContext'; import { useQuerySettings } from '@src/authz-module/hooks/useQuerySettings'; import OrgFilter from '@src/authz-module/components/TableControlBar/OrgFilter'; import RolesFilter from '@src/authz-module/components/TableControlBar/RolesFilter'; From 8f18e4e11129de5aafa0db0392e6fccb7b20145c Mon Sep 17 00:00:00 2001 From: jacobo-dominguez-wgu Date: Sun, 19 Apr 2026 09:35:04 -0700 Subject: [PATCH 10/11] test: addint ut to improve coverage --- src/authz-module/audit-user/index.test.tsx | 74 ++++++++++++++++++++++ src/authz-module/audit-user/index.tsx | 2 +- 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src/authz-module/audit-user/index.test.tsx b/src/authz-module/audit-user/index.test.tsx index 18f06a0e..3c0dfdd7 100644 --- a/src/authz-module/audit-user/index.test.tsx +++ b/src/authz-module/audit-user/index.test.tsx @@ -13,6 +13,10 @@ jest.mock('@edx/frontend-platform/auth', () => ({ configure: jest.fn(), })); +jest.mock('@edx/frontend-platform/logging', () => ({ + logError: jest.fn(), +})); + const mockUser = { username: 'johndoe', email: 'john@example.com', @@ -273,6 +277,76 @@ describe('AuditUserPage', () => { }); }); + it('shows error toast when role revocation succeeds but returns errors', async () => { + (getAuthenticatedHttpClient as jest.Mock).mockReturnValue({ + get: jest + .fn() + .mockResolvedValueOnce({ data: mockUser }) + .mockResolvedValueOnce({ data: mockAssignments }), + delete: jest.fn().mockResolvedValue({ + data: { + errors: ['Failed to revoke user role'], + completed: [], + }, + }), + }); + + renderWithRouter(); + + await waitFor(() => { + expect(screen.getByRole('button', { name: /delete role action/i })).toBeInTheDocument(); + }); + + const user = userEvent.setup(); + const deleteButton = screen.getByRole('button', { name: /delete role action/i }); + await user.click(deleteButton); + + await waitFor(() => { + expect(screen.getByRole('dialog')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /remove/i })).toBeInTheDocument(); + }); + + const removeButton = screen.getByRole('button', { name: /remove/i }); + await user.click(removeButton); + + await waitFor(() => { + expect(screen.getByText(/something went wrong/i)).toBeInTheDocument(); + }); + }); + + it('shows error toast with retry when role revocation fails', async () => { + (getAuthenticatedHttpClient as jest.Mock).mockReturnValue({ + get: jest + .fn() + .mockResolvedValueOnce({ data: mockUser }) + .mockResolvedValueOnce({ data: mockAssignments }), + delete: jest.fn().mockRejectedValue(new Error('Network error')), + }); + + renderWithRouter(); + + await waitFor(() => { + expect(screen.getByRole('button', { name: /delete role action/i })).toBeInTheDocument(); + }); + + const user = userEvent.setup(); + const deleteButton = screen.getByRole('button', { name: /delete role action/i }); + await user.click(deleteButton); + + await waitFor(() => { + expect(screen.getByRole('dialog')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /remove/i })).toBeInTheDocument(); + }); + + const removeButton = screen.getByRole('button', { name: /remove/i }); + await user.click(removeButton); + + await waitFor(() => { + expect(screen.getByText(/something went wrong on our end/i)).toBeInTheDocument(); + expect(screen.getByText(/try again later/i)).toBeInTheDocument(); + }); + }); + it('shows the extra warning when rolesCount is 1', async () => { (getAuthenticatedHttpClient as jest.Mock).mockReturnValue({ get: jest diff --git a/src/authz-module/audit-user/index.tsx b/src/authz-module/audit-user/index.tsx index d64964a7..cd316ba1 100644 --- a/src/authz-module/audit-user/index.tsx +++ b/src/authz-module/audit-user/index.tsx @@ -195,7 +195,7 @@ const AuditUserPage = () => { title: '', }} navLinks={navLinks} - activeLabel={username || ''} + activeLabel={user?.username || ''} pageTitle={user?.username || ''} pageSubtitle={user?.email || ''} actions={ From 8d1126058aeef3d5c26f759a90d6f10b141537a8 Mon Sep 17 00:00:00 2001 From: jacobo-dominguez-wgu Date: Mon, 20 Apr 2026 12:44:19 -0600 Subject: [PATCH 11/11] fix: addressing pr comments --- src/authz-module/audit-user/index.test.tsx | 2 +- src/authz-module/audit-user/index.tsx | 16 ++++++------ src/authz-module/authz-home/index.test.tsx | 2 +- .../components/ConfirmDeletionModal.tsx | 3 ++- .../components/TableCells.test.tsx | 2 +- src/authz-module/components/TableCells.tsx | 14 +++++++---- src/authz-module/components/messages.ts | 2 +- src/authz-module/data/hooks.ts | 3 +++ src/authz-module/index.tsx | 2 +- .../LibrariesTeamManager.test.tsx | 2 +- .../LibrariesUserManager.test.tsx | 2 +- .../LibrariesUserManager.tsx | 2 +- .../AddNewTeamMemberTrigger.test.tsx | 2 +- .../AddNewTeamMemberTrigger.tsx | 2 +- .../AssignNewRoleTrigger.test.tsx | 2 +- .../AssignNewRoleTrigger.tsx | 2 +- .../components/TeamTable/index.test.tsx | 2 +- .../components/TeamTable/index.tsx | 2 +- .../libraries-manager/messages.ts | 25 ------------------- .../roles-permissions/libraries/messages.ts | 25 ------------------- .../roles-permissions/libraries/utils.ts | 2 +- .../team-members/TeamMembersTable.test.tsx | 2 +- .../team-members/TeamMembersTable.tsx | 2 +- .../ToastManagerContext.test.tsx | 0 .../ToastManager}/ToastManagerContext.tsx | 2 +- src/types.ts | 6 +++++ 26 files changed, 46 insertions(+), 82 deletions(-) rename src/{authz-module/data/context => components/ToastManager}/ToastManagerContext.test.tsx (100%) rename src/{authz-module/data/context => components/ToastManager}/ToastManagerContext.tsx (98%) diff --git a/src/authz-module/audit-user/index.test.tsx b/src/authz-module/audit-user/index.test.tsx index 3c0dfdd7..5a26a6b8 100644 --- a/src/authz-module/audit-user/index.test.tsx +++ b/src/authz-module/audit-user/index.test.tsx @@ -5,7 +5,7 @@ import { MemoryRouter, Route, Routes } from 'react-router-dom'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { IntlProvider } from '@edx/frontend-platform/i18n'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { ToastManagerProvider } from '@src/authz-module/data/context/ToastManagerContext'; +import { ToastManagerProvider } from '@src/components/ToastManager/ToastManagerContext'; import AuditUserPage from './index'; jest.mock('@edx/frontend-platform/auth', () => ({ diff --git a/src/authz-module/audit-user/index.tsx b/src/authz-module/audit-user/index.tsx index cd316ba1..465eae7d 100644 --- a/src/authz-module/audit-user/index.tsx +++ b/src/authz-module/audit-user/index.tsx @@ -22,22 +22,22 @@ import { } from '@src/authz-module/components/TableCells'; import { useQuerySettings } from '@src/authz-module/hooks/useQuerySettings'; import { useRevokeUserRoles, useUserAssignedRoles } from '@src/authz-module/data/hooks'; -import { Role } from 'types'; -import { useToastManager } from '@src/authz-module/data/context/ToastManagerContext'; +import { RoleToDelete } from 'types'; +import { useToastManager } from '@src/components/ToastManager/ToastManagerContext'; import messages from './messages'; import ConfirmDeletionModal from '../components/ConfirmDeletionModal'; const AuditUserPage = () => { const { formatMessage } = useIntl(); const { username } = useParams(); - const { authenticatedUser } = useContext(AppContext) as AppContextType; + const { authenticatedUser } = useContext(AppContext as React.Context); const navigate = useNavigate(); const { isLoading: isLoadingUser, data: user, isError: isErrorUser, error: errorUser, } = useUserAccount(username); const { querySettings, handleTableFetch } = useQuerySettings(); const { isLoading: isLoadingUserAssignments, data: { results: userAssignments, count } = { results: [], count: 0 } } = useUserAssignedRoles(username ?? '', querySettings); - const [roleToDelete, setRoleToDelete] = useState(null); + const [roleToDelete, setRoleToDelete] = useState(null); const [showConfirmDeletionModal, setShowConfirmDeletionModal] = useState(false); const { showToast, showErrorToast, Bold, Br, @@ -57,7 +57,7 @@ const AuditUserPage = () => { useEffect(() => () => fetchData.cancel(), [fetchData]); - const handleShowConfirmDeletionModal = useCallback((role: Role) => { + const handleShowConfirmDeletionModal = useCallback((role: RoleToDelete) => { if (isRevokingUserRolePending) { return; } setRoleToDelete(role); @@ -148,7 +148,6 @@ const AuditUserPage = () => { { Bold, Br }, ), }); - // authzQueryKeys.userRoles(username, querySettings), return; } @@ -157,7 +156,7 @@ const AuditUserPage = () => { message: formatMessage( baseMessages['authz.team.remove.user.toast.success.description'], { - role: roleToDelete.name, + role: roleToDelete.name ?? roleToDelete.role, rolesCount: remainingRolesCount, }, ), @@ -184,7 +183,8 @@ const AuditUserPage = () => { context={{ userName: user?.username || '', scope: roleToDelete?.scope || '', - role: roleToDelete?.name || '', + role: roleToDelete?.role || '', + name: roleToDelete?.name || '', rolesCount: count || 0, }} /> diff --git a/src/authz-module/authz-home/index.test.tsx b/src/authz-module/authz-home/index.test.tsx index 5544143d..d1526a23 100644 --- a/src/authz-module/authz-home/index.test.tsx +++ b/src/authz-module/authz-home/index.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { screen } from '@testing-library/react'; import { useAllRoleAssignments, useOrgs, useScopes } from '@src/authz-module/data/hooks'; -import { ToastManagerProvider } from '@src/authz-module/data/context/ToastManagerContext'; +import { ToastManagerProvider } from '@src/components/ToastManager/ToastManagerContext'; import { renderWithAllProviders } from '@src/setupTest'; import userEvent from '@testing-library/user-event'; import AuthzHome from './index'; diff --git a/src/authz-module/components/ConfirmDeletionModal.tsx b/src/authz-module/components/ConfirmDeletionModal.tsx index 152082ca..568ca8c5 100644 --- a/src/authz-module/components/ConfirmDeletionModal.tsx +++ b/src/authz-module/components/ConfirmDeletionModal.tsx @@ -16,6 +16,7 @@ interface ConfirmDeletionModalProps { userName: string; scope: string; role: string; + name?: string; rolesCount: number; } } @@ -57,7 +58,7 @@ const ConfirmDeletionModal = ({

{intl.formatMessage(messages['authz.team.remove.user.modal.body.1'], { userName: context.userName, scope: context.scope, - role: context.role, + role: context.name ?? context.role, })}

{context.rolesCount === 1 && ( diff --git a/src/authz-module/components/TableCells.test.tsx b/src/authz-module/components/TableCells.test.tsx index 02ace29f..f43c3d94 100644 --- a/src/authz-module/components/TableCells.test.tsx +++ b/src/authz-module/components/TableCells.test.tsx @@ -508,7 +508,7 @@ describe('TableCells Components', () => { expect(deleteButton).toBeInTheDocument(); await user.click(deleteButton); - expect(mockOnClickDeleteButton).toHaveBeenCalledWith({ role: 'library_admin', scope: 'Test Scope' }); + expect(mockOnClickDeleteButton).toHaveBeenCalledWith({ name: 'Library Admin', role: 'library_admin', scope: 'Test Scope' }); }); it('renders a disabled button for admin roles when isUserAuthenticatedPage is true', () => { diff --git a/src/authz-module/components/TableCells.tsx b/src/authz-module/components/TableCells.tsx index 4d5fb927..3652324e 100644 --- a/src/authz-module/components/TableCells.tsx +++ b/src/authz-module/components/TableCells.tsx @@ -6,7 +6,7 @@ import { Info, } from '@openedx/paragon/icons'; import { - TableCellValue, AppContextType, UserRole, Role, + TableCellValue, AppContextType, UserRole, RoleToDelete, } from '@src/types'; import { useNavigate } from 'react-router-dom'; import { useContext, useMemo } from 'react'; @@ -27,11 +27,14 @@ type ExtendedCellProps = CellPropsWithValue & { getCellProps: (props?: Record) => Record; }; }; -type ActionsCellProps = CellProps & { - onClickDeleteButton: (role: Role) => void; + +type ActionsCellExtraProps = { + onClickDeleteButton: (role: RoleToDelete) => void; isUserAuthenticatedPage: boolean; }; +type ActionsCellProps = CellProps & ActionsCellExtraProps; + const NameCell = ({ row }: CellProps) => { const intl = useIntl(); const { authenticatedUser } = useContext(AppContext) as AppContextType; @@ -139,7 +142,8 @@ const ActionsCell = ({ row, onClickDeleteButton, isUserAuthenticatedPage }: Acti const roleToDelete = { role, scope: row.original.scope, - } as Role; + name: MAP_ROLE_KEY_TO_LABEL[role] || '', + } as RoleToDelete; onClickDeleteButton(roleToDelete); }; @@ -179,7 +183,7 @@ const ActionsCell = ({ row, onClickDeleteButton, isUserAuthenticatedPage }: Acti ); }; -const createActionsCell = (extraProps) => function customActionsCell(cellProps) { +const createActionsCell = (extraProps: ActionsCellExtraProps) => function customActionsCell(cellProps) { return ; }; diff --git a/src/authz-module/components/messages.ts b/src/authz-module/components/messages.ts index d2cbe5eb..3cb4a879 100644 --- a/src/authz-module/components/messages.ts +++ b/src/authz-module/components/messages.ts @@ -134,7 +134,7 @@ const messages = defineMessages({ }, 'authz.manage.removing.button': { id: 'authz.manage.removing.button', - defaultMessage: 'Removing...', + defaultMessage: 'Removing', description: 'AuthZ removing button title', }, 'authz.team.remove.user.modal.body.1': { diff --git a/src/authz-module/data/hooks.ts b/src/authz-module/data/hooks.ts index f811bc53..5b3ed7b4 100644 --- a/src/authz-module/data/hooks.ts +++ b/src/authz-module/data/hooks.ts @@ -3,6 +3,7 @@ import { } from '@tanstack/react-query'; import { appId } from '@src/constants'; import { LibraryMetadata } from '@src/types'; +import { useQuerySettings } from '@src/authz-module/hooks/useQuerySettings'; import { assignTeamMembersRole, AssignTeamMembersRoleRequest, getAllRoleAssignments, GetAllRoleAssignmentsResponse, getLibrary, getOrgs, GetOrgsResponse, @@ -107,6 +108,7 @@ export const useAssignTeamMembersRole = () => { */ export const useRevokeUserRoles = () => { const queryClient = useQueryClient(); + const { querySettings: defaultQuerySettings } = useQuerySettings(); return useMutation({ mutationFn: async ({ data }: { data: RevokeUserRolesRequest @@ -117,6 +119,7 @@ export const useRevokeUserRoles = () => { queryClient.invalidateQueries({ queryKey: authzQueryKeys.userRoles(users, querySettings), }); + queryClient.invalidateQueries({ queryKey: authzQueryKeys.allRoleAssignments(defaultQuerySettings) }); }, }); }; diff --git a/src/authz-module/index.tsx b/src/authz-module/index.tsx index d60e53f1..7fa21a68 100644 --- a/src/authz-module/index.tsx +++ b/src/authz-module/index.tsx @@ -5,7 +5,7 @@ import { QueryErrorResetBoundary } from '@tanstack/react-query'; import LoadingPage from '@src/components/LoadingPage'; import LibrariesErrorFallback from '@src/authz-module/libraries-manager/ErrorPage'; import { CustomErrors } from '@src/constants'; -import { ToastManagerProvider } from './data/context/ToastManagerContext'; +import { ToastManagerProvider } from '@src/components/ToastManager/ToastManagerContext'; import { LibrariesUserManager, LibrariesLayout, LibrariesTeamManager } from './libraries-manager'; import AuthzHome from './authz-home'; import AuditUserPage from './audit-user'; diff --git a/src/authz-module/libraries-manager/LibrariesTeamManager.test.tsx b/src/authz-module/libraries-manager/LibrariesTeamManager.test.tsx index 205ed9bd..f0f42a9a 100644 --- a/src/authz-module/libraries-manager/LibrariesTeamManager.test.tsx +++ b/src/authz-module/libraries-manager/LibrariesTeamManager.test.tsx @@ -3,9 +3,9 @@ import userEvent from '@testing-library/user-event'; import { renderWrapper } from '@src/setupTest'; import { initializeMockApp } from '@edx/frontend-platform/testing'; import { useLibrary } from '@src/authz-module/data/hooks'; +import { ToastManagerProvider } from '@src/components/ToastManager/ToastManagerContext'; import { useLibraryAuthZ } from './context'; import LibrariesTeamManager from './LibrariesTeamManager'; -import { ToastManagerProvider } from '../data/context/ToastManagerContext'; import { CONTENT_LIBRARY_PERMISSIONS } from '../constants'; jest.mock('./context', () => { diff --git a/src/authz-module/libraries-manager/LibrariesUserManager.test.tsx b/src/authz-module/libraries-manager/LibrariesUserManager.test.tsx index 2a125883..46553efb 100644 --- a/src/authz-module/libraries-manager/LibrariesUserManager.test.tsx +++ b/src/authz-module/libraries-manager/LibrariesUserManager.test.tsx @@ -2,10 +2,10 @@ import { useParams } from 'react-router-dom'; import { screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { renderWrapper } from '@src/setupTest'; +import { ToastManagerProvider } from '@src/components/ToastManager/ToastManagerContext'; import LibrariesUserManager from './LibrariesUserManager'; import { useLibraryAuthZ } from './context'; import { useLibrary, useTeamMembers, useRevokeUserRoles } from '../data/hooks'; -import { ToastManagerProvider } from '../data/context/ToastManagerContext'; jest.mock('@edx/frontend-platform/logging', () => ({ logError: jest.fn(), diff --git a/src/authz-module/libraries-manager/LibrariesUserManager.tsx b/src/authz-module/libraries-manager/LibrariesUserManager.tsx index 3d4c1cd0..10fab16e 100644 --- a/src/authz-module/libraries-manager/LibrariesUserManager.tsx +++ b/src/authz-module/libraries-manager/LibrariesUserManager.tsx @@ -4,7 +4,7 @@ import { useIntl } from '@edx/frontend-platform/i18n'; import { Container, Skeleton } from '@openedx/paragon'; import { ROUTES } from '@src/authz-module/constants'; import { Role } from 'types'; -import { useToastManager } from '@src/authz-module/data/context/ToastManagerContext'; +import { useToastManager } from '@src/components/ToastManager/ToastManagerContext'; import AuthZLayout from '../components/AuthZLayout'; import { useLibraryAuthZ } from './context'; import RoleCard from '../components/RoleCard'; diff --git a/src/authz-module/libraries-manager/components/AddNewTeamMemberModal/AddNewTeamMemberTrigger.test.tsx b/src/authz-module/libraries-manager/components/AddNewTeamMemberModal/AddNewTeamMemberTrigger.test.tsx index 70456581..c7bd1cee 100644 --- a/src/authz-module/libraries-manager/components/AddNewTeamMemberModal/AddNewTeamMemberTrigger.test.tsx +++ b/src/authz-module/libraries-manager/components/AddNewTeamMemberModal/AddNewTeamMemberTrigger.test.tsx @@ -3,7 +3,7 @@ import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { renderWrapper } from '@src/setupTest'; import { useAssignTeamMembersRole } from '@src/authz-module/data/hooks'; -import { ToastManagerProvider } from '@src/authz-module/data/context/ToastManagerContext'; +import { ToastManagerProvider } from '@src/components/ToastManager/ToastManagerContext'; import AddNewTeamMemberTrigger from './AddNewTeamMemberTrigger'; jest.mock('@edx/frontend-platform/logging'); diff --git a/src/authz-module/libraries-manager/components/AddNewTeamMemberModal/AddNewTeamMemberTrigger.tsx b/src/authz-module/libraries-manager/components/AddNewTeamMemberModal/AddNewTeamMemberTrigger.tsx index 99d23bbf..66c5c408 100644 --- a/src/authz-module/libraries-manager/components/AddNewTeamMemberModal/AddNewTeamMemberTrigger.tsx +++ b/src/authz-module/libraries-manager/components/AddNewTeamMemberModal/AddNewTeamMemberTrigger.tsx @@ -6,7 +6,7 @@ import { Plus } from '@openedx/paragon/icons'; import { PutAssignTeamMembersRoleResponse } from '@src/authz-module/data/api'; import { useAssignTeamMembersRole } from '@src/authz-module/data/hooks'; import { RoleOperationErrorStatus, DEFAULT_TOAST_DELAY } from '@src/authz-module/constants'; -import { AppToast, useToastManager } from '@src/authz-module/data/context/ToastManagerContext'; +import { AppToast, useToastManager } from '@src/components/ToastManager/ToastManagerContext'; import baseMessages from '@src/authz-module/messages'; import AddNewTeamMemberModal from './AddNewTeamMemberModal'; import messages from './messages'; diff --git a/src/authz-module/libraries-manager/components/AssignNewRoleModal/AssignNewRoleTrigger.test.tsx b/src/authz-module/libraries-manager/components/AssignNewRoleModal/AssignNewRoleTrigger.test.tsx index c63f4832..069d762c 100644 --- a/src/authz-module/libraries-manager/components/AssignNewRoleModal/AssignNewRoleTrigger.test.tsx +++ b/src/authz-module/libraries-manager/components/AssignNewRoleModal/AssignNewRoleTrigger.test.tsx @@ -3,7 +3,7 @@ import userEvent from '@testing-library/user-event'; import { renderWrapper } from '@src/setupTest'; import { useLibraryAuthZ } from '@src/authz-module/libraries-manager/context'; import { useAssignTeamMembersRole } from '@src/authz-module/data/hooks'; -import { ToastManagerProvider } from '@src/authz-module/data/context/ToastManagerContext'; +import { ToastManagerProvider } from '@src/components/ToastManager/ToastManagerContext'; import AssignNewRoleTrigger from './AssignNewRoleTrigger'; jest.mock('@edx/frontend-platform/logging'); diff --git a/src/authz-module/libraries-manager/components/AssignNewRoleModal/AssignNewRoleTrigger.tsx b/src/authz-module/libraries-manager/components/AssignNewRoleModal/AssignNewRoleTrigger.tsx index 88023730..71aecb71 100644 --- a/src/authz-module/libraries-manager/components/AssignNewRoleModal/AssignNewRoleTrigger.tsx +++ b/src/authz-module/libraries-manager/components/AssignNewRoleModal/AssignNewRoleTrigger.tsx @@ -5,7 +5,7 @@ import { Plus } from '@openedx/paragon/icons'; import { useLibraryAuthZ } from '@src/authz-module/libraries-manager/context'; import { useAssignTeamMembersRole } from '@src/authz-module/data/hooks'; -import { useToastManager } from '@src/authz-module/data/context/ToastManagerContext'; +import { useToastManager } from '@src/components/ToastManager/ToastManagerContext'; import AssignNewRoleModal from './AssignNewRoleModal'; import messages from '../messages'; diff --git a/src/authz-module/libraries-manager/components/TeamTable/index.test.tsx b/src/authz-module/libraries-manager/components/TeamTable/index.test.tsx index 3d809e58..247ede1c 100644 --- a/src/authz-module/libraries-manager/components/TeamTable/index.test.tsx +++ b/src/authz-module/libraries-manager/components/TeamTable/index.test.tsx @@ -3,7 +3,7 @@ import userEvent from '@testing-library/user-event'; import { renderWrapper } from '@src/setupTest'; import { useTeamMembers } from '@src/authz-module/data/hooks'; import { useLibraryAuthZ } from '@src/authz-module/libraries-manager/context'; -import { ToastManagerProvider } from '@src/authz-module/data/context/ToastManagerContext'; +import { ToastManagerProvider } from '@src/components/ToastManager/ToastManagerContext'; import { CONTENT_LIBRARY_PERMISSIONS } from '@src/authz-module/constants'; import TeamTable from './index'; diff --git a/src/authz-module/libraries-manager/components/TeamTable/index.tsx b/src/authz-module/libraries-manager/components/TeamTable/index.tsx index ec0a0246..99f38bc3 100644 --- a/src/authz-module/libraries-manager/components/TeamTable/index.tsx +++ b/src/authz-module/libraries-manager/components/TeamTable/index.tsx @@ -11,7 +11,7 @@ import { import { useTeamMembers } from '@src/authz-module/data/hooks'; import { useLibraryAuthZ } from '@src/authz-module/libraries-manager/context'; import { SKELETON_ROWS, TABLE_DEFAULT_PAGE_SIZE } from '@src/authz-module/constants'; -import { useToastManager } from '@src/authz-module/data/context/ToastManagerContext'; +import { useToastManager } from '@src/components/ToastManager/ToastManagerContext'; import { useQuerySettings } from '@src/authz-module/hooks/useQuerySettings'; import TableControlBar from './components/TableControlBar'; import messages from './messages'; diff --git a/src/authz-module/libraries-manager/messages.ts b/src/authz-module/libraries-manager/messages.ts index 27f65ceb..10aba4f6 100644 --- a/src/authz-module/libraries-manager/messages.ts +++ b/src/authz-module/libraries-manager/messages.ts @@ -81,31 +81,6 @@ const messages = defineMessages({ defaultMessage: 'Something went wrong on our end.

Please try again later.', description: 'Libraries default error message', }, - 'authz.team.toast.500.error.message': { - id: 'authz.team.toast.500.error.message', - defaultMessage: 'We\'re experiencing technical difficulties.

Please try again later.', - description: 'Internal server error message', - }, - 'authz.team.toast.502.error.message': { - id: 'authz.team.toast.502.error.message', - defaultMessage: 'We\'re having trouble connecting to our services.

Please try again later.', - description: 'Bad gateway error message', - }, - 'authz.team.toast.503.error.message': { - id: 'authz.team.toast.503.error.message', - defaultMessage: 'The service is temporarily unavailable.

Please try again in a few moments.', - description: 'Service temporarily unavailable message', - }, - 'authz.team.toast.408.error.message': { - id: 'authz.team.toast.408.error.message', - defaultMessage: 'The request took too long.

Please check your connection and try again.', - description: 'Request timeout message', - }, - 'authz.team.toast.retry.label': { - id: 'authz.team.toast.retry.label', - defaultMessage: 'Retry', - description: 'Label for retry button.', - }, }); export default messages; diff --git a/src/authz-module/roles-permissions/libraries/messages.ts b/src/authz-module/roles-permissions/libraries/messages.ts index edbfae66..13e102f4 100644 --- a/src/authz-module/roles-permissions/libraries/messages.ts +++ b/src/authz-module/roles-permissions/libraries/messages.ts @@ -61,31 +61,6 @@ const messages = defineMessages({ defaultMessage: 'Something went wrong on our end.

Please try again later.', description: 'Libraries default error message', }, - 'authz.team.toast.500.error.message': { - id: 'authz.team.toast.500.error.message', - defaultMessage: 'We\'re experiencing technical difficulties.

Please try again later.', - description: 'Internal server error message', - }, - 'authz.team.toast.502.error.message': { - id: 'authz.team.toast.502.error.message', - defaultMessage: 'We\'re having trouble connecting to our services.

Please try again later.', - description: 'Bad gateway error message', - }, - 'authz.team.toast.503.error.message': { - id: 'authz.team.toast.503.error.message', - defaultMessage: 'The service is temporarily unavailable.

Please try again in a few moments.', - description: 'Service temporarily unavailable message', - }, - 'authz.team.toast.408.error.message': { - id: 'authz.team.toast.408.error.message', - defaultMessage: 'The request took too long.

Please check your connection and try again.', - description: 'Request timeout message', - }, - 'authz.team.toast.retry.label': { - id: 'authz.team.toast.retry.label', - defaultMessage: 'Retry', - description: 'Label for retry button.', - }, }); export default messages; diff --git a/src/authz-module/roles-permissions/libraries/utils.ts b/src/authz-module/roles-permissions/libraries/utils.ts index 7dd5cf53..4cedb3f3 100644 --- a/src/authz-module/roles-permissions/libraries/utils.ts +++ b/src/authz-module/roles-permissions/libraries/utils.ts @@ -4,7 +4,7 @@ import { EnrichedPermission, PermissionMetadata, PermissionsResourceGrouped, PermissionsRoleGrouped, ResourceMetadata, Role, RoleResourceGroup, } from '@src/types'; -import actionMessages from '../../components/RoleCard/messages'; +import actionMessages from '@src/authz-module/components/RoleCard/messages'; /** * Derives the localized label and action key for a given permission. diff --git a/src/authz-module/team-members/TeamMembersTable.test.tsx b/src/authz-module/team-members/TeamMembersTable.test.tsx index b6f91819..4252a6db 100644 --- a/src/authz-module/team-members/TeamMembersTable.test.tsx +++ b/src/authz-module/team-members/TeamMembersTable.test.tsx @@ -3,7 +3,7 @@ import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { renderWithAllProviders } from '@src/setupTest'; import { useAllRoleAssignments, useOrgs, useScopes } from '@src/authz-module/data/hooks'; -import { ToastManagerProvider } from '@src/authz-module/data/context/ToastManagerContext'; +import { ToastManagerProvider } from '@src/components/ToastManager/ToastManagerContext'; import TeamMembersTable from './TeamMembersTable'; const mockedAllRoleAssignments = { diff --git a/src/authz-module/team-members/TeamMembersTable.tsx b/src/authz-module/team-members/TeamMembersTable.tsx index d5f25b57..4749dcd6 100644 --- a/src/authz-module/team-members/TeamMembersTable.tsx +++ b/src/authz-module/team-members/TeamMembersTable.tsx @@ -6,7 +6,7 @@ import { TextFilter, } from '@openedx/paragon'; -import { useToastManager } from '@src/authz-module/data/context/ToastManagerContext'; +import { useToastManager } from '@src/components/ToastManager/ToastManagerContext'; import { useQuerySettings } from '@src/authz-module/hooks/useQuerySettings'; import OrgFilter from '@src/authz-module/components/TableControlBar/OrgFilter'; import RolesFilter from '@src/authz-module/components/TableControlBar/RolesFilter'; diff --git a/src/authz-module/data/context/ToastManagerContext.test.tsx b/src/components/ToastManager/ToastManagerContext.test.tsx similarity index 100% rename from src/authz-module/data/context/ToastManagerContext.test.tsx rename to src/components/ToastManager/ToastManagerContext.test.tsx diff --git a/src/authz-module/data/context/ToastManagerContext.tsx b/src/components/ToastManager/ToastManagerContext.tsx similarity index 98% rename from src/authz-module/data/context/ToastManagerContext.tsx rename to src/components/ToastManager/ToastManagerContext.tsx index 04b76c7c..dcf454d2 100644 --- a/src/authz-module/data/context/ToastManagerContext.tsx +++ b/src/components/ToastManager/ToastManagerContext.tsx @@ -5,7 +5,7 @@ import { logError } from '@edx/frontend-platform/logging'; import { useIntl } from '@edx/frontend-platform/i18n'; import { Toast } from '@openedx/paragon'; import messages from '@src/authz-module/messages'; -import { DEFAULT_TOAST_DELAY, RETRY_TOAST_DELAY } from '../../constants'; +import { DEFAULT_TOAST_DELAY, RETRY_TOAST_DELAY } from '@src/authz-module/constants'; type ToastType = 'success' | 'error' | 'error-retry'; diff --git a/src/types.ts b/src/types.ts index baf50b07..2b269964 100644 --- a/src/types.ts +++ b/src/types.ts @@ -119,3 +119,9 @@ export interface UserRole { username?: string; email?: string; } + +export type RoleToDelete = { + role: string; + name?: string; + scope: string; +};