-
Notifications
You must be signed in to change notification settings - Fork 13.8k
[NEW] Livechat analytics #15230
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
Merged
Merged
[NEW] Livechat analytics #15230
Changes from all commits
Commits
Show all changes
33 commits
Select commit
Hold shift + click to select a range
f3241e8
Add livechat transfer history to room object
4493e70
Add metrics to get the visitor inactivity on livechat room close
ef4ebff
Add setting to know when its considered visitor abandonment
c15b6a0
Merge branch 'develop' into livechat-analytics
1c66120
Add collection to track available time and history of livechat agents
c6d196b
Change date format
8b00fe6
Merge branch 'develop' into livechat-analytics
50803ca
Merge branch 'develop' into livechat-analytics
efcda9e
Merge remote-tracking branch 'origin/livechat-analytics' into livecha…
d579936
Merge branch 'develop' into livechat-analytics
f1a7f43
Apply suggestions from review
6960c6f
Merge branch 'develop' into livechat-analytics
8acf467
Merge branch 'develop' into livechat-analytics
renatobecker 6f2ae7b
Merge branch 'develop' into livechat-analytics
81c1376
Apply suggestions from review
eaf4360
Apply suggestions from review
9c51a37
fix property name
0fd5a96
Rename var and add ptbr translation
c5c1dc9
code improve
0e7b1a1
improve
4eb2772
Fix typo.
renatobecker 8b7bb33
Fix typos.
renatobecker 738be31
livechat agent history save history by day
0aa93f0
Convert livechat room transfer data to system message
032b5ee
Merge branch 'develop' into livechat-analytics
e47cc80
Change query to retrieve transferred data from messages
2421b94
remove code duplication
ab97a6b
Add support to filter by department id
29d0500
Code improve
862e93f
Fix message filter
d3be44e
Fix cronjob
b6dc580
Fix livechat agent service history
d4ab16e
Add a condition to read the department model.
renatobecker File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| import moment from 'moment'; | ||
|
|
||
| import { settings } from '../../../settings'; | ||
| import { callbacks } from '../../../callbacks'; | ||
| import { LivechatRooms, Messages, LivechatOfficeHour } from '../../../models'; | ||
|
|
||
| const getSecondsWhenOfficeHoursIsDisabled = (room, agentLastMessage) => moment(new Date(room.closedAt)).diff(moment(new Date(agentLastMessage.ts)), 'seconds'); | ||
| const getOfficeHoursDictionary = () => LivechatOfficeHour.find().fetch().reduce((acc, day) => { | ||
| acc[day.day] = { | ||
| start: day.start, | ||
| finish: day.finish, | ||
| open: day.open, | ||
| }; | ||
| return acc; | ||
| }, {}); | ||
| const getSecondsSinceLastAgentResponse = (room, agentLastMessage) => { | ||
| if (!settings.get('Livechat_enable_office_hours')) { | ||
| return getSecondsWhenOfficeHoursIsDisabled(room, agentLastMessage); | ||
| } | ||
|
|
||
| let totalSeconds = 0; | ||
| const officeDays = getOfficeHoursDictionary(); | ||
| const endOfConversation = moment(new Date(room.closedAt)); | ||
| const startOfInactivity = moment(new Date(agentLastMessage.ts)); | ||
| const daysOfInactivity = endOfConversation.clone().startOf('day').diff(startOfInactivity.clone().startOf('day'), 'days'); | ||
| const inactivityDay = moment(new Date(agentLastMessage.ts)); | ||
|
|
||
| for (let index = 0; index <= daysOfInactivity; index++) { | ||
| const today = inactivityDay.clone().format('dddd'); | ||
| const officeDay = officeDays[today]; | ||
| const startTodaysOfficeHour = moment(officeDay.start, 'HH:mm').add(index, 'days'); | ||
| const endTodaysOfficeHour = moment(officeDay.finish, 'HH:mm').add(index, 'days'); | ||
| if (officeDays[today].open) { | ||
| const firstDayOfInactivity = startOfInactivity.clone().format('D') === inactivityDay.clone().format('D'); | ||
| const lastDayOfInactivity = endOfConversation.clone().format('D') === inactivityDay.clone().format('D'); | ||
|
|
||
| if (!firstDayOfInactivity && !lastDayOfInactivity) { | ||
| totalSeconds += endTodaysOfficeHour.clone().diff(startTodaysOfficeHour, 'seconds'); | ||
| } else { | ||
| const end = endOfConversation.isBefore(endTodaysOfficeHour) ? endOfConversation : endTodaysOfficeHour; | ||
| const start = firstDayOfInactivity ? inactivityDay : startTodaysOfficeHour; | ||
| totalSeconds += end.clone().diff(start, 'seconds'); | ||
| } | ||
| } | ||
| inactivityDay.add(1, 'days'); | ||
| } | ||
| return totalSeconds; | ||
| }; | ||
|
|
||
| callbacks.add('livechat.closeRoom', (room) => { | ||
| const closedByAgent = room.closer !== 'visitor'; | ||
| const wasTheLastMessageSentByAgent = room.lastMessage && !room.lastMessage.token; | ||
| if (closedByAgent && wasTheLastMessageSentByAgent) { | ||
| const agentLastMessage = Messages.findAgentLastMessageByVisitorLastMessageTs(room._id, room.v.lastMessageTs).fetch()[0]; | ||
| const secondsSinceLastAgentResponse = getSecondsSinceLastAgentResponse(room, agentLastMessage); | ||
| LivechatRooms.setVisitorInactivityInSecondsByRoomId(room._id, secondsSinceLastAgentResponse); | ||
| } | ||
| }, callbacks.priority.HIGH, 'process-room-abandonment'); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import { callbacks } from '../../../callbacks'; | ||
| import { LivechatRooms } from '../../../models'; | ||
|
|
||
| callbacks.add('afterSaveMessage', function(message, room) { | ||
| if (!(typeof room.t !== 'undefined' && room.t === 'l' && room.v && room.v.token)) { | ||
| return message; | ||
| } | ||
| if (message.token) { | ||
| LivechatRooms.setVisitorLastMessageTimestampByRoomId(room._id, message.ts); | ||
| } | ||
| return message; | ||
| }, callbacks.priority.HIGH, 'save-last-visitor-message-timestamp'); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -257,3 +257,18 @@ export const forwardRoomToDepartment = async (room, guest, departmentId) => { | |||||||||||||||||
|
|
||||||||||||||||||
| return true; | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| export const normalizeTransferredByData = (transferredBy, room) => { | ||||||||||||||||||
| if (!transferredBy || !room) { | ||||||||||||||||||
| throw new Error('You must provide "transferredBy" and "room" params to "getTransferredByData"'); | ||||||||||||||||||
| } | ||||||||||||||||||
| const { servedBy: { _id: agentId } = {} } = room; | ||||||||||||||||||
| const { _id, username, name, userType: transferType } = transferredBy; | ||||||||||||||||||
| const type = transferType || (_id === agentId ? 'agent' : 'user'); | ||||||||||||||||||
| return { | ||||||||||||||||||
|
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.
Suggested change
|
||||||||||||||||||
| _id, | ||||||||||||||||||
| username, | ||||||||||||||||||
| name, | ||||||||||||||||||
| type, | ||||||||||||||||||
| }; | ||||||||||||||||||
| }; | ||||||||||||||||||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -36,6 +36,7 @@ import { sendMessage } from '../../../lib/server/functions/sendMessage'; | |||||
| import { updateMessage } from '../../../lib/server/functions/updateMessage'; | ||||||
| import { deleteMessage } from '../../../lib/server/functions/deleteMessage'; | ||||||
| import { FileUpload } from '../../../file-upload/server'; | ||||||
| import { normalizeTransferredByData } from './Helper'; | ||||||
|
|
||||||
| export const Livechat = { | ||||||
| Analytics, | ||||||
|
|
@@ -430,7 +431,10 @@ export const Livechat = { | |||||
| forwardOpenChats(userId) { | ||||||
| LivechatRooms.findOpenByAgent(userId).forEach((room) => { | ||||||
| const guest = LivechatVisitors.findOneById(room.v._id); | ||||||
| this.transfer(room, guest, { departmentId: guest.department }); | ||||||
| const user = Users.findOneById(userId); | ||||||
| const { _id, username, name } = user; | ||||||
| const transferredBy = normalizeTransferredByData({ _id, username, name }, room); | ||||||
| this.transfer(room, guest, { roomId: room._id, transferredBy, departmentId: guest.department }); | ||||||
| }); | ||||||
| }, | ||||||
|
|
||||||
|
|
@@ -461,8 +465,38 @@ export const Livechat = { | |||||
| } | ||||||
| }, | ||||||
|
|
||||||
| saveTransferHistory(room, transferData) { | ||||||
| const { departmentId: previousDepartment } = room; | ||||||
| const { department: nextDepartment, transferredBy, transferredTo, scope } = transferData; | ||||||
| check(transferredBy, Match.ObjectIncluding({ | ||||||
| _id: String, | ||||||
| username: String, | ||||||
| name: String, | ||||||
| type: String, | ||||||
| })); | ||||||
| const user = Users.findOneByUsername(transferredBy.username); | ||||||
| const transfer = { | ||||||
|
renatobecker-zz marked this conversation as resolved.
|
||||||
| transferData: { | ||||||
| transferredBy, | ||||||
| ts: new Date(), | ||||||
| scope: scope || (nextDepartment ? 'department' : 'agent'), | ||||||
| ...previousDepartment && { previousDepartment }, | ||||||
| ...nextDepartment && { nextDepartment }, | ||||||
| ...transferredTo && { transferredTo }, | ||||||
| }, | ||||||
| }; | ||||||
| return Messages.createTransferHistoryWithRoomIdMessageAndUser(room._id, '', user, transfer); | ||||||
| }, | ||||||
|
|
||||||
| async transfer(room, guest, transferData) { | ||||||
| return RoutingManager.transferRoom(room, guest, transferData); | ||||||
| const result = await RoutingManager.transferRoom(room, guest, transferData); | ||||||
|
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.
Suggested change
|
||||||
| if (!result) { | ||||||
| return false; | ||||||
| } | ||||||
| if (transferData.departmentId) { | ||||||
| transferData.department = LivechatDepartment.findOneById(transferData.departmentId, { fields: { name: 1 } }); | ||||||
| } | ||||||
| return this.saveTransferHistory(room, transferData); | ||||||
| }, | ||||||
|
|
||||||
| returnRoomAsInquiry(rid, departmentId) { | ||||||
|
|
@@ -489,7 +523,9 @@ export const Livechat = { | |||||
| if (!inquiry) { | ||||||
| return false; | ||||||
| } | ||||||
|
|
||||||
| const transferredBy = normalizeTransferredByData(user, room); | ||||||
| const transferData = { roomId: rid, scope: 'queue', departmentId, transferredBy }; | ||||||
| this.saveTransferHistory(room, transferData); | ||||||
| return RoutingManager.unassignAgent(inquiry, departmentId); | ||||||
|
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.
Suggested change
|
||||||
| }, | ||||||
|
|
||||||
|
|
@@ -866,6 +902,7 @@ export const Livechat = { | |||||
| }, | ||||||
|
|
||||||
| notifyAgentStatusChanged(userId, status) { | ||||||
| callbacks.runAsync('livechat.agentStatusChanged', { userId, status }); | ||||||
| if (!settings.get('Livechat_show_agent_info')) { | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.