Skip to content

Commit

Permalink
Merge branch 'develop' into chore/refactor-editRoomInfo
Browse files Browse the repository at this point in the history
  • Loading branch information
dougfabris committed Oct 6, 2023
2 parents b9daeb4 + bdc9d8c commit 67f3b18
Show file tree
Hide file tree
Showing 31 changed files with 877 additions and 189 deletions.
5 changes: 5 additions & 0 deletions .changeset/dull-trainers-drive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': patch
---

fix: Remove model-level query restrictions for monitors
5 changes: 5 additions & 0 deletions .changeset/sweet-feet-relate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rocket.chat/meteor": patch
---

fix: user dropdown menu position on RTL layout
2 changes: 1 addition & 1 deletion apps/meteor/app/livechat/server/lib/Livechat.js
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ export const Livechat = {
Livechat.logger.debug(`Closing open chats for user ${userId}`);
const user = await Users.findOneById(userId);

const extraQuery = await callbacks.run('livechat.applyDepartmentRestrictions', {});
const extraQuery = await callbacks.run('livechat.applyDepartmentRestrictions', {}, { userId });
const openChats = LivechatRooms.findOpenByAgent(userId, extraQuery);
const promises = [];
await openChats.forEach((room) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const MessageContentBody = ({ mentions, channels, md, searchText }: MessageConte
text-decoration: underline;
}
&:focus {
border: 2px solid ${Palette.stroke['stroke-extra-light-highlight']};
box-shadow: 0 0 0 2px ${Palette.stroke['stroke-extra-light-highlight']};
border-radius: 2px;
}
}
Expand Down
2 changes: 2 additions & 0 deletions apps/meteor/client/sidebar/header/UserMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const UserMenu = ({ user }: { user: IUser }) => {
<FeaturePreviewOff>
<GenericMenu
icon={<UserAvatarWithStatus />}
placement='bottom-end'
selectionMode='multiple'
sections={sections}
title={t('User_menu')}
Expand All @@ -36,6 +37,7 @@ const UserMenu = ({ user }: { user: IUser }) => {
<GenericMenu
icon={<UserAvatarWithStatusUnstable />}
medium
placement='bottom-end'
selectionMode='multiple'
sections={sections}
title={t('User_menu')}
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/client/views/admin/rooms/EditRoom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ const EditRoom = ({ room, onChange, onDelete }: EditRoomProps): ReactElement =>
</Box>
)}
<Field>
<FieldLabel>{t('Name')}</FieldLabel>
<FieldLabel required>{t('Name')}</FieldLabel>
<FieldRow>
<TextInput disabled={isDeleting || !canViewName} value={roomName} onChange={handleRoomName} flexGrow={1} />
</FieldRow>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@ import type { FilterOperators } from 'mongodb';
import { hasRoleAsync } from '../../../../../app/authorization/server/functions/hasRole';
import { callbacks } from '../../../../../lib/callbacks';
import { cbLogger } from '../lib/logger';
import { getUnitsFromUser } from '../lib/units';
import { getUnitsFromUser } from '../methods/getUnitsFromUserRoles';

export const addQueryRestrictionsToDepartmentsModel = async (originalQuery: FilterOperators<ILivechatDepartment> = {}) => {
export const addQueryRestrictionsToDepartmentsModel = async (originalQuery: FilterOperators<ILivechatDepartment> = {}, userId: string) => {
const query: FilterOperators<ILivechatDepartment> = { ...originalQuery, type: { $ne: 'u' } };

const units = await getUnitsFromUser();
const units = await getUnitsFromUser(userId);
if (Array.isArray(units)) {
query.ancestors = { $in: units };
}

cbLogger.debug({ msg: 'Applying department query restrictions', userId, units });
return query;
};

Expand All @@ -25,7 +26,7 @@ callbacks.add(
}

cbLogger.debug('Applying department query restrictions');
return addQueryRestrictionsToDepartmentsModel(originalQuery);
return addQueryRestrictionsToDepartmentsModel(originalQuery, userId);
},
callbacks.priority.HIGH,
'livechat-apply-department-restrictions',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { LivechatDepartment } from '@rocket.chat/models';
import type { FilterOperators } from 'mongodb';

import { callbacks } from '../../../../../lib/callbacks';
import { cbLogger } from '../lib/logger';
import { getUnitsFromUser } from '../lib/units';

export const restrictQuery = async (originalQuery: FilterOperators<IOmnichannelRoom> = {}) => {
Expand All @@ -20,6 +21,7 @@ export const restrictQuery = async (originalQuery: FilterOperators<IOmnichannelR
};
query.$and = [condition, ...expressions];

cbLogger.debug({ msg: 'Applying room query restrictions', units });
return query;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
import { LivechatUnit } from '@rocket.chat/models';
import { LivechatUnit, LivechatDepartmentAgents } from '@rocket.chat/models';
import type { ServerMethods } from '@rocket.chat/ui-contexts';
import mem from 'mem';
import { Meteor } from 'meteor/meteor';

import { hasAnyRoleAsync } from '../../../../../app/authorization/server/functions/hasRole';
import { logger } from '../lib/logger';

async function getUnitsFromUserRoles(user: string | null): Promise<string[] | undefined> {
async function getUnitsFromUserRoles(user: string): Promise<string[]> {
return LivechatUnit.findByMonitorId(user);
}

async function getDepartmentsFromUserRoles(user: string): Promise<string[]> {
return (await LivechatDepartmentAgents.findByAgentId(user).toArray()).map((department) => department.departmentId);
}

const memoizedGetUnitFromUserRoles = mem(getUnitsFromUserRoles, { maxAge: 10000 });
const memoizedGetDepartmentsFromUserRoles = mem(getDepartmentsFromUserRoles, { maxAge: 5000 });

export const getUnitsFromUser = async (user: string): Promise<string[] | undefined> => {
if (!user || (await hasAnyRoleAsync(user, ['admin', 'livechat-manager']))) {
return;
}
Expand All @@ -14,10 +26,11 @@ async function getUnitsFromUserRoles(user: string | null): Promise<string[] | un
return;
}

return LivechatUnit.findByMonitorId(user);
}
const unitsAndDepartments = [...(await memoizedGetUnitFromUserRoles(user)), ...(await memoizedGetDepartmentsFromUserRoles(user))];
logger.debug({ msg: 'Calculating units for monitor', user, unitsAndDepartments });

const memoizedGetUnitFromUserRoles = mem(getUnitsFromUserRoles, { maxAge: 10000 });
return unitsAndDepartments;
};

declare module '@rocket.chat/ui-contexts' {
// eslint-disable-next-line @typescript-eslint/naming-convention
Expand All @@ -27,8 +40,11 @@ declare module '@rocket.chat/ui-contexts' {
}

Meteor.methods<ServerMethods>({
'livechat:getUnitsFromUser'(): Promise<string[] | undefined> {
async 'livechat:getUnitsFromUser'(): Promise<string[] | undefined> {
const user = Meteor.userId();
return memoizedGetUnitFromUserRoles(user);
if (!user) {
return;
}
return getUnitsFromUser(user);
},
});
27 changes: 0 additions & 27 deletions apps/meteor/ee/server/models/raw/LivechatRooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import type { FindCursor, UpdateResult, Document, FindOptions, Db, Collection, F

import { readSecondaryPreferred } from '../../../../server/database/readSecondaryPreferred';
import { LivechatRoomsRaw } from '../../../../server/models/raw/LivechatRooms';
import { addQueryRestrictionsToRoomsModel } from '../../../app/livechat-enterprise/server/lib/query.helper';

declare module '@rocket.chat/model-typings' {
interface ILivechatRoomsModel {
Expand Down Expand Up @@ -296,32 +295,6 @@ export class LivechatRoomsRawEE extends LivechatRoomsRaw implements ILivechatRoo
return this.updateOne(query, update);
}

/** @deprecated Use updateOne or updateMany instead */
async update(...args: Parameters<LivechatRoomsRaw['update']>) {
const [query, ...restArgs] = args;
const restrictedQuery = await addQueryRestrictionsToRoomsModel(query);
return super.update(restrictedQuery, ...restArgs);
}

async updateOne(...args: [...Parameters<LivechatRoomsRaw['updateOne']>, { bypassUnits?: boolean }?]) {
const [query, update, opts, extraOpts] = args;
if (extraOpts?.bypassUnits) {
// When calling updateOne from a service, we cannot call the meteor code inside the query restrictions
// So the solution now is to pass a bypassUnits flag to the updateOne method which prevents checking
// units restrictions on the query, but just for the query the service is actually using
// We need to find a way of remove the meteor dependency when fetching units, and then, we can remove this flag
return super.updateOne(query, update, opts);
}
const restrictedQuery = await addQueryRestrictionsToRoomsModel(query);
return super.updateOne(restrictedQuery, update, opts);
}

async updateMany(...args: Parameters<LivechatRoomsRaw['updateMany']>) {
const [query, ...restArgs] = args;
const restrictedQuery = await addQueryRestrictionsToRoomsModel(query);
return super.updateMany(restrictedQuery, ...restArgs);
}

getConversationsBySource(start: Date, end: Date, extraQuery: Filter<IOmnichannelRoom>): AggregationCursor<ReportResult> {
return this.col.aggregate(
[
Expand Down
9 changes: 0 additions & 9 deletions apps/meteor/ee/server/models/raw/LivechatUnit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,6 @@ export class LivechatUnitRaw extends BaseRaw<IOmnichannelBusinessUnit> implement
return this.col.findOne(query, options);
}

async update(
originalQuery: Filter<IOmnichannelBusinessUnit>,
update: Filter<IOmnichannelBusinessUnit>,
options: FindOptions<IOmnichannelBusinessUnit>,
): Promise<UpdateResult> {
const query = await addQueryRestrictions(originalQuery);
return this.col.updateOne(query, update, options);
}

remove(query: Filter<IOmnichannelBusinessUnit>): Promise<DeleteResult> {
return this.deleteMany(query);
}
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/ee/server/startup/maxRoomsPerGuest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ callbacks.add(
'beforeAddedToRoom',
async ({ user }) => {
if (user.roles?.includes('guest')) {
if (await License.shouldPreventAction('roomsPerGuest', { userId: user._id })) {
if (await License.shouldPreventAction('roomsPerGuest', 0, { userId: user._id })) {
throw new Meteor.Error('error-max-rooms-per-guest-reached', i18n.t('error-max-rooms-per-guest-reached'));
}
}
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/ee/server/startup/seatsCap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ callbacks.add(
callbacks.add(
'beforeUserImport',
async ({ userCount }) => {
if (await License.shouldPreventAction('activeUsers', {}, userCount)) {
if (await License.shouldPreventAction('activeUsers', userCount)) {
throw new Meteor.Error('error-license-user-limit-reached', i18n.t('error-license-user-limit-reached'));
}
},
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/lib/callbacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ interface EventLikeCallbackSignatures {
'livechat.afterTakeInquiry': (inq: InquiryWithAgentInfo, agent: { agentId: string; username: string }) => void;
'livechat.afterAgentRemoved': (params: { agent: Pick<IUser, '_id' | 'username'> }) => void;
'afterAddedToRoom': (params: { user: IUser; inviter?: IUser }, room: IRoom) => void;
'beforeAddedToRoom': (params: { user: AtLeast<IUser, 'federated' | 'roles'>; inviter: IUser }) => void;
'beforeAddedToRoom': (params: { user: AtLeast<IUser, '_id' | 'federated' | 'roles'>; inviter: IUser }) => void;
'afterCreateDirectRoom': (params: IRoom, second: { members: IUser[]; creatorId: IUser['_id'] }) => void;
'beforeDeleteRoom': (params: IRoom) => void;
'beforeJoinDefaultChannels': (user: IUser) => void;
Expand Down
15 changes: 0 additions & 15 deletions apps/meteor/server/models/raw/LivechatRooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1518,11 +1518,6 @@ export class LivechatRoomsRaw extends BaseRaw<IOmnichannelRoom> implements ILive
{
$set: { pdfTranscriptRequested: true },
},
{},
// @ts-expect-error - extra arg not on base types
{
bypassUnits: true,
},
);
}

Expand All @@ -1534,11 +1529,6 @@ export class LivechatRoomsRaw extends BaseRaw<IOmnichannelRoom> implements ILive
{
$unset: { pdfTranscriptRequested: 1 },
},
{},
// @ts-expect-error - extra arg not on base types
{
bypassUnits: true,
},
);
}

Expand All @@ -1550,11 +1540,6 @@ export class LivechatRoomsRaw extends BaseRaw<IOmnichannelRoom> implements ILive
{
$set: { pdfTranscriptFileId: fileId },
},
{},
// @ts-expect-error - extra arg not on base types
{
bypassUnits: true,
},
);
}

Expand Down
26 changes: 8 additions & 18 deletions apps/meteor/tests/data/livechat/department.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { faker } from '@faker-js/faker';
import { expect } from 'chai';
import type { ILivechatDepartment, IUser, LivechatDepartmentDTO } from '@rocket.chat/core-typings';
import { api, credentials, methodCall, request } from '../api-data';
import { IUserCredentialsHeader, password } from '../user';
import { login } from '../users.helper';
import { createAgent, makeAgentAvailable } from './rooms';
import { IUserCredentialsHeader } from '../user';
import { createAnOnlineAgent } from './users';
import { WithRequiredProperty } from './utils';

export const NewDepartmentData = ((): Partial<ILivechatDepartment> => ({
enabled: true,
Expand Down Expand Up @@ -59,29 +59,19 @@ new Promise((resolve, reject) => {

export const createDepartmentWithAnOnlineAgent = async (): Promise<{department: ILivechatDepartment, agent: {
credentials: IUserCredentialsHeader;
user: IUser;
user: WithRequiredProperty<IUser, 'username'>;
}}> => {
// TODO moving here for tests
const username = `user.test.${Date.now()}`;
const email = `${username}@rocket.chat`;
const { body } = await request
.post(api('users.create'))
.set(credentials)
.send({ email, name: username, username, password });
const agent = body.user;
const createdUserCredentials = await login(agent.username, password);
await createAgent(agent.username);
await makeAgentAvailable(createdUserCredentials);
const { user, credentials } = await createAnOnlineAgent();

const department = await createDepartmentWithMethod() as ILivechatDepartment;

await addOrRemoveAgentFromDepartment(department._id, {agentId: agent._id, username: (agent.username as string)}, true);
await addOrRemoveAgentFromDepartment(department._id, {agentId: user._id, username: user.username}, true);

return {
department,
agent: {
credentials: createdUserCredentials,
user: agent,
credentials,
user,
}
};
};
Expand Down
3 changes: 3 additions & 0 deletions apps/meteor/tests/data/livechat/rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ export const getLivechatRoomInfo = (roomId: string): Promise<IOmnichannelRoom> =
});
};

/**
* @summary Sends message as visitor
*/
export const sendMessage = (roomId: string, message: string, visitorToken: string): Promise<IMessage> => {
return new Promise((resolve, reject) => {
request
Expand Down
23 changes: 22 additions & 1 deletion apps/meteor/tests/data/livechat/users.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { faker } from "@faker-js/faker";
import type { IUser } from "@rocket.chat/core-typings";
import { password } from "../user";
import { IUserCredentialsHeader, password } from "../user";
import { createUser, login } from "../users.helper";
import { createAgent, makeAgentAvailable } from "./rooms";
import { api, credentials, request } from "../api-data";
Expand Down Expand Up @@ -29,3 +29,24 @@ export const removeAgent = async (userId: string): Promise<void> => {
.set(credentials)
.expect(200);
}

export const createAnOnlineAgent = async (): Promise<{
credentials: IUserCredentialsHeader;
user: IUser & { username: string };
}> => {
const username = `user.test.${Date.now()}`;
const email = `${username}@rocket.chat`;
const { body } = await request
.post(api('users.create'))
.set(credentials)
.send({ email, name: username, username, password });
const agent = body.user;
const createdUserCredentials = await login(agent.username, password);
await createAgent(agent.username);
await makeAgentAvailable(createdUserCredentials);

return {
credentials: createdUserCredentials,
user: agent,
};
}
4 changes: 4 additions & 0 deletions apps/meteor/tests/data/livechat/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
export type DummyResponse<T, E = 'wrapped'> =
E extends 'wrapped' ? { body: { [k: string]: T } } : { body: T };

export type WithRequiredProperty<Type, Key extends keyof Type> = Type & {
[Property in Key]-?: Type[Property];
};

export const sleep = (ms: number) => {
return new Promise((resolve) => setTimeout(resolve, ms));
}
Loading

0 comments on commit 67f3b18

Please sign in to comment.