diff options
| author | Alexandre Kühn <aku@odoo.com> | 2020-06-18 16:13:35 +0000 |
|---|---|---|
| committer | Alexandre Kühn <aku@odoo.com> | 2020-06-22 14:53:30 +0000 |
| commit | 40793fde23a64a651859f227864a7c0c914837d5 (patch) | |
| tree | eb05f0c2e860fe9c1cb17eca4eee2019d294f45b /addons/mail/static/src/models/thread_cache | |
| parent | 5d7901a53b743e84846b19343e5ebf57b65df403 (diff) | |
[FIX] mail: consistent relations between threads, caches and messages
task-2171881
closes odoo/odoo#53440
X-original-commit: 80d13bb828ee75a50d8cea29046a8eb49c71948a
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.js | 169 |
1 files changed, 151 insertions, 18 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 edd991c693c..b942e0a6e08 100644 --- a/addons/mail/static/src/models/thread_cache/thread_cache.js +++ b/addons/mail/static/src/models/thread_cache/thread_cache.js @@ -47,7 +47,7 @@ function factory(dependencies) { } this.update({ isLoadingMore: true }); const minMessageId = Math.min( - ...this.messages.map(message => message.id) + ...this.fetchedMessages.map(message => message.id) ); domain = [['id', '<', minMessageId]].concat(domain); const messagesData = await this.async(() => this.env.services.rpc({ @@ -70,7 +70,7 @@ function factory(dependencies) { await this.async(() => this.loadMessages()); return; } - const messageIds = this.messages.map(message => message.id); + const messageIds = this.fetchedMessages.map(message => message.id); const searchDomain = JSON.parse(this.stringifiedDomain); let domain = searchDomain.length ? searchDomain : []; domain = this._extendMessageDomain(domain); @@ -96,10 +96,45 @@ function factory(dependencies) { /** * @private + * @returns {mail.message[]} + */ + _computeFetchedMessages() { + if (!this.thread) { + return [['unlink-all']]; + } + const toUnlinkMessages = []; + for (const message of this.fetchedMessages) { + if (!this.thread.messages.includes(message)) { + toUnlinkMessages.push(message); + } + } + return [['unlink', toUnlinkMessages]]; + } + + /** + * @private + * @returns {mail.message|undefined} + */ + _computeLastFetchedMessage() { + const { + length: l, + [l - 1]: lastFetchedMessage, + } = this.orderedFetchedMessages; + if (!lastFetchedMessage) { + return [['unlink-all']]; + } + return [['replace', lastFetchedMessage]]; + } + + /** + * @private * @returns {mail.message|undefined} */ _computeLastMessage() { - const { length: l, [l - 1]: lastMessage } = this.orderedMessages; + const { + length: l, + [l - 1]: lastMessage, + } = this.orderedMessages; if (!lastMessage) { return [['unlink-all']]; } @@ -110,6 +145,41 @@ function factory(dependencies) { * @private * @returns {mail.message[]} */ + _computeMessages() { + if (!this.thread) { + return [['unlink-all']]; + } + 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 + let newerMessages; + if (!this.lastFetchedMessage) { + newerMessages = this.thread.messages; + } else { + newerMessages = this.thread.messages.filter(message => + message.id > this.lastFetchedMessage.id + ); + } + messages = messages.concat(newerMessages); + return [['replace', messages]]; + } + + /** + * @private + * @returns {mail.message[]} + */ + _computeOrderedFetchedMessages() { + return [['replace', this.fetchedMessages.sort((m1, m2) => m1.id < m2.id ? -1 : 1)]]; + } + + /** + * @private + * @returns {mail.message[]} + */ _computeOrderedMessages() { return [['replace', this.messages.sort((m1, m2) => m1.id < m2.id ? -1 : 1)]]; } @@ -180,25 +250,32 @@ function factory(dependencies) { * @param {Object[]} messageData */ _handleMessagesLoaded(messagesData) { + const messages = messagesData.map(data => + this.env.models['mail.message'].insert( + this.env.models['mail.message'].convertData(data) + ) + ); + + if (!this.thread) { + 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, - messages: [['insert', messagesData.map(data => - this.env.models['mail.message'].convertData(data)) - ]], - }); - if (!this.thread) { - 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 }]], - }; - })]], }); for (const viewer of this.thread.viewers) { viewer.handleThreadCacheLoaded(this); @@ -211,6 +288,27 @@ function factory(dependencies) { checkedMessages: many2many('mail.message', { inverse: 'checkedThreadCaches', }), + /** + * List of messages that have been fetched by this cache. + * + * This DOES NOT necessarily includes all messages linked to this thread + * cache (@see messages field for that): it just contains list + * of successive messages that have been explicitly fetched by this + * cache. For all non-main caches, this corresponds to all messages. + * For the main cache, however, messages received from longpolling + * should be displayed on main cache but they have not been explicitly + * fetched by cache, so they ARE NOT in this list (at least, not until a + * fetch on this thread cache contains this message). + * + * The distinction between messages and fetched messages is important + * to manage "holes" in message list, while still allowing to display + * new messages on main cache of thread in real-time. + */ + fetchedMessages: many2many('mail.message', { + // adjust with messages unlinked from thread + compute: '_computeFetchedMessages', + dependencies: ['threadMessages'], + }), isAllHistoryLoaded: attr({ default: false, }), @@ -223,6 +321,17 @@ function factory(dependencies) { isLoadingMore: 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 + * cache (@see lastMessage field for that). @see fetchedMessages field + * for a deeper explanation about "fetched" messages. + */ + lastFetchedMessage: many2one('mail.message', { + compute: '_computeLastFetchedMessage', + dependencies: ['orderedFetchedMessages'], + }), lastMessage: many2one('mail.message', { compute: '_computeLastMessage', dependencies: ['orderedMessages'], @@ -230,9 +339,30 @@ function factory(dependencies) { messagesCheckboxes: attr({ related: 'messages.hasCheckbox', }), + /** + * List of messages linked to this cache. + */ messages: many2many('mail.message', { - inverse: 'threadCaches', + compute: '_computeMessages', + dependencies: [ + 'fetchedMessages', + 'threadMessages', + ], + }), + /** + * Ordered list of messages that have been fetched by this cache. + * + * This DOES NOT necessarily includes all messages linked to this thread + * cache (@see orderedMessages field for that). @see fetchedMessages + * field for deeper explanation about "fetched" messages. + */ + orderedFetchedMessages: many2many('mail.message', { + compute: '_computeOrderedFetchedMessages', + dependencies: ['fetchedMessages'], }), + /** + * Ordered list of messages linked to this cache. + */ orderedMessages: many2many('mail.message', { compute: '_computeOrderedMessages', dependencies: ['messages'], @@ -243,6 +373,9 @@ function factory(dependencies) { thread: many2one('mail.thread', { inverse: 'caches', }), + threadMessages: many2many('mail.message', { + related: 'thread.messages', + }), uncheckedMessages: many2many('mail.message', { compute: '_computeUncheckedMessages', dependencies: [ |
