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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 29 additions & 4 deletions src/authz-module/components/UserPermissions.test.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { screen } from '@testing-library/react';
import { initializeMockApp } from '@edx/frontend-platform/testing';
import { renderWrapper } from '@src/setupTest';
import * as coursesConstants from '@src/authz-module/roles-permissions';
Expand Down Expand Up @@ -52,6 +53,30 @@ describe('UserPermissions', () => {
expect(container.querySelector('.d-flex')).toBeInTheDocument();
});

it('renders library role permissions with their metadata labels', () => {
const props = {
row: {
original: {
role: 'library_admin',
},
},
};

const { container } = renderWrapper(<UserPermissions {...props} />);

// Resource group headers from libraryResourceTypes
expect(screen.getByText('Library')).toBeInTheDocument();
expect(screen.getByText('Team')).toBeInTheDocument();
// Explicit labels from the permission metadata
expect(screen.getAllByText('Manage tags').length).toBeGreaterThan(0);
expect(screen.getAllByText('Publish').length).toBeGreaterThan(0);
expect(screen.getAllByText('Reuse').length).toBeGreaterThan(0);
// No permission renders with an empty label
const labels = Array.from(container.querySelectorAll('li span.font-weight-light'));
expect(labels.length).toBeGreaterThan(0);
labels.forEach((label) => expect(label.textContent?.trim()).not.toBe(''));
});

