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

feat: Omnichannel MAC limitations #30463

Merged
merged 60 commits into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
72549d0
feat: sidebar mac limit indicator
aleksandernsilva Sep 11, 2023
c7593e1
feat: workspace over mac limit sidebar section
aleksandernsilva Sep 13, 2023
a0e9aa3
feat: added over the mac limit banner to current chats
aleksandernsilva Sep 14, 2023
1426476
fix: MacActivityIcon now expects a visitor instead of a room
aleksandernsilva Sep 14, 2023
0119206
chore: renamed MacActivityIcon file
aleksandernsilva Sep 14, 2023
cdd41ed
feat: over the mac limit read-only composer
aleksandernsilva Sep 14, 2023
6d1c168
feat: hidding room actions when over mac limit
aleksandernsilva Sep 14, 2023
d567ce9
feat: added mac limit hooks
aleksandernsilva Sep 14, 2023
57fe520
fix: added default value to activity
aleksandernsilva Sep 15, 2023
06280a4
chore: renamed MacActivityIcon to RoomActivityIcon
aleksandernsilva Sep 19, 2023
4ea725b
chore: return always true for over mac limit (test only)
aleksandernsilva Sep 19, 2023
1515984
feat: added highlight red dot
aleksandernsilva Sep 19, 2023
4b769e5
chore: added mac endpoints and stream
aleksandernsilva Oct 5, 2023
c6e3b31
fix: reading the endpoint incorrectly
aleksandernsilva Oct 6, 2023
db6f60f
fix: incomplete validation on useIsRoomActive
aleksandernsilva Oct 6, 2023
ef31597
fix: incorrect validation on useIsOverMacLimit
aleksandernsilva Oct 6, 2023
7ff5752
Limit endpoints on MAC limit reached
KevLehman Sep 20, 2023
703bcc6
types of endpoint
KevLehman Sep 21, 2023
f31ab68
block meteor methods
KevLehman Sep 21, 2023
cfaa542
use license
KevLehman Sep 29, 2023
fdc7466
stream
KevLehman Oct 3, 2023
771b67b
Service events
KevLehman Oct 6, 2023
11763fb
ts
KevLehman Oct 9, 2023
c8a0ff6
Dont limit saveinfo/messages endpoints
KevLehman Oct 9, 2023
9d6c8ea
tests
KevLehman Oct 9, 2023
34552f8
tests
KevLehman Oct 9, 2023
8233f47
tests
KevLehman Oct 9, 2023
9be0b37
dedupe events received from license
KevLehman Oct 10, 2023
f562911
ts
KevLehman Oct 10, 2023
5774dde
Create seven-emus-pay.md
KevLehman Oct 10, 2023
8733606
unused ts-expect-error
KevLehman Oct 10, 2023
1f09f39
fix: show red dot highlight only to managers
aleksandernsilva Oct 10, 2023
bd50c88
chore: changed position of the mac banner on the sidebar
aleksandernsilva Oct 10, 2023
edff4a6
translations
KevLehman Oct 10, 2023
b8a6ac4
fix: incorrect over mac limit validation
aleksandernsilva Oct 10, 2023
65cca47
limits
KevLehman Oct 10, 2023
c724d4a
feat: disabled canned responses when over mac limit
aleksandernsilva Oct 10, 2023
d2917cb
fix: canned responses crashing the GUI after page refresh
aleksandernsilva Oct 10, 2023
a24905b
feat: disable file upload on inactive rooms
aleksandernsilva Oct 10, 2023
fe265be
feat: only check mac limits when omnichannel is enabled
aleksandernsilva Oct 10, 2023
68cb787
chore: incorrect check
aleksandernsilva Oct 11, 2023
d942942
fix: invalidate mac check query when event is received
aleksandernsilva Oct 11, 2023
8173bdf
queue qol
KevLehman Oct 11, 2023
290f133
Rename 22-mac.ts to 23-mac.ts
KevLehman Oct 11, 2023
80e6808
ts
KevLehman Oct 18, 2023
8b82230
remove mac/check endpoint
KevLehman Oct 23, 2023
9fe517d
remove wrap function
KevLehman Oct 23, 2023
b9f42d7
why
KevLehman Oct 23, 2023
efb312b
chore: removed useIsRoomActive and added useIsRoomOverMacLimit
aleksandernsilva Oct 23, 2023
b365bf1
chore: removed mac/check endpoint in favor of license
aleksandernsilva Oct 23, 2023
8676738
chore: improved highlight var naming
aleksandernsilva Oct 23, 2023
4b22fac
chore: moved mac section into omnichannel section
aleksandernsilva Oct 23, 2023
5c05377
chore: renamed useMacValidations hook
aleksandernsilva Oct 23, 2023
19d645e
fix: ignore closed room for mac validations
aleksandernsilva Oct 23, 2023
9032b0d
chore: created OmnichannelBadges component
aleksandernsilva Oct 23, 2023
793954c
Update token.ts
KevLehman Oct 23, 2023
102bcbc
chore: adjusted highlight badges
aleksandernsilva Oct 23, 2023
22165bf
undo administration badge
ggazzo Oct 25, 2023
95fc6ec
refactored useIsRoomOverMacLimit
ggazzo Oct 25, 2023
b48d18a
review
ggazzo Oct 25, 2023
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
10 changes: 10 additions & 0 deletions .changeset/seven-emus-pay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@rocket.chat/meteor": patch
"@rocket.chat/core-services": patch
"@rocket.chat/core-typings": patch
"@rocket.chat/rest-typings": patch
"@rocket.chat/ddp-client": patch
---

