Skip to content

Commit

Permalink
feat: add sidepanel to teams api (#32868)
Browse files Browse the repository at this point in the history
  • Loading branch information
juliajforesti authored Aug 6, 2024
1 parent 9939508 commit e28be46
Show file tree
Hide file tree
Showing 14 changed files with 229 additions and 7 deletions.
9 changes: 9 additions & 0 deletions .changeset/swift-maps-tickle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@rocket.chat/core-services': minor
'@rocket.chat/model-typings': minor
'@rocket.chat/core-typings': minor
'@rocket.chat/rest-typings': minor
'@rocket.chat/meteor': minor
---

Added `sidepanel` field to `teams.create` and `rooms.saveRoomSettings` endpoints
9 changes: 7 additions & 2 deletions apps/meteor/app/api/server/v1/teams.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Team } from '@rocket.chat/core-services';
import type { ITeam, UserStatus } from '@rocket.chat/core-typings';
import { TEAM_TYPE } from '@rocket.chat/core-typings';
import { TEAM_TYPE, isValidSidepanel } from '@rocket.chat/core-typings';
import { Users, Rooms } from '@rocket.chat/models';
import {
isTeamsConvertToChannelProps,
Expand Down Expand Up @@ -85,7 +85,11 @@ API.v1.addRoute(
}),
);

const { name, type, members, room, owner } = this.bodyParams;
const { name, type, members, room, owner, sidepanel } = this.bodyParams;

if (sidepanel?.items && !isValidSidepanel(sidepanel)) {
throw new Error('error-invalid-sidepanel');
}

const team = await Team.create(this.userId, {
team: {
Expand All @@ -95,6 +99,7 @@ API.v1.addRoute(
room,
members,
owner,
sidepanel,
});

return API.v1.success({ team });
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Team } from '@rocket.chat/core-services';
import type { IRoom, IRoomWithRetentionPolicy, IUser, MessageTypesValues } from '@rocket.chat/core-typings';
import { TEAM_TYPE } from '@rocket.chat/core-typings';
import { TEAM_TYPE, isValidSidepanel } from '@rocket.chat/core-typings';
import type { ServerMethods } from '@rocket.chat/ddp-client';
import { Rooms, Users } from '@rocket.chat/models';
import { Match } from 'meteor/check';
Expand Down Expand Up @@ -49,6 +49,7 @@ type RoomSettings = {
favorite: boolean;
defaultValue: boolean;
};
sidepanel?: IRoom['sidepanel'];
};

type RoomSettingsValidators = {
Expand Down Expand Up @@ -80,6 +81,24 @@ const validators: RoomSettingsValidators = {
});
}
},
async sidepanel({ room, userId, value }) {
if (!room.teamMain) {
throw new Meteor.Error('error-action-not-allowed', 'Invalid room', {
method: 'saveRoomSettings',
});
}

if (!(await hasPermissionAsync(userId, 'edit-team', room._id))) {
throw new Meteor.Error('error-action-not-allowed', 'You do not have permission to change sidepanel items', {
method: 'saveRoomSettings',
});
}

if (!isValidSidepanel(value)) {
throw new Meteor.Error('error-invalid-sidepanel');
}
},

