From f80bbb4b2a1230ba543e97d3da01c7b2c52d24cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20K=C3=BChn?= Date: Thu, 30 Aug 2018 16:10:20 +0200 Subject: [PATCH] [IMP] mail, mail_bot: improved push notifications permission response This commit makes small improvements on the messaging menu with the new module `mail_bot`: [IMP] When OdooBot has a request, it now adds "+1" to the counter of the messaging menu. The related preview also has a bold title, and displays "(1)" next to the title. [REF] Static previews are no longer in mail_service. This was only used for "OdooBot has a request" preview. This logic has now been moved to the module `mail_bot` (was previously in `mail`). [FIX] Title of response of push notifications permission clearly states whether permission has been granted or denied (was displaying "[Object object]" before this commit). Task-ID 1877502 --- .../static/src/js/services/mail_manager.js | 33 +---- .../src/js/systray/systray_messaging_menu.js | 72 +++++------ .../tests/document_thread_window_tests.js | 5 - ...stray_messaging_menu_mail_failure_tests.js | 3 - .../systray/systray_messaging_menu_tests.js | 3 - .../mail_bot/static/src/js/mailbot_service.js | 36 +++++- .../static/src/js/systray_messaging_menu.js | 121 ++++++++++++++++++ .../tests/systray_messaging_menu_tests.js | 107 ++++++++++++++++ addons/mail_bot/views/assets.xml | 6 + 9 files changed, 303 insertions(+), 83 deletions(-) create mode 100644 addons/mail_bot/static/src/js/systray_messaging_menu.js create mode 100644 addons/mail_bot/static/tests/systray_messaging_menu_tests.js diff --git a/addons/mail/static/src/js/services/mail_manager.js b/addons/mail/static/src/js/services/mail_manager.js index a9cbf7d1072..734f12f0cf3 100644 --- a/addons/mail/static/src/js/services/mail_manager.js +++ b/addons/mail/static/src/js/services/mail_manager.js @@ -46,7 +46,6 @@ var PREVIEW_MSG_MAX_SIZE = 350; // optimal for native english speakers var MailManager = AbstractService.extend({ dependencies: ['ajax', 'bus_service', 'local_storage'], _ODOOBOT_ID: "ODOOBOT", // default authorID for transient messages - IS_STATIC_PREVIEW_ENABLED: true, /** * @override @@ -232,17 +231,13 @@ var MailManager = AbstractService.extend({ var inboxDef = this._getSystrayInboxPreviews(filter); var failureDef = this._getSystrayMailFailurePreviews(filter); defs = defs.concat([channelDef, inboxDef, failureDef]); - if (this.IS_STATIC_PREVIEW_ENABLED) { - var staticDef = this._getSystrayStaticPreviews(filter); - defs.push(staticDef); - } return $.when.apply($, defs) - .then(function (previewsChannel, previewsInbox, previewsFailure, previewsStatic) { + .then(function (previewsChannel, previewsInbox, previewsFailure) { // order: failures > inbox > channel, each group must be sorted previewsChannel = self._sortPreviews(previewsChannel); previewsInbox = self._sortPreviews(previewsInbox); previewsFailure = self._sortPreviews(previewsFailure); - return _.union(previewsStatic, previewsFailure, previewsInbox, previewsChannel); + return _.union(previewsFailure, previewsInbox, previewsChannel); }); }, /** @@ -816,30 +811,6 @@ var MailManager = AbstractService.extend({ return $.when([]); } }, - /** - * Get the previews of static systray elements, adding notification request - * - * @private - * @param {string|undefined} [filter] - * @returns {$.Promise} resolved with list of object that are - * compatible with preview template of mail systray menu - */ - _getSystrayStaticPreviews: function (filter) { - var elems = []; - if ( - window.Notification && window.Notification.permission === "default" && - (filter === 'mailbox_inbox' || !filter) - ) { - elems.push({ - title: _t("OdooBot has a request"), - imageSRC: "/mail/static/src/img/odoobot.png", - status: 'bot', - body: _t("Enable desktop notifications to chat"), - id: 'request_notification', - }); - } - return $.when(elems); - }, /** * Initialize the internal state of the mail service. Ensure that all * attributes are set before doing any operation on them. diff --git a/addons/mail/static/src/js/systray/systray_messaging_menu.js b/addons/mail/static/src/js/systray/systray_messaging_menu.js index 4fc3e9a4940..5a687131504 100644 --- a/addons/mail/static/src/js/systray/systray_messaging_menu.js +++ b/addons/mail/static/src/js/systray/systray_messaging_menu.js @@ -7,8 +7,6 @@ var SystrayMenu = require('web.SystrayMenu'); var Widget = require('web.Widget'); var QWeb = core.qweb; -var _t = core._t; - /** * Menu item appended in the systray part of the navbar * @@ -91,6 +89,35 @@ var MessagingMenu = Widget.extend({ }); } }, + /** + * Compute the counter next to the systray messaging menu. This counter is + * the sum of unread messages in channels, the counter of the mailbox inbox, + * and the amount of mail failures. + * + * @private + * @returns {integer} + */ + _computeCounter: function () { + var channels = this.call('mail_service', 'getChannels'); + var channelUnreadCounters = _.map(channels, function (channel) { + return channel.getUnreadCounter(); + }); + var unreadChannelCounter = _.reduce(channelUnreadCounters, function (c1, c2) { + return c1 + c2; + }, 0); + var inboxCounter = this.call('mail_service', 'getMailbox', 'inbox').getMailboxCounter(); + var mailFailureCounter = this.call('mail_service', 'getMailFailures').length; + + return unreadChannelCounter + inboxCounter + mailFailureCounter; + }, + /** + * @private + * @returns {$.Promise} resolved with list of previews that are + * compatible with the 'mail.Preview' template. + */ + _getPreviews: function () { + return this.call('mail_service', 'getSystrayPreviews', this._filter); + }, /** * @private * @return {boolean} whether the messaging menu is shown or not. @@ -152,27 +179,6 @@ var MessagingMenu = Widget.extend({ previews: previews, })); }, - /** - * Display the browser notification request dialog when the user clicks on systray's corresponding notification - * - * @private - */ - _requestNotificationPermission: function () { - var self = this; - var def = window.Notification && window.Notification.requestPermission(); - if (def) { - def.then(function (value) { - if (value !== 'granted') { - self.call('bus_service', 'sendNotification', self, _t('Permission denied'), - _t('Odoo will not have the permission to send native notifications on this device.')); - } else { - self.call('bus_service', 'sendNotification', self, _t('Permission granted'), - _t('Odoo has now the permission to send you native notifications on this device.')); - } - }); - } - this.$(".o_mail_navbar_request_permission").slideUp(); - }, /** * Get and render list of previews, based on the selected filter * @@ -200,7 +206,7 @@ var MessagingMenu = Widget.extend({ _updatePreviews: function () { // Display spinner while waiting for conversations preview this._$previews.html(QWeb.render('Spinner')); - this.call('mail_service', 'getSystrayPreviews', this._filter) + this._getPreviews() .then(this._renderPreviews.bind(this)); }, /** @@ -222,19 +228,7 @@ var MessagingMenu = Widget.extend({ * @private */ _updateCounter: function () { - var counter; - - var channels = this.call('mail_service', 'getChannels'); - var channelUnreadCounters = _.map(channels, function (channel) { - return channel.getUnreadCounter(); - }); - var unreadChannelCounter = _.reduce(channelUnreadCounters, function (c1, c2) { - return c1 + c2; - }, 0); - var inboxCounter = this.call('mail_service', 'getMailbox', 'inbox').getMailboxCounter(); - var mailFailureCounter = this.call('mail_service', 'getMailFailures').length; - - counter = unreadChannelCounter + inboxCounter + mailFailureCounter; + var counter = this._computeCounter(); this.$('.o_notification_counter').text(counter); this.$el.toggleClass('o_no_notification', !counter); if (this._isShown()) { @@ -289,8 +283,6 @@ var MessagingMenu = Widget.extend({ var documentID = $target.data('document-id'); var documentModel = $target.data('document-model'); this._openDocument(documentModel, documentID); - } else if (previewID === 'request_notification') { - this._requestNotificationPermission(); } else { // preview of thread this.call('mail_service', 'openThread', previewID); @@ -301,7 +293,7 @@ var MessagingMenu = Widget.extend({ * as read * * @private - * @param {MouseEvent} event + * @param {MouseEvent} ev */ _onClickPreviewMarkAsRead: function (ev) { ev.stopPropagation(); diff --git a/addons/mail/static/tests/document_thread_window_tests.js b/addons/mail/static/tests/document_thread_window_tests.js index a14b2ebc159..d7064b8695d 100644 --- a/addons/mail/static/tests/document_thread_window_tests.js +++ b/addons/mail/static/tests/document_thread_window_tests.js @@ -84,12 +84,7 @@ QUnit.module('Document Thread', { partner_id: partnerID, // so that needaction messages are treated as needactions }; this.services = mailTestUtils.getMailServices(); - this.MailService = this.services.mail_service; - this.MailService.prototype.IS_STATIC_PREVIEW_ENABLED = false; }, - afterEach: function () { - this.MailService.prototype.IS_STATIC_PREVIEW_ENABLED = true; - } }); QUnit.test('open a document thread in a thread window', function (assert) { diff --git a/addons/mail/static/tests/systray/systray_messaging_menu_mail_failure_tests.js b/addons/mail/static/tests/systray/systray_messaging_menu_mail_failure_tests.js index 0ef6679c675..c1fc1bd8dae 100644 --- a/addons/mail/static/tests/systray/systray_messaging_menu_mail_failure_tests.js +++ b/addons/mail/static/tests/systray/systray_messaging_menu_mail_failure_tests.js @@ -58,11 +58,8 @@ QUnit.module('MessagingMenu (Mail Failures)', { }; this.services = mailTestUtils.getMailServices(); - this.MailService = this.services.mail_service; - this.MailService.prototype.IS_STATIC_PREVIEW_ENABLED = false; }, afterEach: function () { - this.MailService.prototype.IS_STATIC_PREVIEW_ENABLED = true; // unpatch _.debounce and _.throttle _.debounce = this.underscoreDebounce; _.throttle = this.underscoreThrottle; diff --git a/addons/mail/static/tests/systray/systray_messaging_menu_tests.js b/addons/mail/static/tests/systray/systray_messaging_menu_tests.js index 16458550406..ca0c889039b 100644 --- a/addons/mail/static/tests/systray/systray_messaging_menu_tests.js +++ b/addons/mail/static/tests/systray/systray_messaging_menu_tests.js @@ -93,11 +93,8 @@ QUnit.module('MessagingMenu', { }, }; this.services = mailTestUtils.getMailServices(); - this.MailService = this.services.mail_service; - this.MailService.prototype.IS_STATIC_PREVIEW_ENABLED = false; }, afterEach: function () { - this.MailService.prototype.IS_STATIC_PREVIEW_ENABLED = true; // unpatch _.debounce and _.throttle _.debounce = this.underscoreDebounce; _.throttle = this.underscoreThrottle; diff --git a/addons/mail_bot/static/src/js/mailbot_service.js b/addons/mail_bot/static/src/js/mailbot_service.js index 300e95d06db..a75780ab090 100644 --- a/addons/mail_bot/static/src/js/mailbot_service.js +++ b/addons/mail_bot/static/src/js/mailbot_service.js @@ -5,8 +5,9 @@ var AbstractService = require('web.AbstractService'); var core = require('web.core'); var session = require('web.session'); +var _t = core._t; + var MailBotService = AbstractService.extend({ - name: 'mailbot_service', /** * @override */ @@ -22,7 +23,40 @@ var MailBotService = AbstractService.extend({ }, 2*60*1000); } }, + /** + * Get the previews related to the OdooBot (conversation not included). + * For instance, when there is no conversation with OdooBot and OdooBot has + * a request, it should display a preview in the systray messaging menu. + * + * @param {string|undefined} [filter] + * @returns {Object[]} list of objects that are compatible with the + * 'mail.Preview' template. + */ + getPreviews: function (filter) { + var previews = []; + if (this.hasRequest() && (filter === 'mailbox_inbox' || !filter)) { + previews.push({ + title: _t("OdooBot has a request"), + imageSRC: "/mail/static/src/img/odoobot.png", + status: 'bot', + body: _t("Enable desktop notifications to chat"), + id: 'request_notification', + unreadCounter: 1, + }); + } + return previews; + }, + /** + * Tell whether OdooBot has a request or not. + * + * @returns {boolean} + */ + hasRequest: function () { + return window.Notification && window.Notification.permission === "default"; + }, }); + core.serviceRegistry.add('mailbot_service', MailBotService); return MailBotService; + }); diff --git a/addons/mail_bot/static/src/js/systray_messaging_menu.js b/addons/mail_bot/static/src/js/systray_messaging_menu.js new file mode 100644 index 00000000000..05c0ee54f24 --- /dev/null +++ b/addons/mail_bot/static/src/js/systray_messaging_menu.js @@ -0,0 +1,121 @@ +odoo.define('mail_bot.systray.MessagingMenu', function (require) { +"use strict"; + +var MessagingMenu = require('mail.systray.MessagingMenu'); +var core = require('web.core'); + +var _t = core._t; + +return MessagingMenu.include({ + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Override so that 'mailbot has a request' is included in the computation + * of of the counter. + * + * @override + * @private + * @returns {integer} + */ + _computeCounter: function () { + var counter = this._super.apply(this, arguments); + if (this.call('mailbot_service', 'hasRequest')) { + counter++; + } + return counter; + }, + /** + * Override so that the mailbot previews are included in the systray + * messaging menu (e.g. 'OdooBot has a request') + * + * @override + * @private + * @returns {$.Promise} resolved with list of previews that are + * compatible with the 'mail.Preview' template. + */ + _getPreviews: function () { + var mailbotPreviews = this.call('mailbot_service', 'getPreviews', this._filter); + return this._super.apply(this, arguments).then(function (previews) { + return _.union(mailbotPreviews, previews); + }); + }, + /** + * Handle the response of the user when prompted whether push notifications + * are granted or denied. + * + * Also refreshes the counter after a response from a push notification + * request. This is useful because the counter contains a part for the + * OdooBot, and the OdooBot influences the counter by 1 when it requests + * for notifications. This should no longer be the case when push + * notifications are either granted or denied. + * + * @private + * @param {string} value + */ + _handleResponseNotificationPermission: function (value) { + if (value !== 'granted') { + this.call('bus_service', 'sendNotification', _t('Permission denied'), + _t('Odoo will not have the permission to send native notifications on this device.')); + } else { + this.call('bus_service', 'sendNotification', _t('Yay, push notifications are enabled!'), + _t('Odoo has now the permission to send you native notifications on this device.')); + } + this._updateCounter(); + }, + /** + * Display the browser notification request dialog when the user clicks on + * systray's corresponding notification + * + * @private + */ + _requestNotificationPermission: function () { + var def = window.Notification && window.Notification.requestPermission(); + if (def) { + def.then(this._handleResponseNotificationPermission.bind(this)); + } + this.$('.o_mail_navbar_request_permission').slideUp(); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * Override so that it handles preview related to OdooBot + * + * @override + * @private + * @param {MouseEvent} ev + */ + _onClickPreview: function (ev) { + var previewID = $(ev.currentTarget).data('preview-id'); + if (previewID === 'request_notification') { + this._requestNotificationPermission(); + } else { + this._super.apply(this, arguments); + } + }, + /** + * Override so that it handles clicking on 'mark as read' similarly to + * requesting push notification permission. + * + * @override + * @private + * @param {MouseEvent} ev + */ + _onClickPreviewMarkAsRead: function (ev) { + ev.stopPropagation(); + var $preview = $(ev.currentTarget).closest('.o_mail_preview'); + var previewID = $preview.data('preview-id'); + if (previewID === 'request_notification') { + this._requestNotificationPermission(); + } else { + this._super.apply(this, arguments); + } + } +}); + +}); diff --git a/addons/mail_bot/static/tests/systray_messaging_menu_tests.js b/addons/mail_bot/static/tests/systray_messaging_menu_tests.js new file mode 100644 index 00000000000..a04aba7056a --- /dev/null +++ b/addons/mail_bot/static/tests/systray_messaging_menu_tests.js @@ -0,0 +1,107 @@ +odoo.define('mail_bot.systray.MessagingMenuTests', function (require) { +"use strict"; + +var MessagingMenu = require('mail.systray.MessagingMenu'); +var mailTestUtils = require('mail.testUtils'); + +var MailBotService = require('mail_bot.MailBotService'); + +var testUtils = require('web.test_utils'); + +QUnit.module('mail_bot', {}, function () { +QUnit.module('MessagingMenu', { + beforeEach: function () { + var self = this; + + this.data = { + 'mail.channel': { + fields: {}, + records: [], + }, + 'mail.message': { + fields: {}, + records: [], + }, + }; + + // Patch mailbot_service so that it does do not do any RPC, and + // the detection of push notifications permissions can be simulated in + // the test cases. By default, shows 'OdooBot has a request' + this.hasMailbotRequest = true; + testUtils.patch(MailBotService, { + /** + * @override + */ + start: function () {}, + /** + * @override + * @returns {boolean} + */ + hasRequest: function () { + return self.hasMailbotRequest; + }, + }); + + this.services = _.extend({}, mailTestUtils.getMailServices(), { + mailbot_service: MailBotService + }); + }, + afterEach: function () { + // unpatch MailBotService + testUtils.unpatch(MailBotService); + } +}); + +QUnit.test('messaging menu widget: rendering with OdooBot has a request', function (assert) { + assert.expect(5); + + var messagingMenu = new MessagingMenu(); + testUtils.addMockEnvironment(messagingMenu, { + data: this.data, + services: this.services, + }); + messagingMenu.appendTo($('#qunit-fixture')); + + assert.strictEqual(messagingMenu.$('.o_notification_counter').length, 1, + "should display a notification counter next to the messaging menu"); + assert.strictEqual(messagingMenu.$('.o_notification_counter').text(), '1', + "should display a counter of '1' next to the messaging menu"); + + messagingMenu.$('.dropdown-toggle').click(); + assert.strictEqual(messagingMenu.$('.o_preview_info').length, 1, + "should display a preview in the messaging menu"); + assert.strictEqual(messagingMenu.$('.o_preview_name').text().trim(), + 'OdooBot has a request', + "preview should display that OdooBot has a request"); + assert.strictEqual(messagingMenu.$('.o_preview_counter').text().replace(/\s/g, ''), + '(1)', "should display an counter of '1' next to the preview"); + + messagingMenu.destroy(); +}); + +QUnit.test('messaging menu widget: rendering without OdooBot has a request', function (assert) { + assert.expect(3); + + this.hasMailbotRequest = false; + + var messagingMenu = new MessagingMenu(); + testUtils.addMockEnvironment(messagingMenu, { + data: this.data, + services: this.services, + }); + messagingMenu.appendTo($('#qunit-fixture')); + + assert.strictEqual(messagingMenu.$('.o_notification_counter').length, 1, + "should display a notification counter next to the messaging menu"); + assert.strictEqual(messagingMenu.$('.o_notification_counter').text(), '0', + "should display a counter of '0' next to the messaging menu"); + messagingMenu.$('.dropdown-toggle').click(); + assert.strictEqual(messagingMenu.$('.o_preview_info').length, 0, + "should display no preview in the messaging menu"); + + messagingMenu.destroy(); +}); + + +}); +}); diff --git a/addons/mail_bot/views/assets.xml b/addons/mail_bot/views/assets.xml index 134b54e931e..43c9a37e694 100644 --- a/addons/mail_bot/views/assets.xml +++ b/addons/mail_bot/views/assets.xml @@ -4,6 +4,12 @@ +