diff options
| author | dmonzonis <mod@odoo.com> | 2020-05-07 07:09:43 +0000 |
|---|---|---|
| committer | Thibault DelavallΓ©e <tde@odoo.com> | 2020-05-27 12:47:16 +0000 |
| commit | 936d206559035627b07a25e7a0f807ad43d39f54 (patch) | |
| tree | 23b7d64a07f15a26b630383e70bc472fef1bbb70 /addons | |
| parent | 5b20117495b908bde3877c02db3ada7249eb7993 (diff) | |
[IMP] mail_bot: improve OdooBot workflow
This commit attempts to improve the workflow of the OdooBot onboarding
tutorial, as well as the answer given by the bot while idle.
These changes come from watching how users interact with OdooBot. To
improve the user's experience, the following changes have been made:
1. Move the attachment to the end of the tour, as it is where most
people just close the bot.
2. Remove the quotation marks around commands that OdooBot tells the
user to type, like "/" or ":)", as some users try to actually type
the quotation marks too. Instead, use a light grey background around
the command that the user should type.
3. Some users ask OdooBot questions, but the responses they get are
useless. To solve this, use a message linking to the documentation or the
videos when:
- The user gives wrong answers twice for the same stage of the tour
- The user talks again when the tour is completed
- There's a question mark in the answer
To achieve this a "failed" state field is added on user model, allowing to
distinguish state in the bot workflow from state of answers (failed / not
failed).
Task ID: 2233014
PR #49661
Diffstat (limited to 'addons')
| -rw-r--r-- | addons/im_livechat_mail_bot/models/mail_bot.py | 13 | ||||
| -rw-r--r-- | addons/mail/models/mail_channel.py | 6 | ||||
| -rw-r--r-- | addons/mail_bot/__manifest__.py | 2 | ||||
| -rw-r--r-- | addons/mail_bot/models/mail_bot.py | 51 | ||||
| -rw-r--r-- | addons/mail_bot/models/mail_channel.py | 2 | ||||
| -rw-r--r-- | addons/mail_bot/models/res_users.py | 2 | ||||
| -rw-r--r-- | addons/mail_bot/static/src/scss/odoobot_style.scss | 8 | ||||
| -rw-r--r-- | addons/mail_bot/views/assets.xml | 1 | ||||
| -rw-r--r-- | addons/test_mail_full/tests/test_odoobot.py | 37 |
9 files changed, 82 insertions, 40 deletions
diff --git a/addons/im_livechat_mail_bot/models/mail_bot.py b/addons/im_livechat_mail_bot/models/mail_bot.py index 593e3d69f63..a17e1cac292 100644 --- a/addons/im_livechat_mail_bot/models/mail_bot.py +++ b/addons/im_livechat_mail_bot/models/mail_bot.py @@ -10,13 +10,16 @@ class MailBot(models.AbstractModel): def _get_answer(self, record, body, values, command): odoobot_state = self.env.user.odoobot_state if self._is_bot_in_private_channel(record): - if odoobot_state == "onboarding_ping" and self._is_bot_pinged(values): + if odoobot_state == "onboarding_attachement" and values.get("attachment_ids"): + self.env.user.odoobot_failed = False self.env.user.odoobot_state = "onboarding_canned" - return _("That's me! π<br/>Try to type \":\" to use canned responses.") + return _("That's me! π<br/>Try typing <span class=\"o_odoobot_command\">:</span> to use canned responses.") elif odoobot_state == "onboarding_canned" and values.get("canned_response_ids"): + self.env.user.odoobot_failed = False self.env.user.odoobot_state = "idle" return _("Good, you can customize canned responses in the live chat application.<br/><br/><b>It's the end of this overview</b>, enjoy discovering Odoo!") - #repeat question if needed - elif odoobot_state == 'onboarding_canned': - return _("Not sure wat you are doing. Please press : and wait for the propositions. Select one of them and press enter.") + # repeat question if needed + elif odoobot_state == 'onboarding_canned' and not self._is_help_requested(body): + self.env.user.odoobot_failed = True + return _("Not sure what you are doing. Please, type <span class=\"o_odoobot_command\">:</span> and wait for the propositions. Select one of them and press enter.") 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 b6e11e4fdc3..62489a88e5b 100644 --- a/addons/mail/models/mail_channel.py +++ b/addons/mail/models/mail_channel.py @@ -973,7 +973,7 @@ class Channel(models.Model): }) def _define_command_help(self): - return {'help': _("Show an helper message")} + return {'help': _("Show a helper message")} def _execute_command_help(self, **kwargs): partner = self.env.user.partner_id @@ -987,9 +987,9 @@ class Channel(models.Model): 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> - Type <b>#channel</b>.to mention a channel.<br> + Type <b>#channel</b> to mention a channel.<br> Type <b>/command</b> to execute a command.<br> - Type <b>:shortcut</b> to insert canned responses in your message.<br>""") + Type <b>:shortcut</b> to insert a canned response in your message.<br>""") self._send_transient_message(partner, msg) diff --git a/addons/mail_bot/__manifest__.py b/addons/mail_bot/__manifest__.py index c1010924ea6..348754d3f62 100644 --- a/addons/mail_bot/__manifest__.py +++ b/addons/mail_bot/__manifest__.py @@ -3,7 +3,7 @@ { 'name': 'OdooBot', - 'version': '1.0', + 'version': '1.1', 'category': 'Productivity/Discuss', 'summary': 'Add OdooBot in discussions', 'description': "", diff --git a/addons/mail_bot/models/mail_bot.py b/addons/mail_bot/models/mail_bot.py index 7a4aebee197..6c17d60bbd4 100644 --- a/addons/mail_bot/models/mail_bot.py +++ b/addons/mail_bot/models/mail_bot.py @@ -25,7 +25,7 @@ class MailBot(models.AbstractModel): 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(".?!") + 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') @@ -38,42 +38,53 @@ class MailBot(models.AbstractModel): if self._is_bot_in_private_channel(record): # main flow if odoobot_state == 'onboarding_emoji' and self._body_contains_emoji(body): - self.env.user.odoobot_state = "onboarding_attachement" - return _("Great! π<br/>Now, try to <b>send an attachment</b>, like a picture of your cute dog...") - elif odoobot_state == 'onboarding_attachement' and values.get("attachment_ids"): self.env.user.odoobot_state = "onboarding_command" - return _("Not a cute dog, but you get it π<br/>To access special features, <b>start your sentence with '/'</b>. Try to get help.") + self.env.user.odoobot_failed = False + return _("Great! π<br/>To access special commands, <b>start your sentence with</b> <span class=\"o_odoobot_command\">/</span>. Try getting help.") elif odoobot_state == 'onboarding_command' and command == 'help': self.env.user.odoobot_state = "onboarding_ping" - return _("Wow you are a natural!<br/>Ping someone to grab its attention with @nameoftheuser. <b>Try to ping me using @OdooBot</b> in a sentence.") + self.env.user.odoobot_failed = False + return _("Wow you are a natural!<br/>Ping someone to grab its attention with @username. <b>Try to ping me using</b> <span class=\"o_odoobot_command\">@OdooBot</span> in a sentence.") elif odoobot_state == 'onboarding_ping' and self._is_bot_pinged(values): + self.env.user.odoobot_state = "onboarding_attachement" + self.env.user.odoobot_failed = False + return _("Yep, I am here! π <br/>Now, try <b>sending an attachment</b>, like a picture of your cute dog...") + elif odoobot_state == 'onboarding_attachement' and values.get("attachment_ids"): self.env.user.odoobot_state = "idle" - return _("Yep, I am here! π <br/>You finished the tour, you can <b>close this chat window</b>. Enjoy discovering Odoo.") - elif odoobot_state == "idle" and (_('start the tour') in body.lower()): + self.env.user.odoobot_failed = False + return _("I am a simple bot, but if that's a dog, he is the cutest π <br/>Congratulations, you finished this tour. You can now <b>close this chat window</b>. Enjoy discovering Odoo.") + elif odoobot_state in ("idle", "not_initialized") and (_('start the tour') in body.lower()): self.env.user.odoobot_state = "onboarding_emoji" return _("To start, try to send me an emoji :)") # easter eggs elif odoobot_state == "idle" and 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 odoobot_state == "idle" and (('help' in body) or _('help') in body): - return _("I'm just a bot... :( You can 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 nice! I'm a bot but I have feelings... π") + # help message + elif self._is_help_requested(body) or odoobot_state == 'idle': + return _("Unfortunately, I'm just a bot π I don't understand! If you need help discovering our product, please check " + "<a href=\"https://www.odoo.com/page/docs\" target=\"_blank\">our documentation</a> or " + "<a href=\"https://www.odoo.com/slides\" target=\"_blank\">our videos</a>.") else: - #repeat question + # repeat question if odoobot_state == 'onboarding_emoji': - return _("Not exactly. To continue the tour, send an emoji, <b>type \":)\"</b> and press enter.") + self.env.user.odoobot_failed = True + return _("Not exactly. To continue the tour, send an emoji: <b>type</b> <span class=\"o_odoobot_command\">:)</span> and press enter.") elif odoobot_state == 'onboarding_attachement': - return _("To <b>send an attachment</b>, click the π icon on the right, and select a file.") + self.env.user.odoobot_failed = True + return _("To <b>send an attachment</b>, click on the <i class=\"fa fa-paperclip\" aria-hidden=\"true\"></i> icon and select a file.") elif odoobot_state == 'onboarding_command': - return _("Not sure wat you are doing. Please press / and wait for the propositions. Select \"help\" and press enter") + self.env.user.odoobot_failed = True + return _("Not sure what you are doing. Please, type <span class=\"o_odoobot_command\">/</span> and wait for the propositions. Select <span class=\"o_odoobot_command\">help</span> and press enter") elif odoobot_state == 'onboarding_ping': - return _("Sorry, I am not listening. To get someone's attention, <b>ping him</b>. Write \"@odoobot\" and select me.") + self.env.user.odoobot_failed = True + return _("Sorry, I am not listening. To get someone's attention, <b>ping him</b>. Write <span class=\"o_odoobot_command\">@OdooBot</span> and select me.") return random.choice([ - _("I'm not smart enough to answer your question.<br/>To follow my guide, ask") + ": <b>"+_('start the tour') + "</b>", + _("I'm not smart enough to answer your question.<br/>To follow my guide, ask: <span class=\"o_odoobot_command\">start the tour</span>."), _("Hmmm..."), _("I'm afraid I don't understand. Sorry!"), - _("Sorry I'm sleepy. Or not! Maybe I'm just trying to hide my unawareness of human language...<br/>I can show you features if you write")+ ": '<b>"+_('start the tour')+"</b>'.", + _("Sorry I'm sleepy. Or not! Maybe I'm just trying to hide my unawareness of human language...<br/>I can show you features if you write: <span class=\"o_odoobot_command\">start the tour</span>.") ]) return False @@ -219,3 +230,9 @@ class MailBot(models.AbstractModel): 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 + + def _is_help_requested(self, body): + """Returns whether a message linking to the documentation and videos + should be sent back to the user. + """ + return any(token in body for token in ['help', _('help'), '?']) or self.env.user.odoobot_failed diff --git a/addons/mail_bot/models/mail_channel.py b/addons/mail_bot/models/mail_channel.py index 8b792780971..909696e5618 100644 --- a/addons/mail_bot/models/mail_channel.py +++ b/addons/mail_bot/models/mail_channel.py @@ -23,7 +23,7 @@ class Channel(models.Model): 'email_send': False, 'name': 'OdooBot' }) - message = _("Hello,<br/>Odoo's chat helps employees collaborate efficiently. I'm here to help you discover its features.<br/><b>Try to send me an emoji :)</b>") + message = _("Hello,<br/>Odoo's chat helps employees collaborate efficiently. I'm here to help you discover its features.<br/><b>Try to send me an emoji</b> <span class=\"o_odoobot_command\">:)</span>") channel.sudo().message_post(body=message, author_id=odoobot_id, message_type="comment", subtype_xmlid="mail.mt_comment") self.env.user.odoobot_state = 'onboarding_emoji' return channel diff --git a/addons/mail_bot/models/res_users.py b/addons/mail_bot/models/res_users.py index f1975df23e4..8468bc16d0f 100644 --- a/addons/mail_bot/models/res_users.py +++ b/addons/mail_bot/models/res_users.py @@ -5,6 +5,7 @@ from odoo import models, fields class Users(models.Model): _inherit = 'res.users' + odoobot_state = fields.Selection( [ ('not_initialized', 'Not initialized'), @@ -15,6 +16,7 @@ class Users(models.Model): ('idle', 'Idle'), ('disabled', 'Disabled'), ], string="OdooBot Status", readonly=True, required=True, default="not_initialized") # keep track of the state: correspond to the code of the last message sent + odoobot_failed = fields.Boolean(readonly=True) def __init__(self, pool, cr): """ Override of __init__ to add access rights. diff --git a/addons/mail_bot/static/src/scss/odoobot_style.scss b/addons/mail_bot/static/src/scss/odoobot_style.scss new file mode 100644 index 00000000000..aa5e187bb00 --- /dev/null +++ b/addons/mail_bot/static/src/scss/odoobot_style.scss @@ -0,0 +1,8 @@ +.o_odoobot_command { + background-color: lightgray; + border-radius: 4px; + padding: 2px; + padding-left: 10px; + padding-right: 10px; + font-weight: bold; +} diff --git a/addons/mail_bot/views/assets.xml b/addons/mail_bot/views/assets.xml index 243fa5228a2..e3d5ee81feb 100644 --- a/addons/mail_bot/views/assets.xml +++ b/addons/mail_bot/views/assets.xml @@ -3,6 +3,7 @@ <data> <template id="assets_backend" name="mailbot assets" inherit_id="web.assets_backend"> <xpath expr="." position="inside"> + <link rel="stylesheet" type="text/scss" href="/mail_bot/static/src/scss/odoobot_style.scss"/> <script type="text/javascript" src="/mail_bot/static/src/js/mailbot_service.js"></script> <script type="text/javascript" src="/mail_bot/static/src/js/notification_alert.js"></script> <script type="text/javascript" src="/mail_bot/static/src/js/systray_messaging_menu.js"></script> diff --git a/addons/test_mail_full/tests/test_odoobot.py b/addons/test_mail_full/tests/test_odoobot.py index 2e4dc55490a..ddd6015b7e1 100644 --- a/addons/test_mail_full/tests/test_odoobot.py +++ b/addons/test_mail_full/tests/test_odoobot.py @@ -58,6 +58,19 @@ class TestOdoobot(TestMailCommon, TestRecipients): channel = self.env['mail.channel'].with_user(self.user_employee).init_odoobot() kwargs['body'] = 'tagada π' + last_message = self.assertNextMessage( + channel.message_post(**kwargs), + sender=self.odoobot, + answer=("help",) + ) + 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=("@OdooBot",) + ) + kwargs['body'] = '' + kwargs['partner_ids'] = [self.env['ir.model.data'].xmlid_to_res_id("base.partner_root")] self.assertNextMessage( channel.message_post(**kwargs), sender=self.odoobot, @@ -70,20 +83,12 @@ class TestOdoobot(TestMailCommon, TestRecipients): 'res_model': 'mail.compose.message', }) kwargs['attachment_ids'] = [attachment.id] - last_message = self.assertNextMessage( - channel.message_post(**kwargs), - sender=self.odoobot, - answer=("help",) - ) - kwargs['attachment_ids'] = [] + # For the end of the flow, we only test that the state changed, but not to which + # one since it depends on the intalled apps, which can add more steps (like livechat) + channel.message_post(**kwargs) + self.assertNotEqual(self.user_employee.odoobot_state, 'onboarding_attachement') - 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=("@OdooBot",) - ) - # we dont test the end of the flow since it will depends of the installed apps (livechat) + # Test miscellaneous messages self.user_employee.odoobot_state = "idle" kwargs['partner_ids'] = [] kwargs['body'] = "I love you" @@ -98,6 +103,12 @@ class TestOdoobot(TestMailCommon, TestRecipients): sender=self.odoobot, answer=("I have feelings",) ) + kwargs['body'] = "help me" + self.assertNextMessage( + channel.message_post(**kwargs), + sender=self.odoobot, + answer=("If you need help",) + ) @mute_logger('odoo.addons.mail.models.mail_mail') def test_odoobot_no_default_answer(self): |
