summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSébastien Theys <seb@odoo.com>2020-10-09 10:55:21 +0000
committerSébastien Theys <seb@odoo.com>2020-10-09 12:04:01 +0000
commitaa006658ea25abaa2b8d03003f1d4f9214fb6468 (patch)
tree06ba9046da4f2c4d6382c88a3075f3ec912b7ff9
parentde62d7bae2c466b74b0d9a83398c5f031233afdb (diff)
[FIX] mail: fix discuss search filters and related issues
- don't reset `stringifiedDomain` when changing thread (this led to the control panel displaying obsolete filters) - refresh non-main cache that are currently displayed when there is a known change of messages in the same thread's main cache - refresh non-main-cache on displaying them, to fetch potentially new messages - fix discuss "select all" conditions task-2171873 task-2282374 closes odoo/odoo#59631 X-original-commit: b89dc5f949b3ad062fc4197a4152066c207e1bf9 Related: odoo/enterprise#13958 Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
-rw-r--r--addons/mail/static/src/components/discuss/tests/discuss_domain_tests.js407
-rw-r--r--addons/mail/static/src/components/message_list/message_list.js9
-rw-r--r--addons/mail/static/src/components/thread_view/thread_view_tests.js10
-rw-r--r--addons/mail/static/src/models/discuss/discuss.js2
-rw-r--r--addons/mail/static/src/models/messaging_menu/messaging_menu.js17
-rw-r--r--addons/mail/static/src/models/messaging_notification_handler/messaging_notification_handler.js6
-rw-r--r--addons/mail/static/src/models/thread/thread.js6
-rw-r--r--addons/mail/static/src/models/thread_cache/thread_cache.js107
-rw-r--r--addons/mail/static/src/models/thread_view/thread_view.js3
-rw-r--r--addons/mail/static/src/widgets/discuss/discuss.js4
-rw-r--r--addons/mail/views/assets.xml1
11 files changed, 524 insertions, 48 deletions
diff --git a/addons/mail/static/src/components/discuss/tests/discuss_domain_tests.js b/addons/mail/static/src/components/discuss/tests/discuss_domain_tests.js
new file mode 100644
index 00000000000..21146652d28
--- /dev/null
+++ b/addons/mail/static/src/components/discuss/tests/discuss_domain_tests.js
@@ -0,0 +1,407 @@
+odoo.define('mail/static/src/components/discuss/tests/discuss_domain_tests.js', function (require) {
+'use strict';
+
+const {
+ afterEach,
+ afterNextRender,
+ beforeEach,
+ start,
+} = require('mail/static/src/utils/test_utils.js');
+
+QUnit.module('mail', {}, function () {
+QUnit.module('components', {}, function () {
+QUnit.module('discuss', {}, function () {
+QUnit.module('discuss_domain_tests.js', {
+ beforeEach() {
+ beforeEach(this);
+
+ this.start = async params => {
+ const { afterEvent, env, widget } = await start(Object.assign({}, params, {
+ autoOpenDiscuss: true,
+ data: this.data,
+ hasDiscuss: true,
+ }));
+ this.afterEvent = afterEvent;
+ this.env = env;
+ this.widget = widget;
+ };
+ },
+ afterEach() {
+ afterEach(this);
+ },
+});
+
+QUnit.test('discuss should filter messages based on given domain', async function (assert) {
+ assert.expect(2);
+
+ this.data['mail.message'].records.push({
+ body: "test",
+ needaction: true,
+ needaction_partner_ids: [this.data.currentPartnerId],
+ }, {
+ body: "not empty",
+ needaction: true,
+ needaction_partner_ids: [this.data.currentPartnerId],
+ });
+ await this.start();
+ assert.containsN(
+ document.body,
+ '.o_Message',
+ 2,
+ "should have 2 messages in Inbox initially"
+ );
+
+ await this.afterEvent({
+ eventName: 'o-thread-view-hint-processed',
+ func: () => {
+ // simulate control panel search
+ this.env.messaging.discuss.update({
+ stringifiedDomain: JSON.stringify([['body', 'ilike', 'test']]),
+ });
+ },
+ message: "should wait until search filter is applied",
+ predicate: ({ hint, threadViewer }) => {
+ return (
+ hint.type === 'messages-loaded' &&
+ threadViewer.thread.model === 'mail.box' &&
+ threadViewer.thread.id === 'inbox'
+ );
+ },
+ });
+ assert.containsOnce(
+ document.body,
+ '.o_Message',
+ "should only have the 1 message containing 'test' remaining after doing a search"
+ );
+});
+
+QUnit.test('discuss should keep filter domain on changing thread', async function (assert) {
+ assert.expect(3);
+
+ this.data['mail.channel'].records.push({ id: 20 });
+ this.data['mail.message'].records.push({
+ body: "test",
+ channel_ids: [20],
+ }, {
+ body: "not empty",
+ channel_ids: [20],
+ });
+ await this.start();
+ const channel = this.env.models['mail.thread'].findFromIdentifyingData({
+ id: 20,
+ model: 'mail.channel',
+ });
+ assert.containsNone(
+ document.body,
+ '.o_Message',
+ "should have no message in Inbox initially"
+ );
+
+ await this.afterEvent({
+ eventName: 'o-thread-view-hint-processed',
+ func: () => {
+ // simulate control panel search
+ this.env.messaging.discuss.update({
+ stringifiedDomain: JSON.stringify([['body', 'ilike', 'test']]),
+ });
+ },
+ message: "should wait until search filter is applied",
+ predicate: ({ hint, threadViewer }) => {
+ return (
+ hint.type === 'messages-loaded' &&
+ threadViewer.thread.model === 'mail.box' &&
+ threadViewer.thread.id === 'inbox'
+ );
+ },
+ });
+ assert.containsNone(
+ document.body,
+ '.o_Message',
+ "should have still no message in Inbox after doing a search"
+ );
+
+ await this.afterEvent({
+ eventName: 'o-thread-view-hint-processed',
+ func: () => {
+ document.querySelector(`
+ .o_DiscussSidebar_item[data-thread-local-id="${channel.localId}"]
+ `).click();
+ },
+ message: "should wait until channel 20 is loaded after clicking on it",
+ predicate: ({ hint, threadViewer }) => {
+ return (
+ hint.type === 'messages-loaded' &&
+ threadViewer.thread.model === 'mail.channel' &&
+ threadViewer.thread.id === 20
+ );
+ },
+ });
+ assert.containsOnce(
+ document.body,
+ '.o_Message',
+ "should only have the 1 message containing 'test' in channel 20 (due to the domain still applied on changing thread)"
+ );
+});
+
+QUnit.test('discuss should refresh filtered thread on receiving new message', async function (assert) {
+ assert.expect(2);
+
+ this.data['mail.channel'].records.push({ id: 20 });
+ await this.start();
+ const channel = this.env.models['mail.thread'].findFromIdentifyingData({
+ id: 20,
+ model: 'mail.channel',
+ });
+ await this.afterEvent({
+ eventName: 'o-thread-view-hint-processed',
+ func: () => {
+ document.querySelector(`
+ .o_DiscussSidebar_item[data-thread-local-id="${channel.localId}"]
+ `).click();
+ },
+ message: "should wait until channel 20 is loaded after clicking on it",
+ predicate: ({ hint, threadViewer }) => {
+ return (
+ hint.type === 'messages-loaded' &&
+ threadViewer.thread.model === 'mail.channel' &&
+ threadViewer.thread.id === 20
+ );
+ },
+ });
+ await this.afterEvent({
+ eventName: 'o-thread-view-hint-processed',
+ func: () => {
+ // simulate control panel search
+ this.env.messaging.discuss.update({
+ stringifiedDomain: JSON.stringify([['body', 'ilike', 'test']]),
+ });
+ },
+ message: "should wait until search filter is applied",
+ predicate: ({ hint, threadViewer }) => {
+ return (
+ hint.type === 'messages-loaded' &&
+ threadViewer.thread.model === 'mail.channel' &&
+ threadViewer.thread.id === 20
+ );
+ },
+ });
+ assert.containsNone(
+ document.body,
+ '.o_Message',
+ "should have initially no message in channel 20 matching the search 'test'"
+ );
+
+ // simulate receiving a message
+ await this.afterEvent({
+ eventName: 'o-thread-view-hint-processed',
+ func: () => this.env.services.rpc({
+ route: '/mail/chat_post',
+ params: {
+ uuid: channel.uuid,
+ message_content: "test",
+ },
+ }),
+ message: "should wait until channel 20 refreshed its filtered message list",
+ predicate: data => {
+ return (
+ data.threadViewer.thread.model === 'mail.channel' &&
+ data.threadViewer.thread.id === 20 &&
+ data.hint.type === 'messages-loaded'
+ );
+ },
+ });
+ assert.containsOnce(
+ document.body,
+ '.o_Message',
+ "should only have the 1 message containing 'test' in channel 20 after just receiving it"
+ );
+});
+
+QUnit.test('discuss should refresh filtered thread on changing thread', async function (assert) {
+ assert.expect(4);
+
+ this.data['mail.channel'].records.push({ id: 20 }, { id: 21 });
+ await this.start();
+ const channel20 = this.env.models['mail.thread'].findFromIdentifyingData({
+ id: 20,
+ model: 'mail.channel',
+ });
+ const channel21 = this.env.models['mail.thread'].findFromIdentifyingData({
+ id: 21,
+ model: 'mail.channel',
+ });
+ await this.afterEvent({
+ eventName: 'o-thread-view-hint-processed',
+ func: () => {
+ document.querySelector(`
+ .o_DiscussSidebar_item[data-thread-local-id="${channel20.localId}"]
+ `).click();
+ },
+ message: "should wait until channel 20 is loaded after clicking on it",
+ predicate: ({ hint, threadViewer }) => {
+ return (
+ hint.type === 'messages-loaded' &&
+ threadViewer.thread.model === 'mail.channel' &&
+ threadViewer.thread.id === 20
+ );
+ },
+ });
+ await this.afterEvent({
+ eventName: 'o-thread-view-hint-processed',
+ func: () => {
+ // simulate control panel search
+ this.env.messaging.discuss.update({
+ stringifiedDomain: JSON.stringify([['body', 'ilike', 'test']]),
+ });
+ },
+ message: "should wait until search filter is applied",
+ predicate: ({ hint, threadViewer }) => {
+ return (
+ hint.type === 'messages-loaded' &&
+ threadViewer.thread.model === 'mail.channel' &&
+ threadViewer.thread.id === 20
+ );
+ },
+ });
+ assert.containsNone(
+ document.body,
+ '.o_Message',
+ "should have initially no message in channel 20 matching the search 'test'"
+ );
+
+ await this.afterEvent({
+ eventName: 'o-thread-view-hint-processed',
+ func: () => {
+ document.querySelector(`
+ .o_DiscussSidebar_item[data-thread-local-id="${channel21.localId}"]
+ `).click();
+ },
+ message: "should wait until channel 21 is loaded after clicking on it",
+ predicate: ({ hint, threadViewer }) => {
+ return (
+ hint.type === 'messages-loaded' &&
+ threadViewer.thread.model === 'mail.channel' &&
+ threadViewer.thread.id === 21
+ );
+ },
+ });
+ assert.containsNone(
+ document.body,
+ '.o_Message',
+ "should have no message in channel 21 matching the search 'test'"
+ );
+ // simulate receiving a message on channel 20 while channel 21 is displayed
+ await this.env.services.rpc({
+ route: '/mail/chat_post',
+ params: {
+ uuid: channel20.uuid,
+ message_content: "test",
+ },
+ });
+ assert.containsNone(
+ document.body,
+ '.o_Message',
+ "should still have no message in channel 21 matching the search 'test' after receiving a message on channel 20"
+ );
+
+ await this.afterEvent({
+ eventName: 'o-thread-view-hint-processed',
+ func: () => {
+ document.querySelector(`
+ .o_DiscussSidebar_item[data-thread-local-id="${channel20.localId}"]
+ `).click();
+ },
+ message: "should wait until channel 20 is loaded with the new message after clicking on it",
+ predicate: ({ hint, threadViewer }) => {
+ return (
+ hint.type === 'messages-loaded' &&
+ threadViewer.thread.model === 'mail.channel' &&
+ threadViewer.thread.id === 20 &&
+ threadViewer.threadCache.fetchedMessages.length === 1
+ );
+ },
+ });
+ assert.containsOnce(
+ document.body,
+ '.o_Message',
+ "should now have the 1 message containing 'test' in channel 20 when displaying it, after having received the message while the channel was not visible"
+ );
+});
+
+QUnit.test('select all and unselect all buttons should work on filtered thread', async function (assert) {
+ assert.expect(4);
+
+ this.data['mail.channel'].records.push({
+ id: 20,
+ is_moderator: true,
+ moderation: true,
+ name: "general",
+ });
+ this.data['mail.message'].records.push({
+ body: "<p>test</p>",
+ model: 'mail.channel',
+ moderation_status: 'pending_moderation',
+ res_id: 20,
+ });
+ await this.start();
+ await this.afterEvent({
+ eventName: 'o-thread-view-hint-processed',
+ func: () => {
+ document.querySelector(`
+ .o_DiscussSidebar_item[data-thread-local-id="${this.env.messaging.moderation.localId}"]
+ `).click();
+ },
+ message: "should wait until moderation box is loaded after clicking on it",
+ predicate: ({ hint, threadViewer }) => {
+ return (
+ hint.type === 'messages-loaded' &&
+ threadViewer.thread.model === 'mail.box' &&
+ threadViewer.thread.id === 'moderation'
+ );
+ },
+ });
+ await this.afterEvent({
+ eventName: 'o-thread-view-hint-processed',
+ func: () => {
+ // simulate control panel search
+ this.env.messaging.discuss.update({
+ stringifiedDomain: JSON.stringify([['body', 'ilike', 'test']]),
+ });
+ },
+ message: "should wait until search filter is applied",
+ predicate: ({ hint, threadViewer }) => {
+ return (
+ hint.type === 'messages-loaded' &&
+ threadViewer.thread.model === 'mail.box' &&
+ threadViewer.thread.id === 'moderation'
+ );
+ },
+ });
+ assert.containsOnce(
+ document.body,
+ '.o_Message',
+ "should only have the 1 message containing 'test' in moderation box"
+ );
+ assert.notOk(
+ document.querySelector('.o_Message_checkbox').checked,
+ "the moderation checkbox should not be checked initially"
+ );
+
+ await afterNextRender(() => document.querySelector('.o_widget_Discuss_controlPanelButtonSelectAll').click());
+ assert.ok(
+ document.querySelector('.o_Message_checkbox').checked,
+ "the moderation checkbox should be checked after clicking on 'select all'"
+ );
+
+ await afterNextRender(() => document.querySelector('.o_widget_Discuss_controlPanelButtonUnselectAll').click());
+ assert.notOk(
+ document.querySelector('.o_Message_checkbox').checked,
+ "the moderation checkbox should be unchecked after clicking on 'unselect all'"
+ );
+});
+
+});
+});
+});
+
+});
diff --git a/addons/mail/static/src/components/message_list/message_list.js b/addons/mail/static/src/components/message_list/message_list.js
index 72368ec3689..b7332514141 100644
--- a/addons/mail/static/src/components/message_list/message_list.js
+++ b/addons/mail/static/src/components/message_list/message_list.js
@@ -124,12 +124,18 @@ class MessageList extends Component {
case 'home-menu-shown':
this._adjustFromHomeMenuShown(hint);
break;
+ case 'messages-loaded':
+ this.threadView.markComponentHintProcessed(hint);
+ break;
case 'message-received':
this._adjustFromMessageReceived(hint);
break;
case 'more-messages-loaded':
this._adjustFromMoreMessagesLoaded(hint);
break;
+ case 'new-messages-loaded':
+ this.threadView.markComponentHintProcessed(hint);
+ break;
}
}
this._willPatchSnapshot = undefined;
@@ -333,9 +339,6 @@ class MessageList extends Component {
isProcessed = true;
}
if (isProcessed) {
- this.env.messagingBus.trigger('o-component-message-list-thread-cache-changed', {
- threadViewer: this.threadView.threadViewer,
- });
this.threadView.markComponentHintProcessed(hint);
}
}
diff --git a/addons/mail/static/src/components/thread_view/thread_view_tests.js b/addons/mail/static/src/components/thread_view/thread_view_tests.js
index 320512a0f8c..2bf4c08a7a8 100644
--- a/addons/mail/static/src/components/thread_view/thread_view_tests.js
+++ b/addons/mail/static/src/components/thread_view/thread_view_tests.js
@@ -741,13 +741,17 @@ QUnit.test('basic rendering of canceled notification', async function (assert) {
}]],
});
await this.afterEvent({
- eventName: 'o-component-message-list-thread-cache-changed',
+ eventName: 'o-thread-view-hint-processed',
func: () => {
this.createThreadViewComponent(threadViewer.threadView);
},
message: "thread become loaded with messages",
- predicate: ({ threadViewer }) => {
- return threadViewer.thread.model === 'mail.channel' && threadViewer.thread.id === 11;
+ predicate: ({ hint, threadViewer }) => {
+ return (
+ hint.type === 'messages-loaded' &&
+ threadViewer.thread.model === 'mail.channel' &&
+ threadViewer.thread.id === 11
+ );
},
});
diff --git a/addons/mail/static/src/models/discuss/discuss.js b/addons/mail/static/src/models/discuss/discuss.js
index 4bdc5e43549..5fe14196602 100644
--- a/addons/mail/static/src/models/discuss/discuss.js
+++ b/addons/mail/static/src/models/discuss/discuss.js
@@ -175,7 +175,6 @@ function factory(dependencies) {
*/
async openThread(thread) {
this.update({
- stringifiedDomain: '[]',
thread: [['link', thread]],
});
this.focus();
@@ -509,6 +508,7 @@ function factory(dependencies) {
}),
/**
* Determines the domain to apply when fetching messages for `this.thread`.
+ * This value should only be written by the control panel.
*/
stringifiedDomain: attr({
default: '[]',
diff --git a/addons/mail/static/src/models/messaging_menu/messaging_menu.js b/addons/mail/static/src/models/messaging_menu/messaging_menu.js
index 615082ea847..f255aa210e0 100644
--- a/addons/mail/static/src/models/messaging_menu/messaging_menu.js
+++ b/addons/mail/static/src/models/messaging_menu/messaging_menu.js
@@ -50,16 +50,11 @@ function factory(dependencies) {
return;
}
const inbox = this.env.messaging.inbox;
- if (
- !inbox ||
- !inbox.mainCache ||
- inbox.mainCache.isLoaded ||
- inbox.mainCache.isLoading
- ) {
+ if (!inbox || !inbox.mainCache) {
return;
}
// populate some needaction messages on threads.
- inbox.mainCache.update({ hasToLoadMessages: true });
+ inbox.mainCache.update({ isCacheRefreshRequested: true });
}
/**
@@ -127,8 +122,6 @@ function factory(dependencies) {
'isOpen',
'messagingInbox',
'messagingInboxMainCache',
- 'messagingInboxMainCacheIsLoaded',
- 'messagingInboxMainCacheIsLoading',
],
}),
/**
@@ -152,12 +145,6 @@ function factory(dependencies) {
messagingInboxMainCache: one2one('mail.thread_cache', {
related: 'messagingInbox.mainCache',
}),
- messagingInboxMainCacheIsLoaded: attr({
- related: 'messagingInboxMainCache.isLoaded',
- }),
- messagingInboxMainCacheIsLoading: attr({
- related: 'messagingInboxMainCache.isLoading',
- }),
};
MessagingMenu.modelName = 'mail.messaging_menu';
diff --git a/addons/mail/static/src/models/messaging_notification_handler/messaging_notification_handler.js b/addons/mail/static/src/models/messaging_notification_handler/messaging_notification_handler.js
index 6eb06ee01af..4b3baded714 100644
--- a/addons/mail/static/src/models/messaging_notification_handler/messaging_notification_handler.js
+++ b/addons/mail/static/src/models/messaging_notification_handler/messaging_notification_handler.js
@@ -551,8 +551,6 @@ function factory(dependencies) {
const inboxMailbox = this.env.messaging.inbox;
// 1. move messages from inbox to history
- // AKU TODO: flag other caches to invalidate
- // task-2171873
for (const message_id of message_ids) {
// We need to ignore all not yet known messages because we don't want them
// to be shown partially as they would be linked directly to mainCache
@@ -625,10 +623,6 @@ function factory(dependencies) {
continue;
}
message.update({ isStarred: starred });
- if (!starred) {
- // AKU TODO: flag starred other caches for invalidation
- // task-2171873
- }
starredMailbox.update({
counter: starred
? starredMailbox.counter + 1
diff --git a/addons/mail/static/src/models/thread/thread.js b/addons/mail/static/src/models/thread/thread.js
index d50fca3e3f4..ed48d14f270 100644
--- a/addons/mail/static/src/models/thread/thread.js
+++ b/addons/mail/static/src/models/thread/thread.js
@@ -1803,6 +1803,9 @@ function factory(dependencies) {
suggestedRecipientInfoList: one2many('mail.suggested_recipient_info', {
inverse: 'thread',
}),
+ threadViews: one2many('mail.thread_view', {
+ inverse: 'thread',
+ }),
/**
* States the `mail.activity` that belongs to `this` and that are due
* specifically today.
@@ -1825,9 +1828,6 @@ function factory(dependencies) {
dependencies: ['orderedOtherTypingMembers'],
}),
uuid: attr(),
- threadViews: one2many('mail.thread_view', {
- inverse: 'thread',
- }),
};
Thread.modelName = 'mail.thread';
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 080040981f2..4e2bb5208e2 100644
--- a/addons/mail/static/src/models/thread_cache/thread_cache.js
+++ b/addons/mail/static/src/models/thread_cache/thread_cache.js
@@ -20,7 +20,7 @@ function factory(dependencies) {
return;
}
if (!this.isLoaded) {
- this.update({ hasToLoadMessages: true });
+ this.update({ isCacheRefreshRequested: true });
return;
}
this.update({ isLoadingMore: true });
@@ -31,7 +31,7 @@ function factory(dependencies) {
limit,
}));
for (const threadView of this.threadViews) {
- threadView.addComponentHint('more-messages-loaded');
+ threadView.addComponentHint('more-messages-loaded', { fetchedMessages });
}
this.update({ isLoadingMore: false });
if (fetchedMessages.length < limit) {
@@ -48,14 +48,18 @@ function factory(dependencies) {
return;
}
if (!this.isLoaded) {
- this.update({ hasToLoadMessages: true });
+ this.update({ isCacheRefreshRequested: true });
return;
}
const messageIds = this.fetchedMessages.map(message => message.id);
- return this._loadMessages({
+ const fetchedMessages = this._loadMessages({
extraDomain: [['id', '>', Math.max(...messageIds)]],
limit: false,
});
+ for (const threadView of this.threadViews) {
+ threadView.addComponentHint('new-messages-loaded', { fetchedMessages });
+ }
+ return fetchedMessages;
}
//----------------------------------------------------------------------
@@ -140,8 +144,6 @@ function factory(dependencies) {
}
let messages = this.fetchedMessages;
if (this.stringifiedDomain !== '[]') {
- // AKU TODO: flag for invalidation if there are newer messages
- // in thread. task-2171873
return [['replace', messages]];
}
// main cache: adjust with newer messages
@@ -178,13 +180,34 @@ function factory(dependencies) {
* @returns {boolean}
*/
_computeHasToLoadMessages() {
- return (
- this.thread &&
- !this.thread.isTemporary &&
- this.threadViews.length > 0 &&
- !this.isLoaded &&
- !this.isLoading
- );
+ if (!this.thread) {
+ // happens during destroy or compute executed in wrong order
+ return false;
+ }
+ const wasCacheRefreshRequested = this.isCacheRefreshRequested;
+ // mark hint as processed
+ this.update({ isCacheRefreshRequested: false });
+ if (this.thread.isTemporary) {
+ // temporary threads don't exist on the server
+ return false;
+ }
+ if (!wasCacheRefreshRequested && this.threadViews.length === 0) {
+ // don't load message that won't be used
+ return false;
+ }
+ if (!wasCacheRefreshRequested && (this.isLoaded || this.isLoading)) {
+ // avoid duplicate RPC
+ return false;
+ }
+ const isMainCache = this.thread.mainCache === this;
+ if (isMainCache && (this.isLoaded || this.isLoading)) {
+ // Ignore request on the main cache if it is already loaded or
+ // loading. Indeed the main cache is automatically sync with
+ // server updates already, so there is never a need to refresh
+ // it past the first time.
+ return false;
+ }
+ return true;
}
/**
@@ -279,7 +302,31 @@ function factory(dependencies) {
if (!this.hasToLoadMessages) {
return;
}
- this._loadMessages();
+ this._loadMessages().then(fetchedMessages => {
+ for (const threadView of this.threadViews) {
+ threadView.addComponentHint('messages-loaded', { fetchedMessages });
+ }
+ });
+ }
+
+ /**
+ * Handles change of messages on this thread cache. This is useful to
+ * refresh non-main caches that are currently displayed when the main
+ * cache receives updates. This is necessary because only the main cache
+ * is aware of changes in real time.
+ */
+ _onMessagesChanged() {
+ if (!this.thread) {
+ return;
+ }
+ if (this.thread.mainCache !== this) {
+ return;
+ }
+ for (const threadView of this.thread.threadViews) {
+ if (threadView.threadCache) {
+ threadView.threadCache.update({ isCacheRefreshRequested: true });
+ }
+ }
}
}
@@ -315,15 +362,19 @@ function factory(dependencies) {
dependencies: ['threadMessages'],
}),
/**
- * Determines whether `this` should load initial messages.
+ * Determines whether `this` should load initial messages. This field is
+ * computed and should be considered read-only.
+ * @see `isCacheRefreshRequested` to request manual refresh of messages.
*/
hasToLoadMessages: attr({
compute: '_computeHasToLoadMessages',
dependencies: [
+ 'isCacheRefreshRequested',
'isLoaded',
'isLoading',
'thread',
'threadIsTemporary',
+ 'threadMainCache',
'threadViews',
],
}),
@@ -340,6 +391,14 @@ function factory(dependencies) {
default: false,
}),
/**
+ * Determines whether `this` should consider refreshing its messages.
+ * This field is a hint that may or may not lead to an actual refresh.
+ * @see `hasToLoadMessages`
+ */
+ isCacheRefreshRequested: attr({
+ default: false,
+ }),
+ /**
* Last message that has been fetched by this thread cache.
*
* This DOES NOT necessarily mean the last message linked to this thread
@@ -379,6 +438,18 @@ function factory(dependencies) {
],
}),
/**
+ * Not a real field, used to trigger `_onMessagesChanged` when one of
+ * the dependencies changes.
+ */
+ onMessagesChanged: attr({
+ compute: '_onMessagesChanged',
+ dependencies: [
+ 'messages',
+ 'thread',
+ 'threadMainCache',
+ ],
+ }),
+ /**
* Ordered list of messages that have been fetched by this cache.
*
* This DOES NOT necessarily includes all messages linked to this thread
@@ -408,6 +479,12 @@ function factory(dependencies) {
threadIsTemporary: attr({
related: 'thread.isTemporary',
}),
+ /**
+ * Serves as compute dependency.
+ */
+ threadMainCache: many2one('mail.thread_cache', {
+ related: 'thread.mainCache',
+ }),
threadMessages: many2many('mail.message', {
related: 'thread.messages',
}),
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 ee53e616b1c..0c6cdf6d458 100644
--- a/addons/mail/static/src/models/thread_view/thread_view.js
+++ b/addons/mail/static/src/models/thread_view/thread_view.js
@@ -118,6 +118,9 @@ function factory(dependencies) {
*/
_onThreadCacheChanged() {
this.addComponentHint('change-of-thread-cache');
+ if (this.threadCache) {
+ this.threadCache.update({ isCacheRefreshRequested: true });
+ }
}
/**
diff --git a/addons/mail/static/src/widgets/discuss/discuss.js b/addons/mail/static/src/widgets/discuss/discuss.js
index ac1eea69bf0..86f3ba32adb 100644
--- a/addons/mail/static/src/widgets/discuss/discuss.js
+++ b/addons/mail/static/src/widgets/discuss/discuss.js
@@ -361,7 +361,7 @@ const DiscussWidget = AbstractAction.extend({
_onClickSelectAll() {
this.env.models['mail.message'].checkAll(
this.discuss.thread,
- this.discuss.threadStringifiedDomain
+ this.discuss.stringifiedDomain
);
},
/**
@@ -370,7 +370,7 @@ const DiscussWidget = AbstractAction.extend({
_onClickUnselectAll() {
this.env.models['mail.message'].uncheckAll(
this.discuss.thread,
- this.discuss.threadStringifiedDomain
+ this.discuss.stringifiedDomain
);
},
/**
diff --git a/addons/mail/views/assets.xml b/addons/mail/views/assets.xml
index bfe82a1c8eb..e3ba3797b25 100644
--- a/addons/mail/views/assets.xml
+++ b/addons/mail/views/assets.xml
@@ -281,6 +281,7 @@
<script type="text/javascript" src="/mail/static/src/components/composer_suggestion/composer_suggestion_command_tests.js"/>
<script type="text/javascript" src="/mail/static/src/components/composer_suggestion/composer_suggestion_partner_tests.js"/>
<script type="text/javascript" src="/mail/static/src/components/dialog_manager/dialog_manager_tests.js"></script>
+ <script type="text/javascript" src="/mail/static/src/components/discuss/tests/discuss_domain_tests.js"/>
<script type="text/javascript" src="/mail/static/src/components/discuss/tests/discuss_inbox_tests.js"></script>
<script type="text/javascript" src="/mail/static/src/components/discuss/tests/discuss_moderation_tests.js"></script>
<script type="text/javascript" src="/mail/static/src/components/discuss/tests/discuss_pinned_tests.js"></script>