diff --git a/apps/meteor/.storybook/main.ts b/apps/meteor/.storybook/main.ts index 5c595e337ffdc..4e358f72d3b52 100644 --- a/apps/meteor/.storybook/main.ts +++ b/apps/meteor/.storybook/main.ts @@ -11,6 +11,7 @@ export default { getAbsolutePath('@storybook/addon-interactions'), getAbsolutePath('@storybook/addon-webpack5-compiler-babel'), getAbsolutePath('@storybook/addon-styling-webpack'), + getAbsolutePath('@storybook/addon-a11y'), ], typescript: { diff --git a/apps/meteor/client/views/admin/permissions/PermissionsPage.tsx b/apps/meteor/client/views/admin/permissions/PermissionsPage.tsx new file mode 100644 index 0000000000000..43b192054968d --- /dev/null +++ b/apps/meteor/client/views/admin/permissions/PermissionsPage.tsx @@ -0,0 +1,97 @@ +import { Margins, Tabs, Button } from '@rocket.chat/fuselage'; +import { useEffectEvent } from '@rocket.chat/fuselage-hooks'; +import { useRoute, usePermission, useSetModal } from '@rocket.chat/ui-contexts'; +import type { ReactElement } from 'react'; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; + +import CustomRoleUpsellModal from './CustomRoleUpsellModal'; +import PermissionsContextBar from './PermissionsContextBar'; +import PermissionsTable from './PermissionsTable'; +import { usePermissionsAndRoles } from './hooks/usePermissionsAndRoles'; +import { usePagination } from '../../../components/GenericTable/hooks/usePagination'; +import { Page, PageHeader, PageContent } from '../../../components/Page'; + +const PermissionsPage = ({ isEnterprise }: { isEnterprise: boolean }): ReactElement => { + const { t } = useTranslation(); + const [filter, setFilter] = useState(''); + const canViewPermission = usePermission('access-permissions'); + const canViewSettingPermission = usePermission('access-setting-permissions'); + const defaultType = canViewPermission ? 'permissions' : 'settings'; + const [type, setType] = useState(defaultType); + const router = useRoute('admin-permissions'); + const setModal = useSetModal(); + + const paginationProps = usePagination(); + const { permissions, total, roleList } = usePermissionsAndRoles(type, filter, paginationProps.itemsPerPage, paginationProps.current); + + const handlePermissionsTab = useEffectEvent(() => { + if (type === 'permissions') { + return; + } + setType('permissions'); + }); + + const handleSettingsTab = useEffectEvent(() => { + if (type === 'settings') { + return; + } + setType('settings'); + }); + + const handleAdd = useEffectEvent(() => { + if (!isEnterprise) { + setModal( setModal(null)} />); + return; + } + router.push({ + context: 'new', + }); + }); + + return ( + + + + + + + + + {t('Permissions')} + + + {t('Settings')} + + + + + + + + + + + + ); +}; + +export default PermissionsPage; diff --git a/apps/meteor/client/views/admin/permissions/PermissionsRouter.tsx b/apps/meteor/client/views/admin/permissions/PermissionsRouter.tsx index d6d20ca53a2db..3699760db36af 100644 --- a/apps/meteor/client/views/admin/permissions/PermissionsRouter.tsx +++ b/apps/meteor/client/views/admin/permissions/PermissionsRouter.tsx @@ -1,7 +1,7 @@ import { useRouteParameter, usePermission } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; -import PermissionsTable from './PermissionsTable'; +import PermissionsPage from './PermissionsPage'; import UsersInRole from './UsersInRole'; import PageSkeleton from '../../../components/PageSkeleton'; import { useIsEnterprise } from '../../../hooks/useIsEnterprise'; @@ -25,7 +25,7 @@ const PermissionsRouter = (): ReactElement => { return ; } - return ; + return ; }; export default PermissionsRouter; diff --git a/apps/meteor/client/views/admin/permissions/PermissionsTable/PermissionsTable.spec.tsx b/apps/meteor/client/views/admin/permissions/PermissionsTable/PermissionsTable.spec.tsx new file mode 100644 index 0000000000000..3922eab005c32 --- /dev/null +++ b/apps/meteor/client/views/admin/permissions/PermissionsTable/PermissionsTable.spec.tsx @@ -0,0 +1,22 @@ +import { mockAppRoot } from '@rocket.chat/mock-providers'; +import { composeStories } from '@storybook/react'; +import { render } from '@testing-library/react'; +import { axe } from 'jest-axe'; + +import * as stories from './PermissionsTable.stories'; + +const testCases = Object.values(composeStories(stories)).map((Story) => [Story.storyName || 'Story', Story]); + +test.each(testCases)(`renders %s without crashing`, async (_storyname, Story) => { + const { baseElement } = render(, { wrapper: mockAppRoot().build() }); + expect(baseElement).toMatchSnapshot(); +}); + +test.each(testCases)('%s should have no a11y violations', async (_storyname, Story) => { + const { container } = render(, { wrapper: mockAppRoot().build() }); + + // TODO: Needed to skip `label` because fuselage‘s `CheckBox` has a a11y empty label issue + // TODO: Needed to skip `button-name` because fuselage‘s `Pagination` buttons are missing names + const results = await axe(container, { rules: { 'label': { enabled: false }, 'button-name': { enabled: false } } }); + expect(results).toHaveNoViolations(); +}); diff --git a/apps/meteor/client/views/admin/permissions/PermissionsTable/PermissionsTable.stories.tsx b/apps/meteor/client/views/admin/permissions/PermissionsTable/PermissionsTable.stories.tsx new file mode 100644 index 0000000000000..624381acd9212 --- /dev/null +++ b/apps/meteor/client/views/admin/permissions/PermissionsTable/PermissionsTable.stories.tsx @@ -0,0 +1,136 @@ +import type { IPermission, IRole } from '@rocket.chat/core-typings'; +import { Margins } from '@rocket.chat/fuselage'; +import type { Meta, StoryFn } from '@storybook/react'; + +import PermissionsTable from './PermissionsTable'; +import { PageContent } from '../../../../components/Page'; + +export default { + title: 'views/admin/PermissionsTable', + component: PermissionsTable, + decorators: [ + (fn) => ( + + {fn()} + + ), + ], +} satisfies Meta; + +const roles: IRole[] = [ + { + description: 'Owner of the workspace', + name: 'owner', + protected: true, + scope: 'Users', + _id: 'owner', + }, + { + description: 'Administrator', + name: 'admin', + protected: true, + scope: 'Users', + _id: 'admin', + }, + { + description: 'Leader', + name: 'leader', + protected: false, + scope: 'Subscriptions', + _id: 'leader', + }, + { + description: 'Moderator', + name: 'moderator', + protected: false, + scope: 'Subscriptions', + _id: 'moderator', + }, + { + description: 'User', + name: 'user', + protected: true, + scope: 'Users', + _id: 'user', + }, + { + description: 'Guest', + name: 'guest', + protected: true, + scope: 'Users', + _id: 'guest', + }, + { + description: 'Bot', + name: 'bot', + protected: true, + scope: 'Users', + _id: 'bot', + }, + { + description: 'App', + name: 'app', + protected: true, + scope: 'Users', + _id: 'app', + }, +]; + +const permissions: IPermission[] = [ + { + _id: '0', + _updatedAt: new Date('2023-01-01'), + roles: ['admin'], + group: 'admin', + level: 'settings', + section: 'General', + settingId: 'general_settings', + sorter: 1, + }, + { + _id: '1', + _updatedAt: new Date('2023-01-01'), + roles: ['user'], + group: 'admin', + level: 'settings', + section: 'General', + settingId: 'general_settings', + sorter: 2, + }, + { + _id: '2', + _updatedAt: new Date('2023-01-01'), + roles: ['user'], + group: 'admin', + level: 'settings', + section: 'General', + settingId: 'general_settings', + sorter: 3, + }, + { + _id: '3', + _updatedAt: new Date('2023-01-01'), + roles: ['user'], + group: 'admin', + level: 'settings', + section: 'General', + settingId: 'general_settings', + sorter: 4, + }, +]; + +export const Default: StoryFn = (args) => ; +Default.args = { + total: permissions.length, + permissions, + roleList: roles, + setFilter: () => undefined, +}; + +export const Empty: StoryFn = (args) => ; +Empty.args = { + total: 0, + permissions: [], + roleList: [], + setFilter: () => undefined, +}; diff --git a/apps/meteor/client/views/admin/permissions/PermissionsTable/PermissionsTable.tsx b/apps/meteor/client/views/admin/permissions/PermissionsTable/PermissionsTable.tsx index 605f2fc155721..68a050b431d6b 100644 --- a/apps/meteor/client/views/admin/permissions/PermissionsTable/PermissionsTable.tsx +++ b/apps/meteor/client/views/admin/permissions/PermissionsTable/PermissionsTable.tsx @@ -1,61 +1,30 @@ +import type { IPermission, IRole } from '@rocket.chat/core-typings'; import { css } from '@rocket.chat/css-in-js'; -import { Margins, Tabs, Button, Pagination, Palette } from '@rocket.chat/fuselage'; -import { useEffectEvent } from '@rocket.chat/fuselage-hooks'; -import { useRoute, usePermission, useMethod, useTranslation, useSetModal } from '@rocket.chat/ui-contexts'; -import type { ReactElement } from 'react'; -import { useState } from 'react'; +import { Pagination, Palette } from '@rocket.chat/fuselage'; +import { useMethod } from '@rocket.chat/ui-contexts'; +import { useTranslation } from 'react-i18next'; import PermissionRow from './PermissionRow'; import PermissionsTableFilter from './PermissionsTableFilter'; import RoleHeader from './RoleHeader'; import GenericNoResults from '../../../../components/GenericNoResults'; import { GenericTable, GenericTableHeader, GenericTableHeaderCell, GenericTableBody } from '../../../../components/GenericTable'; -import { usePagination } from '../../../../components/GenericTable/hooks/usePagination'; -import { Page, PageHeader, PageContent } from '../../../../components/Page'; -import CustomRoleUpsellModal from '../CustomRoleUpsellModal'; -import PermissionsContextBar from '../PermissionsContextBar'; -import { usePermissionsAndRoles } from '../hooks/usePermissionsAndRoles'; +import type { usePagination } from '../../../../components/GenericTable/hooks/usePagination'; -const PermissionsTable = ({ isEnterprise }: { isEnterprise: boolean }): ReactElement => { - const t = useTranslation(); - const [filter, setFilter] = useState(''); - const canViewPermission = usePermission('access-permissions'); - const canViewSettingPermission = usePermission('access-setting-permissions'); - const defaultType = canViewPermission ? 'permissions' : 'settings'; - const [type, setType] = useState(defaultType); - const router = useRoute('admin-permissions'); - const setModal = useSetModal(); +type PermissionsTableProps = { + roleList: IRole[]; + permissions: IPermission[]; + setFilter: (filter: string) => void; + total: number; + paginationProps: ReturnType; +}; + +const PermissionsTable = ({ roleList, permissions, setFilter, total, paginationProps }: PermissionsTableProps) => { + const { t } = useTranslation(); const grantRole = useMethod('authorization:addPermissionToRole'); const removeRole = useMethod('authorization:removeRoleFromPermission'); - const { current, itemsPerPage, setItemsPerPage: onSetItemsPerPage, setCurrent: onSetCurrent, ...paginationProps } = usePagination(); - const { permissions, total, roleList } = usePermissionsAndRoles(type, filter, itemsPerPage, current); - - const handlePermissionsTab = useEffectEvent(() => { - if (type === 'permissions') { - return; - } - setType('permissions'); - }); - - const handleSettingsTab = useEffectEvent(() => { - if (type === 'settings') { - return; - } - setType('settings'); - }); - - const handleAdd = useEffectEvent(() => { - if (!isEnterprise) { - setModal( setModal(null)} />); - return; - } - router.push({ - context: 'new', - }); - }); - const fixedColumnStyle = css` tr > th { &:first-child { @@ -77,74 +46,32 @@ const PermissionsTable = ({ isEnterprise }: { isEnterprise: boolean }): ReactEle `; return ( - - - - - - - - - {t('Permissions')} - - - {t('Settings')} - - - - - - - {permissions?.length === 0 && } - {permissions?.length > 0 && ( - <> - - - {t('Name')} - {roleList?.map(({ _id, name, description }) => ( - - ))} - - - {permissions?.map((permission) => ( - - ))} - - - - - )} - - - - - + <> + + {permissions?.length === 0 && } + {permissions?.length > 0 && ( + <> + + + {t('Name')} + {roleList?.map(({ _id, name, description }) => )} + + + {permissions.map((permission) => ( + + ))} + + + + + )} + ); }; diff --git a/apps/meteor/client/views/admin/permissions/PermissionsTable/__snapshots__/PermissionsTable.spec.tsx.snap b/apps/meteor/client/views/admin/permissions/PermissionsTable/__snapshots__/PermissionsTable.spec.tsx.snap new file mode 100644 index 0000000000000..fd49f99680402 --- /dev/null +++ b/apps/meteor/client/views/admin/permissions/PermissionsTable/__snapshots__/PermissionsTable.spec.tsx.snap @@ -0,0 +1,1168 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`renders Default without crashing 1`] = ` + +
+
+ +
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ Name +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+ admin > General > general_settings + + + + + + + + +
+ admin > General > general_settings + + + + + + + + +
+ admin > General > general_settings + + + + + + + + +
+ admin > General > general_settings + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +`; + +exports[`renders Empty without crashing 1`] = ` + +
+
+ +
+
+ +

+ No_results_found +

+
+
+
+
+ +`;