From 75b966269bb41478bf5af8dbba0b66c19cb509ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20D=C3=ADaz?= Date: Thu, 11 Jul 2019 17:36:34 +0200 Subject: [PATCH] [IMP] mail_tracking: Failed Messages (Discuss & View) --- mail_tracking/README.rst | 36 +- mail_tracking/__manifest__.py | 7 + mail_tracking/controllers/main.py | 25 +- mail_tracking/demo/demo.xml | 54 +++ mail_tracking/models/__init__.py | 2 +- mail_tracking/models/mail_composer.py | 30 ++ mail_tracking/models/mail_message.py | 100 ++++- mail_tracking/models/mail_thread.py | 72 ++-- mail_tracking/models/mail_tracking_email.py | 13 +- mail_tracking/readme/ROADMAP.rst | 5 + mail_tracking/readme/USAGE.rst | 27 +- mail_tracking/static/description/index.html | 56 ++- .../static/img/failed_message_discuss.png | Bin 0 -> 47458 bytes .../static/img/failed_message_widget.png | Bin 0 -> 60533 bytes .../static/src/css/failed_message.less | 108 ++++++ .../static/src/css/mail_tracking.less | 15 +- mail_tracking/static/src/js/failed_message.js | 365 ++++++++++++++++++ mail_tracking/static/src/js/mail_tracking.js | 91 +---- .../static/src/xml/client_action.xml | 31 ++ .../static/src/xml/failed_message.xml | 63 +++ .../static/src/xml/mail_tracking.xml | 4 - mail_tracking/tests/test_mail_tracking.py | 41 ++ mail_tracking/views/assets.xml | 4 + mail_tracking/views/mail_message_view.xml | 14 + .../wizard/mail_compose_message_view.xml | 19 + 25 files changed, 998 insertions(+), 184 deletions(-) create mode 100644 mail_tracking/demo/demo.xml create mode 100644 mail_tracking/models/mail_composer.py create mode 100644 mail_tracking/readme/ROADMAP.rst create mode 100644 mail_tracking/static/img/failed_message_discuss.png create mode 100644 mail_tracking/static/img/failed_message_widget.png create mode 100644 mail_tracking/static/src/css/failed_message.less create mode 100644 mail_tracking/static/src/js/failed_message.js create mode 100644 mail_tracking/static/src/xml/client_action.xml create mode 100644 mail_tracking/static/src/xml/failed_message.xml create mode 100644 mail_tracking/views/mail_message_view.xml create mode 100644 mail_tracking/wizard/mail_compose_message_view.xml diff --git a/mail_tracking/README.rst b/mail_tracking/README.rst index 24a693d7f..3f7ee5365 100644 --- a/mail_tracking/README.rst +++ b/mail_tracking/README.rst @@ -58,25 +58,25 @@ status icon will appear just right to name of notified partner. These are all available status icons: -.. |sent| image:: static/src/img/sent.png +.. |sent| image:: https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/src/img/sent.png :width: 10px -.. |delivered| image:: static/src/img/delivered.png +.. |delivered| image:: https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/src/img/delivered.png :width: 15px -.. |opened| image:: static/src/img/opened.png +.. |opened| image:: https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/src/img/opened.png :width: 15px -.. |error| image:: static/src/img/error.png +.. |error| image:: https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/src/img/error.png :width: 10px -.. |waiting| image:: static/src/img/waiting.png +.. |waiting| image:: https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/src/img/waiting.png :width: 10px -.. |unknown| image:: static/src/img/unknown.png +.. |unknown| image:: https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/src/img/unknown.png :width: 10px -.. |cc| image:: static/src/img/cc.png +.. |cc| image:: https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/src/img/cc.png :width: 10px |unknown| **Unknown**: No email tracking info available. Maybe this notified partner has 'Receive Inbox Notifications by Email' == 'Never' @@ -93,6 +93,28 @@ These are all available status icons: |cc| **Cc**: It's a Carbon-Copy recipient. Can't know the status so is 'Unknown' + +When the message generates and 'error' status, it will apear on discuss 'Failed' +channel. Any view that uses 'mail_thread' widget can show the failed messages +too. + +* Discuss + + .. image:: https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/img/failed_message_discuss.png + +* Chatter + + .. image:: https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/img/failed_message_widget.png + +Known issues / Roadmap +====================== + +* Handle message updates on discuss 'channel_failed' instead of showing the + 'outdated' message. +* Adapt chat_manager changes in v12 +* Adapt discuss changes in v12 +* Add pivot for tracking events and mail trackings + Bug Tracker =========== diff --git a/mail_tracking/__manifest__.py b/mail_tracking/__manifest__.py index 7a49a8a5d..c6c3d54b4 100644 --- a/mail_tracking/__manifest__.py +++ b/mail_tracking/__manifest__.py @@ -24,10 +24,17 @@ "views/assets.xml", "views/mail_tracking_email_view.xml", "views/mail_tracking_event_view.xml", + "views/mail_message_view.xml", "views/res_partner_view.xml", + "wizard/mail_compose_message_view.xml", ], "qweb": [ "static/src/xml/mail_tracking.xml", + "static/src/xml/failed_message.xml", + "static/src/xml/client_action.xml", + ], + 'demo': [ + 'demo/demo.xml', ], "pre_init_hook": "pre_init_hook", } diff --git a/mail_tracking/controllers/main.py b/mail_tracking/controllers/main.py index a12d25ccf..2f53ecbb7 100644 --- a/mail_tracking/controllers/main.py +++ b/mail_tracking/controllers/main.py @@ -3,7 +3,9 @@ import werkzeug from psycopg2 import OperationalError -from odoo import api, http, registry, SUPERUSER_ID +from odoo import api, http, registry, SUPERUSER_ID, _ +from odoo.addons.mail.controllers.main import MailController +from odoo.http import request import logging import base64 _logger = logging.getLogger(__name__) @@ -37,7 +39,7 @@ def _env_get(db, callback, tracking_id, event_type, **kw): return res -class MailTrackingController(http.Controller): +class MailTrackingController(MailController): def _request_metadata(self): request = http.request.httprequest @@ -85,3 +87,22 @@ class MailTrackingController(http.Controller): response.mimetype = 'image/gif' response.data = base64.b64decode(BLANK) return response + + @http.route() + def mail_client_action(self): + values = super().mail_client_action() + values['channel_slots']['channel_channel'].append({ + 'id': 'channel_failed', + 'name': _("Failed"), + 'uuid': None, + 'state': 'open', + 'is_minimized': False, + 'channel_type': 'static', + 'public': False, + 'mass_mailing': None, + 'group_based_subscription': None, + }) + values.update({ + 'failed_counter': request.env['mail.message'].get_failed_count(), + }) + return values diff --git a/mail_tracking/demo/demo.xml b/mail_tracking/demo/demo.xml new file mode 100644 index 000000000..e3bfd311f --- /dev/null +++ b/mail_tracking/demo/demo.xml @@ -0,0 +1,54 @@ + + + + + + + res.partner + + comment + + 1 + This is a failed message

]]>
+ res1@yourcompany.example.com + + + Failed Message +
+ + + Failed Message + + + res1@yourcompany.example.com + demo@yourcompany.example.com + error + + + + + + res.partner + + comment + + 1 + This is another failed message

]]>
+ res10@yourcompany.example.com + + + Failed Message +
+ + + Failed Message + + + res10@yourcompany.example.com + demo@yourcompany.example.com + error + + + +
+
diff --git a/mail_tracking/models/__init__.py b/mail_tracking/models/__init__.py index f3e396192..39a44435c 100644 --- a/mail_tracking/models/__init__.py +++ b/mail_tracking/models/__init__.py @@ -1,10 +1,10 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from . import ir_mail_server -from . import mail_thread from . import mail_mail from . import mail_message from . import mail_tracking_email from . import mail_tracking_event +from . import mail_composer from . import res_partner from . import mail_thread diff --git a/mail_tracking/models/mail_composer.py b/mail_tracking/models/mail_composer.py new file mode 100644 index 000000000..f4f847e38 --- /dev/null +++ b/mail_tracking/models/mail_composer.py @@ -0,0 +1,30 @@ +# Copyright 2019 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import models, fields, api + + +class MailComposer(models.TransientModel): + _inherit = 'mail.compose.message' + + hide_followers = fields.Boolean(string="Hide follower message", + default=False) + + @api.multi + def send_mail(self, auto_commit=False): + """ This method marks as reviewed the message when using the 'Retry' + option in the mail_failed_message widget""" + message = self.env['mail.message'].browse( + self._context.get('message_id')) + if message.exists(): + message.mail_tracking_needs_action = False + return super().send_mail(auto_commit=auto_commit) + + @api.model + def get_record_data(self, values): + values = super(MailComposer, self).get_record_data(values) + if self._context.get('default_hide_followers', False): + values['partner_ids'] = [ + (6, 0, self._context.get('default_partner_ids', list())) + ] + return values diff --git a/mail_tracking/models/mail_message.py b/mail_tracking/models/mail_message.py index ac94ccd7f..800b1f5af 100644 --- a/mail_tracking/models/mail_message.py +++ b/mail_tracking/models/mail_message.py @@ -15,14 +15,18 @@ class MailMessage(models.Model): mail_tracking_ids = fields.One2many( comodel_name='mail.tracking.email', inverse_name='mail_message_id', - string="Mail Trackings Associated with this message", + string="Mail Trackings", ) - track_needs_action = fields.Boolean( - string="The message tracking will be considered" - "for filter tracking issues", - default=True, + mail_tracking_needs_action = fields.Boolean( + help="The message tracking will be considered" + " to filter tracking issues", + default=False, ) + @api.model + def get_failed_states(self): + return {'error', 'rejected', 'spam', 'bounced', 'soft-bounced'} + def _tracking_status_map_get(self): return { 'False': 'waiting', @@ -120,6 +124,17 @@ class MailMessage(models.Model): res[message.id] = partner_trackings return res + @api.multi + def _get_failed_message(self): + res = {} + for message in self: + res.update({ + message.id: message.mail_tracking_needs_action + and bool(message.mail_tracking_ids.filtered( + lambda x: x.state in self.get_failed_states())) + }) + return res + @api.model def _message_read_dict_postprocess(self, messages, message_tree): res = super(MailMessage, self)._message_read_dict_postprocess( @@ -127,23 +142,76 @@ class MailMessage(models.Model): mail_message_ids = {m.get('id') for m in messages if m.get('id')} mail_messages = self.browse(mail_message_ids) partner_trackings = mail_messages.tracking_status() + failed_message = mail_messages._get_failed_message() for message_dict in messages: mail_message_id = message_dict.get('id', False) if mail_message_id: - message_dict['partner_trackings'] = \ - partner_trackings[mail_message_id] + message_dict.update({ + 'partner_trackings': partner_trackings[mail_message_id], + 'failed_message': failed_message[mail_message_id], + }) return res + @api.model + def _prepare_dict_failed_message(self, message): + failed_trackings = message.mail_tracking_ids.filtered( + lambda x: x.state in self.get_failed_states()) + failed_partners = failed_trackings.mapped('partner_id') + failed_recipients = failed_partners.name_get() + return { + 'id': message.id, + 'date': message.date, + 'author_id': message.author_id.name_get()[0], + 'body': message.body, + 'failed_recipients': failed_recipients, + } + + @api.multi + def get_failed_messages(self): + return [self._prepare_dict_failed_message(msg) for msg in self] + @api.multi def toggle_tracking_status(self): """Toggle message tracking action needed to ignore them in the tracking issues filter""" - # a user should always be able to star a message he can read - self.check_access_rule('read') - self.track_needs_action = not self.track_needs_action - notification = {'type': 'toggle_track', 'message_ids': [self.id], - 'tracked': self.track_needs_action, - 'res_id': self.res_id, 'model': self.model} - self.env['bus.bus'].sendone((self._cr.dbname, 'res.partner', - self.env.user.partner_id.id), - notification) + self.mail_tracking_needs_action = not self.mail_tracking_needs_action + return self.mail_tracking_needs_action + + def _get_failed_message_domain(self): + return [ + ('mail_tracking_ids.state', 'in', list(self.get_failed_states())), + ('mail_tracking_needs_action', '=', True) + ] + + @api.model + def get_failed_count(self): + """ Gets the number of failed messages """ + return self.search_count(self._get_failed_message_domain()) + + @api.model + def message_fetch(self, domain, limit=20): + # HACK: Because can't modify the domain in discuss JS to search the + # failed messages we force the change here to clean it of + # not valid criterias + if self.env.context.get('filter_failed_message'): + domain = self._get_failed_message_domain() + return super().message_fetch(domain, limit=limit) + + @api.multi + def _notify(self, force_send=False, send_after_commit=True, + user_signature=True): + self_sudo = self.sudo() + hide_followers = self_sudo._context.get('default_hide_followers', + False) + if hide_followers: + # HACK: Because Odoo uses subtype to found message followers + # whe modify it to False to avoid include them. + orig_subtype_id = self_sudo.subtype_id + self_sudo.subtype_id = False + res = super()._notify(force_send=force_send, + send_after_commit=send_after_commit, + user_signature=user_signature) + if hide_followers: + self_sudo.subtype_id = orig_subtype_id + + return res diff --git a/mail_tracking/models/mail_thread.py b/mail_tracking/models/mail_thread.py index 081491cb9..98c3c7eb9 100644 --- a/mail_tracking/models/mail_thread.py +++ b/mail_tracking/models/mail_thread.py @@ -1,17 +1,22 @@ # Copyright 2019 Alexandre Díaz # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from odoo import models, api, _ +from odoo import fields, models, api, _ from email.utils import getaddresses from odoo.tools import email_split_and_format from lxml import etree -import logging -_logger = logging.getLogger(__name__) class MailThread(models.AbstractModel): _inherit = "mail.thread" + failed_message_ids = fields.One2many( + 'mail.message', 'res_id', string='Failed Messages', + domain=lambda self: + [('model', '=', self._name)] + + self.env['mail.message']._get_failed_message_domain(), + auto_join=True) + @api.multi @api.returns('self', lambda value: value.id) def message_post(self, body='', subject=None, message_type='notification', @@ -30,6 +35,8 @@ class MailThread(models.AbstractModel): @api.multi def message_get_suggested_recipients(self): + """Adds email Cc recipients as suggested recipients. + If the recipient have an res.partner uses it.""" res = super().message_get_suggested_recipients() ResPartnerObj = self.env['res.partner'] email_cc_formated_list = [] @@ -49,36 +56,53 @@ class MailThread(models.AbstractModel): partner = ResPartnerObj.browse(partner_id, self._prefetch) record._message_add_suggested_recipient( res, partner=partner, reason=_('Cc')) + return res @api.model def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False): - """Add a filter to any model with mail.thread that will show up records - with tracking errors. + """Add filters for failed messages. + + These filters will show up on any form or search views of any + model inheriting from ``mail.thread``. """ res = super().fields_view_get( view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu) - if view_type != 'search': + if view_type != 'search' and view_type != 'form': return res - - # Create filter element - filter_name = "message_ids_with_tracking_errors" - tracking_error_domain = """[ - ("message_ids.mail_tracking_ids.state", "in", - ['error', 'rejected', 'spam', 'bounced', 'soft-bounced']), - ("message_ids.track_needs_action", "=", True)]""" - new_filter = etree.Element( - 'filter', { - 'string': _('Messages with errors'), - 'name': filter_name, - 'domain': tracking_error_domain}) - separator = etree.Element('separator', {}) - new_filter.append(separator) - - # Modify view to add new filter element doc = etree.XML(res['arch']) - node = doc.xpath("//search")[0] - node.insert(0, new_filter) + if view_type == 'search': + # Modify view to add new filter element + nodes = doc.xpath("//search") + if nodes: + # Create filter element + new_filter = etree.Element( + 'filter', { + 'string': _('Failed sent messages'), + 'name': "failed_message_ids", + 'domain': str([ + ['failed_message_ids.mail_tracking_ids.state', + 'in', + list( + self.env['mail.message'].get_failed_states() + )], + ['failed_message_ids.mail_tracking_needs_action', + '=', True] + ]) + }) + nodes[0].append(etree.Element('separator')) + nodes[0].append(new_filter) + elif view_type == 'form': + # Modify view to add new field element + nodes = doc.xpath( + "//field[@name='message_ids' and @widget='mail_thread']") + if nodes: + # Create field + field_failed_messages = etree.Element('field', { + 'name': 'failed_message_ids', + 'widget': 'mail_failed_message', + }) + nodes[0].addprevious(field_failed_messages) res['arch'] = etree.tostring(doc, encoding='unicode') return res diff --git a/mail_tracking/models/mail_tracking_email.py b/mail_tracking/models/mail_tracking_email.py index edcb2ded2..f7bdcd8d7 100644 --- a/mail_tracking/models/mail_tracking_email.py +++ b/mail_tracking/models/mail_tracking_email.py @@ -94,11 +94,10 @@ class MailTrackingEmail(models.Model): @api.multi def write(self, vals): - if 'state' in vals and vals['state'] in \ - ['error', 'rejected', 'spam', 'bounced', 'soft-bounced']: - for tracking_mail in self: - if tracking_mail.mail_message_id: - tracking_mail.mail_message_id.track_needs_action = True + if vals.get('state') in self.env['mail.message'].get_failed_states(): + self.mapped('mail_message_id').write({ + 'mail_tracking_needs_action': True, + }) super().write(vals) @api.model @@ -106,8 +105,8 @@ class MailTrackingEmail(models.Model): if not email: return False res = self._email_last_tracking_state(email) - return res and res[0].get('state', '') in ['rejected', 'error', - 'spam', 'bounced'] + return res and res[0].get('state', '') in {'rejected', 'error', + 'spam', 'bounced'} @api.model def _email_last_tracking_state(self, email): diff --git a/mail_tracking/readme/ROADMAP.rst b/mail_tracking/readme/ROADMAP.rst new file mode 100644 index 000000000..dcb039c01 --- /dev/null +++ b/mail_tracking/readme/ROADMAP.rst @@ -0,0 +1,5 @@ +* Handle message updates on discuss 'channel_failed' instead of showing the + 'outdated' message. +* Adapt chat_manager changes in v12 +* Adapt discuss changes in v12 +* Add pivot for tracking events and mail trackings diff --git a/mail_tracking/readme/USAGE.rst b/mail_tracking/readme/USAGE.rst index aff1328d2..2a6ed40f1 100644 --- a/mail_tracking/readme/USAGE.rst +++ b/mail_tracking/readme/USAGE.rst @@ -10,25 +10,25 @@ status icon will appear just right to name of notified partner. These are all available status icons: -.. |sent| image:: static/src/img/sent.png +.. |sent| image:: ../static/src/img/sent.png :width: 10px -.. |delivered| image:: static/src/img/delivered.png +.. |delivered| image:: ../static/src/img/delivered.png :width: 15px -.. |opened| image:: static/src/img/opened.png +.. |opened| image:: ../static/src/img/opened.png :width: 15px -.. |error| image:: static/src/img/error.png +.. |error| image:: ../static/src/img/error.png :width: 10px -.. |waiting| image:: static/src/img/waiting.png +.. |waiting| image:: ../static/src/img/waiting.png :width: 10px -.. |unknown| image:: static/src/img/unknown.png +.. |unknown| image:: ../static/src/img/unknown.png :width: 10px -.. |cc| image:: static/src/img/cc.png +.. |cc| image:: ../static/src/img/cc.png :width: 10px |unknown| **Unknown**: No email tracking info available. Maybe this notified partner has 'Receive Inbox Notifications by Email' == 'Never' @@ -44,3 +44,16 @@ These are all available status icons: |opened| **Opened**: Opened by partner |cc| **Cc**: It's a Carbon-Copy recipient. Can't know the status so is 'Unknown' + + +When the message generates and 'error' status, it will apear on discuss 'Failed' +channel. Any view that uses 'mail_thread' widget can show the failed messages +too. + +* Discuss + + .. image:: ../static/img/failed_message_discuss.png + +* Chatter + + .. image:: ../static/img/failed_message_widget.png diff --git a/mail_tracking/static/description/index.html b/mail_tracking/static/description/index.html index 7d6f75c5b..3bf4696b7 100644 --- a/mail_tracking/static/description/index.html +++ b/mail_tracking/static/description/index.html @@ -376,11 +376,12 @@ right to his name.