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

chore: Improve system messages for omni-visitor abandonment feature #29724

Merged
merged 20 commits into from
Jul 13, 2023
Merged
Show file tree
Hide file tree
Changes from 5 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
7 changes: 7 additions & 0 deletions .changeset/hip-mugs-promise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@rocket.chat/meteor": patch
"@rocket.chat/core-typings": patch
"@rocket.chat/rest-typings": patch
---

fix: Omnichannel Visitor Abandonment feature not working for apps
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,12 @@ callbacks.add(
return params;
}

if (!room.v?.lastMessageTs) {
const contactLastMessageAt = room.contactLastMessageAt || room.v?.lastMessageTs;
if (!contactLastMessageAt) {
return params;
}

const agentLastMessage = await Messages.findAgentLastMessageByVisitorLastMessageTs(room._id, room.v.lastMessageTs);
const agentLastMessage = await Messages.findAgentLastMessageByVisitorLastMessageTs(room._id, contactLastMessageAt);
if (!agentLastMessage) {
return params;
}
Expand Down
10 changes: 10 additions & 0 deletions apps/meteor/app/livechat/server/hooks/saveLastVisitorMessageTs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,25 @@ import { isOmnichannelRoom } from '@rocket.chat/core-typings';
import { LivechatRooms } from '@rocket.chat/models';

import { callbacks } from '../../../../lib/callbacks';
import { callbackLogger } from '../lib/logger';

callbacks.add(
'afterSaveMessage',
async function (message, room) {
if (!(isOmnichannelRoom(room) && room.v.token)) {
return message;
}
if (message.t) {
// skip if message is a special type
return message;
}
if (message.token) {
await LivechatRooms.setVisitorLastMessageTimestampByRoomId(room._id, message.ts);
callbackLogger.debug({
msg: 'Saved last visitor message timestamp',
rid: room._id,
msgId: message._id,
});
}
return message;
},
Expand Down
5 changes: 3 additions & 2 deletions apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,9 @@ export const processWaitingQueue = async (department: string | undefined, inquir
};

export const setPredictedVisitorAbandonmentTime = async (room: IOmnichannelRoom) => {
const contactLastMessageAt = room.contactLastMessageAt || room.v?.lastMessageTs;
if (
!room.v?.lastMessageTs ||
!contactLastMessageAt ||
!settings.get('Livechat_abandoned_rooms_action') ||
settings.get('Livechat_abandoned_rooms_action') === 'none'
) {
Expand All @@ -195,7 +196,7 @@ export const setPredictedVisitorAbandonmentTime = async (room: IOmnichannelRoom)
return;
}

const willBeAbandonedAt = moment(room.v.lastMessageTs).add(Number(secondsToAdd), 'seconds').toDate();
const willBeAbandonedAt = moment(contactLastMessageAt).add(Number(secondsToAdd), 'seconds').toDate();
await LivechatRooms.setPredictedVisitorAbandonmentByRoomId(room._id, willBeAbandonedAt);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,13 @@ export class VisitorInactivityMonitor {

_initializeMessageCache() {
this.messageCache.clear();
this.messageCache.set('default', settings.get('Livechat_abandoned_rooms_closed_custom_message') || i18n.t('Closed_automatically'));
}

async _getDepartmentAbandonedCustomMessage(departmentId: string) {
this.logger.debug(`Getting department abandoned custom message for department ${departmentId}`);
if (this.messageCache.has('departmentId')) {
if (this.messageCache.has(departmentId)) {
this.logger.debug(`Using cached department abandoned custom message for department ${departmentId}`);
return this.messageCache.get('departmentId');
return this.messageCache.get(departmentId);
}
const department = await LivechatDepartment.findOneById(departmentId);
if (!department) {
Expand All @@ -91,7 +90,7 @@ export class VisitorInactivityMonitor {

async closeRooms(room: IOmnichannelRoom) {
this.logger.debug(`Closing room ${room._id}`);
let comment = this.messageCache.get('default');
let comment = await this.getDefaultAbandonedCustomMessage('close', room.v._id);
if (room.departmentId) {
comment = (await this._getDepartmentAbandonedCustomMessage(room.departmentId)) || comment;
}
Expand All @@ -105,22 +104,8 @@ export class VisitorInactivityMonitor {

async placeRoomOnHold(room: IOmnichannelRoom) {
this.logger.debug(`Placing room ${room._id} on hold`);
const timeout = settings.get<number>('Livechat_visitor_inactivity_timeout');

const { v: { _id: visitorId } = {} } = room;
if (!visitorId) {
this.logger.debug(`Room ${room._id} does not have a visitor`);
throw new Error('error-invalid_visitor');
}

const visitor = await LivechatVisitors.findOneById(visitorId);
if (!visitor) {
this.logger.debug(`Room ${room._id} does not have a visitor`);
throw new Error('error-invalid_visitor');
}

const guest = visitor.name || visitor.username;
const comment = i18n.t('Omnichannel_On_Hold_due_to_inactivity', { guest, timeout });
const comment = await this.getDefaultAbandonedCustomMessage('on-hold', room.v._id);

const result = await Promise.allSettled([
OmnichannelEEService.placeRoomOnHold(room, comment, this.user),
Expand Down Expand Up @@ -170,4 +155,41 @@ export class VisitorInactivityMonitor {

this._initializeMessageCache();
}

private async getDefaultAbandonedCustomMessage(abandonmentAction: 'close' | 'on-hold', visitorId: string) {
this.logger.debug(`Getting default abandoned custom message for ${abandonmentAction}`);
KevLehman marked this conversation as resolved.
Show resolved Hide resolved

const visitor = await LivechatVisitors.findOneById(visitorId, {
murtaza98 marked this conversation as resolved.
Show resolved Hide resolved
projection: {
name: 1,
username: 1,
},
});
if (!visitor) {
this.logger.error({
msg: 'Error getting default abandoned custom message: visitor not found',
visitorId,
});
throw new Error('error-invalid_visitor');
}

const timeout = settings.get<number>('Livechat_visitor_inactivity_timeout');

const guest = visitor.name || visitor.username;

if (abandonmentAction === 'on-hold') {
return i18n.t('Omnichannel_On_Hold_due_to_inactivity', {
guest,
timeout,
});
}

return (
settings.get<string>('Livechat_abandoned_rooms_closed_custom_message') ||
i18n.t('Omnichannel_chat_closed_due_to_inactivity', {
guest,
timeout,
})
);
}
}
3 changes: 2 additions & 1 deletion apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -3079,6 +3079,7 @@
"Livechat_offline": "Omnichannel offline",
"Livechat_offline_message_sent": "Livechat offline message sent",
"Livechat_OfflineMessageToChannel_enabled": "Send Livechat offline messages to a channel",
"Omnichannel_chat_closed_due_to_inactivity": "The chat was automatically closed because we haven't received any reply from {{guest}} in {{timeout}} seconds",
"Omnichannel_on_hold_chat_resumed": "On Hold Chat Resumed: {{comment}}",
"Omnichannel_on_hold_chat_automatically": "The chat was automatically resumed from On Hold upon receiving a new message from {{guest}}",
"Omnichannel_on_hold_chat_resumed_manually": "The chat was manually resumed from On Hold by {{user}}",
Expand Down Expand Up @@ -5939,4 +5940,4 @@
"Uninstall_grandfathered_app": "Uninstall {{appName}}?",
"App_will_lose_grandfathered_status": "**This {{context}} app will lose its grandfathered status.** \n \nWorkspaces on Community Edition can have up to {{limit}} {{context}} apps enabled. Grandfathered apps count towards the limit but the limit is not applied to them.",
"Theme_Appearence": "Theme Appearence"
}
}
4 changes: 2 additions & 2 deletions apps/meteor/server/models/raw/LivechatRooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2383,13 +2383,13 @@ export class LivechatRoomsRaw extends BaseRaw<IOmnichannelRoom> implements ILive
return this.deleteOne(query);
}

setVisitorLastMessageTimestampByRoomId(roomId: string, lastMessageTs: Date) {
setVisitorLastMessageTimestampByRoomId(roomId: string, contactLastMessageAt: Date) {
const query = {
_id: roomId,
};
const update = {
$set: {
'v.lastMessageTs': lastMessageTs,
contactLastMessageAt,
murtaza98 marked this conversation as resolved.
Show resolved Hide resolved
},
};

Expand Down
5 changes: 5 additions & 0 deletions packages/core-typings/src/IInquiry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ export interface IVisitor {
token: string;
status: 'online' | 'busy' | 'away' | 'offline';
phone?: string | null;

/**
* @deprecated
* use `room.contactLastMessageTs` instead
* */
lastMessageTs?: Date;
}

Expand Down
3 changes: 3 additions & 0 deletions packages/core-typings/src/IRoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,9 @@ export interface IOmnichannelRoom extends IOmnichannelGenericRoom {
// which is controlled by Livechat_auto_transfer_chat_timeout setting
autoTransferredAt?: Date;
autoTransferOngoing?: boolean;

// The timestamp when the contact last sent a message
contactLastMessageAt?: Date;
}

export interface IVoipRoom extends IOmnichannelGenericRoom {
Expand Down
4 changes: 2 additions & 2 deletions packages/rest-typings/src/v1/omnichannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -955,8 +955,8 @@ export type LivechatRoomsProps = {

export type VisitorSearchChatsResult = Pick<
IOmnichannelRoom,
'fname' | 'ts' | 'msgs' | 'servedBy' | 'closedAt' | 'closedBy' | 'closer' | 'tags' | '_id' | 'closingMessage'
> & { v: Omit<IOmnichannelRoom['v'], 'lastMessageTs'> };
'fname' | 'ts' | 'msgs' | 'servedBy' | 'closedAt' | 'closedBy' | 'closer' | 'tags' | '_id' | 'closingMessage' | 'v'
>;

const LivechatRoomsSchema = {
type: 'object',
Expand Down