-
Notifications
You must be signed in to change notification settings - Fork 13.5k
[NEW][ENTERPRISE] Auto close abandoned Omnichannel rooms #17055
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
Changes from all commits
7cf020f
e4b1f55
0bda9f2
41a7d41
0e25d59
798b7f0
e7f3e16
11ea67a
f37c04f
36cbd48
9012947
07bab28
4e538fa
3077a4b
dd1fc4b
0bb1c4d
7817726
cf4bc21
1d2c721
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
|
|
||
| import { callbacks } from '../../../callbacks'; | ||
| import { LivechatRooms } from '../../../models'; | ||
|
|
||
| callbacks.add('afterSaveMessage', function(message, room) { | ||
| // skips this callback if the message was edited | ||
| if (!message || message.editedAt) { | ||
| return message; | ||
| } | ||
|
|
||
| // if the message has not a token, it was sent by the agent, so ignore it | ||
| if (!message.token) { | ||
| return message; | ||
| } | ||
|
|
||
| // check if room is yet awaiting for response | ||
| if (typeof room.t !== 'undefined' && room.t === 'l' && room.waitingResponse) { | ||
| return message; | ||
| } | ||
|
|
||
| LivechatRooms.setNotResponseByRoomId(room._id); | ||
|
|
||
| return message; | ||
| }, callbacks.priority.LOW, 'markRoomNotResponded'); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| import { callbacks } from '../../../../../app/callbacks/server'; | ||
| import { settings } from '../../../../../app/settings/server'; | ||
| import { setPredictedVisitorAbandonmentTime } from '../lib/Helper'; | ||
|
|
||
| callbacks.add('afterSaveMessage', function(message, room) { | ||
| if (!settings.get('Livechat_auto_close_abandoned_rooms') || settings.get('Livechat_visitor_inactivity_timeout') <= 0) { | ||
| return message; | ||
| } | ||
| // skips this callback if the message was edited | ||
| if (message.editedAt) { | ||
| return false; | ||
| } | ||
| // message valid only if it is a livechat room | ||
| if (!(typeof room.t !== 'undefined' && room.t === 'l' && room.v && room.v.token)) { | ||
| return false; | ||
| } | ||
| // if the message has a type means it is a special message (like the closing comment), so skips | ||
| if (message.t) { | ||
| return false; | ||
| } | ||
| const sentByAgent = !message.token; | ||
| if (sentByAgent) { | ||
| setPredictedVisitorAbandonmentTime(room); | ||
| } | ||
| return message; | ||
| }, callbacks.priority.HIGH, 'save-visitor-inactivity'); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| import { SyncedCron } from 'meteor/littledata:synced-cron'; | ||
| import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; | ||
|
|
||
| import { settings } from '../../../../../app/settings/server'; | ||
| import { LivechatRooms, LivechatDepartment, Users } from '../../../../../app/models/server'; | ||
| import { Livechat } from '../../../../../app/livechat/server/lib/Livechat'; | ||
|
|
||
| export class VisitorInactivityMonitor { | ||
| constructor() { | ||
| this._started = false; | ||
| this._name = 'Omnichannel Visitor Inactivity Monitor'; | ||
| this.messageCache = new Map(); | ||
| this.userToPerformAutomaticClosing; | ||
| } | ||
|
|
||
| start() { | ||
|
MarcosSpessatto marked this conversation as resolved.
|
||
| this._startMonitoring(); | ||
| this._initializeMessageCache(); | ||
| this.userToPerformAutomaticClosing = Users.findOneById('rocket.cat'); | ||
| } | ||
|
|
||
| _startMonitoring() { | ||
| if (this.isRunning()) { | ||
| return; | ||
| } | ||
| const everyMinute = '* * * * *'; | ||
| SyncedCron.add({ | ||
| name: this._name, | ||
| schedule: (parser) => parser.cron(everyMinute), | ||
| job: () => { | ||
| this.handleAbandonedRooms(); | ||
| }, | ||
| }); | ||
| this._started = true; | ||
| } | ||
|
|
||
| stop() { | ||
| if (!this.isRunning()) { | ||
| return; | ||
| } | ||
|
|
||
| SyncedCron.remove(this._name); | ||
|
|
||
| this._started = false; | ||
| } | ||
|
|
||
| isRunning() { | ||
| return this._started; | ||
| } | ||
|
|
||
| _initializeMessageCache() { | ||
| this.messageCache.clear(); | ||
| this.messageCache.set('default', settings.get('Livechat_abandoned_rooms_closed_custom_message') || TAPi18n.__('Closed_automatically')); | ||
| } | ||
|
|
||
| _getDepartmentAbandonedCustomMessage(departmentId) { | ||
| if (this.messageCache.has('departmentId')) { | ||
| return this.messageCache.get('departmentId'); | ||
| } | ||
| const department = LivechatDepartment.findOneById(departmentId); | ||
| if (!department) { | ||
| return; | ||
| } | ||
| this.messageCache.set(department._id, department.abandonedRoomsCloseCustomMessage); | ||
| return department.abandonedRoomsCloseCustomMessage; | ||
| } | ||
|
|
||
| closeRooms(room) { | ||
| let comment = this.messageCache.get('default'); | ||
| if (room.departmentId) { | ||
| comment = this._getDepartmentAbandonedCustomMessage(room.departmentId) || comment; | ||
| } | ||
| Livechat.closeRoom({ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The user who is closing the chats is required here. So, my suggestion is to use |
||
| comment, | ||
| room, | ||
| user: this.userToPerformAutomaticClosing, | ||
| }); | ||
| } | ||
|
|
||
| handleAbandonedRooms() { | ||
| if (!settings.get('Livechat_auto_close_abandoned_rooms')) { | ||
| return; | ||
| } | ||
| LivechatRooms.findAbandonedOpenRooms(new Date()).forEach((room) => this.closeRooms(room)); | ||
| this._initializeMessageCache(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,24 @@ | ||
| import { Meteor } from 'meteor/meteor'; | ||
|
|
||
| import { settings } from '../../../../app/settings'; | ||
| import { checkWaitingQueue } from './lib/Helper'; | ||
| import { checkWaitingQueue, updatePredictedVisitorAbandonment } from './lib/Helper'; | ||
| import { VisitorInactivityMonitor } from './lib/VisitorInactivityMonitor'; | ||
| import './lib/query.helper'; | ||
|
|
||
| const visitorActivityMonitor = new VisitorInactivityMonitor(); | ||
|
|
||
| Meteor.startup(function() { | ||
| settings.onload('Livechat_maximum_chats_per_agent', function(/* key, value */) { | ||
| checkWaitingQueue(); | ||
| }); | ||
| settings.onload('Livechat_auto_close_abandoned_rooms', function(_, value) { | ||
| updatePredictedVisitorAbandonment(); | ||
| if (!value) { | ||
| return visitorActivityMonitor.stop(); | ||
| } | ||
| visitorActivityMonitor.start(); | ||
| }); | ||
| settings.onload('Livechat_visitor_inactivity_timeout', function() { | ||
| updatePredictedVisitorAbandonment(); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method needs to be renamed on the Model as well.
The old name is still there.