Skip to content

Commit 695bff3

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

11 files changed

Lines changed: 277 additions & 167 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
@@ -21,13 +21,17 @@ describe('RoleCard', () => {
2121
description: 'Can manage everything',
2222
showDelete: true,
2323
userCounter: 2,
24-
permissions: [
24+
permissionsByResource: [
2525
{
2626
key: 'library',
2727
label: 'Library Resource',
28-
actions: [
29-
{ key: 'view', label: 'View' },
30-
{ key: 'manage', label: 'Manage', disabled: true },
28+
permissions: [
29+
{
30+
key: 'view', label: 'View', actionKey: 'view', disabled: false,
31+
},
32+
{
33+
key: 'manage', label: 'Manage', actionKey: 'manage', disabled: true,
34+
},
3135
],
3236
},
3337
],
@@ -81,7 +85,7 @@ describe('RoleCard', () => {
8185
});
8286

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

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

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

2020
const CardTitle = ({ title, userCounter }: CardTitleProps) => (
@@ -30,7 +30,7 @@ const CardTitle = ({ title, userCounter }: CardTitleProps) => (
3030
);
3131

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

@@ -50,13 +50,11 @@ const RoleCard = ({
5050
title={intl.formatMessage(messages['authz.permissions.title'])}
5151
>
5252
<Container>
53-
{permissions.map(({ key, label, actions }) => (
53+
{permissionsByResource.map((resourceGroup) => (
5454
<PermissionRow
55-
key={`${title}-${key}`}
56-
resourceLabel={label}
57-
actions={actions}
55+
key={`${title}-${resourceGroup.key}`}
56+
resource={resourceGroup}
5857
/>
59-
6058
))}
6159
</Container>
6260
</Collapsible>

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

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

2727
jest.mock('../components/RoleCard', () => ({
2828
__esModule: true,
29-
default: ({ title, description, permissions }: { title: string, description: string, permissions: any[] }) => (
29+
default: ({ title, description, permissionsByResource }: {
30+
title: string,
31+
description: string,
32+
permissionsByResource: any[]
33+
}) => (
3034
<div data-testid="role-card">
3135
<div>{title}</div>
3236
<div>{description}</div>
33-
<div>{permissions.length} permissions</div>
37+
<div>{permissionsByResource.length} permissions</div>
3438
</div>
3539
),
3640
}));

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

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import AuthZLayout from '../components/AuthZLayout';
99
import RoleCard from '../components/RoleCard';
1010
import PermissionTable from '../components/PermissionTable';
1111
import { useLibraryAuthZ } from './context';
12-
import { buildPermissionMatrix, buildPermissionsByRoleMatrix } from './utils';
12+
import { buildPermissionMatrixByResource, buildPermissionMatrixByRole } from './utils';
1313

1414
import messages from './messages';
1515

@@ -21,16 +21,17 @@ const LibrariesTeamManager = () => {
2121
const { data: library } = useLibrary(libraryId);
2222
const rootBradecrumb = intl.formatMessage(messages['library.authz.breadcrumb.root']) || '';
2323
const pageTitle = intl.formatMessage(messages['library.authz.manage.page.title']);
24-
const libraryRoles = useMemo(() => roles.map(role => ({
25-
...role,
26-
permissions: buildPermissionsByRoleMatrix({
27-
rolePermissions: role.permissions, permissions, resources, intl,
28-
}),
29-
})), [roles, permissions, resources, intl]);
3024

31-
const permissionsTable = useMemo(() => {
32-
if (!roles || !permissions || !resources) { return []; }
33-
return buildPermissionMatrix(roles, permissions, resources, intl);
25+
const [libraryPermissionsByRole, libraryPermissionsByResource] = useMemo(() => {
26+
if (!roles && !permissions && !resources) { return [null, null]; }
27+
const permissionsByRole = buildPermissionMatrixByRole({
28+
roles, permissions, resources, intl,
29+
});
30+
const permissionsByResource = buildPermissionMatrixByResource({
31+
roles, permissions, resources, intl,
32+
});
33+
34+
return [permissionsByRole, permissionsByResource];
3435
}, [roles, permissions, resources, intl]);
3536

3637
return (
@@ -53,21 +54,21 @@ const LibrariesTeamManager = () => {
5354
</Tab>
5455
<Tab eventKey="roles" title={intl.formatMessage(messages['library.authz.tabs.roles'])}>
5556
<Container className="p-5">
56-
{libraryRoles && libraryRoles.map(role => (
57+
{libraryPermissionsByRole && libraryPermissionsByRole.map(role => (
5758
<RoleCard
5859
key={`${role.role}-description`}
5960
title={role.name}
6061
userCounter={role.userCount}
6162
description={role.description}
62-
permissions={role.permissions as any[]}
63+
permissionsByResource={role.resources as any[]}
6364
/>
6465
))}
6566
</Container>
6667
</Tab>
6768
<Tab eventKey="permissions" title={intl.formatMessage(messages['library.authz.tabs.permissions'])}>
6869
<Container className="p-5 container-mw-lg">
69-
{!permissionsTable ? <Skeleton count={2} height={200} />
70-
: <PermissionTable permissionsTable={permissionsTable} roles={roles} />}
70+
{!libraryPermissionsByResource ? <Skeleton count={2} height={200} />
71+
: <PermissionTable permissionsTable={libraryPermissionsByResource} roles={roles} />}
7172
</Container>
7273
</Tab>
7374
</Tabs>

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

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

1212
import messages from './messages';
1313

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

3733
return (
@@ -52,7 +48,7 @@ const LibrariesUserManager = () => {
5248
objectName={library.title}
5349
description={role.description}
5450
showDelete
55-
permissions={role.permissions as any[]}
51+
permissionsByResource={role.resources as any[]}
5652
/>
5753
))}
5854
</Container>

0 commit comments

Comments
 (0)