From 78c2dba93fb3d4f5149e2ff681a1eb708618f8b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20K=C3=BChn?= Date: Fri, 18 Aug 2023 11:39:52 +0000 Subject: [PATCH] [FIX] mail: show full company logo in avatar of message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before this commit, avatar url of author of message always had style `.o_object_fit_cover`, which makes a nice crop when ratio of image does not match ratio of the `img`. When the author of message is a company, it shows the logo of the company, and it doesn't look nice to crop it. This commit fixes the issue by using `.o_object_fit_contain` for avatar of company, so that the logo is fully visible. When displaying the avatar of a partner whose `is_company` is undefined, the data is group-fetched, in order for models to eventually know the value of `is_company` and use the correct desirable showing. Task-3381748 closes odoo/odoo#133311 X-original-commit: 48194366f2364e2eb9d99dc4fb71e2de83d8f7a2 Signed-off-by: Alexandre Kühn (aku) --- .../tests/helpers/model_definitions_setup.js | 1 + addons/im_livechat/tests/test_message.py | 1 + addons/mail/models/mail_message.py | 1 + addons/mail/models/res_partner.py | 4 +- addons/mail/static/src/core/common/message.js | 7 +++ .../mail/static/src/core/common/message.xml | 2 +- .../static/src/core/common/persona_model.js | 2 + .../static/src/core/common/persona_service.js | 49 ++++++++++++++++++- .../static/src/core/common/thread_service.js | 15 +++++- .../mail/static/src/core/web/message_patch.js | 6 +++ .../static/src/core/web/message_patch.xml | 1 - .../mock_server/models/mail_message.js | 1 + .../helpers/mock_server/models/res_partner.js | 1 + .../tests/test_performance.py | 17 +++++++ addons/web/static/src/legacy/scss/ui.scss | 4 ++ 15 files changed, 107 insertions(+), 5 deletions(-) diff --git a/addons/bus/static/tests/helpers/model_definitions_setup.js b/addons/bus/static/tests/helpers/model_definitions_setup.js index fb497076d95..3c660d3d035 100644 --- a/addons/bus/static/tests/helpers/model_definitions_setup.js +++ b/addons/bus/static/tests/helpers/model_definitions_setup.js @@ -28,6 +28,7 @@ addModelNamesToFetch([ insertModelFields("res.partner", { description: { string: "description", type: "text" }, + is_company: { default: () => false }, }); //-------------------------------------------------------------------------- diff --git a/addons/im_livechat/tests/test_message.py b/addons/im_livechat/tests/test_message.py index 8e9e8d204be..8f37f037633 100644 --- a/addons/im_livechat/tests/test_message.py +++ b/addons/im_livechat/tests/test_message.py @@ -56,6 +56,7 @@ class TestImLivechatMessage(HttpCase): 'attachment_ids': [], 'author': { 'id': self.users[1].partner_id.id, + 'is_company': self.users[1].partner_id.is_company, 'name': "test1", }, 'body': message.body, diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py index 08166eb11f5..2742fb954f3 100644 --- a/addons/mail/models/mail_message.py +++ b/addons/mail/models/mail_message.py @@ -878,6 +878,7 @@ class Message(models.Model): 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',)] guestAuthor = { diff --git a/addons/mail/models/res_partner.py b/addons/mail/models/res_partner.py index 991346d0e8a..8f9cffe8f55 100644 --- a/addons/mail/models/res_partner.py +++ b/addons/mail/models/res_partner.py @@ -216,7 +216,7 @@ class Partner(models.Model): def mail_partner_format(self, fields=None): partners_format = dict() if not fields: - fields = {'id': True, 'name': True, 'email': True, 'active': True, 'im_status': True, 'user': {}} + fields = {'id': True, 'name': True, 'email': True, 'active': True, 'im_status': True, 'is_company': True, 'user': {}} for partner in self: data = {} if 'id' in fields: @@ -229,6 +229,8 @@ class Partner(models.Model): data['active'] = partner.active if 'im_status' in fields: data['im_status'] = partner.im_status + if 'is_company' in fields: + data['is_company'] = partner.is_company if 'user' in fields: internal_users = partner.user_ids - partner.user_ids.filtered('share') main_user = internal_users[0] if len(internal_users) > 0 else partner.user_ids[0] if len(partner.user_ids) > 0 else self.env['res.users'] diff --git a/addons/mail/static/src/core/common/message.js b/addons/mail/static/src/core/common/message.js index 5384d243bdd..3649f756ab7 100644 --- a/addons/mail/static/src/core/common/message.js +++ b/addons/mail/static/src/core/common/message.js @@ -164,6 +164,13 @@ export class Message extends Component { }; } + get authorAvatarAttClass() { + return { + o_object_fit_contain: this.props.message.author?.is_company, + o_object_fit_cover: !this.props.message.author?.is_company, + }; + } + get authorAvatarUrl() { if ( this.message.type === "email" && diff --git a/addons/mail/static/src/core/common/message.xml b/addons/mail/static/src/core/common/message.xml index 822b210728a..79b6b7b37ef 100644 --- a/addons/mail/static/src/core/common/message.xml +++ b/addons/mail/static/src/core/common/message.xml @@ -16,7 +16,7 @@
- +
diff --git a/addons/mail/static/src/core/common/persona_model.js b/addons/mail/static/src/core/common/persona_model.js index b9a93a3363f..583a9badafb 100644 --- a/addons/mail/static/src/core/common/persona_model.js +++ b/addons/mail/static/src/core/common/persona_model.js @@ -17,6 +17,8 @@ export class Persona extends Record { localId; /** @type {number} */ id; + /** @type {boolean | undefined} */ + is_company; /** @type {'partner' | 'guest'} */ type; /** @type {string} */ diff --git a/addons/mail/static/src/core/common/persona_service.js b/addons/mail/static/src/core/common/persona_service.js index ea18cff4b8a..e71bbd41c68 100644 --- a/addons/mail/static/src/core/common/persona_service.js +++ b/addons/mail/static/src/core/common/persona_service.js @@ -4,6 +4,8 @@ import { Persona } from "@mail/core/common/persona_model"; import { assignDefined, createLocalId, nullifyClearCommands } from "@mail/utils/common/misc"; import { registry } from "@web/core/registry"; +import { useSequential } from "@mail/utils/common/hooks"; +import { markRaw } from "@odoo/owl"; export const DEFAULT_AVATAR = "/mail/static/src/img/smiley/avatar.jpg"; @@ -19,8 +21,15 @@ export class PersonaService { setup(env, services) { this.env = env; this.rpc = services.rpc; + this.orm = services.orm; /** @type {import("@mail/core/common/store_service").Store} */ this.store = services["mail.store"]; + this.sequential = useSequential(); + /** Queue used for handling sequential of fetching is_company of persona */ + this._sQueue = markRaw({ + /** @type {Set} */ + todo: new Set(), + }); } async updateGuestName(guest, name) { @@ -61,6 +70,44 @@ export class PersonaService { } } + async fetchIsCompany(persona) { + if (persona.type !== "partner") { + // non-partner persona are always considered as not a company + persona.is_company = false; + return; + } + this._sQueue.todo.add(persona.id); + await new Promise(setTimeout); // group synchronous request to fetch is_company + await this.sequential(async () => { + const ongoing = new Set(); + if (this._sQueue.todo.size === 0) { + return; + } + // load 'todo' into 'ongoing' + this._sQueue.todo.forEach((id) => ongoing.add(id)); + this._sQueue.todo.clear(); + // fetch is_company + const partnerData = await this.orm.silent.read( + "res.partner", + [...ongoing], + ["is_company"], + { + context: { active_test: false }, + } + ); + for (const { id, is_company } of partnerData) { + this.insert({ id, is_company, type: "partner" }); + ongoing.delete(id); + this._sQueue.todo.delete(id); + } + for (const id of ongoing) { + // no is_company found => assumes persona is not a company + this.insert({ id, is_company: false, type: "partner" }); + this._sQueue.todo.delete(id); + } + }); + } + /** * List of known partner ids with a direct chat, ordered * by most recent interest (1st item being the most recent) @@ -87,7 +134,7 @@ export class PersonaService { } export const personaService = { - dependencies: ["rpc", "mail.store"], + dependencies: ["orm", "rpc", "mail.store"], /** * @param {import("@web/env").OdooEnv} env * @param {Partial} services diff --git a/addons/mail/static/src/core/common/thread_service.js b/addons/mail/static/src/core/common/thread_service.js index 5841839dcd0..4219f75f69d 100644 --- a/addons/mail/static/src/core/common/thread_service.js +++ b/addons/mail/static/src/core/common/thread_service.js @@ -50,6 +50,7 @@ export class ThreadService { this.router = services.router; this.ui = services.ui; this.messageService = services["mail.message"]; + this.personaService = services["mail.persona"]; } /** @@ -1085,6 +1086,9 @@ export class ThreadService { if (!persona) { return DEFAULT_AVATAR; } + if (persona.is_company === undefined) { + this.personaService.fetchIsCompany(persona); + } if (thread?.model === "discuss.channel") { if (persona.type === "partner") { return url(`/discuss/channel/${thread.id}/partner/${persona.id}/avatar_128`); @@ -1148,7 +1152,16 @@ export class ThreadService { } export const threadService = { - dependencies: ["mail.store", "orm", "rpc", "notification", "router", "mail.message", "ui"], + dependencies: [ + "mail.store", + "orm", + "rpc", + "notification", + "router", + "mail.message", + "mail.persona", + "ui", + ], /** * @param {import("@web/env").OdooEnv} env * @param {Partial} services diff --git a/addons/mail/static/src/core/web/message_patch.js b/addons/mail/static/src/core/web/message_patch.js index 2b6cb290b18..00fb0f7fe6e 100644 --- a/addons/mail/static/src/core/web/message_patch.js +++ b/addons/mail/static/src/core/web/message_patch.js @@ -23,6 +23,12 @@ patch(Message.prototype, { this.userService = useService("user"); this.messaging = useState(useService("mail.messaging")); }, + get authorAvatarAttClass() { + return { + ...super.authorAvatarAttClass, + "o_redirect cursor-pointer": this.hasAuthorClickable, + }; + }, getAuthorText() { return this.hasAuthorClickable() ? _t("Open profile") : undefined; }, diff --git a/addons/mail/static/src/core/web/message_patch.xml b/addons/mail/static/src/core/web/message_patch.xml index b585c30687a..5790fd51ead 100644 --- a/addons/mail/static/src/core/web/message_patch.xml +++ b/addons/mail/static/src/core/web/message_patch.xml @@ -33,7 +33,6 @@ ev => this.onClickAuthor(ev) - { 'cursor-pointer': hasAuthorClickable() } getAuthorText() ev => this.onClickAuthor(ev) 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 69c646e0db3..0d5fb285419 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 @@ -202,6 +202,7 @@ patch(MockServer.prototype, { }); formattedAuthor = { id: author.id, + is_company: author.is_company, name: author.name, }; } else { diff --git a/addons/mail/static/tests/helpers/mock_server/models/res_partner.js b/addons/mail/static/tests/helpers/mock_server/models/res_partner.js index 0b6e60fbbd1..d49c42a9adb 100644 --- a/addons/mail/static/tests/helpers/mock_server/models/res_partner.js +++ b/addons/mail/static/tests/helpers/mock_server/models/res_partner.js @@ -273,6 +273,7 @@ patch(MockServer.prototype, { email: partner.email, id: partner.id, im_status: partner.im_status, + is_company: partner.is_company, name: partner.name, user: mainUser ? { diff --git a/addons/test_discuss_full/tests/test_performance.py b/addons/test_discuss_full/tests/test_performance.py index acf1d4deb44..fd164000022 100644 --- a/addons/test_discuss_full/tests/test_performance.py +++ b/addons/test_discuss_full/tests/test_performance.py @@ -146,6 +146,7 @@ class TestDiscussFullPerformance(HttpCase): 'email': 'e.e@example.com', 'id': self.users[0].partner_id.id, 'im_status': 'online', + 'is_company': False, 'name': 'Ernest Employee', 'out_of_office_date_end': False, 'user': { @@ -196,6 +197,7 @@ class TestDiscussFullPerformance(HttpCase): 'email': 'e.e@example.com', 'id': self.users[0].partner_id.id, 'im_status': 'online', + 'is_company': False, 'name': 'Ernest Employee', 'out_of_office_date_end': False, 'user': { @@ -246,6 +248,7 @@ class TestDiscussFullPerformance(HttpCase): 'email': 'e.e@example.com', 'id': self.users[0].partner_id.id, 'im_status': 'online', + 'is_company': False, 'name': 'Ernest Employee', 'out_of_office_date_end': False, 'user': { @@ -296,6 +299,7 @@ class TestDiscussFullPerformance(HttpCase): 'email': 'e.e@example.com', 'id': self.users[0].partner_id.id, 'im_status': 'online', + 'is_company': False, 'name': 'Ernest Employee', 'out_of_office_date_end': False, 'user': { @@ -346,6 +350,7 @@ class TestDiscussFullPerformance(HttpCase): 'email': 'e.e@example.com', 'id': self.users[0].partner_id.id, 'im_status': 'online', + 'is_company': False, 'name': 'Ernest Employee', 'out_of_office_date_end': False, 'user': { @@ -397,6 +402,7 @@ class TestDiscussFullPerformance(HttpCase): 'email': 'e.e@example.com', 'id': self.users[0].partner_id.id, 'im_status': 'online', + 'is_company': False, 'name': 'Ernest Employee', 'out_of_office_date_end': False, 'user': { @@ -417,6 +423,7 @@ class TestDiscussFullPerformance(HttpCase): 'email': False, 'id': self.users[12].partner_id.id, 'im_status': 'offline', + 'is_company': False, 'name': 'test12', 'out_of_office_date_end': False, 'user': { @@ -483,6 +490,7 @@ class TestDiscussFullPerformance(HttpCase): 'email': 'e.e@example.com', 'id': self.users[0].partner_id.id, 'im_status': 'online', + 'is_company': False, 'name': 'Ernest Employee', 'out_of_office_date_end': False, 'user': { @@ -503,6 +511,7 @@ class TestDiscussFullPerformance(HttpCase): 'email': False, 'id': self.users[14].partner_id.id, 'im_status': 'offline', + 'is_company': False, 'name': 'test14', 'out_of_office_date_end': False, 'user': { @@ -569,6 +578,7 @@ class TestDiscussFullPerformance(HttpCase): 'email': 'e.e@example.com', 'id': self.users[0].partner_id.id, 'im_status': 'online', + 'is_company': False, 'name': 'Ernest Employee', 'out_of_office_date_end': False, 'user': { @@ -589,6 +599,7 @@ class TestDiscussFullPerformance(HttpCase): 'email': False, 'id': self.users[15].partner_id.id, 'im_status': 'offline', + 'is_company': False, 'name': 'test15', 'out_of_office_date_end': False, 'user': { @@ -655,6 +666,7 @@ class TestDiscussFullPerformance(HttpCase): 'email': 'e.e@example.com', 'id': self.users[0].partner_id.id, 'im_status': 'online', + 'is_company': False, 'name': 'Ernest Employee', 'out_of_office_date_end': False, 'user': { @@ -675,6 +687,7 @@ class TestDiscussFullPerformance(HttpCase): 'email': 'test2@example.com', 'id': self.users[2].partner_id.id, 'im_status': 'offline', + 'is_company': False, 'name': 'test2', 'out_of_office_date_end': False, 'user': { @@ -741,6 +754,7 @@ class TestDiscussFullPerformance(HttpCase): 'email': 'e.e@example.com', 'id': self.users[0].partner_id.id, 'im_status': 'online', + 'is_company': False, 'name': 'Ernest Employee', 'out_of_office_date_end': False, 'user': { @@ -761,6 +775,7 @@ class TestDiscussFullPerformance(HttpCase): 'email': False, 'id': self.users[3].partner_id.id, 'im_status': 'offline', + 'is_company': False, 'name': 'test3', 'out_of_office_date_end': False, 'user': { @@ -998,6 +1013,7 @@ class TestDiscussFullPerformance(HttpCase): 'email': 'odoobot@example.com', 'id': self.user_root.partner_id.id, 'im_status': 'bot', + 'is_company': False, 'name': 'OdooBot', 'out_of_office_date_end': False, 'user': [('clear',)], @@ -1008,6 +1024,7 @@ class TestDiscussFullPerformance(HttpCase): 'email': 'e.e@example.com', 'id': self.users[0].partner_id.id, 'im_status': 'online', + 'is_company': False, 'name': 'Ernest Employee', 'out_of_office_date_end': False, 'user': { diff --git a/addons/web/static/src/legacy/scss/ui.scss b/addons/web/static/src/legacy/scss/ui.scss index c5410a0cf91..812b36a0d39 100644 --- a/addons/web/static/src/legacy/scss/ui.scss +++ b/addons/web/static/src/legacy/scss/ui.scss @@ -154,6 +154,10 @@ span.o_force_ltr { object-fit: cover; } +.o_object_fit_contain { + object-fit: contain; +} + .o_image_24_cover { width: 24px; height: 24px;