[NEW] Add `teams.convertToChannel` endpoint (#22188)

* Add teams.convertToChannel endpoint and update ConvertToTeam modal text

* Add tests and replace Promise.await
pull/22247/head
Matheus Barbosa Silva 4 years ago committed by GitHub
parent 8f114b1595
commit 3ad7b11ba3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 43
      app/api/server/v1/teams.ts
  2. 1
      app/authorization/server/startup.js
  3. 1
      app/models/server/raw/Rooms.js
  4. 6
      app/models/server/raw/TeamMember.ts
  5. 6
      client/views/room/contextualBar/Info/ConvertToTeamModal.js
  6. 1
      packages/rocketchat-i18n/i18n/ca.i18n.json
  7. 2
      packages/rocketchat-i18n/i18n/en.i18n.json
  8. 1
      packages/rocketchat-i18n/i18n/es.i18n.json
  9. 1
      packages/rocketchat-i18n/i18n/nl.i18n.json
  10. 1
      packages/rocketchat-i18n/i18n/ru.i18n.json
  11. 3
      server/sdk/types/ITeamService.ts
  12. 10
      server/services/team/service.ts
  13. 157
      tests/end-to-end/api/25-teams.js

@ -71,6 +71,46 @@ API.v1.addRoute('teams.create', { authRequired: true }, {
},
});
API.v1.addRoute('teams.convertToChannel', { authRequired: true }, {
post() {
check(this.bodyParams, Match.ObjectIncluding({
teamId: Match.Maybe(String),
teamName: Match.Maybe(String),
roomsToRemove: Match.Maybe([String]),
}));
const { roomsToRemove, teamId, teamName } = this.bodyParams;
if (!teamId && !teamName) {
return API.v1.failure('missing-teamId-or-teamName');
}
const team = teamId ? Promise.await(Team.getOneById(teamId)) : Promise.await(Team.getOneByName(teamName));
if (!team) {
return API.v1.failure('team-does-not-exist');
}
if (!hasPermission(this.userId, 'convert-team', team.roomId)) {
return API.v1.unauthorized();
}
const rooms: string[] = Promise.await(Team.getMatchingTeamRooms(team._id, roomsToRemove));
if (rooms.length) {
rooms.forEach((room) => {
Meteor.call('eraseRoom', room);
});
}
Promise.all([
Team.unsetTeamIdOfRooms(team._id),
Team.removeAllMembersFromTeam(team._id),
Team.deleteById(team._id),
]);
return API.v1.success();
},
});
API.v1.addRoute('teams.addRooms', { authRequired: true }, {
post() {
const { rooms, teamId, teamName } = this.bodyParams;
@ -381,6 +421,9 @@ API.v1.addRoute('teams.delete', { authRequired: true }, {
// Move every other room back to the workspace
Promise.await(Team.unsetTeamIdOfRooms(team._id));
// Delete all team memberships
Team.removeAllMembersFromTeam(teamId);
// And finally delete the team itself
Promise.await(Team.deleteById(team._id));

@ -128,6 +128,7 @@ Meteor.startup(function() {
{ _id: 'message-impersonate', roles: ['bot', 'app'] },
{ _id: 'create-team', roles: ['admin', 'user'] },
{ _id: 'delete-team', roles: ['admin', 'owner'] },
{ _id: 'convert-team', roles: ['admin', 'owner'] },
{ _id: 'edit-team', roles: ['admin', 'owner'] },
{ _id: 'add-team-member', roles: ['admin', 'owner', 'moderator'] },
{ _id: 'edit-team-member', roles: ['admin', 'owner', 'moderator'] },

@ -207,6 +207,7 @@ export class RoomsRaw extends BaseRaw {
$unset: {
teamId: '',
teamDefault: '',
teamMain: '',
},
};

@ -106,4 +106,10 @@ export class TeamMemberRaw extends BaseRaw<T> {
userId,
});
}
deleteByTeamId(teamId: string): Promise<DeleteWriteOpResultObject> {
return this.col.deleteMany({
teamId,
});
}
}

@ -1,4 +1,3 @@
import { Box } from '@rocket.chat/fuselage';
import React from 'react';
import GenericModal from '../../../../components/GenericModal';
@ -16,10 +15,7 @@ const ChannelToTeamModal = ({ onClose, onConfirm }) => {
onConfirm={onConfirm}
confirmText={t('Convert')}
>
<Box is='span' fontWeight='600' color='danger-500'>
{t('This_cant_be_undone')}
</Box>
{` ${t('Once_you_convert_a_channel_to_team')}`}
{` ${t('Converting_channel_to_a_team')}`}
</GenericModal>
);
};

@ -3030,7 +3030,6 @@
"Old Colors": "Colors antics",
"Old Colors (minor)": "Colors antics (menors)",
"Older_than": "Més antic que",
"Once_you_convert_a_channel_to_team": "Una vegada que converteixi una cadena en un equip, no podrà tornar a convertir-lo en un Channel",
"Omnichannel": "LiveChat",
"Omnichannel_Directory": "Directori de LiveChat",
"Omnichannel_appearance": "Aparença de LiveChat",

@ -963,6 +963,7 @@
"Conversations_per_day": "Conversations per Day",
"Convert": "Convert",
"Convert_Ascii_Emojis": "Convert ASCII to Emoji",
"Converting_channel_to_a_team": "You are converting this Channel to a Team. All members will be kept.",
"Copied": "Copied",
"Copy": "Copy",
"Copy_text": "Copy Text",
@ -3033,7 +3034,6 @@
"Old Colors": "Old Colors",
"Old Colors (minor)": "Old Colors (minor)",
"Older_than": "Older than",
"Once_you_convert_a_channel_to_team": "Once you convert a Channel to a Team, you can not turn it back to a channel.",
"Omnichannel": "Omnichannel",
"Omnichannel_Directory": "Omnichannel Directory",
"Omnichannel_appearance": "Omnichannel Appearance",

@ -3030,7 +3030,6 @@
"Old Colors": "Colores Antiguos",
"Old Colors (minor)": "Colores Antiguos (menores)",
"Older_than": "Más antiguo que",
"Once_you_convert_a_channel_to_team": "Una vez que convierta un canal en un equipo, no podrá volver a convertirlo en un Channel",
"Omnichannel": "LiveChat",
"Omnichannel_Directory": "Directorio de LiveChat",
"Omnichannel_appearance": "Apariencia de LiveChat",

@ -3033,7 +3033,6 @@
"Old Colors": "Oude kleuren",
"Old Colors (minor)": "Oude kleuren (klein)",
"Older_than": "Ouder dan",
"Once_you_convert_a_channel_to_team": "Eenmaal je een kanaal naar een team hebt geconverteerd, kan je het niet meer naar een kanaal terugzetten.",
"Omnichannel": "Omnichannel",
"Omnichannel_Directory": "Omnichannel-directory",
"Omnichannel_appearance": "Omnichannel-uiterlijk",

@ -3028,7 +3028,6 @@
"Old Colors": "Старый цвет",
"Old Colors (minor)": "Старые цвета (второстепенные)",
"Older_than": "Старше, чем",
"Once_you_convert_a_channel_to_team": "Преобразовав чат в Команду, вы не сможете преобразовать его обратно в чат.",
"Omnichannel": "Настройки Omnichannel",
"Omnichannel_Directory": "Каталог Omnichannel",
"Omnichannel_appearance": "Внешний вид Omnichannel",

@ -84,4 +84,7 @@ export interface ITeamService {
getMembersByTeamIds(teamIds: Array<string>, options: FindOneOptions<ITeamMember>): Promise<Array<ITeamMember>>;
update(uid: string, teamId: string, updateData: ITeamUpdateData): Promise<void>;
listTeamsBySubscriberUserId(uid: string, options?: FindOneOptions<ITeamMember>): Promise<Array<ITeamMember> | null>;
insertMemberOnTeams(userId: string, teamIds: Array<string>): Promise<void>;
removeMemberFromTeams(userId: string, teamIds: Array<string>): Promise<void>;
removeAllMembersFromTeam(teamId: string): Promise<void>;
}

@ -654,6 +654,16 @@ export class TeamService extends ServiceClass implements ITeamService {
}));
}
async removeAllMembersFromTeam(teamId: string): Promise<void> {
const team = await this.TeamModel.findOneById(teamId);
if (!team) {
return;
}
await this.TeamMembersModel.deleteByTeamId(team._id);
}
async addMember(inviter: IUser, userId: string, teamId: string): Promise<boolean> {
const isAlreadyAMember = await this.TeamMembersModel.findOneByUserIdAndTeamId(userId, teamId, { projection: { _id: 1 } });

@ -136,6 +136,163 @@ describe('[Teams]', () => {
});
});
describe('/teams.convertToChannel', () => {
let testTeam;
let channelToEraseId;
let channelToKeepId;
const teamName = `test-team-convert-to-channel-${ Date.now() }`;
const channelToEraseName = `${ teamName }-channelToErase`;
const channelToKeepName = `${ teamName }-channelToKeep`;
before('Create test team', (done) => {
request.post(api('teams.create'))
.set(credentials)
.send({
name: teamName,
type: 1,
})
.end((err, res) => {
testTeam = res.body.team;
done();
});
});
before('create channel (to erase after its team is converted to a channel)', (done) => {
request.post(api('channels.create'))
.set(credentials)
.send({
name: channelToEraseName,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
channelToEraseId = res.body.channel._id;
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('channel._id');
expect(res.body).to.have.nested.property('channel.name', channelToEraseName);
expect(res.body).to.have.nested.property('channel.t', 'c');
expect(res.body).to.have.nested.property('channel.msgs', 0);
})
.then(() => done());
});
before('add first channel to team', (done) => {
request.post(api('teams.addRooms'))
.set(credentials)
.send({
rooms: [channelToEraseId],
teamId: testTeam._id,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('rooms');
expect(res.body.rooms[0]).to.have.property('teamId', testTeam._id);
expect(res.body.rooms[0]).to.not.have.property('teamDefault');
})
.then(() => done())
.catch(done);
});
before('create channel (to keep after its team is converted to a channel)', (done) => {
request.post(api('channels.create'))
.set(credentials)
.send({
name: channelToKeepName,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
channelToKeepId = res.body.channel._id;
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('channel._id');
expect(res.body).to.have.nested.property('channel.name', channelToKeepName);
expect(res.body).to.have.nested.property('channel.t', 'c');
expect(res.body).to.have.nested.property('channel.msgs', 0);
})
.then(() => done());
});
before('add second channel to team', (done) => {
request.post(api('teams.addRooms'))
.set(credentials)
.send({
rooms: [channelToKeepId],
teamId: testTeam._id,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('rooms');
expect(res.body.rooms[0]).to.have.property('teamId', testTeam._id);
expect(res.body.rooms[0]).to.not.have.property('teamDefault');
})
.then(() => done());
});
it('should convert the team to a channel, delete the specified room and move the other back to the workspace', (done) => {
request.post(api('teams.convertToChannel'))
.set(credentials)
.send({
teamName,
roomsToRemove: [channelToEraseId],
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
})
.then(() => {
request.get(api('channels.info'))
.set(credentials)
.query({
roomId: channelToEraseId,
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((response) => {
expect(response.body).to.have.property('success', false);
expect(response.body).to.have.property('error');
expect(response.body.error).to.include('[error-room-not-found]');
});
})
.then(() => {
request.get(api('channels.info'))
.set(credentials)
.query({
roomId: channelToKeepId,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((response) => {
expect(response.body).to.have.property('success', true);
expect(response.body).to.have.property('channel');
expect(response.body.channel).to.have.property('_id', channelToKeepId);
expect(response.body.channel).to.not.have.property('teamId');
});
})
.then(() => {
request.get(api('channels.info'))
.set(credentials)
.query({
roomId: testTeam.roomId,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((response) => {
expect(response.body).to.have.property('success', true);
expect(response.body).to.have.property('channel');
expect(response.body.channel).to.have.property('_id', testTeam.roomId);
expect(response.body.channel).to.not.have.property('teamId');
expect(response.body.channel).to.not.have.property('teamMain');
});
})
.then(() => done())
.catch(done);
});
});
describe('/teams.addMembers', () => {
let testTeam;
before('Create test team', (done) => {

Loading…
Cancel
Save