summaryrefslogtreecommitdiff
path: root/addons/mail/static/src/models/thread_cache
diff options
context:
space:
mode:
authorXavier Dubuc <xdu@odoo.com>2020-09-03 16:24:19 +0000
committerSébastien Theys <seb@odoo.com>2020-09-04 14:59:01 +0000
commit8d3413ff647de8d98c6858062dd07dde4160af95 (patch)
tree3dc134e83c874d5c7f7bf739f13a88ab986ddb1b /addons/mail/static/src/models/thread_cache
parenta5ab687be4e7890262ce9429e18dd011021513d5 (diff)
[FIX] mail, sms, snailmail: prevent message_fetch for hidden threads
Automatically load messages from a thread cache only if the thread cache is used in at least one thread view. Ensure thread viewer don't keep thread view records alive when they are not displayed. This change is particularly important for performance, to avoid spamming RPC at init if many threads are opened in chat windows. task-2310623 closes odoo/odoo#57061 X-original-commit: dd6981c7812134e0588f80a157eaba45512cd68e Related: odoo/enterprise#12936 Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
Diffstat (limited to 'addons/mail/static/src/models/thread_cache')
-rw-r--r--addons/mail/static/src/models/thread_cache/thread_cache.js211
1 files changed, 114 insertions, 97 deletions
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 e6147fd68a0..d909fb88984 100644
--- a/addons/mail/static/src/models/thread_cache/thread_cache.js
+++ b/addons/mail/static/src/models/thread_cache/thread_cache.js
@@ -2,9 +2,7 @@ odoo.define('mail/static/src/models/thread_cache/thread_cache.js', function (req
'use strict';
const { registerNewModel } = require('mail/static/src/model/model_core.js');
-const { attr, many2many, many2one } = require('mail/static/src/model/model_field.js');
-
-const MESSAGE_FETCH_LIMIT = 30;
+const { attr, many2many, many2one, one2many } = require('mail/static/src/model/model_field.js');
function factory(dependencies) {
@@ -15,79 +13,49 @@ function factory(dependencies) {
//----------------------------------------------------------------------
/**
- * Load this thread cache, by fetching the most recent messages in this
- * conversation.
+ * @returns {mail.message[]|undefined}
*/
- async loadMessages() {
- if (this.isLoaded && this.isLoading) {
+ async loadMoreMessages() {
+ if (this.isAllHistoryLoaded || this.isLoading) {
return;
}
- const searchDomain = JSON.parse(this.stringifiedDomain);
- let domain = searchDomain.length ? searchDomain : [];
- domain = this._extendMessageDomain(domain);
- this.update({ isLoading: true });
- let messagesData = [];
- if (!this.thread.isTemporary) {
- messagesData = await this.async(() => this.env.services.rpc({
- model: 'mail.message',
- method: 'message_fetch',
- args: [domain],
- kwargs: this._getFetchMessagesKwargs(),
- }, { shadow: true }));
- }
- this._handleMessagesLoaded(messagesData);
- }
-
- async loadMoreMessages() {
- const searchDomain = JSON.parse(this.stringifiedDomain);
- let domain = searchDomain.length ? searchDomain : [];
- domain = this._extendMessageDomain(domain);
- if (this.isAllHistoryLoaded && this.isLoadingMore) {
+ if (!this.isLoaded) {
+ this.update({ hasToLoadMessages: true });
return;
}
this.update({ isLoadingMore: true });
- const minMessageId = Math.min(
- ...this.fetchedMessages.map(message => message.id)
- );
- domain = [['id', '<', minMessageId]].concat(domain);
- const messagesData = await this.async(() => this.env.services.rpc({
- model: 'mail.message',
- method: 'message_fetch',
- args: [domain],
- kwargs: this._getFetchMessagesKwargs(),
- }, { shadow: true }));
- for (const threadView of this.thread.threadViews) {
+ const messageIds = this.fetchedMessages.map(message => message.id);
+ const limit = 30;
+ const fetchedMessages = await this.async(() => this._loadMessages({
+ extraDomain: [['id', '<', Math.min(...messageIds)]],
+ limit,
+ }));
+ for (const threadView of this.threadViews) {
threadView.addComponentHint('more-messages-loaded');
}
- this._handleMessagesLoaded(messagesData);
+ this.update({ isLoadingMore: false });
+ if (fetchedMessages.length < limit) {
+ this.update({ isAllHistoryLoaded: true });
+ }
+ return fetchedMessages;
}
+ /**
+ * @returns {mail.message[]|undefined}
+ */
async loadNewMessages() {
if (this.isLoading) {
return;
}
if (!this.isLoaded) {
- await this.async(() => this.loadMessages());
+ this.update({ hasToLoadMessages: true });
return;
}
const messageIds = this.fetchedMessages.map(message => message.id);
- const searchDomain = JSON.parse(this.stringifiedDomain);
- let domain = searchDomain.length ? searchDomain : [];
- domain = this._extendMessageDomain(domain);
- if (messageIds.length > 0) {
- const lastMessageId = Math.max(...messageIds);
- domain = [['id', '>', lastMessageId]].concat(domain);
- }
- this.update({ isLoading: true });
- const messageFetchKwargs = this._getFetchMessagesKwargs();
- messageFetchKwargs.limit = false;
- const messagesData = await this.async(() => this.env.services.rpc({
- model: 'mail.message',
- method: 'message_fetch',
- args: [domain],
- kwargs: messageFetchKwargs,
- }, { shadow: true }));
- this._handleMessagesLoaded(messagesData);
+ return this._loadMessages({
+ extraDomain: [['id', '>', Math.max(...messageIds)]],
+ limit: false,
+ });
}
//----------------------------------------------------------------------
@@ -207,6 +175,20 @@ function factory(dependencies) {
/**
* @private
+ * @returns {boolean}
+ */
+ _computeHasToLoadMessages() {
+ return (
+ this.thread &&
+ !this.thread.isTemporary &&
+ this.threadViews.length > 0 &&
+ !this.isLoaded &&
+ !this.isLoading
+ );
+ }
+
+ /**
+ * @private
* @returns {mail.message[]}
*/
_computeUncheckedMessages() {
@@ -247,54 +229,53 @@ function factory(dependencies) {
/**
* @private
- * @returns {Object}
+ * @param {Object} [param0={}]
+ * @param {Array[]} [param0.extraDomain]
+ * @param {integer} [param0.limit=30]
+ * @returns {mail.message[]}
*/
- _getFetchMessagesKwargs() {
- const thread = this.thread;
- let kwargs = {
- limit: MESSAGE_FETCH_LIMIT,
- context: this.env.session.user_context,
- };
- if (thread.moderation) {
- // thread is a channel
- kwargs.moderated_channel_ids = [thread.id];
+ async _loadMessages({ extraDomain, limit = 30 } = {}) {
+ this.update({ isLoading: true });
+ const searchDomain = JSON.parse(this.stringifiedDomain);
+ let domain = searchDomain.length ? searchDomain : [];
+ domain = this._extendMessageDomain(domain);
+ if (extraDomain) {
+ domain = extraDomain.concat(domain);
+ }
+ const context = this.env.session.user_context;
+ const moderated_channel_ids = this.thread.moderation
+ ? [this.thread.id]
+ : undefined;
+ const messages = await this.async(() =>
+ this.env.models['mail.message'].performRpcMessageFetch(
+ domain,
+ limit,
+ moderated_channel_ids,
+ context,
+ )
+ );
+ this.update({
+ fetchedMessages: [['link', messages]],
+ isLoaded: true,
+ isLoading: false,
+ });
+ if (!extraDomain && messages.length < limit) {
+ this.update({ isAllHistoryLoaded: true });
}
- return kwargs;
+ return messages;
}
/**
+ * Loads this thread cache, by fetching the most recent messages in this
+ * conversation.
+ *
* @private
- * @param {Object[]} messageData
*/
- _handleMessagesLoaded(messagesData) {
- const messages = this.env.models['mail.message'].insert(messagesData.map(
- messageData => this.env.models['mail.message'].convertData(messageData)
- ));
-
- if (!this.thread) {
+ _onHasToLoadMessagesChanged() {
+ if (!this.hasToLoadMessages) {
return;
}
- this.thread.update({
- messageSeenIndicators: [[
- 'insert',
- messagesData.map(messageData => {
- return {
- id: this.env.models['mail.message_seen_indicator'].computeId(messageData.id, this.thread.id),
- message: [['insert', { id: messageData.id }]],
- };
- })
- ]],
- });
- this.update({
- fetchedMessages: [['link', messages]],
- isAllHistoryLoaded: messagesData.length < MESSAGE_FETCH_LIMIT,
- isLoaded: true,
- isLoading: false,
- isLoadingMore: false,
- });
- for (const threadView of this.thread.threadViews) {
- threadView.handleThreadCacheLoaded(this);
- }
+ this._loadMessages();
}
}
@@ -329,6 +310,19 @@ function factory(dependencies) {
compute: '_computeFetchedMessages',
dependencies: ['threadMessages'],
}),
+ /**
+ * Determines whether `this` should load initial messages.
+ */
+ hasToLoadMessages: attr({
+ compute: '_computeHasToLoadMessages',
+ dependencies: [
+ 'isLoaded',
+ 'isLoading',
+ 'thread',
+ 'threadIsTemporary',
+ 'threadViews',
+ ],
+ }),
isAllHistoryLoaded: attr({
default: false,
}),
@@ -370,6 +364,17 @@ function factory(dependencies) {
],
}),
/**
+ * Loads initial messages from `this`.
+ * This is not a "real" field, its compute function is used to trigger
+ * the load of messages at the right time.
+ */
+ onHasToLoadMessagesChanged: attr({
+ compute: '_onHasToLoadMessagesChanged',
+ dependencies: [
+ 'hasToLoadMessages',
+ ],
+ }),
+ /**
* Ordered list of messages that have been fetched by this cache.
*
* This DOES NOT necessarily includes all messages linked to this thread
@@ -393,9 +398,21 @@ function factory(dependencies) {
thread: many2one('mail.thread', {
inverse: 'caches',
}),
+ /**
+ * Serves as compute dependency.
+ */
+ threadIsTemporary: attr({
+ related: 'thread.isTemporary',
+ }),
threadMessages: many2many('mail.message', {
related: 'thread.messages',
}),
+ /**
+ * States the 'mail.thread_view' that are currently displaying `this`.
+ */
+ threadViews: one2many('mail.thread_view', {
+ inverse: 'threadCache',
+ }),
uncheckedMessages: many2many('mail.message', {
compute: '_computeUncheckedMessages',
dependencies: [