async roomType({ userId, room, value }) {
if (value === room.t) {
return;
Expand Down Expand Up @@ -213,6 +232,11 @@ const settingSavers: RoomSettingsSavers = {
await saveRoomTopic(rid, value, user);
}
},
async sidepanel({ value, rid, room }) {
if (JSON.stringify(value) !== JSON.stringify(room.sidepanel)) {
await Rooms.setSidepanelById(rid, value);
}
},
async roomAnnouncement({ value, room, rid, user }) {
if (!value && !room.announcement) {
return;
Expand Down Expand Up @@ -339,6 +363,7 @@ const fields: (keyof RoomSettings)[] = [
'retentionOverrideGlobal',
'encrypted',
'favorite',
'sidepanel',
];

const validate = <TRoomSetting extends keyof RoomSettings>(
Expand Down
2 changes: 2 additions & 0 deletions apps/meteor/app/lib/server/functions/createRoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export const createRoom = async <T extends RoomType>(
readOnly?: boolean,
roomExtraData?: Partial<IRoom>,
options?: ICreateRoomParams['options'],
sidepanel?: ICreateRoomParams['sidepanel'],
): Promise<
ICreatedRoom & {
rid: string;
Expand Down Expand Up @@ -187,6 +188,7 @@ export const createRoom = async <T extends RoomType>(
},
ts: now,
ro: readOnly === true,
sidepanel,
};

if (teamId) {
Expand Down
4 changes: 4 additions & 0 deletions apps/meteor/server/models/raw/Rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,10 @@ export class RoomsRaw extends BaseRaw<IRoom> implements IRoomsModel {
return this.updateOne({ _id: roomId }, { $set: { name } });
}

setSidepanelById(roomId: IRoom['_id'], sidepanel: IRoom['sidepanel']): Promise<UpdateResult> {
return this.updateOne({ _id: roomId }, { $set: { sidepanel } });
}

setFnameById(_id: IRoom['_id'], fname: IRoom['fname']): Promise<UpdateResult> {
const query: Filter<IRoom> = { _id };

Expand Down
4 changes: 2 additions & 2 deletions apps/meteor/server/services/room/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class RoomService extends ServiceClassInternal implements IRoomService {
protected name = 'room';

async create(uid: string, params: ICreateRoomParams): Promise<IRoom> {
const { type, name, members = [], readOnly, extraData, options } = params;
const { type, name, members = [], readOnly, extraData, options, sidepanel } = params;

const hasPermission = await Authorization.hasPermission(uid, `create-${type}`);
if (!hasPermission) {
Expand All @@ -29,7 +29,7 @@ export class RoomService extends ServiceClassInternal implements IRoomService {
}

// TODO convert `createRoom` function to "raw" and move to here
return createRoom(type, name, user, members, false, readOnly, extraData, options) as unknown as IRoom;
return createRoom(type, name, user, members, false, readOnly, extraData, options, sidepanel) as unknown as IRoom;
}

async createDirectMessage({ to, from }: { to: string; from: string }): Promise<{ rid: string }> {
Expand Down
6 changes: 5 additions & 1 deletion apps/meteor/server/services/team/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ import { settings } from '../../../app/settings/server';
export class TeamService extends ServiceClassInternal implements ITeamService {
protected name = 'team';

async create(uid: string, { team, room = { name: team.name, extraData: {} }, members, owner }: ITeamCreateParams): Promise<ITeam> {
async create(
uid: string,
{ team, room = { name: team.name, extraData: {} }, members, owner, sidepanel }: ITeamCreateParams,
): Promise<ITeam> {
if (!(await checkUsernameAvailability(team.name))) {
throw new Error('team-name-already-exists');
}
Expand Down Expand Up @@ -120,6 +123,7 @@ export class TeamService extends ServiceClassInternal implements ITeamService {
teamId,
teamMain: true,
},
sidepanel,
};

const createdRoom = await Room.create(owner || uid, newRoom);
Expand Down
70 changes: 69 additions & 1 deletion apps/meteor/tests/end-to-end/api/rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2129,12 +2129,15 @@ describe('[Rooms]', () => {
describe('rooms.saveRoomSettings', () => {
let testChannel: IRoom;
const randomString = `randomString${Date.now()}`;
const teamName = `team-${Date.now()}`;
let discussion: IRoom;
let testTeam: ITeam;

before(async () => {
const result = await createRoom({ type: 'c', name: `channel.test.${Date.now()}-${Math.random()}` });
testChannel = result.body.channel;

const resTeam = await request.post(api('teams.create')).set(credentials).send({ name: teamName, type: 0 });
const resDiscussion = await request
.post(api('rooms.createDiscussion'))
.set(credentials)
Expand All @@ -2143,10 +2146,17 @@ describe('[Rooms]', () => {
t_name: `discussion-create-from-tests-${testChannel.name}`,
});

testTeam = resTeam.body.team;
discussion = resDiscussion.body.discussion;
});

after(() => Promise.all([deleteRoom({ type: 'p', roomId: discussion._id }), deleteRoom({ type: 'p', roomId: testChannel._id })]));
after(() =>
Promise.all([
deleteRoom({ type: 'p', roomId: discussion._id }),
deleteTeam(credentials, testTeam.name),
deleteRoom({ type: 'p', roomId: testChannel._id }),
]),
);

it('should update the room settings', (done) => {
const imageDataUri = `data:image/png;base64,${fs.readFileSync(path.join(process.cwd(), imgURL)).toString('base64')}`;
Expand Down Expand Up @@ -2290,6 +2300,64 @@ describe('[Rooms]', () => {
expect(res.body.room).to.not.have.property('favorite');
});
});
it('should update the team sidepanel items to channels and discussions', async () => {
const sidepanelItems = ['channels', 'discussions'];
const response = await request
.post(api('rooms.saveRoomSettings'))
.set(credentials)
.send({
rid: testTeam.roomId,
sidepanel: { items: sidepanelItems },
})
.expect('Content-Type', 'application/json')
.expect(200);

expect(response.body).to.have.property('success', true);

const channelInfoResponse = await request
.get(api('channels.info'))
.set(credentials)
.query({ roomId: response.body.rid })
.expect('Content-Type', 'application/json')
.expect(200);

expect(channelInfoResponse.body).to.have.property('success', true);
expect(channelInfoResponse.body.channel).to.have.property('sidepanel');
expect(channelInfoResponse.body.channel.sidepanel).to.have.property('items').that.is.an('array').to.have.deep.members(sidepanelItems);
});
it('should throw error when updating team sidepanel with incorrect items', async () => {
const sidepanelItems = ['wrong'];
await request
.post(api('rooms.saveRoomSettings'))
.set(credentials)
.send({
rid: testTeam.roomId,
sidepanel: { items: sidepanelItems },
})
.expect(400);
});
it('should throw error when updating team sidepanel with more than 2 items', async () => {
const sidepanelItems = ['channels', 'discussions', 'extra'];
await request
.post(api('rooms.saveRoomSettings'))
.set(credentials)
.send({
rid: testTeam.roomId,
sidepanel: { items: sidepanelItems },
})
.expect(400);
});
it('should throw error when updating team sidepanel with duplicated items', async () => {
const sidepanelItems = ['channels', 'channels'];
await request
.post(api('rooms.saveRoomSettings'))
.set(credentials)
.send({
rid: testTeam.roomId,
sidepanel: { items: sidepanelItems },
})
.expect(400);
});
});

describe('rooms.images', () => {
Expand Down
78 changes: 78 additions & 0 deletions apps/meteor/tests/end-to-end/api/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,84 @@ describe('[Teams]', () => {
})
.end(done);
});

it('should create a team with sidepanel items containing channels', async () => {
const teamName = `test-team-with-sidepanel-${Date.now()}`;
const sidepanelItems = ['channels'];

const response = await request
.post(api('teams.create'))
.set(credentials)
.send({
name: teamName,
type: 0,
sidepanel: {
items: sidepanelItems,
},
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});

await request
.get(api('channels.info'))
.set(credentials)
.query({ roomId: response.body.team.roomId })
.expect('Content-Type', 'application/json')
.expect(200)
.expect((response) => {
expect(response.body).to.have.property('success', true);
expect(response.body.channel).to.have.property('sidepanel');
expect(response.body.channel.sidepanel).to.have.property('items').that.is.an('array').to.have.deep.members(sidepanelItems);
});
await deleteTeam(credentials, teamName);
});

it('should throw error when creating a team with sidepanel with more than 2 items', async () => {
await request
.post(api('teams.create'))
.set(credentials)
.send({
name: `test-team-with-sidepanel-error-${Date.now()}`,
type: 0,
sidepanel: {
items: ['channels', 'discussion', 'other'],
},
})
.expect('Content-Type', 'application/json')
.expect(400);
});

it('should throw error when creating a team with sidepanel with incorrect items', async () => {
await request
.post(api('teams.create'))
.set(credentials)
.send({
name: `test-team-with-sidepanel-error-${Date.now()}`,
type: 0,
sidepanel: {
items: ['other'],
},
})
.expect('Content-Type', 'application/json')
.expect(400);
});
it('should throw error when creating a team with sidepanel with duplicated items', async () => {
await request
.post(api('teams.create'))
.set(credentials)
.send({
name: `test-team-with-sidepanel-error-${Date.now()}`,
type: 0,
sidepanel: {
items: ['channels', 'channels'],
},
})
.expect('Content-Type', 'application/json')
.expect(400);
});
});

describe('/teams.convertToChannel', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/core-services/src/types/IRoomService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface ICreateRoomParams {
readOnly?: boolean;
extraData?: Partial<ICreateRoomExtraData>;
options?: ICreateRoomOptions;
sidepanel?: IRoom['sidepanel'];
}
export interface IRoomService {
addMember(uid: string, rid: string): Promise<boolean>;
Expand Down
1 change: 1 addition & 0 deletions packages/core-services/src/types/ITeamService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface ITeamCreateParams {
room: ITeamCreateRoom;
members?: Array<string> | null; // list of user _ids
owner?: string | null; // the team owner. If not present, owner = requester
sidepanel?: IRoom['sidepanel'];
}

export interface ITeamMemberParams {
Expand Down
Loading

0 comments on commit e28be46

Please sign in to comment.