feat: Limit of members that can be added using the autojoin feature in a team's channel to the value of the API_Users_Limit setting (#31859)

Co-authored-by: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com>
pull/32589/head^2
Gustavo Reis Bauer 2 years ago committed by GitHub
parent 972ba42f43
commit 16b67aa0ff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 6
      .changeset/clean-moose-cover.md
  2. 5
      apps/meteor/client/views/teams/contextualBar/channels/TeamsChannelItem.tsx
  3. 4
      apps/meteor/client/views/teams/contextualBar/channels/TeamsChannelItemMenu.tsx
  4. 6
      apps/meteor/client/views/teams/contextualBar/channels/TeamsChannels.tsx
  5. 1
      apps/meteor/client/views/teams/contextualBar/channels/TeamsChannelsWithData.tsx
  6. 31
      apps/meteor/client/views/teams/contextualBar/channels/hooks/useToggleAutoJoin.tsx
  7. 17
      apps/meteor/server/services/team/service.ts
  8. 13
      apps/meteor/tests/data/teams.helper.ts
  9. 79
      apps/meteor/tests/end-to-end/api/25-teams.js
  10. 2
      packages/i18n/src/locales/en.i18n.json

@ -0,0 +1,6 @@
---
"@rocket.chat/meteor": minor
"@rocket.chat/i18n": minor
---
Introduced the use of the `API_User_Limit` setting to limit amount of members to simultaneously auto-join a room in a team

