feat: add roles tab

This commit is contained in:
Diana Olarte
2025-10-02 23:09:41 +10:00
committed by Adolfo R. Brandes
parent 315e5c6e32
commit 1541eca462
3 changed files with 81 additions and 18 deletions

View File

@@ -17,21 +17,25 @@ interface PermissionRowProps {
const PermissionRow = ({ resourceLabel, actions }: PermissionRowProps) => (
<Row className="row align-items-center border px-2 py-2">
<Col md={3}>
<Col md={2}>
<span className="small font-weight-bold">{resourceLabel}</span>
</Col>
<Col>
<div className="w-100 d-flex flex-wrap">
{actions.map(action => (
<Chip
key={action.key}
iconBefore={actionsDictionary[action.key as ActionKey] as ComponentType}
disabled={action.disabled}
className="mr-4 my-2 px-3 bg-primary-100 border-0 permission-chip"
variant="light"
>
{action.label}
</Chip>
<div className="w-100 d-flex flex-wrap align-items-center">
{actions.map((action, index) => (
<>
<Chip
key={action.key}
iconBefore={actionsDictionary[action.key as ActionKey] as ComponentType}
disabled={action.disabled}
className="mx-3 my-2 px-3 bg-primary-100 border-0 permission-chip"
variant="light"
>
{action.label}
</Chip>
{(index === actions.length - 1) ? null
: (<hr className="border-right mx-2" style={{ height: '24px' }} />)}
</>
))}
</div>
</Col>

View File

@@ -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: () => <div data-testid="add-team-member-trigger">MockAddNewTeamMemberTrigger</div>,
}));
jest.mock('../components/RoleCard', () => ({
__esModule: true,
default: ({ title, description, permissions }: { title: string, description: string, permissions: any[] }) => (
<div data-testid="role-card">
<div>{title}</div>
<div>{description}</div>
<div>{permissions.length} permissions</div>
</div>
),
}));
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(<LibrariesTeamManager />);
// 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();
});
});

View File

@@ -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 (
<div className="authz-libraries">
<AuthZLayout
@@ -39,7 +51,17 @@ const LibrariesTeamManager = () => {
<TeamTable />
</Tab>
<Tab eventKey="roles" title={intl.formatMessage(messages['library.authz.tabs.roles'])}>
Role tab.
<Container className="p-5">
{libraryRoles && libraryRoles.map(role => (
<RoleCard
key={`${role}-description`}
title={role.name}
userCounter={role.userCount}
description={role.description}
permissions={role.permissions as any[]}
/>
))}
</Container>
</Tab>
<Tab id="libraries-permissions-tab" eventKey="permissions" title={intl.formatMessage(messages['library.authz.tabs.permissions'])}>
Permissions tab.