diff --git a/addons/mail/static/src/components/message/message.js b/addons/mail/static/src/components/message/message.js
index e63d35332ea..0f410b78673 100644
--- a/addons/mail/static/src/components/message/message.js
+++ b/addons/mail/static/src/components/message/message.js
@@ -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';
}
diff --git a/addons/mail/static/src/components/message/message.scss b/addons/mail/static/src/components/message/message.scss
index bb2fea6e47d..0b03a8ba835 100644
--- a/addons/mail/static/src/components/message/message.scss
+++ b/addons/mail/static/src/components/message/message.scss
@@ -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;
diff --git a/addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview.js b/addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview.js
index 2b6bede0909..5caefd7f341 100644
--- a/addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview.js
+++ b/addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview.js
@@ -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`;
diff --git a/addons/mail/static/src/components/thread_preview/thread_preview.js b/addons/mail/static/src/components/thread_preview/thread_preview.js
index 0e5a226a5f2..b10e8ee2586 100644
--- a/addons/mail/static/src/components/thread_preview/thread_preview.js
+++ b/addons/mail/static/src/components/thread_preview/thread_preview.js
@@ -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`;
}
diff --git a/addons/mail/static/src/models/country/country.js b/addons/mail/static/src/models/country/country.js
index 2e44f54712d..fb3617cf783 100644
--- a/addons/mail/static/src/models/country/country.js
+++ b/addons/mail/static/src/models/country/country.js
@@ -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(),
};
diff --git a/addons/mail/static/src/models/partner/partner.js b/addons/mail/static/src/models/partner/partner.js
index 4a8a1c54f0d..e52e148c9e1 100644
--- a/addons/mail/static/src/models/partner/partner.js
+++ b/addons/mail/static/src/models/partner/partner.js
@@ -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',
}),
diff --git a/addons/mail/static/src/variables.scss b/addons/mail/static/src/variables.scss
index 8d8caab19d6..e9bae9ab8cc 100644
--- a/addons/mail/static/src/variables.scss
+++ b/addons/mail/static/src/variables.scss
@@ -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;
diff --git a/addons/website_livechat/__manifest__.py b/addons/website_livechat/__manifest__.py
index 2ffb375b6eb..f30748fec11 100644
--- a/addons/website_livechat/__manifest__.py
+++ b/addons/website_livechat/__manifest__.py
@@ -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',
],
}
diff --git a/addons/website_livechat/models/mail_channel.py b/addons/website_livechat/models/mail_channel.py
index f82559491ab..8d6f91969ac 100644
--- a/addons/website_livechat/models/mail_channel.py
+++ b/addons/website_livechat/models/mail_channel.py
@@ -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,
diff --git a/addons/website_livechat/static/src/components/discuss/discuss.js b/addons/website_livechat/static/src/components/discuss/discuss.js
new file mode 100644
index 00000000000..18b41cea432
--- /dev/null
+++ b/addons/website_livechat/static/src/components/discuss/discuss.js
@@ -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,
+});
+
+});
diff --git a/addons/website_livechat/static/src/components/discuss/discuss.xml b/addons/website_livechat/static/src/components/discuss/discuss.xml
new file mode 100644
index 00000000000..af5a14696f0
--- /dev/null
+++ b/addons/website_livechat/static/src/components/discuss/discuss.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/website_livechat/static/src/components/discuss/discuss_tests.js b/addons/website_livechat/static/src/components/discuss/discuss_tests.js
new file mode 100644
index 00000000000..153e55e4533
--- /dev/null
+++ b/addons/website_livechat/static/src/components/discuss/discuss_tests.js
@@ -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",
+ );
+});
+
+});
+});
+});
+
+});
diff --git a/addons/website_livechat/static/src/components/visitor_banner/visitor_banner.js b/addons/website_livechat/static/src/components/visitor_banner/visitor_banner.js
new file mode 100644
index 00000000000..0b39134dcfc
--- /dev/null
+++ b/addons/website_livechat/static/src/components/visitor_banner/visitor_banner.js
@@ -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;
+
+});
diff --git a/addons/website_livechat/static/src/components/visitor_banner/visitor_banner.scss b/addons/website_livechat/static/src/components/visitor_banner/visitor_banner.scss
new file mode 100644
index 00000000000..9f042d2a16e
--- /dev/null
+++ b/addons/website_livechat/static/src/components/visitor_banner/visitor_banner.scss
@@ -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;
+}
diff --git a/addons/website_livechat/static/src/components/visitor_banner/visitor_banner.xml b/addons/website_livechat/static/src/components/visitor_banner/visitor_banner.xml
new file mode 100644
index 00000000000..b1dc4bd3283
--- /dev/null
+++ b/addons/website_livechat/static/src/components/visitor_banner/visitor_banner.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/website_livechat/static/src/models/thread/thread.js b/addons/website_livechat/static/src/models/thread/thread.js
new file mode 100644
index 00000000000..46d80ca794f
--- /dev/null
+++ b/addons/website_livechat/static/src/models/thread/thread.js
@@ -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',
+ }),
+});
+
+});
diff --git a/addons/website_livechat/static/src/models/visitor/visitor.js b/addons/website_livechat/static/src/models/visitor/visitor.js
new file mode 100644
index 00000000000..8c1b417e0d0
--- /dev/null
+++ b/addons/website_livechat/static/src/models/visitor/visitor.js
@@ -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);
+
+});
diff --git a/addons/website_livechat/static/tests/helpers/mock_models.js b/addons/website_livechat/static/tests/helpers/mock_models.js
index 93ae70331a8..bc4079596f8 100644
--- a/addons/website_livechat/static/tests/helpers/mock_models.js
+++ b/addons/website_livechat/static/tests/helpers/mock_models.js
@@ -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: [],
},
});
diff --git a/addons/website_livechat/static/tests/helpers/mock_server.js b/addons/website_livechat/static/tests/helpers/mock_server.js
index 8a75f07abfe..1383e521fe9 100644
--- a/addons/website_livechat/static/tests/helpers/mock_server.js
+++ b/addons/website_livechat/static/tests/helpers/mock_server.js
@@ -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
diff --git a/addons/website_livechat/views/assets.xml b/addons/website_livechat/views/assets.xml
index 92f52b64f75..7fe3cb2c102 100644
--- a/addons/website_livechat/views/assets.xml
+++ b/addons/website_livechat/views/assets.xml
@@ -4,11 +4,16 @@
-
+
+
+
+
+
+
@@ -23,6 +28,7 @@
+