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: Allow admins to control if visitors can close omnichannel conversations #33139

Merged
merged 11 commits into from
Sep 9, 2024
8 changes: 8 additions & 0 deletions .changeset/healthy-rivers-nail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@rocket.chat/meteor": minor
"@rocket.chat/i18n": minor
"@rocket.chat/livechat": minor
---

Added new setting `Allow visitors to finish conversations` that allows admins to decide if omnichannel visitors can close a conversation or not. This doesn't affect agent's capabilities of room closing, neither apps using the livechat bridge to close rooms.
However, if currently your integration relies on `livechat/room.close` endpoint for closing conversations, it's advised to use the authenticated version `livechat/room.closeByUser` of it before turning off this setting.
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ API.v1.addRoute(
'Livechat_background',
'Livechat_widget_position',
'Livechat_hide_system_messages',
'Omnichannel_allow_visitors_to_close_conversation',
];

const valid = settings.every((setting) => validSettingList.includes(setting._id));
Expand Down
1 change: 1 addition & 0 deletions apps/meteor/app/livechat/server/api/lib/appearance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export async function findAppearance(): Promise<{ appearance: ISetting[] }> {
'Livechat_background',
'Livechat_widget_position',
'Livechat_hide_system_messages',
'Omnichannel_allow_visitors_to_close_conversation',
],
},
};
Expand Down
1 change: 1 addition & 0 deletions apps/meteor/app/livechat/server/api/lib/livechat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ export async function settings({ businessUnit = '' }: { businessUnit?: string }
hiddenSystemMessages: initSettings.Livechat_hide_system_messages,
livechatLogo: initSettings.Assets_livechat_widget_logo,
hideWatermark: initSettings.Livechat_hide_watermark || false,
visitorsCanCloseChat: initSettings.Omnichannel_allow_visitors_to_close_conversation,
},
theme: {
title: initSettings.Livechat_title,
Expand Down
4 changes: 4 additions & 0 deletions apps/meteor/app/livechat/server/api/v1/room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ API.v1.addRoute(
async post() {
const { rid, token } = this.bodyParams;

if (!rcSettings.get('Omnichannel_allow_visitors_to_close_conversation')) {
throw new Error('error-not-allowed-to-close-conversation');
KevLehman marked this conversation as resolved.
Show resolved Hide resolved
}

const visitor = await findGuest(token);
if (!visitor) {
throw new Error('invalid-token');
Expand Down
1 change: 1 addition & 0 deletions apps/meteor/app/livechat/server/lib/LivechatTyped.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1079,6 +1079,7 @@ class LivechatClass {
'Livechat_background',
'Assets_livechat_widget_logo',
'Livechat_hide_watermark',
'Omnichannel_allow_visitors_to_close_conversation',
] as const;

type SettingTypes = (typeof validSettings)[number] | 'Livechat_Show_Connecting';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const AppearanceForm = () => {
const livechatWidgetPositionField = useUniqueId();
const livechatBackgroundField = useUniqueId();
const livechatHideSystemMessagesField = useUniqueId();
const omnichannelVisitorsCanCloseConversationField = useUniqueId();

return (
<Accordion>
Expand Down Expand Up @@ -140,6 +141,20 @@ const AppearanceForm = () => {
/>
</FieldRow>
</Field>
<Field>
<FieldRow>
<FieldLabel htmlFor={omnichannelVisitorsCanCloseConversationField}>
{t('Omnichannel_allow_visitors_to_close_conversation')}
</FieldLabel>
<Controller
name='Omnichannel_allow_visitors_to_close_conversation'
control={control}
render={({ field: { value, ...field } }) => (
<ToggleSwitch id={omnichannelVisitorsCanCloseConversationField} {...field} checked={value} />
)}
/>
</FieldRow>
</Field>
</FieldGroup>
</Accordion.Item>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type LivechatAppearanceSettings = {
Livechat_conversation_finished_text: string;
Livechat_enable_message_character_limit: boolean;
Livechat_message_character_limit: number;
Omnichannel_allow_visitors_to_close_conversation: boolean;
};

type AppearanceSettings = Partial<LivechatAppearanceSettings>;
Expand Down
7 changes: 7 additions & 0 deletions apps/meteor/server/settings/omnichannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,13 @@ export const createOmniSettings = () =>
i18nLabel: 'Show_agent_email',
});

await this.add('Omnichannel_allow_visitors_to_close_conversation', true, {
type: 'boolean',
group: 'Omnichannel',
public: true,
enableQuery: omnichannelEnabledQuery,
});

await this.add('Livechat_request_comment_when_closing_conversation', true, {
type: 'boolean',
group: 'Omnichannel',
Expand Down
52 changes: 52 additions & 0 deletions apps/meteor/tests/e2e/omnichannel/omnichannel-livechat.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createFakeVisitor } from '../../mocks/data';
import { createAuxContext } from '../fixtures/createAuxContext';
import { Users } from '../fixtures/userStates';
import { HomeOmnichannel, OmnichannelLiveChat } from '../page-objects';
import { setSettingValueById } from '../utils';
import { createAgent } from '../utils/omnichannel/agents';
import { test, expect } from '../utils/test';

Expand Down Expand Up @@ -93,6 +94,57 @@ test.describe.serial('OC - Livechat', () => {
});
});

test.describe.serial('OC - Livechat - Visitors closing the room is disabled', () => {
let poLiveChat: OmnichannelLiveChat;
let poHomeOmnichannel: HomeOmnichannel;

test.beforeAll(async ({ api }) => {
await api.post('/livechat/users/agent', { username: 'user1' });
});

test.beforeAll(async ({ browser, api }) => {
const { page: livechatPage } = await createAuxContext(browser, Users.user1, '/livechat', false);

poLiveChat = new OmnichannelLiveChat(livechatPage, api);
});

test.beforeAll(async ({ browser, api }) => {
await setSettingValueById(api, 'Livechat_allow_visitor_closing_chat', false);
const { page: omniPage } = await createAuxContext(browser, Users.user1, '/', true);
poHomeOmnichannel = new HomeOmnichannel(omniPage);
});

test.afterAll(async ({ api }) => {
await setSettingValueById(api, 'Livechat_allow_visitor_closing_chat', true);
await api.delete('/livechat/users/agent/user1');
await poLiveChat.page.close();
});

test('OC - Livechat - Close Chat disabled', async () => {
await poLiveChat.page.reload();
await poLiveChat.openAnyLiveChat();
await poLiveChat.sendMessage(firstVisitor, false);
await poLiveChat.onlineAgentMessage.fill('this_a_test_message_from_user');
await poLiveChat.btnSendMessageToOnlineAgent.click();

await test.step('expect to close a livechat conversation', async () => {
await expect(poLiveChat.btnOptions).not.toBeVisible();
await expect(poLiveChat.btnCloseChat).not.toBeVisible();
});
});

test('OC - Livechat - Close chat disabled, agents can close', async () => {
await poHomeOmnichannel.sidenav.openChat(firstVisitor.name);

await test.step('expect livechat conversation to be closed by agent', async () => {
await poHomeOmnichannel.content.btnCloseChat.click();
await poHomeOmnichannel.content.closeChatModal.inputComment.fill('this_is_a_test_comment');
await poHomeOmnichannel.content.closeChatModal.btnConfirm.click();
await expect(poHomeOmnichannel.toastSuccess).toBeVisible();
});
});
});

test.describe.serial('OC - Livechat - Resub after close room', () => {
let poLiveChat: OmnichannelLiveChat;
let poHomeOmnichannel: HomeOmnichannel;
Expand Down
19 changes: 19 additions & 0 deletions apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,25 @@ describe('LIVECHAT - rooms', () => {
expect(latestRoom).to.not.have.property('pdfTranscriptFileId');
},
);

describe('Special case: visitors closing is disabled', () => {
before(async () => {
await updateSetting('Omnichannel_allow_visitors_to_close_conversation', false);
});
after(async () => {
await updateSetting('Omnichannel_allow_visitors_to_close_conversation', true);
});
it('should not allow visitor to close a conversation', async () => {
KevLehman marked this conversation as resolved.
Show resolved Hide resolved
const { room, visitor } = await startANewLivechatRoomAndTakeIt();
await request
.post(api('livechat/room.close'))
.send({
token: visitor.token,
rid: room._id,
})
.expect(400);
});
});
});

describe('livechat/room.forward', () => {
Expand Down
2 changes: 2 additions & 0 deletions packages/i18n/src/locales/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -4037,6 +4037,8 @@
"Omnichannel_Reports_Summary": "Gain insights into your operation and export your metrics.",
"Omnichannel_max_fallback_forward_depth": "Maximum fallback forward departments depth",
"Omnichannel_max_fallback_forward_depth_Description": "Maximum number of hops that a room being transfered will do when the target department has a Fallback Forward Department set up. When limit is reached, chat won't be transferred and process will stop. Depending on your configuration, setting a high number may cause performance issues.",
"Omnichannel_allow_visitors_to_close_conversation": "Allow visitors to finish conversations",
"Omnichannel_allow_visitors_to_close_conversation_Description": "When disabled, visitors won't be able to finish an ongoing conversation either via UI or via API.",
"On": "On",
"on-hold-livechat-room": "On Hold Omnichannel Room",
"on-hold-livechat-room_description": "Permission to on hold omnichannel room",
Expand Down
2 changes: 2 additions & 0 deletions packages/livechat/src/routes/Chat/connector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const ChatConnector: FunctionalComponent<{ path: string; default: boolean
nameFieldRegistrationForm,
emailFieldRegistrationForm,
limitTextLength,
visitorsCanCloseChat,
},
messages: { conversationFinishedMessage },
theme: { title = '' } = {},
Expand Down Expand Up @@ -94,6 +95,7 @@ export const ChatConnector: FunctionalComponent<{ path: string; default: boolean
ongoingCall={ongoingCall}
messageListPosition={messageListPosition}
theme={theme}
visitorsCanCloseChat={visitorsCanCloseChat}
/>
);
};
Expand Down
4 changes: 2 additions & 2 deletions packages/livechat/src/routes/Chat/container.js
Original file line number Diff line number Diff line change
Expand Up @@ -288,8 +288,8 @@ class ChatContainer extends Component {
};

canFinishChat = () => {
lucas-a-pelegrino marked this conversation as resolved.
Show resolved Hide resolved
const { room, connecting } = this.props;
return room !== undefined || connecting;
const { room, connecting, visitorsCanCloseChat } = this.props;
return visitorsCanCloseChat && (room !== undefined || connecting);
};

canRemoveUserData = () => {
Expand Down
1 change: 1 addition & 0 deletions packages/livechat/src/store/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export type StoreState = {
hideWatermark?: boolean;
livechatLogo?: { url: string };
transcript?: boolean;
visitorsCanCloseChat?: boolean;
};
online?: boolean;
departments: Department[];
Expand Down
Loading