Skip to content

Commit 1990362

Browse files
authored
refactor: Decouple permissions table component from page (RocketChat#36872)
1 parent 02252b1 commit 1990362

7 files changed

Lines changed: 1467 additions & 116 deletions

File tree

apps/meteor/.storybook/main.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export default {
1111
getAbsolutePath('@storybook/addon-interactions'),
1212
getAbsolutePath('@storybook/addon-webpack5-compiler-babel'),
1313
getAbsolutePath('@storybook/addon-styling-webpack'),
14+
getAbsolutePath('@storybook/addon-a11y'),
1415
],
1516

1617
typescript: {
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { Margins, Tabs, Button } from '@rocket.chat/fuselage';
2+
import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
3+
import { useRoute, usePermission, useSetModal } from '@rocket.chat/ui-contexts';
4+
import type { ReactElement } from 'react';
5+
import { useState } from 'react';
6+
import { useTranslation } from 'react-i18next';
7+
8+
import CustomRoleUpsellModal from './CustomRoleUpsellModal';
9+
import PermissionsContextBar from './PermissionsContextBar';
10+
import PermissionsTable from './PermissionsTable';
11+
import { usePermissionsAndRoles } from './hooks/usePermissionsAndRoles';
12+
import { usePagination } from '../../../components/GenericTable/hooks/usePagination';
13+
import { Page, PageHeader, PageContent } from '../../../components/Page';
14+
15+
const PermissionsPage = ({ isEnterprise }: { isEnterprise: boolean }): ReactElement => {
16+
const { t } = useTranslation();
17+
const [filter, setFilter] = useState('');
18+
const canViewPermission = usePermission('access-permissions');
19+
const canViewSettingPermission = usePermission('access-setting-permissions');
20+
const defaultType = canViewPermission ? 'permissions' : 'settings';
21+
const [type, setType] = useState(defaultType);
22+
const router = useRoute('admin-permissions');
23+
const setModal = useSetModal();
24+
25+
const paginationProps = usePagination();
26+
const { permissions, total, roleList } = usePermissionsAndRoles(type, filter, paginationProps.itemsPerPage, paginationProps.current);
27+
28+
const handlePermissionsTab = useEffectEvent(() => {
29+
if (type === 'permissions') {
30+
return;
31+
}
32+
setType('permissions');
33+
});
34+
35+
const handleSettingsTab = useEffectEvent(() => {
36+
if (type === 'settings') {
37+
return;
38+
}
39+
setType('settings');
40+
});
41+
42+
const handleAdd = useEffectEvent(() => {
43+
if (!isEnterprise) {
44+
setModal(<CustomRoleUpsellModal onClose={() => setModal(null)} />);
45+
return;
46+
}
47+
router.push({
48+
context: 'new',
49+
});
50+
});
51+
52+
return (
53+
<Page flexDirection='row'>
54+
<Page>
55+
<PageHeader title={t('Permissions')}>
56+
<Button primary onClick={handleAdd} aria-label={t('New')} name={t('New_role')}>
57+
{t('New_role')}
58+
</Button>
59+
</PageHeader>
60+
<Margins blockEnd={16}>
61+
<Tabs>
62+
<Tabs.Item
63+
data-qa='PermissionTable-Permissions'
64+
selected={type === 'permissions'}
65+
onClick={handlePermissionsTab}
66+
disabled={!canViewPermission}
67+
>
68+
{t('Permissions')}
69+
</Tabs.Item>
70+
<Tabs.Item
71+
data-qa='PermissionTable-Settings'
72+
selected={type === 'settings'}
73+
onClick={handleSettingsTab}
74+
disabled={!canViewSettingPermission}
75+
>
76+
{t('Settings')}
77+
</Tabs.Item>
78+
</Tabs>
79+
</Margins>
80+
<PageContent mb='neg-x8'>
81+
<Margins block={8}>
82+
<PermissionsTable
83+
roleList={roleList}
84+
permissions={permissions}
85+
total={total}
86+
setFilter={setFilter}
87+
paginationProps={paginationProps}
88+
/>
89+
</Margins>
90+
</PageContent>
91+
</Page>
92+
<PermissionsContextBar />
93+
</Page>
94+
);
95+
};
96+
97+
export default PermissionsPage;

apps/meteor/client/views/admin/permissions/PermissionsRouter.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useRouteParameter, usePermission } from '@rocket.chat/ui-contexts';
22
import type { ReactElement } from 'react';
33

4-
import PermissionsTable from './PermissionsTable';
4+
import PermissionsPage from './PermissionsPage';
55
import UsersInRole from './UsersInRole';
66
import PageSkeleton from '../../../components/PageSkeleton';
77
import { useIsEnterprise } from '../../../hooks/useIsEnterprise';
@@ -25,7 +25,7 @@ const PermissionsRouter = (): ReactElement => {
2525
return <UsersInRole />;
2626
}
2727

28-
return <PermissionsTable isEnterprise={!!data?.isEnterprise} />;
28+
return <PermissionsPage isEnterprise={!!data?.isEnterprise} />;
2929
};
3030

