diff --git a/addons/mail/static/src/components/notification_list/notification_list.js b/addons/mail/static/src/components/notification_list/notification_list.js index bcf43d651b1..ea256c13963 100644 --- a/addons/mail/static/src/components/notification_list/notification_list.js +++ b/addons/mail/static/src/components/notification_list/notification_list.js @@ -69,21 +69,21 @@ class NotificationList extends Component { if (props.filter === 'all') { // threads with needactions threadNeedactionNotifications = this.env.models['mail.thread'] - .all(t => t.model !== 'mail.box' && t.needactionMessages.length > 0) + .all(t => t.model !== 'mail.box' && t.needactionMessagesAsOriginThread.length > 0) .sort((t1, t2) => { - if (t1.needactionMessages.length > 0 && t2.needactionMessages.length === 0) { + if (t1.needactionMessagesAsOriginThread.length > 0 && t2.needactionMessagesAsOriginThread.length === 0) { return -1; } - if (t1.needactionMessages.length === 0 && t2.needactionMessages.length > 0) { + if (t1.needactionMessagesAsOriginThread.length === 0 && t2.needactionMessagesAsOriginThread.length > 0) { return 1; } - if (t1.lastNeedactionMessage && t2.lastNeedactionMessage) { - return t1.lastNeedactionMessage.date.isBefore(t2.lastNeedactionMessage.date) ? 1 : -1; + if (t1.lastNeedactionMessageAsOriginThread && t2.lastNeedactionMessageAsOriginThread) { + return t1.lastNeedactionMessageAsOriginThread.date.isBefore(t2.lastNeedactionMessageAsOriginThread.date) ? 1 : -1; } - if (t1.lastNeedactionMessage) { + if (t1.lastNeedactionMessageAsOriginThread) { return -1; } - if (t2.lastNeedactionMessage) { + if (t2.lastNeedactionMessageAsOriginThread) { return 1; } return t1.id < t2.id ? -1 : 1; diff --git a/addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview.js b/addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview.js index 5caefd7f341..c313a1dd2c1 100644 --- a/addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview.js +++ b/addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview.js @@ -21,21 +21,21 @@ class ThreadNeedactionPreview extends Component { useStore(props => { const thread = this.env.models['mail.thread'].get(props.threadLocalId); const mainThreadCache = thread ? thread.mainCache : undefined; - let lastNeedactionMessageAuthor; - let lastNeedactionMessage; + let lastNeedactionMessageAsOriginThreadAuthor; + let lastNeedactionMessageAsOriginThread; let threadCorrespondent; if (thread) { - lastNeedactionMessage = mainThreadCache.lastNeedactionMessage; + lastNeedactionMessageAsOriginThread = mainThreadCache.lastNeedactionMessageAsOriginThread; threadCorrespondent = thread.correspondent; } - if (lastNeedactionMessage) { - lastNeedactionMessageAuthor = lastNeedactionMessage.author; + if (lastNeedactionMessageAsOriginThread) { + lastNeedactionMessageAsOriginThreadAuthor = lastNeedactionMessageAsOriginThread.author; } return { isDeviceMobile: this.env.messaging.device.isMobile, - lastNeedactionMessage: lastNeedactionMessage ? lastNeedactionMessage.__state : undefined, - lastNeedactionMessageAuthor: lastNeedactionMessageAuthor - ? lastNeedactionMessageAuthor.__state + lastNeedactionMessageAsOriginThread: lastNeedactionMessageAsOriginThread ? lastNeedactionMessageAsOriginThread.__state : undefined, + lastNeedactionMessageAsOriginThreadAuthor: lastNeedactionMessageAsOriginThreadAuthor + ? lastNeedactionMessageAsOriginThreadAuthor.__state : undefined, thread: thread ? thread.__state : undefined, threadCorrespondent: threadCorrespondent @@ -77,11 +77,11 @@ class ThreadNeedactionPreview extends Component { * * @returns {string} */ - get inlineLastNeedactionMessageBody() { - if (!this.thread.lastNeedactionMessage) { + get inlineLastNeedactionMessageAsOriginThreadBody() { + if (!this.thread.lastNeedactionMessageAsOriginThread) { return ''; } - return mailUtils.htmlToTextContentInline(this.thread.lastNeedactionMessage.prettyBody); + return mailUtils.htmlToTextContentInline(this.thread.lastNeedactionMessageAsOriginThread.prettyBody); } /** @@ -105,7 +105,7 @@ class ThreadNeedactionPreview extends Component { // handled in `_onClickMarkAsRead` return; } - this.thread.markNeedactionMessagesAsRead(); + this.thread.markNeedactionMessagesAsOriginThreadAsRead(); this.thread.open(); if (!this.env.messaging.device.isMobile) { this.env.messaging.messagingMenu.close(); @@ -117,7 +117,7 @@ class ThreadNeedactionPreview extends Component { * @param {MouseEvent} ev */ _onClickMarkAsRead(ev) { - this.thread.markNeedactionMessagesAsRead(); + this.thread.markNeedactionMessagesAsOriginThreadAsRead(); } } diff --git a/addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview.xml b/addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview.xml index 3047003f292..3fd33224d59 100644 --- a/addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview.xml +++ b/addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview.xml @@ -28,24 +28,24 @@ - () + () - + - +
- - + + - + diff --git a/addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview_tests.js b/addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview_tests.js index fe01b8f7c7d..f3e1d49148b 100644 --- a/addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview_tests.js +++ b/addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview_tests.js @@ -345,6 +345,95 @@ QUnit.test('preview should display last needaction message preview even if there ); }); +QUnit.test('needaction preview should only show on its origin thread', async function (assert) { + assert.expect(2); + + this.data['mail.channel'].records.push({ id: 12 }); + this.data['mail.message'].records.push({ + channel_ids: [12], + id: 21, + model: 'res.partner', + needaction: true, + needaction_partner_ids: [this.data.currentPartnerId], + res_id: 11, + }); + this.data['mail.notification'].records.push({ + mail_message_id: 21, + notification_status: 'sent', + notification_type: 'inbox', + res_partner_id: this.data.currentPartnerId, + }); + await this.start({ hasMessagingMenu: true }); + await afterNextRender(() => this.afterEvent({ + eventName: 'o-thread-cache-loaded-messages', + func: () => document.querySelector('.o_MessagingMenu_toggler').click(), + message: "should wait until inbox loaded initial needaction messages", + predicate: ({ threadCache }) => { + return threadCache.thread.model === 'mail.box' && threadCache.thread.id === 'inbox'; + }, + })); + assert.containsOnce( + document.body, + '.o_ThreadNeedactionPreview', + "should have only one preview" + ); + const thread = this.env.models['mail.thread'].findFromIdentifyingData({ + id: 11, + model: 'res.partner', + }); + assert.containsOnce( + document.body, + `.o_ThreadNeedactionPreview[data-thread-local-id="${thread.localId}"]`, + "preview should be on the origin thread" + ); +}); + +QUnit.test('chat window header should not have unread counter for non-channel thread', async function (assert) { + assert.expect(2); + + this.data['res.partner'].records.push({ id: 11 }); + this.data['mail.message'].records.push({ + author_id: 11, + body: 'not empty', + id: 21, + model: 'res.partner', + needaction: true, + needaction_partner_ids: [this.data.currentPartnerId], + res_id: 11, + }); + this.data['mail.notification'].records.push({ + mail_message_id: 21, + notification_status: 'sent', + notification_type: 'inbox', + res_partner_id: this.data.currentPartnerId, + }); + await this.start({ + hasChatWindow: true, + hasMessagingMenu: true, + }); + await afterNextRender(() => this.afterEvent({ + eventName: 'o-thread-cache-loaded-messages', + func: () => document.querySelector('.o_MessagingMenu_toggler').click(), + message: "should wait until inbox loaded initial needaction messages", + predicate: ({ threadCache }) => { + return threadCache.thread.model === 'mail.box' && threadCache.thread.id === 'inbox'; + }, + })); + await afterNextRender(() => + document.querySelector('.o_ThreadNeedactionPreview').click() + ); + assert.containsOnce( + document.body, + '.o_ChatWindow', + "should have opened the chat window on clicking on the preview" + ); + assert.containsNone( + document.body, + '.o_ChatWindowHeader_counter', + "chat window header should not have unread counter for non-channel thread" + ); +}); + }); }); }); diff --git a/addons/mail/static/src/models/message/message.js b/addons/mail/static/src/models/message/message.js index cc8f60c2530..59e243a14fe 100644 --- a/addons/mail/static/src/models/message/message.js +++ b/addons/mail/static/src/models/message/message.js @@ -682,7 +682,9 @@ function factory(dependencies) { /** * Origin thread of this message (if any). */ - originThread: many2one('mail.thread'), + originThread: many2one('mail.thread', { + inverse: 'messagesAsOriginThread', + }), originThreadIsModeratedByCurrentPartner: attr({ default: false, related: 'originThread.isModeratedByCurrentPartner', diff --git a/addons/mail/static/src/models/thread/thread.js b/addons/mail/static/src/models/thread/thread.js index 923a156a39c..bd37b09ffd1 100644 --- a/addons/mail/static/src/models/thread/thread.js +++ b/addons/mail/static/src/models/thread/thread.js @@ -655,11 +655,11 @@ function factory(dependencies) { } /** - * Mark all needaction messages of this thread as read. + * Marks as read all needaction messages with this thread as origin. */ - async markNeedactionMessagesAsRead() { + async markNeedactionMessagesAsOriginThreadAsRead() { await this.async(() => - this.env.models['mail.message'].markAsRead(this.needactionMessages) + this.env.models['mail.message'].markAsRead(this.needactionMessagesAsOriginThread) ); } @@ -1223,16 +1223,16 @@ function factory(dependencies) { * @private * @returns {mail.message|undefined} */ - _computeLastNeedactionMessage() { - const orderedNeedactionMessages = this.needactionMessages.sort( + _computeLastNeedactionMessageAsOriginThread() { + const orderedNeedactionMessagesAsOriginThread = this.needactionMessagesAsOriginThread.sort( (m1, m2) => m1.id < m2.id ? -1 : 1 ); const { length: l, - [l - 1]: lastNeedactionMessage, - } = orderedNeedactionMessages; - if (lastNeedactionMessage) { - return [['link', lastNeedactionMessage]]; + [l - 1]: lastNeedactionMessageAsOriginThread, + } = orderedNeedactionMessagesAsOriginThread; + if (lastNeedactionMessageAsOriginThread) { + return [['link', lastNeedactionMessageAsOriginThread]]; } return [['unlink']]; } @@ -1250,6 +1250,10 @@ function factory(dependencies) { * @returns {integer} */ _computeLocalMessageUnreadCounter() { + if (this.model !== 'mail.channel') { + // unread counter only makes sense on channels + return clear(); + } // By default trust the server up to the last message it used // because it's not possible to do better. let baseCounter = this.serverMessageUnreadCounter; @@ -1289,8 +1293,8 @@ function factory(dependencies) { * @private * @returns {mail.message[]} */ - _computeNeedactionMessages() { - return [['replace', this.messages.filter(message => message.isNeedaction)]]; + _computeNeedactionMessagesAsOriginThread() { + return [['replace', this.messagesAsOriginThread.filter(message => message.isNeedaction)]]; } /** @@ -1805,9 +1809,14 @@ function factory(dependencies) { compute: '_computeLastMessage', dependencies: ['orderedMessages'], }), - lastNeedactionMessage: many2one('mail.message', { - compute: '_computeLastNeedactionMessage', - dependencies: ['needactionMessages'], + /** + * States the last known needaction message having this thread as origin. + */ + lastNeedactionMessageAsOriginThread: many2one('mail.message', { + compute: '_computeLastNeedactionMessageAsOriginThread', + dependencies: [ + 'needactionMessagesAsOriginThread', + ], }), /** * Last non-transient message. @@ -1882,6 +1891,18 @@ function factory(dependencies) { inverse: 'threads', readonly: true, }), + /** + * All messages that have been originally posted in this thread. + */ + messagesAsOriginThread: one2many('mail.message', { + inverse: 'originThread', + }), + /** + * Serves as compute dependency. + */ + messagesAsOriginThreadIsNeedaction: attr({ + related: 'messagesAsOriginThread.isNeedaction', + }), /** * All messages that are contained on this channel on the server. * Equivalent to the inverse of python field `channel_ids`. @@ -1918,9 +1939,15 @@ function factory(dependencies) { }), moduleIcon: attr(), name: attr(), - needactionMessages: many2many('mail.message', { - compute: '_computeNeedactionMessages', - dependencies: ['messages'], + /** + * States all known needaction messages having this thread as origin. + */ + needactionMessagesAsOriginThread: many2many('mail.message', { + compute: '_computeNeedactionMessagesAsOriginThread', + dependencies: [ + 'messagesAsOriginThread', + 'messagesAsOriginThreadIsNeedaction', + ], }), /** * Not a real field, used to trigger `_onChangeFollowersPartner` when one of