[IMP] mail: compute messaging menu counter with dependencies

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) <aku@odoo.com>
This commit is contained in:
Sébastien Theys
2021-04-30 12:02:20 +00:00
parent 5a1905ee7d
commit 8f6603db45
10 changed files with 176 additions and 127 deletions
@@ -37,7 +37,7 @@ class NotificationAlert extends Component {
return (
windowNotification &&
windowNotification.permission !== "granted" &&
!this.env.messaging.isNotificationPermissionDefault()
!this.env.messaging.isNotificationPermissionDefault
);
}
@@ -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',
@@ -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."),
+3 -27
View File
@@ -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)) {
@@ -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,
@@ -223,8 +223,6 @@ function factory(dependencies) {
}
}));
this.messaging.notificationGroupManager.computeGroups();
// manually force recompute of counter (after computing the groups)
this.messaging.messagingMenu.update();
}
/**
@@ -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';
@@ -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();
}
/**
@@ -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 {};
}
}
/**
@@ -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',