[FIX] im_livechat, mail, website_livechat: open chat website visitor
Also fix an issue with livechats not being considered in 'chat' filter of messaging menu. Task-Id 2282426 closes odoo/odoo#58468 X-original-commit: 631e52536964763bbfe857305f023e5e67084e95 Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
odoo.define('im_livechat/static/src/components/messaging_menu/messaging_menu_tests.js', function (require) {
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
afterEach,
|
||||
afterNextRender,
|
||||
beforeEach,
|
||||
start,
|
||||
} = require('mail/static/src/utils/test_utils.js');
|
||||
|
||||
QUnit.module('im_livechat', {}, function () {
|
||||
QUnit.module('components', {}, function () {
|
||||
QUnit.module('messaging_menu', {}, function () {
|
||||
QUnit.module('messaging_menu_tests.js', {
|
||||
beforeEach() {
|
||||
beforeEach(this);
|
||||
|
||||
this.start = async params => {
|
||||
let { env, widget } = await start(Object.assign({}, params, {
|
||||
data: this.data,
|
||||
hasMessagingMenu: true,
|
||||
}));
|
||||
this.env = env;
|
||||
this.widget = widget;
|
||||
};
|
||||
},
|
||||
afterEach() {
|
||||
afterEach(this);
|
||||
},
|
||||
});
|
||||
|
||||
QUnit.test('livechats should be in "chat" filter', async function (assert) {
|
||||
assert.expect(7);
|
||||
|
||||
this.data['mail.channel'].records.push({
|
||||
anonymous_name: "Visitor 11",
|
||||
channel_type: 'livechat',
|
||||
id: 11,
|
||||
livechat_operator_id: this.data.currentPartnerId,
|
||||
members: [this.data.currentPartnerId, this.data.publicPartnerId],
|
||||
});
|
||||
await this.start();
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_MessagingMenu',
|
||||
"should have messaging menu"
|
||||
);
|
||||
|
||||
await afterNextRender(() => document.querySelector('.o_MessagingMenu_toggler').click());
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_MessagingMenu_tabButton[data-tab-id="all"]',
|
||||
"should have a tab/filter 'all' in messaging menu"
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_MessagingMenu_tabButton[data-tab-id="chat"]',
|
||||
"should have a tab/filter 'chat' in messaging menu"
|
||||
);
|
||||
assert.hasClass(
|
||||
document.querySelector('.o_MessagingMenu_tabButton[data-tab-id="all"]'),
|
||||
'o-active',
|
||||
"tab/filter 'all' of messaging menu should be active initially"
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
`.o_ThreadPreview[data-thread-local-id="${
|
||||
this.env.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 11,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]`,
|
||||
"livechat should be listed in 'all' tab/filter of messaging menu"
|
||||
);
|
||||
|
||||
await afterNextRender(() =>
|
||||
document.querySelector('.o_MessagingMenu_tabButton[data-tab-id="chat"]').click()
|
||||
);
|
||||
assert.hasClass(
|
||||
document.querySelector('.o_MessagingMenu_tabButton[data-tab-id="chat"]'),
|
||||
'o-active',
|
||||
"tab/filter 'chat' of messaging menu should become active after click"
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
`.o_ThreadPreview[data-thread-local-id="${
|
||||
this.env.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 11,
|
||||
model: 'mail.channel',
|
||||
}).localId
|
||||
}"]`,
|
||||
"livechat should be listed in 'chat' tab/filter of messaging menu"
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -86,6 +86,12 @@ registerInstancePatchModel('mail.thread', 'im_livechat/static/src/models/thread/
|
||||
}
|
||||
return this._super();
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_computeIsChatChannel() {
|
||||
return this.channel_type === 'livechat' || this._super();
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
<script type="text/javascript" src="/im_livechat/static/src/bugfix/bugfix_tests.js"></script>
|
||||
<script type="text/javascript" src="/im_livechat/static/src/components/composer/composer_tests.js"></script>
|
||||
<script type="text/javascript" src="/im_livechat/static/src/components/discuss/discuss_tests.js"></script>
|
||||
<script type="text/javascript" src="/im_livechat/static/src/components/messaging_menu/messaging_menu_tests.js"/>
|
||||
<script type="text/javascript" src="/im_livechat/static/src/components/thread_icon/thread_icon_tests.js"></script>
|
||||
<script type="text/javascript" src="/im_livechat/static/src/components/thread_textual_typing_status/thread_textual_typing_status_tests.js"></script>
|
||||
<script type="text/javascript" src="/im_livechat/static/src/legacy/public_livechat.js"></script>
|
||||
|
||||
@@ -187,7 +187,7 @@ class NotificationList extends Component {
|
||||
} else if (props.filter === 'chat') {
|
||||
return this.env.models['mail.thread']
|
||||
.all(thread =>
|
||||
thread.channel_type === 'chat' &&
|
||||
thread.isChatChannel &&
|
||||
thread.isPinned &&
|
||||
thread.model === 'mail.channel'
|
||||
)
|
||||
|
||||
@@ -964,6 +964,14 @@ function factory(dependencies) {
|
||||
return ['chat', 'livechat'].includes(this.channel_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_computeIsChatChannel() {
|
||||
return this.channel_type === 'chat';
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
@@ -1426,6 +1434,19 @@ function factory(dependencies) {
|
||||
],
|
||||
}),
|
||||
id: attr(),
|
||||
/**
|
||||
* States whether this thread is a `mail.channel` qualified as chat.
|
||||
*
|
||||
* Useful to list chat channels, like in messaging menu with the filter
|
||||
* 'chat'.
|
||||
*/
|
||||
isChatChannel: attr({
|
||||
compute: '_computeIsChatChannel',
|
||||
dependencies: [
|
||||
'channel_type',
|
||||
],
|
||||
default: false,
|
||||
}),
|
||||
isCurrentPartnerFollowing: attr({
|
||||
compute: '_computeIsCurrentPartnerFollowing',
|
||||
default: false,
|
||||
|
||||
@@ -507,6 +507,22 @@ var MockServer = Class.extend({
|
||||
return Math.max(record.id, max);
|
||||
}, 0) + 1;
|
||||
},
|
||||
/**
|
||||
* Simulate a 'call_button' operation from a view.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} param0
|
||||
* @param {Array<integer[]>} param0.args
|
||||
* @param {Object} [param0.kargs]
|
||||
* @param {string} param0.method
|
||||
* @param {string} param0.model
|
||||
* @returns {any}
|
||||
* @throws {Error} in case the call button of provided model/method is not
|
||||
* implemented.
|
||||
*/
|
||||
_mockCallButton({ args, kwargs, method, model }) {
|
||||
throw new Error(`Unimplemented mocked call button on "${model}"/"${method}"`);
|
||||
},
|
||||
/**
|
||||
* Simulate a 'copy' operation, so we simply try to duplicate a record in
|
||||
* memory
|
||||
@@ -1820,6 +1836,8 @@ var MockServer = Class.extend({
|
||||
*/
|
||||
_performRpc: function (route, args) {
|
||||
switch (route) {
|
||||
case '/web/dataset/call_button':
|
||||
return Promise.resolve(this._mockCallButton(args));
|
||||
case '/web/action/load':
|
||||
return Promise.resolve(this._mockLoadAction(args));
|
||||
|
||||
|
||||
@@ -57,8 +57,13 @@ class WebsiteVisitor(models.Model):
|
||||
operator = self.env.user
|
||||
country = visitor.country_id
|
||||
visitor_name = "%s (%s)" % (visitor.display_name, country.name) if country else visitor.display_name
|
||||
channel_partner_to_add = [(4, operator.partner_id.id)]
|
||||
if visitor.partner_id:
|
||||
channel_partner_to_add.append((4, visitor.partner_id.id))
|
||||
else:
|
||||
channel_partner_to_add.append((4, self.env.ref('base.public_partner').id))
|
||||
mail_channel_vals_list.append({
|
||||
'channel_partner_ids': [(4, operator.partner_id.id)],
|
||||
'channel_partner_ids': channel_partner_to_add,
|
||||
'livechat_channel_id': visitor.website_id.channel_id.id,
|
||||
'livechat_operator_id': self.env.user.partner_id.id,
|
||||
'channel_type': 'livechat',
|
||||
@@ -73,6 +78,25 @@ class WebsiteVisitor(models.Model):
|
||||
if mail_channel_vals_list:
|
||||
mail_channels = self.env['mail.channel'].create(mail_channel_vals_list)
|
||||
# Open empty chatter to allow the operator to start chatting with the visitor.
|
||||
mail_channels_info = mail_channels.channel_info('channel_minimize')
|
||||
values = {
|
||||
'fold_state': 'open',
|
||||
'is_minimized': True,
|
||||
}
|
||||
mail_channels_uuid = mail_channels.mapped('uuid')
|
||||
domain = [('partner_id', '=', self.env.user.partner_id.id), ('channel_id.uuid', 'in', mail_channels_uuid)]
|
||||
channel_partners = self.env['mail.channel.partner'].search(domain)
|
||||
channel_partners.write(values)
|
||||
mail_channels_info = mail_channels.channel_info('send_chat_request')
|
||||
notifications = []
|
||||
for mail_channel_info in mail_channels_info:
|
||||
self.env['bus.bus'].sendone((self._cr.dbname, 'res.partner', operator.partner_id.id), mail_channel_info)
|
||||
notifications.append([(self._cr.dbname, 'res.partner', operator.partner_id.id), mail_channel_info])
|
||||
self.env['bus.bus'].sendmany(notifications)
|
||||
|
||||
def _link_to_partner(self, partner, update_values=None):
|
||||
""" Adapt partner in members of related livechats """
|
||||
if partner:
|
||||
self.mail_channel_ids.channel_partner_ids = [
|
||||
(3, self.env.ref('base.public_partner').id),
|
||||
(4, partner.id),
|
||||
]
|
||||
super(WebsiteVisitor, self)._link_to_partner(partner, update_values=update_values)
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
odoo.define('website_livechat/static/src/models/messaging_notification_handler/messaging_notification_handler.js', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { registerInstancePatchModel } = require('mail/static/src/model/model_core.js');
|
||||
|
||||
registerInstancePatchModel('mail.messaging_notification_handler', 'website_livechat/static/src/models/messaging_notification_handler/messaging_notification_handler.js', {
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Private
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_handleNotificationPartner(data) {
|
||||
const { info } = data;
|
||||
if (info === 'send_chat_request') {
|
||||
this._handleNotificationPartnerChannel(data);
|
||||
const channel = this.env.models['mail.thread'].findFromIdentifyingData({
|
||||
id: data.id,
|
||||
model: 'mail.channel',
|
||||
});
|
||||
this.env.messaging.chatWindowManager.openThread(channel, {
|
||||
makeActive: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
return this._super(data);
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
odoo.define('website_livechat/static/src/models/messaging_notification_handler/messaging_notification_handler_tests.js', function (require) {
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
afterEach,
|
||||
afterNextRender,
|
||||
beforeEach,
|
||||
start,
|
||||
} = require('mail/static/src/utils/test_utils.js');
|
||||
|
||||
const FormView = require('web.FormView');
|
||||
const {
|
||||
mock: {
|
||||
intercept,
|
||||
},
|
||||
} = require('web.test_utils');
|
||||
|
||||
QUnit.module('website_livechat', {}, function () {
|
||||
QUnit.module('models', {}, function () {
|
||||
QUnit.module('messaging_notification_handler', {}, function () {
|
||||
QUnit.module('messaging_notification_handler_tests.js', {
|
||||
beforeEach() {
|
||||
beforeEach(this);
|
||||
|
||||
this.start = async params => {
|
||||
const { env, widget } = await start(Object.assign({}, {
|
||||
data: this.data,
|
||||
}, params));
|
||||
this.env = env;
|
||||
this.widget = widget;
|
||||
};
|
||||
},
|
||||
afterEach() {
|
||||
afterEach(this);
|
||||
},
|
||||
});
|
||||
|
||||
QUnit.test('should open chat window on send chat request to website visitor', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
this.data['website.visitor'].records.push({
|
||||
id: 11,
|
||||
name: "Visitor #11",
|
||||
});
|
||||
await this.start({
|
||||
data: this.data,
|
||||
hasChatWindow: true,
|
||||
hasView: true,
|
||||
// View params
|
||||
View: FormView,
|
||||
model: 'website.visitor',
|
||||
arch: `
|
||||
<form>
|
||||
<header>
|
||||
<button name="action_send_chat_request" string="Send chat request" class="btn btn-primary" type="button"/>
|
||||
</header>
|
||||
<field name="name"/>
|
||||
</form>
|
||||
`,
|
||||
res_id: 11,
|
||||
});
|
||||
intercept(this.widget, 'execute_action', payload => {
|
||||
this.env.services.rpc({
|
||||
route: '/web/dataset/call_button',
|
||||
params: {
|
||||
args: [payload.data.env.resIDs],
|
||||
kwargs: { context: payload.data.env.context },
|
||||
method: payload.data.action_data.name,
|
||||
model: payload.data.env.model,
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await afterNextRender(() =>
|
||||
document.querySelector('button[name="action_send_chat_request"]').click()
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_ChatWindow',
|
||||
"should have a chat window open after sending chat request to website visitor"
|
||||
);
|
||||
assert.hasClass(
|
||||
document.querySelector('.o_ChatWindow'),
|
||||
'o-focused',
|
||||
"chat window of livechat should be focused on open"
|
||||
);
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_ChatWindowHeader_name').textContent,
|
||||
"Visitor #11",
|
||||
"chat window of livechat should have name of visitor in the name"
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
odoo.define('website_livechat/static/tests/helpers/mock_models.js', function (require) {
|
||||
'use strict';
|
||||
|
||||
const MockModels = require('mail/static/tests/helpers/mock_models.js');
|
||||
|
||||
MockModels.patch('website_livechat/static/tests/helpers/mock_models.js', T =>
|
||||
class extends T {
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Public
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
static generateData() {
|
||||
const data = super.generateData(...arguments);
|
||||
Object.assign(data, {
|
||||
'website.visitor': {
|
||||
fields: {},
|
||||
records: [],
|
||||
},
|
||||
});
|
||||
Object.assign(data['mail.channel'].fields, {
|
||||
livechat_visitor_id: { string: "Visitor", type: 'many2one', relation: 'website.visitor' },
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
odoo.define('website_livechat/static/tests/helpers/mock_server.js', function (require) {
|
||||
'use strict';
|
||||
|
||||
require('im_livechat/static/tests/helpers/mock_server.js'); // ensure mail overrides are applied first
|
||||
|
||||
const MockServer = require('web.MockServer');
|
||||
|
||||
MockServer.include({
|
||||
/**
|
||||
* Simulate a 'call_button' operation from a view.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_mockCallButton({ args, kwargs, method, model }) {
|
||||
if (model === 'website.visitor' && method === 'action_send_chat_request') {
|
||||
return this._mockWebsiteVisitorActionSendChatRequest(args[0]);
|
||||
}
|
||||
return this._super(...arguments);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {integer[]} ids
|
||||
*/
|
||||
_mockWebsiteVisitorActionSendChatRequest(ids) {
|
||||
const visitors = this._getRecords('website.visitor', [['id', 'in', ids]]);
|
||||
for (const visitor of visitors) {
|
||||
const country = visitor.country_id
|
||||
? this._getRecords('res.country', [['id', '=', visitor.country_id]])
|
||||
: undefined;
|
||||
const visitor_name = `${visitor.display_name}${country ? `(${country.name})` : ''}`;
|
||||
const members = [this.currentPartnerId];
|
||||
if (visitor.partner_id) {
|
||||
members.push(visitor.partner_id);
|
||||
} else {
|
||||
members.push(this.publicPartnerId);
|
||||
}
|
||||
const livechatId = this._mockCreate('mail.channel', {
|
||||
anonymous_name: visitor_name,
|
||||
channel_type: 'livechat',
|
||||
livechat_operator_id: this.currentPartnerId,
|
||||
members,
|
||||
public: 'private',
|
||||
});
|
||||
// notify operator
|
||||
const channelInfo = this._mockMailChannelChannelInfo([livechatId], 'send_chat_request')[0];
|
||||
const notification = [[false, 'res.partner', this.currentPartnerId], channelInfo];
|
||||
this._widget.call('bus_service', 'trigger', 'notification', [notification]);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
@@ -5,6 +5,7 @@
|
||||
<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/models/messaging_notification_handler/messaging_notification_handler.js"/>
|
||||
</xpath>
|
||||
<xpath expr="link[last()]" position="after">
|
||||
<link rel="stylesheet" type="text/scss" href="/website_livechat/static/src/bugfix/bugfix.scss"/>
|
||||
@@ -21,7 +22,10 @@
|
||||
|
||||
<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>
|
||||
<script type="text/javascript" src="/website_livechat/static/src/bugfix/bugfix_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"/>
|
||||
</xpath>
|
||||
</template>
|
||||
</data>
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<template id="assets_backend" inherit_id="website.assets_backend" name="website_livechat assets backend">
|
||||
<xpath expr="." position="inside">
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<!--
|
||||
Integrate Livechat in Common Frontend for Website
|
||||
Template registering all the assets required to execute the Livechat from a page containing Odoo
|
||||
|
||||
Reference in New Issue
Block a user