Skip to content

Commit 5ceb49f

Browse files
committed
feat: create permissions tab
1 parent 2ea803c commit 5ceb49f

3 files changed

Lines changed: 152 additions & 15 deletions

File tree

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { Check, Close } from "@openedx/paragon/icons";
2+
import { actionsDictionary } from "./RoleCard/constants";
3+
import { Icon } from "@openedx/paragon";
4+
5+
const PermissionTable = ({ permissionsTable, roles }) => (
6+
<table className="pgn__data-table bg-light-100">
7+
<thead>
8+
<tr>
9+
<th className='bg-light-100'></th>
10+
{roles.map(role => (
11+
<th key={role.name} className="text-center bg-light-100">{role.name}</th>
12+
))}
13+
</tr>
14+
</thead>
15+
<tbody>
16+
{permissionsTable.map(resourceGroup => (
17+
<>
18+
<tr className="bg-info-100 text-primary lead">
19+
<td colSpan={roles.length + 1} className="text-start py-3">
20+
<strong>{resourceGroup.resourceLabel}</strong>
21+
</td>
22+
</tr>
23+
{
24+
resourceGroup.permissions.map(permission => (
25+
<tr key={permission.key} className='border-top'>
26+
<td className="text-start d-flex align-items-center">{
27+
<Icon className="d-inline-block mr-2" size="sm" src={actionsDictionary[permission.actionKey]} />}
28+
{permission.label}
29+
</td>
30+
{roles.map(role => (
31+
<td key={role.name} className="text-center">
32+
{permission.roles[role.name] ? <Icon className="d-inline-block" src={Check} /> : <Icon className="text-danger d-inline-block" src={Close} />}
33+
</td>
34+
))}
35+
</tr>
36+
))
37+
}
38+
</>
39+
))}
40+
</tbody>
41+
</table>
42+
);
43+
44+
export default PermissionTable;

src/authz-module/libraries-manager/LibrariesTeamManager.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import { useLibrary } from '@src/authz-module/data/hooks';
55
import TeamTable from './components/TeamTable';
66
import AuthZLayout from '../components/AuthZLayout';
77
import RoleCard from '../components/RoleCard';
8+
import PermissionTable from '../components/PermissionTable';
89
import { useLibraryAuthZ } from './context';
9-
import { buildPermissionsByRoleMatrix } from './utils';
10+
import { buildPermissionMatrix, buildPermissionsByRoleMatrix } from './utils';
1011

1112
import messages from './messages';
1213

@@ -25,6 +26,11 @@ const LibrariesTeamManager = () => {
2526
}),
2627
})), [roles, permissions, resources, intl]);
2728