3131
export default PermissionsRouter;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { mockAppRoot } from '@rocket.chat/mock-providers';
2+
import { composeStories } from '@storybook/react';
3+
import { render } from '@testing-library/react';
4+
import { axe } from 'jest-axe';
5+
6+
import * as stories from './PermissionsTable.stories';
7+
8+
const testCases = Object.values(composeStories(stories)).map((Story) => [Story.storyName || 'Story', Story]);
9+
10+
test.each(testCases)(`renders %s without crashing`, async (_storyname, Story) => {
11+
const { baseElement } = render(<Story />, { wrapper: mockAppRoot().build() });
12+
expect(baseElement).toMatchSnapshot();
13+
});
14+
15+
test.each(testCases)('%s should have no a11y violations', async (_storyname, Story) => {
16+
const { container } = render(<Story />, { wrapper: mockAppRoot().build() });
17+
18+
// TODO: Needed to skip `label` because fuselage‘s `CheckBox` has a a11y empty label issue
19+
// TODO: Needed to skip `button-name` because fuselage‘s `Pagination` buttons are missing names
20+
const results = await axe(container, { rules: { 'label': { enabled: false }, 'button-name': { enabled: false } } });
21+
expect(results).toHaveNoViolations();
22+
});
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import type { IPermission, IRole } from '@rocket.chat/core-typings';
2+
import { Margins } from '@rocket.chat/fuselage';
3+
import type { Meta, StoryFn } from '@storybook/react';
4+
5+
import PermissionsTable from './PermissionsTable';
6+
import { PageContent } from '../../../../components/Page';
7+
8+
export default {
9+
title: 'views/admin/PermissionsTable',
10+
component: PermissionsTable,
11+
decorators: [
12+
(fn) => (
13+
<PageContent mb='neg-x8'>
14+
<Margins block={8}>{fn()}</Margins>
15+
</PageContent>
16+
),
17+
],
18+
} satisfies Meta<typeof PermissionsTable>;
19+
20+
const roles: IRole[] = [
21+
{
22+
description: 'Owner of the workspace',
23+
name: 'owner',
24+
protected: true,
25+
scope: 'Users',
26+
_id: 'owner',
27+
},
28+
{
29+
description: 'Administrator',
30+
name: 'admin',
31+
protected: true,
32+
scope: 'Users',
33+
_id: 'admin',
34+
},
35+
{
36+
description: 'Leader',
37+
name: 'leader',
38+
protected: false,
39+
scope: 'Subscriptions',
40+
_id: 'leader',
41+
},
42+
{
43+
description: 'Moderator',
44+
name: 'moderator',
45+
protected: false,
46+
scope: 'Subscriptions',
47+
_id: 'moderator',
48+
},
49+
{
50+
description: 'User',
51+
name: 'user',
52+
protected: true,
53+
scope: 'Users',
54+
_id: 'user',
55+
},
56+
{
57+
description: 'Guest',
58+
name: 'guest',
59+
protected: true,
60+
scope: 'Users',
61+
_id: 'guest',
62+
},
63+
{
64+
description: 'Bot',
65+
name: 'bot',
66+
protected: true,
67+
scope: 'Users',
68+
_id: 'bot',
69+
},
70+
{
71+
description: 'App',
72+
name: 'app',
73+
protected: true,
74+
scope: 'Users',
75+
_id: 'app',
76+
},
77+
];
78+
79+
const permissions: IPermission[] = [
80+
{
81+
_id: '0',
82+
_updatedAt: new Date('2023-01-01'),
83+
roles: ['admin'],
84+
group: 'admin',
85+
level: 'settings',
86+
section: 'General',
87+
settingId: 'general_settings',
88+
sorter: 1,
89+
},
90+
{
91+
_id: '1',
92+
_updatedAt: new Date('2023-01-01'),
93+
roles: ['user'],
94+
group: 'admin',
95+
level: 'settings',
96+
section: 'General',
97+
settingId: 'general_settings',
98+
sorter: 2,
99+
},
100+
{
101+
_id: '2',
102+
_updatedAt: new Date('2023-01-01'),
103+
roles: ['user'],
104+
group: 'admin',
105+
level: 'settings',
106+
section: 'General',
107+
settingId: 'general_settings',
108+
sorter: 3,
109+
},
110+
{
111+
_id: '3',
112+
_updatedAt: new Date('2023-01-01'),
113+
roles: ['user'],
114+
group: 'admin',
115+
level: 'settings',
116+
section: 'General',
117+
settingId: 'general_settings',
118+
sorter: 4,
119+
},
120+
];
121+
122+
export const Default: StoryFn<typeof PermissionsTable> = (args) => <PermissionsTable {...args} />;
123+
Default.args = {
124+
total: permissions.length,
125+
permissions,
126+
roleList: roles,
127+
setFilter: () => undefined,
128+
};
129+
130+
export const Empty: StoryFn<typeof PermissionsTable> = (args) => <PermissionsTable {...args} />;
131+
Empty.args = {
132+
total: 0,
133+
permissions: [],
134+
roleList: [],
135+
setFilter: () => undefined,
136+
};

0 commit comments

Comments
 (0)