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 d6e30f5a6ff..2c07f367a78 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 @@ -107,7 +107,6 @@ class ThreadNeedactionPreview extends Component { // handled in `_onClickMarkAsRead` return; } - this.thread.markNeedactionMessagesAsOriginThreadAsRead(); this.thread.open(); if (!this.env.messaging.device.isMobile) { this.env.messaging.messagingMenu.close(); @@ -119,7 +118,10 @@ class ThreadNeedactionPreview extends Component { * @param {MouseEvent} ev */ _onClickMarkAsRead(ev) { - this.thread.markNeedactionMessagesAsOriginThreadAsRead(); + this.env.models['mail.message'].markAllAsRead([ + ['model', '=', this.thread.model], + ['res_id', '=', this.thread.id], + ]); } } 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 f3e1d49148b..ca1fe22c785 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 @@ -44,7 +44,7 @@ QUnit.module('thread_needaction_preview_tests.js', { }); QUnit.test('mark as read', async function (assert) { - assert.expect(4); + assert.expect(5); this.data['mail.message'].records.push({ id: 21, @@ -63,8 +63,16 @@ QUnit.test('mark as read', async function (assert) { hasChatWindow: true, hasMessagingMenu: true, async mockRPC(route, args) { - if (route.includes('set_message_done')) { - assert.step('set_message_done'); + if (route.includes('mark_all_as_read')) { + assert.step('mark_all_as_read'); + assert.deepEqual( + args.kwargs.domain, + [ + ['model', '=', 'res.partner'], + ['res_id', '=', 11], + ], + "should mark all as read the correct thread" + ); } return this._super(...arguments); }, @@ -87,8 +95,8 @@ QUnit.test('mark as read', async function (assert) { document.querySelector('.o_ThreadNeedactionPreview_markAsRead').click() ); assert.verifySteps( - ['set_message_done'], - "should have marked the message as read" + ['mark_all_as_read'], + "should have marked the thread as read" ); assert.containsNone( document.body, @@ -98,7 +106,7 @@ QUnit.test('mark as read', async function (assert) { }); QUnit.test('click on preview should mark as read and open the thread', async function (assert) { - assert.expect(5); + assert.expect(6); this.data['mail.message'].records.push({ id: 21, @@ -117,8 +125,16 @@ QUnit.test('click on preview should mark as read and open the thread', async fun hasChatWindow: true, hasMessagingMenu: true, async mockRPC(route, args) { - if (route.includes('set_message_done')) { - assert.step('set_message_done'); + if (route.includes('mark_all_as_read')) { + assert.step('mark_all_as_read'); + assert.deepEqual( + args.kwargs.domain, + [ + ['model', '=', 'res.partner'], + ['res_id', '=', 11], + ], + "should mark all as read the correct thread" + ); } return this._super(...arguments); }, @@ -146,7 +162,7 @@ QUnit.test('click on preview should mark as read and open the thread', async fun document.querySelector('.o_ThreadNeedactionPreview').click() ); assert.verifySteps( - ['set_message_done'], + ['mark_all_as_read'], "should have marked the message as read on clicking on the preview" ); assert.containsOnce( diff --git a/addons/mail/static/src/models/thread/thread.js b/addons/mail/static/src/models/thread/thread.js index f208147ba91..afcb4dc6d67 100644 --- a/addons/mail/static/src/models/thread/thread.js +++ b/addons/mail/static/src/models/thread/thread.js @@ -1484,9 +1484,6 @@ function factory(dependencies) { if (this.channel_type === 'chat' && !this.areFollowersLoaded) { this.refreshFollowers(); } - if (this.needactionMessagesAsOriginThread.length > 0) { - this.markNeedactionMessagesAsOriginThreadAsRead(); - } } /** diff --git a/addons/mail/static/src/models/thread_cache/thread_cache.js b/addons/mail/static/src/models/thread_cache/thread_cache.js index 8383936a3f1..1760a509fba 100644 --- a/addons/mail/static/src/models/thread_cache/thread_cache.js +++ b/addons/mail/static/src/models/thread_cache/thread_cache.js @@ -307,6 +307,44 @@ function factory(dependencies) { return messages; } + /** + * Calls "mark all as read" when this thread becomes displayed in a + * view (which is notified by `isMarkAllAsReadRequested` being `true`), + * but delays the call until some other conditions are met, such as the + * messages being loaded. + * The reason to wait until messages are loaded is to avoid a race + * condition because "mark all as read" will change the state of the + * messages in parallel to fetch reading them. + * + * @private + */ + _onChangeMarkAllAsRead() { + if ( + !this.isMarkAllAsReadRequested || + !this.thread || + !this.thread.mainCache || + !this.isLoaded || + this.isLoading + ) { + // wait for change of state before deciding what to do + return; + } + this.update({ isMarkAllAsReadRequested: false }); + if ( + this.thread.isTemporary || + this.thread.model === 'mail.box' || + this.thread.mainCache !== this || + this.threadViews.length === 0 + ) { + // ignore the request + return; + } + this.env.models['mail.message'].markAllAsRead([ + ['model', '=', this.thread.model], + ['res_id', '=', this.thread.id], + ]); + } + /** * Loads this thread cache, by fetching the most recent messages in this * conversation. @@ -413,6 +451,16 @@ function factory(dependencies) { isCacheRefreshRequested: attr({ default: false, }), + /** + * Determines whether this cache should consider calling "mark all as + * read" on this thread. + * + * This field is a hint that may or may not lead to an actual call. + * @see `_onChangeMarkAllAsRead` + */ + isMarkAllAsReadRequested: attr({ + default: false, + }), /** * Last message that has been fetched by this thread cache. * @@ -458,6 +506,23 @@ function factory(dependencies) { 'messagesAreEmpty', ], }), + /** + * Not a real field, used to trigger its compute method when one of the + * dependencies changes. + */ + onChangeMarkAllAsRead: attr({ + compute: '_onChangeMarkAllAsRead', + dependencies: [ + 'isLoaded', + 'isLoading', + 'isMarkAllAsReadRequested', + 'thread', + 'threadIsTemporary', + 'threadMainCache', + 'threadModel', + 'threadViews', + ], + }), /** * Loads initial messages from `this`. * This is not a "real" field, its compute function is used to trigger @@ -520,6 +585,12 @@ function factory(dependencies) { threadMessages: many2many('mail.message', { related: 'thread.messages', }), + /** + * Serves as compute dependency. + */ + threadModel: attr({ + related: 'thread.model', + }), /** * States the 'mail.thread_view' that are currently displaying `this`. */ diff --git a/addons/mail/static/src/models/thread_view/thread_view.js b/addons/mail/static/src/models/thread_view/thread_view.js index 6d13ab20118..a6704c4d186 100644 --- a/addons/mail/static/src/models/thread_view/thread_view.js +++ b/addons/mail/static/src/models/thread_view/thread_view.js @@ -138,7 +138,10 @@ function factory(dependencies) { this.update({ componentHintList: clear() }); this.addComponentHint('change-of-thread-cache'); if (this.threadCache) { - this.threadCache.update({ isCacheRefreshRequested: true }); + this.threadCache.update({ + isCacheRefreshRequested: true, + isMarkAllAsReadRequested: true, + }); } this.update({ lastVisibleMessage: [['unlink']] }); }