Skip to content

Commit f4c0e33

Browse files
committed
feat: create ResourceTooltip component and enhance types for permission matrix by role and resource
1 parent a144f44 commit f4c0e33

11 files changed

Lines changed: 285 additions & 188 deletions

File tree

src/authz-module/components/PermissionTable.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { Check, Close } from '@openedx/paragon/icons';
22
import { Icon } from '@openedx/paragon';
3-
import { Role } from '@src/types';
4-
import { PermissionMatrix } from '@src/authz-module/libraries-manager/utils';
3+
import { PermissionsResourceGrouped, Role } from '@src/types';
54
import { actionsDictionary } from './RoleCard/constants';
5+
import ResourceTooltip from './ResourceTooltip';
66

77
type PermissionTableProps = {
88
roles: Role[];
9-
permissionsTable: PermissionMatrix;
9+
permissionsTable: PermissionsResourceGrouped[];
1010
};
1111

1212
const PermissionTable = ({ permissionsTable, roles }: PermissionTableProps) => (
@@ -24,7 +24,8 @@ const PermissionTable = ({ permissionsTable, roles }: PermissionTableProps) => (
2424
<>
2525
<tr className="bg-info-100 text-primary">
2626
<td colSpan={roles.length + 1} className="text-start py-3 px-4">
27-
<strong>{resourceGroup.resourceLabel}</strong>
27+
<strong>{resourceGroup.label}</strong>
28+
<ResourceTooltip resourceGroup={resourceGroup} />
2829
</td>
2930
</tr>
3031
{
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { Icon, OverlayTrigger, Popover } from '@openedx/paragon';
2+
import { Info } from '@openedx/paragon/icons';
3+
import { PermissionsResourceGrouped, RoleResourceGroup } from '@src/types';
4+
5+
type ResourceTooltipProps = {
6+
resourceGroup: PermissionsResourceGrouped | RoleResourceGroup;
7+
};
8+
9+
const ResourceTooltip = ({ resourceGroup }:ResourceTooltipProps) => (
10+
<OverlayTrigger
11+
key={`overlay-${resourceGroup.key}`}
12+
placement="right"
13+
overlay={(
14+
<Popover variant="light" id={`tooltip-${resourceGroup.label}`}>
15+
<Popover.Content className="p-3">
16+
<h3 className="text-primary">{resourceGroup.label}</h3>
17+
<p>{resourceGroup.description}</p>
18+
<ul>
19+
{resourceGroup.permissions.map(permission => (
20+
<li><b>{permission.label.trim()}:</b> {permission.description}</li>
21+
))}
22+
</ul>
23+
</Popover.Content>
24+
</Popover>
25+
)}
26+
>
27+
<Icon className="d-inline-block text-gray ml-2 my-auto" size="sm" src={Info} />
28+
</OverlayTrigger>
29+
);
30+
31+
export default ResourceTooltip;

src/authz-module/components/RoleCard/PermissionsRow.tsx

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,34 @@ import { ComponentType } from 'react';
22
import {
33
Chip, Col, Row,
44
} from '@openedx/paragon';
5+
import { RoleResourceGroup } from '@src/types';
56
import { actionsDictionary, ActionKey } from './constants';
7+
import ResourceTooltip from '../ResourceTooltip';
68

7-
interface Action {
8-
key: string;
9-
label?: string;
10-
disabled?: boolean;
11-
}
9+
type PermissionRowProps = {
10+
resource: RoleResourceGroup;
11+
};
1212

13-
interface PermissionRowProps {
14-
resourceLabel: string;
15-
actions: Action[];
16-
}
17-
18-
const PermissionRow = ({ resourceLabel, actions }: PermissionRowProps) => (
13+
const PermissionRow = ({ resource }: PermissionRowProps) => (
1914
<Row className="row align-items-center border px-2 py-2">
2015
<Col md={2}>
21-
<span className="small font-weight-bold">{resourceLabel}</span>
16+
<span className="small font-weight-bold">{resource.label}</span>
17+
<ResourceTooltip resourceGroup={resource} />
2218
</Col>
2319
<Col>
2420
<div className="w-100 d-flex flex-wrap align-items-center">
25-
{actions.map((action, index) => (
21+
{resource.permissions.map((action, index) => (
2622
<>
2723
<Chip
2824
key={action.key}
29-
iconBefore={actionsDictionary[action.key as ActionKey] as ComponentType}
25+
iconBefore={actionsDictionary[action.actionKey as ActionKey] as ComponentType}
3026
disabled={action.disabled}
3127
className="mx-3 my-2 px-3 bg-primary-100 border-0 permission-chip"
3228
variant="light"
3329
>
3430
{action.label}
3531
</Chip>
36-
{(index === actions.length - 1) ? null
32+
{(index === resource.permissions.length - 1) ? null
3733
: (<hr className="border-right mx-2" style={{ height: '24px' }} />)}
3834
</>
3935
))}

src/authz-module/components/RoleCard/index.test.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,17 @@ describe('RoleCard', () => {
2222
description: 'Can manage everything',
2323
showDelete: true,
2424
userCounter: 2,
25-
permissions: [
25+
permissionsByResource: [
2626
{
2727
key: 'library',
2828
label: 'Library Resource',
29-
actions: [
30-
{ key: 'view', label: 'View' },
31-
{ key: 'manage', label: 'Manage', disabled: true },
29+
permissions: [
30+
{
31+
key: 'view', label: 'View', actionKey: 'view', disabled: false,
32+
},
33+
{
34+
key: 'manage', label: 'Manage', actionKey: 'manage', disabled: true,
35+
},
3236
],
3337
},
3438
],
@@ -83,7 +87,7 @@ describe('RoleCard', () => {
8387
});
8488

8589
it('handles empty permissions gracefully', () => {
86-
renderWrapper(<RoleCard {...defaultProps} permissions={[]} />);
90+
renderWrapper(<RoleCard {...defaultProps} permissionsByResource={[]} />);
8791
expect(screen.queryByText('Library Resource')).not.toBeInTheDocument();
8892
});
8993
});

src/authz-module/components/RoleCard/index.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ interface RoleCardProps extends CardTitleProps {
1515
objectName?: string | null;
1616
description: string;
1717
showDelete?: boolean;
18-
permissions: any[];
18+
permissionsByResource: any[];
1919
}
2020

2121
const CardTitle = ({ title, userCounter = null }: CardTitleProps) => (
@@ -31,7 +31,7 @@ const CardTitle = ({ title, userCounter = null }: CardTitleProps) => (
3131
);
3232

3333
const RoleCard = ({
34-
title, objectName, description, showDelete, permissions, userCounter,
34+
title, objectName, description, showDelete, permissionsByResource, userCounter,
3535
}: RoleCardProps) => {
3636
const intl = useIntl();
3737

@@ -51,13 +51,11 @@ const RoleCard = ({
5151
title={intl.formatMessage(messages['authz.permissions.title'])}
5252
>
5353
<Container>
54-
{permissions.map(({ key, label, actions }) => (
54+
{permissionsByResource.map((resourceGroup) => (
5555
<PermissionRow
56-
key={`${title}-${key}`}
57-
resourceLabel={label}
58-
actions={actions}
56+
key={`${title}-${resourceGroup.key}`}
57+
resource={resourceGroup}
5958
/>
60-
6159
))}
6260
</Container>
6361
</Collapsible>

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,15 @@ jest.mock('./components/AddNewTeamMemberModal', () => ({
3232

3333
jest.mock('../components/RoleCard', () => ({
3434
__esModule: true,
35-
default: ({ title, description, permissions }: { title: string, description: string, permissions: any[] }) => (
35+
default: ({ title, description, permissionsByResource }: {
36+
title: string,
37+
description: string,
38+
permissionsByResource: any[]
39+
}) => (
3640
<div data-testid="role-card">
3741
<div>{title}</div>
3842
<div>{description}</div>
39-
<div>{permissions.length} permissions</div>
43+
<div>{permissionsByResource.length} permissions</div>
4044
</div>
4145
),
4246
}));

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

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import RoleCard from '../components/RoleCard';
1111
import PermissionTable from '../components/PermissionTable';
1212
import { useLibraryAuthZ } from './context';
1313
import { AddNewTeamMemberTrigger } from './components/AddNewTeamMemberModal';
14-
import { buildPermissionMatrix, buildPermissionsByRoleMatrix } from './utils';
14+
import { buildPermissionMatrixByResource, buildPermissionMatrixByRole } from './utils';
1515

1616
import messages from './messages';
1717

@@ -24,16 +24,17 @@ const LibrariesTeamManager = () => {
2424
const { data: library } = useLibrary(libraryId);
2525
const rootBradecrumb = intl.formatMessage(messages['library.authz.breadcrumb.root']) || '';
2626
const pageTitle = intl.formatMessage(messages['library.authz.manage.page.title']);
27-
const libraryRoles = useMemo(() => roles.map(role => ({
28-
...role,
29-
permissions: buildPermissionsByRoleMatrix({
30-
rolePermissions: role.permissions, permissions, resources, intl,
31-
}),
32-
})), [roles, permissions, resources, intl]);
3327

34-
const permissionsTable = useMemo(() => {
35-
if (!roles || !permissions || !resources) { return []; }
36-
return buildPermissionMatrix(roles, permissions, resources, intl);
28+
const [libraryPermissionsByRole, libraryPermissionsByResource] = useMemo(() => {
29+
if (!roles && !permissions && !resources) { return [null, null]; }
30+
const permissionsByRole = buildPermissionMatrixByRole({
31+
roles, permissions, resources, intl,
32+
});
33+
const permissionsByResource = buildPermissionMatrixByResource({
34+
roles, permissions, resources, intl,
35+
});
36+
37+
return [permissionsByRole, permissionsByResource];
3738
}, [roles, permissions, resources, intl]);
3839

3940
return (
@@ -60,22 +61,22 @@ const LibrariesTeamManager = () => {
6061
</Tab>
6162
<Tab eventKey="roles" title={intl.formatMessage(messages['library.authz.tabs.roles'])}>
6263
<Container className="p-5">
63-
{!libraryRoles ? <Skeleton count={2} height={200} /> : null}
64-
{libraryRoles && libraryRoles.map(role => (
65-
<RoleCard
66-
key={`${role.role}-description`}
67-
title={role.name}
68-
userCounter={role.userCount}
69-
description={role.description}
70-
permissions={role.permissions as any[]}
71-
/>
72-
))}
64+
{!libraryPermissionsByRole ? <Skeleton count={2} height={200} />
65+
: libraryPermissionsByRole.map(role => (
66+
<RoleCard
67+
key={`${role.role}-description`}
68+
title={role.name}
69+
userCounter={role.userCount}
70+
description={role.description}
71+
permissionsByResource={role.resources as any[]}
72+
/>
73+
))}
7374
</Container>
7475
</Tab>
7576
<Tab id="libraries-permissions-tab" eventKey="permissions" title={intl.formatMessage(messages['library.authz.tabs.permissions'])}>
7677
<Container className="p-5 container-mw-lg">
77-
{!permissionsTable ? <Skeleton count={2} height={200} />
78-
: <PermissionTable permissionsTable={permissionsTable} roles={roles} />}
78+
{!libraryPermissionsByResource ? <Skeleton count={2} height={200} />
79+
: <PermissionTable permissionsTable={libraryPermissionsByResource} roles={roles} />}
7980
</Container>
8081
</Tab>
8182
</Tabs>

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

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { useLibraryAuthZ } from './context';
88
import RoleCard from '../components/RoleCard';
99
import { AssignNewRoleTrigger } from './components/AssignNewRoleModal';
1010
import { useLibrary, useTeamMembers } from '../data/hooks';
11-
import { buildPermissionsByRoleMatrix } from './utils';
11+
import { buildPermissionMatrixByRole } from './utils';
1212

1313
import messages from './messages';
1414

@@ -25,14 +25,10 @@ const LibrariesUserManager = () => {
2525
const { data: teamMembers, isLoading } = useTeamMembers(libraryId);
2626
const user = teamMembers?.find(member => member.username === username);
2727
const userRoles = useMemo(() => {
28-
const assignedRoles = roles.filter(role => user?.roles.includes(role.role))
29-
.map(role => ({
30-
...role,
31-
permissions: buildPermissionsByRoleMatrix({
32-
rolePermissions: role.permissions, permissions, resources, intl,
33-
}),
34-
}));
35-
return assignedRoles;
28+
const assignedRoles = roles.filter(role => user?.roles.includes(role.role));
29+
return buildPermissionMatrixByRole({
30+
roles: assignedRoles, permissions, resources, intl,
31+
});
3632
}, [roles, user?.roles, permissions, resources, intl]);
3733

3834
return (
@@ -60,7 +56,7 @@ const LibrariesUserManager = () => {
6056
objectName={library.title}
6157
description={role.description}
6258
showDelete
63-
permissions={role.permissions as any[]}
59+
permissionsByResource={role.resources as any[]}
6460
/>
6561
))}
6662
</Container>

0 commit comments

Comments
 (0)