From 7d18bf0f83eecb2b3a4baea669be588ca7dbf43e Mon Sep 17 00:00:00 2001 From: tsm-odoo Date: Mon, 12 Feb 2024 12:22:11 +0100 Subject: [PATCH] [FIX] im_livechat, mail: broken history navigation This PR fixes 2 issues with discuss navigation: 1. Broken backwards navigation when going back and forth from the live chat session history. 2. Broken backwards navigation when trying to access the same thread than the current one. Steps to reproduce 1: - Open the command palette - Go to the live chat session history view - Click on one of your channels - History back => leads to the session history view - History forward => leads to discuss - History back => stays on discuss, history is broken This occurs because the active id is not passed in the action context when navigating backwards which leads to the URL being pushed again in history (URL without active id is different). The active id should be put in the context when available. Steps to reproduce 2: - Go to discuss - Click on the active thread - History back => stuck on discuss, cannot navigate backwards anymore. We should not push in history when accessing the same thread than the current one. task-3422516 closes odoo/odoo#153094 X-original-commit: 029b79ad7dc28aab83289e7c3ebd4cc0dcc5d609 Signed-off-by: Matthieu Stockbauer (tsm) --- .../im_livechat_history_back_and_forth.js | 58 +++++++++++++++++++ addons/im_livechat/tests/__init__.py | 1 + addons/im_livechat/tests/common.py | 2 +- .../im_livechat/tests/test_session_history.py | 21 +++++++ .../mail/static/src/core/common/discuss.xml | 2 +- .../src/core/common/discuss_app_model.js | 1 + .../static/src/core/common/thread_service.js | 5 +- .../src/core/web/discuss_client_action.js | 3 +- .../mail/static/tests/helpers/test_utils.js | 3 +- .../src/webclient/actions/action_service.js | 20 ++++--- 10 files changed, 101 insertions(+), 15 deletions(-) create mode 100644 addons/im_livechat/static/tests/tours/im_livechat_history_back_and_forth.js create mode 100644 addons/im_livechat/tests/test_session_history.py diff --git a/addons/im_livechat/static/tests/tours/im_livechat_history_back_and_forth.js b/addons/im_livechat/static/tests/tours/im_livechat_history_back_and_forth.js new file mode 100644 index 00000000000..2ccc7e0ae1f --- /dev/null +++ b/addons/im_livechat/static/tests/tours/im_livechat_history_back_and_forth.js @@ -0,0 +1,58 @@ +/** @odoo-module */ + +import { registry } from "@web/core/registry"; + +registry.category("web_tour.tours").add("im_livechat_history_back_and_forth_tour", { + test: true, + steps: () => [ + { + trigger: "body", + // Open Command Palette + run() { + this.$anchor[0].dispatchEvent( + new KeyboardEvent("keydown", { key: "K", ctrlKey: true, bubbles: true }) + ); + }, + }, + { + trigger: ".o_command_palette_search input", + run: "text /", + }, + { + trigger: ".o_command_palette_search input", + run: "text Live Chat", + }, + { + trigger: ".o_command:contains(Sessions History)", + }, + { + trigger: ".o_data_cell:contains(Visitor operator)", + }, + { + trigger: ".o-mail-DiscussSidebar-item:contains(Visitor).o-active", + run() { + history.back(); + }, + }, + { + trigger: ".o_data_cell:contains(Visitor operator)", + run() { + history.forward(); + }, + }, + { + trigger: ".o-mail-DiscussSidebar-item:contains(Visitor).o-active", + }, + { + trigger: ".o-mail-DiscussSidebar-item:contains(Visitor).o-active", + run() { + history.back(); + }, + }, + { + trigger: ".o_data_cell:contains(Visitor operator)", + run() {}, + isCheck: true, + }, + ], +}); diff --git a/addons/im_livechat/tests/__init__.py b/addons/im_livechat/tests/__init__.py index 4f6047dc3f1..f80fe315f33 100644 --- a/addons/im_livechat/tests/__init__.py +++ b/addons/im_livechat/tests/__init__.py @@ -13,3 +13,4 @@ from . import test_im_livechat_support_page from . import test_js from . import test_message from . import test_upload_attachment +from . import test_session_history diff --git a/addons/im_livechat/tests/common.py b/addons/im_livechat/tests/common.py index 03910f89774..fc16e36e7f9 100644 --- a/addons/im_livechat/tests/common.py +++ b/addons/im_livechat/tests/common.py @@ -46,6 +46,6 @@ class TestImLivechatCommon(HttpCase): def _compute_available_operator_ids(channel_self): for record in channel_self: - record.available_operator_ids = type(self).operators + record.available_operator_ids = record.user_ids self.patch(type(self.env['im_livechat.channel']), '_compute_available_operator_ids', _compute_available_operator_ids) diff --git a/addons/im_livechat/tests/test_session_history.py b/addons/im_livechat/tests/test_session_history.py new file mode 100644 index 00000000000..d1e6df24c5e --- /dev/null +++ b/addons/im_livechat/tests/test_session_history.py @@ -0,0 +1,21 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.tests import new_test_user, tagged +from odoo.addons.im_livechat.tests.common import TestImLivechatCommon + + +@tagged("-at_install", "post_install") +class TestImLivechatSessionHistory(TestImLivechatCommon): + def test_session_history_navigation_back_and_forth(self): + operator = new_test_user(self.env, login="operator", groups="base.group_user,im_livechat.im_livechat_group_manager") + self.env["bus.presence"].create({"user_id": operator.id, "status": "online"}) + self.livechat_channel.user_ids |= operator + self.authenticate(None, None) + infos = self.make_jsonrpc_request("/im_livechat/get_session", { + "channel_id": self.livechat_channel.id, + "anonymous_name": "Visitor", + "previous_operator_id": operator.partner_id.id + }) + channel = self.env["discuss.channel"].browse(infos["id"]) + channel.with_user(operator).message_post(body="Hello, how can I help you?") + self.start_tour("/web", "im_livechat_history_back_and_forth_tour", login="operator", step_delay=25) diff --git a/addons/mail/static/src/core/common/discuss.xml b/addons/mail/static/src/core/common/discuss.xml index adb8c346479..def28936387 100644 --- a/addons/mail/static/src/core/common/discuss.xml +++ b/addons/mail/static/src/core/common/discuss.xml @@ -70,7 +70,7 @@ -
+

