[IMP] im_livechat: allow visitors to receive calls

task-2900771

closes odoo/odoo#135109

Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
This commit is contained in:
tsm-odoo
2023-09-13 03:37:56 +00:00
parent eaf7e6d581
commit 4be75dd002
13 changed files with 89 additions and 55 deletions
+1
View File
@@ -98,6 +98,7 @@ Help your customers with this chat, and analyse their feedback.
'web/static/lib/odoo_ui_icons/style.css',
'mail/static/src/core/common/**/*',
'mail/static/src/discuss/core/common/*',
'mail/static/src/discuss/call/common/**',
'mail/static/src/discuss/typing/**/*',
'mail/static/src/utils/common/**/*',
('remove', 'mail/static/src/**/*.dark.scss'),
@@ -6,5 +6,6 @@ from . import chatbot
from . import link_preview
from . import main
from . import message_reaction
from . import rtc
from . import thread
from . import webclient
+6 -2
View File
@@ -275,14 +275,18 @@ class LivechatController(http.Controller):
channel._email_livechat_transcript(email)
@http.route('/im_livechat/visitor_leave_session', type='json', auth="public", cors="*")
@add_guest_to_context
def visitor_leave_session(self, uuid):
""" Called when the livechat visitor leaves the conversation.
This will clean the chat request and warn the operator that the conversation is over.
This allows also to re-send a new chat request to the visitor, as while the visitor is
in conversation with an operator, it's not possible to send the visitor a chat request."""
discuss_channel = request.env['discuss.channel'].sudo().search([('uuid', '=', uuid)])
if discuss_channel:
discuss_channel._close_livechat_session()
if not discuss_channel:
return
channel_member_sudo = request.env["discuss.channel.member"]._get_as_sudo_from_context_or_raise(channel_id=discuss_channel.id)
channel_member_sudo._rtc_leave_call()
discuss_channel._close_livechat_session()
@http.route(['/im_livechat/chat_history'], type="json", auth="public", cors="*")
def im_livechat_chat_history(self, uuid, last_id=False, limit=20):
+32
View File
@@ -0,0 +1,32 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.http import route
from odoo.addons.mail.models.discuss.mail_guest import add_guest_to_context
from odoo.addons.mail.controllers.discuss.rtc import RtcController
class LivechatRtcController(RtcController):
@route("/mail/rtc/session/notify_call_members", cors="*")
@add_guest_to_context
def session_call_notify(self, peer_notifications):
return super().session_call_notify(peer_notifications)
@route("/mail/rtc/session/update_and_broadcast", cors="*")
@add_guest_to_context
def session_update_and_broadcast(self, session_id, values):
return super().session_update_and_broadcast(session_id, values)
@route("/mail/rtc/channel/join_call", cors="*")
@add_guest_to_context
def channel_call_join(self, channel_id, check_rtc_session_ids=None):
return super().channel_call_join(channel_id, check_rtc_session_ids=check_rtc_session_ids)
@route("/mail/rtc/channel/leave_call", cors="*")
@add_guest_to_context
def channel_call_leave(self, channel_id):
return super().channel_call_leave(channel_id)
@route("/discuss/channel/ping", cors="*")
@add_guest_to_context
def channel_ping(self, channel_id, rtc_session_id=None, check_rtc_session_ids=None):
return super().channel_ping(channel_id, rtc_session_id=rtc_session_id, check_rtc_session_ids=check_rtc_session_ids)
@@ -31,6 +31,10 @@ patch(Thread, {
});
patch(Thread.prototype, {
get typesAllowingCalls() {
return super.typesAllowingCalls.concat(["livechat"]);
},
get isChannel() {
return this.type === "livechat" || super.isChannel;
},
@@ -39,7 +39,7 @@ patch(Store.prototype, {
},
});
const allowedThreadActions = new Set(["fold-chat-window", "close", "restart"]);
const allowedThreadActions = new Set(["fold-chat-window", "close", "restart", "settings"]);
for (const [actionName] of threadActionsRegistry.getEntries()) {
if (!allowedThreadActions.has(actionName)) {
threadActionsRegistry.remove(actionName);
@@ -127,14 +127,13 @@ export class LivechatService {
*/
async leaveSession({ notifyServer = true } = {}) {
const session = JSON.parse(this.cookie.current[this.SESSION_COOKIE] ?? "{}");
if (this.state === SESSION_STATE.PERSISTED && notifyServer) {
this.busService.deleteChannel(session.uuid);
await this.rpc("/im_livechat/visitor_leave_session", { uuid: session.uuid });
}
this.cookie.deleteCookie(this.SESSION_COOKIE);
this.state = SESSION_STATE.NONE;
this.sessionInitialized = false;
if (!session?.uuid || !notifyServer) {
return;
}
this.busService.deleteChannel(session.uuid);
await this.rpc("/im_livechat/visitor_leave_session", { uuid: session.uuid });
}
/**
@@ -4,11 +4,10 @@ import { Messaging } from "@mail/core/common/messaging_service";
import { patch } from "@web/core/utils/patch";
import { session } from "@web/session";
import { SESSION_STATE } from "@im_livechat/embed/core/livechat_service";
patch(Messaging.prototype, {
initialize() {
if (this.env.services["im_livechat.livechat"].state === SESSION_STATE.PERSISTED) {
if (this.env.services["im_livechat.livechat"].guestToken) {
return super.initialize();
}
if (session.livechatData?.options.current_partner_id) {
@@ -1,9 +1,16 @@
/* @odoo-module */
import { _t } from "@web/core/l10n/translation";
import { registry } from "@web/core/registry";
import { SESSION_STATE } from "@im_livechat/embed/core/livechat_service";
registry.category("mail.thread/actions").add("restart", {
import { threadActionsRegistry } from "@mail/core/common/thread_actions";
import "@mail/discuss/call/common/thread_actions";
import { useComponent } from "@odoo/owl";
import { _t } from "@web/core/l10n/translation";
import { useService } from "@web/core/utils/hooks";
import { patch } from "@web/core/utils/patch";
threadActionsRegistry.add("restart", {
condition(component) {
return component.chatbotService.canRestart;
},
@@ -15,3 +22,22 @@ registry.category("mail.thread/actions").add("restart", {
},
sequence: 99,
});
const callSettingsAction = threadActionsRegistry.get("settings");
patch(callSettingsAction, {
condition(component) {
if (component.thread?.type !== "livechat") {
return super.condition(...arguments);
}
return (
component.livechatService.state === SESSION_STATE.PERSISTED &&
component.rtcService.state.channel?.eq(component.thread)
);
},
setup() {
super.setup(...arguments);
const component = useComponent();
component.livechatService = useService("im_livechat.livechat");
component.rtcService = useService("discuss.rtc");
},
});
@@ -56,6 +56,10 @@ patch(Thread.prototype, {
return this.type === "livechat" || super.isChatChannel;
},
get typesAllowingCalls() {
return super.typesAllowingCalls.concat(["livechat"]);
},
get isLastMessageFromCustomer() {
if (this.type !== "livechat") {
return super.isLastMessageFromCustomer;
@@ -1,30 +1,9 @@
/* @odoo-module */
import { Command } from "@mail/../tests/helpers/command";
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
import { click, start, startServer } from "@mail/../tests/helpers/test_utils";
QUnit.module("chat window (patch)");
QUnit.test("No call buttons", async () => {
const pyEnv = await startServer();
const guestId = pyEnv["mail.guest"].create({ name: "Visitor 11" });
pyEnv["discuss.channel"].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
Command.create({ guest_id: guestId }),
],
channel_type: "livechat",
livechat_operator_id: pyEnv.currentPartnerId,
});
await start();
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-NotificationItem");
await contains(".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow-command i.fa-phone", { count: 0 });
await contains(".o-mail-ChatWindow-command i.fa-gear", { count: 0 });
});
QUnit.test("closing a chat window with no message from admin side unpins it", async (assert) => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Demo" });
@@ -7,25 +7,6 @@ import { start, startServer } from "@mail/../tests/helpers/test_utils";
QUnit.module("discuss (patch)");
QUnit.test("No call buttons", async () => {
const pyEnv = await startServer();
const guestId = pyEnv["mail.guest"].create({ name: "Visitor 11" });
pyEnv["discuss.channel"].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
Command.create({ guest_id: guestId }),
],
channel_type: "livechat",
livechat_operator_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss();
await contains(".o-mail-Discuss-header");
await contains(".o-mail-Discuss-header button[title='Start a Call']", { count: 0 });
await contains(".o-mail-Discuss-header button[title='Show Call Settings']", { count: 0 });
});
QUnit.test("add livechat in the sidebar on visitor sending first message", async () => {
const pyEnv = await startServer();
pyEnv["res.users"].write([pyEnv.currentUserId], { im_status: "online" });
@@ -248,9 +248,13 @@ export class Thread extends Record {
return ["chat", "channel", "group"].includes(this.type);
}
get typesAllowingCalls() {
return ["chat", "channel", "group"];
}
get allowCalls() {
return (
["chat", "channel", "group"].includes(this.type) &&
this.typesAllowingCalls.includes(this.type) &&
!this.correspondent?.eq(this._store.odoobot)
);
}