@ -22,11 +22,12 @@ import TeamsChannelItemMenu from './TeamsChannelItemMenu';
type TeamsChannelItemProps = {
room: IRoom;
mainRoom: IRoom;
onClickView: (room: IRoom) => void;
reload: () => void;
};
const TeamsChannelItem = ({ room, onClickView, reload }: TeamsChannelItemProps) => {
const TeamsChannelItem = ({ room, mainRoom, onClickView, reload }: TeamsChannelItemProps) => {
const t = useTranslation();
const rid = room._id;
const type = room.t;
@ -68,7 +69,7 @@ const TeamsChannelItem = ({ room, onClickView, reload }: TeamsChannelItemProps)
</OptionContent>
{(canRemoveTeamChannel || canEditTeamChannel || canDeleteTeamChannel) && (
<OptionMenu onClick={onClick}>
{showButton ? <TeamsChannelItemMenu room={room} reload={reload} /> : <IconButton tiny icon='kebab' />}
{showButton ? <TeamsChannelItemMenu room={room} mainRoom={mainRoom} reload={reload} /> : <IconButton tiny icon='kebab' />}
</OptionMenu>
)}
</Option>

@ -9,12 +9,12 @@ import { useDeleteRoom } from '../../../hooks/roomActions/useDeleteRoom';
import { useRemoveRoomFromTeam } from './hooks/useRemoveRoomFromTeam';
import { useToggleAutoJoin } from './hooks/useToggleAutoJoin';
const TeamsChannelItemMenu = ({ room, reload }: { room: IRoom; reload?: () => void }) => {
const TeamsChannelItemMenu = ({ room, mainRoom, reload }: { room: IRoom; mainRoom: IRoom; reload?: () => void }) => {
const t = useTranslation();
const { handleRemoveRoom, canRemoveTeamChannel } = useRemoveRoomFromTeam(room, { reload });
const { handleDelete, canDeleteRoom } = useDeleteRoom(room, { reload });
const { handleToggleAutoJoin, canEditTeamChannel } = useToggleAutoJoin(room, { reload });
const { handleToggleAutoJoin, canEditTeamChannel } = useToggleAutoJoin(room, { reload, mainRoom });
const toggleAutoJoin = {
id: 'toggleAutoJoin',

@ -23,6 +23,7 @@ import TeamsChannelItem from './TeamsChannelItem';
type TeamsChannelsProps = {
loading: boolean;
channels: IRoom[];
mainRoom: IRoom;
text: string;
type: 'all' | 'autoJoin';
setType: Dispatch<SetStateAction<'all' | 'autoJoin'>>;
@ -39,6 +40,7 @@ type TeamsChannelsProps = {
const TeamsChannels = ({
loading,
channels = [],
mainRoom,
text,
type,
setText,
@ -123,7 +125,9 @@ const TeamsChannels = ({
data={channels}
// eslint-disable-next-line react/no-multi-comp
components={{ Scroller: VirtuosoScrollbars, Footer: () => <InfiniteListAnchor loadMore={loadMoreChannels} /> }}
itemContent={(index, data) => <TeamsChannelItem onClickView={onClickView} room={data} reload={reload} key={index} />}
itemContent={(index, data) => (
<TeamsChannelItem onClickView={onClickView} room={data} mainRoom={mainRoom} reload={reload} key={index} />
)}
/>
</Box>
</>

@ -54,6 +54,7 @@ const TeamsChannelsWithData = () => {
return (
<TeamsChannels
loading={phase === AsyncStatePhase.LOADING}
mainRoom={room}
type={type}
text={text}
setType={setType}

@ -1,18 +1,41 @@
import type { IRoom } from '@rocket.chat/core-typings';
import { useEndpoint, usePermission, useToastMessageDispatch } from '@rocket.chat/ui-contexts';
import { useEndpoint, usePermission, useSetting, useToastMessageDispatch } from '@rocket.chat/ui-contexts';
import { t } from 'i18next';
export const useToggleAutoJoin = (room: IRoom, { reload }: { reload?: () => void }) => {
import { roomCoordinator } from '../../../../../lib/rooms/roomCoordinator';
export const useToggleAutoJoin = (room: IRoom, { reload, mainRoom }: { reload?: () => void; mainRoom: IRoom }) => {
const dispatchToastMessage = useToastMessageDispatch();
const updateRoomEndpoint = useEndpoint('POST', '/v1/teams.updateRoom');
const canEditTeamChannel = usePermission('edit-team-channel', room._id);
const maxNumberOfAutoJoinMembers = useSetting<number>('API_User_Limit');
const handleToggleAutoJoin = async () => {
// Sanity check, the setting has a default value, therefore it should always be defined
if (!maxNumberOfAutoJoinMembers) {
return;
}
try {
await updateRoomEndpoint({
const { room: updatedRoom } = await updateRoomEndpoint({
roomId: room._id,
isDefault: !room.teamDefault,
});
dispatchToastMessage({ type: 'success', message: room.teamDefault ? 'channel set as non autojoin' : 'channel set as autojoin' });
if (updatedRoom.teamDefault) {
// If the number of members in the mainRoom (the team) is greater than the limit, show an info message
// informing that not all members will be auto-joined to the channel
const messageType = mainRoom.usersCount > maxNumberOfAutoJoinMembers ? 'info' : 'success';
const message = mainRoom.usersCount > maxNumberOfAutoJoinMembers ? 'Team_Auto-join_exceeded_user_limit' : 'Team_Auto-join_updated';
dispatchToastMessage({
type: messageType,
message: t(message, {
channelName: roomCoordinator.getRoomName(room.t, room),
numberOfMembers: updatedRoom.usersCount,
limit: maxNumberOfAutoJoinMembers,
}),
});
}
reload?.();
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });

@ -32,6 +32,7 @@ import { addUserToRoom } from '../../../app/lib/server/functions/addUserToRoom';
import { checkUsernameAvailability } from '../../../app/lib/server/functions/checkUsernameAvailability';
import { getSubscribedRoomsForUserWithDetails } from '../../../app/lib/server/functions/getRoomsWithSingleOwner';
import { removeUserFromRoom } from '../../../app/lib/server/functions/removeUserFromRoom';
import { settings } from '../../../app/settings/server';
export class TeamService extends ServiceClassInternal implements ITeamService {
protected name = 'team';
@ -472,11 +473,21 @@ export class TeamService extends ServiceClassInternal implements ITeamService {
room.teamDefault = isDefault;
await Rooms.setTeamDefaultById(rid, isDefault);
if (room.teamDefault) {
const teamMembers = await this.members(uid, room.teamId, true, undefined, undefined);
if (isDefault) {
const maxNumberOfAutoJoinMembers = settings.get<number>('API_User_Limit');
const teamMembers = await this.members(
uid,
room.teamId,
true,
{ offset: 0, count: maxNumberOfAutoJoinMembers },
// We should not get the owner of the room, since he is already a member
{ _id: { $ne: room.u._id } },
);
for await (const m of teamMembers.records) {
await addUserToRoom(room._id, m.user, user);
if (await addUserToRoom(room._id, m.user, user)) {
room.usersCount++;
}
}
}

@ -1,5 +1,6 @@
import { ITeam, TEAM_TYPE } from "@rocket.chat/core-typings"
import { api, request } from "./api-data"
import { IUser } from "@rocket.chat/apps-engine/definition/users";
export const createTeam = async (credentials: Record<string, any>, teamName: string, type: TEAM_TYPE): Promise<ITeam> => {
const response = await request.post(api('teams.create')).set(credentials).send({
@ -14,4 +15,14 @@ export const deleteTeam = async (credentials: Record<string, any>, teamName: str
await request.post(api('teams.delete')).set(credentials).send({
teamName,
});
};
};
export const addMembers = async (credentials: Record<string, any>, teamName: string, members: IUser['id'][]): Promise<void> => {
await request
.post(api('teams.addMembers'))
.set(credentials)
.send({
teamName: teamName,
members: members.map((userId) => ({ userId, roles: ['member'] }))
});
};

@ -1,11 +1,11 @@
import { TEAM_TYPE } from '@rocket.chat/core-typings';
import { expect } from 'chai';
import { before, describe, it, after } from 'mocha';
import { after, afterEach, before, beforeEach, describe, it } from 'mocha';
import { getCredentials, api, request, credentials, methodCall } from '../../data/api-data';
import { updatePermission } from '../../data/permissions.helper';
import { updatePermission, updateSetting } from '../../data/permissions.helper';
import { createRoom, deleteRoom } from '../../data/rooms.helper';
import { createTeam, deleteTeam } from '../../data/teams.helper';
import { addMembers, createTeam, deleteTeam } from '../../data/teams.helper';
import { adminUsername, password } from '../../data/user';
import { createUser, deleteUser, login } from '../../data/users.helper';
@ -1594,6 +1594,79 @@ describe('[Teams]', () => {
.end(done);
});
});
describe('team auto-join', () => {
let testTeam;
let createdRoom;
let testUser1;
let testUser2;
before(async () => {
const [testUserResult, testUser1Result] = await Promise.all([createUser(), createUser()]);
testUser1 = testUserResult;
testUser2 = testUser1Result;
});
beforeEach(async () => {
const createTeamPromise = createTeam(credentials, `test-team-name${Date.now()}`, 0);
const createRoomPromise = createRoom({ name: `test-room-name${Date.now()}`, type: 'c' });
const [testTeamCreationResult, testRoomCreationResult] = await Promise.all([createTeamPromise, createRoomPromise]);
testTeam = testTeamCreationResult;
createdRoom = testRoomCreationResult;
await request
.post(api('teams.addRooms'))
.set(credentials)
.expect(200)
.send({
rooms: [createdRoom.body.channel._id],
teamName: testTeam.name,
});
});
afterEach(() =>
Promise.all([deleteTeam(credentials, testTeam.name), deleteRoom({ roomId: createdRoom.body.channel._id, type: 'c' })]),
);
after(() => Promise.all([updateSetting('API_User_Limit', 500), deleteUser(testUser1), deleteUser(testUser2)]));
it('should add members when the members count is less than or equal to the API_User_Limit setting', async () => {
await updateSetting('API_User_Limit', 2);
await addMembers(credentials, testTeam.name, [testUser1._id, testUser2._id]);
await request
.post(api('teams.updateRoom'))
.set(credentials)
.send({
roomId: createdRoom.body.channel._id,
isDefault: true,
})
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('room.usersCount').and.to.be.equal(3);
});
});
it('should not add all members when we update a team channel to be auto-join and the members count is greater than the API_User_Limit setting', async () => {
await updateSetting('API_User_Limit', 1);
await addMembers(credentials, testTeam.name, [testUser1._id, testUser2._id]);
await request
.post(api('teams.updateRoom'))
.set(credentials)
.send({
roomId: createdRoom.body.channel._id,
isDefault: true,
})
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('room.usersCount').and.to.be.equal(2);
});
});
});
});
describe('/teams.removeRoom', () => {

@ -5127,6 +5127,8 @@
"Team_Add_existing_channels": "Add Existing Channels",
"Team_Add_existing": "Add Existing",
"Team_Auto-join": "Auto-join",
"Team_Auto-join_exceeded_user_limit": "Auto-join has a limit of {{limit}} members, #{{channelName}} now has {{numberOfMembers}} members",
"Team_Auto-join_updated": "#{{channelName}} now has {{numberOfMembers}} members",
"Team_Channels": "Team Channels",
"Team_Delete_Channel_modal_content_danger": "This can’t be undone.",
"Team_Delete_Channel_modal_content": "Would you like to delete this Channel?",

Loading…
Cancel
Save