29+
const permissionsTable = useMemo(() => {
30+
if (!roles || !permissions || !resources) return [];
31+
return buildPermissionMatrix(roles, permissions, resources, intl);
32+
}, [roles, permissions, resources]);
33+
2834
return (
2935
<div className="authz-libraries">
3036
<AuthZLayout
@@ -47,7 +53,7 @@ const LibrariesTeamManager = () => {
4753
<Container className="p-5">
4854
{libraryRoles && libraryRoles.map(role => (
4955
<RoleCard
50-
key={`${role}-description`}
56+
key={`${role.role}-description`}
5157
title={role.name}
5258
userCounter={role.userCount}
5359
description={role.description}
@@ -57,11 +63,13 @@ const LibrariesTeamManager = () => {
5763
</Container>
5864
</Tab>
5965
<Tab eventKey="permissions" title={intl.formatMessage(messages['library.authz.tabs.permissions'])}>
60-
Permissions tab.
66+
<Container className="p-5 container-mw-lg">
67+
<PermissionTable permissionsTable={permissionsTable} roles={libraryRoles} />
68+
</Container>
6169
</Tab>
6270
</Tabs>
6371
</AuthZLayout>
64-
</div>
72+
</div >
6573
);
6674
};
6775

Lines changed: 96 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,118 @@
1+
import { IntlShape } from '@edx/frontend-platform/i18n';
12
import { actionKeys } from '@src/authz-module/components/RoleCard/constants';
23
import actionMessages from '../components/RoleCard/messages';
4+
import { PermissionMetadata, ResourceMetadata, Role } from 'types';
5+
6+
function getPermissionMetadata(
7+
permission: PermissionMetadata,
8+
intl: IntlShape,
9+
): { label: string; actionKey: string | undefined } {
10+
const actionKey = actionKeys.find(action => permission.key.includes(action));
11+
let messageKey = `authz.permissions.actions.${actionKey}`;
12+
let messageResource = '';
13+
14+
if (actionKey === 'tag' || actionKey === 'team') {
15+
messageKey = 'authz.permissions.actions.manage';
16+
messageResource = actionKey === 'tag' ? 'Tags' : '';
17+
}
18+
19+
const label = permission.label || intl.formatMessage(actionMessages[messageKey], { resource: messageResource });
20+
21+
return { label, actionKey };
22+
}
23+
324

425
const buildPermissionsByRoleMatrix = ({
5-
rolePermissions, permissions, resources, intl,
26+
rolePermissions,
27+
permissions,
28+
resources,
29+
intl,
630
}) => {
731
const permissionsMatrix = {};
832
const allowedPermissions = new Set(rolePermissions);
933

1034
permissions.forEach((permission) => {
11-
const resourceLabel = resources.find(r => r.key === permission.resource)?.label || permission.resource;
12-
const actionKey = actionKeys.find(action => permission.key.includes(action));
13-
let messageKey = `authz.permissions.actions.${actionKey}`;
14-
let messageResource = '';
35+
const resourceLabel =
36+
resources.find((r) => r.key === permission.resource)?.label ||
37+
permission.resource;
38+
39+
const { label, actionKey } = getPermissionMetadata(permission, intl);
1540

16-
permissionsMatrix[permission.resource] = permissionsMatrix[permission.resource]
17-
|| { key: permission.resource, label: resourceLabel, actions: [] };
41+
if (!actionKey) return; // Skip unknown actions
1842

19-
if (actionKey === 'tag' || actionKey === 'team') {
20-
messageKey = 'authz.permissions.actions.manage';
21-
messageResource = actionKey === 'tag' ? 'Tags' : messageResource;
43+
// Initialize resource group if not already present
44+
if (!permissionsMatrix[permission.resource]) {
45+
permissionsMatrix[permission.resource] = {
46+
key: permission.resource,
47+
label: resourceLabel,
48+
actions: [],
49+
};
2250
}
2351

2452
permissionsMatrix[permission.resource].actions.push({
2553
key: actionKey,
26-
label: permission.label || intl.formatMessage(actionMessages[messageKey], { resource: messageResource }),
54+
label,
2755
disabled: !allowedPermissions.has(permission.key),
2856
});
2957
});
58+
3059
return Object.values(permissionsMatrix);
3160
};
3261

62+
63+
64+
65+
type PermissionMatrix = {
66+
resource: string;
67+
resourceLabel: string;
68+
permissions: {
69+
key: string;
70+
label: string;
71+
roles: Record<string, boolean>;
72+
}[];
73+
}[];
74+
75+
export function buildPermissionMatrix(
76+
roles: Role[],
77+
permissions: PermissionMetadata[],
78+
resources: ResourceMetadata[],
79+
intl: IntlShape,
80+
): PermissionMatrix {
81+
const permissionsByResource = permissions.reduce<Record<string, PermissionMetadata[]>>((acc, perm) => {
82+
if (!acc[perm.resource]) acc[perm.resource] = [];
83+
acc[perm.resource].push(perm);
84+
return acc;
85+
}, {});
86+
87+
const matrix: PermissionMatrix = resources.map(resource => {
88+
const resourcePermissions = permissionsByResource[resource.key] || [];
89+
90+
const permissionRows = resourcePermissions.map(permission => {
91+
const rolesMap: Record<string, boolean> = {};
92+
93+
roles.forEach(role => {
94+
rolesMap[role.name] = role.permissions.includes(permission.key);
95+
});
96+
97+
const { label, actionKey } = getPermissionMetadata(permission, intl);
98+
99+
return {
100+
key: permission.key,
101+
actionKey, // Important for icon mapping
102+
label,
103+
roles: rolesMap,
104+
};
105+
});
106+
107+
return {
108+
resource: resource.key,
109+
resourceLabel: resource.label,
110+
permissions: permissionRows,
111+
};
112+
});
113+
114+
return matrix;
115+
}
116+
117+
33118
export { buildPermissionsByRoleMatrix };

0 commit comments

Comments
 (0)