No conversation selected.

diff --git a/addons/mail/static/src/core/common/discuss_app_model.js b/addons/mail/static/src/core/common/discuss_app_model.js index 7ca954f9e02..84208d7c492 100644 --- a/addons/mail/static/src/core/common/discuss_app_model.js +++ b/addons/mail/static/src/core/common/discuss_app_model.js @@ -46,6 +46,7 @@ export class DiscussApp extends Record { activeTab = "main"; chatWindows = Record.many("ChatWindow"); isActive = false; + hasRestoredThread = false; thread = Record.one("Thread"); channels = Record.one("DiscussAppCategory"); chats = Record.one("DiscussAppCategory"); diff --git a/addons/mail/static/src/core/common/thread_service.js b/addons/mail/static/src/core/common/thread_service.js index 1105a2adcbf..e40bebaf7b7 100644 --- a/addons/mail/static/src/core/common/thread_service.js +++ b/addons/mail/static/src/core/common/thread_service.js @@ -642,7 +642,10 @@ export class ThreadService { * @param {import("models").Thread} thread * @param {boolean} pushState */ - setDiscussThread(thread, pushState = true) { + setDiscussThread(thread, pushState) { + if (pushState === undefined) { + pushState = thread.localId !== this.store.discuss.thread?.localId; + } this.store.discuss.thread = thread; const activeId = typeof thread.id === "string" diff --git a/addons/mail/static/src/core/web/discuss_client_action.js b/addons/mail/static/src/core/web/discuss_client_action.js index 1f3c1c0d24a..43cc5e9c638 100644 --- a/addons/mail/static/src/core/web/discuss_client_action.js +++ b/addons/mail/static/src/core/web/discuss_client_action.js @@ -58,8 +58,9 @@ export class DiscussClientAction extends Component { activeThread = await this.threadService.fetchChannel(parseInt(id)); } if (activeThread && activeThread.notEq(this.store.discuss.thread)) { - this.threadService.setDiscussThread(activeThread); + this.threadService.setDiscussThread(activeThread, false); } + this.store.discuss.hasRestoredThread = true; } } diff --git a/addons/mail/static/tests/helpers/test_utils.js b/addons/mail/static/tests/helpers/test_utils.js index a8ad972d00f..b7c309c15cc 100644 --- a/addons/mail/static/tests/helpers/test_utils.js +++ b/addons/mail/static/tests/helpers/test_utils.js @@ -6,7 +6,6 @@ import { timings } from "@bus/misc"; import { loadEmoji } from "@web/core/emoji_picker/emoji_picker"; import { loadLamejs } from "@mail/discuss/voice_message/common/voice_message_service"; import { patchBrowserNotification } from "@mail/../tests/helpers/patch_notifications"; -import { DISCUSS_ACTION_ID } from "@mail/../tests/helpers/test_constants"; import { getAdvanceTime } from "@mail/../tests/helpers/time_control"; import { getWebClientReady } from "@mail/../tests/helpers/webclient_setup"; @@ -37,7 +36,7 @@ function getOpenDiscuss(webClient, { context = {}, params = {}, ...props } = {}) return async function openDiscuss(pActiveId) { const actionOpenDiscuss = { context: { ...context, active_id: pActiveId }, - id: DISCUSS_ACTION_ID, + id: "mail.action_discuss", params, tag: "mail.action_discuss", type: "ir.actions.client", diff --git a/addons/web/static/src/webclient/actions/action_service.js b/addons/web/static/src/webclient/actions/action_service.js index 5c9ed50c1a0..89f0aadddef 100644 --- a/addons/web/static/src/webclient/actions/action_service.js +++ b/addons/web/static/src/webclient/actions/action_service.js @@ -315,9 +315,19 @@ function makeActionManager(env) { const options = { clearBreadcrumbs: true }; let actionRequest = null; if (state.action) { + const context = {}; + if (state.active_id) { + context.active_id = state.active_id; + } + if (state.active_ids) { + context.active_ids = parseActiveIds(state.active_ids); + } else if (state.active_id) { + context.active_ids = [state.active_id]; + } // ClientAction if (actionRegistry.contains(state.action)) { actionRequest = { + context, params: state, tag: state.action, type: "ir.actions.client", @@ -325,15 +335,7 @@ function makeActionManager(env) { } else { // The action to load isn't the current one => executes it actionRequest = state.action; - const context = { params: state }; - if (state.active_id) { - context.active_id = state.active_id; - } - if (state.active_ids) { - context.active_ids = parseActiveIds(state.active_ids); - } else if (state.active_id) { - context.active_ids = [state.active_id]; - } + context.params = state; Object.assign(options, { additionalContext: context, viewType: state.view_type,