feat: Improve UI when MAC limits are reached
feat: Limit endpoints on MAC limit reached
6 changes: 3 additions & 3 deletions apps/meteor/app/lib/server/methods/sendMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export async function executeSendMessage(uid: IUser['_id'], message: AtLeast<IMe
const room = await canSendMessageAsync(rid, { uid, username: user.username, type: user.type });

metrics.messagesSent.inc(); // TODO This line needs to be moved to it's proper place. See the comments on: https://github.com/RocketChat/Rocket.Chat/pull/5736
return sendMessage(user, message, room, false, previewUrls);
return await sendMessage(user, message, room, false, previewUrls);
} catch (err: any) {
SystemLogger.error({ msg: 'Error sending message:', err });

Expand All @@ -107,7 +107,7 @@ declare module '@rocket.chat/ui-contexts' {
}

Meteor.methods<ServerMethods>({
sendMessage(message, previewUrls) {
async sendMessage(message, previewUrls) {
check(message, Object);

const uid = Meteor.userId();
Expand All @@ -118,7 +118,7 @@ Meteor.methods<ServerMethods>({
}

try {
return executeSendMessage(uid, message, previewUrls);
return await executeSendMessage(uid, message, previewUrls);
} catch (error: any) {
if ((error.error || error.message) === 'error-not-allowed') {
throw new Meteor.Error(error.error || error.message, error.reason, {
Expand Down
9 changes: 9 additions & 0 deletions apps/meteor/app/livechat/server/api/v1/room.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Omnichannel } from '@rocket.chat/core-services';
import type { ILivechatAgent, IOmnichannelRoom, IUser, SelectedAgent, TransferByData } from '@rocket.chat/core-typings';
import { isOmnichannelRoom, OmnichannelSourceType } from '@rocket.chat/core-typings';
import { LivechatVisitors, Users, LivechatRooms, Subscriptions, Messages } from '@rocket.chat/models';
Expand Down Expand Up @@ -326,6 +327,10 @@ API.v1.addRoute(
throw new Error('This_conversation_is_already_closed');
}

if (!(await Omnichannel.isWithinMACLimit(room))) {
throw new Error('error-mac-limit-reached');
}

const guest = await LivechatVisitors.findOneEnabledById(room.v?._id);
if (!guest) {
throw new Error('error-invalid-visitor');
Expand Down Expand Up @@ -412,6 +417,10 @@ API.v1.addRoute(
throw new Error('error-invalid-room');
}

if (!(await Omnichannel.isWithinMACLimit(room))) {
throw new Error('error-mac-limit-reached');
}

if (!(await canAccessRoomAsync(room, user))) {
throw new Error('error-not-allowed');
}
Expand Down
9 changes: 7 additions & 2 deletions apps/meteor/app/livechat/server/api/v1/transcript.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Omnichannel } from '@rocket.chat/core-services';
import type { IOmnichannelRoom } from '@rocket.chat/core-typings';
import { LivechatRooms, Users } from '@rocket.chat/models';
import { isPOSTLivechatTranscriptParams, isPOSTLivechatTranscriptRequestParams } from '@rocket.chat/rest-typings';
Expand Down Expand Up @@ -34,8 +35,8 @@ API.v1.addRoute(
{
async delete() {
const { rid } = this.urlParams;
const room = await LivechatRooms.findOneById<Pick<IOmnichannelRoom, 'open' | 'transcriptRequest'>>(rid, {
projection: { open: 1, transcriptRequest: 1 },
const room = await LivechatRooms.findOneById<Pick<IOmnichannelRoom, 'open' | 'transcriptRequest' | 'v'>>(rid, {
projection: { open: 1, transcriptRequest: 1, v: 1 },
});

if (!room?.open) {
Expand All @@ -45,6 +46,10 @@ API.v1.addRoute(
throw new Error('error-transcript-not-requested');
}

if (!(await Omnichannel.isWithinMACLimit(room))) {
throw new Error('error-mac-limit-reached');
}

await LivechatRooms.unsetEmailTranscriptRequestedByRoomId(rid);

return API.v1.success();
Expand Down
11 changes: 10 additions & 1 deletion apps/meteor/app/livechat/server/api/v1/videoCall.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Message } from '@rocket.chat/core-services';
import { Message, Omnichannel } from '@rocket.chat/core-services';
import type { IOmnichannelRoom } from '@rocket.chat/core-typings';
import { Messages, Settings, Rooms } from '@rocket.chat/models';
import { isGETWebRTCCall, isPUTWebRTCCallId } from '@rocket.chat/rest-typings';

Expand Down Expand Up @@ -27,6 +28,10 @@ API.v1.addRoute(
throw new Error('invalid-room');
}

if (!(await Omnichannel.isWithinMACLimit(room as IOmnichannelRoom))) {
throw new Error('error-mac-limit-reached');
}

const webrtcCallingAllowed = rcSettings.get('WebRTC_Enabled') === true && rcSettings.get('Omnichannel_call_provider') === 'WebRTC';
if (!webrtcCallingAllowed) {
throw new Error('webRTC calling not enabled');
Expand Down Expand Up @@ -79,6 +84,10 @@ API.v1.addRoute(
throw new Error('invalid-room');
}

if (!(await Omnichannel.isWithinMACLimit(room as IOmnichannelRoom))) {
throw new Error('error-mac-limit-reached');
}

const call = await Messages.findOneById(callId);
if (!call || call.t !== 'livechat_webrtc_video_call') {
throw new Error('invalid-callId');
Expand Down
30 changes: 30 additions & 0 deletions apps/meteor/app/livechat/server/hooks/checkMAC.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Omnichannel } from '@rocket.chat/core-services';
import type { IOmnichannelRoom } from '@rocket.chat/core-typings';
import { isEditedMessage } from '@rocket.chat/core-typings';

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

callbacks.add('beforeSaveMessage', async (message, room) => {
if (!room || room.t !== 'l') {
return message;
}

if (isEditedMessage(message)) {
return message;
}

if (message.token) {
return message;
}

if (message.t) {
return message;
}

const canSendMessage = await Omnichannel.isWithinMACLimit(room as IOmnichannelRoom);
if (!canSendMessage) {
throw new Error('error-mac-limit-reached');
}

return message;
});
1 change: 1 addition & 0 deletions apps/meteor/app/livechat/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import './hooks/saveContactLastChat';
import './hooks/saveLastMessageToInquiry';
import './hooks/afterUserActions';
import './hooks/afterAgentRemoved';
import './hooks/checkMAC';
import './methods/addAgent';
import './methods/addManager';
import './methods/changeLivechatStatus';
Expand Down
6 changes: 5 additions & 1 deletion apps/meteor/app/livechat/server/lib/Livechat.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Note: Please don't add any new methods to this file, since its still in js and we are migrating to ts
// Please add new methods to LivechatTyped.ts
import { Message } from '@rocket.chat/core-services';
import { Message, Omnichannel } from '@rocket.chat/core-services';
import { Logger } from '@rocket.chat/logger';
import {
LivechatVisitors,
Expand Down Expand Up @@ -411,6 +411,10 @@ export const Livechat = {
throw new Meteor.Error('error-transcript-already-requested', 'Transcript already requested');
}

if (!(await Omnichannel.isWithinMACLimit(room))) {
throw new Error('error-mac-limit-reached');
}

const { _id, username, name, utcOffset } = user;
const transcriptRequest = {
requestedAt: new Date(),
Expand Down
6 changes: 5 additions & 1 deletion apps/meteor/app/livechat/server/lib/LivechatTyped.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import dns from 'dns';
import * as util from 'util';

import { Message, VideoConf, api } from '@rocket.chat/core-services';
import { Message, VideoConf, api, Omnichannel } from '@rocket.chat/core-services';
import type {
IOmnichannelRoom,
IOmnichannelRoomClosingInfo,
Expand Down Expand Up @@ -521,6 +521,10 @@ class LivechatClass {
throw new Error('error-invalid-room');
}

if (!(await Omnichannel.isWithinMACLimit(room))) {
throw new Error('error-mac-limit-reached');
}

const showAgentInfo = settings.get<string>('Livechat_show_agent_info');
const closingMessage = await Messages.findLivechatClosingMessage(rid, { projection: { ts: 1 } });
const ignoredMessageTypes: MessageTypesValues[] = [
Expand Down
9 changes: 9 additions & 0 deletions apps/meteor/app/livechat/server/lib/QueueManager.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Omnichannel } from '@rocket.chat/core-services';
import type { ILivechatInquiryRecord, ILivechatVisitor, IMessage, IOmnichannelRoom, SelectedAgent } from '@rocket.chat/core-typings';
import { Logger } from '@rocket.chat/logger';
import { LivechatInquiry, LivechatRooms, Users } from '@rocket.chat/models';
Expand All @@ -20,6 +21,14 @@ export const queueInquiry = async (inquiry: ILivechatInquiryRecord, defaultAgent
logger.debug(`Delegating inquiry with id ${inquiry._id} to agent ${defaultAgent?.username}`);

await callbacks.run('livechat.beforeRouteChat', inquiry, inquiryAgent);
const room = await LivechatRooms.findOneById(inquiry.rid, { projection: { v: 1 } });
if (!room || !(await Omnichannel.isWithinMACLimit(room))) {
logger.error({ msg: 'MAC limit reached, not routing inquiry', inquiry });
// We'll queue these inquiries so when new license is applied, they just start rolling again
// Minimizing disruption
await saveQueueInquiry(inquiry);
return;
}
const dbInquiry = await LivechatInquiry.findOneById(inquiry._id);

if (!dbInquiry) {
Expand Down
14 changes: 14 additions & 0 deletions apps/meteor/app/livechat/server/lib/RoutingManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,11 @@ export const RoutingManager: Routing = {
await Promise.all([Message.saveSystemMessage('command', rid, 'connected', user), Message.saveSystemMessage('uj', rid, '', user)]);
}

if (!room) {
logger.debug(`Cannot assign agent to inquiry ${inquiry._id}: Room not found`);
throw new Meteor.Error('error-room-not-found', 'Room not found');
}

await dispatchAgentDelegated(rid, agent.agentId);
logger.debug(`Agent ${agent.agentId} assigned to inquriy ${inquiry._id}. Instances notified`);

Expand All @@ -173,6 +178,10 @@ export const RoutingManager: Routing = {
return false;
}

if (!(await Omnichannel.isWithinMACLimit(room))) {
throw new Error('error-mac-limit-reached');
}

if (departmentId && departmentId !== department) {
logger.debug(`Switching department for inquiry ${inquiry._id} [Current: ${department} | Next: ${departmentId}]`);
await updateChatDepartment({
Expand Down Expand Up @@ -260,6 +269,11 @@ export const RoutingManager: Routing = {
},

async transferRoom(room, guest, transferData) {
if (!(await Omnichannel.isWithinMACLimit(room))) {
throw new Error('error-mac-limit-reached');
}

logger.debug(`Transfering room ${room._id} by ${transferData.transferredBy._id}`);
if (transferData.departmentId) {
logger.debug(`Transfering room ${room._id} to department ${transferData.departmentId}`);
return forwardRoomToDepartment(room, guest, transferData);
Expand Down
5 changes: 5 additions & 0 deletions apps/meteor/app/livechat/server/methods/returnAsInquiry.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Omnichannel } from '@rocket.chat/core-services';
import type { ILivechatDepartment, IRoom } from '@rocket.chat/core-typings';
import { LivechatRooms } from '@rocket.chat/models';
import type { ServerMethods } from '@rocket.chat/ui-contexts';
Expand Down Expand Up @@ -29,6 +30,10 @@ Meteor.methods<ServerMethods>({
});
}

if (!(await Omnichannel.isWithinMACLimit(room))) {
throw new Meteor.Error('error-mac-limit-reached', 'MAC limit reached', { method: 'livechat:returnAsInquiry' });
}

if (!room.open) {
throw new Meteor.Error('room-closed', 'Room closed', { method: 'livechat:returnAsInquiry' });
}
Expand Down
8 changes: 7 additions & 1 deletion apps/meteor/app/livechat/server/methods/takeInquiry.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { LivechatInquiry, Users } from '@rocket.chat/models';
import { Omnichannel } from '@rocket.chat/core-services';
import { LivechatInquiry, LivechatRooms, Users } from '@rocket.chat/models';
import type { ServerMethods } from '@rocket.chat/ui-contexts';
import { Meteor } from 'meteor/meteor';

Expand Down Expand Up @@ -48,6 +49,11 @@ export const takeInquiry = async (
});
}

const room = await LivechatRooms.findOneById(inquiry.rid);
if (!room || !(await Omnichannel.isWithinMACLimit(room))) {
throw new Error('error-mac-limit-reached');
}

const agent = {
agentId: user._id,
username: user.username,
Expand Down
5 changes: 5 additions & 0 deletions apps/meteor/app/livechat/server/methods/transfer.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Omnichannel } from '@rocket.chat/core-services';
import type { IUser } from '@rocket.chat/core-typings';
import { LivechatVisitors, LivechatRooms, Subscriptions, Users } from '@rocket.chat/models';
import type { ServerMethods } from '@rocket.chat/ui-contexts';
Expand Down Expand Up @@ -49,6 +50,10 @@ Meteor.methods<ServerMethods>({
throw new Meteor.Error('room-closed', 'Room closed', { method: 'livechat:transfer' });
}

if (!(await Omnichannel.isWithinMACLimit(room))) {
throw new Meteor.Error('error-mac-limit-reached', 'MAC limit reached', { method: 'livechat:transfer' });
}

const subscription = await Subscriptions.findOneByRoomIdAndUserId(room._id, uid, {
projection: { _id: 1 },
});
Expand Down
2 changes: 2 additions & 0 deletions apps/meteor/client/contexts/OmnichannelContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export type OmnichannelContextValue = {
agentAvailable: boolean;
routeConfig?: OmichannelRoutingConfig;
showOmnichannelQueueLink: boolean;
isOverMacLimit: boolean;
livechatPriorities: {
data: Serialized<ILivechatPriority>[];
isLoading: boolean;
Expand All @@ -22,6 +23,7 @@ export const OmnichannelContext = createContext<OmnichannelContextValue>({
isEnterprise: false,
agentAvailable: false,
showOmnichannelQueueLink: false,
isOverMacLimit: false,
livechatPriorities: {
data: [],
isLoading: false,
Expand Down
6 changes: 6 additions & 0 deletions apps/meteor/client/hooks/omnichannel/useIsOverMacLimit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { useOmnichannel } from './useOmnichannel';

export const useIsOverMacLimit = (): boolean => {
const { isOverMacLimit } = useOmnichannel();
return isOverMacLimit;
};
23 changes: 23 additions & 0 deletions apps/meteor/client/hooks/omnichannel/useIsRoomOverMacLimit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { IRoom } from '@rocket.chat/core-typings';
import { isOmnichannelRoom, type IOmnichannelGenericRoom, isVoipRoom } from '@rocket.chat/core-typings';

import { useIsOverMacLimit } from './useIsOverMacLimit';

const getPeriod = (date: Date) => `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;

export const useIsRoomOverMacLimit = (room: IRoom) => {
const isOverMacLimit = useIsOverMacLimit();

if (!isOmnichannelRoom(room) && !isVoipRoom(room)) {
return false;
}

if (!room.open) {
return false;
}

const { v: { activity = [] } = {} } = room as IOmnichannelGenericRoom;

const currentPeriod = getPeriod(new Date());
return isOverMacLimit && !activity.includes(currentPeriod);
};
Loading
Loading