diff options
| author | XavierDo <xdo@odoo.com> | 2019-01-07 11:19:00 +0000 |
|---|---|---|
| committer | XavierDo <xdo@odoo.com> | 2019-02-07 08:54:33 +0000 |
| commit | 07a261db7196d3f7aa7d50d98575c5b6efca9242 (patch) | |
| tree | 3cc3debc34ecfdf484b174dc8c5850e7e07651f4 /addons/mail/static/src/js | |
| parent | 768844040f887868759309ad101fe50191fc949a (diff) | |
[IMP] bus, mail, mail_bot: add im_status service
This commit is a refactoring of im_status management.
This commit also add im_status in two places, near the author in a mail thread and on each suggestion when using mentions.
A service to manage im_status will have multiple benefits here:
-centralise information, avoid to call the server multiple time for the same im_status
-update all im_status at once and keep consistency in display.
With this commit, the im_status updates are now done by rpc call.
Updates where previously made with the bus but this has some drawback,
since the bus will only give the information 50 seconds after the beginning of
the request, in the worst case, we van wait 2*50 seconds to get an update.
More than that, the im_status where only updates for pinned dm_chat. Dm chat
are synchronized cross tabs, making the use of the bus possible for this purpose.
Since we will need to display im_status not linked to dm_chat, the list to update
will be different from tab to tab making the use of bus difficult for this purpose.
Technical notes:
-im_search has been moved from bus to mail addons since it concerns mail.channel
-Update of an im_status should be reflected everywhere in the page.
Since im_status is a rendered template used in multiple widget, we should add
the correct logic to all concerened widget. The current solution is simple:
use a jquery selector to find every place where im_status is rendered.
-we add a new im_status: im_partner. This will indicate that that
the partner has no user linked to him, making it possible to avoid to ask
for status updates for this partner.
-The update will only be done when the tab is focused. (and will be done
asap once the tab get focus back)
Diffstat (limited to 'addons/mail/static/src/js')
10 files changed, 285 insertions, 51 deletions
diff --git a/addons/mail/static/src/js/composers/basic_composer.js b/addons/mail/static/src/js/composers/basic_composer.js index 8a045ac2d82..58e334808bf 100644 --- a/addons/mail/static/src/js/composers/basic_composer.js +++ b/addons/mail/static/src/js/composers/basic_composer.js @@ -371,7 +371,15 @@ var BasicComposer = Widget.extend({ { limit: limit, search: search } ); } - return suggestions; + return $.when(suggestions).then(function (suggestions) { + //add im_status on suggestions + _.each(suggestions, function (suggestionsSet) { + _.each(suggestionsSet, function (suggestion) { + suggestion.im_status = self.call('mail_service', 'getImStatus', { partnerID: suggestion.id }); + }); + }); + return suggestions; + }); }); }, /** diff --git a/addons/mail/static/src/js/discuss.js b/addons/mail/static/src/js/discuss.js index 808cd8338ad..073befc5fcd 100644 --- a/addons/mail/static/src/js/discuss.js +++ b/addons/mail/static/src/js/discuss.js @@ -56,7 +56,10 @@ var PartnerInviteDialog = Dialog.extend({ allowClear: true, multiple: true, formatResult: function (item) { - var status = QWeb.render('mail.UserStatus', { status: item.im_status }); + var status = QWeb.render('mail.UserStatus', { + status: self.call('mail_service', 'getImStatus', { partnerID: item.id }), + partnerID: item.id, + }); return $('<span>').text(item.text).prepend(status); }, query: function (query) { @@ -992,10 +995,10 @@ var Discuss = AbstractAction.extend({ .on('new_channel', this, this._onNewChannel) .on('is_thread_bottom_visible', this, this._onIsThreadBottomVisible) .on('unsubscribe_from_channel', this, this._onChannelLeft) + .on('updated_im_status', this, this._onUpdatedImStatus) .on('update_needaction', this, this._onUpdateNeedaction) .on('update_starred', this, this._onUpdateStarred) .on('update_thread_unread_counter', this, this._onUpdateThreadUnreadCounter) - .on('update_dm_presence', this, this._onUpdateDmPresence) .on('activity_updated', this, this._onActivityUpdated) .on('update_moderation_counter', this, this._onUpdateModerationCounter) .on('update_typing_partners', this, this._onTypingPartnersUpdated) @@ -1572,7 +1575,7 @@ var Discuss = AbstractAction.extend({ /** * @private */ - _onUpdateDmPresence: function () { + _onUpdatedImStatus: function () { this._throttledUpdateThreads(); }, /** diff --git a/addons/mail/static/src/js/models/messages/abstract_message.js b/addons/mail/static/src/js/models/messages/abstract_message.js index c1ae22b9708..bb2a19cc6ac 100644 --- a/addons/mail/static/src/js/models/messages/abstract_message.js +++ b/addons/mail/static/src/js/models/messages/abstract_message.js @@ -76,6 +76,14 @@ var AbstractMessage = Class.extend({ return this._serverAuthorID[0]; }, /** + * Threads do not have an im status by default + * + * @return {undefined} + */ + getAuthorImStatus: function () { + return undefined; + }, + /** * Get the relative url of the avatar to display next to the message * * @abstract diff --git a/addons/mail/static/src/js/models/messages/message.js b/addons/mail/static/src/js/models/messages/message.js index dc98bb7195b..a414a11874b 100644 --- a/addons/mail/static/src/js/models/messages/message.js +++ b/addons/mail/static/src/js/models/messages/message.js @@ -66,6 +66,16 @@ var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin this._customerEmailData.push(data); }, /** + * @override + * @return {string|undefined} + */ + getAuthorImStatus: function () { + if (!this.hasAuthor()) { + return undefined; + } + return this.call('mail_service', 'getImStatus', { partnerID: this.getAuthorID() }); + }, + /** * Get the name of the author of this message * If there are no author, return "". * diff --git a/addons/mail/static/src/js/models/threads/dm_chat.js b/addons/mail/static/src/js/models/threads/dm_chat.js index 5e69dbeff8c..4967a1e9963 100644 --- a/addons/mail/static/src/js/models/threads/dm_chat.js +++ b/addons/mail/static/src/js/models/threads/dm_chat.js @@ -26,8 +26,14 @@ var DMChat = TwoUserChannel.extend({ this._directPartnerID = data.direct_partner[0].id; this._name = data.custom_channel_name || data.direct_partner[0].name; - this._status = data.direct_partner[0].im_status; + this._outOfOfficeMessage = data.direct_partner[0].out_of_office_message; + this._outOfOfficeDateEnd = data.direct_partner[0].out_of_office_date_end; this._type = 'dm_chat'; + + this.call('mail_service', 'updateImStatus', [{ + id: this._directPartnerID, + im_status: data.direct_partner[0].im_status + }]); }, //-------------------------------------------------------------------------- @@ -53,10 +59,11 @@ var DMChat = TwoUserChannel.extend({ return result; }, /** - * @param {string} newStatus + * @override + * @return {string} */ - setStatus: function (newStatus) { - this._status = newStatus; + getStatus: function () { + return this.call('mail_service', 'getImStatus', { partnerID: this._directPartnerID }); }, }); diff --git a/addons/mail/static/src/js/services/mail_manager.js b/addons/mail/static/src/js/services/mail_manager.js index 777df1df4a6..204a88bb8e9 100644 --- a/addons/mail/static/src/js/services/mail_manager.js +++ b/addons/mail/static/src/js/services/mail_manager.js @@ -443,10 +443,6 @@ var MailManager = AbstractService.extend({ channel = this._makeChannel(data, options); if (channel.getType() === 'dm_chat') { this._pinnedDmPartners.push(channel.getDirectPartnerID()); - this.call('bus_service', 'updateOption', - 'bus_presence_partner_ids', - this._pinnedDmPartners - ); } this._threads.push(channel); if (data.last_message) { @@ -1041,10 +1037,6 @@ var MailManager = AbstractService.extend({ var index = this._pinnedDmPartners.indexOf(channel.getDirectPartnerID()); if (index > -1) { this._pinnedDmPartners.splice(index, 1); - this.call('bus_service', 'updateOption', - 'bus_presence_partner_ids', - this._pinnedDmPartners - ); } } this._threads = _.without(this._threads, channel); diff --git a/addons/mail/static/src/js/services/mail_notification_manager.js b/addons/mail/static/src/js/services/mail_notification_manager.js index b59d7b91434..b77fa3e65fd 100644 --- a/addons/mail/static/src/js/services/mail_notification_manager.js +++ b/addons/mail/static/src/js/services/mail_notification_manager.js @@ -494,22 +494,6 @@ MailManager.include({ }); }, /** - * On receiving an update on user status (e.g. becoming 'online', 'offline', - * 'idle', etc.). - * - * @private - * @param {Object} data partner infos - * @param {integer} data.id partner ID - * @param {string} data.im_status partner new 'im status' (e.g. 'online') - */ - _handlePresenceNotification: function (data) { - var dmChat = this.getDMChatFromPartnerID(data.id); - if (dmChat) { - dmChat.setStatus(data.im_status); - this._mailBus.trigger('update_dm_presence', dmChat); - } - }, - /** * @override * @private */ @@ -569,8 +553,6 @@ MailManager.include({ }); } else if (model === 'res.partner') { self._handlePartnerNotification(notif[1]); - } else if (model === 'bus.presence') { - self._handlePresenceNotification(notif[1]); } }); }, diff --git a/addons/mail/static/src/js/services/mail_service.js b/addons/mail/static/src/js/services/mail_service.js index 4f6b0c29dfc..2256e696c2f 100644 --- a/addons/mail/static/src/js/services/mail_service.js +++ b/addons/mail/static/src/js/services/mail_service.js @@ -5,6 +5,7 @@ var core = require('web.core'); var MailManager = require('mail.Manager'); +require('mail.Manager.Status'); require('mail.Manager.Notification'); require('mail.Manager.Window'); require('mail.Manager.DocumentThread'); diff --git a/addons/mail/static/src/js/services/mail_status_manager.js b/addons/mail/static/src/js/services/mail_status_manager.js new file mode 100644 index 00000000000..2b75cdc6ee7 --- /dev/null +++ b/addons/mail/static/src/js/services/mail_status_manager.js @@ -0,0 +1,216 @@ +odoo.define('mail.Manager.Status', function (require) { +"use strict"; + +var core = require('web.core'); +var MailManager = require('mail.Manager'); +var QWeb = core.qweb; + +/** + * Mail Manager: IM Status + * + * This component handles im status of partners, which is useful for DM Chats, + * partner mention suggestions, and chatter messages that display the user icon. + */ +MailManager.include({ + _UPDATE_INTERVAL: 50, + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * Returns the uin cache im status, and triggers an asynchronous throttled + * update of im_status cache for missings + * + * @param {Object} data + * @param {integer} data.partnerID + * @return {String} + */ + getImStatus: function (data) { + var self = this; + var partnerID = data.partnerID; + if (!this._imStatus[partnerID]) { + // Add to list to call it in next bus update or _fetchMissingImStatus + this._imStatus[partnerID] = undefined; + // fetch after some time if no other getImStatus occurs + this._clearStatusServiceTimeout(this._fetchStatusTimeout); + this._fetchStatusTimeout = this._setStatusServiceTimeout(function () { + self._fetchMissingImStatus(); + }, 500); + } + return this._imStatus[partnerID]; + }, + /** + * Update status manually, to avoid to do a rpc and an asynchronous update + * after getImStatus. Can be done by any caller knowing the last im_status + * state. + * + * @param {Object[]} statusList, A list of {id, im_status} + */ + updateImStatus: function (statusList) { + var updatedIDs = []; + var self = this; + _.each(statusList, function (status) { + if (self._imStatus[status.id] === status.im_status) { + return; + } + updatedIDs.push(status.id); + self._imStatus[status.id] = status.im_status; + }); + if (! _.isEmpty(updatedIDs)) { + this._mailBus.trigger('updated_im_status', updatedIDs); // useful for thread window header + this._renderImStatus(updatedIDs); + } + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * A simple clearTimeout, useful for test + * + * @private + * @param {integer} ids + */ + _clearStatusServiceTimeout: function (id) { + clearTimeout(id); + }, + /** + * Fetch the list of im_status for partner with id in ids list and triggers + * an update. + * + * @private + * @param {Object} data + * @param {integer[]} data.partnerIDs + * @return {Deferred} + */ + _fetchImStatus: function (data) { + var self = this; + var partnerIDs = data.partnerIDs; + if (_.isEmpty(partnerIDs)) { + return $.when(); + } + return this._rpc({ + model: 'res.partner', + method: 'read', + args: [partnerIDs, ['id', 'im_status']], + }).then( function (results) { + self.updateImStatus(results); + }); + }, + /** + * Fetch the list of im_status for partner with an unknown im_status and + * triggers an update. + * + * @private + * @return {Deferred} + */ + _fetchMissingImStatus: function () { + var missing = []; + _.each(this._imStatus, function (value, key) { + if (value === undefined) { + missing.push(Number(key)); + } + }); + return this._fetchImStatus({ partnerIDs: missing }); + }, + /** + * @private + * @return {integer[]} a list of partner ids that needs update + */ + _getImStatusToUpdate: function () { + var toUpdate = []; + _.each(this._imStatus, function (status, key) { + //filter on im_partner and bot: useless to update them, status won't change + if (['im_partner', 'bot'].indexOf(status) === -1) { + toUpdate.push(Number(key)); + } + }); + return toUpdate; + }, + /** + * @override + * @private + */ + _initializeInternalState: function () { + this._super.apply(this, arguments); + this._fetchStatusTimeout = undefined; + this._imStatus = {}; + this._isTabFocused = true; + this._updateImStatusLoop(); + }, + /** + * @override + * @private + */ + _listenOnBuses: function () { + this._super.apply(this, arguments); + $(window).on("focus", this._onWindowFocusChange.bind(this, true)); + $(window).on("blur", this._onWindowFocusChange.bind(this, false)); + $(window).on("unload", this._onWindowFocusChange.bind(this, false)); + }, + /** + * @private + * @param {integer[]} updatedIds + */ + _renderImStatus: function (updatedIds) { + var self = this; + $('.o_updatable_im_status').each(function () { + var $this = $(this); + var partnerID = $this.data('partner-id'); + if (partnerID !== undefined && updatedIds.indexOf(partnerID) !== -1) { // todo instead add id on o_updatable_im_status and select only concerned ones + var status = QWeb.render('mail.UserStatus', { + status: self.getImStatus({ partnerID: partnerID }), + partnerID: partnerID, + }); + $this.replaceWith(status); + } + }); + }, + /** + * A simple setTimeout, useful for test + * + * @private + * @param {function} func + * @param {integer} duration + * @return {integer} + */ + _setStatusServiceTimeout: function (func, duration) { + return setTimeout(func, duration); + }, + /** + * Once initialised, this loop will update the im_status of registered + * users. + * + * @private + * @param {integer} [counter=0] The recursion loop counter + */ + _updateImStatusLoop: function (counter) { + var self = this; + if (!_.isNumber(counter)) { + counter = 0; + } + this._setStatusServiceTimeout(function () { + if (counter >= self._UPDATE_INTERVAL && self._isTabFocused) { + self._fetchImStatus({ partnerIDs: self._getImStatusToUpdate() }); + counter = 0; + } + self._updateImStatusLoop(counter+1); + }, 1000); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + * @param {boolean} focused + */ + _onWindowFocusChange: function (focused) { + this._isTabFocused = focused; + }, +}); + +}); diff --git a/addons/mail/static/src/js/services/mail_window_manager.js b/addons/mail/static/src/js/services/mail_window_manager.js index 4971e7f3f4b..e047c71ab89 100644 --- a/addons/mail/static/src/js/services/mail_window_manager.js +++ b/addons/mail/static/src/js/services/mail_window_manager.js @@ -334,8 +334,8 @@ MailManager.include({ .on('new_channel', this, this._onNewChannel) .on('is_thread_bottom_visible', this, this._onIsThreadBottomVisible) .on('unsubscribe_from_channel', this, this._onUnsubscribeFromChannel) - .on('update_thread_unread_counter', this, this._onUpdateThreadUnreadCounter) - .on('update_dm_presence', this, this._onUpdateDmPresence); + .on('updated_im_status', this, this._onUpdatedImStatus) + .on('update_thread_unread_counter', this, this._onUpdateThreadUnreadCounter); core.bus.on('resize', this, _.debounce(this._repositionThreadWindows.bind(this), 100)); }, @@ -664,6 +664,28 @@ MailManager.include({ this._closeThreadWindow(channelID); }, /** + * Called when there is a change of the im status of the partner. + * The header of the thread window should be updated accordingly, + * in order to display the correct new im status of this users. + * + * @private + * @param {integer} partnerID + */ + _onUpdatedImStatus: function (partnerIDs) { + var self = this; + _.each(partnerIDs, function (partnerID) { + var thread = self.getDMChatFromPartnerID(partnerID); + if (! thread) { + return; + } + var threadWindow = self._getThreadWindow(thread.getID()); + if (!threadWindow) { + return; + } + threadWindow.renderHeader(); + }); + }, + /** * Called when a thread has its unread counter that has changed. * The unread counter on the thread windows should be updated. * @@ -691,21 +713,6 @@ MailManager.include({ } }, /** - * Called when there is a change of the im status of the user linked to - * DMs. The header of the thread window should be updated accordingly, - * in order to display the correct new im status of this users. - * - * @private - * @param {mail.model.Thread} thread - */ - _onUpdateDmPresence: function (thread) { - _.each(this._threadWindows, function (threadWindow) { - if (thread.getID() === threadWindow.getID()) { - threadWindow.renderHeader(); - } - }); - }, - /** * Called when a message has been updated. * * @private |
