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

chore: allow Omnichannel Chats to be placed onHold even when the last message is not from the Agent #30527

Merged
merged 23 commits into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
06b610c
Updated useQuickActions.tsx to allow Omnichannel Chats to be placed o…
cabaceira Sep 29, 2023
aff1d43
added new setting and logic as per CC-13, allowing the default behavi…
cabaceira Oct 2, 2023
d090dbe
fixed minor identation issue, fixed the naming of the setting under t…
cabaceira Oct 3, 2023
e0901fa
changed according to PR suggestions
cabaceira Oct 17, 2023
2827a00
adjusted lint warnings
cabaceira Oct 17, 2023
96cf998
added backend validation in services/omnichannel.internalService.ts, …
cabaceira Oct 23, 2023
284740d
validating Room object projections
cabaceira Oct 24, 2023
a0965f2
Fixing PR corrections per reviewers
cabaceira Oct 27, 2023
f8c1960
updating typo on settings labels and conflicts resolution with dev
cabaceira Oct 27, 2023
74110a1
fixed conflict with useQuickActions.tsx
cabaceira Oct 27, 2023
f3edd78
removed tsx.orig file, updated types for placeRoomOnHold
cabaceira Oct 27, 2023
dc90be6
changed location of validations, updated types for placeRoomOnHold
cabaceira Oct 27, 2023
9d99e50
removed ts-ignore lint annotations
cabaceira Oct 30, 2023
97b2964
reverting removal of ts-ignore lint annotation
cabaceira Oct 30, 2023
0ad1c51
language for settings reviewed, last revert of ts-ignore lint annotat…
cabaceira Oct 30, 2023
496b25b
Merge branch 'develop' into omnichannel/chats-onHold-action-consolida…
cabaceira Oct 30, 2023
6100af8
included tests, more generic error to support all scenarios
cabaceira Oct 30, 2023
241ab1d
included tests, more generic error to support all scenarios , last cl…
cabaceira Oct 30, 2023
ebe1512
fixed tests, included visitor from startNewLivechatRoomAndTakeIt inst…
cabaceira Oct 30, 2023
fd2ff0a
Merge branch 'develop' into omnichannel/chats-onHold-action-consolida…
kodiakhq[bot] Nov 1, 2023
475afa1
Merge branch 'develop' into omnichannel/chats-onHold-action-consolida…
kodiakhq[bot] Nov 1, 2023
a4857b1
Merge branch 'develop' into omnichannel/chats-onHold-action-consolida…
kodiakhq[bot] Nov 1, 2023
31427d2
Merge branch 'develop' into omnichannel/chats-onHold-action-consolida…
kodiakhq[bot] Nov 1, 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
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
Loading