Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IMPROVE] Performance for some Omnichannel features #25217

Merged
merged 5 commits into from
Apr 20, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/meteor/app/apps/server/bridges/livechat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ export class AppLivechatBridge extends LivechatBridge {
protected async findVisitorByEmail(email: string, appId: string): Promise<IVisitor | undefined> {
this.orch.debugLog(`The App ${appId} is looking for livechat visitors.`);

return this.orch.getConverters()?.get('visitors').convertVisitor(LivechatVisitors.findOneGuestByEmailAddress(email));
return this.orch.getConverters()?.get('visitors').convertVisitor(LivechatVisitors.findOneGuestByEmailAddress(email.toLowerCase()));
}

protected async findVisitorByToken(token: string, appId: string): Promise<IVisitor | undefined> {
Expand Down
8 changes: 6 additions & 2 deletions apps/meteor/app/livechat/server/api/lib/queue.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export async function findQueueMetrics({ userId, agentId, includeOfflineAgents,
throw new Error('error-not-authorized');
}

const queue = await LivechatRooms.getQueueMetrics({
const result = await LivechatRooms.getQueueMetrics({
departmentId,
agentId,
includeOfflineAgents,
Expand All @@ -16,7 +16,11 @@ export async function findQueueMetrics({ userId, agentId, includeOfflineAgents,
count,
},
});
const total = (await LivechatRooms.getQueueMetrics({ departmentId, agentId, includeOfflineAgents })).length;

const {
sortedResults: queue,
totalCount: [{ total } = { total: 0 }],
} = result[0];

return {
queue,
Expand Down
5 changes: 4 additions & 1 deletion apps/meteor/app/livechat/server/api/v1/config.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { Match, check } from 'meteor/check';
import mem from 'mem';

import { API } from '../../../../api/server';
import { Livechat } from '../../lib/Livechat';
import { settings, findOpenRoom, getExtraConfigInfo, findAgent } from '../lib/livechat';

const cachedSettings = mem(settings, { maxAge: 1000, cacheKey: JSON.stringify });

API.v1.addRoute('livechat/config', {
async get() {
try {
Expand All @@ -20,7 +23,7 @@ API.v1.addRoute('livechat/config', {

const { token, department, businessUnit } = this.queryParams;

const config = await settings({ businessUnit });
const config = await cachedSettings({ businessUnit });

const status = Livechat.online(department);
const guest = token && Livechat.findGuest(token);
Expand Down
8 changes: 5 additions & 3 deletions apps/meteor/app/livechat/server/lib/Contacts.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export const Contacts = {
registerContact({ token, name, email, phone, username, customFields = {}, contactManager = {} } = {}) {
check(token, String);

const visitorEmail = s.trim(email).toLowerCase();

let contactId;
const updateUser = {
$set: {
Expand All @@ -25,7 +27,7 @@ export const Contacts = {

let existingUser = null;

if (s.trim(email) !== '' && (existingUser = LivechatVisitors.findOneGuestByEmailAddress(email))) {
if (visitorEmail !== '' && (existingUser = LivechatVisitors.findOneGuestByEmailAddress(visitorEmail))) {
contactId = existingUser._id;
} else {
const userData = {
Expand All @@ -39,9 +41,9 @@ export const Contacts = {

updateUser.$set.name = name;
updateUser.$set.phone = (phone && [{ phoneNumber: phone }]) || null;
updateUser.$set.visitorEmails = (email && [{ address: email }]) || null;
updateUser.$set.visitorEmails = (visitorEmail && [{ address: visitorEmail }]) || null;

const allowedCF = LivechatCustomField.find({ scope: 'visitor' }).map(({ _id }) => _id);
const allowedCF = LivechatCustomField.find({ scope: 'visitor' }, { fields: { _id: 1 } }).map(({ _id }) => _id);

const livechatData = Object.keys(customFields)
.filter((key) => allowedCF.includes(key) && customFields[key] !== '' && customFields[key] !== undefined)
Expand Down
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 @@ -289,7 +289,7 @@ export const Livechat = {
};

if (email) {
email = email.trim();
email = email.trim().toLowerCase();
validateEmail(email);
updateUser.$set.visitorEmails = [{ address: email }];
}
Expand Down
2 changes: 2 additions & 0 deletions apps/meteor/app/models/server/models/LivechatCustomField.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { Base } from './_Base';
export class LivechatCustomField extends Base {
constructor() {
super('livechat_custom_field');

this.tryEnsureIndex({ scope: 1 });
}

// FIND
Expand Down
2 changes: 2 additions & 0 deletions apps/meteor/app/models/server/models/LivechatDepartment.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export class LivechatDepartment extends Base {
numAgents: 1,
enabled: 1,
});
this.tryEnsureIndex({ parentId: 1 }, { sparse: true });
this.tryEnsureIndex({ ancestors: 1 }, { sparse: true });
}

// FIND
Expand Down
1 change: 1 addition & 0 deletions apps/meteor/app/models/server/models/LivechatInquiry.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export class LivechatInquiry extends Base {
this.tryEnsureIndex({ department: 1 });
this.tryEnsureIndex({ status: 1 }); // 'ready', 'queued', 'taken'
this.tryEnsureIndex({ queueOrder: 1, estimatedWaitingTimeQueue: 1, estimatedServiceTimeAt: 1 });
this.tryEnsureIndex({ 'v.token': 1, 'status': 1 }); // visitor token and status
}

findOneById(inquiryId) {
Expand Down
1 change: 1 addition & 0 deletions apps/meteor/app/models/server/models/LivechatRooms.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export class LivechatRooms extends Base {
this.tryEnsureIndex({ 'v._id': 1 }, { sparse: true });
this.tryEnsureIndex({ t: 1, departmentId: 1, closedAt: 1 }, { partialFilterExpression: { closedAt: { $exists: true } } });
this.tryEnsureIndex({ source: 1 }, { sparse: true });
this.tryEnsureIndex({ departmentAncestors: 1 }, { sparse: true });
}

findLivechat(filter = {}, offset = 0, limit = 20) {
Expand Down
6 changes: 4 additions & 2 deletions apps/meteor/app/models/server/models/LivechatVisitors.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import _ from 'underscore';
import s from 'underscore.string';
import { escapeRegExp } from '@rocket.chat/string-helpers';

import { Base } from './_Base';
import Settings from './Settings';
Expand All @@ -11,6 +10,9 @@ export class LivechatVisitors extends Base {

this.tryEnsureIndex({ token: 1 });
this.tryEnsureIndex({ 'phone.phoneNumber': 1 }, { sparse: true });
this.tryEnsureIndex({ 'visitorEmails.address': 1 }, { sparse: true });
this.tryEnsureIndex({ name: 1 }, { sparse: true });
this.tryEnsureIndex({ username: 1 });
}

/**
Expand Down Expand Up @@ -200,7 +202,7 @@ export class LivechatVisitors extends Base {

findOneGuestByEmailAddress(emailAddress) {
const query = {
'visitorEmails.address': new RegExp(`^${escapeRegExp(emailAddress)}$`, 'i'),
'visitorEmails.address': emailAddress,
sampaiodiego marked this conversation as resolved.
Show resolved Hide resolved
};

return this.findOne(query);
Expand Down
1 change: 0 additions & 1 deletion apps/meteor/app/models/server/models/Users.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ export class Users extends Base {
this.tryEnsureIndex({ statusConnection: 1 }, { sparse: 1 });
this.tryEnsureIndex({ appId: 1 }, { sparse: 1 });
this.tryEnsureIndex({ type: 1 });
this.tryEnsureIndex({ 'visitorEmails.address': 1 });
this.tryEnsureIndex({ federation: 1 }, { sparse: true });
this.tryEnsureIndex({ isRemote: 1 }, { sparse: true });
this.tryEnsureIndex({ 'services.saml.inResponseTo': 1 });
Expand Down
16 changes: 13 additions & 3 deletions apps/meteor/app/models/server/raw/LivechatRooms.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,23 @@ export class LivechatRoomsRaw extends BaseRaw {
firstParams.push(matchUsers);
}
const sort = { $sort: options.sort || { chats: -1 } };
const params = [...firstParams, usersGroup, project, sort];
const pagination = [sort];

if (options.offset) {
params.push({ $skip: options.offset });
pagination.push({ $skip: options.offset });
}
if (options.count) {
params.push({ $limit: options.count });
pagination.push({ $limit: options.count });
}

const facet = {
$facet: {
sortedResults: pagination,
totalCount: [{ $group: { _id: null, total: { $sum: 1 } } }],
},
};

const params = [...firstParams, usersGroup, project, facet];
return this.col.aggregate(params).toArray();
}

Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/models/server/raw/LivechatVisitors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export class LivechatVisitorsRaw extends BaseRaw<ILivechatVisitor> {
const query = {
$or: [
{
'visitorEmails.address': filter,
'visitorEmails.address': _emailOrPhoneOrNameOrUsername,
},
{
'phone.phoneNumber': _emailOrPhoneOrNameOrUsername,
Expand Down
16 changes: 0 additions & 16 deletions apps/meteor/ee/app/livechat-enterprise/server/lib/units.js

This file was deleted.

20 changes: 20 additions & 0 deletions apps/meteor/ee/app/livechat-enterprise/server/lib/units.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Meteor } from 'meteor/meteor';
import mem from 'mem';

import LivechatUnit from '../../../models/server/models/LivechatUnit';

export function hasUnits(): boolean {
// @ts-expect-error - this prop is injected dynamically on ee license
return LivechatUnit.unfilteredFind({ type: 'u' }).count() > 0;
}

// Units should't change really often, so we can cache the result
const memoizedHasUnits = mem(hasUnits, { maxAge: 5000 });

export function getUnitsFromUser(): { [k: string]: any }[] | undefined {
if (!memoizedHasUnits()) {
return;
}

return Meteor.call('livechat:getUnitsFromUser');
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import { Meteor } from 'meteor/meteor';
import mem from 'mem';

import { hasAnyRoleAsync } from '../../../../../app/authorization/server/functions/hasRole';
import LivechatUnit from '../../../models/server/models/LivechatUnit';

export async function getUnitsFromUserRoles(user: Meteor.User | null): Promise<{ [k: string]: any }[] | undefined> {
sampaiodiego marked this conversation as resolved.
Show resolved Hide resolved
if (!user || (await hasAnyRoleAsync(user._id, ['admin', 'livechat-manager']))) {
return;
}

return (await hasAnyRoleAsync(user._id, ['livechat-monitor'])) && LivechatUnit.findByMonitorId(user._id);
}

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

Meteor.methods({
async 'livechat:getUnitsFromUserRoles'() {
'livechat:getUnitsFromUser'(): Promise<{ [k: string]: any }[] | undefined> {
const user = Meteor.user();
if (!user || (await hasAnyRoleAsync(user._id, ['admin', 'livechat-manager']))) {
return;
}

return (await hasAnyRoleAsync(user._id, ['livechat-monitor'])) && LivechatUnit.findByMonitorId(user._id);
return memoizedGetUnitFromUserRoles(user);
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const t = (s: string): string => TAPi18n.__(s, { lng: language });

function getGuestByEmail(email: string, name: string, department = ''): any {
logger.debug(`Attempt to register a guest for ${email} on department: ${department}`);
const guest = LivechatVisitors.findOneGuestByEmailAddress(email);
const guest = LivechatVisitors.findOneGuestByEmailAddress(email.toLowerCase());

if (guest) {
logger.debug(`Guest with email ${email} found with id ${guest._id}`);
Expand Down
2 changes: 2 additions & 0 deletions apps/meteor/server/startup/migrations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,6 @@ import './v256';
import './v257';
import './v258';
import './v259';
import './v260';
import './v261';
sampaiodiego marked this conversation as resolved.
Show resolved Hide resolved
import './xrun';
9 changes: 9 additions & 0 deletions apps/meteor/server/startup/migrations/v260.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { addMigration } from '../../lib/migrations';
import { Users } from '../../../app/models/server';

addMigration({
version: 260,
up() {
Users.tryDropIndex({ 'visitorEmails.address': 1 });
},
});
25 changes: 25 additions & 0 deletions apps/meteor/server/startup/migrations/v261.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { addMigration } from '../../lib/migrations';
import { ILivechatVisitor } from '../../../definition/ILivechatVisitor';
import { Users } from '../../../app/models/server';

// Convert all visitor emails to lowercase
addMigration({
version: 261,
up() {
const updates: unknown[] = [];
Users.find({ 'visitorEmails.address': /[A-Z]/ })
.limit(6000)
sampaiodiego marked this conversation as resolved.
Show resolved Hide resolved
.forEach((user: ILivechatVisitor) => {
const visitorEmails = user.visitorEmails?.map((e) => {
e.address = e.address.toLowerCase();
return e;
});
updates.push({ updateOne: { filter: { _id: user._id }, update: { $set: { visitorEmails } } } });
})
// @ts-expect-error - col is not typed looks like
.then(() => Users.col.bulkWrite(updates))
.then(() => {
console.log('Migration 261: Converted all visitor emails to lowercase');
});
},
});