From 6b78c08783fac6e1fa0ace44999347f6f87a73e4 Mon Sep 17 00:00:00 2001 From: Killian Frappart Date: Fri, 29 Dec 2023 12:31:51 +0100 Subject: [PATCH] [FIX] mail, im_livechat: remove automatic thread pin when opened MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Threads are automatically marked as pinned when they are opened but this behavior is not always desirable. They are now implicitly pinned when it makes sense to do so. closes odoo/odoo#146841 Signed-off-by: Sébastien Theys (seb) --- .../discuss_sidebar_categories_livechat.js | 5 ++- .../static/tests/sidebar_patch_tests.js | 34 ++++++++++++++++++- addons/mail/models/discuss/discuss_channel.py | 2 +- .../static/src/core/common/thread_model.js | 2 ++ .../static/src/core/common/thread_service.js | 13 ++++--- .../src/core/web/discuss_client_action.js | 18 ++++------ .../common/discuss_core_common_service.js | 9 +---- .../core/web/discuss_sidebar_categories.js | 2 +- .../tests/discuss/core/web/sidebar_tests.js | 30 ++++++++++++++++ .../mock_server/models/discuss_channel.js | 19 ++++------- .../helpers/mock_server/models/mail_thread.js | 32 +++++------------ 11 files changed, 101 insertions(+), 65 deletions(-) diff --git a/addons/im_livechat/static/src/core/web/discuss_sidebar_categories_livechat.js b/addons/im_livechat/static/src/core/web/discuss_sidebar_categories_livechat.js index 4233cba978f..6f8c0c0a09e 100644 --- a/addons/im_livechat/static/src/core/web/discuss_sidebar_categories_livechat.js +++ b/addons/im_livechat/static/src/core/web/discuss_sidebar_categories_livechat.js @@ -5,7 +5,10 @@ import { discussSidebarCategoriesRegistry } from "@mail/discuss/core/web/discuss discussSidebarCategoriesRegistry.add( "livechats", { - predicate: (store) => store.discuss.livechat.threads.some((thread) => thread?.is_pinned), + predicate: (store) => + store.discuss.livechat.threads.some( + (thread) => thread.displayToSelf || thread.isLocallyPinned + ), value: (store) => store.discuss.livechat, }, { sequence: 20 } diff --git a/addons/im_livechat/static/tests/sidebar_patch_tests.js b/addons/im_livechat/static/tests/sidebar_patch_tests.js index 7b546c7cebd..5e191098279 100644 --- a/addons/im_livechat/static/tests/sidebar_patch_tests.js +++ b/addons/im_livechat/static/tests/sidebar_patch_tests.js @@ -7,7 +7,7 @@ import { start } from "@mail/../tests/helpers/test_utils"; import { url } from "@web/core/utils/urls"; import { nextTick } from "@web/../tests/helpers/utils"; -import { click, contains } from "@web/../tests/utils"; +import { click, contains, insertText } from "@web/../tests/utils"; QUnit.module("discuss sidebar (patch)"); @@ -472,3 +472,35 @@ QUnit.test("Message unread counter", async () => { ); await contains(".o-mail-DiscussSidebarChannel .badge", { text: "1" }); }); + +QUnit.test("unknown livechat can be displayed and interacted with", async () => { + const pyEnv = await startServer(); + const partnerId = pyEnv["res.partner"].create({ name: "Jane" }); + const channelId = pyEnv["discuss.channel"].create({ + channel_member_ids: [[0, 0, { partner_id: partnerId }]], + channel_type: "livechat", + livechat_operator_id: partnerId, + }); + const { openDiscuss } = await start(); + await openDiscuss(); + await contains("button.o-active", { text: "Inbox" }); + await contains(".o-mail-DiscussSidebarCategory-livechat", { count: 0 }); + await contains(".o-mail-DiscussSidebarChannel", { count: 0 }); + await openDiscuss(channelId); + await contains( + ".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel.o-active", + { + text: "Jane", + } + ); + await insertText(".o-mail-Composer-input", "Hello", { replace: true }); + await click(".o-mail-Composer-send:enabled"); + await contains(".o-mail-Message", { text: "Hello" }); + await click("button", { text: "Inbox" }); + await contains(".o-mail-DiscussSidebarChannel:not(.o-active)", { text: "Jane" }); + await click("div[title='Unpin Conversation']", { + parent: [".o-mail-DiscussSidebarChannel", { text: "Jane" }], + }); + await contains(".o-mail-DiscussSidebarCategory-livechat", { count: 0 }); + await contains(".o-mail-DiscussSidebarChannel", { count: 0 }); +}); diff --git a/addons/mail/models/discuss/discuss_channel.py b/addons/mail/models/discuss/discuss_channel.py index 426599b35d5..9a8d88ce6b6 100644 --- a/addons/mail/models/discuss/discuss_channel.py +++ b/addons/mail/models/discuss/discuss_channel.py @@ -539,7 +539,7 @@ class Channel(models.Model): # Last interest and is_pinned are updated for a channel when posting a message. # So a notification is needed to update UI, and it should come before the # notification of the message itself to ensure the channel automatically opens. - payload = {"id": self.id, "isServerPinned": True, "last_interest_dt": fields.Datetime.now()} + payload = {"id": self.id, "is_pinned": True, "last_interest_dt": fields.Datetime.now()} bus_notifications = [ (self, "discuss.channel/last_interest_dt_changed", payload), (self, "discuss.channel/new_message", {"id": self.id, "message": message_format}), diff --git a/addons/mail/static/src/core/common/thread_model.js b/addons/mail/static/src/core/common/thread_model.js index f9e46e2cce0..edccebaa061 100644 --- a/addons/mail/static/src/core/common/thread_model.js +++ b/addons/mail/static/src/core/common/thread_model.js @@ -279,6 +279,8 @@ export class Thread extends Record { custom_notifications = false; /** @type {String} */ mute_until_dt; + /** @type {Boolean} */ + isLocallyPinned = false; get accessRestrictedToGroupText() { if (!this.authorizedGroupFullName) { diff --git a/addons/mail/static/src/core/common/thread_service.js b/addons/mail/static/src/core/common/thread_service.js index 56ee60be5e6..e6d9aef1eee 100644 --- a/addons/mail/static/src/core/common/thread_service.js +++ b/addons/mail/static/src/core/common/thread_service.js @@ -403,15 +403,15 @@ export class ThreadService { } async unpin(thread) { + thread.isLocallyPinned = false; if (thread.eq(this.store.discuss.thread)) { this.router.replaceState({ active_id: undefined }); } - if (thread.model !== "discuss.channel") { - return; + if (thread.model === "discuss.channel" && thread.is_pinned) { + return this.orm.silent.call("discuss.channel", "channel_pin", [thread.id], { + pinned: false, + }); } - return this.orm.silent.call("discuss.channel", "channel_pin", [thread.id], { - pinned: false, - }); } pin(thread) { @@ -642,6 +642,9 @@ export class ThreadService { if (pushState) { this.router.pushState({ active_id: activeId }); } + if (!thread.is_pinned) { + thread.isLocallyPinned = true; + } } /** 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 16cdb6e6302..1f3c1c0d24a 100644 --- a/addons/mail/static/src/core/web/discuss_client_action.js +++ b/addons/mail/static/src/core/web/discuss_client_action.js @@ -53,18 +53,12 @@ export class DiscussClientAction extends Component { // legacy format (sent in old emails, shared links, ...) model = "discuss.channel"; } - const activeThread = this.store.Thread.get({ model, id }); - if (!activeThread || activeThread.notEq(this.store.discuss.thread)) { - const thread = - this.store.Thread.get({ model, id }) ?? - (await this.threadService.fetchChannel(parseInt(id))); - if (!thread) { - return; - } - if (!thread.is_pinned) { - await this.threadService.pin(thread); - } - this.threadService.setDiscussThread(thread); + let activeThread = this.store.Thread.get({ model, id }); + if (!activeThread?.type && model === "discuss.channel") { + activeThread = await this.threadService.fetchChannel(parseInt(id)); + } + if (activeThread && activeThread.notEq(this.store.discuss.thread)) { + this.threadService.setDiscussThread(activeThread); } } } diff --git a/addons/mail/static/src/discuss/core/common/discuss_core_common_service.js b/addons/mail/static/src/discuss/core/common/discuss_core_common_service.js index 6c4ef563c52..ae85a224cf0 100644 --- a/addons/mail/static/src/discuss/core/common/discuss_core_common_service.js +++ b/addons/mail/static/src/discuss/core/common/discuss_core_common_service.js @@ -51,11 +51,7 @@ export class DiscussCoreCommon { } }); this.busService.subscribe("discuss.channel/last_interest_dt_changed", (payload) => { - const { id, last_interest_dt } = payload; - const channel = this.store.Thread.get({ model: "discuss.channel", id }); - if (channel) { - channel.last_interest_dt = last_interest_dt; - } + this.store.Thread.insert({ model: "discuss.channel", ...payload }); }); this.busService.subscribe("discuss.channel/leave", (payload) => { const thread = this.store.Thread.insert({ @@ -252,9 +248,6 @@ export class DiscussCoreCommon { return; } } - if (!channel.is_pinned) { - this.threadService.pin(channel); - } this.store.Message.get(messageData.temporary_id)?.delete(); messageData.temporary_id = null; const message = this.store.Message.insert(messageData, { html: true }); diff --git a/addons/mail/static/src/discuss/core/web/discuss_sidebar_categories.js b/addons/mail/static/src/discuss/core/web/discuss_sidebar_categories.js index e10546ba2c2..3c863dc5c14 100644 --- a/addons/mail/static/src/discuss/core/web/discuss_sidebar_categories.js +++ b/addons/mail/static/src/discuss/core/web/discuss_sidebar_categories.js @@ -84,7 +84,7 @@ export class DiscussSidebarCategories extends Component { filteredThreads(category) { return category.threads.filter((thread) => { return ( - thread.displayToSelf && + (thread.displayToSelf || thread.isLocallyPinned) && (!this.state.quickSearchVal || cleanTerm(thread.name).includes(cleanTerm(this.state.quickSearchVal))) ); diff --git a/addons/mail/static/tests/discuss/core/web/sidebar_tests.js b/addons/mail/static/tests/discuss/core/web/sidebar_tests.js index 3f89ac5c154..db3628ef1d9 100644 --- a/addons/mail/static/tests/discuss/core/web/sidebar_tests.js +++ b/addons/mail/static/tests/discuss/core/web/sidebar_tests.js @@ -55,3 +55,33 @@ QUnit.test( await contains(".o-mail-NavigableList-item", { text: "Create: # test" }); } ); + +QUnit.test("unknown channel can be displayed and interacted with", async () => { + const pyEnv = await startServer(); + const partnerId = pyEnv["res.partner"].create({ name: "Jane" }); + const channelId = pyEnv["discuss.channel"].create({ + channel_member_ids: [[0, 0, { partner_id: partnerId }]], + channel_type: "channel", + name: "Not So Secret", + }); + const { openDiscuss } = await start(); + await openDiscuss(); + await contains("button.o-active", { text: "Inbox" }); + await contains(".o-mail-DiscussSidebarChannel", { count: 0 }); + await openDiscuss(channelId); + await contains( + ".o-mail-DiscussSidebarCategory-channel + .o-mail-DiscussSidebarChannel.o-active", + { + text: "Not So Secret", + } + ); + await insertText(".o-mail-Composer-input", "Hello", { replace: true }); + await click(".o-mail-Composer-send:enabled"); + await contains(".o-mail-Message", { text: "Hello" }); + await click("button", { text: "Inbox" }); + await contains(".o-mail-DiscussSidebarChannel:not(.o-active)", { text: "Not So Secret" }); + await click("div[title='Leave this channel']", { + parent: [".o-mail-DiscussSidebarChannel", { text: "Not So Secret" }], + }); + await contains(".o-mail-DiscussSidebarChannel", { count: 0 }); +}); diff --git a/addons/mail/static/tests/helpers/mock_server/models/discuss_channel.js b/addons/mail/static/tests/helpers/mock_server/models/discuss_channel.js index a4a28795533..a8a563b756b 100644 --- a/addons/mail/static/tests/helpers/mock_server/models/discuss_channel.js +++ b/addons/mail/static/tests/helpers/mock_server/models/discuss_channel.js @@ -204,18 +204,13 @@ patch(MockServer.prototype, { _mockDiscussChannelMessagePost(id, kwargs, context) { const message_type = kwargs.message_type || "notification"; const channel = this.getRecords("discuss.channel", [["id", "=", id]])[0]; - if (channel.channel_type !== "channel") { - const memberOfCurrentUser = this._mockDiscussChannelMember__getAsSudoFromContext( - channel.id - ); - if (memberOfCurrentUser) { - this.pyEnv["discuss.channel.member"].write([memberOfCurrentUser.id], { - last_interest_dt: serializeDateTime(today()), - - is_pinned: true, - }); - } - } + const members = this.pyEnv["discuss.channel.member"].search([ + ["channel_id", "=", channel.id], + ]); + this.pyEnv["discuss.channel.member"].write(members, { + last_interest_dt: serializeDateTime(today()), + is_pinned: true, + }); const messageData = this._mockMailThreadMessagePost( "discuss.channel", [id], diff --git a/addons/mail/static/tests/helpers/mock_server/models/mail_thread.js b/addons/mail/static/tests/helpers/mock_server/models/mail_thread.js index 671efb90e05..00ffafc4051 100644 --- a/addons/mail/static/tests/helpers/mock_server/models/mail_thread.js +++ b/addons/mail/static/tests/helpers/mock_server/models/mail_thread.js @@ -286,34 +286,18 @@ patch(MockServer.prototype, { const messageFormat = this._mockMailMessageMessageFormat([messageId])[0]; const notifications = []; if (model === "discuss.channel") { - // members const channels = this.getRecords("discuss.channel", [["id", "=", message.res_id]]); for (const channel of channels) { - // notify update of last_interest_dt const now = serializeDateTime(today()); - const members = this.getRecords("discuss.channel.member", [ - ["id", "in", channel.channel_member_ids], + notifications.push([ + channel, + "discuss.channel/last_interest_dt_changed", + { + id: channel.id, + is_pinned: true, + last_interest_dt: now, + }, ]); - this.pyEnv["discuss.channel.member"].write( - members.map((member) => member.id), - { last_interest_dt: now } - ); - for (const member of members) { - const target = member.guest_id - ? this.pyEnv["mail.guest"].searchRead([["id", "=", member.guest_id]])[0] - : this.pyEnv["res.partner"].searchRead([["id", "=", member.partner_id]], { - context: { active_test: false }, - })[0]; - notifications.push([ - target, - "discuss.channel/last_interest_dt_changed", - { - id: channel.id, - isServerPinned: member.is_pinned, - last_interest_dt: member.last_interest_dt, - }, - ]); - } notifications.push([ channel, "discuss.channel/new_message",