Skip to content

Commit

Permalink
refactor: Improve Omnichannel queries (#29711)
Browse files Browse the repository at this point in the history
  • Loading branch information
KevLehman authored Aug 25, 2023
1 parent 89c4463 commit bf1cd3d
Show file tree
Hide file tree
Showing 32 changed files with 190 additions and 67 deletions.
2 changes: 1 addition & 1 deletion apps/meteor/app/apps/server/converters/rooms.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export class AppRoomsConverter {

let departmentId;
if (room.department) {
const department = await LivechatDepartment.findOneById(room.department.id);
const department = await LivechatDepartment.findOneById(room.department.id, { projection: { _id: 1 } });
departmentId = department._id;
}

Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/e2e/server/methods/setRoomKeyID.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Meteor.methods<ServerMethods>({
throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'e2e.setRoomKeyID' });
}

const room = await Rooms.findOneById(rid, { fields: { e2eKeyId: 1 } });
const room = await Rooms.findOneById<Pick<IRoom, '_id' | 'e2eKeyId'>>(rid, { projection: { e2eKeyId: 1 } });

if (!room) {
throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'e2e.setRoomKeyID' });
Expand Down
4 changes: 2 additions & 2 deletions apps/meteor/app/livechat/server/api/lib/departments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ export async function findDepartmentById({
department: await LivechatDepartment.findOne(query),
...(includeAgents &&
canViewLivechatDepartments && {
agents: await LivechatDepartmentAgents.find({ departmentId }).toArray(),
agents: await LivechatDepartmentAgents.findByDepartmentId(departmentId).toArray(),
}),
};

Expand Down Expand Up @@ -192,6 +192,6 @@ export async function findDepartmentsBetweenIds({
ids: string[];
fields: Record<string, unknown>;
}): Promise<{ departments: ILivechatDepartment[] }> {
const departments = await LivechatDepartment.findInIds(ids, fields).toArray();
const departments = await LivechatDepartment.findInIds(ids, { projection: fields }).toArray();
return { departments };
}
6 changes: 4 additions & 2 deletions apps/meteor/app/livechat/server/api/lib/inquiries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import { getOmniChatSortQuery } from '../../../lib/inquiries';
import { getInquirySortMechanismSetting } from '../../lib/settings';

const agentDepartments = async (userId: IUser['_id']): Promise<string[]> => {
const agentDepartments = (await LivechatDepartmentAgents.findByAgentId(userId).toArray()).map(({ departmentId }) => departmentId);
return (await LivechatDepartment.find({ _id: { $in: agentDepartments }, enabled: true }).toArray()).map(({ _id }) => _id);
const agentDepartments = (await LivechatDepartmentAgents.findByAgentId(userId, { projection: { departmentId: 1 } }).toArray()).map(
({ departmentId }) => departmentId,
);
return (await LivechatDepartment.findEnabledInIds(agentDepartments, { projection: { _id: 1 } }).toArray()).map(({ _id }) => _id);
};

const applyDepartmentRestrictions = async (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { IOmnichannelRoom, IMessage, IBusinessHourWorkHour } from '@rocket.chat/core-typings';
import type { IOmnichannelRoom, IMessage, IBusinessHourWorkHour, ILivechatDepartment } from '@rocket.chat/core-typings';
import { isOmnichannelRoom } from '@rocket.chat/core-typings';
import { LivechatBusinessHours, LivechatDepartment, Messages, LivechatRooms } from '@rocket.chat/models';
import moment from 'moment';
Expand Down Expand Up @@ -27,7 +27,11 @@ const getSecondsSinceLastAgentResponse = async (room: IOmnichannelRoom, agentLas
return getSecondsWhenOfficeHoursIsDisabled(room, agentLastMessage);
}
let officeDays;
const department = room.departmentId ? await LivechatDepartment.findOneById(room.departmentId) : null;
const department = room.departmentId
? await LivechatDepartment.findOneById<Pick<ILivechatDepartment, 'businessHourId'>>(room.departmentId, {
projection: { businessHourId: 1 },
})
: null;
if (department?.businessHourId) {
const businessHour = await LivechatBusinessHours.findOneById(department.businessHourId);
if (!businessHour) {
Expand Down
6 changes: 5 additions & 1 deletion apps/meteor/app/livechat/server/lib/Departments.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { ILivechatDepartmentAgents } from '@rocket.chat/core-typings';
import { Logger } from '@rocket.chat/logger';
import { LivechatDepartment, LivechatDepartmentAgents, LivechatRooms } from '@rocket.chat/models';

Expand All @@ -24,7 +25,10 @@ class DepartmentHelperClass {
}
this.logger.debug(`Department record removed: ${_id}`);

const agentsIds: string[] = await LivechatDepartmentAgents.findAgentsByDepartmentId(department._id)
const agentsIds: string[] = await LivechatDepartmentAgents.findAgentsByDepartmentId<Pick<ILivechatDepartmentAgents, 'agentId'>>(
department._id,
{ projection: { agentId: 1 } },
)
.cursor.map((agent) => agent.agentId)
.toArray();

Expand Down
11 changes: 9 additions & 2 deletions apps/meteor/app/livechat/server/lib/Helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type {
ILivechatDepartmentAgents,
TransferByData,
ILivechatAgent,
ILivechatDepartment,
} from '@rocket.chat/core-typings';
import { LivechatInquiryStatus, OmnichannelSourceType, DEFAULT_SLA_CONFIG, UserStatus } from '@rocket.chat/core-typings';
import { LivechatPriorityWeight } from '@rocket.chat/core-typings/src/ILivechatPriority';
Expand Down Expand Up @@ -519,7 +520,9 @@ export const forwardRoomToDepartment = async (room: IOmnichannelRoom, guest: ILi
if (!user) {
throw new Error('error-user-is-offline');
}
const isInDepartment = await LivechatDepartmentAgents.findOneByAgentIdAndDepartmentId(agentId, departmentId);
const isInDepartment = await LivechatDepartmentAgents.findOneByAgentIdAndDepartmentId(agentId, departmentId, {
projection: { _id: 1 },
});
if (!isInDepartment) {
throw new Error('error-user-not-belong-to-department');
}
Expand Down Expand Up @@ -549,7 +552,11 @@ export const forwardRoomToDepartment = async (room: IOmnichannelRoom, guest: ILi

const { servedBy, chatQueued } = roomTaken;
if (!chatQueued && oldServedBy && servedBy && oldServedBy._id === servedBy._id) {
const department = departmentId ? await LivechatDepartment.findOneById(departmentId) : null;
const department = departmentId
? await LivechatDepartment.findOneById<Pick<ILivechatDepartment, '_id' | 'fallbackForwardDepartment'>>(departmentId, {
projection: { fallbackForwardDepartment: 1 },
})
: null;
if (!department?.fallbackForwardDepartment?.length) {
logger.debug(`Cannot forward room ${room._id}. Chat assigned to agent ${servedBy._id} (Previous was ${oldServedBy._id})`);
throw new Error('error-no-agents-online-in-department');
Expand Down
4 changes: 3 additions & 1 deletion apps/meteor/app/livechat/server/lib/Livechat.js
Original file line number Diff line number Diff line change
Expand Up @@ -709,7 +709,9 @@ export const Livechat = {
});
}
const ret = (await LivechatDepartmentRaw.removeById(_id)).deletedCount;
const agentsIds = (await LivechatDepartmentAgents.findByDepartmentId(_id).toArray()).map((agent) => agent.agentId);
const agentsIds = (await LivechatDepartmentAgents.findByDepartmentId(_id, { projection: { agentId: 1 } }).toArray()).map(
(agent) => agent.agentId,
);
await LivechatDepartmentAgents.removeByDepartmentId(_id);
await LivechatDepartmentRaw.unsetFallbackDepartmentByDepartmentId(_id);
if (ret) {
Expand Down
19 changes: 15 additions & 4 deletions apps/meteor/app/livechat/server/lib/LivechatTyped.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
SelectedAgent,
ILivechatAgent,
IMessage,
ILivechatDepartment,
} from '@rocket.chat/core-typings';
import { UserStatus, isOmnichannelRoom } from '@rocket.chat/core-typings';
import { Logger, type MainLogger } from '@rocket.chat/logger';
Expand Down Expand Up @@ -299,7 +300,10 @@ class LivechatClass {
room = null;
}

if (guest.department && !(await LivechatDepartment.findOneById(guest.department))) {
if (
guest.department &&
!(await LivechatDepartment.findOneById<Pick<ILivechatDepartment, '_id'>>(guest.department, { projection: { _id: 1 } }))
) {
await LivechatVisitors.removeDepartmentById(guest._id);
const tmpGuest = await LivechatVisitors.findOneById(guest._id);
if (tmpGuest) {
Expand Down Expand Up @@ -357,7 +361,9 @@ class LivechatClass {
return onlineForDep;
}

const dep = await LivechatDepartment.findOneById(department);
const dep = await LivechatDepartment.findOneById<Pick<ILivechatDepartment, '_id' | 'fallbackForwardDepartment'>>(department, {
projection: { fallbackForwardDepartment: 1 },
});
if (!dep?.fallbackForwardDepartment) {
return onlineForDep;
}
Expand Down Expand Up @@ -586,7 +592,7 @@ class LivechatClass {

if (department) {
Livechat.logger.debug(`Attempt to find a department with id/name ${department}`);
const dep = await LivechatDepartment.findOneByIdOrName(department);
const dep = await LivechatDepartment.findOneByIdOrName(department, { projection: { _id: 1 } });
if (!dep) {
Livechat.logger.debug('Invalid department provided');
throw new Meteor.Error('error-invalid-department', 'The provided department is invalid');
Expand Down Expand Up @@ -673,7 +679,12 @@ class LivechatClass {
};
}

const department = await LivechatDepartment.findOneById(departmentId);
const department = await LivechatDepartment.findOneById<Pick<ILivechatDepartment, 'requestTagBeforeClosingChat' | 'chatClosingTags'>>(
departmentId,
{
projection: { requestTagBeforeClosingChat: 1, chatClosingTags: 1 },
},
);
if (!department) {
return {
updatedOptions: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { IUser, ILivechatDepartment, IOmnichannelRoom } from '@rocket.chat/core-typings';
import type { IUser, IOmnichannelRoom } from '@rocket.chat/core-typings';
import { LivechatDepartmentAgents, LivechatInquiry, LivechatRooms, LivechatDepartment } from '@rocket.chat/models';

import { hasPermissionAsync } from '../../authorization/server/functions/hasPermission';
Expand Down Expand Up @@ -47,10 +47,10 @@ export const validators: OmnichannelRoomAccessValidator[] = [

let departmentIds;
if (!(await hasRoleAsync(user._id, 'livechat-manager'))) {
const departmentAgents = (await LivechatDepartmentAgents.findByAgentId(user._id).toArray()).map((d) => d.departmentId);
departmentIds = (await LivechatDepartment.find({ _id: { $in: departmentAgents }, enabled: true }).toArray()).map(
(d: ILivechatDepartment) => d._id,
const departmentAgents = (await LivechatDepartmentAgents.findByAgentId(user._id, { projection: { departmentId: 1 } }).toArray()).map(
(d) => d.departmentId,
);
departmentIds = (await LivechatDepartment.findEnabledInIds(departmentAgents, { projection: { _id: 1 } }).toArray()).map((d) => d._id);
}

const filter = {
Expand All @@ -75,7 +75,9 @@ export const validators: OmnichannelRoomAccessValidator[] = [
if (!room.departmentId || room.open || !user?._id) {
return;
}
const agentOfDepartment = await LivechatDepartmentAgents.findOneByAgentIdAndDepartmentId(user._id, room.departmentId);
const agentOfDepartment = await LivechatDepartmentAgents.findOneByAgentIdAndDepartmentId(user._id, room.departmentId, {
projection: { _id: 1 },
});
if (!agentOfDepartment) {
return;
}
Expand Down
8 changes: 4 additions & 4 deletions apps/meteor/app/statistics/server/lib/statistics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,11 @@ export const statistics = {
statistics.totalThreads = await Messages.countThreads();

// livechat visitors
statistics.totalLivechatVisitors = await LivechatVisitors.col.estimatedDocumentCount();
statistics.totalLivechatVisitors = await LivechatVisitors.estimatedDocumentCount();

// livechat agents
statistics.totalLivechatAgents = await Users.countAgents();
statistics.totalLivechatManagers = await Users.col.countDocuments({ roles: 'livechat-manager' });
statistics.totalLivechatManagers = await Users.countDocuments({ roles: 'livechat-manager' });

// livechat enabled
statistics.livechatEnabled = settings.get('Livechat_enabled');
Expand All @@ -147,14 +147,14 @@ export const statistics = {

// Number of departments
statsPms.push(
LivechatDepartment.col.count().then((count) => {
LivechatDepartment.estimatedDocumentCount().then((count) => {
statistics.departments = count;
}),
);

// Number of archived departments
statsPms.push(
LivechatDepartment.col.countDocuments({ archived: true }).then((count) => {
LivechatDepartment.countArchived().then((count) => {
statistics.archivedDepartments = count;
}),
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { IOmnichannelCannedResponse } from '@rocket.chat/core-typings';
import type { IOmnichannelCannedResponse, ILivechatDepartment } from '@rocket.chat/core-typings';
import { LivechatDepartment, CannedResponse, Users } from '@rocket.chat/models';
import type { ServerMethods } from '@rocket.chat/ui-contexts';
import { Match, check } from 'meteor/check';
Expand Down Expand Up @@ -76,7 +76,10 @@ Meteor.methods<ServerMethods>({
});
}

if (responseData.departmentId && !(await LivechatDepartment.findOneById(responseData.departmentId))) {
if (
responseData.departmentId &&
!(await LivechatDepartment.findOneById<Pick<ILivechatDepartment, '_id'>>(responseData.departmentId, { projection: { _id: 1 } }))
) {
throw new Meteor.Error('error-invalid-department', 'Invalid department', {
method: 'saveCannedResponse',
});
Expand Down
28 changes: 12 additions & 16 deletions apps/meteor/ee/app/license/server/getStatistics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,37 +55,37 @@ async function getEEStatistics(): Promise<EEOnlyStats | undefined> {

// Number of livechat tags
statsPms.push(
LivechatTag.col.count().then((count) => {
LivechatTag.estimatedDocumentCount().then((count) => {
statistics.livechatTags = count;
return true;
}),
);

// Number of canned responses
statsPms.push(
CannedResponse.col.estimatedDocumentCount().then((count) => {
CannedResponse.estimatedDocumentCount().then((count) => {
statistics.cannedResponses = count;
return true;
}),
);

// Number of Service Level Agreements
statsPms.push(
OmnichannelServiceLevelAgreements.col.count().then((count) => {
OmnichannelServiceLevelAgreements.estimatedDocumentCount().then((count) => {
statistics.slas = count;
return true;
}),
);

statsPms.push(
LivechatRooms.col.countDocuments({ priorityId: { $exists: true } }).then((count) => {
LivechatRooms.countPrioritizedRooms().then((count) => {
statistics.omnichannelRoomsWithPriorities = count;
return true;
}),
);

statsPms.push(
LivechatRooms.col.countDocuments({ slaId: { $exists: true } }).then((count) => {
LivechatRooms.countRoomsWithSla().then((count) => {
statistics.omnichannelRoomsWithSlas = count;
return true;
}),
Expand All @@ -101,28 +101,24 @@ async function getEEStatistics(): Promise<EEOnlyStats | undefined> {

statsPms.push(
// Total livechat monitors
Users.col.countDocuments({ roles: 'livechat-monitor' }).then((count) => {
Users.countByRole('livechat-monitor').then((count) => {
statistics.livechatMonitors = count;
return true;
}),
);

// Number of PDF transcript requested
statsPms.push(
LivechatRooms.find({ pdfTranscriptRequested: { $exists: true } })
.count()
.then((count) => {
statistics.omnichannelPdfTranscriptRequested = count;
}),
LivechatRooms.countRoomsWithPdfTranscriptRequested().then((count) => {
statistics.omnichannelPdfTranscriptRequested = count;
}),
);

// Number of PDF transcript that succeeded
statsPms.push(
LivechatRooms.find({ pdfTranscriptFileId: { $exists: true } })
.count()
.then((count) => {
statistics.omnichannelPdfTranscriptSucceeded = count;
}),
LivechatRooms.countRoomsWithTranscriptSent().then((count) => {
statistics.omnichannelPdfTranscriptSucceeded = count;
}),
);

await Promise.all(statsPms).catch(log);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const getDepartmentsWhichUserCanAccess = async (userId: string, includeDi
};

export const hasAccessToDepartment = async (userId: string, departmentId: string): Promise<boolean> => {
const department = await LivechatDepartmentAgents.findOneByAgentIdAndDepartmentId(userId, departmentId);
const department = await LivechatDepartmentAgents.findOneByAgentIdAndDepartmentId(userId, departmentId, { projection: { _id: 1 } });
if (department) {
helperLogger.debug(`User ${userId} has access to department ${departmentId} because they are an agent`);
return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { ILivechatDepartment } from '@rocket.chat/core-typings';
import { LivechatDepartment } from '@rocket.chat/models';

import { settings } from '../../../../../app/settings/server';
Expand All @@ -8,7 +9,12 @@ callbacks.add(
'livechat.applySimultaneousChatRestrictions',
async (_: any, { departmentId }: { departmentId?: string } = {}) => {
if (departmentId) {
const departmentLimit = (await LivechatDepartment.findOneById(departmentId))?.maxNumberSimultaneousChat || 0;
const departmentLimit =
(
await LivechatDepartment.findOneById<Pick<ILivechatDepartment, 'maxNumberSimultaneousChat'>>(departmentId, {
projection: { maxNumberSimultaneousChats: 1 },
})
)?.maxNumberSimultaneousChat || 0;
if (departmentLimit > 0) {
cbLogger.debug(`Applying department filters. Max chats per department ${departmentLimit}`);
return { $match: { 'queueInfo.chats': { $gte: Number(departmentLimit) } } };
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { ILivechatDepartment } from '@rocket.chat/core-typings';
import { LivechatDepartment } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';

Expand All @@ -18,7 +19,9 @@ callbacks.add(
return options;
}
const { department: departmentToTransfer } = transferData;
const currentDepartment = await LivechatDepartment.findOneById(departmentId);
const currentDepartment = await LivechatDepartment.findOneById<Pick<ILivechatDepartment, 'departmentsAllowedToForward'>>(departmentId, {
projection: { departmentsAllowedToForward: 1 },
});
if (!currentDepartment) {
cbLogger.debug('Skipping callback. Current department does not exists');
return options;
Expand Down
Loading

0 comments on commit bf1cd3d

Please sign in to comment.