[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:
@@ -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();
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user