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",