diff options
| author | Sébastien Theys <seb@odoo.com> | 2020-06-15 18:12:21 +0000 |
|---|---|---|
| committer | Sébastien Theys <seb@odoo.com> | 2020-06-15 18:12:21 +0000 |
| commit | 3fea5b2136627c1d7e83b2a5e111952fe3eb10a1 (patch) | |
| tree | 5eefad07972a87ce4689b83260f981bc87aadad4 /addons/mail_bot/static | |
| parent | 0a82848606151ef60eb6523e9c4c161171f214e7 (diff) | |
[REF] mail, *: refactor messaging with OWL
This commit is a significant rewriting of client-side discuss, chatter,
chat window, and messaging menu using OWL. The behavior should be broadly
the same, with some slight functional changes here and there.
From a technical standpoint, the code of messaging is mainly organized in 2
main groups of modules:
- models, which are logical entities that depict the client-side state of
messaging as a whole.
- components, which are in charge of displaying information from models.
This refactoring also introduces new JS guidelines regarding folder structure
(/static) and naming rules for JS modules.
Community PR: https://github.com/odoo/odoo/pull/39023
Enterprise PR: https://github.com/odoo/enterprise/pull/6249
Task-1914207
This PR is a collaborative work by Alexandre, Julien, Sébastien and Xavier,
with the precious help of Lucas to speed it up towards the end.
closes odoo/odoo#39023
Related: odoo/enterprise#6249
Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
Co-authored-by: Alexandre Kühn <aku@odoo.com>
Co-authored-by: Julien Giannone <jgi@odoo.com>
Co-authored-by: Lucas Perais <lpe@odoo.com>
Co-authored-by: Sébastien Theys <seb@odoo.com>
Co-authored-by: Xavier Dubuc <xdu@odoo.com>
Diffstat (limited to 'addons/mail_bot/static')
23 files changed, 838 insertions, 428 deletions
diff --git a/addons/mail_bot/static/src/bugfix/bugfix.js b/addons/mail_bot/static/src/bugfix/bugfix.js new file mode 100644 index 00000000000..bd676eb1da2 --- /dev/null +++ b/addons/mail_bot/static/src/bugfix/bugfix.js @@ -0,0 +1,10 @@ +/** + * This file allows introducing new JS modules without contaminating other files. + * This is useful when bug fixing requires adding such JS modules in stable + * versions of Odoo. Any module that is defined in this file should be isolated + * in its own file in master. + */ +odoo.define('mail_bot/static/src/bugfix/bugfix.js', function (require) { +'use strict'; + +}); diff --git a/addons/mail_bot/static/src/bugfix/bugfix.scss b/addons/mail_bot/static/src/bugfix/bugfix.scss new file mode 100644 index 00000000000..c4272e527b4 --- /dev/null +++ b/addons/mail_bot/static/src/bugfix/bugfix.scss @@ -0,0 +1,6 @@ +/** +* This file allows introducing new styles without contaminating other files. +* This is useful when bug fixing requires adding new components for instance in +* stable versions of Odoo. Any style that is defined in this file should be isolated +* in its own file in master. +*/ diff --git a/addons/mail_bot/static/src/bugfix/bugfix.xml b/addons/mail_bot/static/src/bugfix/bugfix.xml new file mode 100644 index 00000000000..c17906f75bb --- /dev/null +++ b/addons/mail_bot/static/src/bugfix/bugfix.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates xml:space="preserve"> + +<!-- + This file allows introducing new static templates without contaminating other files. + This is useful when bug fixing requires adding new components for instance in stable + versions of Odoo. Any template that is defined in this file should be isolated + in its own file in master. +--> + +</templates> diff --git a/addons/mail_bot/static/src/bugfix/bugfix_tests.js b/addons/mail_bot/static/src/bugfix/bugfix_tests.js new file mode 100644 index 00000000000..3aa5456b8f0 --- /dev/null +++ b/addons/mail_bot/static/src/bugfix/bugfix_tests.js @@ -0,0 +1,18 @@ +odoo.define('mail_bot/static/src/bugfix/bugfix_tests.js', function (require) { +'use strict'; + +/** + * This file allows introducing new QUnit test modules without contaminating + * other test files. This is useful when bug fixing requires adding new + * components for instance in stable versions of Odoo. Any test that is defined + * in this file should be isolated in its own file in master. + */ +QUnit.module('mail_bot', {}, function () { +QUnit.module('bugfix', {}, function () { +QUnit.module('bugfix_tests.js', { + +}); +}); +}); + +}); diff --git a/addons/mail_bot/static/src/components/messaging_menu/messaging_menu.js b/addons/mail_bot/static/src/components/messaging_menu/messaging_menu.js new file mode 100644 index 00000000000..c1deec24095 --- /dev/null +++ b/addons/mail_bot/static/src/components/messaging_menu/messaging_menu.js @@ -0,0 +1,27 @@ +odoo.define('mail_bot/static/src/components/messaging_menu/messaging_menu,js', function (require) { +'use strict'; + +const components = { + MessagingMenu: require('mail/static/src/components/messaging_menu/messaging_menu.js'), +}; + +const { patch } = require('web.utils'); + +patch(components.MessagingMenu, 'mail_bot/static/src/components/messaging_menu/messaging_menu,js', { + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + */ + _onOdoobotRequestClicked() { + const device = this.messagingMenu.messaging.device; + if (!device.isMobile) { + this.messagingMenu.close(); + } + }, +}); + +}); diff --git a/addons/mail_bot/static/src/components/messaging_menu/messaging_menu.xml b/addons/mail_bot/static/src/components/messaging_menu/messaging_menu.xml new file mode 100644 index 00000000000..a6eb2cf2cd9 --- /dev/null +++ b/addons/mail_bot/static/src/components/messaging_menu/messaging_menu.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates xml:space="preserve"> + <t t-inherit="mail.MessagingMenu" t-inherit-mode="extension"> + <xpath expr="//NotificationList" position="attributes"> + <attribute name="t-on-o-odoobot-request-clicked" t-translation="off">_onOdoobotRequestClicked</attribute> + </xpath> + </t> +</templates> diff --git a/addons/mail_bot/static/src/components/messaging_menu/messaging_menu_tests.js b/addons/mail_bot/static/src/components/messaging_menu/messaging_menu_tests.js new file mode 100644 index 00000000000..7bd03053699 --- /dev/null +++ b/addons/mail_bot/static/src/components/messaging_menu/messaging_menu_tests.js @@ -0,0 +1,202 @@ +odoo.define('mail_bot/static/src/components/messaging_menu/messaging_menu_tests.js', function (require) { +"use strict"; + +const { + afterEach: utilsAfterEach, + afterNextRender, + beforeEach: utilsBeforeEach, + start: utilsStart, +} = require('mail/static/src/utils/test_utils.js'); + +QUnit.module('mail_bot', {}, function () { +QUnit.module('components', {}, function () { +QUnit.module('messaging_menu', {}, function () { +QUnit.module('messaging_menu_tests.js', { + beforeEach() { + utilsBeforeEach(this); + + this.start = async params => { + let { widget } = await utilsStart(Object.assign({}, params, { + data: this.data, + hasMessagingMenu: true, + })); + this.widget = widget; + }; + }, + afterEach() { + utilsAfterEach(this); + if (this.widget) { + this.widget.destroy(); + } + }, +}); + +QUnit.test('rendering with OdooBot has a request (default)', async function (assert) { + assert.expect(4); + + await this.start({ + env: { + browser: { + Notification: { + permission: 'default', + }, + }, + }, + async mockRPC(route, args) { + if (args.method === 'channel_fetch_preview') { + return []; + } + return this._super(...arguments); + }, + }); + + assert.ok( + document.querySelector('.o_MessagingMenu_counter'), + "should display a notification counter next to the messaging menu for OdooBot request" + ); + assert.strictEqual( + document.querySelector('.o_MessagingMenu_counter').textContent, + "1", + "should display a counter of '1' next to the messaging menu" + ); + + await afterNextRender(() => + document.querySelector('.o_MessagingMenu_toggler').click() + ); + assert.containsOnce( + document.body, + '.o_NotificationRequest', + "should display a notification in the messaging menu" + ); + assert.strictEqual( + document.querySelector('.o_NotificationRequest_name').textContent.trim(), + 'OdooBot has a request', + "notification should display that OdooBot has a request" + ); +}); + +QUnit.test('rendering without OdooBot has a request (denied)', async function (assert) { + assert.expect(2); + + await this.start({ + env: { + browser: { + Notification: { + permission: 'denied', + }, + }, + }, + async mockRPC(route, args) { + if (args.method === 'channel_fetch_preview') { + return []; + } + return this._super(...arguments); + }, + }); + + assert.containsNone( + document.body, + '.o_MessagingMenu_counter', + "should not display a notification counter next to the messaging menu" + ); + + await afterNextRender(() => + document.querySelector('.o_MessagingMenu_toggler').click() + ); + assert.containsNone( + document.body, + '.o_NotificationRequest', + "should display no notification in the messaging menu" + ); +}); + +QUnit.test('rendering without OdooBot has a request (accepted)', async function (assert) { + assert.expect(2); + + await this.start({ + env: { + browser: { + Notification: { + permission: 'granted', + }, + }, + }, + async mockRPC(route, args) { + if (args.method === 'channel_fetch_preview') { + return []; + } + return this._super(...arguments); + }, + }); + + assert.containsNone( + document.body, + '.o_MessagingMenu_counter', + "should not display a notification counter next to the messaging menu" + ); + + await afterNextRender(() => + document.querySelector('.o_MessagingMenu_toggler').click() + ); + assert.containsNone( + document.body, + '.o_NotificationRequest', + "should display no notification in the messaging menu" + ); +}); + +QUnit.test('respond to notification prompt (denied)', async function (assert) { + assert.expect(3); + + await this.start({ + env: { + browser: { + Notification: { + permission: 'default', + async requestPermission() { + this.permission = 'denied'; + return this.permission; + }, + }, + }, + }, + async mockRPC(route, args) { + if (args.method === 'channel_fetch_preview') { + return []; + } + return this._super(...arguments); + }, + }); + + await afterNextRender(() => + document.querySelector('.o_MessagingMenu_toggler').click() + ); + await afterNextRender(() => + document.querySelector('.o_NotificationRequest').click() + ); + assert.containsOnce( + document.body, + '.toast .o_notification_content', + "should display a toast notification with the deny confirmation" + ); + assert.containsNone( + document.body, + '.o_MessagingMenu_counter', + "should not display a notification counter next to the messaging menu" + ); + + await afterNextRender(() => + document.querySelector('.o_MessagingMenu_toggler').click() + ); + assert.containsNone( + document.body, + '.o_NotificationRequest', + "should display no notification in the messaging menu" + ); +}); + +}); +}); +}); + +}); diff --git a/addons/mail_bot/static/src/components/notification_alert/notification_alert.js b/addons/mail_bot/static/src/components/notification_alert/notification_alert.js new file mode 100644 index 00000000000..4cfcbc362a8 --- /dev/null +++ b/addons/mail_bot/static/src/components/notification_alert/notification_alert.js @@ -0,0 +1,52 @@ +odoo.define('mail_bot/static/src/components/notification_alert/notification_alert.js', function (require) { +'use strict'; + +const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); + +const { Component } = owl; + +class NotificationAlert extends Component { + + /** + * @override + */ + constructor(...args) { + super(...args); + useStore(props => { + const isMessagingInitialized = this.env.isMessagingInitialized(); + return { + isMessagingInitialized, + isNotificationBlocked: this.isNotificationBlocked, + }; + }); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * @returns {boolean} + */ + get isNotificationBlocked() { + if (!this.env.isMessagingInitialized()) { + return false; + } + const windowNotification = this.env.browser.Notification; + return ( + windowNotification && + windowNotification.permission !== "granted" && + !this.env.messaging.isNotificationPermissionDefault() + ); + } + +} + +Object.assign(NotificationAlert, { + props: {}, + template: 'mail_bot.NotificationAlert', +}); + +return NotificationAlert; + +}); diff --git a/addons/mail_bot/static/src/components/notification_alert/notification_alert.xml b/addons/mail_bot/static/src/components/notification_alert/notification_alert.xml new file mode 100644 index 00000000000..e9b3eef9331 --- /dev/null +++ b/addons/mail_bot/static/src/components/notification_alert/notification_alert.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates xml:space="preserve"> + + <t t-name="mail_bot.NotificationAlert" owl="1"> + <div class="o_NotificationAlert"> + <t t-if="env.isMessagingInitialized()"> + <center t-if="isNotificationBlocked" class="o_notification_alert alert alert-primary"> + Odoo Push notifications have been blocked. Go to your browser settings to allow them. + </center> + </t> + </div> + </t> + +</templates> diff --git a/addons/mail_bot/static/src/components/notification_list/notification_list.js b/addons/mail_bot/static/src/components/notification_list/notification_list.js new file mode 100644 index 00000000000..c0fa47930c9 --- /dev/null +++ b/addons/mail_bot/static/src/components/notification_list/notification_list.js @@ -0,0 +1,44 @@ +odoo.define('mail_bot/static/src/components/notification_list/notification_list.js', function (require) { +'use strict'; + +const components = { + mail: { + NotificationList: require('mail/static/src/components/notification_list/notification_list.js'), + }, + mail_bot: { + NotificationRequest: require('mail_bot/static/src/components/notification_request/notification_request.js'), + }, +}; + +const { patch } = require('web.utils'); + +Object.assign(components.mail.NotificationList.components, components.mail_bot); + +patch(components.mail.NotificationList, 'mail_bot/static/src/components/notification_list/notification_list.js', { + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Override so that 'OdooBot has a request' is included at the top of the + * list. + * + * @override + */ + _useStoreSelector(props) { + const res = this._super(...arguments); + if ( + props.filter === 'all' && + this.env.messaging.isNotificationPermissionDefault() + ) { + res.notifications.unshift({ + type: 'odoobotRequest', + uniqueId: `odoobotRequest`, + }); + } + return res; + } +}); + +}); diff --git a/addons/mail_bot/static/src/components/notification_list/notification_list.xml b/addons/mail_bot/static/src/components/notification_list/notification_list.xml new file mode 100644 index 00000000000..da2a28ee090 --- /dev/null +++ b/addons/mail_bot/static/src/components/notification_list/notification_list.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates xml:space="preserve"> + <t t-inherit="mail.NotificationList" t-inherit-mode="extension"> + <xpath expr="//*[@name='separatorCondition']" position="before"> + <t t-if="notification.type === 'odoobotRequest'"> + <NotificationRequest + class="o_NotificationList_notificationRequest" + t-att-class="{ 'o-mobile': env.messaging.device.isMobile }" + /> + </t> + </xpath> + </t> +</templates> diff --git a/addons/mail_bot/static/src/components/notification_request/notification_request.js b/addons/mail_bot/static/src/components/notification_request/notification_request.js new file mode 100644 index 00000000000..ef9639c688b --- /dev/null +++ b/addons/mail_bot/static/src/components/notification_request/notification_request.js @@ -0,0 +1,90 @@ +odoo.define('mail_bot/static/src/components/notification_request/notification_request.js', function (require) { +'use strict'; + +const components = { + PartnerImStatusIcon: require('mail/static/src/components/partner_im_status_icon/partner_im_status_icon.js'), +}; +const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); + +const { Component } = owl; + +class NotificationRequest extends Component { + + /** + * @override + */ + constructor(...args) { + super(...args); + useStore(props => { + return { + isDeviceMobile: this.env.messaging.device.isMobile, + partnerRoot: this.env.messaging.partnerRoot + ? this.env.messaging.partnerRoot.__state + : undefined, + }; + }); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * @returns {string} + */ + getHeaderText() { + return _.str.sprintf( + this.env._t("%s has a request"), + this.env.messaging.partnerRoot.nameOrDisplayName + ); + } + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Handle the response of the user when prompted whether push notifications + * are granted or denied. + * + * @private + * @param {string} value + */ + _handleResponseNotificationPermission(value) { + // manually force recompute because the permission is not in the store + this.env.messaging.messagingMenu.update(); + if (value !== 'granted') { + this.env.services['bus_service'].sendNotification( + this.env._t("Permission denied"), + this.env._t("Odoo will not have the permission to send native notifications on this device.") + ); + } + } + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + */ + _onClick() { + const windowNotification = this.env.browser.Notification; + const def = windowNotification && windowNotification.requestPermission(); + if (def) { + def.then(this._handleResponseNotificationPermission.bind(this)); + } + this.trigger('o-odoobot-request-clicked'); + } + +} + +Object.assign(NotificationRequest, { + components, + props: {}, + template: 'mail_bot.NotificationRequest', +}); + +return NotificationRequest; + +}); diff --git a/addons/mail_bot/static/src/components/notification_request/notification_request.scss b/addons/mail_bot/static/src/components/notification_request/notification_request.scss new file mode 100644 index 00000000000..8d4b38db848 --- /dev/null +++ b/addons/mail_bot/static/src/components/notification_request/notification_request.scss @@ -0,0 +1,73 @@ +// ------------------------------------------------------------------ +// Layout +// ------------------------------------------------------------------ + +.o_NotificationRequest { + @include o-mail-notification-list-item-layout(); +} + +.o_NotificationRequest_content { + @include o-mail-notification-list-item-content-layout(); +} + +.o_NotificationRequest_core { + @include o-mail-notification-list-item-core-layout(); +} + +.o_NotificationRequest_coreItem { + @include o-mail-notification-list-item-core-item-layout(); +} + +.o_NotificationRequest_header { + @include o-mail-notification-list-item-header-layout(); +} + +.o_NotificationRequest_image { + @include o-mail-notification-list-item-image-layout(); +} + +.o_NotificationRequest_imageContainer { + @include o-mail-notification-list-item-image-container-layout(); +} + +.o_NotificationRequest_inlineText { + @include o-mail-notification-list-item-inline-text-layout(); +} + +.o_NotificationRequest_name { + @include o-mail-notification-list-item-name-layout(); +} + +.o_NotificationRequest_partnerImStatusIcon { + @include o-mail-notification-list-item-partner-im-status-icon-layout(); +} + +.o_NotificationRequest_sidebar { + @include o-mail-notification-list-item-sidebar-layout(); +} + +// ------------------------------------------------------------------ +// Style +// ------------------------------------------------------------------ + +.o_NotificationRequest { + @include o-mail-notification-list-item-style(); + + &:hover { + .o_NotificationRequest_partnerImStatusIcon { + @include o-mail-notification-list-item-hover-partner-im-status-icon-style(); + } + } +} + +.o_NotificationRequest_core { + @include o-mail-notification-list-item-core-style(); +} + +.o_NotificationRequest_name { + @include o-mail-notification-list-item-bold-style(); +} + +.o_NotificationRequest_partnerImStatusIcon { + @include o-mail-notification-list-item-partner-im-status-icon-style(); +} diff --git a/addons/mail_bot/static/src/components/notification_request/notification_request.xml b/addons/mail_bot/static/src/components/notification_request/notification_request.xml new file mode 100644 index 00000000000..10b22bac2cd --- /dev/null +++ b/addons/mail_bot/static/src/components/notification_request/notification_request.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates xml:space="preserve"> + + <t t-name="mail_bot.NotificationRequest" owl="1"> + <div class="o_NotificationRequest" t-on-click="_onClick"> + <div class="o_NotificationRequest_sidebar"> + <div class="o_NotificationRequest_imageContainer o_NotificationRequest_sidebarItem"> + <img class="o_NotificationRequest_image rounded-circle" src="/mail/static/src/img/odoobot.png" alt="Avatar of OdooBot"/> + <PartnerImStatusIcon + class="o_NotificationRequest_partnerImStatusIcon" + t-att-class="{ 'o-mobile': env.messaging.device.isMobile }" + partnerLocalId="env.messaging.partnerRoot.localId" + /> + </div> + </div> + <div class="o_NotificationRequest_content"> + <div class="o_NotificationRequest_header"> + <span class="o_NotificationRequest_name" t-att-class="{ 'o-mobile': env.messaging.device.isMobile }"> + <t t-esc="getHeaderText()"/> + </span> + </div> + <div class="o_NotificationRequest_core"> + <span class="o_NotificationRequest_coreItem o_NotificationRequest_inlineText"> + Enable desktop notifications to chat. + </span> + </div> + </div> + </div> + </t> + +</templates> diff --git a/addons/mail_bot/static/src/js/mailbot_service.js b/addons/mail_bot/static/src/js/mailbot_service.js deleted file mode 100644 index 5312e5d5129..00000000000 --- a/addons/mail_bot/static/src/js/mailbot_service.js +++ /dev/null @@ -1,88 +0,0 @@ -odoo.define('mail_bot.MailBotService', function (require) { -"use strict"; - -var AbstractService = require('web.AbstractService'); -var core = require('web.core'); -var session = require('web.session'); - -var _t = core._t; - -var MailBotService = AbstractService.extend({ - /** - * @override - */ - start: function () { - this._hasRequest = (window.Notification && window.Notification.permission === "default") || false; - if ('odoobot_initialized' in session && ! session.odoobot_initialized) { - this._showOdoobotTimeout(); - } - }, - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * 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) { - if (!this.isRequestingForNativeNotifications()) { - return []; - } - if (filter && filter !== 'mailbox_inbox') { - return []; - } - var previews = [{ - 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 is requesting to enable push notifications. - * - * @returns {boolean} - */ - isRequestingForNativeNotifications: function () { - return this._hasRequest; - }, - /** - * Called when user either accepts or refuses push notifications. - */ - removeRequest: function () { - this._hasRequest = false; - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * @private - */ - _showOdoobotTimeout: function () { - var self = this; - setTimeout(function () { - session.odoobot_initialized = true; - self._rpc({ - model: 'mail.channel', - method: 'init_odoobot', - }); - }, 2*60*1000); - }, -}); - -core.serviceRegistry.add('mailbot_service', MailBotService); -return MailBotService; - -}); diff --git a/addons/mail_bot/static/src/js/notification_alert.js b/addons/mail_bot/static/src/js/notification_alert.js deleted file mode 100644 index 48b105dfe48..00000000000 --- a/addons/mail_bot/static/src/js/notification_alert.js +++ /dev/null @@ -1,26 +0,0 @@ -odoo.define('mail.NotificationAlert', function (require) { -"use strict"; - -var Widget = require('web.Widget'); -var widgetRegistry = require('web.widget_registry'); - -// ----------------------------------------------------------------------------- -// Display Notification alert on user preferences form view -// ----------------------------------------------------------------------------- -var NotificationAlert = Widget.extend({ - template: 'mail.NotificationAlert', - /** - * @override - */ - init: function () { - this._super.apply(this, arguments); - var hasRequest = this.call('mailbot_service', 'isRequestingForNativeNotifications'); - this.isNotificationBlocked = window.Notification && window.Notification.permission !== "granted" && !hasRequest; - }, -}); - -widgetRegistry.add('notification_alert', NotificationAlert); - -return NotificationAlert; - -}); diff --git a/addons/mail_bot/static/src/js/systray_messaging_menu.js b/addons/mail_bot/static/src/js/systray_messaging_menu.js deleted file mode 100644 index f7bccee84ed..00000000000 --- a/addons/mail_bot/static/src/js/systray_messaging_menu.js +++ /dev/null @@ -1,119 +0,0 @@ -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', 'isRequestingForNativeNotifications')) { - 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<Object[]>} 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) { - this.call('mailbot_service', 'removeRequest'); - 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.')); - } - 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/src/models/messaging/messaging.js b/addons/mail_bot/static/src/models/messaging/messaging.js new file mode 100644 index 00000000000..a95171ccf69 --- /dev/null +++ b/addons/mail_bot/static/src/models/messaging/messaging.js @@ -0,0 +1,22 @@ +odoo.define('mail_bot/static/src/models/messaging/messaging.js', function (require) { +'use strict'; + +const { registerInstancePatchModel } = require('mail/static/src/model/model_core.js'); + +registerInstancePatchModel('mail.messaging', 'mail_bot/static/src/models/messaging/messaging.js', { + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + + /** + * @returns {boolean} + */ + isNotificationPermissionDefault() { + const windowNotification = this.env.browser.Notification; + return windowNotification + ? windowNotification.permission === 'default' + : false; + }, +}); + +}); diff --git a/addons/mail_bot/static/src/models/messaging_initializer/messaging_initializer.js b/addons/mail_bot/static/src/models/messaging_initializer/messaging_initializer.js new file mode 100644 index 00000000000..05ee8196b2c --- /dev/null +++ b/addons/mail_bot/static/src/models/messaging_initializer/messaging_initializer.js @@ -0,0 +1,35 @@ +odoo.define('mail_bot/static/src/models/messaging_initializer/messaging_initializer.js', function (require) { +'use strict'; + +const { registerInstancePatchModel } = require('mail/static/src/model/model_core.js'); + +registerInstancePatchModel('mail.messaging_initializer', 'mail_bot/static/src/models/messaging_initializer/messaging_initializer.js', { + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @private + */ + _showOdoobotTimeout() { + setTimeout(() => { + this.env.session.odoobot_initialized = true; + this.env.services.rpc({ + model: 'mail.channel', + method: 'init_odoobot', + }); + }, 2 * 60 * 1000); + }, + /** + * @override + */ + async _start() { + await this.async(() => this._super()); + + if ('odoobot_initialized' in this.env.session && !this.env.session.odoobot_initialized) { + this._showOdoobotTimeout(); + } + }, +}); + +}); diff --git a/addons/mail_bot/static/src/models/messaging_menu/messaging_menu.js b/addons/mail_bot/static/src/models/messaging_menu/messaging_menu.js new file mode 100644 index 00000000000..cb411be987c --- /dev/null +++ b/addons/mail_bot/static/src/models/messaging_menu/messaging_menu.js @@ -0,0 +1,28 @@ +odoo.define('mail_bot/static/src/models/messaging_menu/messaging_menu.js', function (require) { +'use strict'; + +const { registerInstancePatchModel } = require('mail/static/src/model/model_core.js'); + +registerInstancePatchModel('mail.messaging_menu', 'mail_bot/static/src/models/messaging_menu/messaging_menu.js', { + + //---------------------------------------------------------------------- + // Private + //---------------------------------------------------------------------- + + /** + * @override + */ + _updateCounter() { + let counter = this._super(); + if (!this.messaging) { + // compute after delete + return counter; + } + if (this.messaging.isNotificationPermissionDefault()) { + counter += 1; + } + return counter; + }, +}); + +}); diff --git a/addons/mail_bot/static/src/widgets/notification_alert/notification_alert.js b/addons/mail_bot/static/src/widgets/notification_alert/notification_alert.js new file mode 100644 index 00000000000..feeb0595832 --- /dev/null +++ b/addons/mail_bot/static/src/widgets/notification_alert/notification_alert.js @@ -0,0 +1,45 @@ +odoo.define('mail_bot/static/src/widgets/notification_alert/notification_alert.js', function (require) { +"use strict"; + +const components = { + NotificationAlert: require('mail_bot/static/src/components/notification_alert/notification_alert.js'), +}; + +const { ComponentWrapper, WidgetAdapterMixin } = require('web.OwlCompatibility'); + +const Widget = require('web.Widget'); +const widgetRegistry = require('web.widget_registry'); + +class NotificationAlertWrapper extends ComponentWrapper {} + +// ----------------------------------------------------------------------------- +// Display Notification alert on user preferences form view +// ----------------------------------------------------------------------------- +const NotificationAlert = Widget.extend(WidgetAdapterMixin, { + /** + * @override + */ + init() { + this._super(...arguments); + this.component = undefined; + }, + /** + * @override + */ + async start() { + await this._super(...arguments); + + this.component = new NotificationAlertWrapper( + this, + components.NotificationAlert, + {} + ); + await this.component.mount(this.el); + }, +}); + +widgetRegistry.add('notification_alert', NotificationAlert); + +return NotificationAlert; + +}); diff --git a/addons/mail_bot/static/src/widgets/notification_alert/notification_alert_tests.js b/addons/mail_bot/static/src/widgets/notification_alert/notification_alert_tests.js new file mode 100644 index 00000000000..1cc11dcd15c --- /dev/null +++ b/addons/mail_bot/static/src/widgets/notification_alert/notification_alert_tests.js @@ -0,0 +1,109 @@ +odoo.define('mail_bot/static/src/widgets/notification_alert/notification_alert_tests.js', function (require) { +'use strict'; + +const { + afterEach: utilsAfterEach, + beforeEach: utilsBeforeEach, + start, +} = require('mail/static/src/utils/test_utils.js'); + +const FormView = require('web.FormView'); + +QUnit.module('mail_bot', {}, function () { +QUnit.module('widgets', {}, function () { +QUnit.module('notification_alert_tests.js', { + beforeEach() { + utilsBeforeEach(this); + + this.start = async params => { + let { widget } = await start(Object.assign({ + data: this.data, + hasView: true, + // View params + View: FormView, + model: 'mail.message', + arch: ` + <form> + <widget name="notification_alert"/> + </form> + `, + }, params)); + this.widget = widget; + }; + }, + afterEach() { + utilsAfterEach(this); + if (this.widget) { + this.widget.destroy(); + this.widget = undefined; + } + }, +}); + +QUnit.skip('notification_alert widget: display blocked notification alert', async function (assert) { + // FIXME: Test should work, but for some reasons OWL always flags the + // component as not mounted, even though it is in the DOM and it's state + // is good for rendering... + assert.expect(1); + + await this.start({ + env: { + browser: { + Notification: { + permission: 'denied', + }, + }, + }, + }); + + assert.containsOnce( + document.body, + '.o_notification_alert', + "Blocked notification alert should be displayed" + ); +}); + +QUnit.test('notification_alert widget: no notification alert when granted', async function (assert) { + assert.expect(1); + + await this.start({ + env: { + browser: { + Notification: { + permission: 'granted', + }, + }, + }, + }); + + assert.containsNone( + document.body, + '.o_notification_alert', + "Blocked notification alert should not be displayed" + ); +}); + +QUnit.test('notification_alert widget: no notification alert when default', async function (assert) { + assert.expect(1); + + await this.start({ + env: { + browser: { + Notification: { + permission: 'default', + }, + }, + }, + }); + + assert.containsNone( + document.body, + '.o_notification_alert', + "Blocked notification alert should not be displayed" + ); +}); + +}); +}); + +}); diff --git a/addons/mail_bot/static/tests/systray_messaging_menu_tests.js b/addons/mail_bot/static/tests/systray_messaging_menu_tests.js deleted file mode 100644 index 2494d639b29..00000000000 --- a/addons/mail_bot/static/tests/systray_messaging_menu_tests.js +++ /dev/null @@ -1,195 +0,0 @@ -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 FormView = require('web.FormView'); -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: [], - }, - }; - - this.services = _.extend({}, mailTestUtils.getMailServices(), { - mailbot_service: MailBotService - }); - - // By default, permission are to ask user for push notification ("default"). - // Use requestPermissionDef to simulate permission change, e.g. "granted" - this.requestPermissionDef = testUtils.makeTestPromise(); - this.ORIGINAL_WINDOW_NOTIFICATION = window.Notification; - window.Notification = { - permission: "default", - requestPermission: function () { - return self.requestPermissionDef; - }, - }; - - // Patch mailbot_service so that it does do not do any RPC - this.isMailbotRequesting = true; - testUtils.mock.patch(MailBotService, { - /** - * @override - */ - _showOdoobotTimeout: function () {}, - }); - - // Patch Bus Service so that it does not play any audio (may raise - // Uncaught rejected Promise due to Chrome autoplay policy: https://goo.gl/xX8pDD) - testUtils.patch(this.services.bus_service, { - /** - * @override - */ - _beep: function () {}, - }); - }, - afterEach: function () { - // unpatch MailBotService and BusService - testUtils.mock.unpatch(MailBotService); - testUtils.mock.unpatch(this.services.bus_service); - window.Notification = this.ORIGINAL_WINDOW_NOTIFICATION; - } -}); - -QUnit.test('messaging menu widget: rendering with OdooBot has a request', async function (assert) { - assert.expect(5); - - var messagingMenu = new MessagingMenu(); - await testUtils.mock.addMockEnvironment(messagingMenu, { - data: this.data, - services: this.services, - }); - await messagingMenu.appendTo($('#qunit-fixture')); - - assert.containsOnce(messagingMenu, '.o_notification_counter', - "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"); - - await testUtils.dom.click(messagingMenu.$('.dropdown-toggle')); - assert.containsOnce(messagingMenu, '.o_preview_info', - "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 (denied)', async function (assert) { - assert.expect(3); - - window.Notification.permission = 'denied'; - - var messagingMenu = new MessagingMenu(); - await testUtils.mock.addMockEnvironment(messagingMenu, { - data: this.data, - services: this.services, - }); - await messagingMenu.appendTo($('#qunit-fixture')); - - assert.containsOnce(messagingMenu, '.o_notification_counter', - "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"); - await testUtils.dom.click(messagingMenu.$('.dropdown-toggle')); - assert.containsNone(messagingMenu, '.o_preview_info', - "should display no preview in the messaging menu"); - - messagingMenu.destroy(); -}); - -QUnit.test('messaging menu widget: rendering without OdooBot has a request (accepted)', async function (assert) { - assert.expect(3); - - window.Notification.permission = 'granted'; - - var messagingMenu = new MessagingMenu(); - await testUtils.mock.addMockEnvironment(messagingMenu, { - data: this.data, - services: this.services, - }); - await messagingMenu.appendTo($('#qunit-fixture')); - - assert.containsOnce(messagingMenu, '.o_notification_counter', - "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"); - await testUtils.dom.click(messagingMenu.$('.dropdown-toggle')); - assert.containsNone(messagingMenu, '.o_preview_info', - "should display no preview in the messaging menu"); - - messagingMenu.destroy(); -}); - -QUnit.test('messaging menu widget: respond to notification prompt', async function (assert) { - assert.expect(4); - - var messagingMenu = new MessagingMenu(); - await testUtils.mock.addMockEnvironment(messagingMenu, { - data: this.data, - services: this.services, - }); - await messagingMenu.appendTo($('#qunit-fixture')); - - assert.containsOnce(messagingMenu, '.o_notification_counter', - "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"); - - await testUtils.dom.click(messagingMenu.$('.dropdown-toggle')); - await testUtils.dom.click(messagingMenu.$('.o_preview_info')); - - // simulate "default" response, which is equivalent to "Not Now" in Firefox. - this.requestPermissionDef.resolve("default"); - await testUtils.nextTick(); - assert.strictEqual(messagingMenu.$('.o_notification_counter').text(), '0', - "should display a counter of '0' next to the messaging menu"); - - await testUtils.dom.click(messagingMenu.$('.dropdown-toggle')); - assert.containsNone(messagingMenu, '.o_preview_info', - "should display no preview in the messaging menu"); - - messagingMenu.destroy(); -}); - -QUnit.test('notification_alert widget: display blocked notification alert', async function (assert) { - assert.expect(2); - - window.Notification.permission = 'denied'; - - var form = await testUtils.createView({ - View: FormView, - model: 'mail.message', - data: this.data, - arch: '<form>' + - '<widget name="notification_alert"/>' + - '</form>', - }); - assert.containsOnce(form, '.o_notification_alert', "Blocked notification alert should be displayed"); - - window.Notification.permission = 'granted'; - await form.reload(); - assert.containsNone(form, '.o_notification_alert', "Blocked notification alert should not be displayed"); - - form.destroy(); -}); -}); -}); |
