Remove t from invite-related code (#27053)

* Remove t from actions/invite_actions

* Remove t from components/invitation_modal and related code

* Remove t from components/channel_invite_modal

* Re-add the ability for reason to be a string

* Address feedback
Этот коммит содержится в:
Harrison Healey
2024-05-24 12:04:47 -04:00
коммит произвёл GitHub
родитель 497870a565
Коммит 38b5974410
12 изменённых файлов: 468 добавлений и 207 удалений

Просмотреть файл

@@ -37,11 +37,11 @@ jest.mock('mattermost-redux/actions/teams', () => ({
getTeamMembersByIds: () => ({type: 'MOCK_RECEIVED_ME'}),
sendEmailInvitesToTeamGracefully: (team: string, emails: string[]) => {
if (team === 'incorrect-default-smtp') {
return ({type: 'MOCK_RECEIVED_ME', data: emails.map((email) => ({email, error: {message: 'SMTP is not configured in System Console.', id: 'api.team.invite_members.unable_to_send_email_with_defaults.app_error'}}))});
return ({type: 'MOCK_RECEIVED_ME', data: emails.map((email) => ({email, error: {message: '(From server) SMTP is not configured in System Console.', id: 'api.team.invite_members.unable_to_send_email_with_defaults.app_error'}}))});
} else if (emails.length > 21) { // Poor attempt to mock rate limiting.
return ({type: 'MOCK_RECEIVED_ME', data: emails.map((email) => ({email, error: {message: 'Invite emails rate limit exceeded.'}}))});
return ({type: 'MOCK_RECEIVED_ME', data: emails.map((email) => ({email, error: {message: '(From server) Invite emails rate limit exceeded.'}}))});
} else if (team === 'error') {
return ({type: 'MOCK_RECEIVED_ME', data: emails.map((email) => ({email, error: {message: 'Unable to add the user to the team.'}}))});
return ({type: 'MOCK_RECEIVED_ME', data: emails.map((email) => ({email, error: {message: '(From server) Unable to add the user to the team.'}}))});
}
// team === 'correct' i.e no error
@@ -50,11 +50,11 @@ jest.mock('mattermost-redux/actions/teams', () => ({
// eslint-disable-next-line @typescript-eslint/no-unused-vars
sendEmailGuestInvitesToChannelsGracefully: (teamId: string, _channelIds: string[], emails: string[], _message: string) => {
if (teamId === 'incorrect-default-smtp') {
return ({type: 'MOCK_RECEIVED_ME', data: emails.map((email) => ({email, error: {message: 'SMTP is not configured in System Console.', id: 'api.team.invite_members.unable_to_send_email_with_defaults.app_error'}}))});
return ({type: 'MOCK_RECEIVED_ME', data: emails.map((email) => ({email, error: {message: '(From server) SMTP is not configured in System Console.', id: 'api.team.invite_members.unable_to_send_email_with_defaults.app_error'}}))});
} else if (emails.length > 21) { // Poor attempt to mock rate limiting.
return ({type: 'MOCK_RECEIVED_ME', data: emails.map((email) => ({email, error: {message: 'Invite emails rate limit exceeded.'}}))});
return ({type: 'MOCK_RECEIVED_ME', data: emails.map((email) => ({email, error: {message: '(From server) Invite emails rate limit exceeded.'}}))});
} else if (teamId === 'error') {
return ({type: 'MOCK_RECEIVED_ME', data: emails.map((email) => ({email, error: {message: 'Unable to add the guest to the channels.'}}))});
return ({type: 'MOCK_RECEIVED_ME', data: emails.map((email) => ({email, error: {message: '(From server) Unable to add the guest to the channels.'}}))});
}
// teamId === 'correct' i.e no error
@@ -141,15 +141,24 @@ describe('actions/invite_actions', () => {
sent: [
{
email: 'email-one@email-one.com',
reason: 'An invitation email has been sent.',
reason: {
id: 'invite.members.invite-sent',
defaultMessage: 'An invitation email has been sent.',
},
},
{
email: 'email-two@email-two.com',
reason: 'An invitation email has been sent.',
reason: {
id: 'invite.members.invite-sent',
defaultMessage: 'An invitation email has been sent.',
},
},
{
email: 'email-three@email-three.com',
reason: 'An invitation email has been sent.',
reason: {
id: 'invite.members.invite-sent',
defaultMessage: 'An invitation email has been sent.',
},
},
],
},
@@ -165,15 +174,15 @@ describe('actions/invite_actions', () => {
notSent: [
{
email: 'email-one@email-one.com',
reason: 'Unable to add the user to the team.',
reason: '(From server) Unable to add the user to the team.',
},
{
email: 'email-two@email-two.com',
reason: 'Unable to add the user to the team.',
reason: '(From server) Unable to add the user to the team.',
},
{
email: 'email-three@email-three.com',
reason: 'Unable to add the user to the team.',
reason: '(From server) Unable to add the user to the team.',
},
],
},
@@ -192,7 +201,10 @@ describe('actions/invite_actions', () => {
data: {
sent: [
{
reason: 'This member has been added to the team.',
reason: {
id: 'invite.members.added-to-team',
defaultMessage: 'This member has been added to the team.',
},
user: {
id: 'other-user',
roles: 'system_user',
@@ -201,21 +213,30 @@ describe('actions/invite_actions', () => {
],
notSent: [
{
reason: 'This person is already a team member.',
reason: {
id: 'invite.members.already-member',
defaultMessage: 'This person is already a team member.',
},
user: {
id: 'user1',
roles: 'system_user',
},
},
{
reason: 'Contact your admin to make this guest a full member.',
reason: {
id: 'invite.members.user-is-guest',
defaultMessage: 'Contact your admin to make this guest a full member.',
},
user: {
id: 'guest1',
roles: 'system_guest',
},
},
{
reason: 'Contact your admin to make this guest a full member.',
reason: {
id: 'invite.members.user-is-guest',
defaultMessage: 'Contact your admin to make this guest a full member.',
},
user: {
id: 'other-guest',
roles: 'system_guest',
@@ -236,24 +257,44 @@ describe('actions/invite_actions', () => {
const response = await store.dispatch(sendMembersInvites('error', users, []));
expect(response).toEqual({
data: {
sent: [{user: {id: 'other-user', roles: 'system_user'}, reason: 'This member has been added to the team.'}],
sent: [
{
reason: {
id: 'invite.members.added-to-team',
defaultMessage: 'This member has been added to the team.',
},
user: {
id: 'other-user',
roles: 'system_user',
},
},
],
notSent: [
{
reason: 'This person is already a team member.',
reason: {
id: 'invite.members.already-member',
defaultMessage: 'This person is already a team member.',
},
user: {
id: 'user1',
roles: 'system_user',
},
},
{
reason: 'Contact your admin to make this guest a full member.',
reason: {
id: 'invite.members.user-is-guest',
defaultMessage: 'Contact your admin to make this guest a full member.',
},
user: {
id: 'guest1',
roles: 'system_guest',
},
},
{
reason: 'Contact your admin to make this guest a full member.',
reason: {
id: 'invite.members.user-is-guest',
defaultMessage: 'Contact your admin to make this guest a full member.',
},
user: {
id: 'other-guest',
roles: 'system_guest',
@@ -271,7 +312,7 @@ describe('actions/invite_actions', () => {
emails.push('email-' + i + '@example.com');
expectedNotSent.push({
email: 'email-' + i + '@example.com',
reason: 'Invite emails rate limit exceeded.',
reason: '(From server) Invite emails rate limit exceeded.',
});
}
const response = await store.dispatch(sendMembersInvites('correct', [], emails));
@@ -293,7 +334,7 @@ describe('actions/invite_actions', () => {
email: 'email-one@email-one.com',
reason: {
id: 'admin.environment.smtp.smtpFailure',
message: 'SMTP is not configured in System Console. Can be configured <a>here</a>.',
defaultMessage: 'SMTP is not configured in System Console. Can be configured <a>here</a>.',
},
path: ConsolePages.SMTP,
}],
@@ -324,15 +365,24 @@ describe('actions/invite_actions', () => {
sent: [
{
email: 'email-one@email-one.com',
reason: 'An invitation email has been sent.',
reason: {
id: 'invite.guests.added-to-channel',
defaultMessage: 'An invitation email has been sent.',
},
},
{
email: 'email-two@email-two.com',
reason: 'An invitation email has been sent.',
reason: {
id: 'invite.guests.added-to-channel',
defaultMessage: 'An invitation email has been sent.',
},
},
{
email: 'email-three@email-three.com',
reason: 'An invitation email has been sent.',
reason: {
id: 'invite.guests.added-to-channel',
defaultMessage: 'An invitation email has been sent.',
},
},
],
},
@@ -349,15 +399,15 @@ describe('actions/invite_actions', () => {
notSent: [
{
email: 'email-one@email-one.com',
reason: 'Unable to add the guest to the channels.',
reason: '(From server) Unable to add the guest to the channels.',
},
{
email: 'email-two@email-two.com',
reason: 'Unable to add the guest to the channels.',
reason: '(From server) Unable to add the guest to the channels.',
},
{
email: 'email-three@email-three.com',
reason: 'Unable to add the guest to the channels.',
reason: '(From server) Unable to add the guest to the channels.',
},
],
},
@@ -379,7 +429,7 @@ describe('actions/invite_actions', () => {
{
reason: {
id: 'invite.guests.new-member',
message: 'This guest has been added to the team and {count, plural, one {channel} other {channels}}.',
defaultMessage: 'This guest has been added to the team and {count, plural, one {channel} other {channels}}.',
values: {count: channels.length},
},
user: {
@@ -390,7 +440,7 @@ describe('actions/invite_actions', () => {
{
reason: {
id: 'invite.guests.new-member',
message: 'This guest has been added to the team and {count, plural, one {channel} other {channels}}.',
defaultMessage: 'This guest has been added to the team and {count, plural, one {channel} other {channels}}.',
values: {count: channels.length},
},
user: {
@@ -401,14 +451,20 @@ describe('actions/invite_actions', () => {
],
notSent: [
{
reason: 'This person is already a member of the workspace. Invite them as a member instead of a guest.',
reason: {
id: 'invite.members.user-is-not-guest',
defaultMessage: 'This person is already a member of the workspace. Invite them as a member instead of a guest.',
},
user: {
id: 'user1',
roles: 'system_user',
},
},
{
reason: 'This person is already a member of the workspace. Invite them as a member instead of a guest.',
reason: {
id: 'invite.members.user-is-not-guest',
defaultMessage: 'This person is already a member of the workspace. Invite them as a member instead of a guest.',
},
user: {
id: 'other-user',
roles: 'system_user',
@@ -430,14 +486,20 @@ describe('actions/invite_actions', () => {
sent: [],
notSent: [
{
reason: 'This person is already a member of all the channels.',
reason: {
id: 'invite.guests.already-all-channels-member',
defaultMessage: 'This person is already a member of all the channels.',
},
user: {
id: 'guest2',
roles: 'system_guest',
},
},
{
reason: 'This person is already a member of some of the channels.',
reason: {
id: 'invite.guests.already-some-channels-member',
defaultMessage: 'This person is already a member of some of the channels.',
},
user: {
id: 'guest3',
roles: 'system_guest',
@@ -467,7 +529,7 @@ describe('actions/invite_actions', () => {
},
reason: {
id: 'invite.guests.new-member',
message: 'This guest has been added to the team and {count, plural, one {channel} other {channels}}.',
defaultMessage: 'This guest has been added to the team and {count, plural, one {channel} other {channels}}.',
values: {
count: 1,
},
@@ -480,7 +542,7 @@ describe('actions/invite_actions', () => {
},
reason: {
id: 'invite.guests.new-member',
message: 'This guest has been added to the team and {count, plural, one {channel} other {channels}}.',
defaultMessage: 'This guest has been added to the team and {count, plural, one {channel} other {channels}}.',
values: {
count: 1,
},
@@ -489,14 +551,20 @@ describe('actions/invite_actions', () => {
],
notSent: [
{
reason: 'This person is already a member of the workspace. Invite them as a member instead of a guest.',
reason: {
id: 'invite.members.user-is-not-guest',
defaultMessage: 'This person is already a member of the workspace. Invite them as a member instead of a guest.',
},
user: {
id: 'user1',
roles: 'system_user',
},
},
{
reason: 'This person is already a member of the workspace. Invite them as a member instead of a guest.',
reason: {
id: 'invite.members.user-is-not-guest',
defaultMessage: 'This person is already a member of the workspace. Invite them as a member instead of a guest.',
},
user: {
id: 'other-user',
roles: 'system_user',
@@ -520,28 +588,40 @@ describe('actions/invite_actions', () => {
sent: [],
notSent: [
{
reason: 'This person is already a member of the workspace. Invite them as a member instead of a guest.',
reason: {
id: 'invite.members.user-is-not-guest',
defaultMessage: 'This person is already a member of the workspace. Invite them as a member instead of a guest.',
},
user: {
id: 'user1',
roles: 'system_user',
},
},
{
reason: 'Unable to add the guest to the channels.',
reason: {
id: 'invite.guests.unable-to-add-the-user-to-the-channels',
defaultMessage: 'Unable to add the guest to the channels.',
},
user: {
id: 'guest1',
roles: 'system_guest',
},
},
{
reason: 'This person is already a member of the workspace. Invite them as a member instead of a guest.',
reason: {
id: 'invite.members.user-is-not-guest',
defaultMessage: 'This person is already a member of the workspace. Invite them as a member instead of a guest.',
},
user: {
id: 'other-user',
roles: 'system_user',
},
},
{
reason: 'Unable to add the guest to the channels.',
reason: {
id: 'invite.guests.unable-to-add-the-user-to-the-channels',
defaultMessage: 'Unable to add the guest to the channels.',
},
user: {
id: 'other-guest',
roles: 'system_guest',
@@ -559,7 +639,7 @@ describe('actions/invite_actions', () => {
emails.push('email-' + i + '@example.com');
expectedNotSent.push({
email: 'email-' + i + '@example.com',
reason: 'Invite emails rate limit exceeded.',
reason: '(From server) Invite emails rate limit exceeded.',
});
}
@@ -582,7 +662,7 @@ describe('actions/invite_actions', () => {
email: 'email-one@email-one.com',
reason: {
id: 'admin.environment.smtp.smtpFailure',
message: 'SMTP is not configured in System Console. Can be configured <a>here</a>.',
defaultMessage: 'SMTP is not configured in System Console. Can be configured <a>here</a>.',
},
path: ConsolePages.SMTP,
}],

Просмотреть файл

@@ -1,6 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {defineMessage} from 'react-intl';
import type {Channel, ChannelMembership} from '@mattermost/types/channels';
import type {TeamMemberWithError, TeamInviteWithError} from '@mattermost/types/teams';
import type {UserProfile} from '@mattermost/types/users';
@@ -16,11 +18,10 @@ import {isGuest} from 'mattermost-redux/utils/user_utils';
import {addUsersToTeam} from 'actions/team_actions';
import type {InviteResult} from 'components/invitation_modal/result_table';
import type {InviteResults} from 'components/invitation_modal/result_view';
import {ConsolePages} from 'utils/constants';
import {t} from 'utils/i18n';
import {localizeMessage} from 'utils/utils';
export function sendMembersInvites(teamId: string, users: UserProfile[], emails: string[]): ActionFuncAsync<InviteResults> {
return async (dispatch, getState) => {
@@ -34,9 +35,21 @@ export function sendMembersInvites(teamId: string, users: UserProfile[], emails:
for (const user of users) {
const member = getTeamMember(state, teamId, user.id);
if (isGuest(user.roles)) {
notSent.push({user, reason: localizeMessage('invite.members.user-is-guest', 'Contact your admin to make this guest a full member.')});
notSent.push({
user,
reason: defineMessage({
id: 'invite.members.user-is-guest',
defaultMessage: 'Contact your admin to make this guest a full member.',
}),
});
} else if (member) {
notSent.push({user, reason: localizeMessage('invite.members.already-member', 'This person is already a team member.')});
notSent.push({
user,
reason: defineMessage({
id: 'invite.members.already-member',
defaultMessage: 'This person is already a team member.',
}),
});
} else {
usersToAdd.push(user);
}
@@ -52,9 +65,18 @@ export function sendMembersInvites(teamId: string, users: UserProfile[], emails:
for (const userToAdd of usersToAdd) {
const memberWithError = members.find((m: TeamMemberWithError) => m.user_id === userToAdd.id && m.error);
if (memberWithError) {
notSent.push({user: userToAdd, reason: memberWithError.error.message});
notSent.push({
user: userToAdd,
reason: memberWithError.error.message,
});
} else {
sent.push({user: userToAdd, reason: localizeMessage('invite.members.added-to-team', 'This member has been added to the team.')});
sent.push({
user: userToAdd,
reason: defineMessage({
id: 'invite.members.added-to-team',
defaultMessage: 'This member has been added to the team.',
}),
});
}
}
}
@@ -67,17 +89,28 @@ export function sendMembersInvites(teamId: string, users: UserProfile[], emails:
response = {
data: emails.map((email) => ({
email,
error: {error: localizeMessage('invite.members.unable-to-add-the-user-to-the-team', 'Unable to add the user to the team.')},
error: {
error: defineMessage({
id: 'invite.members.unable-to-add-the-user-to-the-team',
defaultMessage: 'Unable to add the user to the team.',
}),
},
})) as unknown as TeamInviteWithError[],
};
}
const invitesWithErrors = response.data || [];
if (response.error) {
if (response.error.server_error_id === 'app.email.rate_limit_exceeded.app_error') {
response.error.message = localizeMessage('invite.rate-limit-exceeded', 'Invite emails rate limit exceeded.');
response.error.message = defineMessage({
id: 'invite.rate-limit-exceeded',
defaultMessage: 'Invite emails rate limit exceeded.',
});
}
for (const email of emails) {
notSent.push({email, reason: response.error.message});
notSent.push({
email,
reason: response.error.message,
});
}
} else {
for (const email of emails) {
@@ -85,16 +118,25 @@ export function sendMembersInvites(teamId: string, users: UserProfile[], emails:
if (inviteWithError && inviteWithError.error.id === 'api.team.invite_members.unable_to_send_email_with_defaults.app_error' && isCurrentUserSystemAdmin(state)) {
notSent.push({
email,
reason: {
id: t('admin.environment.smtp.smtpFailure'),
message: 'SMTP is not configured in System Console. Can be configured <a>here</a>.',
},
reason: defineMessage({
id: 'admin.environment.smtp.smtpFailure',
defaultMessage: 'SMTP is not configured in System Console. Can be configured <a>here</a>.',
}),
path: ConsolePages.SMTP,
});
} else if (inviteWithError) {
notSent.push({email, reason: inviteWithError.error.message});
notSent.push({
email,
reason: inviteWithError.error.message,
});
} else {
sent.push({email, reason: localizeMessage('invite.members.invite-sent', 'An invitation email has been sent.')});
sent.push({
email,
reason: defineMessage({
id: 'invite.members.invite-sent',
defaultMessage: 'An invitation email has been sent.',
}),
});
}
}
}
@@ -114,9 +156,17 @@ export async function sendGuestInviteForUser(
teamId: string,
channels: Channel[],
members: RelationOneToOne<Channel, Record<string, ChannelMembership>>,
) {
): Promise<({sent: InviteResult} | {notSent: InviteResult})> {
if (!isGuest(user.roles)) {
return {notSent: {user, reason: localizeMessage('invite.members.user-is-not-guest', 'This person is already a member of the workspace. Invite them as a member instead of a guest.')}};
return {
notSent: {
user,
reason: defineMessage({
id: 'invite.members.user-is-not-guest',
defaultMessage: 'This person is already a member of the workspace. Invite them as a member instead of a guest.',
}),
},
};
}
let memberOfAll = true;
let memberOfAny = false;
@@ -131,7 +181,15 @@ export async function sendGuestInviteForUser(
}
if (memberOfAll) {
return {notSent: {user, reason: localizeMessage('invite.guests.already-all-channels-member', 'This person is already a member of all the channels.')}};
return {
notSent: {
user,
reason: defineMessage({
id: 'invite.guests.already-all-channels-member',
defaultMessage: 'This person is already a member of all the channels.',
}),
},
};
}
try {
@@ -143,13 +201,40 @@ export async function sendGuestInviteForUser(
}
}
} catch (e) {
return {notSent: {user, reason: localizeMessage('invite.guests.unable-to-add-the-user-to-the-channels', 'Unable to add the guest to the channels.')}};
return {
notSent: {
user,
reason: defineMessage({
id: 'invite.guests.unable-to-add-the-user-to-the-channels',
defaultMessage: 'Unable to add the guest to the channels.',
}),
},
};
}
if (memberOfAny) {
return {notSent: {user, reason: localizeMessage('invite.guests.already-some-channels-member', 'This person is already a member of some of the channels.')}};
return {
notSent: {
user,
reason: defineMessage({
id: 'invite.guests.already-some-channels-member',
defaultMessage: 'This person is already a member of some of the channels.',
}),
},
};
}
return {sent: {user, reason: {id: t('invite.guests.new-member'), message: 'This guest has been added to the team and {count, plural, one {channel} other {channels}}.', values: {count: channels.length}}}};
return {
sent: {
user,
reason: defineMessage({
id: 'invite.guests.new-member',
defaultMessage: 'This guest has been added to the team and {count, plural, one {channel} other {channels}}.',
values: {
count: channels.length,
},
}),
},
};
}
export function sendGuestsInvites(
@@ -167,10 +252,10 @@ export function sendGuestsInvites(
const results = await Promise.all(users.map((user) => sendGuestInviteForUser(dispatch, user, teamId, channels, members)));
for (const result of results) {
if (result.sent) {
if ('sent' in result && result.sent) {
sent.push(result.sent);
}
if (result.notSent) {
if ('notSent' in result && result.notSent) {
notSent.push(result.notSent);
}
}
@@ -183,17 +268,28 @@ export function sendGuestsInvites(
response = {
data: emails.map((email) => ({
email,
error: {error: localizeMessage('invite.guests.unable-to-add-the-user-to-the-channels', 'Unable to add the guest to the channels.')},
error: {
error: defineMessage({
id: 'invite.guests.unable-to-add-the-user-to-the-channels',
defaultMessage: 'Unable to add the guest to the channels.',
}),
},
})) as unknown as TeamInviteWithError[],
};
}
if (response.error) {
if (response.error.server_error_id === 'app.email.rate_limit_exceeded.app_error') {
response.error.message = localizeMessage('invite.rate-limit-exceeded', 'Invite emails rate limit exceeded.');
response.error.message = defineMessage({
id: 'invite.rate-limit-exceeded',
defaultMessage: 'Invite emails rate limit exceeded.',
});
}
for (const email of emails) {
notSent.push({email, reason: response.error.message});
notSent.push({
email,
reason: response.error.message,
});
}
} else {
for (const res of (response.data || [])) {
@@ -201,22 +297,36 @@ export function sendGuestsInvites(
if (res.error.id === 'api.team.invite_members.unable_to_send_email_with_defaults.app_error' && isCurrentUserSystemAdmin(state)) {
notSent.push({
email: res.email,
reason: {
id: t('admin.environment.smtp.smtpFailure'),
message: 'SMTP is not configured in System Console. Can be configured <a>here</a>.',
},
reason: defineMessage({
id: 'admin.environment.smtp.smtpFailure',
defaultMessage: 'SMTP is not configured in System Console. Can be configured <a>here</a>.',
}),
path: ConsolePages.SMTP,
});
} else {
notSent.push({email: res.email, reason: res.error.message});
notSent.push({
email: res.email,
reason: res.error.message,
});
}
} else {
sent.push({email: res.email, reason: localizeMessage('invite.guests.added-to-channel', 'An invitation email has been sent.')});
sent.push({
email: res.email,
reason: defineMessage({
id: 'invite.guests.added-to-channel',
defaultMessage: 'An invitation email has been sent.',
}),
});
}
}
}
}
return {data: {sent, notSent}};
return {
data: {
sent,
notSent,
},
};
};
}
@@ -240,9 +350,21 @@ export function sendMembersInvitesToChannels(
for (const user of users) {
const member = getTeamMember(state, teamId, user.id);
if (isGuest(user.roles)) {
notSent.push({user, reason: localizeMessage('invite.members.user-is-guest', 'Contact your admin to make this guest a full member.')});
notSent.push({
user,
reason: defineMessage({
id: 'invite.members.user-is-guest',
defaultMessage: 'Contact your admin to make this guest a full member.',
}),
});
} else if (member) {
notSent.push({user, reason: localizeMessage('invite.members.already-member', 'This person is already a team member.')});
notSent.push({
user,
reason: defineMessage({
id: 'invite.members.already-member',
defaultMessage: 'This person is already a team member.',
}),
});
} else {
usersToAdd.push(user);
}
@@ -252,15 +374,27 @@ export function sendMembersInvitesToChannels(
const members = response.data || [];
if (response.error) {
for (const userToAdd of usersToAdd) {
notSent.push({user: userToAdd, reason: response.error.message});
notSent.push({
user: userToAdd,
reason: response.error.message,
});
}
} else {
for (const userToAdd of usersToAdd) {
const memberWithError = members.find((m: TeamMemberWithError) => m.user_id === userToAdd.id && m.error);
if (memberWithError) {
notSent.push({user: userToAdd, reason: memberWithError.error.message});
notSent.push({
user: userToAdd,
reason: memberWithError.error.message,
});
} else {
sent.push({user: userToAdd, reason: localizeMessage('invite.members.added-to-team', 'This member has been added to the team.')});
sent.push({
user: userToAdd,
reason: defineMessage({
id: 'invite.members.added-to-team',
defaultMessage: 'This member has been added to the team.',
}),
});
}
}
}
@@ -280,17 +414,25 @@ export function sendMembersInvitesToChannels(
response = {
data: emails.map((email) => ({
email,
error: {error: localizeMessage('invite.members.unable-to-add-the-user-to-the-team', 'Unable to add the user to the team.')},
error: {
error: defineMessage({
id: 'invite.members.unable-to-add-the-user-to-the-team',
defaultMessage: 'Unable to add the user to the team.',
}),
},
})) as unknown as TeamInviteWithError[],
};
}
const invitesWithErrors = response.data || [];
if (response.error) {
if (response.error.server_error_id === 'app.email.rate_limit_exceeded.app_error') {
response.error.message = localizeMessage('invite.rate-limit-exceeded', 'Invite emails rate limit exceeded.');
response.error.message = defineMessage({id: 'invite.rate-limit-exceeded', defaultMessage: 'Invite emails rate limit exceeded.'});
}
for (const email of emails) {
notSent.push({email, reason: response.error.message});
notSent.push({
email,
reason: response.error.message,
});
}
} else {
for (const email of emails) {
@@ -299,17 +441,26 @@ export function sendMembersInvitesToChannels(
if (inviteWithError.error.id === 'api.team.invite_members.unable_to_send_email_with_defaults.app_error' && isCurrentUserSystemAdmin(state)) {
notSent.push({
email,
reason: {
id: t('admin.environment.smtp.smtpFailure'),
message: 'SMTP is not configured in System Console. Can be configured <a>here</a>.',
},
reason: defineMessage({
id: 'admin.environment.smtp.smtpFailure',
defaultMessage: 'SMTP is not configured in System Console. Can be configured <a>here</a>.',
}),
path: ConsolePages.SMTP,
});
} else {
notSent.push({email, reason: inviteWithError.error.message});
notSent.push({
email,
reason: inviteWithError.error.message,
});
}
} else {
sent.push({email, reason: localizeMessage('invite.members.invite-sent', 'An invitation email has been sent.')});
sent.push({
email,
reason: defineMessage({
id: 'invite.members.invite-sent',
defaultMessage: 'An invitation email has been sent.',
}),
});
}
}
}

Просмотреть файл

@@ -15,8 +15,6 @@ import AtMention from 'components/at_mention';
import type {Value} from 'components/multiselect/multiselect';
import SimpleTooltip from 'components/widgets/simple_tooltip';
import {t} from 'utils/i18n';
type UserProfileValue = Value & UserProfile;
export type Props = {
@@ -53,7 +51,7 @@ const TeamWarningBanner = (props: Props) => {
return (
formatMessage(
{
id: t('channel_invite.invite_team_members.guests.messageOverflow'),
id: 'channel_invite.invite_team_members.guests.messageOverflow',
defaultMessage: '{firstUser} and {others} are guest users and need to first be invited to the team before you can add them to the channel. Once they\'ve joined the team, you can add them to this channel.',
},
{
@@ -98,7 +96,7 @@ const TeamWarningBanner = (props: Props) => {
return (
formatMessage(
{
id: t('channel_invite.invite_team_members.guests.message'),
id: 'channel_invite.invite_team_members.guests.message',
defaultMessage: '{count, plural, =1 {{firstUser} is a guest user and needs} other {{users} are guest users and need}} to first be invited to the team before you can add them to the channel. Once they\'ve joined the team, you can add them to this channel.',
},
{
@@ -123,7 +121,7 @@ const TeamWarningBanner = (props: Props) => {
if (users.length > 10) {
return formatMessage(
{
id: t('channel_invite.invite_team_members.messageOverflow'),
id: 'channel_invite.invite_team_members.messageOverflow',
defaultMessage: 'You can add {firstUser} and {others} to this channel once they are members of the {team} team.',
},
{
@@ -168,7 +166,7 @@ const TeamWarningBanner = (props: Props) => {
return (
formatMessage(
{
id: t('channel_invite.invite_team_members.message'),
id: 'channel_invite.invite_team_members.message',
defaultMessage: 'You can add {count, plural, =1 {{firstUser}} other {{users}}} to this channel once they are members of the {team} team.',
},
{

Просмотреть файл

@@ -16,7 +16,7 @@ import {SelfHostedProducts} from 'utils/constants';
import {TestHelper} from 'utils/test_helper';
import {generateId} from 'utils/utils';
import InvitationModal, {View, InvitationModal as BaseInvitationModal} from './invitation_modal';
import InvitationModal, {View} from './invitation_modal';
import type {Props} from './invitation_modal';
import InviteView from './invite_view';
import NoPermissionsView from './no_permissions_view';
@@ -129,7 +129,7 @@ describe('InvitationModal', () => {
<InvitationModal {...props}/>
</Provider>,
);
wrapper.find(BaseInvitationModal).at(0).setState({view: View.RESULT});
wrapper.find(InvitationModal).at(0).setState({view: View.RESULT});
wrapper.update();
expect(wrapper.find(ResultView).length).toBe(1);

Просмотреть файл

@@ -3,8 +3,7 @@
import React from 'react';
import {Modal} from 'react-bootstrap';
import {injectIntl} from 'react-intl';
import type {IntlShape} from 'react-intl';
import {defineMessages} from 'react-intl';
import type {Channel} from '@mattermost/types/channels';
import type {Team} from '@mattermost/types/teams';
@@ -31,6 +30,17 @@ import './invitation_modal.scss';
// false means no backdrop
type Backdrop = 'static' | boolean
const messages = defineMessages({
notValidChannel: {
id: 'invitation-modal.confirm.not-valid-channel',
defaultMessage: 'Does not match a valid channel name.',
},
notValidUserOrEmail: {
id: 'invitation-modal.confirm.not-valid-user-or-email',
defaultMessage: 'Does not match a valid user or email.',
},
});
export type Props = {
actions: {
searchChannels: (teamId: string, term: string) => Promise<ActionResult<Channel[]>>;
@@ -66,7 +76,6 @@ export type Props = {
isCloud: boolean;
canAddUsers: boolean;
canInviteGuests: boolean;
intl: IntlShape;
onExited: () => void;
channelToInvite?: Channel;
initialValue?: string;
@@ -89,7 +98,7 @@ type State = {
show: boolean;
};
export class InvitationModal extends React.PureComponent<Props, State> {
export default class InvitationModal extends React.PureComponent<Props, State> {
defaultState: State = deepFreeze({
view: View.INVITE,
termWithoutResults: null,
@@ -206,20 +215,14 @@ export class InvitationModal extends React.PureComponent<Props, State> {
if (this.state.invite.usersEmailsSearch !== '') {
invites.notSent.push({
text: this.state.invite.usersEmailsSearch,
reason: this.props.intl.formatMessage({
id: 'invitation-modal.confirm.not-valid-user-or-email',
defaultMessage: 'Does not match a valid user or email.',
}),
reason: messages.notValidUserOrEmail,
});
}
if (inviteAs === InviteType.GUEST && this.state.invite.inviteChannels.search !== '') {
invites.notSent.push({
text: this.state.invite.inviteChannels.search,
reason: this.props.intl.formatMessage({
id: 'invitation-modal.confirm.not-valid-channel',
defaultMessage: 'Does not match a valid channel name.',
}),
reason: messages.notValidChannel,
});
}
@@ -431,5 +434,3 @@ export class InvitationModal extends React.PureComponent<Props, State> {
);
}
}
export default injectIntl(InvitationModal);

Просмотреть файл

@@ -4,7 +4,7 @@
import classNames from 'classnames';
import React, {useEffect, useMemo} from 'react';
import {Modal} from 'react-bootstrap';
import {FormattedMessage, useIntl} from 'react-intl';
import {FormattedMessage, defineMessages, useIntl} from 'react-intl';
import {useSelector} from 'react-redux';
import type {Channel} from '@mattermost/types/channels';
@@ -20,7 +20,6 @@ import {getAnalyticsCategory} from 'components/onboarding_tasks';
import UsersEmailsInput from 'components/widgets/inputs/users_emails_input';
import {Constants} from 'utils/constants';
import {t} from 'utils/i18n';
import {getSiteURL} from 'utils/url';
import {getTrackFlowRole, getRoleForTrackFlow, getSourceForTrackFlow} from 'utils/utils';
@@ -133,42 +132,37 @@ export default function InviteView(props: Props) {
const errorProperties = {
showError: false,
errorMessageId: '',
errorMessageDefault: '',
errorMessage: messages.exceededMaxBatch,
errorMessageValues: {
text: '',
text: Constants.MAX_ADD_MEMBERS_BATCH.toString(),
},
extraErrorText: '',
};
if (props.usersEmails.length > Constants.MAX_ADD_MEMBERS_BATCH) {
errorProperties.showError = true;
errorProperties.errorMessageId = t(
'invitation_modal.invite_members.exceeded_max_add_members_batch',
);
errorProperties.errorMessageDefault = 'No more than **{text}** people can be invited at once';
errorProperties.errorMessageValues.text = Constants.MAX_ADD_MEMBERS_BATCH.toString();
}
let placeholder = formatMessage({
id: 'invite_modal.add_invites',
defaultMessage: 'Enter a name or email address',
});
let noMatchMessageId = t(
'invitation_modal.members.users_emails_input.no_user_found_matching',
);
let noMatchMessageDefault =
'No one found matching **{text}**. Enter their email to invite them.';
if (!props.emailInvitationsEnabled) {
let placeholder;
let noMatchMessage;
if (props.emailInvitationsEnabled) {
placeholder = formatMessage({
id: 'invite_modal.add_invites',
defaultMessage: 'Enter a name or email address',
});
noMatchMessage = messages.noUserFound;
} else {
placeholder = formatMessage({
id: 'invitation_modal.members.search-and-add.placeholder-email-disabled',
defaultMessage: 'Add members',
});
noMatchMessageId = t(
'invitation_modal.members.users_emails_input.no_user_found_matching-email-disabled',
);
noMatchMessageDefault = 'No one found matching **{text}**';
noMatchMessage = messages.noUserFoundEmailDisabled;
}
let validAddressMessage;
if (props.inviteType === InviteType.MEMBER) {
validAddressMessage = messages.validAddressMember;
} else {
validAddressMessage = messages.validAddressGuest;
}
const isInviteValid = useMemo(() => {
@@ -233,12 +227,8 @@ export default function InviteView(props: Props) {
props.onChangeUsersEmails(usersEmails);
}}
value={props.usersEmails}
validAddressMessageId={props.inviteType === InviteType.MEMBER ? t(
'invitation_modal.members.users_emails_input.valid_email',
) : t('invitation_modal.guests.users_emails_input.valid_email')}
validAddressMessageDefault={props.inviteType === InviteType.MEMBER ? 'Invite **{email}** as a team member' : 'Invite **{email}** as a guest'}
noMatchMessageId={noMatchMessageId}
noMatchMessageDefault={noMatchMessageDefault}
validAddressMessage={validAddressMessage}
noMatchMessage={noMatchMessage}
onInputChange={props.onUsersInputChange}
inputValue={props.usersEmailsSearch}
emailInvitationsEnabled={props.emailInvitationsEnabled}
@@ -287,3 +277,26 @@ export default function InviteView(props: Props) {
</>
);
}
const messages = defineMessages({
exceededMaxBatch: {
id: 'invitation_modal.invite_members.exceeded_max_add_members_batch',
defaultMessage: 'No more than **{text}** people can be invited at once',
},
noUserFound: {
id: 'invitation_modal.members.users_emails_input.no_user_found_matching',
defaultMessage: 'No one found matching **{text}**. Enter their email to invite them.',
},
noUserFoundEmailDisabled: {
id: 'invitation_modal.members.users_emails_input.no_user_found_matching-email-disabled',
defaultMessage: 'No one found matching **{text}**',
},
validAddressGuest: {
id: 'invitation_modal.guests.users_emails_input.valid_email',
defaultMessage: 'Invite **{email}** as a guest',
},
validAddressMember: {
id: 'invitation_modal.members.users_emails_input.valid_email',
defaultMessage: 'Invite **{email}** as a team member',
},
});

Просмотреть файл

@@ -73,7 +73,7 @@ describe('ResultTable', () => {
test('emails render as email', () => {
props.rows = [{
email: 'aa@aa.aa',
reason: 'some reason',
reason: {id: 'some_reason', defaultMessage: 'some reason'},
}];
const wrapper = shallow(<ResultTable {...props}/>);
expect(wrapper.find(EmailIcon).length).toBe(1);
@@ -82,7 +82,7 @@ describe('ResultTable', () => {
test('unsent invites render as unsent invites', () => {
props.rows = [{
text: '@incomplete_userna',
reason: 'This was not a complete user',
reason: {id: 'incomplete_user', defaultMessage: 'This was not a complete user'},
}];
const wrapper = shallow(<ResultTable {...props}/>);
expect(wrapper.find(AlertIcon).length).toBe(1);
@@ -91,7 +91,7 @@ describe('ResultTable', () => {
test('user invites render as users', () => {
props.rows = [{
user: defaultUser,
reason: 'added successfuly',
reason: {id: 'success', defaultMessage: 'added successfully'},
}];
const wrapper = shallow(<ResultTable {...props}/>);
expect(wrapper.find(Avatar).length).toBe(1);
@@ -105,7 +105,7 @@ describe('ResultTable', () => {
...defaultUser,
is_bot: true,
},
reason: 'added successfuly',
reason: {id: 'success', defaultMessage: 'added successfully'},
}];
const wrapper = shallow(<ResultTable {...props}/>);
expect(wrapper.find(Avatar).length).toBe(1);
@@ -119,7 +119,7 @@ describe('ResultTable', () => {
...defaultUser,
roles: 'system_guest',
},
reason: 'added successfuly',
reason: {id: 'success', defaultMessage: 'added successfully'},
}];
const wrapper = shallow(<ResultTable {...props}/>);
expect(wrapper.find(Avatar).length).toBe(1);

Просмотреть файл

@@ -32,7 +32,7 @@ type InviteUser = {
type I18nLike = {
id: string;
message: string;
defaultMessage: string;
values?: Record<string, React.ReactNode>;
}
@@ -69,12 +69,9 @@ export default function ResultTable(props: Props) {
);
}
function messageWithLink(reason: any, link: any) {
function messageWithLink(reason: I18nLike, link: string) {
return intl.formatMessage(
{
id: reason.id,
defaultMessage: reason.message,
},
reason,
{
a: (chunks: React.ReactNode | React.ReactNodeArray) => (
<a
@@ -150,21 +147,19 @@ export default function ResultTable(props: Props) {
username = text;
}
let reason: React.ReactNode = invitation.reason;
if (typeof invitation?.reason !== 'string' &&
invitation.reason?.id &&
invitation.reason?.message &&
invitation.reason?.values
) {
let reason;
if (typeof invitation.reason === 'string') {
reason = invitation.reason;
} else if (invitation.path) {
reason = messageWithLink(invitation.reason, invitation.path);
} else {
reason = (
<FormattedMessage
id={invitation.reason.id}
defaultMessage={invitation.reason.message}
defaultMessage={invitation.reason.defaultMessage}
values={invitation.reason.values}
/>
);
} else if (invitation.path && invitation.reason) {
reason = messageWithLink(invitation.reason, invitation.path);
}
return (

Просмотреть файл

@@ -3,12 +3,10 @@
import React from 'react';
import {Modal} from 'react-bootstrap';
import {FormattedMessage, useIntl} from 'react-intl';
import {FormattedMessage} from 'react-intl';
import deepFreeze from 'mattermost-redux/utils/deep_freeze';
import {t} from 'utils/i18n';
import {InviteType} from './invite_as';
import ResultTable from './result_table';
import type {InviteResult} from './result_table';
@@ -40,7 +38,23 @@ type Props = {
} & ResultState;
export default function ResultView(props: Props) {
const {formatMessage} = useIntl();
let inviteType;
if (props.inviteType === InviteType.MEMBER) {
inviteType = (
<FormattedMessage
id='invite_modal.invited_members'
defaultMessage='Members'
/>
);
} else {
inviteType = (
<FormattedMessage
id='invite_modal.invited_guests'
defaultMessage='Guests'
/>
);
}
return (
<>
<Modal.Header className={props.headerClass}>
@@ -52,10 +66,7 @@ export default function ResultView(props: Props) {
id='invite_modal.invited'
defaultMessage='{inviteType} invited to {team_name}'
values={{
inviteType: formatMessage({
id: props.inviteType === InviteType.MEMBER ? t('invite_modal.invited_members') : t('invite_modal.invited_guests'),
defaultMessage: props.inviteType === InviteType.MEMBER ? 'Members' : 'Guests',
}),
inviteType,
team_name: props.currentTeamName,
}}
/>

Просмотреть файл

@@ -2,7 +2,7 @@
// See LICENSE.txt for license information.
import React, {useState, useMemo, useEffect} from 'react';
import {FormattedMessage, useIntl} from 'react-intl';
import {FormattedMessage, defineMessages, useIntl} from 'react-intl';
import {CSSTransition} from 'react-transition-group';
import type {UserProfile} from '@mattermost/types/users';
@@ -10,7 +10,6 @@ import type {UserProfile} from '@mattermost/types/users';
import UsersEmailsInput from 'components/widgets/inputs/users_emails_input';
import {Constants} from 'utils/constants';
import {t} from 'utils/i18n';
import Description from './description';
import InviteMembersLink from './invite_members_link';
@@ -64,10 +63,7 @@ const InviteMembers = (props: Props) => {
});
const errorProperties = {
showError: false,
errorMessageId: t(
'invitation_modal.invite_members.exceeded_max_add_members_batch',
),
errorMessageDefault: 'No more than **{text}** people can be invited at once',
errorMessage: messages.exceededMaxBatch,
errorMessageValues: {
text: Constants.MAX_ADD_MEMBERS_BATCH.toString(),
},
@@ -115,8 +111,7 @@ const InviteMembers = (props: Props) => {
inputValue={email}
emailInvitationsEnabled={true}
autoFocus={true}
validAddressMessageId={t('invitation_modal.members.users_emails_input.valid_email')}
validAddressMessageDefault={'Invite **{email}** as a team member'}
validAddressMessage={messages.validAddress}
suppressNoOptionsMessage={suppressNoOptionsMessage}
/>
);
@@ -242,4 +237,15 @@ const InviteMembers = (props: Props) => {
);
};
const messages = defineMessages({
exceededMaxBatch: {
id: 'invitation_modal.invite_members.exceeded_max_add_members_batch',
defaultMessage: 'No more than **{text}** people can be invited at once',
},
validAddress: {
id: 'invitation_modal.members.users_emails_input.valid_email',
defaultMessage: 'Invite **{email}** as a team member',
},
});
export default InviteMembers;

Просмотреть файл

@@ -25,8 +25,10 @@ describe('components/widgets/inputs/UsersEmailsInput', () => {
last_name: 'user',
} as UserProfile,
]}
errorMessageId='errorMessageId'
errorMessageDefault='errorMessageDefault'
errorMessage={{
id: 'errorMessageId',
defaultMessage: 'errorMessageDefault',
}}
onInputChange={jest.fn()}
inputValue=''
emailInvitationsEnabled={false}

Просмотреть файл

@@ -4,7 +4,8 @@
import classNames from 'classnames';
import React from 'react';
import type {RefObject} from 'react';
import {FormattedMessage} from 'react-intl';
import type {MessageDescriptor} from 'react-intl';
import {FormattedMessage, defineMessages} from 'react-intl';
import {components} from 'react-select';
import type {FormatOptionLabelMeta, InputActionMeta, InputProps, OptionsType, Styles, ValueType} from 'react-select';
import AsyncCreatable from 'react-select/async-creatable';
@@ -24,7 +25,6 @@ import BotTag from 'components/widgets/tag/bot_tag';
import GuestTag from 'components/widgets/tag/guest_tag';
import Avatar from 'components/widgets/users/avatar';
import {t} from 'utils/i18n';
import {getDisplayName, getLongDisplayNameParts, imageURLForUser} from 'utils/utils';
import './users_emails_input.scss';
@@ -37,18 +37,14 @@ type Props = {
onBlur?: () => void;
onChange: (change: Array<UserProfile | string>) => void;
showError?: boolean;
errorMessageId: string;
errorMessageDefault: string;
errorMessage?: MessageDescriptor;
errorMessageValues?: Record<string, React.ReactNode>;
value: Array<UserProfile | string>;
onInputChange: (change: string) => void;
inputValue: string;
noMatchMessageId?: string;
noMatchMessageDefault?: string;
validAddressMessageId?: string;
validAddressMessageDefault?: string;
loadingMessageId?: string;
loadingMessageDefault?: string;
noMatchMessage?: MessageDescriptor;
validAddressMessage?: MessageDescriptor;
loadingMessage?: MessageDescriptor;
emailInvitationsEnabled: boolean;
extraErrorText?: React.ReactNode;
autoFocus?: boolean;
@@ -67,14 +63,26 @@ type State = {
const multipleValuesDelimiter = /[\s,;]+/;
const messages = defineMessages({
loadingDefault: {
id: 'widgets.users_emails_input.loading',
defaultMessage: 'Loading',
},
noMatchDefault: {
id: 'widgets.users_emails_input.no_user_found_matching',
defaultMessage: 'No one found matching **{text}**. Enter their email to invite them.',
},
validAddressDefault: {
id: 'widgets.users_emails_input.valid_email',
defaultMessage: 'Add **{email}**',
},
});
export default class UsersEmailsInput extends React.PureComponent<Props, State> {
static defaultProps = {
noMatchMessageId: t('widgets.users_emails_input.no_user_found_matching'),
noMatchMessageDefault: 'No one found matching **{text}**. Enter their email to invite them.',
validAddressMessageId: t('widgets.users_emails_input.valid_email'),
validAddressMessageDefault: 'Add **{email}**',
loadingMessageId: t('widgets.users_emails_input.loading'),
loadingMessageDefault: 'Loading',
noMatchMessage: messages.noMatchDefault,
validAddress: messages.validAddressDefault,
loadingMessage: messages.loadingDefault,
showError: false,
};
private selectRef: RefObject<AsyncCreatable<UserProfile | EmailInvite> & { handleInputChange: (newValue: string, actionMeta: InputActionMeta | { action: 'custom' }) => string }>;
@@ -110,8 +118,7 @@ export default class UsersEmailsInput extends React.PureComponent<Props, State>
loadingMessage = (): string => {
const text = (
<FormattedMessage
id={this.props.loadingMessageId}
defaultMessage={this.props.loadingMessageDefault}
{...this.props.loadingMessage}
/>
);
@@ -200,8 +207,7 @@ export default class UsersEmailsInput extends React.PureComponent<Props, State>
<MailPlusIcon className='mail-plus-icon'/>
<FormattedMarkdownMessage
key='widgets.users_emails_input.valid_email'
id={this.props.validAddressMessageId}
defaultMessage={this.props.validAddressMessageDefault}
{...this.props.validAddressMessage}
values={{email: value}}
disableLinks={true}
/>
@@ -242,8 +248,7 @@ export default class UsersEmailsInput extends React.PureComponent<Props, State>
<div className='users-emails-input__option users-emails-input__option--no-matches'>
<Msg {...props}>
<FormattedMarkdownMessage
id={this.props.noMatchMessageId}
defaultMessage={this.props.noMatchMessageDefault}
{...this.props.noMatchMessage}
values={{text: inputValue}}
disableLinks={true}
/>
@@ -518,8 +523,7 @@ export default class UsersEmailsInput extends React.PureComponent<Props, State>
<div className='InputErrorBox'>
<Msg>
<FormattedMarkdownMessage
id={this.props.errorMessageId}
defaultMessage={this.props.errorMessageDefault}
{...this.props.errorMessage}
values={this.props.errorMessageValues}
disableLinks={true}
/>