Skip to content

Commit

Permalink
chore: allow Omnichannel Chats to be placed onHold even when the last…
Browse files Browse the repository at this point in the history
… message is not from the Agent (#30527)
  • Loading branch information
cabaceira authored Nov 1, 2023
1 parent 7342800 commit 542a6fe
Show file tree
Hide file tree
Showing 9 changed files with 70 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,10 @@ export const useQuickActions = (): {
const canSendTranscriptPDF = usePermission('request-pdf-transcript');
const canCloseRoom = usePermission('close-livechat-room');
const canCloseOthersRoom = usePermission('close-others-livechat-room');
const canPlaceChatOnHold = Boolean(!room.onHold && room.u && !(room as any).lastMessage?.token && manualOnHoldAllowed);
const restrictedOnHold = useSetting('Livechat_allow_manual_on_hold_upon_agent_engagement_only');
const canRoomBePlacedOnHold = !room.onHold && room.u;
const canAgentPlaceOnHold = !room.lastMessage?.token;
const canPlaceChatOnHold = Boolean(manualOnHoldAllowed && canRoomBePlacedOnHold && (!restrictedOnHold || canAgentPlaceOnHold));
const isRoomOverMacLimit = useIsRoomOverMacLimit(room);

const hasPermissionButtons = (id: string): boolean => {
Expand Down
6 changes: 3 additions & 3 deletions apps/meteor/ee/app/livechat-enterprise/server/api/rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ API.v1.addRoute(
async post() {
const { roomId } = this.bodyParams;

type Room = Pick<IOmnichannelRoom, '_id' | 't' | 'open' | 'onHold' | 'lastMessage' | 'servedBy'>;
type Room = Pick<IOmnichannelRoom, '_id' | 't' | 'open' | 'onHold' | 'u' | 'lastMessage' | 'servedBy'>;

const room = await LivechatRooms.findOneById<Room>(roomId, {
projection: { _id: 1, t: 1, open: 1, onHold: 1, lastMessage: 1, servedBy: 1 },
projection: { _id: 1, t: 1, open: 1, onHold: 1, u: 1, lastMessage: 1, servedBy: 1 },
});
if (!room) {
throw new Error('error-invalid-room');
Expand Down Expand Up @@ -51,7 +51,7 @@ API.v1.addRoute(
throw new Error('invalid-param');
}

type Room = Pick<IOmnichannelRoom, '_id' | 't' | 'open' | 'onHold' | 'servedBy'>;
type Room = Pick<IOmnichannelRoom, '_id' | 't' | 'open' | 'onHold' | 'servedBy' | 'u' | 'lastMessage'>;

const room = await LivechatRooms.findOneById<Room>(roomId, {
projection: { t: 1, open: 1, onHold: 1, servedBy: 1 },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { LivechatRooms, Subscriptions, LivechatInquiry } from '@rocket.chat/mode
import { dispatchAgentDelegated } from '../../../../../app/livechat/server/lib/Helper';
import { queueInquiry } from '../../../../../app/livechat/server/lib/QueueManager';
import { RoutingManager } from '../../../../../app/livechat/server/lib/RoutingManager';
import { settings } from '../../../../../app/settings/server';
import { callbacks } from '../../../../../lib/callbacks';

export class OmnichannelEE extends ServiceClassInternal implements IOmnichannelEEService {
Expand Down Expand Up @@ -40,8 +41,12 @@ export class OmnichannelEE extends ServiceClassInternal implements IOmnichannelE
if (room.onHold) {
throw new Error('error-room-is-already-on-hold');
}
if (room.lastMessage?.token) {
throw new Error('error-contact-sent-last-message-so-cannot-place-on-hold');
const restrictedOnHold = settings.get('Livechat_allow_manual_on_hold_upon_agent_engagement_only');
const canRoomBePlacedOnHold = !room.onHold;
const canAgentPlaceOnHold = !room.lastMessage?.token;
const canPlaceChatOnHold = canRoomBePlacedOnHold && (!restrictedOnHold || canAgentPlaceOnHold);
if (!canPlaceChatOnHold) {
throw new Error('error-cannot-place-chat-on-hold');
}
if (!room.servedBy) {
throw new Error('error-unserved-rooms-cannot-be-placed-onhold');
Expand Down
11 changes: 11 additions & 0 deletions apps/meteor/ee/app/livechat-enterprise/server/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,17 @@ export const createSettings = async (): Promise<void> => {
enableQuery: omnichannelEnabledQuery,
});

await settingsRegistry.add('Livechat_allow_manual_on_hold_upon_agent_engagement_only', true, {
type: 'boolean',
group: 'Omnichannel',
section: 'Sessions',
enterprise: true,
invalidValue: false,
public: true,
modules: ['livechat-enterprise'],
enableQuery: { _id: 'Livechat_allow_manual_on_hold', value: true },
});

await settingsRegistry.add('Livechat_auto_transfer_chat_timeout', 0, {
type: 'int',
group: 'Omnichannel',
Expand Down
5 changes: 4 additions & 1 deletion apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -2110,6 +2110,7 @@
"error-you-are-last-owner": "You are the last owner. Please set new owner before leaving the room.",
"error-saving-sla": "An error ocurred while saving the SLA",
"error-duplicated-sla": "An SLA with the same name or due time already exists",
"error-cannot-place-chat-on-hold": "You cannot place chat on-hold",
"error-contact-sent-last-message-so-cannot-place-on-hold": "You cannot place chat on-hold, when the Contact has sent the last message",
"error-unserved-rooms-cannot-be-placed-onhold": "Room cannot be placed on hold before being served",
"Workspace_exceeded_MAC_limit_disclaimer": "The workspace has exceeded the monthly limit of active contacts. Talk to your workspace admin to address this issue.",
Expand Down Expand Up @@ -3075,7 +3076,9 @@
"Livechat_agents": "Omnichannel agents",
"Livechat_Agents": "Agents",
"Livechat_allow_manual_on_hold": "Allow agents to manually place chat On Hold",
"Livechat_allow_manual_on_hold_Description": "If enabled, the agent will get a new option to place a chat On Hold, provided the agent has sent the last message",
"Livechat_allow_manual_on_hold_Description": "If enabled, the agent will get the option to place a chat On Hold",
"Livechat_allow_manual_on_hold_upon_agent_engagement_only" : "Chats on hold only after agent engagement",
"Livechat_allow_manual_on_hold_upon_agent_engagement_only_Description": "Only allow chats to be put on hold if the agent is the one who sent the last message in the conversation.",
"Livechat_AllowedDomainsList": "Livechat Allowed Domains",
"Livechat_Appearance": "Livechat Appearance",
"Livechat_auto_close_on_hold_chats_custom_message": "Custom message for closed chats in On Hold queue",
Expand Down
5 changes: 4 additions & 1 deletion apps/meteor/packages/rocketchat-i18n/i18n/pt-BR.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -1829,6 +1829,7 @@
"error-no-permission-team-channel": "Você não tem permissão para incluir este canal à equipe",
"error-no-owner-channel": "Apenas proprietários podem adicionar este canal à equipe",
"error-you-are-last-owner": "Você é o último proprietário da sala. Defina um novo proprietário antes de sair.",
"error-cannot-place-chat-on-hold": "Você não pode colocar a conversa em espera",
"Errors_and_Warnings": "Erros e avisos",
"Esc_to": "Esc para",
"Estimated_wait_time": "Tempo estimado de espera (tempo em minutos)",
Expand Down Expand Up @@ -2634,7 +2635,9 @@
"Livechat_agents": "Agentes do omnichannel",
"Livechat_Agents": "Agentes",
"Livechat_allow_manual_on_hold": "Permitir que agentes coloquem a conversa em espera manualmente",
"Livechat_allow_manual_on_hold_Description": "Se habilitado, o agente terá uma nova opção para colocar a conversa em espera, desde que o agente tenha enviado a última mensagem",
"Livechat_allow_manual_on_hold_Description": "Se habilitado, o agente terá uma nova opção para colocar a conversa em espera",
"Livechat_allow_manual_on_hold_upon_agent_engagement_only": "Conversas em espera somente após interação do agente",
"Livechat_allow_manual_on_hold_upon_agent_engagement_only_Description": "Permitir que conversas sejam colocados em espera apenas quando o agente for quem enviou a última mensagem",
"Livechat_AllowedDomainsList": "Domínios permitidos em Livechat",
"Livechat_Appearance": "Aparência do livechat",
"Livechat_auto_close_on_hold_chats_custom_message": "Mensagem personalizada para conversas encerradas na fila em espera",
Expand Down
3 changes: 2 additions & 1 deletion apps/meteor/packages/rocketchat-i18n/i18n/pt.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -1281,6 +1281,7 @@
"error-user-registration-disabled": "O registo de utilizadores está desabilitado",
"error-user-registration-secret": "O registo de utilizadores só é permitido via URL privada",
"error-you-are-last-owner": "É o último proprietário da sala. Por favor defina um novo proprietário antes de sair.",
"error-cannot-place-chat-on-hold": "Você não pode colocar a conversa em espera",
"Errors_and_Warnings": "Erros e Avisos",
"Esc_to": "Prima Esc para",
"Event_Trigger": "Gerador de Eventos",
Expand Down Expand Up @@ -3174,4 +3175,4 @@
"registration.component.form.invalidConfirmPass": "A confirmação da senha não é igual à senha",
"registration.component.form.confirmPassword": "Confirmar a senha",
"registration.component.form.sendConfirmationEmail": "Enviar email de confirmação"
}
}
36 changes: 33 additions & 3 deletions apps/meteor/tests/end-to-end/api/livechat/18-rooms-ee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { IS_EE } from '../../../e2e/config/constants';
const user: IUser = await createUser();
const userCredentials = await login(user.username, password);
await createAgent(user.username);
await updateSetting('Livechat_allow_manual_on_hold', true);

agent2 = {
user,
Expand All @@ -48,8 +49,9 @@ import { IS_EE } from '../../../e2e/config/constants';

after(async () => {
await deleteUser(agent2.user);
await updateSetting('Livechat_allow_manual_on_hold', false);
await updateSetting('Livechat_allow_manual_on_hold_upon_agent_engagement_only', true);
});

describe('livechat/room.onHold', () => {
it('should fail if user doesnt have on-hold-livechat-room permission', async () => {
await updatePermission('on-hold-livechat-room', []);
Expand Down Expand Up @@ -115,7 +117,7 @@ import { IS_EE } from '../../../e2e/config/constants';
.expect(400);

expect(response.body.success).to.be.false;
expect(response.body.error).to.be.equal('error-contact-sent-last-message-so-cannot-place-on-hold');
expect(response.body.error).to.be.equal('error-cannot-place-chat-on-hold');
});
it('should fail if room is closed', async () => {
const visitor = await createVisitor();
Expand Down Expand Up @@ -151,7 +153,6 @@ import { IS_EE } from '../../../e2e/config/constants';
it('should put room on hold', async () => {
const { room } = await startANewLivechatRoomAndTakeIt();
await sendAgentMessage(room._id);

const response = await request
.post(api('livechat/room.onHold'))
.set(credentials)
Expand All @@ -165,6 +166,35 @@ import { IS_EE } from '../../../e2e/config/constants';
const updatedRoom = await getLivechatRoomInfo(room._id);
expect(updatedRoom.onHold).to.be.true;
});
it('Should put room on hold, even in the visitor sent the last message', async () => {
const { room, visitor } = await startANewLivechatRoomAndTakeIt();
await updateSetting('Livechat_allow_manual_on_hold_upon_agent_engagement_only', false);
await sendMessage(room._id, '-', visitor.token);
const response = await request
.post(api('livechat/room.onHold'))
.set(credentials)
.send({
roomId: room._id,
})
.expect(200);
expect(response.body.success).to.be.true;
const updatedRoom = await getLivechatRoomInfo(room._id);
expect(updatedRoom.onHold).to.be.true;
});
it('should not put room on hold when visitor sent the last message', async () => {
const { room, visitor } = await startANewLivechatRoomAndTakeIt();
await updateSetting('Livechat_allow_manual_on_hold_upon_agent_engagement_only', true);
await sendMessage(room._id, '-', visitor.token);
const response = await request
.post(api('livechat/room.onHold'))
.set(credentials)
.send({
roomId: room._id,
})
.expect(400);
expect(response.body.success).to.be.false;
expect(response.body.error).to.be.equal('error-cannot-place-chat-on-hold');
});
});

describe('livechat/room.resumeOnHold', () => {
Expand Down
4 changes: 2 additions & 2 deletions packages/core-services/src/types/IOmnichannelEEService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import type { IServiceClass } from './ServiceClass';

export interface IOmnichannelEEService extends IServiceClass {
placeRoomOnHold(
room: Pick<IOmnichannelRoom, '_id' | 't' | 'open' | 'onHold'>,
room: Pick<IOmnichannelRoom, '_id' | 't' | 'open' | 'onHold' | 'u' | 'lastMessage'>,
comment: string,
onHoldBy: Pick<IUser, '_id' | 'username' | 'name'>,
): Promise<void>;

resumeRoomOnHold(
room: Pick<IOmnichannelRoom, '_id' | 't' | 'open' | 'onHold' | 'servedBy'>,
room: Pick<IOmnichannelRoom, '_id' | 't' | 'open' | 'onHold' | 'servedBy' | 'u' | 'lastMessage'>,
comment: string,
resumeBy: Pick<IUser, '_id' | 'username' | 'name'>,
clientAction?: boolean,
Expand Down

0 comments on commit 542a6fe

Please sign in to comment.