From 5471bd4cb84fb7d673e26afa603b1ef5ec14fbdf Mon Sep 17 00:00:00 2001 From: tsm-odoo Date: Thu, 15 Sep 2022 11:53:56 +0000 Subject: [PATCH] [IMP] mail: remove im status route MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit *: bus, hr_holidays. The `/bus/im_status` route polls the server every minute in order for the user im_status to be up to date. This commit removes this poll by sending the im_status on the bus when updating the current user presence. Moreover, before [1], the user bus presence was updated on each poll. When the user didn't poll for 50 seconds, we assumed the user was disconnected. Since [1], the bus presence is updated each 30 seconds by the `im_status` service. This is too frequent: there is no need to update the user presence so often. In order not to overhelm the server with unnecessary requests, the update presence interval as well as the delay to be considered disconnected have been updated: the former from 30 to 60 seconds, the later from 55 to 65 seconds (assuming that a user that has missed an update presence tick is disconnected). [1]: odoo/odoo@a5623d2 closes odoo/odoo#100249 Signed-off-by: Sébastien Theys (seb) --- addons/bus/__manifest__.py | 2 +- addons/bus/controllers/main.py | 6 -- addons/bus/controllers/websocket.py | 4 +- addons/bus/models/bus_presence.py | 4 +- addons/bus/models/ir_websocket.py | 13 +++- addons/bus/static/src/im_status_service.js | 72 ++++++++++++------- .../bus/static/tests/helpers/mock_server.js | 22 +++++- .../mock_server/models/ir_websocket.js | 28 ++++++++ addons/bus/websocket.py | 2 +- .../persona_im_status_icon_tests.js | 14 ++-- addons/hr_presence/models/ir_websocket.py | 4 +- addons/mail/controllers/bus.py | 7 -- addons/mail/models/ir_websocket.py | 10 ++- addons/mail/static/src/models/messaging.js | 55 +++++++------- .../src/models/messaging_initializer.js | 2 +- .../models/messaging_notification_handler.js | 16 +++++ addons/mail/static/src/models/persona.js | 5 ++ addons/mail/static/src/models/throttle.js | 7 ++ addons/mail/static/src/models/timer.js | 11 --- .../mail/static/tests/helpers/mock_server.js | 6 -- .../static/tests/helpers/webclient_setup.js | 2 + .../persona_im_status_icon_tests.js | 24 ++++--- 22 files changed, 201 insertions(+), 115 deletions(-) create mode 100644 addons/bus/static/tests/helpers/mock_server/models/ir_websocket.js diff --git a/addons/bus/__manifest__.py b/addons/bus/__manifest__.py index 31676236bb0..71814b33828 100644 --- a/addons/bus/__manifest__.py +++ b/addons/bus/__manifest__.py @@ -26,7 +26,7 @@ 'bus/static/tests/**/*.js', ], 'web.qunit_mobile_suite_tests': [ - 'bus/static/tests/helpers/*.js', + 'bus/static/tests/helpers/**/*.js', ], 'bus.websocket_worker_assets': [ 'web/static/src/legacy/js/promise_extension.js', diff --git a/addons/bus/controllers/main.py b/addons/bus/controllers/main.py index 209ee685253..230acaa6fbc 100644 --- a/addons/bus/controllers/main.py +++ b/addons/bus/controllers/main.py @@ -6,12 +6,6 @@ from odoo.http import Controller, request, route class BusController(Controller): - @route('/bus/im_status', type="json", auth="user") - def im_status(self, partner_ids): - return { - 'partners': request.env['res.partner'].with_context(active_test=False).search([('id', 'in', partner_ids)]).read(['im_status']) - } - @route('/bus/get_model_definitions', methods=['POST'], type='http', auth='user') def get_model_definitions(self, model_names_to_fetch, **kwargs): return request.make_response(json.dumps( diff --git a/addons/bus/controllers/websocket.py b/addons/bus/controllers/websocket.py index 5384c973915..f0121df7864 100644 --- a/addons/bus/controllers/websocket.py +++ b/addons/bus/controllers/websocket.py @@ -43,8 +43,8 @@ class WebsocketController(Controller): return {'channels': channels, 'notifications': notifications} @route('/websocket/update_bus_presence', type='http', auth='public', cors='*') - def update_bus_presence(self, inactivity_period): - request.env['ir.websocket']._update_bus_presence(int(inactivity_period)) + def update_bus_presence(self, inactivity_period, im_status_ids_by_model): + request.env['ir.websocket']._update_bus_presence(int(inactivity_period), im_status_ids_by_model) @route('/bus/websocket_worker_bundle', type='http', auth='public', cors='*') def get_websocket_worker_bundle(self): diff --git a/addons/bus/models/bus_presence.py b/addons/bus/models/bus_presence.py index f6d5513c6ce..72e1983fcb7 100644 --- a/addons/bus/models/bus_presence.py +++ b/addons/bus/models/bus_presence.py @@ -6,11 +6,11 @@ from psycopg2 import OperationalError from odoo import api, fields, models from odoo import tools -from odoo.addons.bus.models.bus import TIMEOUT from odoo.service.model import PG_CONCURRENCY_ERRORS_TO_RETRY from odoo.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT -DISCONNECTION_TIMER = TIMEOUT + 5 +UPDATE_PRESENCE_DELAY = 60 +DISCONNECTION_TIMER = UPDATE_PRESENCE_DELAY + 5 AWAY_TIMER = 1800 # 30 minutes diff --git a/addons/bus/models/ir_websocket.py b/addons/bus/models/ir_websocket.py index b26df830625..567abe1cd98 100644 --- a/addons/bus/models/ir_websocket.py +++ b/addons/bus/models/ir_websocket.py @@ -8,6 +8,14 @@ class IrWebsocket(models.AbstractModel): _name = 'ir.websocket' _description = 'websocket message handling' + def _get_im_status(self, im_status_ids_by_model): + im_status = {} + if 'res.partner' in im_status_ids_by_model: + im_status['partners'] = self.env['res.partner'].with_context(active_test=False).search( + [('id', 'in', im_status_ids_by_model['res.partner'])]).read(['im_status'] + ) + return im_status + def _build_bus_channel_list(self, channels): """ Return the list of channels to subscribe to. Override this @@ -25,13 +33,16 @@ class IrWebsocket(models.AbstractModel): channels = set(self._build_bus_channel_list(data['channels'])) dispatch.subscribe(channels, data['last'], self.env.registry.db_name, wsrequest.ws) - def _update_bus_presence(self, inactivity_period): + def _update_bus_presence(self, inactivity_period, im_status_ids_by_model): if self.env.uid: self.env['bus.presence'].update( inactivity_period, identity_field='user_id', identity_value=self.env.uid ) + im_status_notification = self._get_im_status(im_status_ids_by_model) + if im_status_ids_by_model: + self.env['bus.bus']._sendone(self.env.user.partner_id, 'bus/im_status', im_status_notification) @classmethod def _authenticate(cls): diff --git a/addons/bus/static/src/im_status_service.js b/addons/bus/static/src/im_status_service.js index 6eb44d23822..23694433a77 100644 --- a/addons/bus/static/src/im_status_service.js +++ b/addons/bus/static/src/im_status_service.js @@ -1,46 +1,68 @@ /** @odoo-module **/ +import { browser } from '@web/core/browser/browser'; import { registry } from '@web/core/registry'; +export const UPDATE_BUS_PRESENCE_DELAY = 60000; /** * This service updates periodically the user presence in order for the * im_status to be up to date. + * + * In order to receive bus notifications related to im_status, one must + * register model/ids to monitor to this service. */ export const imStatusService = { dependencies: ['bus_service', 'multi_tab', 'presence'], start(env, { bus_service, multi_tab, presence }) { - const UPDATE_BUS_PRESENCE_DELAY = 30000; - let updateBusPresenceInterval; + const imStatusModelToIds = {}; + let updateBusPresenceTimeout; function updateBusPresence() { - const now = new Date().getTime(); - bus_service.send("update_presence", now - presence.getLastPresence()); - } - - function stopUpdatingBusPresence() { - clearInterval(updateBusPresenceInterval); - updateBusPresenceInterval = null; - } - - function startUpdatingBusPresence() { - if (updateBusPresenceInterval || !multi_tab.isOnMainTab()) { + clearTimeout(updateBusPresenceTimeout); + if (!multi_tab.isOnMainTab()) { return; } - updateBusPresence(); - updateBusPresenceInterval = setInterval( - updateBusPresence, - UPDATE_BUS_PRESENCE_DELAY, - ); + const now = new Date().getTime(); + bus_service.send("update_presence", { + inactivity_period: now - presence.getLastPresence(), + im_status_ids_by_model: { ...imStatusModelToIds }, + }); + updateBusPresenceTimeout = browser.setTimeout(updateBusPresence, UPDATE_BUS_PRESENCE_DELAY); } + // wait for im_status model/ids to be registered before starting. + browser.setTimeout(updateBusPresence, 250); + multi_tab.bus.addEventListener('become_main_tab', updateBusPresence); + bus_service.addEventListener('reconnect', updateBusPresence); + multi_tab.bus.addEventListener('no_longer_main_tab', () => clearTimeout(updateBusPresenceTimeout)); + bus_service.addEventListener('disconnect', () => clearTimeout(updateBusPresenceTimeout)); - if (multi_tab.isOnMainTab()) { - startUpdatingBusPresence(); - } - multi_tab.bus.addEventListener('become_main_tab', startUpdatingBusPresence); - bus_service.addEventListener('reconnect', startUpdatingBusPresence); - multi_tab.bus.addEventListener('no_longer_main_tab', stopUpdatingBusPresence); - bus_service.addEventListener('disconnect', stopUpdatingBusPresence); + return { + /** + * Register model/ids whose im_status should be monitored. + * Notification related to the im_status are then sent + * through the bus. Overwrite registration if already + * present. + * + * @param {string} model model related to the given ids. + * @param {Number[]} ids ids whose im_status should be + * monitored. + */ + registerToImStatus(model, ids) { + if (!ids.length) { + return this.unregisterFromImStatus(model); + } + imStatusModelToIds[model] = ids; + }, + /** + * Unregister model from im_status notifications. + * + * @param {string} model model to unregister. + */ + unregisterFromImStatus(model) { + delete imStatusModelToIds[model]; + }, + }; }, }; diff --git a/addons/bus/static/tests/helpers/mock_server.js b/addons/bus/static/tests/helpers/mock_server.js index 43340b552b9..e3983320400 100644 --- a/addons/bus/static/tests/helpers/mock_server.js +++ b/addons/bus/static/tests/helpers/mock_server.js @@ -12,7 +12,13 @@ patch(MockServer.prototype, 'bus', { init() { this._super(...arguments); Object.assign(this, TEST_USER_IDS); - this.websocketWorker = patchWebsocketWorkerWithCleanup(); + const self = this; + this.websocketWorker = patchWebsocketWorkerWithCleanup({ + _sendToServer(message) { + self._performWebsocketRequest(message); + this._super(message); + }, + }); this.pendingLongpollingPromise = null; this.notificationsToBeResolved = []; this.lastBusNotificationId = 0; @@ -22,6 +28,19 @@ patch(MockServer.prototype, 'bus', { // Private //-------------------------------------------------------------------------- + /** + * @param {Object} message Message sent through the websocket to the + * server. + * @param {string} [message.event_name] + * @param {any} [message.data] + */ + _performWebsocketRequest({ event_name, data }) { + if (event_name === 'update_presence') { + const { inactivity_period, im_status_ids_by_model } = data; + this._mockIrWebsocket__updatePresence(inactivity_period, im_status_ids_by_model); + } + }, + /** * @override */ @@ -71,7 +90,6 @@ patch(MockServer.prototype, 'bus', { this.websocketWorker.broadcast('notification', values); }, - /** * Simulate the lost of the connection by simulating a closeEvent on * the worker websocket. diff --git a/addons/bus/static/tests/helpers/mock_server/models/ir_websocket.js b/addons/bus/static/tests/helpers/mock_server/models/ir_websocket.js new file mode 100644 index 00000000000..147f0c8e3b1 --- /dev/null +++ b/addons/bus/static/tests/helpers/mock_server/models/ir_websocket.js @@ -0,0 +1,28 @@ +/** @odoo-module **/ + +import { patch } from "@web/core/utils/patch"; +import { MockServer } from "@web/../tests/helpers/mock_server"; + +patch(MockServer.prototype, 'bus/models/ir_websocket', { + /** + * Simulates `_update_presence` on `ir.websocket`. + * + * @param inactivityPeriod + * @param imStatusIdsByModel + */ + _mockIrWebsocket__updatePresence(inactivityPeriod, imStatusIdsByModel) { + this._mockBusBus__sendone(this.currentPartnerId, 'bus/im_status', this._mockIrWebsocket__getImStatus(imStatusIdsByModel)); + }, + /** + * Simulates `_get_im_status` on `ir.websocket`. + * + * @param {Object} imStatusIdsByModel + * @param {Number[]|undefined} res.partner ids of res.partners whose im_status + * should be monitored. + */ + _mockIrWebsocket__getImStatus({ 'res.partner': partnerIds }) { + return { + 'partners': this.mockSearchRead('res.partner', [[['id', 'in', partnerIds]]], { context: { 'active_test': false }, fields: ['im_status'] }) + }; + }, +}); diff --git a/addons/bus/websocket.py b/addons/bus/websocket.py index f1ab4e79a23..aecd42d50a9 100644 --- a/addons/bus/websocket.py +++ b/addons/bus/websocket.py @@ -766,7 +766,7 @@ class WebsocketRequest: if event_name == 'subscribe': ir_websocket._subscribe(data) if event_name == 'update_presence': - ir_websocket._update_bus_presence(data) + ir_websocket._update_bus_presence(**data) def _get_session(self): session = root.session_store.get(self.ws._session.sid) diff --git a/addons/hr_holidays/static/tests/qunit_suite_tests/components/persona_im_status_icon_tests.js b/addons/hr_holidays/static/tests/qunit_suite_tests/components/persona_im_status_icon_tests.js index c3f2d871e4d..c61006c8158 100644 --- a/addons/hr_holidays/static/tests/qunit_suite_tests/components/persona_im_status_icon_tests.js +++ b/addons/hr_holidays/static/tests/qunit_suite_tests/components/persona_im_status_icon_tests.js @@ -1,5 +1,7 @@ /** @odoo-module **/ +import { UPDATE_BUS_PRESENCE_DELAY } from '@bus/im_status_service'; + import { start, startServer } from '@mail/../tests/helpers/test_utils'; QUnit.module('hr_holidays', {}, function () { @@ -18,7 +20,7 @@ QUnit.test('on leave & online', async function (assert) { model: 'mail.channel', res_id: mailChannelId, }); - const { advanceTime, afterNextRender, messaging, openDiscuss } = await start({ + const { advanceTime, afterNextRender, openDiscuss } = await start({ discuss: { params: { default_active_id: mailChannelId, @@ -27,7 +29,7 @@ QUnit.test('on leave & online', async function (assert) { hasTimeControl: true, }); await openDiscuss(); - await afterNextRender(() => advanceTime(messaging.fetchImStatusTimerDuration)); + await afterNextRender(() => advanceTime(UPDATE_BUS_PRESENCE_DELAY)); assert.hasClass( document.querySelector('.o_PersonaImStatusIcon_icon'), 'o-online', @@ -52,7 +54,7 @@ QUnit.test('on leave & away', async function (assert) { model: 'mail.channel', res_id: mailChannelId, }); - const { advanceTime, afterNextRender, messaging, openDiscuss } = await start({ + const { advanceTime, afterNextRender, openDiscuss } = await start({ discuss: { params: { default_active_id: mailChannelId, @@ -61,7 +63,7 @@ QUnit.test('on leave & away', async function (assert) { hasTimeControl: true, }); await openDiscuss(); - await afterNextRender(() => advanceTime(messaging.fetchImStatusTimerDuration)); + await afterNextRender(() => advanceTime(UPDATE_BUS_PRESENCE_DELAY)); assert.hasClass( document.querySelector('.o_PersonaImStatusIcon_icon'), 'o-away', @@ -86,7 +88,7 @@ QUnit.test('on leave & offline', async function (assert) { model: 'mail.channel', res_id: mailChannelId, }); - const { advanceTime, afterNextRender, messaging, openDiscuss } = await start({ + const { advanceTime, afterNextRender, openDiscuss } = await start({ discuss: { params: { default_active_id: mailChannelId, @@ -95,7 +97,7 @@ QUnit.test('on leave & offline', async function (assert) { hasTimeControl: true, }); await openDiscuss(); - await afterNextRender(() => advanceTime(messaging.fetchImStatusTimerDuration)); + await afterNextRender(() => advanceTime(UPDATE_BUS_PRESENCE_DELAY)); assert.hasClass( document.querySelector('.o_PersonaImStatusIcon_icon'), 'o-offline', diff --git a/addons/hr_presence/models/ir_websocket.py b/addons/hr_presence/models/ir_websocket.py index 6ec159ca00c..37024ffc62f 100644 --- a/addons/hr_presence/models/ir_websocket.py +++ b/addons/hr_presence/models/ir_websocket.py @@ -9,8 +9,8 @@ from odoo.addons.bus.websocket import wsrequest class IrWebsocket(models.AbstractModel): _inherit = 'ir.websocket' - def _update_bus_presence(self, inactivity_period): - super()._update_bus_presence(inactivity_period) + def _update_bus_presence(self, inactivity_period, im_status_ids_by_model): + super()._update_bus_presence(inactivity_period, im_status_ids_by_model) # This method can either be called due to an http or a # websocket request. The request itself is necessary to # retrieve the current guest. Let's retrieve the proper diff --git a/addons/mail/controllers/bus.py b/addons/mail/controllers/bus.py index a12ec4cfb66..a737be6c0df 100644 --- a/addons/mail/controllers/bus.py +++ b/addons/mail/controllers/bus.py @@ -50,10 +50,3 @@ class MailChatController(BusController): return [] else: return channel._channel_fetch_message(last_id, limit) - - @route('/bus/im_status', type="json", auth="user") - def im_status(self, partner_ids, guest_ids=None): - im_status = super().im_status(partner_ids) - if guest_ids: - im_status['guests'] = request.env['mail.guest'].sudo().with_context(active_test=False).search([('id', 'in', guest_ids)]).read(['im_status']) - return im_status diff --git a/addons/mail/models/ir_websocket.py b/addons/mail/models/ir_websocket.py index bde8aa05cf4..1461e3c55ac 100644 --- a/addons/mail/models/ir_websocket.py +++ b/addons/mail/models/ir_websocket.py @@ -6,6 +6,12 @@ from odoo.addons.bus.websocket import wsrequest class IrWebsocket(models.AbstractModel): _inherit = 'ir.websocket' + def _get_im_status(self, data): + im_status = super()._get_im_status(data) + if 'mail.guest' in data: + im_status['guests'] = self.env['mail.guest'].sudo().with_context(active_test=False).search([('id', 'in', data['mail.guest'])]).read(['im_status']) + return im_status + def _build_bus_channel_list(self, channels): # This method can either be called due to an http or a # websocket request. The request itself is necessary to @@ -26,8 +32,8 @@ class IrWebsocket(models.AbstractModel): channels.append(mail_channel) return super()._build_bus_channel_list(channels) - def _update_bus_presence(self, inactivity_period): - super()._update_bus_presence(inactivity_period) + def _update_bus_presence(self, inactivity_period, im_status_ids_by_model): + super()._update_bus_presence(inactivity_period, im_status_ids_by_model) if not self.env.uid: # This method can either be called due to an http or a # websocket request. The request itself is necessary to diff --git a/addons/mail/static/src/models/messaging.js b/addons/mail/static/src/models/messaging.js index e861014a7d9..4eb12b6bbe5 100644 --- a/addons/mail/static/src/models/messaging.js +++ b/addons/mail/static/src/models/messaging.js @@ -17,6 +17,8 @@ registerModel({ this.refreshIsNotificationPermissionDefault(); }, _willDelete() { + this.env.services['im_status'].unregisterFromImStatus('res.partner'); + this.env.services['im_status'].unregisterFromImStatus('mail.guest'); this.env.bus.removeEventListener('window_focus', this._handleGlobalWindowFocus); delete odoo.__DEBUG__.messaging; }, @@ -93,9 +95,6 @@ registerModel({ const { message, ...options } = params; return this.env.services.notification.add(message, options); }, - onFetchImStatusTimerTimeout() { - this.startFetchImStatus(); - }, /** * Opens a chat with the provided person and returns it. * @@ -221,36 +220,19 @@ registerModel({ isNotificationPermissionDefault: Boolean(browserNotification) && browserNotification.permission === 'default', }); }, - async startFetchImStatus() { - this.update({ - fetchImStatusTimer: { doReset: this.fetchImStatusTimer ? true : undefined }, - }); + updateImStatusRegistration() { const partnerIds = []; for (const partner of this.models['Partner'].all()) { if (partner.im_status !== 'im_partner' && !partner.is_public) { partnerIds.push(partner.id); } } - if (partnerIds.length === 0) { - return; - } const guestIds = []; for (const guest of this.models['Guest'].all()) { guestIds.push(guest.id); } - if (partnerIds.length !== 0 || guestIds.length !== 0) { - const dataList = await this.messaging.rpc({ - route: '/bus/im_status', - params: { - partner_ids: partnerIds, - guest_ids: guestIds, - }, - }, { shadow: true }); - this.models['Partner'].insert(dataList.partners); - if (dataList.guests) { - this.models['Guest'].insert(dataList.guests); - } - } + this.env.services['im_status'].registerToImStatus('res.partner', partnerIds); + this.env.services['im_status'].registerToImStatus('mail.guest', guestIds); }, /** * @private @@ -329,6 +311,14 @@ registerModel({ this.env.services.bus_service.forceUpdateChannels(); } }, + /** + * @private + */ + _onChangeAllPersonas() { + if (this.isInitialized) { + this.updateImStatusRegisterThrottle.do(); + } + }, /** * @private */ @@ -344,6 +334,9 @@ registerModel({ allMailboxes: many('Mailbox', { inverse: 'messagingAsAnyMailbox', }), + allPersonas: many('Persona', { + inverse: 'messagingAsAnyPersona', + }), /** * Inverse of the messaging field present on all models. This field * therefore contains all existing records. @@ -404,12 +397,6 @@ registerModel({ isCausal: true, readonly: true, }), - fetchImStatusTimer: one('Timer', { - inverse: 'messagingOwnerAsFetchImStatusTimer', - }), - fetchImStatusTimerDuration: attr({ - default: 50 * 1000, - }), hasLinkPreviewFeature: attr(), history: one('Mailbox', { default: {}, @@ -516,6 +503,12 @@ registerModel({ isCausal: true, readonly: true, }), + updateImStatusRegisterThrottle: one('Throttle', { + compute() { + return { func: this.updateImStatusRegistration }; + }, + inverse: 'messagingAsUpdateImStatusRegister', + }), userSetting: one('UserSetting', { default: {}, isCausal: true, @@ -530,5 +523,9 @@ registerModel({ dependencies: ['allCurrentClientThreads'], methodName: '_onChangeAllCurrentClientThreads', }, + { + dependencies: ['allPersonas'], + methodName: '_onChangeAllPersonas', + }, ], }); diff --git a/addons/mail/static/src/models/messaging_initializer.js b/addons/mail/static/src/models/messaging_initializer.js index d9c11d9d7cf..4cc465c39ed 100644 --- a/addons/mail/static/src/models/messaging_initializer.js +++ b/addons/mail/static/src/models/messaging_initializer.js @@ -33,7 +33,7 @@ registerModel({ discuss.openInitThread(); } if (this.messaging.currentUser) { - this.messaging.startFetchImStatus(); + this.messaging.updateImStatusRegistration(); this._loadMessageFailures(); } }, diff --git a/addons/mail/static/src/models/messaging_notification_handler.js b/addons/mail/static/src/models/messaging_notification_handler.js index 9e976be4e3a..b1ee7197e36 100644 --- a/addons/mail/static/src/models/messaging_notification_handler.js +++ b/addons/mail/static/src/models/messaging_notification_handler.js @@ -48,6 +48,8 @@ registerModel({ const proms = notifications.map(message => { if (typeof message === 'object') { switch (message.type) { + case 'bus/im_status': + return this._handleNotificationBusImStatus(message.payload); case 'ir.attachment/delete': return this._handleNotificationAttachmentDelete(message.payload); case 'mail.channel.member/seen': @@ -149,6 +151,20 @@ registerModel({ * @param {Object} message */ _handleNotification(message) {}, + /** + * @private + * @param {Object} payload + * @param {Object[]} [payload.partners] + * @param {Object[]|undefined} [payload.guests] + */ + _handleNotificationBusImStatus({ partners, guests }) { + if (partners) { + this.models['Partner'].insert(partners); + } + if (guests) { + this.models['Guest'].insert(guests); + } + }, /** * @private * @param {Object} payload diff --git a/addons/mail/static/src/models/persona.js b/addons/mail/static/src/models/persona.js index 2715464437b..9bc324c40d6 100644 --- a/addons/mail/static/src/models/persona.js +++ b/addons/mail/static/src/models/persona.js @@ -60,6 +60,11 @@ registerModel({ im_status: attr({ compute: '_computeImStatus', }), + messagingAsAnyPersona: one('Messaging', { + default: {}, + inverse: 'allPersonas', + }), + name: attr({ compute: '_computeName', }), diff --git a/addons/mail/static/src/models/throttle.js b/addons/mail/static/src/models/throttle.js index 53143682023..2f433facd72 100644 --- a/addons/mail/static/src/models/throttle.js +++ b/addons/mail/static/src/models/throttle.js @@ -53,6 +53,9 @@ registerModel({ if (this.threadAsThrottleNotifyCurrentPartnerTypingStatus) { return 2.5 * 1000; } + if (this.messagingAsUpdateImStatusRegister) { + return 10 * 1000; + } return clear(); }, }, @@ -75,6 +78,10 @@ registerModel({ * Inner function to be invoked and throttled. */ func: attr(), + messagingAsUpdateImStatusRegister: one('Messaging', { + identifying: true, + inverse: 'updateImStatusRegisterThrottle', + }), shouldInvoke: attr({ default: false, }), diff --git a/addons/mail/static/src/models/timer.js b/addons/mail/static/src/models/timer.js index 29e2e057e91..8c312cba1e9 100644 --- a/addons/mail/static/src/models/timer.js +++ b/addons/mail/static/src/models/timer.js @@ -33,9 +33,6 @@ registerModel({ if (this.messageViewOwnerAsHighlight) { return 2 * 1000; } - if (this.messagingOwnerAsFetchImStatusTimer) { - return this.messagingOwnerAsFetchImStatusTimer.fetchImStatusTimerDuration; - } if (this.rtcSessionOwnerAsBroadcast) { return 3 * 1000; } @@ -74,10 +71,6 @@ registerModel({ this.messageViewOwnerAsHighlight.onHighlightTimerTimeout(); return; } - if (this.messagingOwnerAsFetchImStatusTimer) { - this.messagingOwnerAsFetchImStatusTimer.onFetchImStatusTimerTimeout(); - return; - } if (this.rtcSessionOwnerAsBroadcast) { this.rtcSessionOwnerAsBroadcast.onBroadcastTimeout(); return; @@ -134,10 +127,6 @@ registerModel({ compute: '_computeDuration', required: true, }), - messagingOwnerAsFetchImStatusTimer: one('Messaging', { - identifying: true, - inverse: 'fetchImStatusTimer', - }), messageViewOwnerAsHighlight: one('MessageView', { identifying: true, inverse: 'highlightTimer', diff --git a/addons/mail/static/tests/helpers/mock_server.js b/addons/mail/static/tests/helpers/mock_server.js index 31f61d9bbb9..67ce38180e2 100644 --- a/addons/mail/static/tests/helpers/mock_server.js +++ b/addons/mail/static/tests/helpers/mock_server.js @@ -62,12 +62,6 @@ patch(MockServer.prototype, 'mail', { */ async _performRPC(route, args) { // routes - if (route === '/bus/im_status') { - const { partner_ids } = args; - return { - 'partners': this.pyEnv['res.partner'].searchRead([['id', 'in', partner_ids]], { context: { 'active_test': false }, fields: ['im_status'] }) - }; - } if (route === '/mail/message/post') { if (args.thread_model === 'mail.channel') { return this._mockMailChannelMessagePost(args.thread_id, args.post_data, args.context); diff --git a/addons/mail/static/tests/helpers/webclient_setup.js b/addons/mail/static/tests/helpers/webclient_setup.js index 231ccc7b6b9..722ad413d9f 100644 --- a/addons/mail/static/tests/helpers/webclient_setup.js +++ b/addons/mail/static/tests/helpers/webclient_setup.js @@ -1,6 +1,7 @@ /** @odoo-module **/ import { busService } from '@bus/services/bus_service'; +import { imStatusService } from '@bus/im_status_service'; import { multiTabService } from '@bus/multi_tab_service'; import { makeMultiTabToLegacyEnv } from '@bus/services/legacy/make_multi_tab_to_legacy_env'; import { makeBusServiceToLegacyEnv } from '@bus/services/legacy/make_bus_service_to_legacy_env'; @@ -84,6 +85,7 @@ function setupMessagingServiceRegistries({ services = { bus_service: busService, + im_status: imStatusService, messaging: messagingService, messagingValues, presence: makeFakePresenceService({ diff --git a/addons/mail/static/tests/qunit_suite_tests/components/persona_im_status_icon_tests.js b/addons/mail/static/tests/qunit_suite_tests/components/persona_im_status_icon_tests.js index 1248ebef2e0..3723f8cc3d7 100644 --- a/addons/mail/static/tests/qunit_suite_tests/components/persona_im_status_icon_tests.js +++ b/addons/mail/static/tests/qunit_suite_tests/components/persona_im_status_icon_tests.js @@ -1,5 +1,7 @@ /** @odoo-module **/ +import { UPDATE_BUS_PRESENCE_DELAY } from '@bus/im_status_service'; + import { start, startServer } from '@mail/../tests/helpers/test_utils'; QUnit.module('mail', {}, function () { @@ -18,7 +20,7 @@ QUnit.test('initially online', async function (assert) { model: 'mail.channel', res_id: mailChannelId, }); - const { advanceTime, afterNextRender, messaging, openDiscuss } = await start({ + const { advanceTime, afterNextRender, openDiscuss } = await start({ discuss: { params: { default_active_id: mailChannelId, @@ -27,7 +29,7 @@ QUnit.test('initially online', async function (assert) { hasTimeControl: true, }); await openDiscuss(); - await afterNextRender(() => advanceTime(messaging.fetchImStatusTimerDuration)); + await afterNextRender(() => advanceTime(UPDATE_BUS_PRESENCE_DELAY)); assert.strictEqual( document.querySelectorAll(`.o_PersonaImStatusIcon.o-online`).length, 1, @@ -47,7 +49,7 @@ QUnit.test('initially offline', async function (assert) { model: 'mail.channel', res_id: mailChannelId, }); - const { advanceTime, afterNextRender, messaging, openDiscuss } = await start({ + const { advanceTime, afterNextRender, openDiscuss } = await start({ discuss: { params: { default_active_id: mailChannelId, @@ -56,7 +58,7 @@ QUnit.test('initially offline', async function (assert) { hasTimeControl: true, }); await openDiscuss(); - await afterNextRender(() => advanceTime(messaging.fetchImStatusTimerDuration)); + await afterNextRender(() => advanceTime(UPDATE_BUS_PRESENCE_DELAY)); assert.strictEqual( document.querySelectorAll(`.o_PersonaImStatusIcon.o-offline`).length, 1, @@ -76,7 +78,7 @@ QUnit.test('initially away', async function (assert) { model: 'mail.channel', res_id: mailChannelId, }); - const { advanceTime, afterNextRender, messaging, openDiscuss } = await start({ + const { advanceTime, afterNextRender, openDiscuss } = await start({ discuss: { params: { default_active_id: mailChannelId, @@ -85,7 +87,7 @@ QUnit.test('initially away', async function (assert) { hasTimeControl: true, }); await openDiscuss(); - await afterNextRender(() => advanceTime(messaging.fetchImStatusTimerDuration)); + await afterNextRender(() => advanceTime(UPDATE_BUS_PRESENCE_DELAY)); assert.strictEqual( document.querySelectorAll(`.o_PersonaImStatusIcon.o-away`).length, 1, @@ -105,7 +107,7 @@ QUnit.test('change icon on change partner im_status', async function (assert) { model: 'mail.channel', res_id: mailChannelId, }); - const { advanceTime, afterNextRender, messaging, openDiscuss } = await start({ + const { advanceTime, afterNextRender, openDiscuss } = await start({ discuss: { params: { default_active_id: mailChannelId, @@ -114,7 +116,7 @@ QUnit.test('change icon on change partner im_status', async function (assert) { hasTimeControl: true, }); await openDiscuss(); - await afterNextRender(() => advanceTime(messaging.fetchImStatusTimerDuration)); + await afterNextRender(() => advanceTime(UPDATE_BUS_PRESENCE_DELAY)); assert.strictEqual( document.querySelectorAll(`.o_PersonaImStatusIcon.o-online`).length, 1, @@ -122,7 +124,7 @@ QUnit.test('change icon on change partner im_status', async function (assert) { ); pyEnv['res.partner'].write([partnerId], { im_status: 'offline' }); - await afterNextRender(() => advanceTime(messaging.fetchImStatusTimerDuration)); + await afterNextRender(() => advanceTime(UPDATE_BUS_PRESENCE_DELAY)); assert.strictEqual( document.querySelectorAll(`.o_PersonaImStatusIcon.o-offline`).length, 1, @@ -130,7 +132,7 @@ QUnit.test('change icon on change partner im_status', async function (assert) { ); pyEnv['res.partner'].write([partnerId], { im_status: 'away' }); - await afterNextRender(() => advanceTime(messaging.fetchImStatusTimerDuration)); + await afterNextRender(() => advanceTime(UPDATE_BUS_PRESENCE_DELAY)); assert.strictEqual( document.querySelectorAll(`.o_PersonaImStatusIcon.o-away`).length, 1, @@ -138,7 +140,7 @@ QUnit.test('change icon on change partner im_status', async function (assert) { ); pyEnv['res.partner'].write([partnerId], { im_status: 'online' }); - await afterNextRender(() => advanceTime(messaging.fetchImStatusTimerDuration)); + await afterNextRender(() => advanceTime(UPDATE_BUS_PRESENCE_DELAY)); assert.strictEqual( document.querySelectorAll(`.o_PersonaImStatusIcon.o-online`).length, 1,