summaryrefslogtreecommitdiff
path: root/addons/mail/static/src/models/thread_view
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_view
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_view')
-rw-r--r--addons/mail/static/src/models/thread_view/thread_view.js200
-rw-r--r--addons/mail/static/src/models/thread_view/thread_viewer.js257
2 files changed, 356 insertions, 101 deletions
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 f0075314561..d7ea1508067 100644
--- a/addons/mail/static/src/models/thread_view/thread_view.js
+++ b/addons/mail/static/src/models/thread_view/thread_view.js
@@ -13,7 +13,7 @@ function factory(dependencies) {
* @override
*/
_willDelete() {
- this._stopLoading();
+ this.env.browser.clearTimeout(this._loaderTimeout);
return super._willDelete(...arguments);
}
@@ -41,16 +41,6 @@ function factory(dependencies) {
}
/**
- * @param {mail.thread_cache} threadCache
- */
- handleThreadCacheLoaded(threadCache) {
- if (threadCache !== this.threadCache) {
- return;
- }
- this._stopLoading();
- }
-
- /**
* @param {Object} hint
*/
markComponentHintProcessed(hint) {
@@ -77,33 +67,19 @@ function factory(dependencies) {
}
}
- /**
- * @param {string} scrollTop
- */
- saveThreadCacheScrollPositionsAsInitial(scrollTop) {
- if (!this.threadCache) {
- return;
- }
- this.update({
- threadCacheInitialScrollPositions: Object.assign({}, this.threadCacheInitialScrollPositions, {
- [this.threadCache.localId]: scrollTop,
- }),
- });
- }
-
//----------------------------------------------------------------------
// Private
//----------------------------------------------------------------------
/**
* @private
- * @returns {mail.thread_cache|undefined}
+ * @returns {integer|undefined}
*/
- _computeThreadCache() {
- if (!this.thread) {
- return [];
+ _computeThreadCacheInitialScrollPosition() {
+ if (!this.threadCache) {
+ return;
}
- return [['link', this.thread.cache(this.stringifiedDomain)]];
+ return this.threadCacheInitialScrollPositions[this.threadCache.localId];
}
/**
@@ -131,75 +107,37 @@ function factory(dependencies) {
/**
* @private
- * @returns {integer|undefined}
- */
- _computeThreadCacheInitialScrollPosition() {
- if (!this.threadCache) {
- return undefined;
- }
- return this.threadCacheInitialScrollPositions[this.threadCache.localId];
- }
-
- /**
- * @private
*/
- _prepareLoading() {
- this._isPreparingLoading = true;
- this._loaderTimeout = setTimeout(() => {
- this.isShowingLoading = true;
- this._isPreparingLoading = false;
- }, 400);
+ _onThreadCacheChanged() {
+ this.addComponentHint('change-of-thread-cache');
}
/**
* @private
*/
- _stopLoading() {
- clearTimeout(this._loaderTimeout);
- this._loaderTimeout = null;
- this.isShowingLoading = false;
- this._isPreparingLoading = false;
- }
-
- /**
- * @override
- */
- _updateAfter(previous) {
- if (this.thread && this.thread !== previous.thread) {
- this._stopLoading();
- if (!this.threadCache.isLoaded && !this.threadCache.isLoading) {
- this.threadCache.loadMessages();
+ _onThreadCacheIsLoadingChanged() {
+ if (this.threadCache && this.threadCache.isLoading) {
+ if (!this.isLoading && !this.isPreparingLoading) {
+ this.update({ isPreparingLoading: true });
+ this.async(() =>
+ new Promise(resolve => {
+ this._loaderTimeout = this.env.browser.setTimeout(resolve, 400);
+ }
+ )).then(() => {
+ const isLoading = this.threadCache
+ ? this.threadCache.isLoading
+ : false;
+ this.update({ isLoading, isPreparingLoading: false });
+ });
}
+ return;
}
- if (this.threadCache !== previous.threadCache) {
- this._stopLoading();
- this.addComponentHint('change-of-thread-cache');
- }
-
- if (
- this.thread && this.threadCache && this.threadCache.isLoading &&
- !this.isShowingLoading && !this._isPreparingLoading
- ) {
- this._prepareLoading();
- }
- }
-
- /**
- * @override
- */
- _updateBefore() {
- return {
- thread: this.thread,
- threadCache: this.threadCache,
- };
+ this.env.browser.clearTimeout(this._loaderTimeout);
+ this.update({ isLoading: false, isPreparingLoading: false });
}
-
}
ThreadView.fields = {
- chatWindow: one2one('mail.chat_window', {
- inverse: 'threadView',
- }),
checkedMessages: many2many('mail.message', {
related: 'threadCache.checkedMessages',
}),
@@ -230,6 +168,31 @@ function factory(dependencies) {
hasComposerFocus: attr({
related: 'composer.hasFocus',
}),
+ /**
+ * States whether `this.threadCache` is currently loading messages.
+ *
+ * This field is related to `this.threadCache.isLoading` but with a
+ * delay on its update to avoid flickering on the UI.
+ *
+ * It is computed through `_onThreadCacheIsLoadingChanged` and it should
+ * otherwise be considered read-only.
+ */
+ isLoading: attr({
+ default: false,
+ }),
+ /**
+ * States whether `this` is aware of `this.threadCache` currently
+ * loading messages, but `this` is not yet ready to display that loading
+ * on the UI.
+ *
+ * This field is computed through `_onThreadCacheIsLoadingChanged` and
+ * it should otherwise be considered read-only.
+ *
+ * @see `this.isLoading`
+ */
+ isPreparingLoading: attr({
+ default: false,
+ }),
lastMessage: many2one('mail.message', {
related: 'thread.lastMessage',
}),
@@ -241,19 +204,48 @@ function factory(dependencies) {
messages: many2many('mail.message', {
related: 'threadCache.messages',
}),
+ /**
+ * Not a real field, used to trigger `_onThreadCacheChanged` when one of
+ * the dependencies changes.
+ */
+ onThreadCacheChanged: attr({
+ compute: '_onThreadCacheChanged',
+ dependencies: [
+ 'threadCache'
+ ],
+ }),
+ /**
+ * Not a real field, used to trigger `_onThreadCacheIsLoadingChanged`
+ * when one of the dependencies changes.
+ *
+ * @see `this.isLoading`
+ */
+ onThreadCacheIsLoadingChanged: attr({
+ compute: '_onThreadCacheIsLoadingChanged',
+ dependencies: [
+ 'threadCache',
+ 'threadCacheIsLoading',
+ ],
+ }),
+ /**
+ * Determines the domain to apply when fetching messages for `this.thread`.
+ */
stringifiedDomain: attr({
- default: '[]',
+ related: 'threadViewer.stringifiedDomain',
}),
+ /**
+ * Determines the `mail.thread` currently displayed by `this`.
+ */
thread: many2one('mail.thread', {
inverse: 'threadViews',
+ related: 'threadViewer.thread',
}),
+ /**
+ * States the `mail.thread_cache` currently displayed by `this`.
+ */
threadCache: many2one('mail.thread_cache', {
- compute: '_computeThreadCache',
- dependencies: [
- 'stringifiedDomain',
- 'thread',
- 'threadCaches',
- ],
+ inverse: 'threadViews',
+ related: 'threadViewer.threadCache',
}),
threadCacheInitialScrollPosition: attr({
compute: '_computeThreadCacheInitialScrollPosition',
@@ -263,17 +255,17 @@ function factory(dependencies) {
],
}),
/**
+ * Serves as compute dependency.
+ */
+ threadCacheIsLoading: attr({
+ related: 'threadCache.isLoading',
+ }),
+ /**
* List of saved initial scroll positions of thread caches.
- * Useful to restore scroll position on changing back to this
- * thread cache. Note that this is only applied when opening
- * the thread cache, because scroll position may change fast so
- * save is already throttled
*/
threadCacheInitialScrollPositions: attr({
default: {},
- }),
- threadCaches: many2many('mail.thread_cache', {
- related: 'thread.caches',
+ related: 'threadViewer.threadCacheInitialScrollPositions',
}),
/**
* Not a real field, used to trigger `thread.markAsSeen` when one of
@@ -288,6 +280,12 @@ function factory(dependencies) {
'threadCache',
],
}),
+ /**
+ * Determines the `mail.thread_viewer` currently managing `this`.
+ */
+ threadViewer: one2one('mail.thread_viewer', {
+ inverse: 'threadView',
+ }),
uncheckedMessages: many2many('mail.message', {
related: 'threadCache.uncheckedMessages',
}),
diff --git a/addons/mail/static/src/models/thread_view/thread_viewer.js b/addons/mail/static/src/models/thread_view/thread_viewer.js
new file mode 100644
index 00000000000..da546338ca3
--- /dev/null
+++ b/addons/mail/static/src/models/thread_view/thread_viewer.js
@@ -0,0 +1,257 @@
+odoo.define('mail/static/src/models/thread_viewer/thread_viewer.js', function (require) {
+'use strict';
+
+const { registerNewModel } = require('mail/static/src/model/model_core.js');
+const { attr, many2one, one2one } = require('mail/static/src/model/model_field.js');
+
+function factory(dependencies) {
+
+ class ThreadViewer extends dependencies['mail.model'] {
+
+ //----------------------------------------------------------------------
+ // Public
+ //----------------------------------------------------------------------
+
+ /**
+ * @param {string} scrollTop
+ */
+ saveThreadCacheScrollPositionsAsInitial(scrollTop) {
+ if (!this.threadCache) {
+ return;
+ }
+ this.update({
+ threadCacheInitialScrollPositions: Object.assign({}, this.threadCacheInitialScrollPositions, {
+ [this.threadCache.localId]: scrollTop,
+ }),
+ });
+ }
+
+ //----------------------------------------------------------------------
+ // Private
+ //----------------------------------------------------------------------
+
+ /**
+ * @private
+ * @returns {boolean}
+ */
+ _computeHasThreadView() {
+ if (this.chatter) {
+ return this.chatter.hasThreadView;
+ }
+ if (this.chatWindow) {
+ return this.chatWindow.hasThreadView;
+ }
+ if (this.discuss) {
+ return this.discuss.hasThreadView;
+ }
+ return this.hasThreadView;
+ }
+
+ /**
+ * @private
+ * @returns {string}
+ */
+ _computeStringifiedDomain() {
+ if (this.chatter) {
+ return '[]';
+ }
+ if (this.chatWindow) {
+ return '[]';
+ }
+ if (this.discuss) {
+ return this.discuss.stringifiedDomain;
+ }
+ return this.stringifiedDomain;
+ }
+
+ /**
+ * @private
+ * @returns {mail.thread|undefined}
+ */
+ _computeThread() {
+ if (this.chatter) {
+ if (!this.chatter.thread) {
+ return [['unlink']];
+ }
+ return [['link', this.chatter.thread]];
+ }
+ if (this.chatWindow) {
+ if (!this.chatWindow.thread) {
+ return [['unlink']];
+ }
+ return [['link', this.chatWindow.thread]];
+ }
+ if (this.discuss) {
+ if (!this.discuss.thread) {
+ return [['unlink']];
+ }
+ return [['link', this.discuss.thread]];
+ }
+ return [];
+ }
+
+ /**
+ * @private
+ * @returns {mail.thread_cache|undefined}
+ */
+ _computeThreadCache() {
+ if (!this.thread) {
+ return [['unlink']];
+ }
+ return [['link', this.thread.cache(this.stringifiedDomain)]];
+ }
+
+ /**
+ * @private
+ * @returns {mail.thread_viewer|undefined}
+ */
+ _computeThreadView() {
+ if (!this.hasThreadView) {
+ return [['unlink']];
+ }
+ if (this.threadView) {
+ return [];
+ }
+ return [['create']];
+ }
+
+ }
+
+ ThreadViewer.fields = {
+ /**
+ * States the `mail.chatter` managing `this`. This field is computed
+ * through the inverse relation and should be considered read-only.
+ */
+ chatter: one2one('mail.chatter', {
+ inverse: 'threadViewer',
+ }),
+ /**
+ * Serves as compute dependency.
+ */
+ chatterHasThreadView: attr({
+ related: 'chatter.hasThreadView',
+ }),
+ /**
+ * Serves as compute dependency.
+ */
+ chatterThread: many2one('mail.thread', {
+ related: 'chatter.thread',
+ }),
+ /**
+ * States the `mail.chat_window` managing `this`. This field is computed
+ * through the inverse relation and should be considered read-only.
+ */
+ chatWindow: one2one('mail.chat_window', {
+ inverse: 'threadViewer',
+ }),
+ /**
+ * Serves as compute dependency.
+ */
+ chatWindowHasThreadView: attr({
+ related: 'chatWindow.hasThreadView',
+ }),
+ /**
+ * Serves as compute dependency.
+ */
+ chatWindowThread: many2one('mail.thread', {
+ related: 'chatWindow.thread',
+ }),
+ /**
+ * States the `mail.discuss` managing `this`. This field is computed
+ * through the inverse relation and should be considered read-only.
+ */
+ discuss: one2one('mail.discuss', {
+ inverse: 'threadViewer',
+ }),
+ /**
+ * Serves as compute dependency.
+ */
+ discussHasThreadView: attr({
+ related: 'discuss.hasThreadView',
+ }),
+ /**
+ * Serves as compute dependency.
+ */
+ discussStringifiedDomain: attr({
+ related: 'discuss.stringifiedDomain',
+ }),
+ /**
+ * Serves as compute dependency.
+ */
+ discussThread: many2one('mail.thread', {
+ related: 'discuss.thread',
+ }),
+ /**
+ * Determines whether `this.thread` should be displayed.
+ */
+ hasThreadView: attr({
+ compute: '_computeHasThreadView',
+ default: false,
+ dependencies: [
+ 'chatterHasThreadView',
+ 'chatWindowHasThreadView',
+ 'discussHasThreadView',
+ ],
+ }),
+ /**
+ * Determines the domain to apply when fetching messages for `this.thread`.
+ */
+ stringifiedDomain: attr({
+ compute: '_computeStringifiedDomain',
+ default: '[]',
+ dependencies: [
+ 'discussStringifiedDomain',
+ ],
+ }),
+ /**
+ * Determines the `mail.thread` that should be displayed by `this`.
+ */
+ thread: many2one('mail.thread', {
+ compute: '_computeThread',
+ dependencies: [
+ 'chatterThread',
+ 'chatWindowThread',
+ 'discussThread',
+ ],
+ }),
+ /**
+ * States the `mail.thread_cache` that should be displayed by `this`.
+ */
+ threadCache: many2one('mail.thread_cache', {
+ compute: '_computeThreadCache',
+ dependencies: [
+ 'stringifiedDomain',
+ 'thread',
+ ],
+ }),
+ /**
+ * Determines the initial scroll positions of thread caches.
+ * Useful to restore scroll position on changing back to this
+ * thread cache. Note that this is only applied when opening
+ * the thread cache, because scroll position may change fast so
+ * save is already throttled.
+ */
+ threadCacheInitialScrollPositions: attr({
+ default: {},
+ }),
+ /**
+ * States the `mail.thread_view` currently displayed and managed by `this`.
+ */
+ threadView: one2one('mail.thread_view', {
+ compute: '_computeThreadView',
+ dependencies: [
+ 'hasThreadView',
+ ],
+ inverse: 'threadViewer',
+ isCausal: true,
+ }),
+ };
+
+ ThreadViewer.modelName = 'mail.thread_viewer';
+
+ return ThreadViewer;
+}
+
+registerNewModel('mail.thread_viewer', factory);
+
+});