From 8f6603db4533dedbfd2e9eceb1cdb7c2356c243a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Theys?= Date: Fri, 30 Apr 2021 11:55:33 +0000 Subject: [PATCH] [IMP] mail: compute messaging menu counter with dependencies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It was the last remaining usage of the deprecated `_updateAfter` which is therefore removed too. Part of task-2258617 closes odoo/odoo#70170 Signed-off-by: Alexandre Kühn (aku) --- .../notification_alert/notification_alert.js | 2 +- .../notification_list/notification_list.js | 2 +- .../notification_request.js | 3 +- addons/mail/static/src/model/model_manager.js | 30 +---- .../static/src/models/messaging/messaging.js | 94 +++++++++++++-- .../messaging_initializer.js | 2 - .../models/messaging_menu/messaging_menu.js | 112 +++++++++++++----- .../messaging_notification_handler.js | 18 --- addons/mail/static/src/models/model/model.js | 32 ----- .../mail/static/src/models/thread/thread.js | 8 +- 10 files changed, 176 insertions(+), 127 deletions(-) diff --git a/addons/mail/static/src/components/notification_alert/notification_alert.js b/addons/mail/static/src/components/notification_alert/notification_alert.js index 523563a56ca..a299a213161 100644 --- a/addons/mail/static/src/components/notification_alert/notification_alert.js +++ b/addons/mail/static/src/components/notification_alert/notification_alert.js @@ -37,7 +37,7 @@ class NotificationAlert extends Component { return ( windowNotification && windowNotification.permission !== "granted" && - !this.env.messaging.isNotificationPermissionDefault() + !this.env.messaging.isNotificationPermissionDefault ); } diff --git a/addons/mail/static/src/components/notification_list/notification_list.js b/addons/mail/static/src/components/notification_list/notification_list.js index 6575b10fdcc..ff423175240 100644 --- a/addons/mail/static/src/components/notification_list/notification_list.js +++ b/addons/mail/static/src/components/notification_list/notification_list.js @@ -143,7 +143,7 @@ class NotificationList extends Component { }).concat(notifications); } // native notification request - if (props.filter === 'all' && this.env.messaging.isNotificationPermissionDefault()) { + if (props.filter === 'all' && this.env.messaging.isNotificationPermissionDefault) { notifications.unshift({ type: 'odoobotRequest', uniqueId: 'odoobotRequest', diff --git a/addons/mail/static/src/components/notification_request/notification_request.js b/addons/mail/static/src/components/notification_request/notification_request.js index 33bdd51fd52..cb80f1905ae 100644 --- a/addons/mail/static/src/components/notification_request/notification_request.js +++ b/addons/mail/static/src/components/notification_request/notification_request.js @@ -52,8 +52,7 @@ class NotificationRequest extends Component { * @param {string} value */ _handleResponseNotificationPermission(value) { - // manually force recompute because the permission is not in the store - this.env.messaging.messagingMenu.update(); + this.env.messaging.refreshIsNotificationPermissionDefault(); if (value !== 'granted') { this.env.services['bus_service'].sendNotification({ message: this.env._t("Odoo will not have the permission to send native notifications on this device."), diff --git a/addons/mail/static/src/model/model_manager.js b/addons/mail/static/src/model/model_manager.js index 66d9a83c1f2..29df7edd013 100644 --- a/addons/mail/static/src/model/model_manager.js +++ b/addons/mail/static/src/model/model_manager.js @@ -70,12 +70,6 @@ class ModelManager { * O(1) reads/writes. */ this._toComputeFields = new Map(); - /** - * Map of "update after" on records that have been registered. - * These are processed after any explicit update and computed/related - * fields. - */ - this._toUpdateAfters = new Map(); } /** @@ -583,6 +577,7 @@ class ModelManager { }); // Ensure X2many relations are Set initially (other fields can stay undefined). for (const field of Model.__fieldList) { + record.__values[field.fieldName] = undefined; if (field.fieldType === 'relation') { if (['one2many', 'many2many'].includes(field.relationType)) { record.__values[field.fieldName] = new Set(); @@ -644,10 +639,9 @@ class ModelManager { this._hasAnyChangeDuringCycle = true; // TODO ideally deleting the record should be done at the top of the // method, and it shouldn't be needed to manually remove - // _toComputeFields and _toUpdateAfters, but it is not possible until - // related are also properly unlinked during `set` + // _toComputeFields, but it is not possible until related are also + // properly unlinked during `set`. this._toComputeFields.delete(record); - this._toUpdateAfters.delete(record); delete Model.__records[record.localId]; } @@ -685,19 +679,6 @@ class ModelManager { } } - // Execution of _updateAfter - while (this._toUpdateAfters.size > 0) { - for (const [record, previous] of this._toUpdateAfters) { - // delete at every step to avoid recursion, indeed _updateAfter - // might trigger an update cycle itself - this._toUpdateAfters.delete(record); - if (!record.exists()) { - throw Error(`Cannot _updateAfter for already deleted record ${record.localId}.`); - } - record._updateAfter(previous); - } - } - // Execution of _created while (this._createdRecords.size > 0) { for (const record of this._createdRecords) { @@ -1081,11 +1062,6 @@ class ModelManager { throw Error(`Cannot update already deleted record ${record.localId}.`); } const { allowWriteReadonly = false } = options; - if (!this._toUpdateAfters.has(record)) { - // queue updateAfter before calling field.set to ensure previous - // contains the value at the start of update cycle - this._toUpdateAfters.set(record, record._updateBefore()); - } const Model = record.constructor; let hasChanged = false; for (const fieldName of Object.keys(data)) { diff --git a/addons/mail/static/src/models/messaging/messaging.js b/addons/mail/static/src/models/messaging/messaging.js index 20c8eef591f..d748ab375a1 100644 --- a/addons/mail/static/src/models/messaging/messaging.js +++ b/addons/mail/static/src/models/messaging/messaging.js @@ -2,7 +2,7 @@ import { registerNewModel } from '@mail/model/model_core'; import { attr, many2many, many2one, one2many, one2one } from '@mail/model/model_field'; -import { create } from '@mail/model/model_field_command'; +import { create, replace } from '@mail/model/model_field_command'; function factory(dependencies) { @@ -33,16 +33,6 @@ function factory(dependencies) { // Public //---------------------------------------------------------------------- - /** - * @returns {boolean} - */ - isNotificationPermissionDefault() { - const windowNotification = this.env.browser.Notification; - return windowNotification - ? windowNotification.permission === 'default' - : false; - } - /** * Open the form view of the record with provided id and model. * Gets the chat with the provided person and returns it. @@ -143,10 +133,43 @@ function factory(dependencies) { return this.env.messaging.openDocument({ id, model }); } + /** + * Refreshes the value of `isNotificationPermissionDefault`. + * + * Must be called in flux-specific way because the browser does not + * provide an API to detect when this value changes. + */ + refreshIsNotificationPermissionDefault() { + this.update({ isNotificationPermissionDefault: this._computeIsNotificationPermissionDefault() }); + } + //---------------------------------------------------------------------- // Private //---------------------------------------------------------------------- + /** + * @private + */ + _computeAllChannels() { + return replace(this.allThreads.filter(thread => thread.model === 'mail.channel')); + } + + /** + * @private + */ + _computeAllPinnedChannels() { + return replace(this.allChannels.filter(channel => channel.isPinned)); + } + + /** + * @private + * @returns {boolean} + */ + _computeIsNotificationPermissionDefault() { + const browserNotification = this.env.browser.Notification; + return browserNotification ? browserNotification.permission === 'default' : false; + } + /** * @private */ @@ -160,6 +183,47 @@ function factory(dependencies) { } Messaging.fields = { + /** + * States all known channels. + */ + allChannels: one2many('mail.thread', { + compute: '_computeAllChannels', + dependencies: [ + 'allThreads', + 'allThreadsModel', + ], + readonly: true, + }), + /** + * Serves as compute dependency. + */ + allChannelsIsPinned: attr({ + related: 'allChannels.isPinned', + }), + /** + * States all known pinned channels. + */ + allPinnedChannels: one2many('mail.thread', { + compute: '_computeAllPinnedChannels', + dependencies: [ + 'allChannels', + 'allChannelsIsPinned', + ], + readonly: true, + }), + /** + * States all known threads. + */ + allThreads: one2many('mail.thread', { + inverse: 'messaging', + readonly: true, + }), + /** + * Serves as compute dependency. + */ + allThreadsModel: attr({ + related: 'allThreads.model', + }), cannedResponses: one2many('mail.canned_response'), chatWindowManager: one2one('mail.chat_window_manager', { default: create(), @@ -203,6 +267,14 @@ function factory(dependencies) { isInitialized: attr({ default: false, }), + /** + * States whether browser Notification Permission is currently in its + * 'default' state. This means it is allowed to make a request to the + * user to enable notifications. + */ + isNotificationPermissionDefault: attr({ + compute: '_computeIsNotificationPermissionDefault', + }), locale: one2one('mail.locale', { default: create(), isCausal: true, diff --git a/addons/mail/static/src/models/messaging_initializer/messaging_initializer.js b/addons/mail/static/src/models/messaging_initializer/messaging_initializer.js index 852bd1dedac..95b599895c0 100644 --- a/addons/mail/static/src/models/messaging_initializer/messaging_initializer.js +++ b/addons/mail/static/src/models/messaging_initializer/messaging_initializer.js @@ -223,8 +223,6 @@ function factory(dependencies) { } })); this.messaging.notificationGroupManager.computeGroups(); - // manually force recompute of counter (after computing the groups) - this.messaging.messagingMenu.update(); } /** 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 c6218abbf6f..6051668eb2f 100644 --- a/addons/mail/static/src/models/messaging_menu/messaging_menu.js +++ b/addons/mail/static/src/models/messaging_menu/messaging_menu.js @@ -1,7 +1,7 @@ /** @odoo-module **/ import { registerNewModel } from '@mail/model/model_core'; -import { attr, one2one } from '@mail/model/model_field'; +import { attr, one2many, one2one } from '@mail/model/model_field'; function factory(dependencies) { @@ -31,6 +31,7 @@ function factory(dependencies) { */ toggleOpen() { this.update({ isOpen: !this.isOpen }); + this.env.messaging.refreshIsNotificationPermissionDefault(); } //---------------------------------------------------------------------- @@ -56,45 +57,39 @@ function factory(dependencies) { * @private * @returns {integer} */ - _updateCounter() { + _computeCounter() { if (!this.env.messaging) { return 0; } - const inboxMailbox = this.env.messaging.inbox; - const unreadChannels = this.env.models['mail.thread'].all(thread => - thread.localMessageUnreadCounter > 0 && - thread.model === 'mail.channel' && - thread.isPinned - ); - let counter = unreadChannels.length; - if (inboxMailbox) { - counter += inboxMailbox.counter; - } - if (this.messaging.notificationGroupManager) { - counter += this.messaging.notificationGroupManager.groups.reduce( + const inboxCounter = this.env.messaging.inbox ? this.env.messaging.inbox.counter : 0; + const unreadChannelsCounter = this.env.messaging.allPinnedChannels.filter( + channel => channel.localMessageUnreadCounter > 0 + ).length; + const notificationGroupsCounter = this.messaging.notificationGroupManager + ? this.messaging.notificationGroupManager.groups.reduce( (total, group) => total + group.notifications.length, 0 - ); - } - if (this.messaging.isNotificationPermissionDefault()) { - counter++; - } - return counter; - } - - /** - * @override - */ - _updateAfter(previous) { - const counter = this._updateCounter(); - if (this.counter !== counter) { - this.update({ counter }); - } + ) + : 0; + const notificationPemissionCounter = this.messaging.isNotificationPermissionDefault ? 1 : 0; + return inboxCounter + unreadChannelsCounter + notificationGroupsCounter + notificationPemissionCounter; } } MessagingMenu.fields = { + /** + * Serves as compute dependency. + */ + allPinnedChannels: one2many('mail.thread', { + related: 'messaging.allPinnedChannels', + }), + /** + * Serves as compute dependency. + */ + allPinnedChannelsLocalMessageUnreadCounter: attr({ + related: 'allPinnedChannels.localMessageUnreadCounter', + }), /** * Tab selected in the messaging menu. * Either 'all', 'chat' or 'channel'. @@ -102,8 +97,24 @@ function factory(dependencies) { activeTabId: attr({ default: 'all', }), + /** + * States the counter of this messaging menu. The counter is an integer + * value to give to the current user an estimate of how many things + * (unread threads, notifications, ...) are yet to be processed by him. + */ counter: attr({ - default: 0, + compute: '_computeCounter', + dependencies: [ + 'allPinnedChannels', + 'allPinnedChannelsLocalMessageUnreadCounter', + 'messaging', + 'messagingInbox', + 'messagingInboxCounter', + 'messagingIsNotificationPermissionDefault', + 'messagingNotificationGroupManager', + 'messagingNotificationGroupManagerGroups', + 'messagingNotificationGroupManagerGroupsNotifications', + ], }), /** * Dummy field to automatically load messages of inbox when messaging @@ -132,15 +143,54 @@ function factory(dependencies) { isOpen: attr({ default: false, }), + /** + * Serves as compute dependency. + */ messaging: one2one('mail.messaging', { inverse: 'messagingMenu', }), + /** + * Serves as compute dependency. + */ messagingInbox: one2one('mail.thread', { related: 'messaging.inbox', }), + /** + * Serves as compute dependency. + */ + messagingInboxCounter: attr({ + related: 'messagingInbox.counter', + }), + /** + * Serves as compute dependency. + */ + messagingIsNotificationPermissionDefault: attr({ + related: 'messaging.isNotificationPermissionDefault', + }), + /** + * Serves as compute dependency. + */ messagingInboxMainCache: one2one('mail.thread_cache', { related: 'messagingInbox.mainCache', }), + /** + * Serves as compute dependency. + */ + messagingNotificationGroupManager: one2one('mail.notification_group_manager', { + related: 'messaging.notificationGroupManager', + }), + /** + * Serves as compute dependency. + */ + messagingNotificationGroupManagerGroups: one2many('mail.notification_group', { + related: 'messagingNotificationGroupManager.groups' + }), + /** + * Serves as compute dependency. + */ + messagingNotificationGroupManagerGroupsNotifications: one2many('mail.notification', { + related: 'messagingNotificationGroupManagerGroups.notifications' + }), }; 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 978566df895..23b9ad5dc62 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 @@ -271,8 +271,6 @@ function factory(dependencies) { if (!wasChannelExisting) { return; } - // manually force recompute of counter - this.messaging.messagingMenu.update(); } /** @@ -325,8 +323,6 @@ function factory(dependencies) { // FIXME force the computing of message values (cf task-2261221) this.env.models['mail.message_seen_indicator'].recomputeSeenValues(channel); } - // manually force recompute of counter - this.messaging.messagingMenu.update(); } /** @@ -382,8 +378,6 @@ function factory(dependencies) { if (originThread && message.isNeedaction) { originThread.update({ message_needaction_counter: increment() }); } - // manually force recompute of counter - this.messaging.messagingMenu.update(); } /** @@ -487,8 +481,6 @@ function factory(dependencies) { }); } // a new thread with unread messages could have been added - // manually force recompute of counter - this.messaging.messagingMenu.update(); } /** @@ -512,8 +504,6 @@ function factory(dependencies) { } // deleting message might have deleted notifications, force recompute this.messaging.notificationGroupManager.computeGroups(); - // manually force recompute of counter (after computing the groups) - this.messaging.messagingMenu.update(); } /** @@ -532,8 +522,6 @@ function factory(dependencies) { } } this.messaging.notificationGroupManager.computeGroups(); - // manually force recompute of counter (after computing the groups) - this.messaging.messagingMenu.update(); } /** @@ -577,8 +565,6 @@ function factory(dependencies) { // messages on the server. inbox.mainCache.update({ hasToLoadMessages: true }); } - // manually force recompute of counter - this.messaging.messagingMenu.update(); } /** @@ -594,8 +580,6 @@ function factory(dependencies) { if (moderationMailbox) { moderationMailbox.update({ counter: increment() }); } - // manually force recompute of counter - this.messaging.messagingMenu.update(); } /** @@ -641,8 +625,6 @@ function factory(dependencies) { isTransient: true, })); this._notifyThreadViewsMessageReceived(message); - // manually force recompute of counter - this.messaging.messagingMenu.update(); } /** diff --git a/addons/mail/static/src/models/model/model.js b/addons/mail/static/src/models/model/model.js index ce8c2e550a0..d8f2bc31b9d 100644 --- a/addons/mail/static/src/models/model/model.js +++ b/addons/mail/static/src/models/model/model.js @@ -231,38 +231,6 @@ function factory() { return _.uniqueId(`${this.modelName}_`); } - /** - * This function is called when this record has been explicitly updated - * with `.update()` or static method `.create()`, at the end of an - * record update cycle. This is a backward-compatible behaviour that - * is deprecated: you should use computed fields instead. - * - * @deprecated - * @abstract - * @private - * @param {Object} previous contains data that have been stored by - * `_updateBefore()`. Useful to make extra update decisions based on - * previous data. - */ - _updateAfter(previous) {} - - /** - * This function is called just at the beginning of an explicit update - * on this function, with `.update()` or static method `.create()`. This - * is useful to remember previous values of fields in `_updateAfter`. - * This is a backward-compatible behaviour that is deprecated: you - * should use computed fields instead. - * - * @deprecated - * @abstract - * @private - * @param {Object} data - * @returns {Object} - */ - _updateBefore() { - return {}; - } - } /** diff --git a/addons/mail/static/src/models/thread/thread.js b/addons/mail/static/src/models/thread/thread.js index 5c65a32e22b..839213a1569 100644 --- a/addons/mail/static/src/models/thread/thread.js +++ b/addons/mail/static/src/models/thread/thread.js @@ -411,8 +411,6 @@ function factory(dependencies) { const channels = this.env.models['mail.thread'].insert( channelInfos.map(channelInfo => this.env.models['mail.thread'].convertData(channelInfo)) ); - // manually force recompute of counter - this.env.messaging.messagingMenu.update(); return channels; } @@ -2015,8 +2013,14 @@ function factory(dependencies) { inverse: 'thread', isCausal: true, }), + /** + * States the current messaging instance. Not useful by itself because + * messaging is already in the env. But this allows the inverse relation + * to contain all known threads. It also serves as compute dependency. + */ messaging: many2one('mail.messaging', { compute: '_computeMessaging', + inverse: 'allThreads', }), messagingCurrentPartner: many2one('mail.partner', { related: 'messaging.currentPartner',