summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXavierDo <xdo@odoo.com>2018-08-02 13:18:53 +0200
committerThibault Delavallée <tde@odoo.com>2018-08-10 13:33:28 +0200
commitcf505c36985221819139ce30d7f03e6e51ec0298 (patch)
tree39c7cd3e7cff094a8b4b231aa87eb3e08b62cbbf
parentc446fb20015d489d9c38e97e10f25cf2e5e3efd7 (diff)
[ADD] mail_bot, im_livechat_mail_bot: add Odoobot, your new best friend
Purpose of this commit is to improve onboarding with a wow effect and a bot to test the Discuss app. Otherwise new users have nobody to talk to. Retention will be improved by both increasing interactions and onboarding of Discuss features. This commit adds a simple bot in discuss. It answers some questions, helps users getting their hand on discuss and eases the onboarding. In this version, the flow is quite simple, and only im_livechat adds some logic in order to show canned response. The logic is contained in new modules: mail_bot and im_livechat_mail_bot in order to keep everything well separated. It also allows users to remove the mailbot if they do not want to keep this functionality. Odoobot will only answer if he is in the onboarding conversation (alone with a user in a channel of type chat) or if a user pings odoobot. Odoobot logic applies to both standard chatter / channel messages and also transient messages (like help commands). Specifications * 2 minutes after first sign in, users will receive a direct chat from Odoobot; * make Odoobot an archived partner; * scenario * Odoobot: "Hello, I'm here to help you discover chat features. Try answering me with an emoji :)"; * User: Send emoji * Odoobot: "Great! :) Did you notice that you can also send attachments, like a picture of your cute dog? Try it!" * Odoobot: "Not a cute dog, but you get it :) To access special features, start your sentence with "/" (I.E. /help)"" * User: /help * Auto message then Odoobot: "Wow you're a natural! As a channel usually contain a lot of users, you can grab the attention with a ping. Try to ping me with @Odoobot!" * User: @Odoobot lorem ipsum * if Livechat installed * add 2 demo canned response so it does not look weird (like "Hello, how may I help you?" and "Have a nice day!") * Odoobot: "Perfect! <br> Try to type ":" to use canned responses." * User tries canned * Odoobot: "Good, you can customize your canned responses in the live chat application. <br><br> + réponse suivante" * Odoobot: "There's 3 different ways in Odoo to interact with your colleagues: via this chat window, [img of chat window] via the Discuss application [img of Discuss app + icon on it] or via the chatter [img of the chatter]. Aaaaand that's it! Enjoy discovering Odoo! :)" * random answers to ping/bad answer * "Mmmmh I'm not sure what you mean.. Can you try again?" * "I'm afraid I don't understand. Sorry!" * when someone pings @OdooBot with no reason: Odoobot: Yaaaay that's me! [party emoji] * fun stuff to add for some answer * User: i love you / love * Odoobot: 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 <3 * User: Fuck * Odoobot: That's not a really nice thing to say, you know? I'm a bot but I have feelings, ok?! </3 This commit is linked to task ID 1838588 and PR #25075.
-rw-r--r--addons/bus/static/src/js/services/bus_service.js2
-rw-r--r--addons/im_livechat_mail_bot/__init__.py4
-rw-r--r--addons/im_livechat_mail_bot/__manifest__.py15
-rw-r--r--addons/im_livechat_mail_bot/models/__init__.py4
-rw-r--r--addons/im_livechat_mail_bot/models/mail_bot.py25
-rw-r--r--addons/mail/models/mail_channel.py6
-rw-r--r--addons/mail/models/mail_thread.py4
-rw-r--r--addons/mail/static/src/img/odoobot.pngbin0 -> 2215 bytes
-rw-r--r--addons/mail/static/src/js/composers/basic_composer.js1
-rw-r--r--addons/mail/static/src/js/models/messages/message.js2
-rw-r--r--addons/mail/static/src/js/models/threads/thread.js1
-rw-r--r--addons/mail_bot/__init__.py4
-rw-r--r--addons/mail_bot/__manifest__.py22
-rw-r--r--addons/mail_bot/data/mailbot_data.xml12
-rw-r--r--addons/mail_bot/models/__init__.py9
-rw-r--r--addons/mail_bot/models/ir_http.py13
-rw-r--r--addons/mail_bot/models/mail_bot.py216
-rw-r--r--addons/mail_bot/models/mail_channel.py41
-rw-r--r--addons/mail_bot/models/mail_thread.py12
-rw-r--r--addons/mail_bot/models/res_partner.py26
-rw-r--r--addons/mail_bot/models/res_users.py9
-rw-r--r--addons/mail_bot/static/img/odoobot_chatter.pngbin0 -> 40576 bytes
-rw-r--r--addons/mail_bot/static/img/odoobot_discuss.pngbin0 -> 30404 bytes
-rw-r--r--addons/mail_bot/static/src/js/mailbot_service.js28
-rw-r--r--addons/mail_bot/views/assets.xml10
-rw-r--r--addons/mail_bot/views/discuss.xml13
-rw-r--r--addons/test_mail/__manifest__.py2
-rw-r--r--addons/test_mail/tests/__init__.py3
-rw-r--r--addons/test_mail/tests/test_odoobot.py124
-rw-r--r--addons/test_mail/tests/test_performance.py24
30 files changed, 615 insertions, 17 deletions
diff --git a/addons/bus/static/src/js/services/bus_service.js b/addons/bus/static/src/js/services/bus_service.js
index af505dfc6e4..72ba8c1531b 100644
--- a/addons/bus/static/src/js/services/bus_service.js
+++ b/addons/bus/static/src/js/services/bus_service.js
@@ -80,7 +80,7 @@ var BusService = CrossTab.extend(ServicesMixin, {
* @param {function} [callback] if given callback will be called when user clicks on notification
*/
_sendNativeNotification: function (title, content, callback) {
- var notification = new Notification(title, {body: content, icon: "/mail/static/src/img/odoo_o.png"});
+ var notification = new Notification(title, {body: content, icon: "/mail/static/src/img/odoobot.png"});
notification.onclick = function () {
window.focus();
if (this.cancel) {
diff --git a/addons/im_livechat_mail_bot/__init__.py b/addons/im_livechat_mail_bot/__init__.py
new file mode 100644
index 00000000000..dc5e6b693d1
--- /dev/null
+++ b/addons/im_livechat_mail_bot/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import models
diff --git a/addons/im_livechat_mail_bot/__manifest__.py b/addons/im_livechat_mail_bot/__manifest__.py
new file mode 100644
index 00000000000..7c1906b78ea
--- /dev/null
+++ b/addons/im_livechat_mail_bot/__manifest__.py
@@ -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,
+}
diff --git a/addons/im_livechat_mail_bot/models/__init__.py b/addons/im_livechat_mail_bot/models/__init__.py
new file mode 100644
index 00000000000..ce6a8455179
--- /dev/null
+++ b/addons/im_livechat_mail_bot/models/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import mail_bot
diff --git a/addons/im_livechat_mail_bot/models/mail_bot.py b/addons/im_livechat_mail_bot/models/mail_bot.py
new file mode 100644
index 00000000000..57eacc5db5d
--- /dev/null
+++ b/addons/im_livechat_mail_bot/models/mail_bot.py
@@ -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)
diff --git a/addons/mail/models/mail_channel.py b/addons/mail/models/mail_channel.py
index 4c46c8b10cc..6f6e53da75d 100644
--- a/addons/mail/models/mail_channel.py
+++ b/addons/mail/models/mail_channel.py
@@ -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>
diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py
index 1288f780d49..f086a3220a7 100644
--- a/addons/mail/models/mail_thread.py
+++ b/addons/mail/models/mail_thread.py
@@ -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
diff --git a/addons/mail/static/src/img/odoobot.png b/addons/mail/static/src/img/odoobot.png
new file mode 100644
index 00000000000..b1921736d32
--- /dev/null
+++ b/addons/mail/static/src/img/odoobot.png
Binary files differ
diff --git a/addons/mail/static/src/js/composers/basic_composer.js b/addons/mail/static/src/js/composers/basic_composer.js
index 6d402829563..bba52c3f8b2 100644
--- a/addons/mail/static/src/js/composers/basic_composer.js
+++ b/addons/mail/static/src/js/composers/basic_composer.js
@@ -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,
});
},
diff --git a/addons/mail/static/src/js/models/messages/message.js b/addons/mail/static/src/js/models/messages/message.js
index 9bf7a79d1b9..0c6480f3264 100644
--- a/addons/mail/static/src/js/models/messages/message.js
+++ b/addons/mail/static/src/js/models/messages/message.js
@@ -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') {
diff --git a/addons/mail/static/src/js/models/threads/thread.js b/addons/mail/static/src/js/models/threads/thread.js
index f1831cd3aa8..5fcd0285a82 100644
--- a/addons/mail/static/src/js/models/threads/thread.js
+++ b/addons/mail/static/src/js/models/threads/thread.js
@@ -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;
diff --git a/addons/mail_bot/__init__.py b/addons/mail_bot/__init__.py
new file mode 100644
index 00000000000..dc5e6b693d1
--- /dev/null
+++ b/addons/mail_bot/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import models
diff --git a/addons/mail_bot/__manifest__.py b/addons/mail_bot/__manifest__.py
new file mode 100644
index 00000000000..01885576d16
--- /dev/null
+++ b/addons/mail_bot/__manifest__.py
@@ -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',
+ ],
+}
diff --git a/addons/mail_bot/data/mailbot_data.xml b/addons/mail_bot/data/mailbot_data.xml
new file mode 100644
index 00000000000..96fe760ca1f
--- /dev/null
+++ b/addons/mail_bot/data/mailbot_data.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>
diff --git a/addons/mail_bot/models/__init__.py b/addons/mail_bot/models/__init__.py
new file mode 100644
index 00000000000..82aa165ecaf
--- /dev/null
+++ b/addons/mail_bot/models/__init__.py
@@ -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
diff --git a/addons/mail_bot/models/ir_http.py b/addons/mail_bot/models/ir_http.py
new file mode 100644
index 00000000000..ae01cb3e76f
--- /dev/null
+++ b/addons/mail_bot/models/ir_http.py
@@ -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
diff --git a/addons/mail_bot/models/mail_bot.py b/addons/mail_bot/models/mail_bot.py
new file mode 100644
index 00000000000..477e6aab7ee
--- /dev/null
+++ b/addons/mail_bot/models/mail_bot.py
@@ -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
diff --git a/addons/mail_bot/models/mail_channel.py b/addons/mail_bot/models/mail_channel.py
new file mode 100644
index 00000000000..23978eff1e6
--- /dev/null
+++ b/addons/mail_bot/models/mail_channel.py
@@ -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
diff --git a/addons/mail_bot/models/mail_thread.py b/addons/mail_bot/models/mail_thread.py
new file mode 100644
index 00000000000..81e9fa44711
--- /dev/null
+++ b/addons/mail_bot/models/mail_thread.py
@@ -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)
diff --git a/addons/mail_bot/models/res_partner.py b/addons/mail_bot/models/res_partner.py
new file mode 100644
index 00000000000..941972a7184
--- /dev/null
+++ b/addons/mail_bot/models/res_partner.py
@@ -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]
+
diff --git a/addons/mail_bot/models/res_users.py b/addons/mail_bot/models/res_users.py
new file mode 100644
index 00000000000..8c880b11a6e
--- /dev/null
+++ b/addons/mail_bot/models/res_users.py
@@ -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)
diff --git a/addons/mail_bot/static/img/odoobot_chatter.png b/addons/mail_bot/static/img/odoobot_chatter.png
new file mode 100644
index 00000000000..e9147652083
--- /dev/null
+++ b/addons/mail_bot/static/img/odoobot_chatter.png
Binary files differ
diff --git a/addons/mail_bot/static/img/odoobot_discuss.png b/addons/mail_bot/static/img/odoobot_discuss.png
new file mode 100644
index 00000000000..18949ce9f9e
--- /dev/null
+++ b/addons/mail_bot/static/img/odoobot_discuss.png
Binary files differ
diff --git a/addons/mail_bot/static/src/js/mailbot_service.js b/addons/mail_bot/static/src/js/mailbot_service.js
new file mode 100644
index 00000000000..300e95d06db
--- /dev/null
+++ b/addons/mail_bot/static/src/js/mailbot_service.js
@@ -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;
+});
diff --git a/addons/mail_bot/views/assets.xml b/addons/mail_bot/views/assets.xml
new file mode 100644
index 00000000000..134b54e931e
--- /dev/null
+++ b/addons/mail_bot/views/assets.xml
@@ -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>
diff --git a/addons/mail_bot/views/discuss.xml b/addons/mail_bot/views/discuss.xml
new file mode 100644
index 00000000000..e7f0650c867
--- /dev/null
+++ b/addons/mail_bot/views/discuss.xml
@@ -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>
diff --git a/addons/test_mail/__manifest__.py b/addons/test_mail/__manifest__.py
index 40d43883d4b..4f76d51cac9 100644
--- a/addons/test_mail/__manifest__.py
+++ b/addons/test_mail/__manifest__.py
@@ -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',
diff --git a/addons/test_mail/tests/__init__.py b/addons/test_mail/tests/__init__.py
index 2d63b95f205..017a983b515 100644
--- a/addons/test_mail/tests/__init__.py
+++ b/addons/test_mail/tests/__init__.py
@@ -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 \ No newline at end of file
+from . import test_res_users
+from . import test_odoobot
diff --git a/addons/test_mail/tests/test_odoobot.py b/addons/test_mail/tests/test_odoobot.py
new file mode 100644
index 00000000000..b171020244a
--- /dev/null
+++ b/addons/test_mail/tests/test_odoobot.py
@@ -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&amp;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
diff --git a/addons/test_mail/tests/test_performance.py b/addons/test_mail/tests/test_performance.py
index f79c235e377..a3816c1de29 100644
--- a/addons/test_mail/tests/test_performance.py
+++ b/addons/test_mail/tests/test_performance.py
@@ -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,