import { OmnichannelAnalytics } from '@rocket.chat/core-services'; import type { IUser } from '@rocket.chat/core-typings'; import { LivechatRooms, Users, LivechatVisitors, LivechatAgentActivity } from '@rocket.chat/models'; import mem from 'mem'; import moment from 'moment'; import { secondsToHHMMSS } from '../../../../lib/utils/secondsToHHMMSS'; import { settings } from '../../../settings'; import { getAnalyticsOverviewDataCachedForRealtime } from '../AnalyticsTyped'; import { findPercentageOfAbandonedRoomsAsync, findAllAverageOfChatDurationTimeAsync, findAllAverageWaitingTimeAsync, findAllNumberOfAbandonedRoomsAsync, findAllAverageServiceTimeAsync, } from './departments'; const findAllChatsStatusAsync = async ({ start, end, departmentId = undefined }: { start: Date; end: Date; departmentId?: string }) => { if (!start || !end) { throw new Error('"start" and "end" must be provided'); } return { open: await LivechatRooms.countAllOpenChatsBetweenDate({ start, end, departmentId }), closed: await LivechatRooms.countAllClosedChatsBetweenDate({ start, end, departmentId }), queued: await LivechatRooms.countAllQueuedChatsBetweenDate({ start, end, departmentId }), onhold: await LivechatRooms.getOnHoldConversationsBetweenDate(start, end, departmentId), }; }; const getProductivityMetricsAsync = async ({ start, end, departmentId = undefined, user, }: { start: string; end: string; departmentId?: string; user: Pick; }) => { if (!start || !end) { throw new Error('"start" and "end" must be provided'); } const totalizers = (await OmnichannelAnalytics.getAnalyticsOverviewData({ daterange: { from: start, to: end, }, analyticsOptions: { name: 'Productivity', }, departmentId, utcOffset: user?.utcOffset, language: user?.language || settings.get('Language') || 'en', executedBy: user._id, })) || []; const averageWaitingTime = await findAllAverageWaitingTimeAsync({ start: new Date(start), end: new Date(end), departmentId, }); const totalOfWaitingTime = averageWaitingTime.departments.length; const sumOfWaitingTime = averageWaitingTime.departments.reduce((acc: number, serviceTime: { averageWaitingTimeInSeconds: number }) => { acc += serviceTime.averageWaitingTimeInSeconds; return acc; }, 0); const totalOfAvarageWaitingTime = totalOfWaitingTime === 0 ? 0 : sumOfWaitingTime / totalOfWaitingTime; return { totalizers: [...totalizers, { title: 'Avg_of_waiting_time', value: secondsToHHMMSS(totalOfAvarageWaitingTime) }], }; }; const getAgentsProductivityMetricsAsync = async ({ start, end, departmentId = undefined, user, }: { start: string; end: string; departmentId?: string; user: Pick; }) => { if (!start || !end) { throw new Error('"start" and "end" must be provided'); } // TODO: check type of date const averageOfAvailableServiceTime = ( await LivechatAgentActivity.findAllAverageAvailableServiceTime({ date: parseInt(moment(start).format('YYYYMMDD')) as any, departmentId, }) )[0]; const averageOfServiceTime = await findAllAverageServiceTimeAsync({ start: new Date(start), end: new Date(end), departmentId, }); const totalizers = (await OmnichannelAnalytics.getAnalyticsOverviewData({ daterange: { from: start, to: end, }, analyticsOptions: { name: 'Conversations', }, departmentId, utcOffset: user.utcOffset, language: user.language || settings.get('Language') || 'en', executedBy: user._id, })) || []; const totalOfServiceTime = averageOfServiceTime.departments.length; const sumOfServiceTime = averageOfServiceTime.departments.reduce( ( acc: number, serviceTime: { averageServiceTimeInSeconds: number; }, ) => { acc += serviceTime.averageServiceTimeInSeconds; return acc; }, 0, ); const totalOfAverageAvailableServiceTime = averageOfAvailableServiceTime ? averageOfAvailableServiceTime.averageAvailableServiceTimeInSeconds : 0; const totalOfAverageServiceTime = totalOfServiceTime === 0 ? 0 : sumOfServiceTime / totalOfServiceTime; return { totalizers: [ ...totalizers.filter((metric: { title: string }) => metric.title === 'Busiest_time'), { title: 'Avg_of_available_service_time', value: secondsToHHMMSS(totalOfAverageAvailableServiceTime), }, { title: 'Avg_of_service_time', value: secondsToHHMMSS(totalOfAverageServiceTime) }, ], }; }; const getChatsMetricsAsync = async ({ start, end, departmentId = undefined }: { start: Date; end: Date; departmentId?: string }) => { if (!start || !end) { throw new Error('"start" and "end" must be provided'); } const abandonedRooms = await findAllNumberOfAbandonedRoomsAsync({ start, end, departmentId, }); const averageOfAbandonedRooms = await findPercentageOfAbandonedRoomsAsync({ start, end, departmentId, }); const averageOfChatDurationTime = await findAllAverageOfChatDurationTimeAsync({ start, end, departmentId, }); const totalOfAbandonedRooms = averageOfAbandonedRooms.departments.length; const totalOfChatDurationTime = averageOfChatDurationTime.departments.length; const sumOfPercentageOfAbandonedRooms = averageOfAbandonedRooms.departments.reduce( ( acc: number, abandonedRoom: { percentageOfAbandonedChats: number; }, ) => { acc += abandonedRoom.percentageOfAbandonedChats; return acc; }, 0, ); const sumOfChatDurationTime = averageOfChatDurationTime.departments.reduce( ( acc: number, chatDurationTime: { averageChatDurationTimeInSeconds: number; }, ) => { acc += chatDurationTime.averageChatDurationTimeInSeconds; return acc; }, 0, ); const totalAbandonedRooms = abandonedRooms.departments.reduce( ( acc: number, item: { abandonedRooms: number; }, ) => { acc += item.abandonedRooms; return acc; }, 0, ); const totalOfAverageAbandonedRooms = totalOfAbandonedRooms === 0 ? 0 : sumOfPercentageOfAbandonedRooms / totalOfAbandonedRooms; const totalOfAverageChatDurationTime = totalOfChatDurationTime === 0 ? 0 : sumOfChatDurationTime / totalOfChatDurationTime; return { totalizers: [ { title: 'Total_abandoned_chats', value: totalAbandonedRooms }, { title: 'Avg_of_abandoned_chats', value: `${totalOfAverageAbandonedRooms}%` }, { title: 'Avg_of_chat_duration_time', value: secondsToHHMMSS(totalOfAverageChatDurationTime), }, ], }; }; const getConversationsMetricsAsync = async ({ start, end, departmentId, user, }: { start: string; end: string; departmentId?: string; user: Pick; }) => { if (!start || !end) { throw new Error('"start" and "end" must be provided'); } const totalizers = (await getAnalyticsOverviewDataCachedForRealtime({ daterange: { from: start, to: end, }, analyticsOptions: { name: 'Conversations', }, ...(departmentId && departmentId !== 'undefined' && { departmentId }), utcOffset: user.utcOffset, language: user.language || settings.get('Language') || 'en', executedBy: user._id, })) || []; const metrics = ['Total_conversations', 'Open_conversations', 'On_Hold_conversations', 'Total_messages']; const visitorsCount = await LivechatVisitors.countVisitorsBetweenDate({ start: new Date(start), end: new Date(end), department: departmentId, }); return { totalizers: [ ...totalizers.filter((metric: { title: string }) => metrics.includes(metric.title)), { title: 'Total_visitors', value: visitorsCount }, ], }; }; const findAllChatMetricsByAgentAsync = async ({ start, end, departmentId = undefined, }: { start: Date; end: Date; departmentId?: string; }) => { if (!start || !end) { throw new Error('"start" and "end" must be provided'); } const open = await LivechatRooms.countAllOpenChatsByAgentBetweenDate({ start, end, departmentId, }); const closed = await LivechatRooms.countAllClosedChatsByAgentBetweenDate({ start, end, departmentId, }); const onhold = await LivechatRooms.countAllOnHoldChatsByAgentBetweenDate({ start, end, departmentId, }); const result: Record = {}; (open || []).forEach((agent: { chats: number; _id: string }) => { result[agent._id] = { open: agent.chats, closed: 0, onhold: 0 }; }); (closed || []).forEach((agent: { _id: string; chats: number }) => { result[agent._id] = { open: result[agent._id] ? result[agent._id].open : 0, closed: agent.chats, }; }); (onhold || []).forEach((agent: { _id: string; chats: number }) => { result[agent._id] = { ...result[agent._id], onhold: agent.chats, }; }); return result; }; const findAllAgentsStatusAsync = async ({ departmentId = undefined }: { departmentId?: string }) => (await Users.countAllAgentsStatus({ departmentId }))[0]; const findAllChatMetricsByDepartmentAsync = async ({ start, end, departmentId = undefined, }: { start: Date; end: Date; departmentId?: string; }) => { if (!start || !end) { throw new Error('"start" and "end" must be provided'); } const open = await LivechatRooms.countAllOpenChatsByDepartmentBetweenDate({ start, end, departmentId, }); const closed = await LivechatRooms.countAllClosedChatsByDepartmentBetweenDate({ start, end, departmentId, }); const result: Record = {}; (open || []).forEach((department: { name: string; chats: number }) => { result[department.name] = { open: department.chats, closed: 0 }; }); (closed || []).forEach((department: { name: string; chats: number }) => { result[department.name] = { open: result[department.name] ? result[department.name].open : 0, closed: department.chats, }; }); return result; }; const findAllResponseTimeMetricsAsync = async ({ start, end, departmentId = undefined, }: { start: Date; end: Date; departmentId?: string; }) => { if (!start || !end) { throw new Error('"start" and "end" must be provided'); } const responseTimes = (await LivechatRooms.calculateResponseTimingsBetweenDates({ start, end, departmentId }))[0]; const reactionTimes = (await LivechatRooms.calculateReactionTimingsBetweenDates({ start, end, departmentId }))[0]; const durationTimings = (await LivechatRooms.calculateDurationTimingsBetweenDates({ start, end, departmentId }))[0]; return { response: { avg: responseTimes ? responseTimes.avg : 0, longest: responseTimes ? responseTimes.longest : 0, }, reaction: { avg: reactionTimes ? reactionTimes.avg : 0, longest: reactionTimes ? reactionTimes.longest : 0, }, chatDuration: { avg: durationTimings ? durationTimings.avg : 0, longest: durationTimings ? durationTimings.longest : 0, }, }; }; export const getConversationsMetricsAsyncCached = mem(getConversationsMetricsAsync, { maxAge: 5000, cacheKey: JSON.stringify }); export const getAgentsProductivityMetricsAsyncCached = mem(getAgentsProductivityMetricsAsync, { maxAge: 5000, cacheKey: JSON.stringify }); export const getChatsMetricsAsyncCached = mem(getChatsMetricsAsync, { maxAge: 5000, cacheKey: JSON.stringify }); export const getProductivityMetricsAsyncCached = mem(getProductivityMetricsAsync, { maxAge: 5000, cacheKey: JSON.stringify }); export const findAllChatsStatusAsyncCached = mem(findAllChatsStatusAsync, { maxAge: 5000, cacheKey: JSON.stringify }); export const findAllChatMetricsByAgentAsyncCached = mem(findAllChatMetricsByAgentAsync, { maxAge: 5000, cacheKey: JSON.stringify }); export const findAllAgentsStatusAsyncCached = mem(findAllAgentsStatusAsync, { maxAge: 5000, cacheKey: JSON.stringify }); export const findAllChatMetricsByDepartmentAsyncCached = mem(findAllChatMetricsByDepartmentAsync, { maxAge: 5000, cacheKey: JSON.stringify, }); export const findAllResponseTimeMetricsAsyncCached = mem(findAllResponseTimeMetricsAsync, { maxAge: 5000, cacheKey: JSON.stringify });