diff --git a/addons/im_livechat/models/chatbot_script_step.py b/addons/im_livechat/models/chatbot_script_step.py index a156e49cf3b..527a3a3df34 100644 --- a/addons/im_livechat/models/chatbot_script_step.py +++ b/addons/im_livechat/models/chatbot_script_step.py @@ -346,7 +346,8 @@ class ChatbotScriptStep(models.Model): # then post a small custom 'Operator has joined' notification discuss_channel._chatbot_post_message( self.chatbot_script_id, - Markup('
%s
') % _('%s has joined', human_operator.partner_id.name)) + Markup('
%s
') + % _('%s has joined', human_operator.livechat_username or human_operator.partner_id.name)) discuss_channel._broadcast(human_operator.partner_id.ids) discuss_channel.channel_pin(pinned=True) diff --git a/addons/im_livechat/models/discuss_channel_member.py b/addons/im_livechat/models/discuss_channel_member.py index 334f3abc914..b962fc1fc51 100644 --- a/addons/im_livechat/models/discuss_channel_member.py +++ b/addons/im_livechat/models/discuss_channel_member.py @@ -27,6 +27,7 @@ class ChannelMember(models.Model): 'active': self.partner_id.active, 'id': self.partner_id.id, 'is_public': self.partner_id.is_public, + 'is_bot': self.partner_id.id in self.channel_id.livechat_channel_id.rule_ids.mapped('chatbot_script_id.operator_partner_id.id') } if self.partner_id.user_livechat_username: data['user_livechat_username'] = self.partner_id.user_livechat_username diff --git a/addons/im_livechat/static/src/core/channel_member_list_patch.js b/addons/im_livechat/static/src/core/channel_member_list_patch.js new file mode 100644 index 00000000000..22101747f16 --- /dev/null +++ b/addons/im_livechat/static/src/core/channel_member_list_patch.js @@ -0,0 +1,10 @@ +/* @odoo-module */ + +import { ChannelMemberList } from "@mail/discuss/core/common/channel_member_list"; +import { patch } from "@web/core/utils/patch"; + +patch(ChannelMemberList.prototype, "im_livechat", { + canOpenChatWith(member) { + return this._super(member) && !member.persona.is_public; + }, +}); diff --git a/addons/im_livechat/static/src/core/channel_member_list_patch.xml b/addons/im_livechat/static/src/core/channel_member_list_patch.xml new file mode 100644 index 00000000000..9b5ed3ef93c --- /dev/null +++ b/addons/im_livechat/static/src/core/channel_member_list_patch.xml @@ -0,0 +1,20 @@ + + + + +
+ $0 +
+ + + + + + + + +
+
+
+
+
diff --git a/addons/im_livechat/static/src/core/channel_member_service_patch.js b/addons/im_livechat/static/src/core/channel_member_service_patch.js new file mode 100644 index 00000000000..0f80b3da5fe --- /dev/null +++ b/addons/im_livechat/static/src/core/channel_member_service_patch.js @@ -0,0 +1,19 @@ +/* @odoo-module */ + +import { ChannelMemberService } from "@mail/core/common/channel_member_service"; +import { patch } from "@web/core/utils/patch"; + +patch(ChannelMemberService, "im_livechat", { + getName(member) { + if (member.thread.type !== "livechat") { + return this._super(); + } + if (member.persona.user_livechat_username) { + return member.persona.user_livechat_username; + } + if (member.persona.is_public) { + return member.thread.anonymous_name; + } + return this._super(); + }, +}); diff --git a/addons/im_livechat/static/src/core/thread_model_patch.js b/addons/im_livechat/static/src/core/thread_model_patch.js index 89ce9ea941d..c56d6fe4396 100644 --- a/addons/im_livechat/static/src/core/thread_model_patch.js +++ b/addons/im_livechat/static/src/core/thread_model_patch.js @@ -30,6 +30,10 @@ patch(Thread.prototype, "im_livechat", { return this.type === "livechat" ? false : this._super(); }, + get correspondents() { + return this._super().filter((correspondent) => !correspondent.is_bot); + }, + get displayName() { if (this.type !== "livechat" || !this.correspondent) { return this._super(); diff --git a/addons/im_livechat/tests/__init__.py b/addons/im_livechat/tests/__init__.py index 8caa7596302..805affd48b6 100644 --- a/addons/im_livechat/tests/__init__.py +++ b/addons/im_livechat/tests/__init__.py @@ -8,5 +8,6 @@ from . import test_digest from . import test_get_discuss_channel from . import test_im_livechat_report from . import test_im_livechat_support_page +from . import test_performance from . import test_js from . import test_message diff --git a/addons/im_livechat/tests/test_get_discuss_channel.py b/addons/im_livechat/tests/test_get_discuss_channel.py index d41dd0be531..f046ccf0353 100644 --- a/addons/im_livechat/tests/test_get_discuss_channel.py +++ b/addons/im_livechat/tests/test_get_discuss_channel.py @@ -36,11 +36,13 @@ class TestGetDiscussChannel(TestImLivechatCommon): 'active': True, 'country': [('clear',)], 'id': operator.partner_id.id, + 'is_bot': False, 'is_public': False, 'user_livechat_username': 'Michel Operator', }, { 'active': False, 'id': public_user.partner_id.id, + 'is_bot': False, 'is_public': True, 'name': 'Public user', }], key=lambda m: m['id'])) @@ -58,6 +60,7 @@ class TestGetDiscussChannel(TestImLivechatCommon): 'active': True, 'country': [('clear',)], 'id': operator.partner_id.id, + 'is_bot': False, 'is_public': False, 'user_livechat_username': 'Michel Operator', }, @@ -75,6 +78,7 @@ class TestGetDiscussChannel(TestImLivechatCommon): 'name': 'Belgium', }, 'id': test_user.partner_id.id, + 'is_bot': False, 'is_public': False, 'name': 'Roger', }, @@ -97,6 +101,7 @@ class TestGetDiscussChannel(TestImLivechatCommon): 'active': True, 'country': [('clear',)], 'id': operator.partner_id.id, + 'is_bot': False, 'is_public': False, 'user_livechat_username': 'Michel Operator', }, diff --git a/addons/im_livechat/tests/test_performance.py b/addons/im_livechat/tests/test_performance.py new file mode 100644 index 00000000000..59e79e1e41d --- /dev/null +++ b/addons/im_livechat/tests/test_performance.py @@ -0,0 +1,20 @@ +from odoo.addons.test_discuss_full.tests.test_performance import TestDiscussFullPerformance + +old_method = TestDiscussFullPerformance._get_init_messaging_result + + +def _get_init_messaging_result(self): + res = old_method(self) + for channel in res['channels']: + if not channel['channel']['channel_type'] == 'livechat': + continue + for channel_member in channel['channel']['channelMembers'][0][1]: + channel_member['persona']['partner']['is_bot'] = False + return res + +def _get_query_count(self): + return 79 + + +TestDiscussFullPerformance._get_init_messaging_result = _get_init_messaging_result +TestDiscussFullPerformance._get_query_count = _get_query_count diff --git a/addons/mail/static/src/core/common/channel_member_service.js b/addons/mail/static/src/core/common/channel_member_service.js index f9b769e6974..1794ecef424 100644 --- a/addons/mail/static/src/core/common/channel_member_service.js +++ b/addons/mail/static/src/core/common/channel_member_service.js @@ -63,6 +63,14 @@ export class ChannelMemberService { break; } } + + /** + * @param {import("@mail/core/common/channel_member_model").ChannelMember} member + * @returns {string} + */ + getName(member) { + return member.persona.nameOrDisplayName; + } } export const channelMemberService = { diff --git a/addons/mail/static/src/core/common/thread_model.js b/addons/mail/static/src/core/common/thread_model.js index f0192f89bd2..b7e2732204a 100644 --- a/addons/mail/static/src/core/common/thread_model.js +++ b/addons/mail/static/src/core/common/thread_model.js @@ -224,18 +224,23 @@ export class Thread { return this.name; } - /** @type {import("@mail/core/common/persona_model").Persona|undefined} */ - get correspondent() { - if (this.type === "channel") { - return undefined; - } - const correspondents = this.channelMembers + /** @type {import("@mail/core/common/persona_model").Persona[]} */ + get correspondents() { + return this.channelMembers .map((member) => member.persona) .filter((persona) => !!persona) .filter( ({ id, type }) => id !== (type === "partner" ? this._store.user?.id : this._store.guest?.id) ); + } + + /** @type {import("@mail/core/common/persona_model").Persona|undefined} */ + get correspondent() { + if (this.type === "channel") { + return undefined; + } + const correspondents = this.correspondents; if (correspondents.length === 1) { // 2 members chat. return correspondents[0]; diff --git a/addons/mail/static/src/discuss/core/common/channel_member_list.js b/addons/mail/static/src/discuss/core/common/channel_member_list.js index e57f4143bae..94f65d82c5b 100644 --- a/addons/mail/static/src/discuss/core/common/channel_member_list.js +++ b/addons/mail/static/src/discuss/core/common/channel_member_list.js @@ -15,6 +15,7 @@ export class ChannelMemberList extends Component { setup() { this.messaging = useMessaging(); this.store = useStore(); + this.channelMemberService = useService("discuss.channel.member"); this.threadService = useState(useService("mail.thread")); onWillStart(() => this.threadService.fetchChannelMembers(this.props.thread)); onWillUpdateProps((nextProps) => { diff --git a/addons/mail/static/src/discuss/core/common/channel_member_list.xml b/addons/mail/static/src/discuss/core/common/channel_member_list.xml index 1dc218a19c7..8213a2bdbca 100644 --- a/addons/mail/static/src/discuss/core/common/channel_member_list.xml +++ b/addons/mail/static/src/discuss/core/common/channel_member_list.xml @@ -32,7 +32,7 @@ t-att-src="threadService.avatarUrl(member.persona, props.thread)"/> - + diff --git a/addons/website_livechat/controllers/main.py b/addons/website_livechat/controllers/main.py index 7129bd0ffab..fefebd161b6 100644 --- a/addons/website_livechat/controllers/main.py +++ b/addons/website_livechat/controllers/main.py @@ -65,7 +65,7 @@ class WebsiteLivechat(LivechatController): """ Override to use visitor name instead of 'Visitor' whenever a visitor start a livechat session. """ visitor_sudo = request.env['website.visitor']._get_visitor_from_request() if visitor_sudo: - anonymous_name = visitor_sudo.with_context(lang=visitor_sudo.lang_id.code).display_name + anonymous_name = _('Visitor #%s') % visitor_sudo.id return super(WebsiteLivechat, self).get_session(channel_id, anonymous_name, previous_operator_id=previous_operator_id, chatbot_script_id=chatbot_script_id, persisted=persisted, **kwargs) def _livechat_templates_get(self): diff --git a/addons/website_livechat/models/discuss_channel.py b/addons/website_livechat/models/discuss_channel.py index d78e72c27d1..f050c9133fd 100644 --- a/addons/website_livechat/models/discuss_channel.py +++ b/addons/website_livechat/models/discuss_channel.py @@ -64,7 +64,7 @@ class DiscussChannel(models.Model): message = _("""%s started a conversation with %s. The chat request has been canceled.""") % (name, operator or _('an operator')) else: - message = _('%s left the conversation.', name) + message = _('Visitor %s left the conversation.', f"#{self.livechat_visitor_id.id}" if self.livechat_visitor_id else '') return message diff --git a/addons/website_livechat/static/src/core/persona_model_patch.js b/addons/website_livechat/static/src/core/persona_model_patch.js index 15666e72b0e..c696424318a 100644 --- a/addons/website_livechat/static/src/core/persona_model_patch.js +++ b/addons/website_livechat/static/src/core/persona_model_patch.js @@ -1,14 +1,21 @@ /** @odoo-module */ import { Persona } from "@mail/core/common/persona_model"; +import { _t } from "@web/core/l10n/translation"; import { patch } from "@web/core/utils/patch"; +import { sprintf } from "@web/core/utils/strings"; patch(Persona.prototype, "website_livechat", { get countryFlagUrl() { - return this.country - ? `/base/static/img/country_flags/${encodeURIComponent( - this.country.code.toLowerCase() - )}.png` + const country = this.partner?.country ?? this.country; + return country + ? `/base/static/img/country_flags/${encodeURIComponent(country.code.toLowerCase())}.png` : undefined; }, + get nameOrDisplayName() { + if (this.type === "visitor" && !this.name) { + return sprintf(_t("Visitor #%s"), this.id); + } + return this._super(); + }, }); diff --git a/addons/website_livechat/static/src/core_ui/thread_patch.xml b/addons/website_livechat/static/src/core_ui/thread_patch.xml index 8bb512bd278..67276467759 100644 --- a/addons/website_livechat/static/src/core_ui/thread_patch.xml +++ b/addons/website_livechat/static/src/core_ui/thread_patch.xml @@ -11,7 +11,7 @@
- + diff --git a/addons/website_livechat/static/tests/thread_patch_tests.js b/addons/website_livechat/static/tests/thread_patch_tests.js index 678dca51ac4..78a21055452 100644 --- a/addons/website_livechat/static/tests/thread_patch_tests.js +++ b/addons/website_livechat/static/tests/thread_patch_tests.js @@ -9,14 +9,16 @@ QUnit.test("Rendering of visitor banner", async (assert) => { const countryId = pyEnv["res.country"].create({ code: "BE" }); const visitorId = pyEnv["website.visitor"].create({ country_id: countryId, - display_name: "Visitor #11", history: "Home → Contact", is_connected: true, lang_name: "English", website_name: "General website", }); + pyEnv["website.visitor"].write([visitorId], { + display_name: `Visitor #${visitorId}`, + }); const channelId = pyEnv["discuss.channel"].create({ - anonymous_name: "Website Visitor 11", + anonymous_name: `Visitor #${visitorId}`, channel_member_ids: [ [0, 0, { partner_id: pyEnv.currentPartnerId }], [0, 0, { partner_id: pyEnv.publicPartnerId }], @@ -34,7 +36,7 @@ QUnit.test("Rendering of visitor banner", async (assert) => { ); assert.containsOnce($, ".o-website_livechat-VisitorBanner .o-mail-ImStatus"); assert.containsOnce($, ".o_country_flag[data-src='/base/static/img/country_flags/be.png']"); - assert.containsOnce($, "span:contains(Visitor #11)"); + assert.containsOnce($, `.o-website_livechat-VisitorBanner span:contains(Visitor #${visitorId})`); assert.containsOnce($, "span:contains(English)"); assert.containsOnce($, "span>:contains(General website)"); assert.containsOnce($, "span:contains(Home → Contact)"); diff --git a/addons/website_livechat/tests/test_livechat_basic_flow.py b/addons/website_livechat/tests/test_livechat_basic_flow.py index 14935cb92ce..89355c89c83 100644 --- a/addons/website_livechat/tests/test_livechat_basic_flow.py +++ b/addons/website_livechat/tests/test_livechat_basic_flow.py @@ -49,7 +49,7 @@ class TestLivechatBasicFlowHttpCase(tests.HttpCase, TestLivechatCommon): channel_1 = self.env['discuss.channel'].search([('livechat_visitor_id', '=', self.visitor.id), ('livechat_active', '=', True)], limit=1) # Check Channel naming - self.assertEqual(channel_1.name, "%s %s" % (self.visitor.display_name, self.operator.livechat_username)) + self.assertEqual(channel_1.name, "%s %s" % (f'Visitor #{channel_1.livechat_visitor_id.id}', self.operator.livechat_username)) channel_1.unlink() # Remove livechat_username @@ -67,7 +67,7 @@ class TestLivechatBasicFlowHttpCase(tests.HttpCase, TestLivechatCommon): channel_2 = self.env['discuss.channel'].search([('livechat_visitor_id', '=', self.visitor.id), ('livechat_active', '=', True)], limit=1) # Check Channel naming - self.assertEqual(channel_2.name, "%s %s" % (self.visitor.display_name, self.operator.name)) + self.assertEqual(channel_2.name, "%s %s" % (f'Visitor #{channel_2.livechat_visitor_id.id}', self.operator.name)) def test_basic_flow_with_rating(self): channel = self._common_basic_flow() @@ -78,7 +78,7 @@ class TestLivechatBasicFlowHttpCase(tests.HttpCase, TestLivechatCommon): self.assertEqual(len(channel.message_ids), 4) self.assertEqual(channel.message_ids[0].author_id, self.env.ref('base.partner_root'), "Odoobot must be the sender of the 'left the conversation' message.") - self.assertIn("%s left the conversation." % self.visitor.display_name, channel.message_ids[0].body) + self.assertIn(f"Visitor #{channel.livechat_visitor_id.id}", channel.message_ids[0].body) self.assertEqual(channel.livechat_active, False, "The livechat session must be inactive as the visitor sent his feedback.") def test_basic_flow_without_rating(self): @@ -88,7 +88,7 @@ class TestLivechatBasicFlowHttpCase(tests.HttpCase, TestLivechatCommon): channel._close_livechat_session() self.assertEqual(len(channel.message_ids), 3) self.assertEqual(channel.message_ids[0].author_id, self.env.ref('base.partner_root'), "Odoobot must be the author the message.") - self.assertIn("%s left the conversation." % self.visitor.display_name, channel.message_ids[0].body) + self.assertIn(f"Visitor #{channel.livechat_visitor_id.id}", channel.message_ids[0].body) self.assertEqual(channel.livechat_active, False, "The livechat session must be inactive since visitor left the conversation.") def test_visitor_info_access_rights(self): @@ -119,7 +119,7 @@ class TestLivechatBasicFlowHttpCase(tests.HttpCase, TestLivechatCommon): # Check Channel and Visitor naming self.assertEqual(self.visitor.display_name, "%s #%s" % (_("Website Visitor"), self.visitor.id)) - self.assertEqual(channel.name, "%s %s" % (self.visitor.display_name, self.operator.livechat_username)) + self.assertEqual(channel.name, "%s %s" % (f'Visitor #{channel.livechat_visitor_id.id}', self.operator.livechat_username)) # Post Message from visitor self._send_message(channel, self.visitor.display_name, "Message from Visitor") diff --git a/addons/website_livechat/tests/test_livechat_request.py b/addons/website_livechat/tests/test_livechat_request.py index 2f827fb57fd..47d7274733d 100644 --- a/addons/website_livechat/tests/test_livechat_request.py +++ b/addons/website_livechat/tests/test_livechat_request.py @@ -67,7 +67,7 @@ class TestLivechatRequestHttpCase(tests.HttpCase, TestLivechatCommon): channel._close_livechat_session() self.assertEqual(len(channel.message_ids), 3) self.assertEqual(channel.message_ids[0].author_id, self.env.ref('base.partner_root'), "Odoobot must be the sender of the 'left the conversation' message.") - self.assertIn("%s left the conversation." % self.visitor.display_name, channel.message_ids[0].body) + self.assertIn(f"Visitor #{channel.livechat_visitor_id.id}", channel.message_ids[0].body) self.assertEqual(channel.livechat_active, False, "The livechat session must be inactive as the visitor sent his feedback.") return channel