Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 2s
396 lines
12 KiB
TypeScript
396 lines
12 KiB
TypeScript
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<IUser, '_id' | 'utcOffset' | 'language'>;
|
|
}) => {
|
|
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<IUser, '_id' | 'utcOffset' | 'language'>;
|
|
}) => {
|
|
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<IUser, '_id' | 'utcOffset' | 'language'>;
|
|
}) => {
|
|
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<string, { open: number; closed: number; onhold?: number }> = {};
|
|
(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<string, { open: number; closed: number }> = {};
|
|
(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 });
|