From f4959e2e7141e9074eb868b48585e32584ab0003 Mon Sep 17 00:00:00 2001 From: Diana Olarte Date: Thu, 2 Oct 2025 23:09:41 +1000 Subject: [PATCH 1/4] feat: add roles tab --- .../components/RoleCard/PermissionsRow.tsx | 28 ++++++------ .../LibrariesTeamManager.test.tsx | 43 +++++++++++++++++-- .../LibrariesTeamManager.tsx | 28 ++++++++++-- 3 files changed, 81 insertions(+), 18 deletions(-) diff --git a/src/authz-module/components/RoleCard/PermissionsRow.tsx b/src/authz-module/components/RoleCard/PermissionsRow.tsx index 7c1a0073..aad0ebbb 100644 --- a/src/authz-module/components/RoleCard/PermissionsRow.tsx +++ b/src/authz-module/components/RoleCard/PermissionsRow.tsx @@ -17,21 +17,25 @@ interface PermissionRowProps { const PermissionRow = ({ resourceLabel, actions }: PermissionRowProps) => ( - + {resourceLabel} -
- {actions.map(action => ( - - {action.label} - +
+ {actions.map((action, index) => ( + <> + + {action.label} + + {(index === actions.length - 1) ? null + : (
)} + ))}
diff --git a/src/authz-module/libraries-manager/LibrariesTeamManager.test.tsx b/src/authz-module/libraries-manager/LibrariesTeamManager.test.tsx index bbbb7ff7..eb19df26 100644 --- a/src/authz-module/libraries-manager/LibrariesTeamManager.test.tsx +++ b/src/authz-module/libraries-manager/LibrariesTeamManager.test.tsx @@ -1,4 +1,4 @@ -import { screen } from '@testing-library/react'; +import { fireEvent, screen } from '@testing-library/react'; import { renderWrapper } from '@src/setupTest'; import { initializeMockApp } from '@edx/frontend-platform/testing'; import { useLibrary } from '@src/authz-module/data/hooks'; @@ -29,6 +29,17 @@ jest.mock('./components/AddNewTeamMemberModal', () => ({ AddNewTeamMemberTrigger: () =>
MockAddNewTeamMemberTrigger
, })); +jest.mock('../components/RoleCard', () => ({ + __esModule: true, + default: ({ title, description, permissions }: { title: string, description: string, permissions: any[] }) => ( +
+
{title}
+
{description}
+
{permissions.length} permissions
+
+ ), +})); + describe('LibrariesTeamManager', () => { beforeEach(() => { initializeMockApp({ @@ -41,8 +52,19 @@ describe('LibrariesTeamManager', () => { libraryName: 'Mock Library', libraryOrg: 'MockOrg', username: 'mockuser', - roles: ['admin'], - permissions: [], + roles: [ + { + name: 'Instructor', + description: 'Can manage content.', + userCount: 3, + permissions: ['view', 'edit'], + }, + ], + permissions: [ + { key: 'view_library', label: 'view', resource: 'library' }, + { key: 'edit_library', name: 'edit', resource: 'library' }, + ], + resources: [{ key: 'library', displayName: 'Library' }], canManageTeam: true, }); @@ -72,4 +94,19 @@ describe('LibrariesTeamManager', () => { // AddNewTeamMemberTrigger is rendered expect(screen.getByTestId('add-team-member-trigger')).toBeInTheDocument(); }); + + it('renders role cards when "Roles" tab is selected', async () => { + renderWrapper(); + + // Click on "Roles" tab + const rolesTab = await screen.findByRole('tab', { name: /roles/i }); + fireEvent.click(rolesTab); + + const roleCards = await screen.findAllByTestId('role-card'); + + expect(roleCards.length).toBeGreaterThan(0); + expect(screen.getByText('Instructor')).toBeInTheDocument(); + expect(screen.getByText(/Can manage content/i)).toBeInTheDocument(); + expect(screen.getByText(/1 permissions/i)).toBeInTheDocument(); + }); }); diff --git a/src/authz-module/libraries-manager/LibrariesTeamManager.tsx b/src/authz-module/libraries-manager/LibrariesTeamManager.tsx index 9595ce88..a3f30314 100644 --- a/src/authz-module/libraries-manager/LibrariesTeamManager.tsx +++ b/src/authz-module/libraries-manager/LibrariesTeamManager.tsx @@ -1,21 +1,33 @@ +import { useMemo } from 'react'; import { useIntl } from '@edx/frontend-platform/i18n'; -import { Tab, Tabs } from '@openedx/paragon'; +import { Container, Tab, Tabs } from '@openedx/paragon'; import { useLibrary } from '@src/authz-module/data/hooks'; import { useLocation } from 'react-router-dom'; import TeamTable from './components/TeamTable'; import AuthZLayout from '../components/AuthZLayout'; +import RoleCard from '../components/RoleCard'; import { useLibraryAuthZ } from './context'; import { AddNewTeamMemberTrigger } from './components/AddNewTeamMemberModal'; +import { buildPermissionsByRoleMatrix } from './utils'; import messages from './messages'; const LibrariesTeamManager = () => { const intl = useIntl(); const { hash } = useLocation(); - const { libraryId, canManageTeam } = useLibraryAuthZ(); + const { + libraryId, canManageTeam, roles, permissions, resources, + } = useLibraryAuthZ(); const { data: library } = useLibrary(libraryId); const rootBradecrumb = intl.formatMessage(messages['library.authz.breadcrumb.root']) || ''; const pageTitle = intl.formatMessage(messages['library.authz.manage.page.title']); + const libraryRoles = useMemo(() => roles.map(role => ({ + ...role, + permissions: buildPermissionsByRoleMatrix({ + rolePermissions: role.permissions, permissions, resources, intl, + }), + })), [roles, permissions, resources, intl]); + return (
{ - Role tab. + + {libraryRoles && libraryRoles.map(role => ( + + ))} + Permissions tab. From 253f78dc80a8f337f023bcf9a59e45bc24c95aef Mon Sep 17 00:00:00 2001 From: Diana Olarte Date: Mon, 13 Oct 2025 22:19:13 +1100 Subject: [PATCH 2/4] refactor: add loading state to the roles cards --- src/authz-module/libraries-manager/LibrariesTeamManager.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/authz-module/libraries-manager/LibrariesTeamManager.tsx b/src/authz-module/libraries-manager/LibrariesTeamManager.tsx index a3f30314..fda4ddec 100644 --- a/src/authz-module/libraries-manager/LibrariesTeamManager.tsx +++ b/src/authz-module/libraries-manager/LibrariesTeamManager.tsx @@ -1,6 +1,8 @@ import { useMemo } from 'react'; import { useIntl } from '@edx/frontend-platform/i18n'; -import { Container, Tab, Tabs } from '@openedx/paragon'; +import { + Container, Skeleton, Tab, Tabs, +} from '@openedx/paragon'; import { useLibrary } from '@src/authz-module/data/hooks'; import { useLocation } from 'react-router-dom'; import TeamTable from './components/TeamTable'; @@ -52,6 +54,7 @@ const LibrariesTeamManager = () => { + {!libraryRoles ? : null} {libraryRoles && libraryRoles.map(role => ( Date: Wed, 22 Oct 2025 19:29:13 +1100 Subject: [PATCH 3/4] fix: add null as defaout for userCounter in CardTitle --- src/authz-module/components/RoleCard/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/authz-module/components/RoleCard/index.tsx b/src/authz-module/components/RoleCard/index.tsx index 9b2f9f3e..1c154668 100644 --- a/src/authz-module/components/RoleCard/index.tsx +++ b/src/authz-module/components/RoleCard/index.tsx @@ -18,7 +18,7 @@ interface RoleCardProps extends CardTitleProps { permissions: any[]; } -const CardTitle = ({ title, userCounter }: CardTitleProps) => ( +const CardTitle = ({ title, userCounter = null }: CardTitleProps) => (
{title} {userCounter !== null && ( From 1c583b9431b0cbfc5c132d92581d046f81f88f7b Mon Sep 17 00:00:00 2001 From: Diana Olarte Date: Wed, 22 Oct 2025 19:45:20 +1100 Subject: [PATCH 4/4] fix: style issues and test --- src/authz-module/index.scss | 2 +- .../libraries-manager/LibrariesTeamManager.test.tsx | 7 +++++-- .../libraries-manager/LibrariesTeamManager.tsx | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/authz-module/index.scss b/src/authz-module/index.scss index ab98fc34..790adc83 100644 --- a/src/authz-module/index.scss +++ b/src/authz-module/index.scss @@ -29,7 +29,7 @@ font-weight: var(--pgn-typography-font-weight-base); } - svg { + .pgn__icon { width: var(--pgn-size-icon-xs); height: var(--pgn-size-icon-xs); } diff --git a/src/authz-module/libraries-manager/LibrariesTeamManager.test.tsx b/src/authz-module/libraries-manager/LibrariesTeamManager.test.tsx index eb19df26..be558e39 100644 --- a/src/authz-module/libraries-manager/LibrariesTeamManager.test.tsx +++ b/src/authz-module/libraries-manager/LibrariesTeamManager.test.tsx @@ -1,4 +1,5 @@ -import { fireEvent, screen } from '@testing-library/react'; +import { screen } from '@testing-library/react'; +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'; @@ -96,11 +97,13 @@ describe('LibrariesTeamManager', () => { }); it('renders role cards when "Roles" tab is selected', async () => { + const user = userEvent.setup(); + renderWrapper(); // Click on "Roles" tab const rolesTab = await screen.findByRole('tab', { name: /roles/i }); - fireEvent.click(rolesTab); + await user.click(rolesTab); const roleCards = await screen.findAllByTestId('role-card'); diff --git a/src/authz-module/libraries-manager/LibrariesTeamManager.tsx b/src/authz-module/libraries-manager/LibrariesTeamManager.tsx index fda4ddec..dd9fc2c5 100644 --- a/src/authz-module/libraries-manager/LibrariesTeamManager.tsx +++ b/src/authz-module/libraries-manager/LibrariesTeamManager.tsx @@ -57,7 +57,7 @@ const LibrariesTeamManager = () => { {!libraryRoles ? : null} {libraryRoles && libraryRoles.map(role => (