summaryrefslogtreecommitdiff
path: root/addons/mail/static
diff options
context:
space:
mode:
authorXavierDo <xdo@odoo.com>2019-01-07 11:19:00 +0000
committerXavierDo <xdo@odoo.com>2019-02-07 08:54:33 +0000
commit07a261db7196d3f7aa7d50d98575c5b6efca9242 (patch)
tree3cc3debc34ecfdf484b174dc8c5850e7e07651f4 /addons/mail/static
parent768844040f887868759309ad101fe50191fc949a (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')
-rw-r--r--addons/mail/static/src/js/composers/basic_composer.js10
-rw-r--r--addons/mail/static/src/js/discuss.js9
-rw-r--r--addons/mail/static/src/js/models/messages/abstract_message.js8
-rw-r--r--addons/mail/static/src/js/models/messages/message.js10
-rw-r--r--addons/mail/static/src/js/models/threads/dm_chat.js15
-rw-r--r--addons/mail/static/src/js/services/mail_manager.js8
-rw-r--r--addons/mail/static/src/js/services/mail_notification_manager.js18
-rw-r--r--addons/mail/static/src/js/services/mail_service.js1
-rw-r--r--addons/mail/static/src/js/services/mail_status_manager.js216
-rw-r--r--addons/mail/static/src/js/services/mail_window_manager.js41
-rw-r--r--addons/mail/static/src/scss/composer.scss3
-rw-r--r--addons/mail/static/src/xml/composer.xml4
-rw-r--r--addons/mail/static/src/xml/discuss.xml15
-rw-r--r--addons/mail/static/src/xml/thread.xml18
-rw-r--r--addons/mail/static/tests/chatter_tests.js16
-rw-r--r--addons/mail/static/tests/discuss_tests.js12
-rw-r--r--addons/mail/static/tests/mail_status_manager_tests.js213
17 files changed, 553 insertions, 64 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
diff --git a/addons/mail/static/src/scss/composer.scss b/addons/mail/static/src/scss/composer.scss
index 096c527aa16..1f2fffcee60 100644
--- a/addons/mail/static/src/scss/composer.scss
+++ b/addons/mail/static/src/scss/composer.scss
@@ -55,6 +55,9 @@
font-style: italic;
color: gray;
}
+ .fa {
+ font-size: 0.8em;
+ }
}
textarea {
resize: none;
diff --git a/addons/mail/static/src/xml/composer.xml b/addons/mail/static/src/xml/composer.xml
index ff4680d7851..b9de0194e93 100644
--- a/addons/mail/static/src/xml/composer.xml
+++ b/addons/mail/static/src/xml/composer.xml
@@ -71,6 +71,10 @@
</t>
<t t-name="mail.MentionSuggestions.Partner" t-extend="mail.MentionSuggestions">
<t t-jquery=".o_mention_proposition" t-operation="append">
+ <t t-call="mail.UserStatus">
+ <t t-set="status" t-value="suggestion.im_status"/>
+ <t t-set="partnerID" t-value="suggestion.id"/>
+ </t>
<span class="o_mention_name"><t t-esc="suggestion.name"/></span>
<t t-if="suggestion.email">
<span class="o_mention_info">(<t t-esc="suggestion.email"/>)</span>
diff --git a/addons/mail/static/src/xml/discuss.xml b/addons/mail/static/src/xml/discuss.xml
index 98430c81ce1..10fb71a5110 100644
--- a/addons/mail/static/src/xml/discuss.xml
+++ b/addons/mail/static/src/xml/discuss.xml
@@ -154,6 +154,7 @@
<span t-if="displayStatus">
<t t-call="mail.UserStatus">
<t t-set="status" t-value="channel.getStatus()"/>
+ <t t-set="partnerID" t-value="channel.getDirectPartnerID()"/>
</t>
</span>
<span t-if="displayHash" class="o_mail_hash">#</span>
@@ -176,11 +177,14 @@
<!--
@param {string} status
+ @param {integer|undefined} [partnerID]
-->
<t t-name="mail.UserStatus">
- <i t-if="status == 'online'" class="o_mail_user_status o_user_online fa fa-circle" title="Online" role="img" aria-label="User is online"/>
- <i t-if="status == 'away'" class="o_mail_user_status o_user_idle fa fa-circle" title="Idle" role="img" aria-label="User is idle"/>
- <i t-if="status == 'offline'" class="o_mail_user_status fa fa-circle-o" title="Offline" role="img" aria-label="User is offline"/>
+ <span t-att-class="partnerID ? 'o_updatable_im_status' : ''" t-att-data-partner-id="partnerID">
+ <i t-if="status == 'online'" class="o_mail_user_status o_user_online fa fa-circle" title="Online" role="img" aria-label="User is online"/>
+ <i t-if="status == 'away'" class="o_mail_user_status o_user_idle fa fa-circle" title="Idle" role="img" aria-label="User is idle"/>
+ <i t-if="status == 'offline'" class="o_mail_user_status fa fa-circle" title="Offline" role="img" aria-label="User is offline"/>
+ </span>
</t>
<!--
@@ -300,8 +304,9 @@
t-att-data-preview-id="preview.id" t-att-data-document-id="preview.documentID" t-att-data-document-model="preview.documentModel" t-att-data-unread-counter="preview.unreadCounter" t-att-data-message-ids="preview.messageIDs">
<div t-attf-class="o_mail_preview_image #{preview.documentModel ? 'o_mail_preview_app' : ''}">
<img class="o_mail_preview_image" t-att-src="preview.imageSRC" alt="Preview"/>
- <i t-if="preview.status === 'online'" class="o_mail_user_status o_user_online fa fa-circle" title="Online" role="img" aria-label="User is online"/>
- <i t-if="preview.status === 'away'" class="o_mail_user_status o_user_idle fa fa-circle" title="Idle" role="img" aria-label="User is idle"/>
+ <t t-call="mail.UserStatus">
+ <t t-set="status" t-value="preview.status"/>
+ </t>
</div>
<div class="o_preview_info">
<div class="o_preview_title">
diff --git a/addons/mail/static/src/xml/thread.xml b/addons/mail/static/src/xml/thread.xml
index be73917a1b5..f6d6ad599ec 100644
--- a/addons/mail/static/src/xml/thread.xml
+++ b/addons/mail/static/src/xml/thread.xml
@@ -258,12 +258,18 @@
<div t-att-class="'o_thread_message ' + (message.getID() === options.selectedMessageID ? 'o_thread_selected_message ' : ' ') + (message.isDiscussion() or message.isNotification() ? ' o_mail_discussion ' : ' o_mail_not_discussion ')" t-att-data-message-id="message.getID()">
<div t-if="options.displayAvatars" class="o_thread_message_sidebar">
<t t-if="message.hasAuthor()">
- <img t-if="displayAuthorMessages[message.getID()]"
- alt=""
- t-att-src="message.getAvatarSource()"
- data-oe-model="res.partner"
- t-att-data-oe-id="message.shouldRedirectToAuthor() ? message.getAuthorID() : ''"
- t-attf-class="o_thread_message_avatar rounded-circle #{message.shouldRedirectToAuthor() ? 'o_mail_redirect' : ''}"/>
+ <div t-if="displayAuthorMessages[message.getID()]" class="o_thread_message_sidebar_image">
+ <img
+ alt=""
+ t-att-src="message.getAvatarSource()"
+ data-oe-model="res.partner"
+ t-att-data-oe-id="message.shouldRedirectToAuthor() ? message.getAuthorID() : ''"
+ t-attf-class="o_thread_message_avatar rounded-circle #{message.shouldRedirectToAuthor() ? 'o_mail_redirect' : ''}"/>
+ <t t-call="mail.UserStatus">
+ <t t-set="status" t-value="message.getAuthorImStatus()"/>
+ <t t-set="partnerID" t-value="message.getAuthorID()"/>
+ </t>
+ </div>
</t>
<t t-else="">
<img t-if="displayAuthorMessages[message.getID()]"
diff --git a/addons/mail/static/tests/chatter_tests.js b/addons/mail/static/tests/chatter_tests.js
index e75157d3ad7..7aaa35ac4e3 100644
--- a/addons/mail/static/tests/chatter_tests.js
+++ b/addons/mail/static/tests/chatter_tests.js
@@ -27,6 +27,18 @@ QUnit.module('Chatter', {
this.services = mailTestUtils.getMailServices();
this.data = {
+ 'res.partner': {
+ fields: {
+ im_status: {
+ string: "im_status",
+ type: "char",
+ }
+ },
+ records: [{
+ id: 1,
+ im_status: 'online',
+ }]
+ },
partner: {
fields: {
display_name: { string: "Displayed name", type: "char" },
@@ -772,7 +784,7 @@ QUnit.test('chatter: post, receive and star messages', function (assert) {
}
if (args.method === 'get_mention_suggestions') {
getSuggestionsDef.resolve();
- return $.when([{email: "test@odoo.com", id: 1, name: "Test User"}]);
+ return $.when([[{email: "test@odoo.com", id: 1, name: "Test User"}], []]);
}
if (args.method === 'message_post') {
var lastMessageData = _.max(this.data['mail.message'].records, function (messageData) {
@@ -1308,7 +1320,7 @@ QUnit.test('chatter: discard changes on message post with post_refresh "recipien
}
if (args.method === 'get_mention_suggestions') {
getSuggestionsDef.resolve();
- return $.when([{email: "me@odoo.com", id: 42, name: "Me"}]);
+ return $.when([[{email: "me@odoo.com", id: 42, name: "Me"}], []]);
}
if (args.method === 'message_format') {
var requested_msgs = _.filter(messages, function (msg) {
diff --git a/addons/mail/static/tests/discuss_tests.js b/addons/mail/static/tests/discuss_tests.js
index 5f903898255..4a8dc9c56a3 100644
--- a/addons/mail/static/tests/discuss_tests.js
+++ b/addons/mail/static/tests/discuss_tests.js
@@ -61,6 +61,18 @@ QUnit.module('Discuss', {
},
},
},
+ 'res.partner': {
+ fields: {
+ im_status: {
+ string: "status",
+ type: 'char',
+ },
+ },
+ records: [{
+ id: 1,
+ im_status: 'online',
+ }]
+ },
};
this.services = mailTestUtils.getMailServices();
},
diff --git a/addons/mail/static/tests/mail_status_manager_tests.js b/addons/mail/static/tests/mail_status_manager_tests.js
new file mode 100644
index 00000000000..f2056bf2280
--- /dev/null
+++ b/addons/mail/static/tests/mail_status_manager_tests.js
@@ -0,0 +1,213 @@
+odoo.define('mail.mailStatusServiceTests', function (require) {
+"use strict";
+
+var mailTestUtils = require('mail.testUtils');
+
+var core = require('web.core');
+var testUtils = require('web.test_utils');
+var Widget = require('web.Widget');
+
+var QWeb = core.qweb;
+
+QUnit.module('mail', {}, function () {
+QUnit.module('service', {}, function () {
+QUnit.module('Status manager', {
+ beforeEach: function () {
+ var self = this;
+ this.services = mailTestUtils.getMailServices();
+ this.timeouts = {};
+ this.countTimeout = 0;
+ this.defaultPatchData = {
+ _clearStatusServiceTimeout: function (id) {
+ self.timeouts[id] = false;
+ },
+ _setStatusServiceTimeout: function (func, duration) {
+ var id = self.countTimeout;
+ self.timeouts[id] = func;
+ self.countTimeout++;
+ return id;
+ },
+ _updateImStatusLoop: function () {}, // avoid spam and infinite loop
+ };
+ this.patchMailService = function (patch) {
+ testUtils.mock.patch(this.services.mail_service, _.extend({}, this.defaultPatchData, patch));
+ };
+ this.resolveTimeouts = function () {
+ var timeouts = _.extend({}, self.timeouts);
+ self.timeouts = {}; // empty timeout before looping to avoid to remove loop timeout
+ _.each(_.values(timeouts), function (func) {
+ if (func !== false) {
+ func();
+ }
+ });
+ };
+ },
+ afterEach: function () {
+ testUtils.mock.unpatch(this.services.mail_service);
+ },
+});
+QUnit.test('simple set im_status', function (assert) {
+ assert.expect(1);
+ this.patchMailService();
+ var parent = testUtils.createParent({
+ services: this.services,
+ mockRPC: function (route, args) {
+ if (route === '/mail/init_messaging') {
+ return this._super.apply(this, arguments);
+ }
+ throw new Error(_.str.sprintf('No rpc call should be performed: %s, %s \n %s', args.model, args.method, route));
+ },
+ });
+ parent.call('mail_service', 'updateImStatus', [{
+ id: 1,
+ im_status: 'online',
+ }]);
+ assert.strictEqual(parent.call('mail_service', 'getImStatus', { partnerID: 1 }), 'online');
+
+ this.resolveTimeouts(); // alway resolve timeout
+ parent.destroy();
+});
+
+QUnit.test('multi get_im_status', function (assert) {
+ assert.expect(9);
+ this.patchMailService();
+ var readCount = 0;
+ var parent = testUtils.createParent({
+ //data: this.data,
+ services: this.services,
+ mockRPC: function (route, args) {
+ if (route === '/mail/init_messaging') {
+ return this._super.apply(this, arguments);
+ }
+ if (args.method === 'read' && args.model === 'res.partner') {
+ assert.deepEqual(args.args[0], [2,3]);
+ assert.deepEqual(args.args[1], ['id', 'im_status']);
+ readCount++;
+ return $.when([
+ {id: 2, im_status: 'away'},
+ {id: 3, im_status: 'im_partner'}
+ ]);
+ }
+ throw new Error(_.str.sprintf('No rpc call should be performed: %s, %s \n %s', args.model, args.method, route));
+ },
+ });
+ parent.call('mail_service', 'updateImStatus', [{
+ id: 1,
+ im_status: 'online',
+ }]);
+ assert.strictEqual(parent.call('mail_service', 'getImStatus', { partnerID: 1 }), 'online');
+ assert.strictEqual(parent.call('mail_service', 'getImStatus', { partnerID: 2 }), undefined);
+ assert.strictEqual(parent.call('mail_service', 'getImStatus', { partnerID: 3 }), undefined);
+
+ this.resolveTimeouts();
+ assert.strictEqual(parent.call('mail_service', 'getImStatus', { partnerID: 1 }), 'online');
+ assert.strictEqual(parent.call('mail_service', 'getImStatus', { partnerID: 2 }), 'away');
+ assert.strictEqual(parent.call('mail_service', 'getImStatus', { partnerID: 3 }), 'im_partner');
+
+ this.resolveTimeouts();
+ assert.strictEqual(readCount, 1, 'Only one read on partner should have been performed');
+
+ parent.destroy();
+});
+
+QUnit.test('update loop', function (assert) {
+ assert.expect(11);
+ delete this.defaultPatchData['_updateImStatusLoop']; // we want to test default behaviour of loop
+ this.patchMailService();
+ var readCount = 0;
+ var parent = testUtils.createParent({
+ services: this.services,
+ mockRPC: function (route, args) {
+ if (route === '/mail/init_messaging') {
+ return this._super.apply(this, arguments);
+ }
+ if (args.method === 'read' && args.model === 'res.partner') {
+ assert.deepEqual(args.args[0], [1, 2]);
+ assert.deepEqual(args.args[1], ['id', 'im_status']);
+ readCount++;
+ return $.when([
+ {"id": 1, "im_status": "online"},
+ {"id": 2, "im_status": "away"}
+ ]);
+ }
+ throw new Error(_.str.sprintf('No rpc call should be performed: %s, %s \n %s', args.model, args.method, route));
+ },
+ });
+ // set initial status
+ parent.call('mail_service', 'updateImStatus', [
+ { id: 1, im_status: 'offline' },
+ { id: 2, im_status: 'offline' },
+ { id: 3, im_status: 'im_partner' }, //shouldn't be updated !!!!
+ ]);
+ //_updateImStatusLoop should be running at one second per iteration, lets make a minute pass.
+ // this test is strongly dependant on the fact that the update loop as 1 second tick and update every 50 seconds.
+ assert.strictEqual(readCount, 0);
+ for (var i = 0; i < 50; i++){
+ this.resolveTimeouts();
+ }
+ assert.strictEqual(readCount, 0);
+ this.resolveTimeouts(); //one more second pass
+ assert.strictEqual(readCount, 1, 'one call should have been made after 50 seconds' );
+ assert.strictEqual(parent.call('mail_service', 'getImStatus', { partnerID: 1 }), 'online');
+ assert.strictEqual(parent.call('mail_service', 'getImStatus', { partnerID: 2 }), 'away');
+
+ //simulate change of focus
+ //original listener: $(window).on("blur", this._onWindowFocusChange.bind(this, false); + unload, ...
+ parent.call('mail_service', '_onWindowFocusChange', false); // remove focus from tab
+ for (var i = 0; i < 200; i++){ // a lot of time has passed
+ this.resolveTimeouts();
+ }
+ assert.strictEqual(readCount, 1, 'No more call should have been performed');
+
+ //simulate change of focus
+ //original listener: $(window).on("focus", this._onWindowFocusChange.bind(this, true);
+ parent.call('mail_service', '_onWindowFocusChange', true); // give focus to tab
+ this.resolveTimeouts(); //one more second pass
+ assert.strictEqual(readCount, 2, 'One more call should have been done once tab focused');
+ parent.destroy();
+});
+
+QUnit.test('update status', function (assert) {
+ // the current solution to look for updatable im_status in dom is not perfect, but waiting for
+ // ability to include widgets in views, this is the most simple solution
+ assert.expect(2);
+ this.patchMailService();
+ var StatusWidget = Widget.extend({
+ start: function () {
+ this.render();
+ this._super.apply(this, arguments);
+ },
+ render: function () {
+ var status = QWeb.render('mail.UserStatus', {
+ status: 'online',
+ partnerID: 1,
+ });
+ this.$el.html(status);
+ }
+ });
+ var statusWidget = new StatusWidget();
+ testUtils.mock.addMockEnvironment(statusWidget, {
+ services: this.services,
+ mockRPC: function (route, args) {
+ if (route === '/mail/init_messaging') {
+ return this._super.apply(this, arguments);
+ }
+ throw new Error(_.str.sprintf('No rpc call should be performed: %s, %s \n %s', args.model, args.method, route));
+ },
+ });
+ statusWidget.appendTo($('#qunit-fixture'));
+ //Render unknow im_status:
+ // set initial status
+ assert.ok(statusWidget.$('.o_updatable_im_status i').hasClass('o_user_online'));
+
+ statusWidget.call('mail_service', 'updateImStatus', [
+ { id: 1, im_status: 'offline' },
+ ]);
+ assert.notOk(statusWidget.$('.o_updatable_im_status i').hasClass('o_user_online'));
+ this.resolveTimeouts();
+ statusWidget.destroy();
+});
+
+});
+});
+});