Skip to content
Open
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,10 +5,10 @@ import { UserMiniEntity } from 'types/users';

import DeleteButton from 'lib/components/core/buttons/DeleteButton';
import { PromptText } from 'lib/components/core/dialogs/Prompt';
import { USER_ROLES } from 'lib/constants/sharedConstants';
import { useAppDispatch } from 'lib/hooks/store';
import toast from 'lib/hooks/toast';
import useTranslation from 'lib/hooks/useTranslation';
import instanceRoleTranslations from 'lib/translations/instance/users/roles';

import { deleteUser } from '../../operations';

Expand Down Expand Up @@ -89,7 +89,7 @@ const UserManagementButtons: FC<Props> = (props) => {
loading={isDeleting}
onClick={onDelete}
title={t(translations.deletionConfirmTitle, {
role: USER_ROLES[user.role],
role: t(instanceRoleTranslations[user.role]),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this implicitly assumes that system roles are a subset of instance roles.

I don't think this assumption will change anytime soon, so I'm OK with doing this.

name: user.name,
email: user.email,
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,25 @@ import {
TableOptions,
TableState,
} from 'types/components/DataTable';
import { AdminStats, UserMiniEntity, UserRoles } from 'types/users';
import {
AdminStats,
USER_SYSTEM_ROLES,
UserMiniEntity,
UserSystemRoles,
} from 'types/users';

import DataTable from 'lib/components/core/layouts/DataTable';
import Link from 'lib/components/core/Link';
import InlineEditTextField from 'lib/components/form/fields/DataTableInlineEditable/TextField';
import {
DEFAULT_TABLE_ROWS_PER_PAGE,
FIELD_DEBOUNCE_DELAY_MS,
USER_ROLES,
} from 'lib/constants/sharedConstants';
import rebuildObjectFromRow from 'lib/helpers/mui-datatables-helpers';
import { useAppDispatch } from 'lib/hooks/store';
import toast from 'lib/hooks/toast';
import useTranslation from 'lib/hooks/useTranslation';
import instanceRoleTranslations from 'lib/translations/instance/users/roles';
import tableTranslations from 'lib/translations/table';

import { indexUsers, updateUser } from '../../operations';
Expand Down Expand Up @@ -117,15 +122,15 @@ const UsersTable: FC<Props> = (props) => {
) as UserMiniEntity;
const newUser = {
...user,
role: newRole as UserRoles,
role: newRole as UserSystemRoles,
};
return dispatch(updateUser(user.id, newUser))
.then(() => {
updateValue(newRole);
toast.success(
t(translations.changeRoleSuccess, {
name: user.name,
role: USER_ROLES[newRole],
role: t(instanceRoleTranslations[newRole as UserSystemRoles]),
}),
);
})
Expand Down Expand Up @@ -315,13 +320,14 @@ const UsersTable: FC<Props> = (props) => {
value={value}
variant="standard"
>
{Object.keys(USER_ROLES).map((option) => (
{/* UserSystemRoles ('normal' | 'administrator') is a subset of InstanceUserRoles */}
{USER_SYSTEM_ROLES.map((option) => (
<MenuItem
key={`role-${userId}-${option}`}
id={`role-${userId}-${option}`}
value={option}
>
{USER_ROLES[option]}
{t(instanceRoleTranslations[option])}
</MenuItem>
))}
</TextField>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { render, screen, waitForElementToBeRemoved } from 'test-utils';

import UsersTable from '../UsersTable';

const baseUserCounts = {
totalUsers: { allCount: 1 },
activeUsers: { allCount: 1 },
coursesCount: 0,
usersCount: 1,
totalCourses: 0,
activeCourses: 0,
instancesCount: 1,
};

const baseUser = {
id: 42,
name: 'Bob',
email: 'bob@example.org',
role: 'normal' as const,
instances: [
{ name: 'Main Instance', host: 'main.coursemology.org', courses: [] },
],
};

describe('<UsersTable />', () => {
describe('instances column', () => {
it('links to the user profile on the instance, not the admin list', async () => {
render(
<UsersTable
filter={{ active: true, role: 'normal' }}
renderRowActionComponent={() => <span />}
title="Users"
userCounts={baseUserCounts}
users={[baseUser]}
/>,
);
await waitForElementToBeRemoved(() => screen.queryByRole('progressbar'));

const link = screen.getByRole('link', { name: /Main Instance/ });
expect(link).toHaveAttribute('href', '//main.coursemology.org/users/42');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import { RoleRequestRowData } from 'types/system/instance/roleRequests';
import AcceptButton from 'lib/components/core/buttons/AcceptButton';
import DeleteButton from 'lib/components/core/buttons/DeleteButton';
import EmailButton from 'lib/components/core/buttons/EmailButton';
import { ROLE_REQUEST_ROLES } from 'lib/constants/sharedConstants';
import { useAppDispatch } from 'lib/hooks/store';
import toast from 'lib/hooks/toast';
import useTranslation from 'lib/hooks/useTranslation';
import instanceRoleTranslations from 'lib/translations/instance/users/roles';

import { approveRoleRequest, rejectRoleRequest } from '../../operations';
import RejectWithMessageForm from '../forms/RejectWithMessageForm';
Expand Down Expand Up @@ -131,7 +131,7 @@ const PendingRoleRequestsButtons: FC<Props> = (props) => {
<DeleteButton
className={`role-request-reject-${roleRequest.id} p-0`}
confirmMessage={t(translations.rejectConfirm, {
role: ROLE_REQUEST_ROLES[roleRequest.role!],
role: t(instanceRoleTranslations[roleRequest.role!]),
name: roleRequest.name,
email: roleRequest.email,
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import { InstanceUserMiniEntity } from 'types/system/instance/users';

import DeleteButton from 'lib/components/core/buttons/DeleteButton';
import { PromptText } from 'lib/components/core/dialogs/Prompt';
import { USER_ROLES } from 'lib/constants/sharedConstants';
import { useAppDispatch } from 'lib/hooks/store';
import toast from 'lib/hooks/toast';
import useTranslation from 'lib/hooks/useTranslation';
import instanceRoleTranslations from 'lib/translations/instance/users/roles';

import { deleteUser } from '../../operations';

Expand Down Expand Up @@ -78,7 +78,7 @@ const UserManagementButtons: FC<Props> = (props) => {
loading={isDeleting}
onClick={onDelete}
title={t(translations.deletionConfirmTitle, {
role: USER_ROLES[user.role],
role: t(instanceRoleTranslations[user.role]),
name: user.name,
email: user.email,
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,22 @@ import {
UseFieldArrayAppend,
UseFieldArrayRemove,
} from 'react-hook-form';
import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl';
import { defineMessages } from 'react-intl';
import { Close } from '@mui/icons-material';
import { Box, Grid, IconButton, Tooltip } from '@mui/material';
import {
IndividualInvite,
IndividualInvites,
} from 'types/system/instance/invitations';
import { INSTANCE_USER_ROLES } from 'types/system/instance/users';

import FormSelectField from 'lib/components/form/fields/SelectField';
import FormTextField from 'lib/components/form/fields/TextField';
import { INSTANCE_USER_ROLES } from 'lib/constants/sharedConstants';
import useTranslation from 'lib/hooks/useTranslation';
import instanceRoleTranslations from 'lib/translations/instance/users/roles';
import tableTranslations from 'lib/translations/table';

interface Props extends WrappedComponentProps {
interface Props {
fieldsConfig: {
control: Control<IndividualInvites>;
fields: IndividualInvite[];
Expand All @@ -43,13 +45,14 @@ const translations = defineMessages({
},
});

const userRoleOptions = Object.keys(INSTANCE_USER_ROLES).map((roleValue) => ({
label: INSTANCE_USER_ROLES[roleValue],
value: roleValue,
}));

const IndividualInvitation: FC<Props> = (props) => {
const { fieldsConfig, index, intl } = props;
const { fieldsConfig, index } = props;
const { t } = useTranslation();

const userRoleOptions = INSTANCE_USER_ROLES.map((roleValue) => ({
label: t(instanceRoleTranslations[roleValue]),
value: roleValue,
}));

const renderInvitationBody = (
<Grid alignItems="center" container flexWrap="nowrap">
Expand All @@ -62,8 +65,8 @@ const IndividualInvitation: FC<Props> = (props) => {
fieldState={fieldState}
fullWidth
id={`name-${index}`}
label={intl.formatMessage(tableTranslations.name)}
placeholder={intl.formatMessage(translations.namePlaceholder)}
label={t(tableTranslations.name)}
placeholder={t(translations.namePlaceholder)}
variant="standard"
/>
)}
Expand All @@ -77,8 +80,8 @@ const IndividualInvitation: FC<Props> = (props) => {
fieldState={fieldState}
fullWidth
id={`email-${index}`}
label={intl.formatMessage(tableTranslations.email)}
placeholder={intl.formatMessage(translations.emailPlaceholder)}
label={t(tableTranslations.email)}
placeholder={t(translations.emailPlaceholder)}
variant="standard"
/>
)}
Expand All @@ -90,7 +93,7 @@ const IndividualInvitation: FC<Props> = (props) => {
<FormSelectField
field={field}
fieldState={fieldState}
label={intl.formatMessage(tableTranslations.role)}
label={t(tableTranslations.role)}
options={userRoleOptions}
/>
)}
Expand All @@ -101,7 +104,7 @@ const IndividualInvitation: FC<Props> = (props) => {
return (
<Box key={index} className="flex items-center justify-start">
{renderInvitationBody}
<Tooltip title={intl.formatMessage(translations.removeInvitation)}>
<Tooltip title={t(translations.removeInvitation)}>
<IconButton
className="p-3"
onClick={(): void => fieldsConfig.remove(index)}
Expand All @@ -113,4 +116,4 @@ const IndividualInvitation: FC<Props> = (props) => {
);
};

export default injectIntl(IndividualInvitation);
export default IndividualInvitation;
Loading