Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { useItemsPerPage } from './useItemsPerPage';
import { useItemsPerPageLabel } from './useItemsPerPageLabel';
import { useShowingResultsLabel } from './useShowingResultsLabel';

/**
* TODO: Move `usePagination` outside from `GenericTable` folder
*/
export const usePagination = (): {
current: ReturnType<typeof useCurrent>[0];
setCurrent: ReturnType<typeof useCurrent>[1];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ const PermissionsPage = ({ isEnterprise }: { isEnterprise: boolean }): ReactElem
const router = useRoute('admin-permissions');
const setModal = useSetModal();

const paginationProps = usePagination();
const { permissions, total, roleList } = usePermissionsAndRoles(type, filter, paginationProps.itemsPerPage, paginationProps.current);
const paginationData = usePagination();
const { permissions, total, roleList } = usePermissionsAndRoles(type, filter, paginationData.itemsPerPage, paginationData.current);

const handlePermissionsTab = useEffectEvent(() => {
if (type === 'permissions') {
Expand Down Expand Up @@ -84,7 +84,7 @@ const PermissionsPage = ({ isEnterprise }: { isEnterprise: boolean }): ReactElem
permissions={permissions}
total={total}
setFilter={setFilter}
paginationProps={paginationProps}
paginationData={paginationData}
/>
</Margins>
</PageContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { axe } from 'jest-axe';

import PermissionsTable from './PermissionsTable';
import * as stories from './PermissionsTable.stories';
import { createMockedPagination } from '../../../../../tests/mocks/data';

const testCases = Object.values(composeStories(stories)).map((Story) => [Story.storyName || 'Story', Story]);

Expand All @@ -24,6 +25,8 @@ test.each(testCases)('%s should have no a11y violations', async (_storyname, Sto
expect(results).toHaveNoViolations();
});

const paginationData = createMockedPagination();

const defaultPermissions: IPermission[] = [
{
_id: 'access-permissions',
Expand Down Expand Up @@ -51,7 +54,13 @@ const roles: IRole[] = [

test('should display modal if the permission is access-permissions and has only one granted role', async () => {
render(
<PermissionsTable permissions={defaultPermissions} total={defaultPermissions.length} setFilter={() => undefined} roleList={roles} />,
<PermissionsTable
permissions={defaultPermissions}
total={defaultPermissions.length}
setFilter={() => undefined}
roleList={roles}
paginationData={paginationData}
/>,
{
wrapper: mockAppRoot().build(),
},
Expand All @@ -70,9 +79,18 @@ test('should NOT display modal if the permission is access-permissions and has m
},
];

render(<PermissionsTable permissions={morePermissions} total={morePermissions.length} setFilter={() => undefined} roleList={roles} />, {
wrapper: mockAppRoot().build(),
});
render(
<PermissionsTable
permissions={morePermissions}
total={morePermissions.length}
setFilter={() => undefined}
roleList={roles}
paginationData={paginationData}
/>,
{
wrapper: mockAppRoot().build(),
},
);

await userEvent.click(screen.getByRole('checkbox', { name: 'access-permissions - Administrator' }));
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Margins } from '@rocket.chat/fuselage';
import type { Meta, StoryFn } from '@storybook/react';

import PermissionsTable from './PermissionsTable';
import { createMockedPagination } from '../../../../../tests/mocks/data';
import { PageContent } from '../../../../components/Page';

export default {
Expand Down Expand Up @@ -98,18 +99,24 @@ const permissions: IPermission[] = [
},
];

export const Default: StoryFn<typeof PermissionsTable> = (args) => <PermissionsTable {...args} />;
const paginationData = createMockedPagination(permissions.length, 10);

const Template: StoryFn<typeof PermissionsTable> = (args) => <PermissionsTable {...args} />;

export const Default = Template.bind({});
Default.args = {
total: permissions.length,
total: 10,
permissions,
roleList: roles,
setFilter: () => undefined,
paginationData,
};

export const Empty: StoryFn<typeof PermissionsTable> = (args) => <PermissionsTable {...args} />;
export const Empty = Template.bind({});
Empty.args = {
total: 0,
permissions: [],
roleList: [],
setFilter: () => undefined,
paginationData,
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,17 @@ type PermissionsTableProps = {
permissions: IPermission[];
setFilter: (filter: string) => void;
total: number;
paginationProps?: ReturnType<typeof usePagination>;
paginationData: ReturnType<typeof usePagination>;
};

const PermissionsTable = ({ roleList, permissions, setFilter, total, paginationProps }: PermissionsTableProps) => {
const PermissionsTable = ({ roleList, permissions, setFilter, total, paginationData }: PermissionsTableProps) => {
const { t } = useTranslation();

const grantRole = useMethod('authorization:addPermissionToRole');
const removeRole = useMethod('authorization:removeRoleFromPermission');

const { current, itemsPerPage, setCurrent, setItemsPerPage, ...paginationProps } = paginationData;

const tableCustomStyle = css`
// Makes the first column of the table sticky
tr > th {
Expand Down Expand Up @@ -77,9 +79,11 @@ const PermissionsTable = ({ roleList, permissions, setFilter, total, paginationP
</GenericTable>
<Pagination
divider
current={current}
itemsPerPage={itemsPerPage}
count={total}
onSetItemsPerPage={paginationProps?.setItemsPerPage}
onSetCurrent={paginationProps?.setCurrent}
onSetItemsPerPage={setItemsPerPage}
onSetCurrent={setCurrent}
{...paginationProps}
/>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1100,7 +1100,7 @@ exports[`renders Default without crashing 1`] = `
<span
class="rcx-box rcx-box--full rcx-pagination__label"
>
Showing results 1 - 4 of 4
Showing results 1 - 4 of 10
</span>
<ol
class="rcx-box rcx-box--full rcx-pagination__list"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import type { IRole, IRoom } from '@rocket.chat/core-typings';
import { Box, Field, FieldLabel, FieldRow, Margins, ButtonGroup, Button, Callout, FieldError } from '@rocket.chat/fuselage';
import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
import { useToastMessageDispatch, useEndpoint, useTranslation, useRouter } from '@rocket.chat/ui-contexts';
import { useQueryClient } from '@tanstack/react-query';
import { useId, type ReactElement } from 'react';
import { useToastMessageDispatch, useEndpoint, useRouter } from '@rocket.chat/ui-contexts';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useId, useMemo, type ReactElement } from 'react';
import { useForm, Controller } from 'react-hook-form';
import { useTranslation } from 'react-i18next';

import UsersInRoleTable from './UsersInRoleTable';
import { useRemoveUserFromRole } from './hooks/useRemoveUserFromRole';
import { usePagination } from '../../../../components/GenericTable/hooks/usePagination';
import { Page, PageHeader, PageContent } from '../../../../components/Page';
import RoomAutoComplete from '../../../../components/RoomAutoComplete';
import UserAutoCompleteMultiple from '../../../../components/UserAutoCompleteMultiple';
Expand All @@ -17,7 +20,7 @@ type UsersInRolePayload = {
};

const UsersInRolePage = ({ role }: { role: IRole }): ReactElement => {
const t = useTranslation();
const { t } = useTranslation();
const dispatchToastMessage = useToastMessageDispatch();
const queryClient = useQueryClient();

Expand Down Expand Up @@ -54,6 +57,28 @@ const UsersInRolePage = ({ role }: { role: IRole }): ReactElement => {
}
});

const getUsersInRoleEndpoint = useEndpoint('GET', '/v1/roles.getUsersInRole');

const paginationData = usePagination();
const { itemsPerPage, current } = paginationData;

const query = useMemo(
() => ({
role: _id,
...(rid && { roomId: rid }),
...(itemsPerPage && { count: itemsPerPage }),
...(current && { offset: current }),
}),
[itemsPerPage, current, rid, _id],
);

const { data, isLoading, isSuccess, refetch, isError } = useQuery({
queryKey: ['getUsersInRole', _id, query],
queryFn: async () => getUsersInRoleEndpoint(query),
});

const handleRemove = useRemoveUserFromRole({ rid, roleId: _id, roleName: name, roleDescription: description });

return (
<Page>
<PageHeader title={`${t('Users_in_role')} "${description || name}"`}>
Expand Down Expand Up @@ -127,7 +152,18 @@ const UsersInRolePage = ({ role }: { role: IRole }): ReactElement => {
</Margins>
</Box>
<Margins blockStart={8}>
{(role.scope === 'Users' || rid) && <UsersInRoleTable rid={rid} roleId={_id} roleName={name} description={description} />}
{(role.scope === 'Users' || rid) && (
<UsersInRoleTable
isLoading={isLoading}
isError={isError}
isSuccess={isSuccess}
total={data?.total || 0}
users={data?.users || []}
onRemove={handleRemove}
refetch={refetch}
paginationData={paginationData}
/>
)}
{role.scope !== 'Users' && !rid && <Callout type='info'>{t('Select_a_room')}</Callout>}
</Margins>
</PageContent>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
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 './UsersInRoleTable.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(<Story />, { wrapper: mockAppRoot().build() });
expect(baseElement).toMatchSnapshot();
});

test.each(testCases)('%s should have no a11y violations', async (_storyname, Story) => {
const { container } = render(<Story />, { wrapper: mockAppRoot().build() });

// TODO: Needed to skip `button-name` because fuselage‘s `Pagination` buttons are missing names
const results = await axe(container, { rules: { 'button-name': { enabled: false } } });
expect(results).toHaveNoViolations();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { Margins } from '@rocket.chat/fuselage';
import type { Meta, StoryFn } from '@storybook/react';

import UsersInRoleTable from './UsersInRoleTable';
import { createMockedPagination } from '../../../../../../tests/mocks/data';
import { PageContent } from '../../../../../components/Page';

export default {
component: UsersInRoleTable,
decorators: [
(fn) => (
<PageContent mb='neg-x8'>
<Margins block={8}>{fn()}</Margins>
</PageContent>
),
],
} satisfies Meta<typeof UsersInRoleTable>;

const generateMockedUsers = (count: number) =>
Array.from({ length: count }, (_, i) => ({
_id: `${i + 1}`,
username: `user.${i + 1}`,
name: `User ${i + 1}`,
emails: [{ address: `user${i + 1}@example.com`, verified: i % 2 === 0 }],
createdAt: new Date().toISOString(),
_updatedAt: new Date().toISOString(),
roles: [i < 5 ? 'admin' : 'user'],
type: 'user',
active: true,
}));

const mockedUsers = generateMockedUsers(5);

const paginationData = createMockedPagination(mockedUsers.length, 30);

const Template: StoryFn<typeof UsersInRoleTable> = (args) => <UsersInRoleTable {...args} />;

export const Default = Template.bind({});
Default.args = {
total: 30,
isLoading: false,
isError: false,
isSuccess: true,
users: mockedUsers,
onRemove: () => undefined,
refetch: () => undefined,
paginationData,
};

export const withLoading = Template.bind({});
withLoading.args = {
total: 0,
isLoading: true,
isError: false,
isSuccess: false,
users: [],
onRemove: () => undefined,
refetch: () => undefined,
paginationData,
};

export const withNoResults = Template.bind({});
withNoResults.args = {
total: 0,
isLoading: false,
isError: false,
isSuccess: true,
users: [],
onRemove: () => undefined,
refetch: () => undefined,
paginationData,
};

export const withError = Template.bind({});
withError.args = {
total: 0,
isLoading: false,
isError: true,
isSuccess: false,
users: [],
onRemove: () => undefined,
refetch: () => undefined,
paginationData,
};
Loading
Loading