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

refactor(omnichannel): afterTakeInquiry room as required param #32389

Merged
merged 6 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
9 changes: 1 addition & 8 deletions apps/meteor/app/livechat/server/hooks/sendToCRM.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,18 +180,11 @@ callbacks.add(

callbacks.add(
'livechat.afterTakeInquiry',
async (inquiry) => {
async ({ inquiry, room }) => {
if (!settings.get('Livechat_webhook_on_chat_taken')) {
return inquiry;
}

const { rid } = inquiry;
const room = await LivechatRooms.findOneById(rid);

if (!room) {
return inquiry;
}

return sendToCRM('LivechatSessionTaken', room);
},
callbacks.priority.MEDIUM,
Expand Down
24 changes: 17 additions & 7 deletions apps/meteor/app/livechat/server/lib/Helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,9 +444,14 @@ export const forwardRoomToAgent = async (room: IOmnichannelRoom, transferData: T
// There are some Enterprise features that may interrupt the forwarding process
// Due to that we need to check whether the agent has been changed or not
logger.debug(`Forwarding inquiry ${inquiry._id} to agent ${agent.agentId}`);
const roomTaken = await RoutingManager.takeInquiry(inquiry, agent, {
...(clientAction && { clientAction }),
});
const roomTaken = await RoutingManager.takeInquiry(
inquiry,
agent,
{
...(clientAction && { clientAction }),
},
room,
);
if (!roomTaken) {
logger.debug(`Cannot forward inquiry ${inquiry._id}`);
return false;
Expand Down Expand Up @@ -571,10 +576,15 @@ export const forwardRoomToDepartment = async (room: IOmnichannelRoom, guest: ILi
// Fake the department to forward the inquiry - Case the forward process does not success
// the inquiry will stay in the same original department
inquiry.department = departmentId;
const roomTaken = await RoutingManager.delegateInquiry(inquiry, agent, {
forwardingToDepartment: { oldDepartmentId },
...(clientAction && { clientAction }),
});
const roomTaken = await RoutingManager.delegateInquiry(
inquiry,
agent,
{
forwardingToDepartment: { oldDepartmentId },
...(clientAction && { clientAction }),
},
room,
);
if (!roomTaken) {
logger.debug(`Cannot forward room ${room._id}. Unable to delegate inquiry`);
return false;
Expand Down
4 changes: 2 additions & 2 deletions apps/meteor/app/livechat/server/lib/QueueManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const queueInquiry = async (inquiry: ILivechatInquiryRecord, defaultAgent
logger.debug(`Delegating inquiry with id ${inquiry._id} to agent ${defaultAgent?.username}`);

await callbacks.run('livechat.beforeRouteChat', inquiry, inquiryAgent);
const room = await LivechatRooms.findOneById(inquiry.rid, { projection: { v: 1 } });
const room = await LivechatRooms.findOneById(inquiry.rid);
if (!room || !(await Omnichannel.isWithinMACLimit(room))) {
logger.error({ msg: 'MAC limit reached, not routing inquiry', inquiry });
// We'll queue these inquiries so when new license is applied, they just start rolling again
Expand All @@ -61,7 +61,7 @@ export const queueInquiry = async (inquiry: ILivechatInquiryRecord, defaultAgent

if (dbInquiry.status === 'ready') {
logger.debug(`Inquiry with id ${inquiry._id} is ready. Delegating to agent ${inquiryAgent?.username}`);
return RoutingManager.delegateInquiry(dbInquiry, inquiryAgent);
return RoutingManager.delegateInquiry(dbInquiry, inquiryAgent, undefined, room);
}
};

Expand Down
39 changes: 23 additions & 16 deletions apps/meteor/app/livechat/server/lib/RoutingManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,23 @@ type Routing = {
inquiry: InquiryWithAgentInfo,
agent?: SelectedAgent | null,
options?: { clientAction?: boolean; forwardingToDepartment?: { oldDepartmentId?: string; transferData?: any } },
room?: IOmnichannelRoom,
): Promise<(IOmnichannelRoom & { chatQueued?: boolean }) | null | void>;
assignAgent(inquiry: InquiryWithAgentInfo, agent: SelectedAgent): Promise<InquiryWithAgentInfo>;
unassignAgent(inquiry: ILivechatInquiryRecord, departmentId?: string, shouldQueue?: boolean): Promise<boolean>;
takeInquiry(
inquiry: Omit<
ILivechatInquiryRecord,
'estimatedInactivityCloseTimeAt' | 'message' | 't' | 'source' | 'estimatedWaitingTimeQueue' | 'priorityWeight' | '_updatedAt'
>,
agent: SelectedAgent | null,
options?: { clientAction?: boolean; forwardingToDepartment?: { oldDepartmentId?: string; transferData?: any } },
options: { clientAction?: boolean; forwardingToDepartment?: { oldDepartmentId?: string; transferData?: any } },
room: IOmnichannelRoom,
): Promise<IOmnichannelRoom | null | void>;
transferRoom(room: IOmnichannelRoom, guest: ILivechatVisitor, transferData: TransferData): Promise<boolean>;
delegateAgent(agent: SelectedAgent | undefined, inquiry: ILivechatInquiryRecord): Promise<SelectedAgent | null | undefined>;
removeAllRoomSubscriptions(room: Pick<IOmnichannelRoom, '_id'>, ignoreUser?: { _id: string }): Promise<void>;

assignAgent(inquiry: InquiryWithAgentInfo, room: IOmnichannelRoom, agent: SelectedAgent): Promise<InquiryWithAgentInfo>;
};

export const RoutingManager: Routing = {
Expand Down Expand Up @@ -101,7 +104,7 @@ export const RoutingManager: Routing = {
return this.getMethod().getNextAgent(department, ignoreAgentId);
},

async delegateInquiry(inquiry, agent, options = {}) {
async delegateInquiry(inquiry, agent, options = {}, room) {
const { department, rid } = inquiry;
logger.debug(`Attempting to delegate inquiry ${inquiry._id}`);
if (!agent || (agent.username && !(await Users.findOneOnlineAgentByUserList(agent.username)) && !(await allowAgentSkipQueue(agent)))) {
Expand All @@ -117,11 +120,15 @@ export const RoutingManager: Routing = {
return LivechatRooms.findOneById(rid);
}

if (!room) {
throw new Meteor.Error('error-invalid-room');
}

logger.debug(`Inquiry ${inquiry._id} will be taken by agent ${agent.agentId}`);
return this.takeInquiry(inquiry, agent, options);
return this.takeInquiry(inquiry, agent, options, room);
},

async assignAgent(inquiry, agent) {
async assignAgent(inquiry: InquiryWithAgentInfo, room: IOmnichannelRoom, agent: SelectedAgent): Promise<InquiryWithAgentInfo> {
check(
agent,
Match.ObjectIncluding({
Expand All @@ -142,19 +149,14 @@ export const RoutingManager: Routing = {
await Rooms.incUsersCountById(rid, 1);

const user = await Users.findOneById(agent.agentId);
const room = await LivechatRooms.findOneById(rid);

if (user) {
await Promise.all([Message.saveSystemMessage('command', rid, 'connected', user), Message.saveSystemMessage('uj', rid, '', user)]);
}

if (!room) {
logger.debug(`Cannot assign agent to inquiry ${inquiry._id}: Room not found`);
throw new Meteor.Error('error-room-not-found', 'Room not found');
}

await dispatchAgentDelegated(rid, agent.agentId);
logger.debug(`Agent ${agent.agentId} assigned to inquriy ${inquiry._id}. Instances notified`);

logger.debug(`Agent ${agent.agentId} assigned to inquiry ${inquiry._id}. Instances notified`);

void Apps.self?.getBridges()?.getListenerBridge().livechatEvent(AppEvents.IPostLivechatAgentAssigned, { room, user });
return inquiry;
Expand Down Expand Up @@ -206,7 +208,7 @@ export const RoutingManager: Routing = {
return true;
},

async takeInquiry(inquiry, agent, options = { clientAction: false }) {
async takeInquiry(inquiry, agent, options = { clientAction: false }, room) {
check(
agent,
Match.ObjectIncluding({
Expand All @@ -227,7 +229,6 @@ export const RoutingManager: Routing = {
logger.debug(`Attempting to take Inquiry ${inquiry._id} [Agent ${agent.agentId}] `);

const { _id, rid } = inquiry;
const room = await LivechatRooms.findOneById(rid);
if (!room?.open) {
logger.debug(`Cannot take Inquiry ${inquiry._id}: Room is closed`);
return room;
Expand Down Expand Up @@ -262,10 +263,16 @@ export const RoutingManager: Routing = {

await LivechatInquiry.takeInquiry(_id);

const inq = await this.assignAgent(inquiry as InquiryWithAgentInfo, agent);
logger.info(`Inquiry ${inquiry._id} taken by agent ${agent.agentId}`);

callbacks.runAsync('livechat.afterTakeInquiry', inq, agent);
callbacks.runAsync(
'livechat.afterTakeInquiry',
{
inquiry: await this.assignAgent(inquiry as InquiryWithAgentInfo, room, agent),
room,
},
agent,
);

void notifyOnLivechatInquiryChangedById(inquiry._id, 'updated', {
status: LivechatInquiryStatus.TAKEN,
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/livechat/server/methods/takeInquiry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export const takeInquiry = async (
};

try {
await RoutingManager.takeInquiry(inquiry, agent, options);
await RoutingManager.takeInquiry(inquiry, agent, options ?? {}, room);
} catch (e: any) {
throw new Meteor.Error(e.message);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { cbLogger } from '../lib/logger';

callbacks.add(
'livechat.afterTakeInquiry',
async (inquiry) => {
async ({ inquiry }) => {
if (!settings.get('Livechat_waiting_queue')) {
return inquiry;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,19 @@ import { settings } from '../../../../../app/settings/server';
import { callbacks } from '../../../../../lib/callbacks';
import { OmnichannelQueueInactivityMonitor } from '../lib/QueueInactivityMonitor';

const removeScheduledQueueCloseTime = async (inquiry: any): Promise<void> => {
if (!inquiry?._id) {
return;
}
await OmnichannelQueueInactivityMonitor.stopInquiry(inquiry._id);
};

settings.watch('Livechat_max_queue_wait_time', (value: number) => {
if (!value || value < 0) {
callbacks.remove('livechat.afterTakeInquiry', 'livechat-after-inquiry-taken-remove-schedule');
return;
}
callbacks.add(
'livechat.afterTakeInquiry',
removeScheduledQueueCloseTime,
async ({ inquiry }): Promise<void> => {
if (!inquiry?._id) {
return;
}
await OmnichannelQueueInactivityMonitor.stopInquiry(inquiry._id);
},
ggazzo marked this conversation as resolved.
Show resolved Hide resolved
callbacks.priority.HIGH,
'livechat-after-inquiry-taken-remove-schedule',
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ settings.watch<boolean>('Livechat_last_chatted_agent_routing', (value) => {

callbacks.add(
'livechat.afterTakeInquiry',
async (inquiry, agent) => {
async ({ inquiry }, agent) => {
if (!inquiry || !agent) {
return inquiry;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { IMessage, IOmnichannelRoom, IRoom } from '@rocket.chat/core-typings';
import { isOmnichannelRoom } from '@rocket.chat/core-typings';
import { LivechatRooms } from '@rocket.chat/models';

import type { CloseRoomParams } from '../../../../../app/livechat/server/lib/LivechatTyped';
import { settings } from '../../../../../app/settings/server';
Expand All @@ -15,31 +14,6 @@ type LivechatCloseCallbackParams = {

let autoTransferTimeout = 0;

const handleAfterTakeInquiryCallback = async (inquiry: any = {}): Promise<any> => {
const { rid } = inquiry;
if (!rid?.trim()) {
return;
}

if (!autoTransferTimeout || autoTransferTimeout <= 0) {
return inquiry;
}

const room = await LivechatRooms.findOneById(rid, { projection: { _id: 1, autoTransferredAt: 1, autoTransferOngoing: 1 } });
if (!room) {
return inquiry;
}

if (room.autoTransferredAt || room.autoTransferOngoing) {
return inquiry;
}

cbLogger.info(`Room ${room._id} will be scheduled to be auto transfered after ${autoTransferTimeout} seconds`);
await AutoTransferChatScheduler.scheduleRoom(rid, autoTransferTimeout as number);

return inquiry;
};

const handleAfterSaveMessage = async (message: IMessage, room: IRoom | undefined): Promise<IMessage> => {
if (!room || !isOmnichannelRoom(room)) {
return message;
Expand Down Expand Up @@ -106,7 +80,21 @@ settings.watch('Livechat_auto_transfer_chat_timeout', (value) => {

callbacks.add(
'livechat.afterTakeInquiry',
handleAfterTakeInquiryCallback,
async ({ inquiry, room }): Promise<any> => {
const { rid } = inquiry;
if (!rid?.trim()) {
return;
}

if (room.autoTransferredAt || room.autoTransferOngoing) {
return inquiry;
}

cbLogger.info(`Room ${room._id} will be scheduled to be auto transfered after ${autoTransferTimeout} seconds`);
await AutoTransferChatScheduler.scheduleRoom(rid, autoTransferTimeout as number);

return inquiry;
},
callbacks.priority.MEDIUM,
'livechat-auto-transfer-job-inquiry',
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ Meteor.methods<ServerMethods>({
const {
servedBy: { _id: agentId, username },
} = room;
await RoutingManager.takeInquiry(inquiry, { agentId, username }, options);
await RoutingManager.takeInquiry(inquiry, { agentId, username }, options, room);

const onHoldChatResumedBy = options.clientAction ? await Meteor.userAsync() : await Users.findOneById('rocket.cat');
if (!onHoldChatResumedBy) {
Expand Down
5 changes: 4 additions & 1 deletion apps/meteor/lib/callbacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ interface EventLikeCallbackSignatures {
'livechat.setUserStatusLivechat': (params: { userId: IUser['_id']; status: OmnichannelAgentStatus }) => void;
'livechat.agentStatusChanged': (params: { userId: IUser['_id']; status: UserStatus }) => void;
'livechat.onNewAgentCreated': (agentId: string) => void;
'livechat.afterTakeInquiry': (inq: InquiryWithAgentInfo, agent: { agentId: string; username: string }) => void;
'livechat.afterTakeInquiry': (
params: { inquiry: InquiryWithAgentInfo; room: IOmnichannelRoom },
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, '_id' | 'federated' | 'roles'>; inviter: IUser }) => void;
Expand Down
6 changes: 1 addition & 5 deletions apps/meteor/server/models/raw/LivechatRooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2284,15 +2284,11 @@ export class LivechatRoomsRaw extends BaseRaw<IOmnichannelRoom> implements ILive
servedBy: {
_id: newAgent.agentId,
username: newAgent.username,
ts: new Date(),
ts: newAgent.ts ?? new Date(),
},
},
};

if (newAgent.ts) {
update.$set.servedBy.ts = newAgent.ts;
}

return this.updateOne(query, update);
}

Expand Down
7 changes: 2 additions & 5 deletions apps/meteor/server/services/omnichannel/queue.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { IOmnichannelRoom } from '@rocket.chat/core-typings';
import { type InquiryWithAgentInfo, type IOmnichannelQueue } from '@rocket.chat/core-typings';
import { License } from '@rocket.chat/license';
import { LivechatInquiry, LivechatRooms } from '@rocket.chat/models';
Expand Down Expand Up @@ -186,9 +185,7 @@ export class OmnichannelQueue implements IOmnichannelQueue {
queueLogger.debug(`Processing inquiry ${inquiry._id} from queue ${queue}`);
const { defaultAgent } = inquiry;

const roomFromDb = await LivechatRooms.findOneById<Pick<IOmnichannelRoom, '_id' | 'servedBy' | 'closedAt'>>(inquiry.rid, {
projection: { servedBy: 1, closedAt: 1 },
});
const roomFromDb = await LivechatRooms.findOneById(inquiry.rid);

// This is a precaution to avoid taking inquiries tied to rooms that no longer exist.
// This should never happen.
Expand All @@ -206,7 +203,7 @@ export class OmnichannelQueue implements IOmnichannelQueue {
return this.reconciliation('closed', { roomId: inquiry.rid, inquiryId: inquiry._id });
}

const room = await RoutingManager.delegateInquiry(inquiry, defaultAgent);
const room = await RoutingManager.delegateInquiry(inquiry, defaultAgent, undefined, roomFromDb);

if (room?.servedBy) {
const {
Expand Down
Loading