[IMP] im_livechat: small name, ui changes

This PR improves several things linked to visitors in the discuss
app:
- Change window header to "Visitor #, country"
- Change operator's name to Online Chat name if defined
- Rename "Public user" to "Visitor #", add country instead of website
- Change name of the thread to "Visitor #, country" (left panel + header)
- Remove "Website" from visitor's name (small and big threads)

part of task-3332872

closes odoo/odoo#125931

Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
tsm-odoo
2023-07-03 21:56:01 +02:00
parent 7ec8d66ce4
commit 61ef69a8cf
20 changed files with 128 additions and 24 deletions
@@ -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('<div class="o_mail_notification">%s</div>') % _('%s has joined', human_operator.partner_id.name))
Markup('<div class="o_mail_notification">%s</div>')
% _('%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)
@@ -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
@@ -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;
},
});
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-inherit="mail.discuss.channel_member" t-inherit-mode="extension">
<xpath expr="//*[@t-ref='displayName']" position="replace">
<div class="d-flex flex-column">
<t>$0</t>
<div class="ms-2 d-flex">
<span t-if="member.persona?.lang_name" class="me-2">
<i class="fa fa-comment-o me-1" aria-label="Lang"/>
<t t-esc="member.persona.lang_name"/>
</span>
<span t-if="member.persona?.country or props.thread.channel?.anonymous_country">
<i class="fa fa-globe me-1" aria-label="country"/>
<t t-esc="member.persona?.country?.name ?? props.thread.channel.anonymous_country.name"/>
</span>
</div>
</div>
</xpath>
</t>
</templates>
@@ -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();
},
});
@@ -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();
+1
View File
@@ -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
@@ -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',
},
@@ -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
@@ -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 = {
@@ -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];
@@ -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) => {
@@ -32,7 +32,7 @@
t-att-src="threadService.avatarUrl(member.persona, props.thread)"/>
<ImStatus persona="member.persona" className="'position-absolute top-100 start-100 translate-middle mt-n1 ms-n1'"/>
</div>
<span class="ms-2 text-truncate" t-esc="member.persona.name"/>
<span t-ref="displayName" class="ms-2 text-truncate" t-esc="channelMemberService.getName(member)"/>
<span class="ms-auto">
<span t-if="props.thread.invitedMemberIds.has(member.id)" class="p-1 fa fa-user-plus"/>
<span t-if="member.rtcSession?.isSelfMuted and !member.rtcSession?.isDeaf" class="p-1 fa fa-microphone-slash"/>
+1 -1
View File
@@ -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):
@@ -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
@@ -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();
},
});
@@ -11,7 +11,7 @@
<div class="d-flex align-items-baseline">
<ImStatus t-if="visitor.isConnected" className="'me-1'" persona="visitor"/>
<span class="me-2 fw-bolder" t-esc="visitor.nameOrDisplayName"/>
<img t-if="visitor.country" class="me-2 o_country_flag" t-att-src="visitor.countryFlagUrl" t-att-alt="visitor.country.code or visitor.country.name"/>
<img t-if="visitor.country" class="me-2 o_country_flag align-self-center" t-att-src="visitor.countryFlagUrl" t-att-alt="visitor.country.code or visitor.country.name"/>
<span class="me-2">
<i class="me-1 fa fa-comment-o" aria-label="Lang"/>
<t t-esc="visitor.langName"/>
@@ -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)");
@@ -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")
@@ -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