From 1a445370954cb0632adebc833270973d2626cbfd Mon Sep 17 00:00:00 2001 From: "Julien Carion (juca)" Date: Wed, 9 Aug 2023 10:10:25 +0200 Subject: [PATCH] [IMP] mail: avatar card preview v2 This commit changes the behavior of the avatar card preview so that it is now triggered on click instead of on hover and the previous behavior of the click event (open chat) is therefore removed. It also adds the functionality to the Message and Activity components of discuss so that clicking on the avatar inside these components will also show the card. It also makes sure that the id of the user is added to the persona even if nothing indicates that it should. task-3442819 closes odoo/odoo#131355 Related: odoo/enterprise#47084 Signed-off-by: Francois Georis (fge) --- .../crm/tests/test_crm_lead_convert_mass.py | 4 +- .../avatar_card/avatar_card_popover_patch.xml | 2 +- .../static/tests/m2x_avatar_employee_tests.js | 102 ++++++------- .../static/tests/web/m2x_avatar_user_tests.js | 15 +- .../tests/test_performance.py | 6 +- addons/im_livechat/__manifest__.py | 1 - .../static/src/web/message_patch.js | 14 -- .../static/tests/message_patch_tests.js | 30 ---- addons/im_livechat/tests/test_message.py | 4 + addons/mail/models/mail_message.py | 6 +- addons/mail/static/src/core/web/activity.js | 11 ++ addons/mail/static/src/core/web/activity.xml | 2 +- .../mail/static/src/core/web/message_patch.js | 19 ++- .../static/src/core/web/message_patch.xml | 5 +- .../src/discuss/core/web/message_patch.js | 32 ---- .../web/avatar_card/avatar_card_popover.js | 2 +- .../web/avatar_card/avatar_card_popover.xml | 6 +- .../many2many_avatar_user_field.js | 61 ++++---- .../many2many_avatar_user_field.xml | 10 -- .../many2one_avatar_user_field.js | 34 +---- .../many2one_avatar_user_field.xml | 10 -- .../static/tests/discuss_app/discuss_tests.js | 12 +- .../mock_server/models/mail_message.js | 6 + .../static/tests/message/message_tests.js | 144 +++++++----------- .../tests/web/fields/m2x_avatar_user_tests.js | 45 ++++-- addons/test_mail/tests/test_performance.py | 24 +-- .../tests/test_mail_performance.py | 2 +- addons/test_mail_full/tests/test_rating.py | 8 +- .../src/core/popover/popover_controller.js | 17 --- .../src/core/popover/popover_service.js | 3 - 30 files changed, 245 insertions(+), 392 deletions(-) delete mode 100644 addons/im_livechat/static/src/web/message_patch.js delete mode 100644 addons/im_livechat/static/tests/message_patch_tests.js delete mode 100644 addons/mail/static/src/discuss/core/web/message_patch.js diff --git a/addons/crm/tests/test_crm_lead_convert_mass.py b/addons/crm/tests/test_crm_lead_convert_mass.py index 29203142dcb..457f132be52 100644 --- a/addons/crm/tests/test_crm_lead_convert_mass.py +++ b/addons/crm/tests/test_crm_lead_convert_mass.py @@ -42,7 +42,7 @@ class TestLeadConvertMass(crm_common.TestLeadConvertMassCommon): with self.assertQueryCount(user_sales_manager=0): test_leads = self.env['crm.lead'].browse(test_leads.ids) - with self.assertQueryCount(user_sales_manager=580): # crm 521 / com 516 / ent 516 + with self.assertQueryCount(user_sales_manager=581): # crm 521 / com 516 / ent 516 test_leads._handle_salesmen_assignment(user_ids=user_ids, team_id=team_id) self.assertEqual(test_leads.team_id, self.sales_team_convert) @@ -167,7 +167,7 @@ class TestLeadConvertMass(crm_common.TestLeadConvertMassCommon): user_ids = self.assign_users.ids # randomness: at least 1 query - with self.assertQueryCount(user_sales_manager=1797): # crm 1410 / com 1677 / ent 1685 + with self.assertQueryCount(user_sales_manager=1798): # crm 1410 / com 1677 / ent 1685 mass_convert = self.env['crm.lead2opportunity.partner.mass'].with_context({ 'active_model': 'crm.lead', 'active_ids': test_leads.ids, diff --git a/addons/hr/static/src/components/avatar_card/avatar_card_popover_patch.xml b/addons/hr/static/src/components/avatar_card/avatar_card_popover_patch.xml index 27277006ae7..10605f9b7d8 100644 --- a/addons/hr/static/src/components/avatar_card/avatar_card_popover_patch.xml +++ b/addons/hr/static/src/components/avatar_card/avatar_card_popover_patch.xml @@ -2,7 +2,7 @@ - + diff --git a/addons/hr/static/tests/m2x_avatar_employee_tests.js b/addons/hr/static/tests/m2x_avatar_employee_tests.js index 80bbd9a37e1..87312833a2b 100644 --- a/addons/hr/static/tests/m2x_avatar_employee_tests.js +++ b/addons/hr/static/tests/m2x_avatar_employee_tests.js @@ -59,34 +59,34 @@ QUnit.test("many2one_avatar_employee widget in list view", async function (asser document.querySelectorAll(".o_data_cell div[name='employee_id']")[2].innerText, "Mario" ); - + // TODO: avatar card employee // click on first employee - dom.click(document.querySelector(".o_data_cell .o_m2o_avatar > img")); - await contains(".o-mail-ChatWindow-name"); - assert.verifySteps([`read hr.employee.public ${employeeId_1}`]); - assert.strictEqual(document.querySelector(".o-mail-ChatWindow-name").textContent, "Mario"); + // dom.click(document.querySelector(".o_data_cell .o_m2o_avatar > img")); + // await contains(".o-mail-ChatWindow-name"); + // assert.verifySteps([`read hr.employee.public ${employeeId_1}`]); + // assert.strictEqual(document.querySelector(".o-mail-ChatWindow-name").textContent, "Mario"); - // click on second employee - dom.click(document.querySelectorAll(".o_data_cell .o_m2o_avatar > img")[1]); - await contains(".o-mail-ChatWindow-name", { count: 2 }); - assert.verifySteps([`read hr.employee.public ${employeeId_2}`]); - assert.strictEqual( - document.querySelectorAll(".o-mail-ChatWindow-name")[1].textContent, - "Luigi" - ); + // // click on second employee + // dom.click(document.querySelectorAll(".o_data_cell .o_m2o_avatar > img")[1]); + // await contains(".o-mail-ChatWindow-name", { count: 2 }); + // assert.verifySteps([`read hr.employee.public ${employeeId_2}`]); + // assert.strictEqual( + // document.querySelectorAll(".o-mail-ChatWindow-name")[1].textContent, + // "Luigi" + // ); - // click on third employee (same as first) - dom.click(document.querySelectorAll(".o_data_cell .o_m2o_avatar > img")[2]); - assert.containsN( - document.body, - ".o-mail-ChatWindow-name", - 2, - "should still have only 2 chat windows because third is the same partner as first" - ); - assert.verifySteps( - [], - "employee should not have been read again because we already know its partner" - ); + // // click on third employee (same as first) + // dom.click(document.querySelectorAll(".o_data_cell .o_m2o_avatar > img")[2]); + // assert.containsN( + // document.body, + // ".o-mail-ChatWindow-name", + // 2, + // "should still have only 2 chat windows because third is the same partner as first" + // ); + // assert.verifySteps( + // [], + // "employee should not have been read again because we already know its partner" + // ); }); QUnit.test("many2one_avatar_employee widget in kanban view", async function (assert) { @@ -158,10 +158,8 @@ QUnit.test( }); await contains(".o_field_widget[name=employee_id] input", { value: "Mario" }); await dom.click(document.querySelector(".o_m2o_avatar > img")); - await contains( - ".o_notification.border-info:contains(You can only chat with employees that have a dedicated user.)" - ); assert.verifySteps([`web_read m2x.avatar.employee ${avatarId}`]); + // Nothing should happen } ); @@ -218,12 +216,10 @@ QUnit.test("many2many_avatar_employee widget in form view", async function (asse await dom.click( document.querySelectorAll(".o_field_many2many_avatar_employee .o_tag .o_m2m_avatar")[1] ); + // TODO: avatar card employee assert.verifySteps([ `web_read m2x.avatar.employee ${avatarId_1}`, - `read hr.employee.public ${employeeId_1}`, - `read hr.employee.public ${employeeId_2}`, ]); - await contains(".o-mail-ChatWindow-name", { count: 2 }); }); QUnit.test("many2many_avatar_employee widget in list view", async function (assert) { @@ -265,21 +261,21 @@ QUnit.test("many2many_avatar_employee widget in list view", async function (asse 2, "should have two avatar" ); + // TODO: avatar card employee + // // click on first employee badge + // dom.click(document.querySelector(".o_data_cell .o_m2m_avatar")); + // await contains(".o-mail-ChatWindow-name"); + // assert.verifySteps([`read hr.employee.public ${employeeId_1}`]); + // assert.strictEqual(document.querySelector(".o-mail-ChatWindow-name").textContent, "Mario"); - // click on first employee badge - dom.click(document.querySelector(".o_data_cell .o_m2m_avatar")); - await contains(".o-mail-ChatWindow-name"); - assert.verifySteps([`read hr.employee.public ${employeeId_1}`]); - assert.strictEqual(document.querySelector(".o-mail-ChatWindow-name").textContent, "Mario"); - - // click on second employee - dom.click(document.querySelectorAll(".o_data_cell .o_m2m_avatar")[1]); - await contains(".o-mail-ChatWindow-name", { count: 2 }); - assert.verifySteps([`read hr.employee.public ${employeeId_2}`]); - assert.strictEqual( - document.querySelectorAll(".o-mail-ChatWindow-name")[1].textContent, - "Yoshi" - ); + // // click on second employee + // dom.click(document.querySelectorAll(".o_data_cell .o_m2m_avatar")[1]); + // await contains(".o-mail-ChatWindow-name", { count: 2 }); + // assert.verifySteps([`read hr.employee.public ${employeeId_2}`]); + // assert.strictEqual( + // document.querySelectorAll(".o-mail-ChatWindow-name")[1].textContent, + // "Yoshi" + // ); }); QUnit.test("many2many_avatar_employee widget in kanban view", async function (assert) { @@ -346,12 +342,9 @@ QUnit.test("many2many_avatar_employee widget in kanban view", async function (as `/web/image/hr.employee.public/${employeeId_1}/avatar_128` ); - await dom.click(document.querySelectorAll(".o_kanban_record img.o_m2m_avatar")[1]); - await dom.click(document.querySelectorAll(".o_kanban_record img.o_m2m_avatar")[0]); - assert.verifySteps([ - `read hr.employee.public ${employeeId_1}`, - `read hr.employee.public ${employeeId_2}`, - ]); + await dom.click(document.querySelectorAll('.o_kanban_record img.o_m2m_avatar')[1]); + await dom.click(document.querySelectorAll('.o_kanban_record img.o_m2m_avatar')[0]); + // TODO: avatar card employee }); QUnit.test( @@ -400,14 +393,9 @@ QUnit.test( await dom.click( document.querySelectorAll(".o_field_many2many_avatar_employee .o_tag .o_m2m_avatar")[1] ); - await contains( - ".o_notification.border-info:contains(You can only chat with employees that have a dedicated user.)" - ); assert.verifySteps([ `web_read m2x.avatar.employee ${employeeId_1}`, - `read hr.employee.public ${employeeId_1}`, - `read hr.employee.public ${employeeId_2}`, ]); - await contains(".o-mail-ChatWindow-name"); + // TODO: avtar card employee } ); diff --git a/addons/hr/static/tests/web/m2x_avatar_user_tests.js b/addons/hr/static/tests/web/m2x_avatar_user_tests.js index 0308002f76f..b0930433126 100644 --- a/addons/hr/static/tests/web/m2x_avatar_user_tests.js +++ b/addons/hr/static/tests/web/m2x_avatar_user_tests.js @@ -1,9 +1,8 @@ /* @odoo-module */ -import { startServer } from "@bus/../tests/helpers/mock_python_environment"; - import { start } from "@mail/../tests/helpers/test_utils"; -import { patchWithCleanup, triggerEvent, getFixture, getNodesTextContent } from "@web/../tests/helpers/utils"; +import { startServer } from "@bus/../tests/helpers/mock_python_environment"; +import { click, patchWithCleanup, getFixture, getNodesTextContent } from "@web/../tests/helpers/utils"; import { registry } from "@web/core/registry"; import { browser } from "@web/core/browser/browser"; import { EventBus } from "@odoo/owl"; @@ -71,6 +70,7 @@ QUnit.module("M2XAvatarUser", ({ beforeEach }) => { "email", "phone", "im_status", + "share", "work_phone", "work_email", "job_title", @@ -107,13 +107,12 @@ QUnit.module("M2XAvatarUser", ({ beforeEach }) => { }, }); // Open card - await triggerEvent(target, ".o_m2o_avatar > img", "mouseover"); - assert.verifySteps(["setTimeout of 350ms", "setTimeout of 250ms", "user read"]); + await click(document, ".o_m2o_avatar > img"); + assert.verifySteps(["setTimeout of 250ms", "user read"]); assert.containsOnce(target, ".o_avatar_card"); - assert.deepEqual(getNodesTextContent(target.querySelectorAll(".o_card_user_infos > *")), ['Mario', 'sub manager', 'Managemment', ' Mario@odoo.pro', ' +585555555']); + assert.deepEqual(getNodesTextContent(target.querySelectorAll(".o_card_user_infos > *")), ['Mario', 'sub manager', 'Managemment', 'Mario@odoo.pro', '+585555555']); // Close card - await triggerEvent(target, ".o_control_panel", "mouseover"); - assert.verifySteps(["setTimeout of 400ms"]); + await click(document, ".o_action_manager"); assert.containsNone(target, ".o_avatar_card"); }); }); diff --git a/addons/hr_work_entry_holidays/tests/test_performance.py b/addons/hr_work_entry_holidays/tests/test_performance.py index ce12484cf49..6fbc70473e8 100644 --- a/addons/hr_work_entry_holidays/tests/test_performance.py +++ b/addons/hr_work_entry_holidays/tests/test_performance.py @@ -32,7 +32,7 @@ class TestWorkEntryHolidaysPerformance(TestWorkEntryHolidaysBase): self.richard_emp.generate_work_entries(date(2018, 1, 1), date(2018, 1, 2)) leave = self.create_leave(datetime(2018, 1, 1, 7, 0), datetime(2018, 1, 1, 18, 0)) - with self.assertQueryCount(__system__=99, admin=102): + with self.assertQueryCount(__system__=100, admin=103): leave.action_validate() leave.action_refuse() @@ -48,7 +48,7 @@ class TestWorkEntryHolidaysPerformance(TestWorkEntryHolidaysBase): @users('__system__', 'admin') @warmup def test_performance_leave_create(self): - with self.assertQueryCount(__system__=45, admin=45): + with self.assertQueryCount(__system__=46, admin=46): leave = self.create_leave(datetime(2018, 1, 1, 7, 0), datetime(2018, 1, 1, 18, 0)) leave.action_refuse() @@ -57,7 +57,7 @@ class TestWorkEntryHolidaysPerformance(TestWorkEntryHolidaysBase): def test_performance_leave_confirm(self): leave = self.create_leave(datetime(2018, 1, 1, 7, 0), datetime(2018, 1, 1, 18, 0)) leave.action_draft() - with self.assertQueryCount(__system__=41, admin=40): + with self.assertQueryCount(__system__=42, admin=41): leave.action_confirm() leave.state = 'refuse' diff --git a/addons/im_livechat/__manifest__.py b/addons/im_livechat/__manifest__.py index 6e38022287c..8bd00970710 100644 --- a/addons/im_livechat/__manifest__.py +++ b/addons/im_livechat/__manifest__.py @@ -78,7 +78,6 @@ Help your customers with this chat, and analyse their feedback. 'im_livechat/static/src/core_ui/**/*', 'im_livechat/static/src/discuss_app/**/*', 'im_livechat/static/src/messaging_menu/**/*', - 'im_livechat/static/src/web/**/*', 'im_livechat/static/src/scss/im_livechat_history.scss', 'im_livechat/static/src/scss/im_livechat_form.scss', ], diff --git a/addons/im_livechat/static/src/web/message_patch.js b/addons/im_livechat/static/src/web/message_patch.js deleted file mode 100644 index 22e0ea451ab..00000000000 --- a/addons/im_livechat/static/src/web/message_patch.js +++ /dev/null @@ -1,14 +0,0 @@ -/* @odoo-module */ - -import { Message } from "@mail/core/common/message"; -import "@mail/discuss/core/web/message_patch"; // dependency ordering - -import { patch } from "@web/core/utils/patch"; - -patch(Message.prototype, { - hasOpenChatFeature() { - return this.message.originThread?.channel?.channel_type === "livechat" - ? false - : super.hasOpenChatFeature(); - }, -}); diff --git a/addons/im_livechat/static/tests/message_patch_tests.js b/addons/im_livechat/static/tests/message_patch_tests.js deleted file mode 100644 index bb4a8a0de28..00000000000 --- a/addons/im_livechat/static/tests/message_patch_tests.js +++ /dev/null @@ -1,30 +0,0 @@ -/* @odoo-module */ - -import { startServer } from "@bus/../tests/helpers/mock_python_environment"; - -import { Command } from "@mail/../tests/helpers/command"; -import { start } from "@mail/../tests/helpers/test_utils"; - -QUnit.module("message (patch)"); - -QUnit.test("redirect to author (open profile) in livechat", async (assert) => { - const pyEnv = await startServer(); - const partnerId = pyEnv["res.partner"].create({ name: "Demo" }); - pyEnv["res.users"].create({ partner_id: partnerId }); - const channelId = pyEnv["discuss.channel"].create({ - channel_member_ids: [ - Command.create({ partner_id: pyEnv.currentPartnerId }), - Command.create({ partner_id: partnerId }), - ], - channel_type: "livechat", - }); - pyEnv["mail.message"].create({ - author_id: partnerId, - body: "not empty", - model: "discuss.channel", - res_id: channelId, - }); - const { openDiscuss } = await start(); - await openDiscuss(channelId); - assert.containsOnce($, ".o-mail-Message-avatarContainer[title='Open profile']"); -}); diff --git a/addons/im_livechat/tests/test_message.py b/addons/im_livechat/tests/test_message.py index 8f37f037633..8381cfe80f5 100644 --- a/addons/im_livechat/tests/test_message.py +++ b/addons/im_livechat/tests/test_message.py @@ -58,6 +58,10 @@ class TestImLivechatMessage(HttpCase): 'id': self.users[1].partner_id.id, 'is_company': self.users[1].partner_id.is_company, 'name': "test1", + 'user': { + 'id': self.users[1].id, + 'isInternalUser': self.users[1]._is_internal(), + } }, 'body': message.body, 'date': message.date, diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py index 2742fb954f3..add464ff833 100644 --- a/addons/mail/models/mail_message.py +++ b/addons/mail/models/mail_message.py @@ -876,11 +876,7 @@ class Message(models.Model): thread_ids_by_model_name[message.model].add(message.res_id) for vals in vals_list: message_sudo = self.browse(vals['id']).sudo().with_prefetch(self.ids) - author = { - 'id': message_sudo.author_id.id, - 'is_company': message_sudo.author_id.is_company, - 'name': message_sudo.author_id.name, - } if message_sudo.author_id else [('clear',)] + author = message_sudo.author_id.mail_partner_format({'id': True, 'name': True, 'is_company': True, 'user': {"id": True}}).get(message_sudo.author_id) if message_sudo.author_id else [('clear',)] guestAuthor = { 'id': message_sudo.author_guest_id.id, 'name': message_sudo.author_guest_id.name, diff --git a/addons/mail/static/src/core/web/activity.js b/addons/mail/static/src/core/web/activity.js index b04f6ca3a6c..fa6405f2cd2 100644 --- a/addons/mail/static/src/core/web/activity.js +++ b/addons/mail/static/src/core/web/activity.js @@ -4,6 +4,7 @@ import { useAttachmentUploader } from "@mail/core/common/attachment_uploader_hoo import { ActivityMailTemplate } from "@mail/core/web/activity_mail_template"; import { ActivityMarkAsDone } from "@mail/core/web/activity_markasdone_popover"; import { computeDelay, getMsToTomorrow } from "@mail/utils/common/dates"; +import { AvatarCardPopover } from "@mail/discuss/web/avatar_card/avatar_card_popover"; import { Component, onMounted, onWillUnmount, useState } from "@odoo/owl"; @@ -34,6 +35,7 @@ export class Activity extends Component { this.threadService = useService("mail.thread"); this.state = useState({ showDetails: false }); this.popover = usePopover(ActivityMarkAsDone, { position: "right" }); + this.avatarCard = usePopover(AvatarCardPopover); onMounted(() => { this.updateDelayAtNight(); }); @@ -83,6 +85,15 @@ export class Activity extends Component { await this.threadService.fetchNewMessages(this.thread); } + onClickAvatar(ev) { + const target = ev.currentTarget; + if (!this.avatarCard.isOpen) { + this.avatarCard.open(target, { + id: this.props.data.user_id[0], + }); + } + } + async edit() { const { id, res_model, res_id } = this.props.data; await this.env.services["mail.activity"].schedule(res_model, res_id, id); diff --git a/addons/mail/static/src/core/web/activity.xml b/addons/mail/static/src/core/web/activity.xml index 671a988c434..b3e3cdd0ccd 100644 --- a/addons/mail/static/src/core/web/activity.xml +++ b/addons/mail/static/src/core/web/activity.xml @@ -5,7 +5,7 @@
- +
{ 'cursor-pointer': hasAuthorClickable() } - getAuthorText() + getAuthorText() ev => this.onClickAuthor(ev) - getAuthorText() + { 'cursor-pointer': hasAuthorClickable() } + getAuthorText() ev => this.onClickAuthor(ev) diff --git a/addons/mail/static/src/discuss/core/web/message_patch.js b/addons/mail/static/src/discuss/core/web/message_patch.js deleted file mode 100644 index 4b8bbf033b7..00000000000 --- a/addons/mail/static/src/discuss/core/web/message_patch.js +++ /dev/null @@ -1,32 +0,0 @@ -/* @odoo-module */ - -import { Message } from "@mail/core/common/message"; -import "@mail/core/web/message_patch"; // dependency ordering -import { markEventHandled } from "@web/core/utils/misc"; - -import { _t } from "@web/core/l10n/translation"; -import { patch } from "@web/core/utils/patch"; - -patch(Message.prototype, { - getAuthorText() { - return this.hasOpenChatFeature() ? _t("Open chat") : super.getAuthorText(); - }, - hasAuthorClickable() { - return ( - super.hasAuthorClickable() && - this.message.author.type !== "guest" && - this.message.originThread?.channel?.channel_type !== "chat" - ); - }, - hasOpenChatFeature() { - return this.hasAuthorClickable(); - }, - onClickAuthor(ev) { - if (this.hasOpenChatFeature()) { - markEventHandled(ev, "Message.ClickAuthor"); - this.threadService.openChat({ partnerId: this.message.author.id }); - return; - } - return super.onClickAuthor(ev); - }, -}); diff --git a/addons/mail/static/src/discuss/web/avatar_card/avatar_card_popover.js b/addons/mail/static/src/discuss/web/avatar_card/avatar_card_popover.js index c7c6132b6e3..19642baa338 100644 --- a/addons/mail/static/src/discuss/web/avatar_card/avatar_card_popover.js +++ b/addons/mail/static/src/discuss/web/avatar_card/avatar_card_popover.js @@ -21,7 +21,7 @@ export class AvatarCardPopover extends Component { } get fieldNames() { - return ["name", "email", "phone", "im_status"]; + return ["name", "email", "phone", "im_status", "share"]; } get email() { diff --git a/addons/mail/static/src/discuss/web/avatar_card/avatar_card_popover.xml b/addons/mail/static/src/discuss/web/avatar_card/avatar_card_popover.xml index f7cc2dc716d..a0210bca304 100644 --- a/addons/mail/static/src/discuss/web/avatar_card/avatar_card_popover.xml +++ b/addons/mail/static/src/discuss/web/avatar_card/avatar_card_popover.xml @@ -21,17 +21,17 @@ - + - +
- +
diff --git a/addons/mail/static/src/views/web/fields/many2many_avatar_user_field/many2many_avatar_user_field.js b/addons/mail/static/src/views/web/fields/many2many_avatar_user_field/many2many_avatar_user_field.js index eb9d959b0a9..938a978e448 100644 --- a/addons/mail/static/src/views/web/fields/many2many_avatar_user_field/many2many_avatar_user_field.js +++ b/addons/mail/static/src/views/web/fields/many2many_avatar_user_field/many2many_avatar_user_field.js @@ -1,12 +1,10 @@ /* @odoo-module */ -import { useOpenChat } from "@mail/core/web/open_chat_hook"; import { useAssignUserCommand } from "@mail/views/web/fields/assign_user_command_hook"; import { registry } from "@web/core/registry"; import { TagsList } from "@web/core/tags_list/tags_list"; import { usePopover } from "@web/core/popover/popover_hook"; -import { browser } from "@web/core/browser/browser"; import { AvatarCardPopover } from "@mail/discuss/web/avatar_card/avatar_card_popover"; import { Many2ManyTagsAvatarField, @@ -21,30 +19,24 @@ import { export class Many2ManyAvatarUserTagsList extends TagsList {} Many2ManyAvatarUserTagsList.template = "mail.Many2ManyAvatarUserTagsList"; -const WithUserChatter = (T) => class UserChatterMixin extends T { - setup() { - super.setup(...arguments); - this.openChat = useOpenChat(this.relation); - if (this.props.withCommand) { - useAssignUserCommand(); +const WithUserChatter = (T) => + class UserChatterMixin extends T { + setup() { + super.setup(...arguments); + if (this.props.withCommand) { + useAssignUserCommand(); + } + this.avatarCard = usePopover(AvatarCardPopover); } - this.avatarCard = usePopover(AvatarCardPopover, { - closeOnHoverAway: true, - }); - this.openTimeout = false; - this.lastOpenedId = 0; - } - getTagProps(record) { - return { - ...super.getTagProps(...arguments), - onImageClicked: () => this.openChat(record.resId), - openCard: (ev) => { - if (this.env.isSmall || this.relation !== "res.users") { - return; - } - const target = ev.currentTarget; - this.openTimeout = browser.setTimeout(() => { + getTagProps(record) { + return { + ...super.getTagProps(...arguments), + onImageClicked: (ev) => { + if (this.env.isSmall || this.relation !== "res.users") { + return; + } + const target = ev.currentTarget; if ( !this.avatarCard.isOpen || (this.lastOpenedId && record.resId !== this.lastOpenedId) @@ -54,15 +46,10 @@ const WithUserChatter = (T) => class UserChatterMixin extends T { }); this.lastOpenedId = record.resId; } - }, 350); - }, - clearTimeout: () => { - browser.clearTimeout(this.openTimeout); - delete this.openTimeout; - }, - }; - } -} + }, + }; + } + }; export class Many2ManyTagsAvatarUserField extends WithUserChatter(Many2ManyTagsAvatarField) { static components = { @@ -83,7 +70,9 @@ export class KanbanMany2ManyAvatarUserTagsList extends KanbanMany2ManyTagsAvatar static template = "mail.KanbanMany2ManyAvatarUserTagsList"; } -export class KanbanMany2ManyTagsAvatarUserField extends WithUserChatter(KanbanMany2ManyTagsAvatarField) { +export class KanbanMany2ManyTagsAvatarUserField extends WithUserChatter( + KanbanMany2ManyTagsAvatarField +) { static template = "mail.KanbanMany2ManyTagsAvatarUserField"; static components = { ...KanbanMany2ManyTagsAvatarField.components, @@ -100,7 +89,9 @@ export const kanbanMany2ManyTagsAvatarUserField = { }; registry.category("fields").add("kanban.many2many_avatar_user", kanbanMany2ManyTagsAvatarUserField); -export class ListMany2ManyTagsAvatarUserField extends WithUserChatter(ListMany2ManyTagsAvatarField) { +export class ListMany2ManyTagsAvatarUserField extends WithUserChatter( + ListMany2ManyTagsAvatarField +) { static template = "mail.ListMany2ManyTagsAvatarUserField"; static components = { ...ListMany2ManyTagsAvatarField.components, diff --git a/addons/mail/static/src/views/web/fields/many2many_avatar_user_field/many2many_avatar_user_field.xml b/addons/mail/static/src/views/web/fields/many2many_avatar_user_field/many2many_avatar_user_field.xml index 54ce2b3c899..f6f09715ec5 100644 --- a/addons/mail/static/src/views/web/fields/many2many_avatar_user_field/many2many_avatar_user_field.xml +++ b/addons/mail/static/src/views/web/fields/many2many_avatar_user_field/many2many_avatar_user_field.xml @@ -1,24 +1,14 @@ - - - tag.onImageClicked - tag.clearTimeout - tag.openCard - - - tag.onImageClicked - tag.clearTimeout - tag.openCard diff --git a/addons/mail/static/src/views/web/fields/many2one_avatar_user_field/many2one_avatar_user_field.js b/addons/mail/static/src/views/web/fields/many2one_avatar_user_field/many2one_avatar_user_field.js index bf86e690474..311cb9c061b 100644 --- a/addons/mail/static/src/views/web/fields/many2one_avatar_user_field/many2one_avatar_user_field.js +++ b/addons/mail/static/src/views/web/fields/many2one_avatar_user_field/many2one_avatar_user_field.js @@ -1,6 +1,5 @@ /* @odoo-module */ -import { useOpenChat } from "@mail/core/web/open_chat_hook"; import { useAssignUserCommand } from "@mail/views/web/fields/assign_user_command_hook"; import { _t } from "@web/core/l10n/translation"; @@ -12,50 +11,31 @@ import { kanbanMany2OneAvatarField, } from "@web/views/fields/many2one_avatar/many2one_avatar_field"; import { usePopover } from "@web/core/popover/popover_hook"; -import { browser } from "@web/core/browser/browser"; import { AvatarCardPopover } from "@mail/discuss/web/avatar_card/avatar_card_popover"; const WithUserChatter = (T) => class extends T { setup() { super.setup(...arguments); - this.openChat = useOpenChat(this.relation); if (this.props.withCommand) { useAssignUserCommand(); } - this.avatarCard = usePopover(AvatarCardPopover, { - closeOnHoverAway: true, - }); - this.openTimeout = false; + this.avatarCard = usePopover(AvatarCardPopover); } - onClickAvatar() { + onClickAvatar(ev) { const id = this.props.record.data[this.props.name][0] ?? false; if (id !== false) { - this.openChat(id); - } - } - - openCard(ev) { - if (this.env.isSmall || this.relation !== "res.users") { - return; - } - const target = ev.currentTarget; - if (!target.querySelector(":scope > img")) { - return; - } - this.openTimeout = browser.setTimeout(() => { + if (this.env.isSmall || this.relation !== "res.users") { + return; + } + const target = ev.currentTarget; if (!this.avatarCard.isOpen) { this.avatarCard.open(target, { id: this.props.record.data[this.props.name][0], }); } - }, 350); - } - - clearTimeout() { - browser.clearTimeout(this.openTimeout); - delete this.openTimeout; + } } }; diff --git a/addons/mail/static/src/views/web/fields/many2one_avatar_user_field/many2one_avatar_user_field.xml b/addons/mail/static/src/views/web/fields/many2one_avatar_user_field/many2one_avatar_user_field.xml index bb1ec4edcd0..e1d0c16cf84 100644 --- a/addons/mail/static/src/views/web/fields/many2one_avatar_user_field/many2one_avatar_user_field.xml +++ b/addons/mail/static/src/views/web/fields/many2one_avatar_user_field/many2one_avatar_user_field.xml @@ -1,24 +1,14 @@ - - - onClickAvatar - clearTimeout - openCard - - - onClickAvatar - clearTimeout - openCard props.readonly and props.displayAvatarName diff --git a/addons/mail/static/tests/discuss_app/discuss_tests.js b/addons/mail/static/tests/discuss_app/discuss_tests.js index 033f2bf51eb..6104b08b758 100644 --- a/addons/mail/static/tests/discuss_app/discuss_tests.js +++ b/addons/mail/static/tests/discuss_app/discuss_tests.js @@ -156,7 +156,12 @@ QUnit.test( QUnit.test("Click on avatar opens its partner chat window", async () => { const pyEnv = await startServer(); const partnerId = pyEnv["res.partner"].create({ name: "testPartner" }); - pyEnv["res.users"].create({ partner_id: partnerId }); + pyEnv["res.users"].create({ + partner_id: partnerId, + name: "testPartner", + email: "test@partner.com", + phone: "+45687468", + }); pyEnv["mail.message"].create({ author_id: partnerId, body: "Test", @@ -168,7 +173,10 @@ QUnit.test("Click on avatar opens its partner chat window", async () => { await openFormView("res.partner", partnerId); await contains(".o-mail-Message-sidebar .o-mail-Message-avatarContainer img"); await click(".o-mail-Message-sidebar .o-mail-Message-avatarContainer img"); - await contains(".o-mail-ChatWindow-name", { text: "testPartner" }); + await contains(".o_avatar_card"); + await contains(".o_card_user_infos > span:contains(testPartner)"); + await contains(".o_card_user_infos > a:contains(test@partner.com)"); + await contains(".o_card_user_infos > a:contains(+45687468)"); }); QUnit.test("Can use channel command /who", async () => { diff --git a/addons/mail/static/tests/helpers/mock_server/models/mail_message.js b/addons/mail/static/tests/helpers/mock_server/models/mail_message.js index 0d5fb285419..15f43d78db7 100644 --- a/addons/mail/static/tests/helpers/mock_server/models/mail_message.js +++ b/addons/mail/static/tests/helpers/mock_server/models/mail_message.js @@ -200,11 +200,17 @@ patch(MockServer.prototype, { const [author] = this.getRecords("res.partner", [["id", "=", message.author_id]], { active_test: false, }); + const [user] = this.getRecords("res.users", [ + ["partner_id", "=", message.author_id], + ]); formattedAuthor = { id: author.id, is_company: author.is_company, name: author.name, }; + if (user) { + formattedAuthor["user"] = { id: user.id }; + } } else { formattedAuthor = [["clear"]]; } diff --git a/addons/mail/static/tests/message/message_tests.js b/addons/mail/static/tests/message/message_tests.js index 6644fb7be14..faf09eae2c9 100644 --- a/addons/mail/static/tests/message/message_tests.js +++ b/addons/mail/static/tests/message/message_tests.js @@ -665,10 +665,15 @@ QUnit.test("message comment of same author within 1min. should be squashed", asy await contains(".o-mail-Message:contains(body2) .o-mail-Message-sidebar .o-mail-Message-date"); }); -QUnit.test("redirect to author (open chat)", async () => { +QUnit.test("open author avatar card", async () => { const pyEnv = await startServer(); const partnerId = pyEnv["res.partner"].create({ name: "Demo" }); - pyEnv["res.users"].create({ partner_id: partnerId }); + pyEnv["res.users"].create({ + partner_id: partnerId, + name: "Demo", + email: "demo@example.com", + phone: "+5646548", + }); const [channelId_1] = pyEnv["discuss.channel"].create([ { name: "General" }, { @@ -691,32 +696,10 @@ QUnit.test("redirect to author (open chat)", async () => { await contains(".o-mail-Discuss-content .o-mail-Message-avatarContainer img"); await click(".o-mail-Discuss-content .o-mail-Message-avatarContainer img"); - await contains(".o-mail-DiscussSidebarChannel.o-active", { text: "Demo" }); -}); - -QUnit.test("open chat from avatar should not work on self-authored messages", async () => { - const pyEnv = await startServer(); - const [channelId] = pyEnv["discuss.channel"].create([ - { name: "General" }, - { - channel_member_ids: [Command.create({ partner_id: pyEnv.currentPartnerId })], - channel_type: "chat", - }, - ]); - pyEnv["mail.message"].create({ - author_id: pyEnv.currentPartnerId, - body: "not empty", - model: "discuss.channel", - res_id: channelId, - }); - const { openDiscuss } = await start(); - openDiscuss(channelId); - await contains(".o-mail-Message-avatarContainer:not(.cursor-pointer)"); - await contains(".o-mail-Message-author:not(.cursor-pointer)"); - await click(".o-mail-Message-avatar"); - // weak test, no guarantee that we waited long enough to open the chat/view - await contains(".o-mail-DiscussSidebarChannel.o-active", { count: 0, text: "Mitchell Admin" }); - await contains(".breadcrumb", { count: 0, text: "Mitchell Admin" }); + await contains(".o_avatar_card"); + await contains(".o_card_user_infos > span:contains(Demo)"); + await contains(".o_card_user_infos > a:contains(demo@example.com)"); + await contains(".o_card_user_infos > a:contains(+5646548)"); }); QUnit.test("toggle_star message", async (assert) => { @@ -1126,32 +1109,47 @@ QUnit.test("allow attachment image download on message", async () => { await contains(".o-mail-AttachmentImage .fa-download"); }); -QUnit.test("chat with author should be opened after clicking on their avatar", async (assert) => { - const pyEnv = await startServer(); - const [partnerId_1, partnerId_2] = pyEnv["res.partner"].create([ - { name: "Partner_1" }, - { name: "Partner_2" }, - ]); - pyEnv["res.users"].create({ partner_id: partnerId_2 }); - pyEnv["mail.message"].create({ - author_id: partnerId_2, - body: "not empty", - model: "res.partner", - res_id: partnerId_1, - }); - const { openFormView } = await start(); - openFormView("res.partner", partnerId_1); - await contains(".o-mail-Message-avatar"); - assert.hasClass($(".o-mail-Message-avatarContainer"), "cursor-pointer"); - await click(".o-mail-Message-avatar"); - await contains(".o-mail-ChatWindow-content"); - await contains(".o-mail-ChatWindow-name", { text: "Partner_2" }); -}); +QUnit.test( + "avatar card from author should be opened after clicking on their avatar", + async (assert) => { + const pyEnv = await startServer(); + const [partnerId_1, partnerId_2] = pyEnv["res.partner"].create([ + { name: "Partner_1" }, + { name: "Partner_2" }, + ]); + pyEnv["res.users"].create({ + partner_id: partnerId_2, + name: "Partner_2", + email: "partner2@mail.com", + phone: "+15968415", + }); + pyEnv["mail.message"].create({ + author_id: partnerId_2, + body: "not empty", + model: "res.partner", + res_id: partnerId_1, + }); + const { openFormView } = await start(); + await openFormView("res.partner", partnerId_1); + await contains(".o-mail-Message-avatar"); + assert.hasClass($(".o-mail-Message-avatarContainer"), "cursor-pointer"); + await click(".o-mail-Message-avatar"); + await contains(".o_avatar_card"); + await contains(".o_card_user_infos > span:contains(Partner_2)"); + await contains(".o_card_user_infos > a:contains(partner2@mail.com)"); + await contains(".o_card_user_infos > a:contains(+15968415)"); + } +); -QUnit.test("chat with author should be opened after clicking on their name", async () => { +QUnit.test("avatar card from author should be opened after clicking on their name", async () => { const pyEnv = await startServer(); - const partnerId = pyEnv["res.partner"].create({ name: "Demo User" }); - pyEnv["res.users"].create({ partner_id: partnerId }); + const partnerId = pyEnv["res.partner"].create({ name: "Demo" }); + pyEnv["res.users"].create({ + partner_id: partnerId, + name: "Demo", + email: "demo@example.com", + phone: "+5646548", + }); pyEnv["mail.message"].create({ author_id: partnerId, body: "not empty", @@ -1159,12 +1157,14 @@ QUnit.test("chat with author should be opened after clicking on their name", asy res_id: partnerId, }); const { openFormView } = await start(); - openFormView("res.partner", partnerId); - await contains(".o-mail-Message span", { text: "Demo User" }); + await openFormView("res.partner", partnerId); + await contains(".o-mail-Message span", { text: "Demo" }); - await click(".o-mail-Message span", { text: "Demo User" }); - await contains(".o-mail-ChatWindow"); - await contains(".o-mail-ChatWindow-name", { text: "Demo User" }); + await click(".o-mail-Message span:contains(Demo)"); + await contains(".o_avatar_card"); + await contains(".o_card_user_infos > span:contains(Demo)"); + await contains(".o_card_user_infos > a:contains(demo@example.com)"); + await contains(".o_card_user_infos > a:contains(+5646548)"); }); QUnit.test("subtype description should be displayed if it is different than body", async () => { @@ -1238,36 +1238,6 @@ QUnit.test("Chat with partner should be opened after clicking on their mention", await contains(".o-mail-ChatWindow-name", { text: "Test Partner" }); }); -QUnit.test( - "open chat with author on avatar click should be disabled when currently chatting with the author", - async (assert) => { - const pyEnv = await startServer(); - const partnerId = pyEnv["res.partner"].create({ name: "test" }); - pyEnv["res.users"].create({ partner_id: partnerId }); - const channelId = pyEnv["discuss.channel"].create({ - name: "test", - channel_member_ids: [ - Command.create({ partner_id: pyEnv.currentPartnerId }), - Command.create({ partner_id: partnerId }), - ], - channel_type: "chat", - }); - pyEnv["mail.message"].create({ - author_id: partnerId, - body: "not empty", - model: "discuss.channel", - res_id: channelId, - }); - const { openDiscuss } = await start(); - openDiscuss(channelId); - await contains(".o-mail-Message-avatar"); - assert.doesNotHaveClass($(".o-mail-Message-avatarContainer"), "cursor-pointer"); - await click(".o-mail-Message-avatar"); - // weak test, no guarantee that we waited long enough for the potential chat window to show - await contains(".o-mail-ChatWindow", { count: 0 }); - } -); - QUnit.test("Channel should be opened after clicking on its mention", async () => { const pyEnv = await startServer(); const partnerId = pyEnv["res.partner"].create({}); diff --git a/addons/mail/static/tests/web/fields/m2x_avatar_user_tests.js b/addons/mail/static/tests/web/fields/m2x_avatar_user_tests.js index 7c5f2f3e904..4b009cffec3 100644 --- a/addons/mail/static/tests/web/fields/m2x_avatar_user_tests.js +++ b/addons/mail/static/tests/web/fields/m2x_avatar_user_tests.js @@ -10,8 +10,8 @@ import { browser } from "@web/core/browser/browser"; import { popoverService } from "@web/core/popover/popover_service"; import { registry } from "@web/core/registry"; import { tooltipService } from "@web/core/tooltip/tooltip_service"; -import { patchWithCleanup, triggerHotkey, getNodesTextContent } from "@web/../tests/helpers/utils"; -import { click, contains, triggerEvents } from "@web/../tests/utils"; +import { patchWithCleanup, triggerHotkey } from "@web/../tests/helpers/utils"; +import { click, contains } from "@web/../tests/utils"; const fakeMultiTab = { start() { @@ -409,7 +409,7 @@ QUnit.test("avatar card preview", async (assert) => { }); const mockRPC = (route, args) => { if (route === "/web/dataset/call_kw/res.users/read") { - assert.deepEqual(args.args[1], ["name", "email", "phone", "im_status"]); + assert.deepEqual(args.args[1], ["name", "email", "phone", "im_status", "share"]); assert.step("user read"); } }; @@ -440,17 +440,14 @@ QUnit.test("avatar card preview", async (assert) => { }, }); // Open card - await triggerEvents(".o_m2o_avatar > img", ["mouseover"]); + await click(".o_m2o_avatar > img"); await contains(".o_avatar_card"); - assert.verifySteps(["setTimeout of 350ms", "setTimeout of 250ms", "user read"]); - assert.deepEqual(getNodesTextContent(document.querySelectorAll(".o_card_user_infos > *")), [ - "Mario", - " Mario@odoo.test", - " +78786987", - ]); + await contains(".o_card_user_infos > span:contains(Mario)"); + await contains(".o_card_user_infos > a:contains(Mario@odoo.test)"); + await contains(".o_card_user_infos > a:contains(+78786987)"); + assert.verifySteps(["setTimeout of 250ms", "user read"]); // Close card - await triggerEvents(".o_control_panel", ["mouseover"]); - assert.verifySteps(["setTimeout of 400ms"]); + await click(".o_action_manager"); await contains(".o_avatar_card", { count: 0 }); }); @@ -478,7 +475,12 @@ QUnit.test("avatar_user widget displays the appropriate user image in form view" QUnit.test("many2one_avatar_user widget in list view", async () => { const pyEnv = await startServer(); const partnerId = pyEnv["res.partner"].create({ name: "Partner 1" }); - const userId = pyEnv["res.users"].create({ name: "Mario", partner_id: partnerId }); + const userId = pyEnv["res.users"].create({ + name: "Mario", + partner_id: partnerId, + email: "Mario@partner.com", + phone: "+45687468", + }); pyEnv["m2x.avatar.user"].create({ user_id: userId }); const views = { "m2x.avatar.user,false,list": @@ -490,13 +492,21 @@ QUnit.test("many2one_avatar_user widget in list view", async () => { views: [[false, "list"]], }); await click(".o_data_cell .o_m2o_avatar > img"); - await contains(".o-mail-ChatWindow-name", { text: "Partner 1" }); + await contains(".o_avatar_card"); + await contains(".o_card_user_infos > span:contains(Mario)"); + await contains(".o_card_user_infos > a:contains(Mario@partner.com)"); + await contains(".o_card_user_infos > a:contains(+45687468)"); }); QUnit.test("many2many_avatar_user widget in form view", async () => { const pyEnv = await startServer(); const partnerId = pyEnv["res.partner"].create({ name: "Partner 1" }); - const userId = pyEnv["res.users"].create({ name: "Mario", partner_id: partnerId }); + const userId = pyEnv["res.users"].create({ + name: "Mario", + partner_id: partnerId, + email: "Mario@partner.com", + phone: "+45687468", + }); const avatarUserId = pyEnv["m2x.avatar.user"].create({ user_ids: [userId] }); const views = { "m2x.avatar.user,false,form": ` @@ -511,5 +521,8 @@ QUnit.test("many2many_avatar_user widget in form view", async () => { views: [[false, "form"]], }); await click(".o_field_many2many_avatar_user .o_avatar img"); - await contains(".o-mail-ChatWindow-name", { text: "Partner 1" }); + await contains(".o_avatar_card"); + await contains(".o_card_user_infos > span:contains(Mario)"); + await contains(".o_card_user_infos > a:contains(Mario@partner.com)"); + await contains(".o_card_user_infos > a:contains(+45687468)"); }); diff --git a/addons/test_mail/tests/test_performance.py b/addons/test_mail/tests/test_performance.py index 45e5862f871..7cd848be2a9 100644 --- a/addons/test_mail/tests/test_performance.py +++ b/addons/test_mail/tests/test_performance.py @@ -401,7 +401,7 @@ class TestMailAPIPerformance(BaseMailPerformance): composer_form.attachment_ids.add(attachment) composer = composer_form.save() - with self.assertQueryCount(admin=53, employee=53): # tm+com 46/46 + with self.assertQueryCount(admin=54, employee=54): # tm+com 46/46 composer._action_send_mail() # notifications @@ -463,7 +463,7 @@ class TestMailAPIPerformance(BaseMailPerformance): 'default_template_id': test_template.id, }).create({}) - with self.assertQueryCount(admin=40, employee=40): # com 36/36 + with self.assertQueryCount(admin=41, employee=41): # com 36/36 composer._action_send_mail() # notifications @@ -487,7 +487,7 @@ class TestMailAPIPerformance(BaseMailPerformance): 'default_template_id': test_template.id, }).create({}) - with self.assertQueryCount(admin=49, employee=49): # com 43/43 + with self.assertQueryCount(admin=50, employee=50): # com 43/43 composer._action_send_mail() # notifications @@ -519,7 +519,7 @@ class TestMailAPIPerformance(BaseMailPerformance): ) composer = composer_form.save() - with self.assertQueryCount(admin=48, employee=48): # com 44/44 + with self.assertQueryCount(admin=50, employee=50): # com 44/44 composer._action_send_mail() # notifications @@ -549,7 +549,7 @@ class TestMailAPIPerformance(BaseMailPerformance): ) composer = composer_form.save() - with self.assertQueryCount(admin=67, employee=67): # com 61/61 + with self.assertQueryCount(admin=69, employee=69): # com 61/61 composer._action_send_mail() # notifications @@ -585,7 +585,7 @@ class TestMailAPIPerformance(BaseMailPerformance): @warmup def test_message_assignation_inbox(self): record = self.env['mail.test.track'].create({'name': 'Test'}) - with self.assertQueryCount(admin=22, employee=22): # com 19/9 + with self.assertQueryCount(admin=23, employee=23): # com 19/9 record.write({ 'user_id': self.user_test_inbox.id, }) @@ -671,7 +671,7 @@ class TestMailAPIPerformance(BaseMailPerformance): def test_message_post_one_inbox_notification(self): record = self.env['mail.test.simple'].create({'name': 'Test'}) - with self.assertQueryCount(admin=20, employee=20): # com 18/18 + with self.assertQueryCount(admin=21, employee=21): # com 18/18 record.message_post( body=Markup('

Test Post Performances with an inbox ping

'), partner_ids=self.user_test.partner_id.ids, @@ -1228,7 +1228,7 @@ class TestMailComplexPerformance(BaseMailPerformance): """ messages_all = self.messages_all.with_env(self.env) - with self.assertQueryCount(employee=25): # test_mail: 24 + with self.assertQueryCount(employee=26): # test_mail: 25 res = messages_all.message_format() self.assertEqual(len(res), 2*2) @@ -1241,7 +1241,7 @@ class TestMailComplexPerformance(BaseMailPerformance): def test_message_format_single(self): message = self.messages_all[0].with_env(self.env) - with self.assertQueryCount(employee=22): # test_mail: 21 + with self.assertQueryCount(employee=23): # test_mail: 22 res = message.message_format() self.assertEqual(len(res), 1) @@ -1262,14 +1262,14 @@ class TestMailComplexPerformance(BaseMailPerformance): 'res_id': record.id } for record in records]) - with self.assertQueryCount(employee=6): + with self.assertQueryCount(employee=7): res = messages.message_format() self.assertEqual(len(res), 6) self.env.flush_all() self.env.invalidate_all() - with self.assertQueryCount(employee=14): + with self.assertQueryCount(employee=15): res = messages.message_format() self.assertEqual(len(res), 6) @@ -1346,7 +1346,7 @@ class TestMailHeavyPerformancePost(BaseMailPerformance): attachments = self.env['ir.attachment'].with_user(self.env.user).create(self.test_attachments_vals) # enable_logging = self.cr._enable_logging() if self.warm else nullcontext() # with self.assertQueryCount(employee=63), enable_logging: - with self.assertQueryCount(employee=65): # com 60 + with self.assertQueryCount(employee=66): # com 60 record_container.with_context({}).message_post( body=Markup('

Test body

'), subject='Test Subject', diff --git a/addons/test_mail_full/tests/test_mail_performance.py b/addons/test_mail_full/tests/test_mail_performance.py index 7eeaa92f41f..0f7ff891dff 100644 --- a/addons/test_mail_full/tests/test_mail_performance.py +++ b/addons/test_mail_full/tests/test_mail_performance.py @@ -91,7 +91,7 @@ class TestMailPerformance(FullBaseMailPerformance): record_ticket = self.env['mail.test.ticket.mc'].browse(self.record_ticket.ids) attachments = self.env['ir.attachment'].create(self.test_attachments_vals) - with self.assertQueryCount(employee=95): # test_mail_full: 88 + with self.assertQueryCount(employee=96): # test_mail_full: 88 new_message = record_ticket.message_post( attachment_ids=attachments.ids, body=Markup('

Test Content

'), diff --git a/addons/test_mail_full/tests/test_rating.py b/addons/test_mail_full/tests/test_rating.py index 223a5d3514f..cb6c9ed5196 100644 --- a/addons/test_mail_full/tests/test_rating.py +++ b/addons/test_mail_full/tests/test_rating.py @@ -172,10 +172,10 @@ class TestRatingPerformance(TestRatingCommon): @users('employee') @warmup def test_rating_last_value_perfs(self): - with self.assertQueryCount(employee=1614): # tmf 1313 / com 1313 + with self.assertQueryCount(employee=1615): # tmf 1313 / com 1313 self.create_ratings('mail.test.rating.thread') - with self.assertQueryCount(employee=2001): # tmf 1901 + with self.assertQueryCount(employee=2101): # tmf 1901 self.apply_ratings(1) with self.assertQueryCount(employee=1900): # tmf 1800 @@ -184,10 +184,10 @@ class TestRatingPerformance(TestRatingCommon): @users('employee') @warmup def test_rating_last_value_perfs_with_rating_mixin(self): - with self.assertQueryCount(employee=1721): # tmf 1419 / com 1419 + with self.assertQueryCount(employee=1722): # tmf 1419 / com 1419 self.create_ratings('mail.test.rating') - with self.assertQueryCount(employee=2204): # tmf 2104 + with self.assertQueryCount(employee=2304): # tmf 2104 self.apply_ratings(1) with self.assertQueryCount(employee=2203): # tmf 2103 diff --git a/addons/web/static/src/core/popover/popover_controller.js b/addons/web/static/src/core/popover/popover_controller.js index bf126f6dbb7..2c835e7d098 100644 --- a/addons/web/static/src/core/popover/popover_controller.js +++ b/addons/web/static/src/core/popover/popover_controller.js @@ -4,7 +4,6 @@ import { Component, onWillDestroy, useExternalListener, xml } from "@odoo/owl"; import { useHotkey } from "../hotkeys/hotkey_hook"; import { useChildRef } from "../utils/hooks"; import { Popover } from "./popover"; -import { browser } from "../browser/browser"; export class PopoverController extends Component { static template = xml` @@ -17,7 +16,6 @@ export class PopoverController extends Component { "target", "close", "closeOnClickAway", - "closeOnHoverAway", "component", "componentProps", "popoverProps", @@ -27,10 +25,6 @@ export class PopoverController extends Component { if (this.props.target.isConnected) { this.popoverRef = useChildRef(); useExternalListener(window, "mousedown", this.onClickAway, { capture: true }); - if (this.props.closeOnHoverAway) { - this.closePopoverTimeout = false; - useExternalListener(window, "mouseover", this.onHoverAway, { capture: true }); - } useHotkey("escape", () => this.props.close()); const targetObserver = new MutationObserver(this.onTargetMutate.bind(this)); targetObserver.observe(this.props.target.parentElement, { childList: true }); @@ -51,17 +45,6 @@ export class PopoverController extends Component { } } - onHoverAway(ev) { - const target = ev.composedPath()[0]; - if (!this.props.target.contains(target) && !this.popoverRef.el.contains(target)) { - this.closePopoverTimeout = browser.setTimeout(() => { - this.props.close(); - }, 400); - } else { - browser.clearTimeout(this.closePopoverTimeout); - } - } - onTargetMutate() { if (!this.props.target.isConnected) { this.props.close(); diff --git a/addons/web/static/src/core/popover/popover_service.js b/addons/web/static/src/core/popover/popover_service.js index af74f47a22c..44851283998 100644 --- a/addons/web/static/src/core/popover/popover_service.js +++ b/addons/web/static/src/core/popover/popover_service.js @@ -7,7 +7,6 @@ import { PopoverController } from "./popover_controller"; /** * @typedef {{ * closeOnClickAway?: boolean | (target: HTMLElement) => boolean; - * closeOnHoverAway?: boolean; * onClose?: () => void; * popoverClass?: string; * position?: import("@web/core/position_hook").Options["position"]; @@ -32,14 +31,12 @@ export const popoverService = { typeof options.closeOnClickAway === "function" ? options.closeOnClickAway : () => options.closeOnClickAway ?? true; - const closeOnHoverAway = options.closeOnHoverAway || false; const remove = overlay.add( PopoverController, { target, close: () => remove(), closeOnClickAway, - closeOnHoverAway, component, componentProps: markRaw(props), popoverProps: {