it('returns null when role is empty', () => {
const props = {
row: {
Expand Down Expand Up @@ -87,8 +112,8 @@ describe('UserPermissions', () => {
},
];

const originalRolesObject = coursesConstants.rolesObject;
const rolesObjectSpy = jest.spyOn(coursesConstants, 'rolesObject', 'get')
const originalRolesObject = coursesConstants.courseRolesWithPermissions;
const courseRolesWithPermissionsSpy = jest.spyOn(coursesConstants, 'courseRolesWithPermissions', 'get')
.mockReturnValue([...originalRolesObject, ...mockRoleObject] as typeof originalRolesObject);

const props = {
Expand All @@ -101,10 +126,10 @@ describe('UserPermissions', () => {

const { getByTestId } = renderWrapper(<UserPermissions {...props} />);
expect(getByTestId('render-permission-inline')).toBeInTheDocument();
rolesObjectSpy.mockRestore();
courseRolesWithPermissionsSpy.mockRestore();
});

it('returns null when role is not found in rolesObject (line 52 coverage)', () => {
it('returns null when role is not found in courseRolesWithPermissions (line 52 coverage)', () => {
const props = {
row: {
original: {
Expand Down
24 changes: 14 additions & 10 deletions src/authz-module/components/UserPermissions.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { useIntl } from '@edx/frontend-platform/i18n';
import { DJANGO_MANAGED_ROLES } from '@src/authz-module/constants';
import {
courseResourceTypes,
coursePermissions,
rolesObject,
courseRolesWithPermissions,
libraryResourceTypes,
libraryPermissions,
rolesLibraryObject,
libraryRolesWithPermissions,
getPermissionMetadata,
} from '@src/authz-module/roles-permissions';
import { PermissionItem } from '@src/types';
import RenderPermissionColumn from './RenderPermissionColumn';
import RenderPermissionInLine from './RenderPermissionInLine';
import RenderAdminRole from './RenderAdminRole';
Expand All @@ -21,6 +22,7 @@ interface UserPermissionsProps {
}

const UserPermissions = ({ row }: UserPermissionsProps) => {
const intl = useIntl();
let roleKey = row?.original?.role;
if (!roleKey) { return null; }

Expand All @@ -39,27 +41,29 @@ const UserPermissions = ({ row }: UserPermissionsProps) => {
? {
resourceTypes: libraryResourceTypes,
permissions: libraryPermissions,
roles: rolesLibraryObject,
roles: libraryRolesWithPermissions,
}
: {
resourceTypes: courseResourceTypes,
permissions: coursePermissions,
roles: rolesObject,
roles: courseRolesWithPermissions,
};

const roleObj = config.roles.find(r => r.role === roleKey);
if (!roleObj) { return null; }

const rolePerms = new Set(roleObj.permissions.map(String));
// Build resource list with permissions (only once)
// Build resource list with permissions (only once). Permissions without an
// explicit label (most library ones) get a localized label derived from
// their action key, the same enrichment the permissions matrix uses.
const resources = config.resourceTypes
.map(resource => {
const perms = config.permissions.filter(
p => p.resource === resource.key && rolePerms.has(String(p.key)),
);
const perms = config.permissions
.filter(p => p.resource === resource.key && rolePerms.has(String(p.key)))
.map(p => getPermissionMetadata(p, intl));
return perms.length ? { ...resource, perms } : null;
})
.filter((r): r is PermissionItem => r !== null);
.filter((r): r is NonNullable<typeof r> => r !== null);

const isSingleRow = resources.length <= 3;
const mid = Math.ceil(resources.length / 2);
Expand Down
8 changes: 5 additions & 3 deletions src/authz-module/roles-permissions/RolesPermissions.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { renderWrapper } from '@src/setupTest';
import RolesPermissions from './RolesPermissions';

// Mock utils
jest.mock('./library/utils', () => ({
jest.mock('./utils', () => ({
buildPermissionMatrixByResource: jest.fn(() => [
{
key: 'test-resource',
Expand All @@ -18,23 +18,25 @@ jest.mock('./library/utils', () => ({

// Mock constants
jest.mock('./course/constants', () => ({
rolesObject: [
courseRolesWithPermissions: [
{
name: 'Course Admin', role: 'admin', permissions: [], userCount: 1,
},
],
coursePermissions: [],
courseResourceTypes: [],
courseRolesMetadata: [],
}));

jest.mock('./library/constants', () => ({
rolesLibraryObject: [
libraryRolesWithPermissions: [
{
name: 'Library Admin', role: 'admin', permissions: [], userCount: 1,
},
],
libraryPermissions: [],
libraryResourceTypes: [],
libraryRolesMetadata: [],
}));

jest.mock('@openedx/paragon', () => ({
Expand Down
32 changes: 16 additions & 16 deletions src/authz-module/roles-permissions/RolesPermissions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,25 @@ import {
import {
coursePermissions,
courseResourceTypes,
rolesObject,
rolesLibraryObject,
courseRolesWithPermissions,
libraryRolesWithPermissions,
libraryPermissions,
libraryResourceTypes,
} from '@src/authz-module/roles-permissions';
import AnchorButton from '../components/AnchorButton';
import PermissionTable from '../components/PermissionTable';

import { buildPermissionMatrixByResource } from './library/utils';
import { buildPermissionMatrixByResource } from './utils';

import messages from './library/messages';
import messages from './messages';

const RolesPermissions = () => {
const intl = useIntl();
const [active, setActive] = useState('courses');

const libraryPermissionsByResource = useMemo(() => {
const permissionsByResource = buildPermissionMatrixByResource({
roles: rolesLibraryObject,
roles: libraryRolesWithPermissions,
permissions: libraryPermissions,
resources: libraryResourceTypes,
intl,
Expand All @@ -39,7 +39,7 @@ const RolesPermissions = () => {

const coursePermissionsByResource = useMemo(() => {
const permissionsByResource = buildPermissionMatrixByResource({
roles: rolesObject,
roles: courseRolesWithPermissions,
permissions: coursePermissions,
resources: courseResourceTypes,
intl,
Expand All @@ -56,13 +56,13 @@ const RolesPermissions = () => {
onClick={() => setActive('courses')}
variant={`${active === 'courses' ? 'primary' : 'outline-primary'}`}
>
{intl.formatMessage(messages['library.authz.tabs.permissionsRoles.courses.tab']) }
{intl.formatMessage(messages['authz.tabs.permissionsRoles.courses.tab']) }
</Button>
<Button
onClick={() => setActive('libraries')}
variant={`${active === 'libraries' ? 'primary' : 'outline-primary'}`}
>
{intl.formatMessage(messages['library.authz.tabs.permissionsRoles.libraries.tab']) }
{intl.formatMessage(messages['authz.tabs.permissionsRoles.libraries.tab']) }
</Button>
</ButtonGroup>
</Container>
Expand All @@ -71,24 +71,24 @@ const RolesPermissions = () => {
<div className="position-relative">
<PermissionTable
permissionsTable={coursePermissionsByResource}
roles={rolesObject}
title={intl.formatMessage(messages['library.authz.tabs.permissionsRoles.courses.tab.title'])}
roles={courseRolesWithPermissions}
title={intl.formatMessage(messages['authz.tabs.permissionsRoles.courses.tab.title'])}
/>
<Alert
variant="info"
className="mt-5"
>
<div className="row align-items-center">
<div className="col col-7">
<p className="text-primary font-weight-bold h4">{intl.formatMessage(messages['library.authz.tabs.permissionsRoles.courses.alert.title'])}</p>
<p className="text-primary font-weight-bold h4">{intl.formatMessage(messages['authz.tabs.permissionsRoles.courses.alert.title'])}</p>
<span>
<span className="font-weight-bold">{intl.formatMessage(messages['library.authz.tabs.permissionsRoles.courses.alert.note'])}</span>
{intl.formatMessage(messages['library.authz.tabs.permissionsRoles.courses.alert.description'])}
<span className="font-weight-bold">{intl.formatMessage(messages['authz.tabs.permissionsRoles.courses.alert.note'])}</span>
{intl.formatMessage(messages['authz.tabs.permissionsRoles.courses.alert.description'])}
</span>
</div>
<div className="col col-5">
<Hyperlink className="d-block text-right h5 font-weight-normal" destination="https://docs.openedx.org/en/latest/educators/references/course_development/course_team_roles.html" target="_blank" showLaunchIcon={false} isInline>
{intl.formatMessage(messages['library.authz.tabs.permissionsRoles.courses.alert.link'])}
{intl.formatMessage(messages['authz.tabs.permissionsRoles.courses.alert.link'])}
</Hyperlink>
</div>
</div>
Expand All @@ -99,8 +99,8 @@ const RolesPermissions = () => {
{ active === 'libraries' && (
<PermissionTable
permissionsTable={libraryPermissionsByResource}
roles={rolesLibraryObject}
title={intl.formatMessage(messages['library.authz.tabs.permissionsRoles.libraries.tab.title'])}
roles={libraryRolesWithPermissions}
title={intl.formatMessage(messages['authz.tabs.permissionsRoles.libraries.tab.title'])}
/>
)}
<AnchorButton />
Expand Down
Loading