Skip to content

Commit

Permalink
feat: Automate omnichannel verification process (#30368)
Browse files Browse the repository at this point in the history
  • Loading branch information
bhardwajdisha authored Sep 14, 2023
1 parent 91b924e commit 6b0535e
Show file tree
Hide file tree
Showing 14 changed files with 162 additions and 6 deletions.
9 changes: 9 additions & 0 deletions apps/meteor/app/livechat/lib/messageTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,20 @@ MessageTypes.registerType({
to: message?.transferData?.transferredTo?.name || message?.transferData?.transferredTo?.username || '',
duration: comment,
}),
autoTransferVerifiedChatsToAgent: (): string =>
t(`Livechat_transfer_to_agent_auto_transfer_verified_chat`, {
from,
to: message?.transferData?.transferredTo?.name || message?.transferData?.transferredTo?.username || '',
}),
autoTransferUnansweredChatsToQueue: (): string =>
t(`Livechat_transfer_return_to_the_queue_auto_transfer_unanswered_chat`, {
from,
duration: comment,
}),
autoTransferVerifiedChatsToQueue: (): string =>
t(`Livechat_transfer_return_to_the_queue_auto_transfer_verified_chat`, {
from,
}),
};
return {
transfer: transferTypes[message.transferData.scope](),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { OmnichannelVerification } from '@rocket.chat/core-services';
import { RoomVerificationState, isOmnichannelRoom } from '@rocket.chat/core-typings';

import { callbacks } from '../../../../lib/callbacks';
import { settings } from '../../../settings/server';

callbacks.add(
'afterSaveMessage',
async (message, room) => {
if (!(isOmnichannelRoom(room) && room.v.token)) {
return message;
}
if (message.t) {
return message;
}
if (!message.token) {
return message;
}
if (
room.verificationStatus === RoomVerificationState.unVerified &&
settings.get('Livechat_automate_verification_process') &&
settings.get('Livechat_verificaion_bot_assign') === room.servedBy?.username
) {
await OmnichannelVerification.initiateVerificationProcess(room._id);
}
},
callbacks.priority.LOW,
'automate-verification-process',
);
13 changes: 12 additions & 1 deletion apps/meteor/app/livechat/server/hooks/verificationCheck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { LivechatRooms, Users } from '@rocket.chat/models';
import { callbacks } from '../../../../lib/callbacks';
import { i18n } from '../../../../server/lib/i18n';
import { sendMessage } from '../../../lib/server/functions/sendMessage';
import { settings } from '../../../settings/server';

callbacks.add(
'afterSaveMessage',
Expand All @@ -25,7 +26,11 @@ callbacks.add(
break;
}
case RoomVerificationState.isListeningToOTP: {
const bot = await Users.findOneById('rocket.cat');
const agent = settings.get('Livechat_verificaion_bot_assign') || 'rocket.cat';
const bot =
room?.servedBy?.username === agent
? await Users.findOneByUsername(settings.get('Livechat_verificaion_bot_assign'))
: await Users.findOneById('rocket.cat');
if (message.msg === 'Resend OTP') {
if (room.source.type === 'widget') {
const wrongOtpInstructionsMessage = {
Expand All @@ -51,6 +56,12 @@ callbacks.add(
groupable: false,
};
await sendMessage(bot, completionMessage, room);
if (
settings.get('Livechat_automate_verification_process') &&
room.servedBy?.username === settings.get('Livechat_verificaion_bot_assign')
) {
await OmnichannelVerification.transferChatAfterVerificationProcess(room._id);
}
break;
}
}
Expand Down
1 change: 1 addition & 0 deletions apps/meteor/app/livechat/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import './hooks/saveAnalyticsData';
import './hooks/sendToCRM';
import './hooks/processRoomAbandonment';
import './hooks/saveLastVisitorMessageTs';
import './hooks/automateVerificationProcess';
import './hooks/markRoomNotResponded';
import './hooks/sendEmailTranscriptOnClose';
import './hooks/saveContactLastChat';
Expand Down
5 changes: 4 additions & 1 deletion apps/meteor/app/livechat/server/lib/Helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,10 @@ export const dispatchInquiryQueued = async (inquiry: ILivechatInquiryRecord, age
}
};

export const forwardRoomToAgent = async (room: IOmnichannelRoom, transferData: TransferData) => {
export const forwardRoomToAgent = async (
room: Pick<IOmnichannelRoom, '_id' | 'v' | 'servedBy' | 'open' | 'departmentId'>,
transferData: TransferData,
) => {
if (!room?.open) {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ settings.watch<boolean>('Omnichannel_contact_manager_routing', (value) => {
callbacks.add(
'livechat.checkDefaultAgentOnNewRoom',
async (defaultAgent, defaultGuest) => {
if (settings.get('Livechat_automate_verification_process')) {
const bot = await Users.findOneByUsername(settings.get('Livechat_verificaion_bot_assign'));
const agentId = bot?._id;
const username = bot?.username || 'rocket.cat';
const agent = { agentId, username };
return agent;
}
if (defaultAgent || !defaultGuest) {
return defaultAgent;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class AutoTransferChatSchedulerClass {
departmentId: 1,
});
if (!room?.open || !room?.servedBy?._id) {
throw new Error('Room is not open or is not being served by an agent');
throw new Error('error-room-not-opened-or-serviced');
}

const {
Expand Down
5 changes: 5 additions & 0 deletions apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -1854,6 +1854,7 @@
"Enable_two-factor_authentication": "Enable two-factor authentication via TOTP",
"Enable_two-factor_authentication_email": "Enable two-factor authentication via Email",
"Enable_unlimited_apps": "Enable unlimited apps",
"Enable_verification_process_before_routing_to_agent":"Enable verification process before routing to agent",
"Enabled": "Enabled",
"Encrypted": "Encrypted",
"Encrypted_channel_Description": "End to end encrypted channel. Search will not work with encrypted channels and notifications may not show the messages content.",
Expand Down Expand Up @@ -2058,6 +2059,7 @@
"error-room-onHold": "Error! Room is On Hold",
"error-room-is-already-on-hold": "Error! Room is already On Hold",
"error-room-not-on-hold": "Error! Room is not On Hold",
"error-room-not-opened-or-serviced": "Error! Room is not open or is not being served by an agent",
"error-selected-agent-room-agent-are-same": "The selected agent and the room agent are the same",
"error-starring-message": "Message could not be stared",
"error-tags-must-be-assigned-before-closing-chat": "Tag(s) must be assigned before closing the chat",
Expand Down Expand Up @@ -3099,9 +3101,11 @@
"Livechat_transfer_return_to_the_queue": "{{from}} returned the chat to the queue",
"Livechat_transfer_return_to_the_queue_with_a_comment": "{{from}} returned the chat to the queue with a comment: {{comment}}",
"Livechat_transfer_return_to_the_queue_auto_transfer_unanswered_chat": "{{from}} returned the chat to the queue since it was unanswered for {{duration}} seconds",
"Livechat_transfer_return_to_the_queue_auto_transfer_verified_chat":"{{from}} returned the chat to the queue since it is verified",
"Livechat_transfer_to_agent": "{{from}} transferred the chat to {{to}}",
"Livechat_transfer_to_agent_with_a_comment": "{{from}} transferred the chat to {{to}} with a comment: {{comment}}",
"Livechat_transfer_to_agent_auto_transfer_unanswered_chat": "{{from}} transferred the chat to {{to}} since it was unanswered for {{duration}} seconds",
"Livechat_transfer_to_agent_auto_transfer_verified_chat":"{{from}} transferred the chat to {{to}} since it is verfied now",
"Livechat_transfer_to_department": "{{from}} transferred the chat to the department {{to}}",
"Livechat_transfer_to_department_with_a_comment": "{{from}} transferred the chat to the department {{to}} with a comment: {{comment}}",
"Livechat_transfer_failed_fallback": "The original department ( {{from}} ) doesn't have online agents. Chat succesfully transferred to {{to}}",
Expand Down Expand Up @@ -5408,6 +5412,7 @@
"Value_messages": "{{value}} messages",
"Value_users": "{{value}} users",
"Verification": "Verification",
"Verification_Bot":"Enter the username for assigning the verification bot.",
"Verification_Description": "You may use the following placeholders: \n - `[Verification_Url]` for the verification URL. \n - `[name]`, `[fname]`, `[lname]` for the user's full name, first name or last name, respectively. \n - `[email]` for the user's email. \n - `[Site_Name]` and `[Site_URL]` for the Application Name and URL respectively. ",
"Verification_Email": "Click <a href=\"[Verification_Url]\">here</a> to verify your email address.",
"Verification_email_body": "Please, click on the button below to confirm your email address.",
Expand Down
68 changes: 66 additions & 2 deletions apps/meteor/server/services/omnichannel-verification/service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { IOmnichannelVerification, ISetVisitorEmailResult } from '@rocket.chat/core-services';
import { ServiceClassInternal } from '@rocket.chat/core-services';
import type { IRoom, IMessage, IOmnichannelGenericRoom, IOmnichannelRoom } from '@rocket.chat/core-typings';
import type { IRoom, IMessage, IOmnichannelGenericRoom, IOmnichannelRoom, IUser } from '@rocket.chat/core-typings';
import { RoomVerificationState } from '@rocket.chat/core-typings';
import { Logger } from '@rocket.chat/logger';
import { LivechatVisitors, LivechatRooms, Users } from '@rocket.chat/models';
Expand All @@ -13,7 +13,10 @@ import { check } from 'meteor/check';
import { checkEmailAvailability } from '../../../app/lib/server/functions/checkEmailAvailability';
import { sendMessage } from '../../../app/lib/server/functions/sendMessage';
import { validateEmailDomain } from '../../../app/lib/server/lib';
import { Livechat } from '../../../app/livechat/server';
import { forwardRoomToAgent } from '../../../app/livechat/server/lib/Helper';
import { Livechat as LivechatTyped } from '../../../app/livechat/server/lib/LivechatTyped';
import { RoutingManager } from '../../../app/livechat/server/lib/RoutingManager';
import * as Mailer from '../../../app/mailer/server/api';
import { settings } from '../../../app/settings/server';
import { i18n } from '../../lib/i18n';
Expand All @@ -24,7 +27,7 @@ interface IRandomOTP {
expire: Date;
}

const bot = await Users.findOneById('rocket.cat');
let bot: IUser | null;
export class OmnichannelVerification extends ServiceClassInternal implements IOmnichannelVerification {
protected name = 'omnichannel-verification';

Expand Down Expand Up @@ -219,6 +222,11 @@ export class OmnichannelVerification extends ServiceClassInternal implements IOm
try {
check(rid, String);
const room = await LivechatRooms.findOneById(rid);
const agent = settings.get('Livechat_verificaion_bot_assign') || 'rocket.cat';
bot =
room?.servedBy?.username === agent
? await Users.findOneByUsername(settings.get('Livechat_verificaion_bot_assign'))
: await Users.findOneById('rocket.cat');
if (room?.verificationStatus !== 'unVerified') {
return;
}
Expand Down Expand Up @@ -314,4 +322,60 @@ export class OmnichannelVerification extends ServiceClassInternal implements IOm
return { success: false, error: error as Error };
}
}

async transferChatAfterVerificationProcess(roomId: IRoom['_id']): Promise<void> {
try {
const room = await LivechatRooms.findOneById<Pick<IOmnichannelRoom, '_id' | 'v' | 'servedBy' | 'open' | 'departmentId'>>(roomId, {
projection: {
_id: 1,
v: 1,
servedBy: 1,
open: 1,
departmentId: 1,
},
});
if (!room?.open || !room?.servedBy?._id) {
throw new Error('error-room-not-opened-or-serviced');
}
const {
departmentId,
servedBy: { _id: ignoreAgentId },
} = room;

if (!RoutingManager.getConfig()?.autoAssignAgent) {
this.logger.debug(`Auto-assign agent is disabled, returning room ${roomId} as inquiry`);

await Livechat.returnRoomAsInquiry(room._id, departmentId, {
scope: 'autoTransferVerifiedChatsToQueue',
transferredBy: bot,
});
return;
}

const agent = await RoutingManager.getNextAgent(departmentId, ignoreAgentId);
if (!agent) {
await Livechat.returnRoomAsInquiry(room._id, departmentId, {
scope: 'autoTransferVerifiedChatsToQueue',
transferredBy: bot,
});
this.logger.error(`No agent found to transfer room ${room._id}`);
return;
}
const transferredBy = bot;

if (!transferredBy) {
this.logger.error(`Error while transferring room ${room._id}: user not found`);
return;
}

await forwardRoomToAgent(room, {
userId: agent?.agentId,
transferredBy,
transferredTo: agent,
scope: 'autoTransferVerifiedChatsToAgent',
});
} catch (error) {
this.logger.error(`Error while transferring room`);
}
}
}
16 changes: 16 additions & 0 deletions apps/meteor/server/settings/omnichannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,22 @@ export const createOmniSettings = () =>
enableQuery: omnichannelEnabledQuery,
});

await this.add('Livechat_automate_verification_process', true, {
type: 'boolean',
group: 'Omnichannel',
public: true,
i18nLabel: 'Enable_verification_process_before_routing_to_agent',
enableQuery: omnichannelEnabledQuery,
});

await this.add('Livechat_verificaion_bot_assign', '', {
type: 'string',
group: 'Omnichannel',
public: true,
i18nLabel: 'Verification_Bot',
enableQuery: [{ _id: 'Livechat_automate_verification_process', value: true }, omnichannelEnabledQuery],
});

await this.add('Livechat_webhookUrl', '', {
type: 'string',
group: 'Omnichannel',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ export interface IOmnichannelVerification extends IServiceClass {
setVisitorEmail(room: IOmnichannelRoom, email: string): Promise<ISetVisitorEmailResult>;
sendVerificationCodeToVisitor(visitorId: string, room: IOmnichannelGenericRoom): Promise<void>;
createLivechatMessage(room: IOmnichannelRoom, text: string): Promise<IMessage['_id']>;
transferChatAfterVerificationProcess(roomId: IRoom['_id']): Promise<void>;
}
9 changes: 8 additions & 1 deletion packages/core-typings/src/omnichannel/routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,14 @@ export type TransferData = {
name?: string;
};
clientAction?: boolean;
scope: 'agent' | 'department' | 'queue' | 'autoTransferUnansweredChatsToAgent' | 'autoTransferUnansweredChatsToQueue';
scope:
| 'agent'
| 'department'
| 'queue'
| 'autoTransferUnansweredChatsToAgent'
| 'autoTransferUnansweredChatsToQueue'
| 'autoTransferVerifiedChatsToQueue'
| 'autoTransferVerifiedChatsToAgent';
comment?: string;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export const normalizeTransferHistoryMessage = (
},
autoTransferUnansweredChatsToAgent: () => t('the_chat_was_transferred_to_another_agent_due_to_unanswered', { duration: comment }),
autoTransferUnansweredChatsToQueue: () => t('the_chat_was_moved_back_to_queue_due_to_unanswered', { duration: comment }),
autoTransferVerifiedChatsToAgent: () => t('the_chat_was_transferred_to_another_agent'),
autoTransferVerifiedChatsToQueue: () => t('from_returned_the_chat_to_the_queue', { from }),
};

return transferTypes[scope]();
Expand Down
1 change: 1 addition & 0 deletions packages/livechat/src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"leave_a_message": "Leave a message",
"livechat_connected": "Livechat connected.",
"livechat_is_not_connected": "Livechat is not connected.",
"Livechat_transfer_to_agent_auto_transfer_verified_chat":"{{from}} transferred the chat to {{to}} since it is verfied now",
"media_types_not_accepted": "Media Types Not Accepted.",
"message": "Message",
"messages": "Messages",
Expand Down

0 comments on commit 6b0535e

Please sign in to comment.