[MERGE][IMP] mail, mail_bot: improve Discuss onboarding and add your new best friend
Purpose of this merge is to improve onboarding of Discuss. This is done via a wow effect and a bot to test the Discuss app; otherwise you have nobody to talk to. Purpose is also to improve retention as "the best way to increase retention is this: when the user invites someone, when this guy activates its account, the user gets a push notification from the invited user that just logged in; that way he will come back to Odoo and start discussing with its colleagues. Concerning your new best friend: it is not a dog but Odoobot. See sub commits for more details. This merge is linked to task ID 1838588 and closes PR #25075.
This commit is contained in:
@@ -81,6 +81,8 @@ class ResUsers(models.Model):
|
||||
values.pop('login', None)
|
||||
values.pop('name', None)
|
||||
partner_user.write(values)
|
||||
if not partner_user.login_date:
|
||||
partner_user._notify_inviter()
|
||||
return (self.env.cr.dbname, partner_user.login, values.get('password'))
|
||||
else:
|
||||
# user does not exist: sign up invited user
|
||||
@@ -92,7 +94,8 @@ class ResUsers(models.Model):
|
||||
if partner.company_id:
|
||||
values['company_id'] = partner.company_id.id
|
||||
values['company_ids'] = [(6, 0, [partner.company_id.id])]
|
||||
self._signup_create_user(values)
|
||||
partner_user = self._signup_create_user(values)
|
||||
partner_user._notify_inviter()
|
||||
else:
|
||||
# no token, sign up an external user
|
||||
values['email'] = values.get('email') or values.get('login')
|
||||
@@ -110,6 +113,20 @@ class ResUsers(models.Model):
|
||||
raise SignupError(_('Signup is not allowed for uninvited users'))
|
||||
return self._create_user_from_template(values)
|
||||
|
||||
@api.multi
|
||||
def _notify_inviter(self):
|
||||
for user in self:
|
||||
invite_partner = user.create_uid.partner_id
|
||||
if invite_partner:
|
||||
# notify invite user that new user is connected
|
||||
title = _("%s connected") % user.name
|
||||
message = _("This is his first connection. Wish him welcome")
|
||||
self.env['bus.bus'].sendone(
|
||||
(self._cr.dbname, 'res.partner', invite_partner.id),
|
||||
{'type': 'user_connection', 'title': title,
|
||||
'message': message, 'partner_id': user.partner_id.id}
|
||||
)
|
||||
|
||||
def _create_user_from_template(self, values):
|
||||
template_user_id = literal_eval(self.env['ir.config_parameter'].sudo().get_param('base.template_portal_user_id', 'False'))
|
||||
template_user = self.browse(template_user_id)
|
||||
|
||||
@@ -27,11 +27,12 @@ var BusService = CrossTab.extend(ServicesMixin, {
|
||||
*
|
||||
* @param {string} title
|
||||
* @param {string} content
|
||||
* @param {function} [callback] if given callback will be called when user clicks on notification
|
||||
*/
|
||||
sendNotification: function (title, content) {
|
||||
sendNotification: function (title, content, callback) {
|
||||
if (window.Notification && Notification.permission === "granted") {
|
||||
if (this.isMasterTab()) {
|
||||
this._sendNativeNotification(title, content);
|
||||
this._sendNativeNotification(title, content, callback);
|
||||
}
|
||||
} else {
|
||||
this.do_notify(title, content);
|
||||
@@ -76,9 +77,10 @@ var BusService = CrossTab.extend(ServicesMixin, {
|
||||
* @private
|
||||
* @param {string} title
|
||||
* @param {string} content
|
||||
* @param {function} [callback] if given callback will be called when user clicks on notification
|
||||
*/
|
||||
_sendNativeNotification: function (title, content) {
|
||||
var notification = new Notification(title, {body: content, icon: "/mail/static/src/img/odoo_o.png"});
|
||||
_sendNativeNotification: function (title, content, callback) {
|
||||
var notification = new Notification(title, {body: content, icon: "/mail/static/src/img/odoobot.png"});
|
||||
notification.onclick = function () {
|
||||
window.focus();
|
||||
if (this.cancel) {
|
||||
@@ -86,6 +88,9 @@ var BusService = CrossTab.extend(ServicesMixin, {
|
||||
} else if (this.close) {
|
||||
this.close();
|
||||
}
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
@@ -0,0 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': 'Odoobot for livechat',
|
||||
'version': '1.0',
|
||||
'category': 'Discuss',
|
||||
'summary': 'Add livechat support for odoobot',
|
||||
'description': "",
|
||||
'website': 'https://www.odoo.com/page/discuss',
|
||||
'depends': ['mail_bot', 'im_livechat'],
|
||||
'installable': True,
|
||||
'application': False,
|
||||
'auto_install': True,
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import mail_bot
|
||||
@@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models, _
|
||||
|
||||
|
||||
class MailBot(models.AbstractModel):
|
||||
_inherit = 'mail.bot'
|
||||
|
||||
def _get_answer(self, record, body, values, command):
|
||||
if self._is_bot_in_private_channel(record):
|
||||
if self._is_bot_pinged(values):
|
||||
return _("That's me! 🎉<br/>Try to type \":\" to use canned responses.")
|
||||
elif values.get("canned_response_ids"):
|
||||
discuss_href = 'href="/web#action=mail.mail_channel_action_client_chat&active_id=%s"' % record.id
|
||||
discuss_src = 'src="/mail_bot/static/img/odoobot_discuss.png"'
|
||||
chatter_src = 'src="/mail_bot/static/img/odoobot_chatter.png"'
|
||||
return _("Good, you can customize your canned responses in the live chat application. <br/><br/>") + \
|
||||
_("There are 3 different ways in Odoo to interact with your colleagues: <br/>\
|
||||
-via this chat window<br/>\
|
||||
-via the <a href=%s>Discuss</a> application:<br/><img %s/><br/><br/>\
|
||||
-or via the chatter:<br/><img %s/><br/><br/>\
|
||||
Aaaaand that's it! Enjoy discovering Odoo!") % (discuss_href, discuss_src, chatter_src)
|
||||
|
||||
return super(MailBot, self)._get_answer(record, body, values, command)
|
||||
@@ -828,6 +828,9 @@ class Channel(models.Model):
|
||||
WHERE C.uuid = %s""", (uuid,))
|
||||
return self._cr.dictfetchall()
|
||||
|
||||
def _channel_fetch_listeners_where_clause(self, uuid):
|
||||
return ("C.uuid = %s", (uuid,))
|
||||
|
||||
@api.multi
|
||||
def channel_fetch_preview(self):
|
||||
""" Return the last message of the given channels """
|
||||
@@ -888,7 +891,8 @@ class Channel(models.Model):
|
||||
if self.public == 'private':
|
||||
msg += _(" This channel is private. People must be invited to join it.")
|
||||
else:
|
||||
channel_partners = self.env['mail.channel.partner'].search([('partner_id', '!=', partner.id), ('channel_id', '=', self.id)])
|
||||
all_channel_partners = self.env['mail.channel.partner'].with_context(active_test=False)
|
||||
channel_partners = all_channel_partners.search([('partner_id', '!=', partner.id), ('channel_id', '=', self.id)])
|
||||
msg = _("You are in a private conversation with <b>@%s</b>.") % (channel_partners[0].partner_id.name if channel_partners else _('Anonymous'))
|
||||
msg += _("""<br><br>
|
||||
Type <b>@username</b> to mention someone, and grab his attention.<br>
|
||||
|
||||
@@ -2030,7 +2030,11 @@ class MailThread(models.AbstractModel):
|
||||
values.pop(x, None)
|
||||
|
||||
# Post the message
|
||||
# canned_response_ids are added by js to be used by other computations (odoobot)
|
||||
# we need to pop it from values since it is not stored on mail.message
|
||||
canned_response_ids = values.pop('canned_response_ids', False)
|
||||
new_message = MailMessage.create(values)
|
||||
values['canned_response_ids'] = canned_response_ids
|
||||
self._message_post_after_hook(new_message, values, model_description=model_description, mail_auto_delete=mail_auto_delete)
|
||||
return new_message
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
@@ -361,6 +361,7 @@ var BasicComposer = Widget.extend({
|
||||
content: this._mentionManager.generateLinks(value),
|
||||
attachment_ids: _.pluck(this.get('attachment_ids'), 'id'),
|
||||
partner_ids: _.uniq(_.pluck(this._mentionManager.getListenerSelection('@'), 'id')),
|
||||
canned_response_ids: _.uniq(_.pluck(this._mentionManager.getListenerSelections()[':'], 'id')),
|
||||
command: commands.length > 0 ? commands[0].name : undefined,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -97,7 +97,7 @@ var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin
|
||||
*/
|
||||
getAvatarSource: function () {
|
||||
if (this._isOdoobotAuthor()) {
|
||||
return '/mail/static/src/img/odoo_o.png';
|
||||
return '/mail/static/src/img/odoobot.png';
|
||||
} else if (this.hasAuthor()) {
|
||||
return '/web/image/res.partner/' + this.getAuthorID() + '/image_small';
|
||||
} else if (this.getType() === 'email') {
|
||||
|
||||
@@ -329,6 +329,7 @@ var Thread = AbstractThread.extend(Mixins.EventDispatcherMixin, ServicesMixin, {
|
||||
partner_ids: data.partner_ids,
|
||||
body: body,
|
||||
attachment_ids: data.attachment_ids,
|
||||
canned_response_ids: data.canned_response_ids,
|
||||
};
|
||||
if ('subject' in data) {
|
||||
messageData.subject = data.subject;
|
||||
|
||||
@@ -46,6 +46,7 @@ var PREVIEW_MSG_MAX_SIZE = 350; // optimal for native english speakers
|
||||
var MailManager = AbstractService.extend({
|
||||
dependencies: ['ajax', 'bus_service', 'local_storage'],
|
||||
_ODOOBOT_ID: "ODOOBOT", // default authorID for transient messages
|
||||
IS_STATIC_PREVIEW_ENABLED: true,
|
||||
|
||||
/**
|
||||
* @override
|
||||
@@ -254,17 +255,23 @@ var MailManager = AbstractService.extend({
|
||||
*/
|
||||
getSystrayPreviews: function (filter) {
|
||||
var self = this;
|
||||
var defs = [];
|
||||
|
||||
var channelDef = this._getSystrayChannelPreviews(filter);
|
||||
var inboxDef = this._getSystrayInboxPreviews(filter);
|
||||
var failureDef = this._getSystrayMailFailurePreviews(filter);
|
||||
|
||||
return $.when(channelDef, inboxDef, failureDef)
|
||||
.then(function (previewsChannel, previewsInbox, previewsFailure) {
|
||||
defs = defs.concat([channelDef, inboxDef, failureDef]);
|
||||
if (this.IS_STATIC_PREVIEW_ENABLED) {
|
||||
var staticDef = this._getSystrayStaticPreviews(filter);
|
||||
defs.push(staticDef);
|
||||
}
|
||||
return $.when.apply($, defs)
|
||||
.then(function (previewsChannel, previewsInbox, previewsFailure, previewsStatic) {
|
||||
// order: failures > inbox > channel, each group must be sorted
|
||||
previewsChannel = self._sortPreviews(previewsChannel);
|
||||
previewsInbox = self._sortPreviews(previewsInbox);
|
||||
previewsFailure = self._sortPreviews(previewsFailure);
|
||||
return _.union(previewsFailure, previewsInbox, previewsChannel);
|
||||
return _.union(previewsFailure, previewsInbox, previewsChannel, previewsStatic);
|
||||
});
|
||||
},
|
||||
/**
|
||||
@@ -797,6 +804,28 @@ var MailManager = AbstractService.extend({
|
||||
return $.when([]);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Get the previews of static systray elements, adding notification request
|
||||
*
|
||||
* @private
|
||||
* @param {string|undefined} [filter]
|
||||
*/
|
||||
_getSystrayStaticPreviews: function (filter) {
|
||||
var elems = [];
|
||||
if (
|
||||
window.Notification && window.Notification.permission === "default" &&
|
||||
(filter === 'mailbox_inbox' || !filter)
|
||||
) {
|
||||
elems.push({
|
||||
title: _t("Odoobot has a request for you"),
|
||||
imageSRC: "/mail/static/src/img/smiley/odoobot.png",
|
||||
status: 'bot',
|
||||
body: _t("Enable desktop notifications to stay up to date"),
|
||||
id: 'request_notification',
|
||||
});
|
||||
}
|
||||
return $.when(elems);
|
||||
},
|
||||
/**
|
||||
* Initialize the canned responses from the server data
|
||||
*
|
||||
|
||||
@@ -165,13 +165,13 @@ MailManager.include({
|
||||
* Add or remove failure when receiving a failure update message
|
||||
*
|
||||
* @private
|
||||
* @param {Object} data
|
||||
* @param {Object[]} data.elements list of mail failure data
|
||||
* @param {string} data.elements[].message_id ID of related message that
|
||||
* @param {Object} datas
|
||||
* @param {Object[]} datas.elements list of mail failure data
|
||||
* @param {string} datas.elements[].message_id ID of related message that
|
||||
* has a mail failure.
|
||||
* @param {Array} data.elements[].notifications list of notifications
|
||||
* @param {Array} datas.elements[].notifications list of notifications
|
||||
* that is related to a mail failure.
|
||||
* @param {string} data.elements[].notifications[0] sending state of a mail
|
||||
* @param {string} datas.elements[].notifications[0] sending state of a mail
|
||||
* failure (e.g. 'exception').
|
||||
*/
|
||||
_handlePartnerMailFailureNotification: function (datas) {
|
||||
@@ -313,6 +313,8 @@ MailManager.include({
|
||||
this._handlePartnerActivityUpdateNotification(data);
|
||||
} else if (data.type === 'mail_failure') {
|
||||
this._handlePartnerMailFailureNotification(data);
|
||||
} else if (data.type === 'user_connection') {
|
||||
this._handlePartnerUserConnectionNotification(data);
|
||||
} else {
|
||||
this._handlePartnerChannelNotification(data);
|
||||
}
|
||||
@@ -402,8 +404,25 @@ MailManager.include({
|
||||
this._mailBus.trigger('unsubscribe_from_channel', data.id);
|
||||
this.do_notify(_("Unsubscribed"), message);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Shows a popup to notify a user connection
|
||||
*
|
||||
* @private
|
||||
* @param {Object} data
|
||||
* @param {Object[]} data.partner_id id of the connected partner
|
||||
* @param {string} data.title title to display on notification
|
||||
* @param {Array} data.messages message to display on notification
|
||||
*/
|
||||
_handlePartnerUserConnectionNotification: function (data) {
|
||||
var self=this;
|
||||
var partner_id = data.partner_id;
|
||||
this.call('bus_service', 'sendNotification', data.title, data.message, function(){
|
||||
self.call('mail_service', 'openDmWindow', partner_id);
|
||||
});
|
||||
},
|
||||
/**
|
||||
*
|
||||
* On receiving an update on user status (e.g. becoming 'online', 'offline',
|
||||
* 'idle', etc.).
|
||||
*
|
||||
|
||||
@@ -150,6 +150,27 @@ var MessagingMenu = Widget.extend({
|
||||
previews: previews,
|
||||
}));
|
||||
},
|
||||
/**
|
||||
* Display the browser notification request dialog when the user clicks on systray's corresponding notification
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_requestNotificationPermission: function () {
|
||||
var self = this;
|
||||
var def = window.Notification && window.Notification.requestPermission();
|
||||
if (def) {
|
||||
def.then(function (value) {
|
||||
if (value !== 'granted') {
|
||||
self.call('bus_service', 'sendNotification', self, _t('Permission denied'),
|
||||
_t('Odoo will not have the permission to send native notifications on this device.'));
|
||||
} else {
|
||||
self.call('bus_service', 'sendNotification', self, _t('Permission granted'),
|
||||
_t('Odoo has now the permission to send you native notifications on this device.'));
|
||||
}
|
||||
});
|
||||
}
|
||||
this.$(".o_mail_navbar_request_permission").slideUp();
|
||||
},
|
||||
/**
|
||||
* Get and render list of previews, based on the selected filter
|
||||
*
|
||||
@@ -269,6 +290,8 @@ var MessagingMenu = Widget.extend({
|
||||
var documentID = $target.data('document-id');
|
||||
var documentModel = $target.data('document-model');
|
||||
this._openDocument(documentModel, documentID);
|
||||
} else if (previewID === 'request_notification') {
|
||||
this._requestNotificationPermission();
|
||||
} else {
|
||||
// preview of thread
|
||||
this.call('mail_service', 'openThread', previewID);
|
||||
|
||||
@@ -84,7 +84,12 @@ QUnit.module('DocumentThreadWindow', {
|
||||
partner_id: partnerID, // so that needaction messages are treated as needactions
|
||||
};
|
||||
this.services = mailTestUtils.getMailServices();
|
||||
this.MailService = this.services.mail_service;
|
||||
this.MailService.prototype.IS_STATIC_PREVIEW_ENABLED = false;
|
||||
},
|
||||
afterEach: function () {
|
||||
this.MailService.prototype.IS_STATIC_PREVIEW_ENABLED = true;
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.test('open a document thread in a thread window', function (assert) {
|
||||
|
||||
@@ -58,8 +58,11 @@ QUnit.module('MessagingMenu (Mail Failures)', {
|
||||
};
|
||||
|
||||
this.services = mailTestUtils.getMailServices();
|
||||
this.MailService = this.services.mail_service;
|
||||
this.MailService.prototype.IS_STATIC_PREVIEW_ENABLED = false;
|
||||
},
|
||||
afterEach: function () {
|
||||
this.MailService.prototype.IS_STATIC_PREVIEW_ENABLED = true;
|
||||
// unpatch _.debounce and _.throttle
|
||||
_.debounce = this.underscoreDebounce;
|
||||
_.throttle = this.underscoreThrottle;
|
||||
|
||||
@@ -93,8 +93,11 @@ QUnit.module('MessagingMenu', {
|
||||
},
|
||||
};
|
||||
this.services = mailTestUtils.getMailServices();
|
||||
this.MailService = this.services.mail_service;
|
||||
this.MailService.prototype.IS_STATIC_PREVIEW_ENABLED = false;
|
||||
},
|
||||
afterEach: function () {
|
||||
this.MailService.prototype.IS_STATIC_PREVIEW_ENABLED = true;
|
||||
// unpatch _.debounce and _.throttle
|
||||
_.debounce = this.underscoreDebounce;
|
||||
_.throttle = this.underscoreThrottle;
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': 'Odoobot',
|
||||
'version': '1.0',
|
||||
'category': 'Discuss',
|
||||
'summary': 'Add Odoobot in discussions',
|
||||
'description': "",
|
||||
'website': 'https://www.odoo.com/page/discuss',
|
||||
'depends': ['mail'],
|
||||
'installable': True,
|
||||
'application': False,
|
||||
'auto_install': True,
|
||||
'data': [
|
||||
'views/assets.xml',
|
||||
'data/mailbot_data.xml',
|
||||
],
|
||||
'qweb': [
|
||||
'views/discuss.xml',
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="partner_odoobot" model="res.partner">
|
||||
<field name="name">OdooBot</field>
|
||||
<field name="customer" eval="False"/>
|
||||
<field name="email">OdooBot</field>
|
||||
<field name="active" eval="False"/>
|
||||
<field name="image" type="base64" file="mail/static/src/img/odoobot.png"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import ir_http
|
||||
from . import mail_bot
|
||||
from . import mail_channel
|
||||
from . import mail_thread
|
||||
from . import res_partner
|
||||
from . import res_users
|
||||
@@ -0,0 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models
|
||||
|
||||
|
||||
class Http(models.AbstractModel):
|
||||
_inherit = 'ir.http'
|
||||
|
||||
def session_info(self):
|
||||
res = super(Http, self).session_info()
|
||||
res['odoobot_initialized'] = self.env.user.odoobot_initialized
|
||||
return res
|
||||
@@ -0,0 +1,216 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import itertools
|
||||
import random
|
||||
|
||||
from odoo import models, _
|
||||
|
||||
|
||||
class MailBot(models.AbstractModel):
|
||||
_name = 'mail.bot'
|
||||
|
||||
def _apply_logic(self, record, values, command=None):
|
||||
""" Apply bot logic to generate an answer (or not) for the user
|
||||
The logic will only be applied if odoobot is in a chat with a user or
|
||||
if someone pinged odoobot.
|
||||
|
||||
:param record: the mail_thread (or mail_channel) where the user
|
||||
message was posted/odoobot will answer.
|
||||
:param values: msg_values of the message_post or other values needed by logic
|
||||
:param command: the name of the called command if the logic is not triggered by a message_post
|
||||
"""
|
||||
odoobot_id = self.env['ir.model.data'].xmlid_to_res_id("mail_bot.partner_odoobot")
|
||||
if len(record) != 1 or values.get("author_id") == odoobot_id:
|
||||
return
|
||||
if self._is_bot_pinged(values) or self._is_bot_in_private_channel(record):
|
||||
body = values.get("body", "").replace(u'\xa0', u' ').strip().lower().strip(".?!")
|
||||
answer = self._get_answer(record, body, values, command)
|
||||
if answer:
|
||||
message_type = values.get('message_type', 'comment')
|
||||
subtype_id = values.get('subtype_id', self.env['ir.model.data'].xmlid_to_res_id('mail.mt_comment'))
|
||||
record.with_context({"mail_create_nosubscribe": True}).message_post(body=answer, author_id=odoobot_id, message_type=message_type, subtype_id=subtype_id)
|
||||
|
||||
def _get_answer(self, record, body, values, command=False):
|
||||
# onboarding
|
||||
if self._is_bot_in_private_channel(record):
|
||||
# main flow
|
||||
if body in ('hi', 'hello'):
|
||||
return _("Hi, I will guide you through chat features. First, try to send me an emoji 😊")
|
||||
elif self._body_contains_emoji(body):
|
||||
return _("Great! :) Did you notice that you can also send attachments, like a picture of your cute dog? Try it!")
|
||||
elif values.get("attachment_ids"):
|
||||
return _("Not a cute dog, but you get it :) To access special features, start your sentence with '/' (e.g. /help).")
|
||||
elif command == 'help':
|
||||
return _("Wow you are a natural! Ping someone to grab its attention with @nameoftheuser. Try to ping me with <b>@OdooBot</b>.")
|
||||
elif self._is_bot_pinged(values):
|
||||
discuss_href = 'href="/web#action=mail.mail_channel_action_client_chat&active_id=%s"' % record.id
|
||||
discuss_src = 'src="/mail_bot/static/img/odoobot_discuss.png"'
|
||||
chatter_src = 'src="/mail_bot/static/img/odoobot_chatter.png"'
|
||||
return _("That's me! 🎉<br/>") + \
|
||||
_("There are 3 different ways in Odoo to interact with your colleagues: <br/>\
|
||||
-via this chat window<br/>\
|
||||
-via the <a href=%s>Discuss</a> application:<br/><img %s/><br/><br/>\
|
||||
-or via the chatter:<br/><img %s/><br/><br/>\
|
||||
Aaaaand that's it! Enjoy discovering Odoo!") % (discuss_href, discuss_src, chatter_src)
|
||||
# easter eggs
|
||||
elif body in ['❤️', _('i love you'), _('love')]:
|
||||
return _("Aaaaaw that's really cute but, you know, bots don't work that way. You're too human for me! Let's keep it professional ❤️")
|
||||
elif body in [_('help'), _('help me'), _('i need help')]:
|
||||
return _("I'm trying to help you, but I'm just a bot... :( You can also check <a href=\"https://www.odoo.com/page/docs\">our documentation</a>) for more information!")
|
||||
elif _('fuck') in body or "fuck" in body:
|
||||
return _("That's not a really nice thing to say, you know? I'm a bot but I have feelings, ok?! 💔")
|
||||
else:
|
||||
return random.choice([
|
||||
_("Mmmmh I'm not sure what you mean.. Can you try again?"),
|
||||
_("I'm afraid I don't understand. Sorry!")
|
||||
])
|
||||
elif self._is_bot_pinged(values):
|
||||
return random.choice([_("Yaaaay that's me!"), _("Odeyhoooo")])
|
||||
return False
|
||||
|
||||
def _body_contains_emoji(self, body):
|
||||
# coming from https://unicode.org/emoji/charts/full-emoji-list.html
|
||||
emoji_list = itertools.chain(
|
||||
range(0x0030, 0x3a),
|
||||
range(0x2194, 0x219a),
|
||||
range(0x21A9, 0x21ab),
|
||||
range(0x231A, 0x231c),
|
||||
range(0x23E9, 0x23f4),
|
||||
range(0x23F8, 0x23fb),
|
||||
range(0x25AA, 0x25ac),
|
||||
range(0x25FB, 0x25ff),
|
||||
range(0x2600, 0x2605),
|
||||
range(0x2614, 0x2616),
|
||||
range(0x2622, 0x2624),
|
||||
range(0x262E, 0x2630),
|
||||
range(0x2638, 0x263b),
|
||||
range(0x2648, 0x2654),
|
||||
range(0x265F, 0x2661),
|
||||
range(0x2665, 0x2667),
|
||||
range(0x267E, 0x2680),
|
||||
range(0x2692, 0x2698),
|
||||
range(0x269B, 0x269d),
|
||||
range(0x26A0, 0x26a2),
|
||||
range(0x26AA, 0x26ac),
|
||||
range(0x26B0, 0x26b2),
|
||||
range(0x26BD, 0x26bf),
|
||||
range(0x26C4, 0x26c6),
|
||||
range(0x26D3, 0x26d5),
|
||||
range(0x26E9, 0x26eb),
|
||||
range(0x26F0, 0x26f6),
|
||||
range(0x26F7, 0x26fb),
|
||||
range(0x2708, 0x270a),
|
||||
range(0x270A, 0x270c),
|
||||
range(0x270C, 0x270e),
|
||||
range(0x2733, 0x2735),
|
||||
range(0x2753, 0x2756),
|
||||
range(0x2763, 0x2765),
|
||||
range(0x2795, 0x2798),
|
||||
range(0x2934, 0x2936),
|
||||
range(0x2B05, 0x2b08),
|
||||
range(0x2B1B, 0x2b1d),
|
||||
range(0x1F170, 0x1f172),
|
||||
range(0x1F191, 0x1f19b),
|
||||
range(0x1F1E6, 0x1f200),
|
||||
range(0x1F201, 0x1f203),
|
||||
range(0x1F232, 0x1f23b),
|
||||
range(0x1F250, 0x1f252),
|
||||
range(0x1F300, 0x1f321),
|
||||
range(0x1F324, 0x1f32d),
|
||||
range(0x1F32D, 0x1f330),
|
||||
range(0x1F330, 0x1f336),
|
||||
range(0x1F337, 0x1f37d),
|
||||
range(0x1F37E, 0x1f380),
|
||||
range(0x1F380, 0x1f394),
|
||||
range(0x1F396, 0x1f398),
|
||||
range(0x1F399, 0x1f39c),
|
||||
range(0x1F39E, 0x1f3a0),
|
||||
range(0x1F3A0, 0x1f3c5),
|
||||
range(0x1F3C6, 0x1f3cb),
|
||||
range(0x1F3CB, 0x1f3cf),
|
||||
range(0x1F3CF, 0x1f3d4),
|
||||
range(0x1F3D4, 0x1f3e0),
|
||||
range(0x1F3E0, 0x1f3f1),
|
||||
range(0x1F3F3, 0x1f3f6),
|
||||
range(0x1F3F8, 0x1f400),
|
||||
range(0x1F400, 0x1f43f),
|
||||
range(0x1F442, 0x1f4f8),
|
||||
range(0x1F4F9, 0x1f4fd),
|
||||
range(0x1F500, 0x1f53e),
|
||||
range(0x1F549, 0x1f54b),
|
||||
range(0x1F54B, 0x1f54f),
|
||||
range(0x1F550, 0x1f568),
|
||||
range(0x1F56F, 0x1f571),
|
||||
range(0x1F573, 0x1f57a),
|
||||
range(0x1F58A, 0x1f58e),
|
||||
range(0x1F595, 0x1f597),
|
||||
range(0x1F5B1, 0x1f5b3),
|
||||
range(0x1F5C2, 0x1f5c5),
|
||||
range(0x1F5D1, 0x1f5d4),
|
||||
range(0x1F5DC, 0x1f5df),
|
||||
range(0x1F5FB, 0x1f600),
|
||||
range(0x1F601, 0x1f611),
|
||||
range(0x1F612, 0x1f615),
|
||||
range(0x1F61C, 0x1f61f),
|
||||
range(0x1F620, 0x1f626),
|
||||
range(0x1F626, 0x1f628),
|
||||
range(0x1F628, 0x1f62c),
|
||||
range(0x1F62E, 0x1f630),
|
||||
range(0x1F630, 0x1f634),
|
||||
range(0x1F635, 0x1f641),
|
||||
range(0x1F641, 0x1f643),
|
||||
range(0x1F643, 0x1f645),
|
||||
range(0x1F645, 0x1f650),
|
||||
range(0x1F680, 0x1f6c6),
|
||||
range(0x1F6CB, 0x1f6d0),
|
||||
range(0x1F6D1, 0x1f6d3),
|
||||
range(0x1F6E0, 0x1f6e6),
|
||||
range(0x1F6EB, 0x1f6ed),
|
||||
range(0x1F6F4, 0x1f6f7),
|
||||
range(0x1F6F7, 0x1f6f9),
|
||||
range(0x1F910, 0x1f919),
|
||||
range(0x1F919, 0x1f91f),
|
||||
range(0x1F920, 0x1f928),
|
||||
range(0x1F928, 0x1f930),
|
||||
range(0x1F931, 0x1f933),
|
||||
range(0x1F933, 0x1f93b),
|
||||
range(0x1F93C, 0x1f93f),
|
||||
range(0x1F940, 0x1f946),
|
||||
range(0x1F947, 0x1f94c),
|
||||
range(0x1F94D, 0x1f950),
|
||||
range(0x1F950, 0x1f95f),
|
||||
range(0x1F95F, 0x1f96c),
|
||||
range(0x1F96C, 0x1f971),
|
||||
range(0x1F973, 0x1f977),
|
||||
range(0x1F97C, 0x1f980),
|
||||
range(0x1F980, 0x1f985),
|
||||
range(0x1F985, 0x1f992),
|
||||
range(0x1F992, 0x1f998),
|
||||
range(0x1F998, 0x1f9a3),
|
||||
range(0x1F9B0, 0x1f9ba),
|
||||
range(0x1F9C1, 0x1f9c3),
|
||||
range(0x1F9D0, 0x1f9e7),
|
||||
range(0x1F9E7, 0x1fa00),
|
||||
[0x23, 0x2a, 0xa9, 0xae, 0x203c, 0x2049, 0x2122, 0x2139, 0x2328, 0x23cf, 0x24c2, 0x25b6, 0x25c0, 0x260e, 0x2611,
|
||||
0x2618, 0x261d, 0x2620, 0x2626, 0x262a, 0x2640, 0x2642, 0x2663, 0x2668, 0x267b, 0x2699, 0x26c8, 0x26ce, 0x26cf,
|
||||
0x26d1, 0x26fd, 0x2702, 0x2705, 0x270f, 0x2712, 0x2714, 0x2716, 0x271d, 0x2721, 0x2728, 0x2744, 0x2747, 0x274c,
|
||||
0x274e, 0x2757, 0x27a1, 0x27b0, 0x27bf, 0x2b50, 0x2b55, 0x3030, 0x303d, 0x3297, 0x3299, 0x1f004, 0x1f0cf, 0x1f17e,
|
||||
0x1f17f, 0x1f18e, 0x1f21a, 0x1f22f, 0x1f321, 0x1f336, 0x1f37d, 0x1f3c5, 0x1f3f7, 0x1f43f, 0x1f440, 0x1f441, 0x1f4f8,
|
||||
0x1f4fd, 0x1f4ff, 0x1f57a, 0x1f587, 0x1f590, 0x1f5a4, 0x1f5a5, 0x1f5a8, 0x1f5bc, 0x1f5e1, 0x1f5e3, 0x1f5e8, 0x1f5ef,
|
||||
0x1f5f3, 0x1f5fa, 0x1f600, 0x1f611, 0x1f615, 0x1f616, 0x1f617, 0x1f618, 0x1f619, 0x1f61a, 0x1f61b, 0x1f61f, 0x1f62c,
|
||||
0x1f62d, 0x1f634, 0x1f6d0, 0x1f6e9, 0x1f6f0, 0x1f6f3, 0x1f6f9, 0x1f91f, 0x1f930, 0x1f94c, 0x1f97a, 0x1f9c0]
|
||||
)
|
||||
if any(chr(emoji) in body for emoji in emoji_list):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _is_bot_pinged(self, values):
|
||||
odoobot_id = self.env['ir.model.data'].xmlid_to_res_id("mail_bot.partner_odoobot")
|
||||
return (4, odoobot_id) in values.get('partner_ids', [])
|
||||
|
||||
def _is_bot_in_private_channel(self, record):
|
||||
odoobot_id = self.env['ir.model.data'].xmlid_to_res_id("mail_bot.partner_odoobot")
|
||||
if record._name == 'mail.channel' and record.channel_type == 'chat':
|
||||
return odoobot_id in record.with_context(active_test=False).channel_partner_ids.ids
|
||||
return False
|
||||
@@ -0,0 +1,41 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, models, _
|
||||
|
||||
|
||||
class Channel(models.Model):
|
||||
_inherit = 'mail.channel'
|
||||
|
||||
def _execute_command_help(self, **kwargs):
|
||||
super(Channel, self)._execute_command_help(**kwargs)
|
||||
self.env['mail.bot']._apply_logic(self, kwargs, command="help") # kwargs are not usefull but...
|
||||
|
||||
@api.model
|
||||
def channel_fetch_listeners(self, uuid):
|
||||
""" Return the id, name and email of partners listening to the given channel """
|
||||
odoobot_id = self.env['ir.model.data'].xmlid_to_res_id("mail_bot.partner_odoobot")
|
||||
self._cr.execute("""
|
||||
SELECT P.id, P.name, P.email
|
||||
FROM mail_channel_partner CP
|
||||
INNER JOIN res_partner P ON CP.partner_id = P.id
|
||||
INNER JOIN mail_channel C ON CP.channel_id = C.id
|
||||
WHERE C.uuid = %s OR P.id = %s""", (uuid, odoobot_id,))
|
||||
return self._cr.dictfetchall()
|
||||
|
||||
@api.model
|
||||
def init_odoobot(self):
|
||||
if not self.env.user.odoobot_initialized:
|
||||
partner = self.env.user.partner_id
|
||||
odoobot_id = self.env['ir.model.data'].xmlid_to_res_id("mail_bot.partner_odoobot")
|
||||
channel = self.with_context({"mail_create_nosubscribe": True}).create({
|
||||
'channel_partner_ids': [(4, partner.id), (4, odoobot_id)],
|
||||
'public': 'private',
|
||||
'channel_type': 'chat',
|
||||
'email_send': False,
|
||||
'name': 'OdooBot'
|
||||
})
|
||||
message = _("Hello, I'm here to help you discover chat features. Try answering me with an emoji 😊")
|
||||
channel.message_post(body=message, author_id=odoobot_id, message_type="comment", subtype="mail.mt_comment")
|
||||
self.env.user.odoobot_initialized = True
|
||||
return channel
|
||||
@@ -0,0 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models
|
||||
|
||||
|
||||
class MailThread(models.AbstractModel):
|
||||
_inherit = 'mail.thread'
|
||||
|
||||
def _message_post_after_hook(self, message, values, model_description=False, mail_auto_delete=True):
|
||||
self.env['mail.bot']._apply_logic(self, values)
|
||||
return super(MailThread, self)._message_post_after_hook(message, values, model_description=model_description, mail_auto_delete=mail_auto_delete)
|
||||
@@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from odoo import api, models
|
||||
|
||||
class Partner(models.Model):
|
||||
_inherit = 'res.partner'
|
||||
|
||||
def _compute_im_status(self):
|
||||
#we asume that mail_bot _compute_im_status will be executed after bus _compute_im_status
|
||||
super(Partner, self)._compute_im_status()
|
||||
odoobot_id = self.env['ir.model.data'].xmlid_to_res_id("mail_bot.partner_odoobot")
|
||||
for partner in self:
|
||||
if partner.id == odoobot_id:
|
||||
partner.im_status = 'bot'
|
||||
|
||||
@api.model
|
||||
def get_mention_suggestions(self, search, limit=8):
|
||||
#add odoobot in mention suggestion when pinging in mail_thread
|
||||
[users, partners] = super(Partner, self).get_mention_suggestions(search, limit=limit)
|
||||
if len(partners) + len(users) < limit and "odoobot".startswith(search.lower()):
|
||||
odoobot = self.env.ref("mail_bot.partner_odoobot")
|
||||
if not any([elem['id'] == odoobot.id for elem in partners]):
|
||||
if odoobot:
|
||||
partners.append({'id': odoobot.id, 'name': odoobot.name, 'email': odoobot.email})
|
||||
return [users, partners]
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models, fields
|
||||
|
||||
class Users(models.Model):
|
||||
_inherit = 'res.users'
|
||||
|
||||
odoobot_initialized = fields.Boolean(readonly=True, default=False)
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
@@ -0,0 +1,28 @@
|
||||
odoo.define('mail_bot.MailBotService', function (require) {
|
||||
"use strict";
|
||||
|
||||
var AbstractService = require('web.AbstractService');
|
||||
var core = require('web.core');
|
||||
var session = require('web.session');
|
||||
|
||||
var MailBotService = AbstractService.extend({
|
||||
name: 'mailbot_service',
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
start: function () {
|
||||
var self = this;
|
||||
if ('odoobot_initialized' in session && ! session.odoobot_initialized) {
|
||||
setTimeout(function () {
|
||||
session.odoobot_initialized = true;
|
||||
self._rpc({
|
||||
model: 'mail.channel',
|
||||
method: 'init_odoobot',
|
||||
});
|
||||
}, 2*60*1000);
|
||||
}
|
||||
},
|
||||
});
|
||||
core.serviceRegistry.add('mailbot_service', MailBotService);
|
||||
return MailBotService;
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<template id="assets_backend" name="mailbot assets" inherit_id="web.assets_backend">
|
||||
<xpath expr="." position="inside">
|
||||
<script type="text/javascript" src="/mail_bot/static/src/js/mailbot_service.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-extend="mail.Preview">
|
||||
<t t-jquery="div[t-attf-class*='o_mail_preview_image']" t-operation="append">
|
||||
<i t-if="preview.status == 'bot'" class="o_mail_user_status o_user_online fa fa-heart" title="Bot" role="img" aria-label="User is a bot"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-extend="mail.UserStatus">
|
||||
<t t-jquery="i:last" t-operation="after">
|
||||
<i t-if="status == 'bot'" class="o_mail_user_status o_user_online fa fa-heart" title="Bot" role="img" aria-label="User is a bot"/>
|
||||
</t>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -9,7 +9,7 @@
|
||||
'description': """This module contains tests related to mail. Those are
|
||||
contained in a separate module as it contains models used only to perform
|
||||
tests independently to functional aspects of other models. """,
|
||||
'depends': ['test_performance', 'mail'],
|
||||
'depends': ['test_performance', 'mail', 'mail_bot'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'data/data.xml',
|
||||
|
||||
@@ -15,4 +15,5 @@ from . import test_ir_actions
|
||||
from . import test_update_notification
|
||||
from . import test_discuss
|
||||
from . import test_performance
|
||||
from . import test_res_users
|
||||
from . import test_res_users
|
||||
from . import test_odoobot
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo.addons.test_mail.tests.common import BaseFunctionalTest, MockEmails, TestRecipients
|
||||
from odoo.tools import mute_logger
|
||||
from odoo.tests import tagged
|
||||
|
||||
|
||||
@tagged("odoobot")
|
||||
class TestOdoobot(BaseFunctionalTest, MockEmails, TestRecipients):
|
||||
|
||||
def setUp(self):
|
||||
super(TestOdoobot, self).setUp()
|
||||
self.odoobot = self.env.ref("mail_bot.partner_odoobot")
|
||||
self.message_post_default_kwargs = {
|
||||
'body': '',
|
||||
'attachment_ids': [],
|
||||
'message_type': 'comment',
|
||||
'partner_ids': [],
|
||||
'subtype': 'mail.mt_comment'
|
||||
}
|
||||
self.odoobot_ping_body = '<a href="http://odoo.com/web#model=res.partner&id=%s" class="o_mail_redirect" data-oe-id="%s" data-oe-model="res.partner" target="_blank">@OdooBot</a>' % (self.odoobot.id, self.odoobot.id)
|
||||
self.test_record_employe = self.test_record.sudo(self.user_employee)
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_odoobot_ping(self):
|
||||
kwargs = self.message_post_default_kwargs.copy()
|
||||
kwargs.update({'body': self.odoobot_ping_body, 'partner_ids': [self.odoobot.id, self.user_admin.partner_id.id]})
|
||||
|
||||
with patch('random.choice', lambda x: x[0]):
|
||||
self.assertNextMessage(
|
||||
self.test_record_employe.with_context({'mail_post_autofollow': True}).message_post(**kwargs),
|
||||
sender=self.odoobot,
|
||||
answer=["Yaaaay that's me!"] # no the perfect idea to base a test on a random but it should work for test phase, better mock random choice?
|
||||
)
|
||||
# Odoobot should not be a follower but user_employee and user_admin should
|
||||
follower = self.test_record.message_follower_ids.mapped('partner_id')
|
||||
self.assertNotIn(self.odoobot, follower)
|
||||
self.assertIn(self.user_employee.partner_id, follower)
|
||||
self.assertIn(self.user_admin.partner_id, follower)
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_onboarding_flow(self):
|
||||
kwargs = self.message_post_default_kwargs.copy()
|
||||
channel = self.env['mail.channel'].sudo(self.user_employee).init_odoobot()
|
||||
|
||||
kwargs['body'] = 'tagada 😊'
|
||||
self.assertNextMessage(
|
||||
channel.message_post(**kwargs),
|
||||
sender=self.odoobot,
|
||||
answer="Great! :) Did you notice that you can also send attachments, like a picture of your cute dog? Try it!"
|
||||
)
|
||||
kwargs['body'] = ''
|
||||
kwargs['attachment_ids'] = [1]
|
||||
last_message = self.assertNextMessage(
|
||||
channel.message_post(**kwargs),
|
||||
sender=self.odoobot,
|
||||
answer="Not a cute dog, but you get it :) To access special features, start your sentence with '/' (e.g. /help)."
|
||||
)
|
||||
kwargs['attachment_ids'] = []
|
||||
|
||||
channel.execute_command(command="help")
|
||||
self.assertNextMessage(
|
||||
last_message, # no message will be post with command help, use last odoobot message instead
|
||||
sender=self.odoobot,
|
||||
answer="Wow you are a natural! Ping someone to grab its attention with @nameoftheuser. Try to ping me with <b>@OdooBot</b>."
|
||||
)
|
||||
# we dont test the end of the flow since it will depends of the installed apps (livechat)
|
||||
|
||||
kwargs['partner_ids'] = []
|
||||
kwargs['body'] = "I love you"
|
||||
self.assertNextMessage(
|
||||
channel.message_post(**kwargs),
|
||||
sender=self.odoobot,
|
||||
answer="Aaaaaw that's really cute but, you know, bots don't work that way. You're too human for me! Let's keep it professional ❤️"
|
||||
)
|
||||
kwargs['body'] = "Go fuck yourself"
|
||||
self.assertNextMessage(
|
||||
channel.message_post(**kwargs),
|
||||
sender=self.odoobot,
|
||||
answer="That's not a really nice thing to say, you know? I'm a bot but I have feelings, ok?! 💔"
|
||||
)
|
||||
# we should have a default answer
|
||||
with patch('random.choice', lambda x: x[0]):
|
||||
kwargs['body'] = "I'm batman"
|
||||
self.assertNextMessage(
|
||||
channel.message_post(**kwargs),
|
||||
sender=self.odoobot,
|
||||
answer="Mmmmh I'm not sure what you mean.. Can you try again?"
|
||||
)
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_odoobot_no_default_answer(self):
|
||||
kwargs = self.message_post_default_kwargs.copy()
|
||||
kwargs.update({'body': "I'm not talking to @odoobot right now", 'partner_ids': []})
|
||||
self.assertNextMessage(
|
||||
self.test_record_employe.message_post(**kwargs),
|
||||
answer=False
|
||||
)
|
||||
|
||||
def assertNextMessage(self, message, answer=None, sender=None):
|
||||
last_message = self.env['mail.message'].search([('id', '=', message.id + 1)])
|
||||
if last_message:
|
||||
body = last_message.body.replace('<p>', '').replace('</p>', '')
|
||||
else:
|
||||
self.assertFalse(answer, "No last message found when an answer was expect")
|
||||
if answer is not None:
|
||||
if answer and not last_message:
|
||||
self.assertTrue(False, "No last message found")
|
||||
if isinstance(answer, list):
|
||||
self.assertIn(body, answer)
|
||||
elif isinstance(answer, tuple):
|
||||
for elem in answer:
|
||||
self.assertIn(elem, body)
|
||||
elif not answer:
|
||||
self.assertFalse(last_message, "No answer should have been post")
|
||||
return
|
||||
else:
|
||||
self.assertEqual(body, answer)
|
||||
if sender:
|
||||
self.assertEqual(sender, last_message.author_id)
|
||||
return last_message
|
||||
@@ -166,7 +166,7 @@ class TestAdvMailPerformance(TransactionCase):
|
||||
'default_res_model': 'mail.test.activity',
|
||||
})
|
||||
|
||||
with self.assertQueryCount(margin=1, admin=9, emp=13): # test_mail only: 9 - 13
|
||||
with self.assertQueryCount(margin=1, admin=10, emp=15): # test_mail only: 9 - 13
|
||||
activity = MailActivity.create({
|
||||
'summary': 'Test Activity',
|
||||
'res_id': record.id,
|
||||
@@ -182,7 +182,7 @@ class TestAdvMailPerformance(TransactionCase):
|
||||
def test_adv_activity_mixin(self):
|
||||
record = self.env['mail.test.activity'].create({'name': 'Test'})
|
||||
|
||||
with self.assertQueryCount(margin=1, admin=9, emp=13): # test_mail only: 9 - 13
|
||||
with self.assertQueryCount(margin=1, admin=10, emp=15): # test_mail only: 9 - 13
|
||||
record.action_start('Test Start')
|
||||
|
||||
record.write({'name': 'Dupe write'})
|
||||
@@ -199,7 +199,7 @@ class TestAdvMailPerformance(TransactionCase):
|
||||
self.user_test.write({'notification_type': 'email'})
|
||||
record = self.env['mail.test.track'].create({'name': 'Test'})
|
||||
|
||||
with self.assertQueryCount(margin=1, admin=56, emp=71): # com runbot: 56 - 71 // test_mail only: 56 - 71
|
||||
with self.assertQueryCount(margin=1, admin=56, emp=75): # com runbot: 56 - 75 // test_mail only: 56 - 75
|
||||
record.write({
|
||||
'user_id': self.user_test.id,
|
||||
})
|
||||
@@ -209,7 +209,7 @@ class TestAdvMailPerformance(TransactionCase):
|
||||
def test_message_assignation_inbox(self):
|
||||
record = self.env['mail.test.track'].create({'name': 'Test'})
|
||||
|
||||
with self.assertQueryCount(margin=1, admin=36, emp=46): # test_mail only: 36 - 46
|
||||
with self.assertQueryCount(margin=1, admin=44, emp=55): # test_mail only: 43 - 54
|
||||
record.write({
|
||||
'user_id': self.user_test.id,
|
||||
})
|
||||
@@ -253,7 +253,7 @@ class TestAdvMailPerformance(TransactionCase):
|
||||
def test_message_post_one_email_notification(self):
|
||||
record = self.env['mail.test.simple'].create({'name': 'Test'})
|
||||
|
||||
with self.assertQueryCount(margin=1, admin=47, emp=64): # com runbot: 45 - 62 // test_mail only: 47 - 64
|
||||
with self.assertQueryCount(margin=1, admin=47, emp=67): # com runbot: 45 - 65 // test_mail only: 47 - 67
|
||||
record.message_post(
|
||||
body='<p>Test Post Performances with an email ping</p>',
|
||||
partner_ids=self.customer.ids,
|
||||
@@ -389,7 +389,7 @@ class TestHeavyMailPerformance(TransactionCase):
|
||||
self.umbrella.message_subscribe(self.user_portal.partner_id.ids)
|
||||
record = self.umbrella.sudo(self.env.user)
|
||||
|
||||
with self.assertQueryCount(admin=79, emp=99): # com runbot 77 - 97 // test_mail only: 79 - 99
|
||||
with self.assertQueryCount(admin=97, emp=124): # com runbot 90 - 113 // test_mail only: 97 - 120
|
||||
record.message_post(
|
||||
body='<p>Test Post Performances</p>',
|
||||
message_type='comment',
|
||||
@@ -406,7 +406,7 @@ class TestHeavyMailPerformance(TransactionCase):
|
||||
record = self.umbrella.sudo(self.env.user)
|
||||
template_id = self.env.ref('test_mail.mail_test_tpl').id
|
||||
|
||||
with self.assertQueryCount(admin=98, emp=130): # com runbot 96 - 128 // test_mail only: 98 - 130
|
||||
with self.assertQueryCount(admin=116, emp=155): # com runbot 109 - 144 // test_mail only: 116 - 151
|
||||
record.message_post_with_template(template_id, message_type='comment', composition_mode='comment')
|
||||
|
||||
self.assertEqual(record.message_ids[0].body, '<p>Adding stuff on %s</p>' % record.name)
|
||||
@@ -476,7 +476,7 @@ class TestHeavyMailPerformance(TransactionCase):
|
||||
})
|
||||
self.assertEqual(rec.message_partner_ids, self.partners | self.env.user.partner_id)
|
||||
|
||||
with self.assertQueryCount(admin=58, emp=73): # com runbot: 58 - 73 // test_mail only: 58 - 73
|
||||
with self.assertQueryCount(admin=66, emp=88): # com runbot: 65 - 83 // test_mail only: 66 - 84
|
||||
rec.write({'user_id': self.user_portal.id})
|
||||
|
||||
self.assertEqual(rec.message_partner_ids, self.partners | self.env.user.partner_id | self.user_portal.partner_id)
|
||||
@@ -499,7 +499,7 @@ class TestHeavyMailPerformance(TransactionCase):
|
||||
customer_id = self.customer.id
|
||||
user_id = self.user_portal.id
|
||||
|
||||
with self.assertQueryCount(margin=1, admin=155, emp=184): # com runbot: 155 - 184 // test_mail only: 155 - 184
|
||||
with self.assertQueryCount(margin=1, admin=155, emp=190): # com runbot: 155 - 190 // test_mail only: 155 - 190
|
||||
rec = self.env['mail.test.full'].create({
|
||||
'name': 'Test',
|
||||
'umbrella_id': umbrella_id,
|
||||
@@ -528,7 +528,7 @@ class TestHeavyMailPerformance(TransactionCase):
|
||||
})
|
||||
self.assertEqual(rec.message_partner_ids, self.user_portal.partner_id | self.env.user.partner_id)
|
||||
|
||||
with self.assertQueryCount(margin=1, admin=94, emp=109): # com runbot: 94 - 109 // test_mail only: 94 - 109
|
||||
with self.assertQueryCount(margin=1, admin=105, emp=126): # com runbot: 104 - 123 // test_mail only: 105 - 124
|
||||
rec.write({
|
||||
'name': 'Test2',
|
||||
'umbrella_id': self.umbrella.id,
|
||||
@@ -566,7 +566,7 @@ class TestHeavyMailPerformance(TransactionCase):
|
||||
})
|
||||
self.assertEqual(rec.message_partner_ids, self.user_portal.partner_id | self.env.user.partner_id)
|
||||
|
||||
with self.assertQueryCount(margin=1, admin=100, emp=119): # test_mail only: 100 - 119
|
||||
with self.assertQueryCount(margin=1, admin=111, emp=137): # test_mail only: 111 - 134
|
||||
rec.write({
|
||||
'name': 'Test2',
|
||||
'umbrella_id': umbrella_id,
|
||||
@@ -600,7 +600,7 @@ class TestHeavyMailPerformance(TransactionCase):
|
||||
})
|
||||
self.assertEqual(rec.message_partner_ids, self.partners | self.env.user.partner_id | self.user_portal.partner_id)
|
||||
|
||||
with self.assertQueryCount(admin=54, emp=75): # test_mail only: 54 - 75
|
||||
with self.assertQueryCount(admin=54, emp=76): # test_mail only: 54 - 75
|
||||
rec.write({
|
||||
'name': 'Test2',
|
||||
'customer_id': customer_id,
|
||||
|
||||
Reference in New Issue
Block a user