[FIX] mail: fix inbox mark all as read on thread view

task-2426357
task-2426366

closes odoo/odoo#65060

X-original-commit: 12f11e0daf7e459bfee518b312e144d78e5a5759
Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
Sébastien Theys
2021-01-26 12:48:13 +00:00
parent fb1896def5
commit 158d16d4ff
5 changed files with 104 additions and 15 deletions
@@ -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],
]);
}
}
@@ -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(
@@ -1484,9 +1484,6 @@ function factory(dependencies) {
if (this.channel_type === 'chat' && !this.areFollowersLoaded) {
this.refreshFollowers();
}
if (this.needactionMessagesAsOriginThread.length > 0) {
this.markNeedactionMessagesAsOriginThreadAsRead();
}
}
/**
@@ -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`.
*/
@@ -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']] });
}