[IMP] mail, website_livechat: bring back visitor banner in discuss
task-2276589 closes odoo/odoo#58858 X-original-commit: 65dbe1c4760e2ffe104426f9aa41007a10dc32f0 Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
This commit is contained in:
committed by
Alexandre Kühn
parent
5be44732cb
commit
52dfe6a4e1
@@ -140,7 +140,7 @@ class Message extends Component {
|
||||
// TODO FIXME for public user this might not be accessible. task-2223236
|
||||
// we should probably use the correspondig attachment id + access token
|
||||
// or create a dedicated route to get message image, checking the access right of the message
|
||||
return `/web/image/res.partner/${this.message.author.id}/image_128`;
|
||||
return this.message.author.avatarUrl;
|
||||
} else if (this.message.message_type === 'email') {
|
||||
return '/mail/static/src/img/email_icon.png';
|
||||
}
|
||||
|
||||
@@ -131,8 +131,8 @@
|
||||
}
|
||||
|
||||
.o_Message_sidebar {
|
||||
flex: 0 0 50px;
|
||||
max-width: 50px;
|
||||
flex: 0 0 $o-mail-message-sidebar-width;
|
||||
max-width: $o-mail-message-sidebar-width;
|
||||
display: flex;
|
||||
margin-inline-end: map-get($spacers, 2);
|
||||
justify-content: center;
|
||||
|
||||
+1
-1
@@ -64,7 +64,7 @@ class ThreadNeedactionPreview extends Component {
|
||||
return this.thread.moduleIcon;
|
||||
}
|
||||
if (this.thread.correspondent) {
|
||||
return `/web/image/res.partner/${this.thread.correspondent.id}/image_128`;
|
||||
return this.thread.correspondent.avatarUrl;
|
||||
}
|
||||
if (this.thread.model === 'mail.channel') {
|
||||
return `/web/image/mail.channel/${this.thread.id}/image_128`;
|
||||
|
||||
@@ -59,7 +59,7 @@ class ThreadPreview extends Component {
|
||||
*/
|
||||
image() {
|
||||
if (this.thread.correspondent) {
|
||||
return `/web/image/res.partner/${this.thread.correspondent.id}/image_128`;
|
||||
return this.thread.correspondent.avatarUrl;
|
||||
}
|
||||
return `/web/image/mail.channel/${this.thread.id}/image_128`;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ odoo.define('mail/static/src/models/country/country.js', function (require) {
|
||||
|
||||
const { registerNewModel } = require('mail/static/src/model/model_core.js');
|
||||
const { attr } = require('mail/static/src/model/model_field.js');
|
||||
const { clear } = require('mail/static/src/model/model_field_command.js');
|
||||
|
||||
function factory(dependencies) {
|
||||
|
||||
@@ -19,9 +20,27 @@ function factory(dependencies) {
|
||||
return `${this.modelName}_${data.id}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {string|undefined}
|
||||
*/
|
||||
_computeFlagUrl() {
|
||||
if (!this.code) {
|
||||
return clear();
|
||||
}
|
||||
return `/base/static/img/country_flags/${this.code}.png`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Country.fields = {
|
||||
code: attr(),
|
||||
flagUrl: attr({
|
||||
compute: '_computeFlagUrl',
|
||||
dependencies: [
|
||||
'code',
|
||||
],
|
||||
}),
|
||||
id: attr(),
|
||||
name: attr(),
|
||||
};
|
||||
|
||||
@@ -190,6 +190,14 @@ function factory(dependencies) {
|
||||
// Private
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
_computeAvatarUrl() {
|
||||
return `/web/image/res.partner/${this.id}/image_128`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
@@ -266,6 +274,12 @@ function factory(dependencies) {
|
||||
active: attr({
|
||||
default: true,
|
||||
}),
|
||||
avatarUrl: attr({
|
||||
compute: '_computeAvatarUrl',
|
||||
dependencies: [
|
||||
'id',
|
||||
],
|
||||
}),
|
||||
correspondentThreads: one2many('mail.thread', {
|
||||
inverse: 'correspondent',
|
||||
}),
|
||||
|
||||
@@ -10,5 +10,7 @@ $o-mail-discuss-sidebar-active-indicator-margin-right: 10px !default;
|
||||
$o-mail-discuss-sidebar-active-indicator-width: 3px !default;
|
||||
$o-mail-discuss-sidebar-scrollbar-width: 15px !default;
|
||||
|
||||
$o-mail-message-sidebar-width: 50px;
|
||||
|
||||
$o-mail-moderation-accept-color: theme-color('success') !default;
|
||||
$o-mail-moderation-reject-color: theme-color('danger') !default;
|
||||
|
||||
@@ -24,5 +24,7 @@ Allow website visitors to chat with the collaborators. This module also brings a
|
||||
'qweb': [
|
||||
'static/src/bugfix/bugfix.xml',
|
||||
'static/src/bugfix/public_bugfix.xml',
|
||||
'static/src/components/discuss/discuss.xml',
|
||||
'static/src/components/visitor_banner/visitor_banner.xml',
|
||||
],
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ class MailChannel(models.Model):
|
||||
channel_infos_dict[channel.id]['visitor'] = {
|
||||
'name': visitor.display_name,
|
||||
'country_code': visitor.country_id.code.lower() if visitor.country_id else False,
|
||||
'country_id': visitor.country_id.id,
|
||||
'is_connected': visitor.is_connected,
|
||||
'history': self.sudo()._get_visitor_history(visitor),
|
||||
'website': visitor.website_id.name,
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
odoo.define('website_livechat/static/src/components/discuss/discuss.js', function (require) {
|
||||
'use strict';
|
||||
|
||||
const components = {
|
||||
Discuss: require('mail/static/src/components/discuss/discuss.js'),
|
||||
VisitorBanner: require('website_livechat/static/src/components/visitor_banner/visitor_banner.js'),
|
||||
};
|
||||
|
||||
Object.assign(components.Discuss.components, {
|
||||
VisitorBanner: components.VisitorBanner,
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-inherit="mail.Discuss.content" t-inherit-mode="extension">
|
||||
<xpath expr="//*[hasclass('o_Discuss_thread')]" position="before">
|
||||
<t t-if="discuss.thread.visitor">
|
||||
<VisitorBanner
|
||||
visitorLocalId="discuss.thread.visitor.localId"
|
||||
/>
|
||||
</t>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,269 @@
|
||||
odoo.define('website_livechat/static/src/components/discuss/discuss_tests.js', function (require) {
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
afterEach,
|
||||
beforeEach,
|
||||
start,
|
||||
} = require('mail/static/src/utils/test_utils.js');
|
||||
|
||||
QUnit.module('website_livechat', {}, function () {
|
||||
QUnit.module('components', {}, function () {
|
||||
QUnit.module('discuss', {}, function () {
|
||||
QUnit.module('discuss_tests.js', {
|
||||
beforeEach() {
|
||||
beforeEach(this);
|
||||
|
||||
this.start = async params => {
|
||||
const { env, widget } = await start(Object.assign({}, params, {
|
||||
autoOpenDiscuss: true,
|
||||
data: this.data,
|
||||
hasDiscuss: true,
|
||||
}));
|
||||
this.env = env;
|
||||
this.widget = widget;
|
||||
};
|
||||
},
|
||||
afterEach() {
|
||||
afterEach(this);
|
||||
},
|
||||
});
|
||||
|
||||
QUnit.test('rendering of visitor banner', async function (assert) {
|
||||
assert.expect(13);
|
||||
|
||||
this.data['res.country'].records.push({
|
||||
id: 11,
|
||||
code: 'FAKE',
|
||||
});
|
||||
this.data['website.visitor'].records.push({
|
||||
id: 11,
|
||||
country_id: 11,
|
||||
display_name: 'Visitor #11',
|
||||
history: 'Home → Contact',
|
||||
is_connected: true,
|
||||
lang: "English",
|
||||
website: "General website",
|
||||
});
|
||||
this.data['mail.channel'].records.push({
|
||||
channel_type: 'livechat',
|
||||
id: 11,
|
||||
livechat_operator_id: this.data.currentPartnerId,
|
||||
livechat_visitor_id: 11,
|
||||
members: [this.data.currentPartnerId, this.data.publicPartnerId],
|
||||
});
|
||||
await this.start({
|
||||
discuss: {
|
||||
context: {
|
||||
active_id: 'mail.channel_11',
|
||||
},
|
||||
},
|
||||
});
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_VisitorBanner',
|
||||
"should have a visitor banner",
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_VisitorBanner_avatar',
|
||||
"should show the visitor avatar in the banner",
|
||||
);
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_VisitorBanner_avatar').dataset.src,
|
||||
"/mail/static/src/img/smiley/avatar.jpg",
|
||||
"should show the default avatar",
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_VisitorBanner_onlineStatusIcon',
|
||||
"should show the visitor online status icon on the avatar",
|
||||
);
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_VisitorBanner_country').dataset.src,
|
||||
"/base/static/img/country_flags/FAKE.png",
|
||||
"should show the flag of the country of the visitor",
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_VisitorBanner_visitor',
|
||||
"should show the visitor name in the banner",
|
||||
);
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_VisitorBanner_visitor').textContent,
|
||||
"Visitor #11",
|
||||
"should have 'Visitor #11' as visitor name",
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_VisitorBanner_language',
|
||||
"should show the visitor language in the banner",
|
||||
);
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_VisitorBanner_language').textContent,
|
||||
"English",
|
||||
"should have 'English' as language of the visitor",
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_VisitorBanner_website',
|
||||
"should show the visitor website in the banner",
|
||||
);
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_VisitorBanner_website').textContent,
|
||||
"General website",
|
||||
"should have 'General website' as website of the visitor",
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_VisitorBanner_history',
|
||||
"should show the visitor history in the banner",
|
||||
);
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_VisitorBanner_history').textContent,
|
||||
"Home → Contact",
|
||||
"should have 'Home → Contact' as history of the visitor",
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('livechat with non-logged visitor should show visitor banner', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.data['res.country'].records.push({
|
||||
id: 11,
|
||||
code: 'FAKE',
|
||||
});
|
||||
this.data['website.visitor'].records.push({
|
||||
id: 11,
|
||||
country_id: 11,
|
||||
display_name: 'Visitor #11',
|
||||
history: 'Home → Contact',
|
||||
is_connected: true,
|
||||
lang: "English",
|
||||
website: "General website",
|
||||
});
|
||||
this.data['mail.channel'].records.push({
|
||||
channel_type: 'livechat',
|
||||
id: 11,
|
||||
livechat_operator_id: this.data.currentPartnerId,
|
||||
livechat_visitor_id: 11,
|
||||
members: [this.data.currentPartnerId, this.data.publicPartnerId],
|
||||
});
|
||||
await this.start({
|
||||
discuss: {
|
||||
context: {
|
||||
active_id: 'mail.channel_11',
|
||||
},
|
||||
},
|
||||
});
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_VisitorBanner',
|
||||
"should have a visitor banner",
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('livechat with logged visitor should show visitor banner', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
this.data['res.country'].records.push({
|
||||
id: 11,
|
||||
code: 'FAKE',
|
||||
});
|
||||
this.data['res.partner'].records.push({
|
||||
id: 12,
|
||||
name: 'Partner Visitor',
|
||||
});
|
||||
this.data['website.visitor'].records.push({
|
||||
id: 11,
|
||||
country_id: 11,
|
||||
display_name: 'Visitor #11',
|
||||
history: 'Home → Contact',
|
||||
is_connected: true,
|
||||
lang: "English",
|
||||
partner_id: 12,
|
||||
website: "General website",
|
||||
});
|
||||
this.data['mail.channel'].records.push({
|
||||
channel_type: 'livechat',
|
||||
id: 11,
|
||||
livechat_operator_id: this.data.currentPartnerId,
|
||||
livechat_visitor_id: 11,
|
||||
members: [this.data.currentPartnerId, 12],
|
||||
});
|
||||
await this.start({
|
||||
discuss: {
|
||||
context: {
|
||||
active_id: 'mail.channel_11',
|
||||
},
|
||||
},
|
||||
});
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_VisitorBanner',
|
||||
"should have a visitor banner",
|
||||
);
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_VisitorBanner_visitor').textContent,
|
||||
"Partner Visitor",
|
||||
"should have partner name as display name of logged visitor on the visitor banner"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('livechat without visitor should not show visitor banner', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
this.data['res.partner'].records.push({ id: 11 });
|
||||
this.data['mail.channel'].records.push({
|
||||
channel_type: 'livechat',
|
||||
id: 11,
|
||||
livechat_operator_id: this.data.currentPartnerId,
|
||||
members: [this.data.currentPartnerId, 11],
|
||||
});
|
||||
await this.start({
|
||||
discuss: {
|
||||
context: {
|
||||
active_id: 'mail.channel_11',
|
||||
},
|
||||
},
|
||||
});
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_MessageList',
|
||||
"should have a message list",
|
||||
);
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
'.o_VisitorBanner',
|
||||
"should not have any visitor banner",
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('non-livechat channel should not show visitor banner', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
this.data['mail.channel'].records.push({ id: 11, name: "General" });
|
||||
await this.start({
|
||||
discuss: {
|
||||
context: {
|
||||
active_id: 'mail.channel_11',
|
||||
},
|
||||
},
|
||||
});
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_MessageList',
|
||||
"should have a message list",
|
||||
);
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
'.o_VisitorBanner',
|
||||
"should not have any visitor banner",
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
odoo.define('website_livechat/static/src/components/visitor_banner/visitor_banner.js', function (require) {
|
||||
'use strict';
|
||||
|
||||
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
class VisitorBanner extends Component {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
useStore(props => {
|
||||
const visitor = this.env.models['website_livechat.visitor'].get(props.visitorLocalId);
|
||||
return {
|
||||
visitor: visitor ? visitor.__state : undefined,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @returns {website_livechat.visitor}
|
||||
*/
|
||||
get visitor() {
|
||||
return this.env.models['website_livechat.visitor'].get(this.props.visitorLocalId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(VisitorBanner, {
|
||||
props: {
|
||||
visitorLocalId: String,
|
||||
},
|
||||
template: 'website_livechat.VisitorBanner',
|
||||
});
|
||||
|
||||
return VisitorBanner;
|
||||
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Layout
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
.o_VisitorBanner {
|
||||
border-bottom-width: $border-width;
|
||||
border-bottom-style: solid;
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
padding: map-get($spacers, 4) map-get($spacers, 2);
|
||||
}
|
||||
|
||||
.o_VisitorBanner_avatar {
|
||||
height: map-get($sizes, 100);
|
||||
width: map-get($sizes, 100);
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.o_VisitorBanner_avatarContainer {
|
||||
height: $o-mail-thread-avatar-size;
|
||||
width: $o-mail-thread-avatar-size;
|
||||
margin-left: map-get($spacers, 1);
|
||||
margin-right: map-get($spacers, 1);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.o_VisitorBanner_country {
|
||||
margin-inline-end: map-get($spacers, 1);
|
||||
}
|
||||
|
||||
.o_VisitorBanner_history {
|
||||
margin-top: map-get($spacers, 1);
|
||||
}
|
||||
|
||||
.o_VisitorBanner_historyIcon {
|
||||
margin-inline-end: map-get($spacers, 1);
|
||||
}
|
||||
|
||||
.o_VisitorBanner_language {
|
||||
margin-inline-end: map-get($spacers, 3);
|
||||
}
|
||||
|
||||
.o_VisitorBanner_languageIcon {
|
||||
margin-inline-end: map-get($spacers, 1);
|
||||
}
|
||||
|
||||
.o_VisitorBanner_onlineStatusIcon {
|
||||
@include o-position-absolute($bottom: 0, $right: 0);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-flow: column;
|
||||
width: 1.2em;
|
||||
height: 1.2em;
|
||||
line-height: 1.3em;
|
||||
font-size: x-small;
|
||||
}
|
||||
|
||||
.o_VisitorBanner_sidebar {
|
||||
display: flex;
|
||||
flex: 0 0 $o-mail-message-sidebar-width;
|
||||
justify-content: center;
|
||||
margin-inline-end: map-get($spacers, 2);
|
||||
max-width: $o-mail-message-sidebar-width;
|
||||
}
|
||||
|
||||
.o_VisitorBanner_visitor {
|
||||
margin-inline-end: map-get($spacers, 3);
|
||||
}
|
||||
|
||||
.o_VisitorBanner_websiteIcon {
|
||||
margin-inline-end: map-get($spacers, 1);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Style
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
.o_VisitorBanner {
|
||||
background: $white;
|
||||
border-bottom-color: gray('400');
|
||||
}
|
||||
|
||||
.o_VisitorBanner_onlineStatusIcon {
|
||||
color: $o-enterprise-primary-color;
|
||||
}
|
||||
|
||||
.o_VisitorBanner_visitor {
|
||||
font-weight: $font-weight-bold;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="website_livechat.VisitorBanner" owl="1">
|
||||
<div class="o_VisitorBanner">
|
||||
<div class="o_VisitorBanner_sidebar">
|
||||
<div class="o_VisitorBanner_avatarContainer">
|
||||
<img class="o_VisitorBanner_avatar rounded-circle" t-att-src="visitor.avatarUrl" alt="Avatar"/>
|
||||
<t t-if="visitor.is_connected">
|
||||
<i class="o_VisitorBanner_onlineStatusIcon fa fa-circle" title="Online" role="img" aria-label="Visitor is online"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_VisitorBanner_content">
|
||||
<t t-if="visitor.country">
|
||||
<img class="o_VisitorBanner_country o_country_flag" t-att-src="visitor.country.flagUrl" t-att-alt="visitor.country.code or visitor.country.name"/>
|
||||
</t>
|
||||
<span class="o_VisitorBanner_visitor" t-esc="visitor.nameOrDisplayName"/>
|
||||
<span class="o_VisitorBanner_language">
|
||||
<i class="o_VisitorBanner_languageIcon fa fa-comment-o" aria-label="Lang"/>
|
||||
<t t-esc="visitor.lang"/>
|
||||
</span>
|
||||
<t t-if="visitor.website">
|
||||
<span class="o_VisitorBanner_website">
|
||||
<i class="o_VisitorBanner_websiteIcon fa fa-globe" aria-label="Website"/>
|
||||
<span t-esc="visitor.website"/>
|
||||
</span>
|
||||
</t>
|
||||
<div class="o_VisitorBanner_history">
|
||||
<i class="o_VisitorBanner_historyIcon fa fa-history" aria-label="History"/>
|
||||
<span t-esc="visitor.history"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -0,0 +1,45 @@
|
||||
odoo.define('website_livechat/static/src/models/thread/thread.js', function (require) {
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
registerClassPatchModel,
|
||||
registerFieldPatchModel,
|
||||
} = require('mail/static/src/model/model_core.js');
|
||||
const { many2one } = require('mail/static/src/model/model_field.js');
|
||||
|
||||
registerClassPatchModel('mail.thread', 'website_livechat/static/src/models/thread/thread.js', {
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Public
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
convertData(data) {
|
||||
const data2 = this._super(data);
|
||||
if ('visitor' in data) {
|
||||
if (data.visitor) {
|
||||
data2.visitor = [[
|
||||
'insert',
|
||||
this.env.models['website_livechat.visitor'].convertData(data.visitor)
|
||||
]];
|
||||
} else {
|
||||
data2.visitor = [['unlink']];
|
||||
}
|
||||
}
|
||||
return data2;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
registerFieldPatchModel('mail.thread', 'website_livechat/static/src/models/thread/thread.js', {
|
||||
/**
|
||||
* Visitor connected to the livechat.
|
||||
*/
|
||||
visitor: many2one('website_livechat.visitor', {
|
||||
inverse: 'threads',
|
||||
}),
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,169 @@
|
||||
odoo.define('website_livechat/static/src/models/partner/partner.js', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { registerNewModel } = require('mail/static/src/model/model_core.js');
|
||||
const { attr, many2one, one2many } = require('mail/static/src/model/model_field.js');
|
||||
|
||||
function factory(dependencies) {
|
||||
|
||||
class Visitor extends dependencies['mail.model'] {
|
||||
//----------------------------------------------------------------------
|
||||
// Public
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
static convertData(data) {
|
||||
const data2 = {};
|
||||
if ('country_id' in data) {
|
||||
if (data.country_id) {
|
||||
data2.country = [['insert', {
|
||||
id: data.country_id,
|
||||
code: data.country_code,
|
||||
}]];
|
||||
} else {
|
||||
data2.country = [['unlink']];
|
||||
}
|
||||
}
|
||||
if ('history' in data) {
|
||||
data2.history = data.history;
|
||||
}
|
||||
if ('is_connected' in data) {
|
||||
data2.is_connected = data.is_connected;
|
||||
}
|
||||
if ('lang' in data) {
|
||||
data2.lang = data.lang;
|
||||
}
|
||||
if ('name' in data) {
|
||||
data2.name = data.name;
|
||||
}
|
||||
if ('partner_id' in data) {
|
||||
if (data.partner_id) {
|
||||
data2.partner = [['insert', { id: data.partner_id }]];
|
||||
} else {
|
||||
data2.partner = [['unlink']];
|
||||
}
|
||||
}
|
||||
if ('website' in data) {
|
||||
data2.website = data.website;
|
||||
}
|
||||
return data2;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Private
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
_computeAvatarUrl() {
|
||||
if (!this.partner) {
|
||||
return '/mail/static/src/img/smiley/avatar.jpg';
|
||||
}
|
||||
return this.partner.avatarUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {mail.country}
|
||||
*/
|
||||
_computeCountry() {
|
||||
if (this.partner && this.partner.country) {
|
||||
return [['link', this.partner.country]];
|
||||
}
|
||||
if (this.country) {
|
||||
return [['link', this.country]];
|
||||
}
|
||||
return [['unlink']];
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
_computeNameOrDisplayName() {
|
||||
if (this.partner) {
|
||||
return this.partner.nameOrDisplayName;
|
||||
}
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
|
||||
Visitor.fields = {
|
||||
/**
|
||||
* Url to the avatar of the visitor.
|
||||
*/
|
||||
avatarUrl: attr({
|
||||
compute: '_computeAvatarUrl',
|
||||
dependencies: [
|
||||
'partner',
|
||||
'partnerAvatarUrl',
|
||||
],
|
||||
}),
|
||||
/**
|
||||
* Country of the visitor.
|
||||
*/
|
||||
country: many2one('mail.country', {
|
||||
compute: '_computeCountry',
|
||||
dependencies: [
|
||||
'country',
|
||||
'partnerCountry',
|
||||
],
|
||||
}),
|
||||
/**
|
||||
* Browsing history of the visitor as a string.
|
||||
*/
|
||||
history: attr(),
|
||||
/**
|
||||
* Determine whether the visitor is connected or not.
|
||||
*/
|
||||
is_connected: attr(),
|
||||
/**
|
||||
* Name of the language of the visitor. (Ex: "English")
|
||||
*/
|
||||
lang: attr(),
|
||||
/**
|
||||
* Name of the visitor.
|
||||
*/
|
||||
name: attr(),
|
||||
nameOrDisplayName: attr({
|
||||
compute: '_computeNameOrDisplayName',
|
||||
dependencies: [
|
||||
'name',
|
||||
'partnerNameOrDisplayName',
|
||||
],
|
||||
}),
|
||||
/**
|
||||
* Partner linked to this visitor, if any.
|
||||
*/
|
||||
partner: many2one('mail.partner'),
|
||||
partnerAvatarUrl: attr({
|
||||
related: 'partner.avatarUrl',
|
||||
}),
|
||||
partnerCountry: many2one('mail.country',{
|
||||
related: 'partner.country',
|
||||
}),
|
||||
partnerNameOrDisplayName: attr({related: 'partner.nameOrDisplayName'}),
|
||||
/**
|
||||
* Threads with this visitor as member
|
||||
*/
|
||||
threads: one2many('mail.thread', {
|
||||
inverse: 'visitor',
|
||||
}),
|
||||
/**
|
||||
* Name of the website on which the visitor is connected. (Ex: "Website 1")
|
||||
*/
|
||||
website: attr(),
|
||||
};
|
||||
|
||||
Visitor.modelName = 'website_livechat.visitor';
|
||||
|
||||
return Visitor;
|
||||
}
|
||||
|
||||
registerNewModel('website_livechat.visitor', factory);
|
||||
|
||||
});
|
||||
@@ -17,7 +17,19 @@ MockModels.patch('website_livechat/static/tests/helpers/mock_models.js', T =>
|
||||
const data = super.generateData(...arguments);
|
||||
Object.assign(data, {
|
||||
'website.visitor': {
|
||||
fields: {},
|
||||
fields: {
|
||||
country_id: { string: "Country", type: 'many2one', relation: 'res.country' },
|
||||
display_name: { string: "Display name", type: 'string' },
|
||||
// Represent the browsing history of the visitor as a string.
|
||||
// To ease testing this allows tests to set it directly instead
|
||||
// of implementing the computation made on server.
|
||||
// This should normally not be a field.
|
||||
history: { string: "History", type: 'string'},
|
||||
is_connected: { string: "Is connected", type: 'boolean' },
|
||||
lang: { string: "Language", type: 'string'},
|
||||
partner_id: {string: "partner", type: "many2one", relation: 'res.partner'},
|
||||
website: { string: "Website", type: 'string' },
|
||||
},
|
||||
records: [],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -17,6 +17,32 @@ MockServer.include({
|
||||
}
|
||||
return this._super(...arguments);
|
||||
},
|
||||
/**
|
||||
* Overrides to add visitor information to livechat channels.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_mockMailChannelChannelInfo(ids, extra_info) {
|
||||
const channelInfos = this._super(...arguments);
|
||||
for (const channelInfo of channelInfos) {
|
||||
const channel = this._getRecords('mail.channel', [['id', '=', channelInfo.id]])[0];
|
||||
if (channel.channel_type === 'livechat' && channelInfo.livechat_visitor_id) {
|
||||
const visitor = this._getRecords('website.visitor', [['id', '=', channelInfo.livechat_visitor_id]])[0];
|
||||
const country = this._getRecords('res.country', [['id', '=', visitor.country_id]])[0];
|
||||
channelInfo.visitor = {
|
||||
name: visitor.display_name,
|
||||
country_code: country && country.code,
|
||||
country_id: country && country.id,
|
||||
is_connected: visitor.is_connected,
|
||||
history: visitor.history, // TODO should be computed
|
||||
website: visitor.website,
|
||||
lang: visitor.lang,
|
||||
partner_id: visitor.partner_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
return channelInfos;
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {integer[]} ids
|
||||
|
||||
@@ -4,11 +4,16 @@
|
||||
|
||||
<template id="assets_backend" name="website livechat assets" inherit_id="web.assets_backend">
|
||||
<xpath expr="script[last()]" position="after">
|
||||
<script type="text/javascript" src="/website_livechat/static/src/bugfix/bugfix.js"></script>
|
||||
<script type="text/javascript" src="/website_livechat/static/src/bugfix/bugfix.js"/>
|
||||
<script type="text/javascript" src="/website_livechat/static/src/components/discuss/discuss.js"/>
|
||||
<script type="text/javascript" src="/website_livechat/static/src/components/visitor_banner/visitor_banner.js"/>
|
||||
<script type="text/javascript" src="/website_livechat/static/src/models/messaging_notification_handler/messaging_notification_handler.js"/>
|
||||
<script type="text/javascript" src="/website_livechat/static/src/models/thread/thread.js"/>
|
||||
<script type="text/javascript" src="/website_livechat/static/src/models/visitor/visitor.js"/>
|
||||
</xpath>
|
||||
<xpath expr="link[last()]" position="after">
|
||||
<link rel="stylesheet" type="text/scss" href="/website_livechat/static/src/bugfix/bugfix.scss"/>
|
||||
<link rel="stylesheet" type="text/scss" href="/website_livechat/static/src/components/visitor_banner/visitor_banner.scss"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
@@ -23,6 +28,7 @@
|
||||
<template id="qunit_suite" name="website_livechat_tests" inherit_id="web.qunit_suite_tests">
|
||||
<xpath expr="." position="inside">
|
||||
<script type="text/javascript" src="/website_livechat/static/src/bugfix/bugfix_tests.js"/>
|
||||
<script type="text/javascript" src="/website_livechat/static/src/components/discuss/discuss_tests.js"/>
|
||||
<script type="text/javascript" src="/website_livechat/static/src/models/messaging_notification_handler/messaging_notification_handler_tests.js"/>
|
||||
<script type="text/javascript" src="/website_livechat/static/tests/helpers/mock_models.js"/>
|
||||
<script type="text/javascript" src="/website_livechat/static/tests/helpers/mock_server.js"/>
|
||||
|
||||
Reference in New Issue
Block a user