[FIX] mail: no wait init_messaging before loading webclient

Before this commit, discuss widgets had to wait for the route
`mail/init_messaging`. This behaviour slowed down the loading of the
web client.

This commit solves the issue by rendering discuss widgets even when
`mail/init_messaging` hasn't been resolved. For instance, the
messaging menu displays a spinner until messaging becomes ready.

Task-Id 2072835

closes odoo/odoo#37031

Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
Alexandre Kühn
2019-09-18 10:07:20 +00:00
parent 3dd2e7c2ef
commit 9a66619db3
11 changed files with 241 additions and 76 deletions
+78 -56
View File
@@ -281,6 +281,8 @@ var Discuss = AbstractAction.extend({
this.options.channelQuickSearchThreshold = 20;
}
this._isMessagingReady = this.call('mail_service', 'isReady');
this._isStarted = false;
this._threadsScrolltop = {};
this._composerStates = {};
this._defaultThreadID = this.options.active_id ||
@@ -292,68 +294,20 @@ var Discuss = AbstractAction.extend({
this._updateThreads.bind(this), 100, { leading: false });
this.controlPanelParams.modelName = 'mail.message';
},
/**
* @override
*/
willStart: function () {
return Promise.all([this._super(), this.call('mail_service', 'isReady')]);
this.call('mail_service', 'getMailBus').on('messaging_ready', this, this._onMessagingReady);
},
/**
* @override
*/
start: function () {
var self = this;
this._basicComposer = new BasicComposer(this, {
mentionPartnersRestricted: true,
showTyping: true,
this._isStarted = true;
return this._super.apply(this, arguments).then(function () {
if (!self._isMessagingReady) {
return;
}
return self._initRender();
});
this._extendedComposer = new ExtendedComposer(this, {
mentionPartnersRestricted: true,
showTyping: true,
});
this._basicComposer
.on('post_message', this, this._onPostMessage)
.on('input_focused', this, this._onComposerFocused);
this._extendedComposer
.on('post_message', this, this._onPostMessage)
.on('input_focused', this, this._onComposerFocused);
this._renderButtons();
var defs = [];
defs.push(
this._renderThread()
);
defs.push(
this._basicComposer.appendTo(this.$('.o_mail_discuss_content')));
defs.push(
this._extendedComposer.appendTo(this.$('.o_mail_discuss_content')));
defs.push(this._super.apply(this, arguments));
return Promise.all(defs)
.then(function () {
return self._setThread(self._defaultThreadID);
})
.then(function () {
self._initThreads();
self._startListening();
self._threadWidget.$el.on('scroll', null, _.debounce(function () {
if (
self._threadWidget.getScrolltop() < 20 &&
!self._threadWidget.$('.o_mail_no_content').length &&
!self._thread.isAllHistoryLoaded(self.domain)
) {
self._loadMoreMessages();
}
if (
self._threadWidget.isAtBottom() &&
self._thread.getType() !== 'mailbox'
) {
self._thread.markAsRead();
}
}, 100));
});
},
/**
* @override
@@ -382,8 +336,8 @@ var Discuss = AbstractAction.extend({
this.call('mail_service', 'getMailBus').trigger('discuss_open', true);
if (this._thread) {
this._threadWidget.scrollToPosition(this._threadsScrolltop[this._thread.getID()]);
this._loadEnoughMessages();
}
this._loadEnoughMessages();
},
/**
* @override
@@ -547,6 +501,60 @@ var Discuss = AbstractAction.extend({
}
}
},
/**
* @private
*/
_initRender: function () {
var self = this;
this._basicComposer = new BasicComposer(this, {
mentionPartnersRestricted: true,
showTyping: true,
});
this._extendedComposer = new ExtendedComposer(this, {
mentionPartnersRestricted: true,
showTyping: true,
});
this._basicComposer
.on('post_message', this, this._onPostMessage)
.on('input_focused', this, this._onComposerFocused);
this._extendedComposer
.on('post_message', this, this._onPostMessage)
.on('input_focused', this, this._onComposerFocused);
this._renderButtons();
var defs = [];
defs.push(
this._renderThread()
);
defs.push(
this._basicComposer.appendTo(this.$('.o_mail_discuss_content')));
defs.push(
this._extendedComposer.appendTo(this.$('.o_mail_discuss_content')));
return Promise.all(defs)
.then(function () {
return self._setThread(self._defaultThreadID);
})
.then(function () {
self._initThreads();
self._startListening();
self._threadWidget.$el.on('scroll', null, _.debounce(function () {
if (
self._threadWidget.getScrolltop() < 20 &&
!self._threadWidget.$('.o_mail_no_content').length &&
!self._thread.isAllHistoryLoaded(self.domain)
) {
self._loadMoreMessages();
}
if (
self._threadWidget.isAtBottom() &&
self._thread.getType() !== 'mailbox'
) {
self._thread.markAsRead();
}
}, 100));
});
},
/**
* Renders the mainside bar with current threads
*
@@ -1401,6 +1409,20 @@ var Discuss = AbstractAction.extend({
this._fetchAndRenderThread();
}
},
/**
* @private
*/
_onMessagingReady: function () {
if (this._isMessagingReady) {
return;
}
this._isMessagingReady = true;
if (!this._isStarted) {
return;
}
this.$('.o_mail_discuss_loading').remove();
this._initRender();
},
/**
* @private
* @param {MouseEvent} ev
@@ -34,7 +34,7 @@ MailManager.include({
this.call('local_storage', 'getItem', key);
}
}
this.isReady().then(this._updateDocumentThreadWindows.bind(this, this._documentThreadWindowStates));
this._mailBus.on('messaging_ready', this, this._updateDocumentThreadWindows.bind(this, this._documentThreadWindowStates));
// listen to localStorage changes to synchronize DocumentThread's
// windows between tabs
this.call('local_storage', 'onStorage', this, this._onStorage);
@@ -46,6 +46,7 @@ var PREVIEW_MSG_MAX_SIZE = 350; // optimal for native english speakers
var MailManager = AbstractService.extend({
dependencies: ['ajax', 'bus_service', 'local_storage'],
_isReady: false,
_ODOOBOT_ID: ["ODOOBOT", "ODOOBOT"], // authorID for transient messages
/**
@@ -294,7 +295,7 @@ var MailManager = AbstractService.extend({
* This is the case when it has fetched the initial state from the server,
* by means of the route 'mail/init_messaging'
*
* @returns {Promise}
* @returns {boolean}
*/
isReady: function () {
return this._isReady;
@@ -636,7 +637,7 @@ var MailManager = AbstractService.extend({
*/
_fetchMailStateFromServer: function () {
var self = this;
this._isReady = session.is_bound.then(function () {
session.is_bound.then(function () {
var context = _.extend(
{ isMobile: config.device.isMobile },
session.user_context
@@ -648,6 +649,8 @@ var MailManager = AbstractService.extend({
}).then(function (result) {
self._updateInternalStateFromServer(result);
self.call('bus_service', 'startPolling');
self._isReady = true;
self._mailBus.trigger('messaging_ready');
});
},
/**
@@ -19,9 +19,6 @@ var ActivityMenu = Widget.extend({
'click .o_mail_preview': '_onActivityFilterClick',
'show.bs.dropdown': '_onActivityMenuShow',
},
willStart: function () {
return Promise.resolve(this.call('mail_service', 'isReady'));
},
start: function () {
this._$activitiesPreview = this.$('.o_mail_systray_dropdown_items');
this.call('mail_service', 'getMailBus').on('activity_updated', this, this._updateCounter);
@@ -26,12 +26,6 @@ var MessagingMenu = Widget.extend({
'click .o_mail_preview_mark_as_read': '_onClickPreviewMarkAsRead',
'show.bs.dropdown': '_onShowDropdown',
},
/**
* @override
*/
willStart: function () {
return Promise.all([this._super.apply(this, arguments), this.call('mail_service', 'isReady')]);
},
/**
* @override
*/
@@ -39,8 +33,10 @@ var MessagingMenu = Widget.extend({
this._$filterButtons = this.$('.o_filter_button');
this._$previews = this.$('.o_mail_systray_dropdown_items');
this._filter = false;
this._isMessagingReady = this.call('mail_service', 'isReady');
this._updateCounter();
var mailBus = this.call('mail_service', 'getMailBus');
mailBus.on('messaging_ready', this, this._onMessagingReady);
mailBus.on('update_needaction', this, this._updateCounter);
mailBus.on('new_channel', this, this._updateCounter);
mailBus.on('update_thread_unread_counter', this, this._updateCounter);
@@ -206,6 +202,9 @@ var MessagingMenu = Widget.extend({
_updatePreviews: function () {
// Display spinner while waiting for conversations preview
this._$previews.html(QWeb.render('Spinner'));
if (!this._isMessagingReady) {
return;
}
this._getPreviews()
.then(this._renderPreviews.bind(this));
},
@@ -228,7 +227,11 @@ var MessagingMenu = Widget.extend({
* @private
*/
_updateCounter: function () {
if (!this._isMessagingReady) {
return;
}
var counter = this._computeCounter();
this.$('.o_mail_messaging_menu_icon').removeClass('fa-spinner fa-spin');
this.$('.o_notification_counter').text(counter);
this.$el.toggleClass('o_no_notification', !counter);
if (this._isShown()) {
@@ -324,6 +327,16 @@ var MessagingMenu = Widget.extend({
}
}
},
/**
* @private
*/
_onMessagingReady: function () {
if (this._isMessagingReady) {
return;
}
this._isMessagingReady = true;
this._updateCounter();
},
});
// Systray menu items display order matches order in the list
+20 -6
View File
@@ -20,23 +20,21 @@ var ThreadField = AbstractField.extend({
*/
init: function () {
this._super.apply(this, arguments);
this._isMessagingReady = this.call('mail_service', 'isReady');
this._isStarted = false;
this.call('mail_service', 'getMailBus').on('messaging_ready', this, this._onMessagingReady);
// Used to automatically mark document thread as read at the moment we
// access the document and render the thread.
this._markAsReadOnNextRender = false;
this._setDocumentThread();
},
/**
* @override
*/
willStart: function () {
return this.alive(this.call('mail_service', 'isReady'));
},
/**
* @override
*/
start: function () {
var self = this;
this._isStarted = true;
this.dp = new concurrency.DropPrevious();
this._threadWidget = new ThreadWidget(this, {
@@ -159,6 +157,9 @@ var ThreadField = AbstractField.extend({
* If it is a new document in create mode, unset the document thread.
*/
_setDocumentThread: function () {
if (!this._isMessagingReady) {
return;
}
var params = {
messageIDs: this.value.res_ids,
name: this.recordData.display_name,
@@ -185,6 +186,19 @@ var ThreadField = AbstractField.extend({
_onLoadMoreMessages: function () {
this._fetchAndRenderThread({ forceFetch: true });
},
/**
* @private
*/
_onMessagingReady: function () {
if (this._isMessagingReady) {
return;
}
this._isMessagingReady = true;
if (!this._isStarted) {
return;
}
this._setDocumentThread();
},
/**
* @private
* @param {mail.model.Message}
+12
View File
@@ -7,6 +7,18 @@
height: 100%;
overflow: hidden;
.o_mail_discuss_loading {
display: flex;
flex: 1 1 auto;
align-items: center;
justify-content: center;
min-height: 100%;
> i {
margin-right: 5px;
}
}
.o_mail_discuss_sidebar {
width: $o-mail-chat-sidebar-width;
height: 100%;
+8 -1
View File
@@ -8,8 +8,15 @@
-->
<t t-name="mail.discuss">
<div class="o_mail_discuss">
<div class="o_mail_discuss_sidebar"/>
<div class="o_mail_discuss_sidebar">
<div class="o_mail_discuss_loading">
<i class="fa fa-spinner fa-spin"/> Please wait...
</div>
</div>
<div class="o_mail_discuss_content">
<div class="o_mail_discuss_loading">
<i class="fa fa-spinner fa-spin"/> Please wait...
</div>
</div>
</div>
</t>
+1 -1
View File
@@ -7,7 +7,7 @@
<t t-name="mail.systray.MessagingMenu">
<li class="o_mail_systray_item">
<a class="dropdown-toggle o-no-caret" data-toggle="dropdown" data-display="static" aria-expanded="false" title="Conversations" href="#" role="button">
<i class="fa fa-comments" role="img" aria-label="Messages"/> <span class="o_notification_counter badge badge-pill"/>
<i class="o_mail_messaging_menu_icon fa fa-comments fa-spinner fa-spin" role="img" aria-label="Messages"/> <span class="o_notification_counter badge badge-pill"/>
</a>
<div class="o_mail_systray_dropdown dropdown-menu dropdown-menu-right" role="menu">
<t t-if="widget.isMobile()">
+50
View File
@@ -115,6 +115,56 @@ QUnit.test('basic rendering', async function (assert) {
discuss.destroy();
});
QUnit.test('messaging not ready', async function (assert) {
assert.expect(9);
const messagingReadyProm = testUtils.makeTestPromise();
const discuss = await createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
async mockRPC(route, args) {
if (route === '/mail/init_messaging') {
const _super = this._super.bind(this, ...arguments); // limitation of class.js
assert.step('/mail/init_messaging:pending');
await messagingReadyProm;
assert.step('/mail/init_messaging:resolved');
return _super();
}
return this._super(...arguments);
},
});
assert.verifySteps(['/mail/init_messaging:pending']);
assert.ok(
discuss.el,
"discuss should be rendered");
assert.strictEqual(
$('.o_action')[0],
discuss.el,
"should display discuss even when messaging is not ready");
assert.containsOnce(
discuss,
'.o_mail_discuss_sidebar .o_mail_discuss_loading',
"should display sidebar is loading (messaging not yet ready)");
assert.containsOnce(
discuss,
'.o_mail_discuss_content .o_mail_discuss_loading',
"should display content is loading (messaging not yet ready)");
messagingReadyProm.resolve();
await testUtils.nextTick();
assert.verifySteps(['/mail/init_messaging:resolved']);
assert.containsNone(
discuss,
'.o_mail_discuss_loading',
"should no longer display sidebar or content is loading (messaging is ready)");
discuss.destroy();
});
QUnit.test('searchview options visibility', async function (assert) {
assert.expect(5);
@@ -120,6 +120,53 @@ QUnit.test('messaging menu widget: menu with no records', async function (assert
messagingMenu.destroy();
});
QUnit.test('messaging menu widget: messaging not ready', async function (assert) {
assert.expect(8);
const messagingReadyProm = testUtils.makeTestPromise();
const messagingMenu = new MessagingMenu();
testUtils.mock.addMockEnvironment(messagingMenu, {
services: this.services,
async mockRPC(route, args) {
if (route === '/mail/init_messaging') {
const _super = this._super.bind(this, ...arguments); // limitation of class.js
assert.step('/mail/init_messaging:pending');
await messagingReadyProm;
assert.step('/mail/init_messaging:resolved');
return _super();
}
if (args.method === 'message_fetch') {
return [];
}
return this._super(...arguments);
},
});
await messagingMenu.appendTo($('#qunit-fixture'));
assert.verifySteps(['/mail/init_messaging:pending']);
assert.ok(
messagingMenu.el,
"messaging menu should be rendered");
assert.strictEqual(
document.querySelector('.o_mail_systray_item'),
messagingMenu.el,
"should display messaging menu even when messaging is not ready");
assert.hasClass(
messagingMenu.$('.o_mail_messaging_menu_icon'),
'fa-spinner',
"Messaging menu icon should be a spinner when messaging menu is not ready");
messagingReadyProm.resolve();
await testUtils.nextTick();
assert.verifySteps(['/mail/init_messaging:resolved']);
assert.doesNotHaveClass(
messagingMenu.$('.o_mail_messaging_menu_icon'),
'fa-spinner',
"Messaging menu icon should no longer be a spinner when messaging menu is ready");
messagingMenu.destroy();
});
QUnit.test('messaging menu widget: messaging menu with 1 record', async function (assert) {
assert.expect(3);
var messagingMenu = new MessagingMenu();