diff --git a/mail_tracking/README.rst b/mail_tracking/README.rst index b8ad2e9..4c3f345 100644 --- a/mail_tracking/README.rst +++ b/mail_tracking/README.rst @@ -52,44 +52,28 @@ status icon will appear just right to name of notified partner. These are all available status icons: -<<<<<<< HEAD -.. |sent| image:: mail_tracking/static/src/img/sent.png +.. |sent| image:: ../static/src/img/sent.png :width: 10px -.. |delivered| image:: mail_tracking/static/src/img/delivered.png +.. |delivered| image:: ../static/src/img/delivered.png :width: 15px -.. |opened| image:: mail_tracking/static/src/img/opened.png +.. |opened| image:: ../static/src/img/opened.png :width: 15px -.. |error| image:: mail_tracking/static/src/img/error.png +.. |error| image:: ../static/src/img/error.png :width: 10px -.. |waiting| image:: mail_tracking/static/src/img/waiting.png +.. |waiting| image:: ../static/src/img/waiting.png :width: 10px -.. |unknown| image:: mail_tracking/static/src/img/unknown.png -======= -.. |sent| image:: https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/src/img/sent.png +.. |unknown| image:: ../static/src/img/unknown.png :width: 10px -.. |delivered| image:: https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/src/img/delivered.png - :width: 15px - -.. |opened| image:: https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/src/img/opened.png - :width: 15px - -.. |error| image:: https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/src/img/error.png +.. |cc| image:: ../static/src/img/cc.png :width: 10px -.. |waiting| image:: https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/src/img/waiting.png - :width: 10px - -.. |unknown| image:: https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/src/img/unknown.png ->>>>>>> 75b9662... [IMP] mail_tracking: Failed Messages (Discuss & View) - :width: 10px - -.. |cc| image:: https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/src/img/cc.png +.. |noemail| image:: ../static/src/img/no_email.png :width: 10px |unknown| **Unknown**: No email tracking info available. Maybe this notified partner has 'Receive Inbox Notifications by Email' == 'Never' @@ -106,34 +90,37 @@ These are all available status icons: |cc| **Cc**: It's a Carbon-Copy recipient. Can't know the status so is 'Unknown' +|noemail| **No Email**: The partner doesn't have a defined email + -<<<<<<< HEAD If you want to see all tracking emails and events you can go to * Settings > Technical > Email > Tracking emails * Settings > Technical > Email > Tracking events -======= -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 + +When the message generates an 'error' status, it will apear on discuss 'Failed' +channel. Any view with chatter can show the failed messages too. * Discuss - .. image:: https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/img/failed_message_discuss.png + .. image:: https://raw.githubusercontent.com/OCA/social/12.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 + .. image:: https://raw.githubusercontent.com/OCA/social/12.0/mail_tracking/static/img/failed_message_widget.png + +You can use "Failed sent messages" filter present in all views to show records +with messages in failed status and that needs an user action. + +* Filter + + .. image:: https://raw.githubusercontent.com/OCA/social/12.0/mail_tracking/static/img/failed_message_filter.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 ->>>>>>> 75b9662... [IMP] mail_tracking: Failed Messages (Discuss & View) Bug Tracker =========== diff --git a/mail_tracking/__manifest__.py b/mail_tracking/__manifest__.py index 3a5b3e4..9770529 100644 --- a/mail_tracking/__manifest__.py +++ b/mail_tracking/__manifest__.py @@ -28,12 +28,12 @@ "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", + "static/src/xml/failed_message/common.xml", + "static/src/xml/failed_message/thread.xml", + "static/src/xml/failed_message/discuss.xml", ], 'demo': [ 'demo/demo.xml', diff --git a/mail_tracking/controllers/main.py b/mail_tracking/controllers/main.py index 2f53ecb..c4a5e0e 100644 --- a/mail_tracking/controllers/main.py +++ b/mail_tracking/controllers/main.py @@ -2,10 +2,11 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). import werkzeug -from psycopg2 import OperationalError -from odoo import api, http, registry, SUPERUSER_ID, _ +import odoo +from contextlib import contextmanager +from odoo import api, http, SUPERUSER_ID + from odoo.addons.mail.controllers.main import MailController -from odoo.http import request import logging import base64 _logger = logging.getLogger(__name__) @@ -13,35 +14,23 @@ _logger = logging.getLogger(__name__) BLANK = 'R0lGODlhAQABAIAAANvf7wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==' -def _env_get(db, callback, tracking_id, event_type, **kw): - res = 'NOT FOUND' - reg = False - current = http.request.db and db == http.request.db - env = current and http.request.env - if not env: - with api.Environment.manage(): - try: - reg = registry(db) - except OperationalError: - _logger.warning("Selected BD '%s' not found", db) - except Exception: # pragma: no cover - _logger.warning("Selected BD '%s' connection error", db) - if reg: - _logger.info("New environment for database '%s'", db) - with reg.cursor() as new_cr: - new_env = api.Environment(new_cr, SUPERUSER_ID, {}) - res = callback(new_env, tracking_id, event_type, **kw) - new_env.cr.commit() - else: - # make sudo when reusing environment - env = env(user=SUPERUSER_ID) - res = callback(env, tracking_id, event_type, **kw) - return res +@contextmanager +def db_env(dbname): + if not http.db_filter([dbname]): + raise werkzeug.exceptions.BadRequest() + cr = None + if dbname == http.request.db: + cr = http.request.cr + if not cr: + cr = odoo.sql_db.db_connect(dbname).cursor() + with api.Environment.manage(): + yield api.Environment(cr, SUPERUSER_ID, {}) class MailTrackingController(MailController): def _request_metadata(self): + """Prepare remote info metadata""" request = http.request.httprequest return { 'ip': request.remote_addr or False, @@ -50,37 +39,45 @@ class MailTrackingController(MailController): 'ua_family': request.user_agent.browser or False, } - def _tracking_open(self, env, tracking_id, event_type, **kw): - tracking_email = env['mail.tracking.email'].search([ - ('id', '=', tracking_id), - ]) - if tracking_email: - metadata = self._request_metadata() - tracking_email.event_create('open', metadata) - else: - _logger.warning( - "MailTracking email '%s' not found", tracking_id) - - def _tracking_event(self, env, tracking_id, event_type, **kw): + @http.route(['/mail/tracking/all/', + '/mail/tracking/event//'], + type='http', auth='none', csrf=False) + def mail_tracking_event(self, db, event_type=None, **kw): + """Route used by external mail service""" metadata = self._request_metadata() - return env['mail.tracking.email'].event_process( - http.request, kw, metadata, event_type=event_type) + res = None + with db_env(db) as env: + try: + res = env['mail.tracking.email'].event_process( + http.request, kw, metadata, event_type=event_type) + except Exception: + pass + if not res or res == 'NOT FOUND': + return werkzeug.exceptions.NotAcceptable() + return res - @http.route('/mail/tracking/all/', - type='http', auth='none', csrf=False) - def mail_tracking_all(self, db, **kw): - return _env_get(db, self._tracking_event, None, None, **kw) - - @http.route('/mail/tracking/event//', - type='http', auth='none', csrf=False) - def mail_tracking_event(self, db, event_type, **kw): - return _env_get(db, self._tracking_event, None, event_type, **kw) - - @http.route('/mail/tracking/open/' - '//blank.gif', - type='http', auth='none') - def mail_tracking_open(self, db, tracking_email_id, **kw): - _env_get(db, self._tracking_open, tracking_email_id, None, **kw) + @http.route(['/mail/tracking/open/' + '//blank.gif', + '/mail/tracking/open/' + '///blank.gif'], + type='http', auth='none', methods=['GET']) + def mail_tracking_open(self, db, tracking_email_id, token=False, **kw): + """Route used to track mail openned (With & Without Token)""" + metadata = self._request_metadata() + with db_env(db) as env: + try: + tracking_email = env['mail.tracking.email'].search([ + ('id', '=', tracking_email_id), + ('state', 'in', ['sent', 'delivered']), + ('token', '=', token), + ]) + if tracking_email: + tracking_email.event_create('open', metadata) + else: + _logger.warning( + "MailTracking email '%s' not found", tracking_email_id) + except Exception: + pass # Always return GIF blank image response = werkzeug.wrappers.Response() @@ -89,20 +86,11 @@ class MailTrackingController(MailController): 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, - }) + def mail_init_messaging(self): + """Route used to initial values of Discuss app""" + values = super().mail_init_messaging() values.update({ - 'failed_counter': request.env['mail.message'].get_failed_count(), + 'failed_counter': + http.request.env['mail.message'].get_failed_count(), }) return values diff --git a/mail_tracking/demo/demo.xml b/mail_tracking/demo/demo.xml index e3bfd31..e90d45d 100644 --- a/mail_tracking/demo/demo.xml +++ b/mail_tracking/demo/demo.xml @@ -2,6 +2,31 @@ + + + res.partner + + comment + + acc@testmail.com,wood.corner26@example.com,toni.rhodes11@example.com + 1 + This is a message with CC

]]>
+ wood.corner26@example.com + + + Message with CC +
+ + + Message with CC + + + demo@yourcompany.example.com + wood.corner26@example.com + sent + + + res.partner @@ -10,7 +35,7 @@ 1 This is a failed message

]]>
- res1@yourcompany.example.com + wood.corner26@example.com Failed Message @@ -20,8 +45,8 @@ Failed Message - res1@yourcompany.example.com - demo@yourcompany.example.com + demo@yourcompany.example.com + wood.corner26@example.com error
@@ -34,7 +59,7 @@ 1 This is another failed message

]]>
- res10@yourcompany.example.com + jackson.group82@example.com Failed Message @@ -44,8 +69,32 @@ Failed Message - res10@yourcompany.example.com - demo@yourcompany.example.com + demo@yourcompany.example.com + jackson.group82@example.com + error + + + + + + res.partner + + comment + + 1 + This is another failed message

]]>
+ admin@example.com + + + Failed Message +
+ + + Failed Message + + + demo@yourcompany.example.com + admin@example.com error diff --git a/mail_tracking/i18n/es.po b/mail_tracking/i18n/es.po index 4ef7df8..9685329 100644 --- a/mail_tracking/i18n/es.po +++ b/mail_tracking/i18n/es.po @@ -1,23 +1,21 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: -# * mail_tracking +# * mail_tracking # -# Translators: -# OCA Transbot , 2017 msgid "" msgstr "" -"Project-Id-Version: Odoo Server 10.0\n" +"Project-Id-Version: Odoo Server 12.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-12-01 02:19+0000\n" -"PO-Revision-Date: 2019-08-04 17:44+0000\n" -"Last-Translator: eduardgm \n" -"Language-Team: Spanish (https://www.transifex.com/oca/teams/23907/es/)\n" +"POT-Creation-Date: 2019-11-12 17:22+0000\n" +"PO-Revision-Date: 2019-11-12 18:26+0100\n" +"Last-Translator: <>\n" +"Language-Team: \n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" -"Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 3.7.1\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: \n" +"X-Generator: Poedit 2.2.4\n" #. module: mail_tracking #: model:ir.model.fields,help:mail_tracking.field_mail_tracking_email__state @@ -43,11 +41,18 @@ msgid "" "recipient Mail Exchange (MX) server.\n" msgstr "" +#. module: mail_tracking +#: code:addons/mail_tracking/models/mail_message.py:189 +#, python-format +msgid "-Unknown Author-" +msgstr "-Autor Desconocido-" + #. module: mail_tracking #: model:ir.model.fields,help:mail_tracking.field_mail_compose_message__email_cc #: model:ir.model.fields,help:mail_tracking.field_mail_message__email_cc msgid "Additional recipients that receive a \"Carbon Copy\" of the e-mail" msgstr "" +"Destinatarios adicionales que reciben una \"Copia de Carbón\" del correo" #. module: mail_tracking #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search @@ -70,8 +75,8 @@ msgid "Bounced" msgstr "Rebotado" #. module: mail_tracking -#: code:addons/mail_tracking/models/mail_thread.py:39 -#: code:addons/mail_tracking/models/mail_thread.py:43 +#: code:addons/mail_tracking/models/mail_thread.py:64 +#: code:addons/mail_tracking/models/mail_thread.py:68 #: model:ir.model.fields,field_description:mail_tracking.field_mail_compose_message__email_cc #: model:ir.model.fields,field_description:mail_tracking.field_mail_message__email_cc #, python-format @@ -93,6 +98,20 @@ msgstr "Clicado" msgid "Clicked URL" msgstr "URL Clicada" +#. module: mail_tracking +#. openerp-web +#: code:addons/mail_tracking/static/src/xml/failed_message/discuss.xml:22 +#, python-format +msgid "Congratulations, you don't have any failed messages" +msgstr "¡Enhorabuena! No tienes mensajes fallidos" + +#. module: mail_tracking +#. openerp-web +#: code:addons/mail_tracking/static/src/js/failed_message/discuss.js:231 +#, python-format +msgid "Congratulations, your failed mailbox is empty" +msgstr "¡Enhorabuena! tu buzón de fallidos está vacio" + #. module: mail_tracking #: model:ir.model,name:mail_tracking.model_res_partner msgid "Contact" @@ -128,21 +147,23 @@ msgstr "Fecha" #. module: mail_tracking #: selection:mail.tracking.event,event_type:0 msgid "Deferral" -msgstr "" +msgstr "Aplazamiento" #. module: mail_tracking #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search #: selection:mail.tracking.email,state:0 msgid "Deferred" -msgstr "" +msgstr "Diferido" #. module: mail_tracking +#: code:addons/mail_tracking/models/mail_message.py:74 #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search #: selection:mail.tracking.email,state:0 #: selection:mail.tracking.event,event_type:0 +#, python-format msgid "Delivered" -msgstr "" +msgstr "Entregado" #. module: mail_tracking #: model:ir.model.fields,field_description:mail_tracking.field_mail_bounced_mixin__display_name @@ -165,94 +186,151 @@ msgstr "Correo electrónico" #: model:ir.model.fields,field_description:mail_tracking.field_mail_bounced_mixin__email_bounced #: model:ir.model.fields,field_description:mail_tracking.field_res_partner__email_bounced #: model:ir.model.fields,field_description:mail_tracking.field_res_users__email_bounced -#, fuzzy msgid "Email Bounced" -msgstr "E mail rebotado" +msgstr "Correo rebotado" #. module: mail_tracking #: model:ir.model.fields,field_description:mail_tracking.field_res_partner__email_score #: model:ir.model.fields,field_description:mail_tracking.field_res_users__email_score -#, fuzzy msgid "Email Score" -msgstr "Reputación del email" +msgstr "Reputación del correo" #. module: mail_tracking #: model:ir.model,name:mail_tracking.model_mail_thread -#, fuzzy msgid "Email Thread" -msgstr "Reputación del email" +msgstr "Hilo de mensajes" #. module: mail_tracking #: model_terms:ir.ui.view,arch_db:mail_tracking.view_res_partner_filter msgid "Email bounced" -msgstr "E mail rebotado" +msgstr "Email rebotado" #. module: mail_tracking +#: model:ir.model,name:mail_tracking.model_mail_resend_message +msgid "Email resend wizard" +msgstr "Asistente de reenvio de correo electrónico" + +#. module: mail_tracking +#: code:addons/mail_tracking/models/mail_message.py:73 #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_form #: selection:mail.tracking.email,state:0 +#, python-format msgid "Error" msgstr "" #. module: mail_tracking #: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_email__error_smtp_server msgid "Error SMTP server" -msgstr "" +msgstr "Error servidor SMTP" #. module: mail_tracking #: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_email__error_description #: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_event__error_description msgid "Error description" -msgstr "" +msgstr "Descripción del error" #. module: mail_tracking #: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_event__error_details msgid "Error details" -msgstr "" +msgstr "Detalles del error" #. module: mail_tracking #: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_email__error_type #: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_event__error_type msgid "Error type" -msgstr "" +msgstr "Tipo de error" #. module: mail_tracking #: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_event__event_type msgid "Event type" -msgstr "" +msgstr "Tipo de evento" #. module: mail_tracking +#. openerp-web +#: code:addons/mail_tracking/static/src/js/failed_message/discuss.js:350 +#: code:addons/mail_tracking/static/src/xml/failed_message/discuss.xml:13 +#: code:addons/mail_tracking/static/src/xml/failed_message/thread.xml:21 #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search +#, python-format msgid "Failed" -msgstr "" +msgstr "Fallido" + +#. module: mail_tracking +#: model:ir.model.fields,field_description:mail_tracking.field_account_analytic_account__failed_message_ids +#: model:ir.model.fields,field_description:mail_tracking.field_calendar_event__failed_message_ids +#: model:ir.model.fields,field_description:mail_tracking.field_hr_department__failed_message_ids +#: model:ir.model.fields,field_description:mail_tracking.field_hr_employee__failed_message_ids +#: model:ir.model.fields,field_description:mail_tracking.field_hr_job__failed_message_ids +#: model:ir.model.fields,field_description:mail_tracking.field_mail_blacklist__failed_message_ids +#: model:ir.model.fields,field_description:mail_tracking.field_mail_channel__failed_message_ids +#: model:ir.model.fields,field_description:mail_tracking.field_mail_thread__failed_message_ids +#: model:ir.model.fields,field_description:mail_tracking.field_res_partner__failed_message_ids +#: model:ir.model.fields,field_description:mail_tracking.field_res_users__failed_message_ids +msgid "Failed Messages" +msgstr "Mensajes Fallidos" + +#. module: mail_tracking +#. openerp-web +#: code:addons/mail_tracking/static/src/xml/failed_message/thread.xml:39 +#, python-format +msgid "Failed Recipients:" +msgstr "Destinatarios Fallidos:" + +#. module: mail_tracking +#. openerp-web +#: code:addons/mail_tracking/static/src/xml/failed_message/thread.xml:7 +#, python-format +msgid "Failed messages" +msgstr "Mensajes fallidos" + +#. module: mail_tracking +#. openerp-web +#: code:addons/mail_tracking/static/src/xml/failed_message/discuss.xml:23 +#, python-format +msgid "Failed messages appear here." +msgstr "Los mensajes fallidos se muestran aquí." + +#. module: mail_tracking +#: code:addons/mail_tracking/models/mail_thread.py:92 +#, python-format +msgid "Failed sent messages" +msgstr "Mensajes enviados fallidos" #. module: mail_tracking #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search msgid "Group By" -msgstr "" +msgstr "Agrupar por" #. module: mail_tracking #: selection:mail.tracking.event,event_type:0 msgid "Hard bounce" -msgstr "" +msgstr "Rebote duro" #. module: mail_tracking #: model:ir.model.fields,field_description:mail_tracking.field_mail_bounced_mixin__id #: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_email__id #: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_event__id msgid "ID" -msgstr "ID" +msgstr "" #. module: mail_tracking #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search msgid "IP" msgstr "" +#. module: mail_tracking +#: model:ir.model.fields,field_description:mail_tracking.field_mail_compose_message__is_failed_message +#: model:ir.model.fields,field_description:mail_tracking.field_mail_mail__is_failed_message +#: model:ir.model.fields,field_description:mail_tracking.field_mail_message__is_failed_message +msgid "Is Failed Message" +msgstr "Es un Mensajes Fallido" + #. module: mail_tracking #: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_event__mobile msgid "Is mobile?" -msgstr "" +msgstr "Es móvil?" #. module: mail_tracking #: model:ir.model.fields,field_description:mail_tracking.field_mail_bounced_mixin____last_update @@ -275,49 +353,68 @@ msgstr "Última actualización en" #. module: mail_tracking #: model:ir.model,name:mail_tracking.model_ir_mail_server -#, fuzzy msgid "Mail Server" -msgstr "ir.mail_server" +msgstr "Servidor de correo" + +#. module: mail_tracking +#: model:ir.model.fields,field_description:mail_tracking.field_mail_compose_message__mail_tracking_needs_action +#: model:ir.model.fields,field_description:mail_tracking.field_mail_mail__mail_tracking_needs_action +#: model:ir.model.fields,field_description:mail_tracking.field_mail_message__mail_tracking_needs_action +msgid "Mail Tracking Needs Action" +msgstr "Mail Tracking necesita acción" + +#. module: mail_tracking +#: model:ir.model.fields,field_description:mail_tracking.field_mail_compose_message__mail_tracking_ids +#: model:ir.model.fields,field_description:mail_tracking.field_mail_mail__mail_tracking_ids +#: model:ir.model.fields,field_description:mail_tracking.field_mail_message__mail_tracking_ids +msgid "Mail Trackings" +msgstr "" #. module: mail_tracking #: model:ir.model,name:mail_tracking.model_mail_bounced_mixin -#, fuzzy msgid "Mail bounced mixin" -msgstr "E mail rebotado" +msgstr "Correo rebotado mixin" #. module: mail_tracking #: model:ir.model,name:mail_tracking.model_mail_tracking_email msgid "MailTracking email" -msgstr "MailTracking email" +msgstr "MailTracking correo" #. module: mail_tracking #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search msgid "MailTracking email search" -msgstr "" +msgstr "MailTracking búsqueda de correo" #. module: mail_tracking #: model:ir.actions.act_window,name:mail_tracking.action_view_mail_tracking_email #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_tree msgid "MailTracking emails" -msgstr "" +msgstr "MailTracking correos" #. module: mail_tracking #: model:ir.model,name:mail_tracking.model_mail_tracking_event #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_form #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_form msgid "MailTracking event" -msgstr "MailTracking event" +msgstr "MailTracking evento" #. module: mail_tracking #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search msgid "MailTracking event search" -msgstr "" +msgstr "MailTracking búsqueda de eventos" #. module: mail_tracking #: model:ir.actions.act_window,name:mail_tracking.action_view_mail_tracking_event #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_tree msgid "MailTracking events" -msgstr "" +msgstr "MailTracking eventos" + +#. module: mail_tracking +#. openerp-web +#: code:addons/mail_tracking/static/src/xml/failed_message/discuss.xml:30 +#, python-format +msgid "Mark all as reviewed" +msgstr "Marcar todos como revisado" #. module: mail_tracking #: model:ir.model,name:mail_tracking.model_mail_message @@ -329,10 +426,10 @@ msgstr "Mensaje" #. module: mail_tracking #. openerp-web -#: code:addons/mail_tracking/static/src/js/mail_tracking.js:135 +#: code:addons/mail_tracking/static/src/js/mail_tracking.js:147 #, python-format msgid "Message tracking" -msgstr "" +msgstr "Seguimiento del mensaje" #. module: mail_tracking #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search @@ -351,17 +448,19 @@ msgstr "SO" #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search #: selection:mail.tracking.event,event_type:0 msgid "Open" -msgstr "" +msgstr "Abrir" #. module: mail_tracking +#: code:addons/mail_tracking/models/mail_message.py:75 #: selection:mail.tracking.email,state:0 +#, python-format msgid "Opened" -msgstr "" +msgstr "Abierto" #. module: mail_tracking #: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_event__os_family msgid "Operating system family" -msgstr "" +msgstr "Familia del sistema operativo" #. module: mail_tracking #: model:ir.model,name:mail_tracking.model_mail_mail @@ -379,83 +478,127 @@ msgstr "Empresa" #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_tree #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search msgid "Recipient" -msgstr "" +msgstr "Destinatario" #. module: mail_tracking #: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_email__recipient msgid "Recipient email" -msgstr "" +msgstr "Correo del destinatario" #. module: mail_tracking #: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_email__recipient_address #: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_event__recipient_address msgid "Recipient email address" -msgstr "" +msgstr "Dirección de correo de destinatario" #. module: mail_tracking #: selection:mail.tracking.email,state:0 #: selection:mail.tracking.event,event_type:0 msgid "Rejected" -msgstr "" +msgstr "Rechazado" + +#. module: mail_tracking +#. openerp-web +#: code:addons/mail_tracking/static/src/xml/failed_message/common.xml:10 +#: code:addons/mail_tracking/static/src/xml/failed_message/thread.xml:35 +#, python-format +msgid "Retry" +msgstr "Reintentar" #. module: mail_tracking #: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_event__smtp_server msgid "SMTP server" -msgstr "" +msgstr "Servidor SMTP" + +#. module: mail_tracking +#: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_email__token +msgid "Security Token" +msgstr "Token de seguridad" #. module: mail_tracking #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_tree msgid "Sender" -msgstr "" +msgstr "Remitente" #. module: mail_tracking #: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_email__sender msgid "Sender email" -msgstr "" +msgstr "Correo del remitente" #. module: mail_tracking +#: code:addons/mail_tracking/models/mail_message.py:74 #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search #: selection:mail.tracking.email,state:0 #: selection:mail.tracking.event,event_type:0 +#, python-format msgid "Sent" -msgstr "" +msgstr "Enviado" + +#. module: mail_tracking +#. openerp-web +#: code:addons/mail_tracking/static/src/xml/failed_message/discuss.xml:30 +#, python-format +msgid "Set all as reviewed" +msgstr "Marcar todos como revisados" + +#. module: mail_tracking +#. openerp-web +#: code:addons/mail_tracking/static/src/xml/failed_message/common.xml:7 +#: code:addons/mail_tracking/static/src/xml/failed_message/thread.xml:32 +#, python-format +msgid "Set as Reviewed" +msgstr "Marcar como Revisado" #. module: mail_tracking #: selection:mail.tracking.event,event_type:0 msgid "Soft bounce" -msgstr "" +msgstr "Rebote débil" #. module: mail_tracking #: selection:mail.tracking.email,state:0 msgid "Soft bounced" -msgstr "" +msgstr "Rebotado débil" #. module: mail_tracking #: selection:mail.tracking.email,state:0 #: selection:mail.tracking.event,event_type:0 msgid "Spam" -msgstr "" +msgstr "No deseado" #. module: mail_tracking #: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_email__state #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search msgid "State" -msgstr "" +msgstr "Estado" #. module: mail_tracking -#. openerp-web -#: code:addons/mail_tracking/static/src/xml/mail_tracking.xml:96 +#: code:addons/mail_tracking/models/mail_message.py:76 #, python-format -msgid "Status: unknown" -msgstr "" +msgid "Status: %s" +msgstr "Estado: %s" #. module: mail_tracking #: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_email__name #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search msgid "Subject" +msgstr "Asunto" + +#. module: mail_tracking +#: model:ir.model.fields,help:mail_tracking.field_mail_compose_message__mail_tracking_needs_action +#: model:ir.model.fields,help:mail_tracking.field_mail_mail__mail_tracking_needs_action +#: model:ir.model.fields,help:mail_tracking.field_mail_message__mail_tracking_needs_action +msgid "The message tracking will be considered to filter tracking issues" msgstr "" +"El seguimiento del correo puede ser considerado para filtrar incidencia de " +"seguimiento" + +#. module: mail_tracking +#: code:addons/mail_tracking/models/mail_message.py:82 +#, python-format +msgid "The partner doesn't have a defined email" +msgstr "El socio no tiene un correo definido" #. module: mail_tracking #: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_email__time @@ -463,27 +606,26 @@ msgstr "" #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search msgid "Time" -msgstr "" +msgstr "Tiempo" #. module: mail_tracking #. openerp-web -#: code:addons/mail_tracking/static/src/xml/mail_tracking.xml:53 +#: code:addons/mail_tracking/static/src/xml/mail_tracking.xml:54 #, python-format msgid "To:" -msgstr "" +msgstr "Para:" #. module: mail_tracking #: model:ir.model.fields,field_description:mail_tracking.field_res_partner__tracking_emails_count #: model:ir.model.fields,field_description:mail_tracking.field_res_users__tracking_emails_count -#, fuzzy msgid "Tracking Emails Count" -msgstr "MailTracking email" +msgstr "Contador de correos con seguimiento" #. module: mail_tracking #: model:ir.ui.menu,name:mail_tracking.menu_mail_tracking_email #: model_terms:ir.ui.view,arch_db:mail_tracking.view_partner_form msgid "Tracking emails" -msgstr "" +msgstr "Correos con seguimiento" #. module: mail_tracking #: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_email__tracking_event_ids @@ -494,15 +636,15 @@ msgstr "Eventos de seguimiento" #. module: mail_tracking #. openerp-web -#: code:addons/mail_tracking/static/src/js/mail_tracking.js:115 +#: code:addons/mail_tracking/static/src/js/mail_tracking.js:127 #, python-format msgid "Tracking partner" -msgstr "" +msgstr "Socio del seguimiento" #. module: mail_tracking #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search msgid "Type" -msgstr "" +msgstr "Tipo" #. module: mail_tracking #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search @@ -515,22 +657,28 @@ msgstr "" msgid "UTC timestamp" msgstr "" +#. module: mail_tracking +#: code:addons/mail_tracking/models/mail_message.py:75 +#, python-format +msgid "Unknown" +msgstr "Desconocido" + #. module: mail_tracking #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search msgid "Unsubscribe" -msgstr "" +msgstr "Darse de baja" #. module: mail_tracking #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search #: selection:mail.tracking.email,state:0 #: selection:mail.tracking.event,event_type:0 msgid "Unsubscribed" -msgstr "" +msgstr "Dado de baja" #. module: mail_tracking #: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_event__ip msgid "User IP" -msgstr "" +msgstr "IP del usuario" #. module: mail_tracking #: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_event__user_agent @@ -554,4 +702,10 @@ msgstr "" #. module: mail_tracking #: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_event__user_country_id msgid "User country" -msgstr "" +msgstr "País del Usuario" + +#. module: mail_tracking +#: code:addons/mail_tracking/models/mail_message.py:73 +#, python-format +msgid "Waiting" +msgstr "Esperando" diff --git a/mail_tracking/i18n/mail_tracking.pot b/mail_tracking/i18n/mail_tracking.pot index f9e75ab..7cfc6d1 100644 --- a/mail_tracking/i18n/mail_tracking.pot +++ b/mail_tracking/i18n/mail_tracking.pot @@ -27,6 +27,12 @@ msgid " * The 'Error' status indicates that there was an error when trying to se "" msgstr "" +#. module: mail_tracking +#: code:addons/mail_tracking/models/mail_message.py:189 +#, python-format +msgid "-Unknown Author-" +msgstr "" + #. module: mail_tracking #: model:ir.model.fields,help:mail_tracking.field_mail_compose_message__email_cc #: model:ir.model.fields,help:mail_tracking.field_mail_message__email_cc @@ -54,8 +60,8 @@ msgid "Bounced" msgstr "" #. module: mail_tracking -#: code:addons/mail_tracking/models/mail_thread.py:39 -#: code:addons/mail_tracking/models/mail_thread.py:43 +#: code:addons/mail_tracking/models/mail_thread.py:64 +#: code:addons/mail_tracking/models/mail_thread.py:68 #: model:ir.model.fields,field_description:mail_tracking.field_mail_compose_message__email_cc #: model:ir.model.fields,field_description:mail_tracking.field_mail_message__email_cc #, python-format @@ -77,6 +83,20 @@ msgstr "" msgid "Clicked URL" msgstr "" +#. module: mail_tracking +#. openerp-web +#: code:addons/mail_tracking/static/src/xml/failed_message/discuss.xml:22 +#, python-format +msgid "Congratulations, you don't have any failed messages" +msgstr "" + +#. module: mail_tracking +#. openerp-web +#: code:addons/mail_tracking/static/src/js/failed_message/discuss.js:231 +#, python-format +msgid "Congratulations, your failed mailbox is empty" +msgstr "" + #. module: mail_tracking #: model:ir.model,name:mail_tracking.model_res_partner msgid "Contact" @@ -121,10 +141,12 @@ msgid "Deferred" msgstr "" #. module: mail_tracking +#: code:addons/mail_tracking/models/mail_message.py:74 #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search #: selection:mail.tracking.email,state:0 #: selection:mail.tracking.event,event_type:0 +#, python-format msgid "Delivered" msgstr "" @@ -169,8 +191,15 @@ msgid "Email bounced" msgstr "" #. module: mail_tracking +#: model:ir.model,name:mail_tracking.model_mail_resend_message +msgid "Email resend wizard" +msgstr "" + +#. module: mail_tracking +#: code:addons/mail_tracking/models/mail_message.py:73 #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_form #: selection:mail.tracking.email,state:0 +#, python-format msgid "Error" msgstr "" @@ -202,11 +231,54 @@ msgid "Event type" msgstr "" #. module: mail_tracking +#. openerp-web +#: code:addons/mail_tracking/static/src/js/failed_message/discuss.js:350 +#: code:addons/mail_tracking/static/src/xml/failed_message/discuss.xml:13 +#: code:addons/mail_tracking/static/src/xml/failed_message/thread.xml:21 #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search +#, python-format msgid "Failed" msgstr "" +#. module: mail_tracking +#: model:ir.model.fields,field_description:mail_tracking.field_account_analytic_account__failed_message_ids +#: model:ir.model.fields,field_description:mail_tracking.field_calendar_event__failed_message_ids +#: model:ir.model.fields,field_description:mail_tracking.field_mail_blacklist__failed_message_ids +#: model:ir.model.fields,field_description:mail_tracking.field_mail_channel__failed_message_ids +#: model:ir.model.fields,field_description:mail_tracking.field_mail_thread__failed_message_ids +#: model:ir.model.fields,field_description:mail_tracking.field_res_partner__failed_message_ids +#: model:ir.model.fields,field_description:mail_tracking.field_res_users__failed_message_ids +msgid "Failed Messages" +msgstr "" + +#. module: mail_tracking +#. openerp-web +#: code:addons/mail_tracking/static/src/xml/failed_message/thread.xml:39 +#, python-format +msgid "Failed Recipients:" +msgstr "" + +#. module: mail_tracking +#. openerp-web +#: code:addons/mail_tracking/static/src/xml/failed_message/thread.xml:7 +#, python-format +msgid "Failed messages" +msgstr "" + +#. module: mail_tracking +#. openerp-web +#: code:addons/mail_tracking/static/src/xml/failed_message/discuss.xml:23 +#, python-format +msgid "Failed messages appear here." +msgstr "" + +#. module: mail_tracking +#: code:addons/mail_tracking/models/mail_thread.py:92 +#, python-format +msgid "Failed sent messages" +msgstr "" + #. module: mail_tracking #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search @@ -230,6 +302,13 @@ msgstr "" msgid "IP" msgstr "" +#. module: mail_tracking +#: model:ir.model.fields,field_description:mail_tracking.field_mail_compose_message__is_failed_message +#: model:ir.model.fields,field_description:mail_tracking.field_mail_mail__is_failed_message +#: model:ir.model.fields,field_description:mail_tracking.field_mail_message__is_failed_message +msgid "Is Failed Message" +msgstr "" + #. module: mail_tracking #: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_event__mobile msgid "Is mobile?" @@ -259,6 +338,20 @@ msgstr "" msgid "Mail Server" msgstr "" +#. module: mail_tracking +#: model:ir.model.fields,field_description:mail_tracking.field_mail_compose_message__mail_tracking_needs_action +#: model:ir.model.fields,field_description:mail_tracking.field_mail_mail__mail_tracking_needs_action +#: model:ir.model.fields,field_description:mail_tracking.field_mail_message__mail_tracking_needs_action +msgid "Mail Tracking Needs Action" +msgstr "" + +#. module: mail_tracking +#: model:ir.model.fields,field_description:mail_tracking.field_mail_compose_message__mail_tracking_ids +#: model:ir.model.fields,field_description:mail_tracking.field_mail_mail__mail_tracking_ids +#: model:ir.model.fields,field_description:mail_tracking.field_mail_message__mail_tracking_ids +msgid "Mail Trackings" +msgstr "" + #. module: mail_tracking #: model:ir.model,name:mail_tracking.model_mail_bounced_mixin msgid "Mail bounced mixin" @@ -298,6 +391,13 @@ msgstr "" msgid "MailTracking events" msgstr "" +#. module: mail_tracking +#. openerp-web +#: code:addons/mail_tracking/static/src/xml/failed_message/discuss.xml:30 +#, python-format +msgid "Mark all as reviewed" +msgstr "" + #. module: mail_tracking #: model:ir.model,name:mail_tracking.model_mail_message #: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_email__mail_message_id @@ -308,7 +408,7 @@ msgstr "" #. module: mail_tracking #. openerp-web -#: code:addons/mail_tracking/static/src/js/mail_tracking.js:135 +#: code:addons/mail_tracking/static/src/js/mail_tracking.js:147 #, python-format msgid "Message tracking" msgstr "" @@ -333,7 +433,9 @@ msgid "Open" msgstr "" #. module: mail_tracking +#: code:addons/mail_tracking/models/mail_message.py:75 #: selection:mail.tracking.email,state:0 +#, python-format msgid "Opened" msgstr "" @@ -377,11 +479,24 @@ msgstr "" msgid "Rejected" msgstr "" +#. module: mail_tracking +#. openerp-web +#: code:addons/mail_tracking/static/src/xml/failed_message/common.xml:10 +#: code:addons/mail_tracking/static/src/xml/failed_message/thread.xml:35 +#, python-format +msgid "Retry" +msgstr "" + #. module: mail_tracking #: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_event__smtp_server msgid "SMTP server" msgstr "" +#. module: mail_tracking +#: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_email__token +msgid "Security Token" +msgstr "" + #. module: mail_tracking #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_tree @@ -394,13 +509,30 @@ msgid "Sender email" msgstr "" #. module: mail_tracking +#: code:addons/mail_tracking/models/mail_message.py:74 #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_email_search #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search #: selection:mail.tracking.email,state:0 #: selection:mail.tracking.event,event_type:0 +#, python-format msgid "Sent" msgstr "" +#. module: mail_tracking +#. openerp-web +#: code:addons/mail_tracking/static/src/xml/failed_message/discuss.xml:30 +#, python-format +msgid "Set all as reviewed" +msgstr "" + +#. module: mail_tracking +#. openerp-web +#: code:addons/mail_tracking/static/src/xml/failed_message/common.xml:7 +#: code:addons/mail_tracking/static/src/xml/failed_message/thread.xml:32 +#, python-format +msgid "Set as Reviewed" +msgstr "" + #. module: mail_tracking #: selection:mail.tracking.event,event_type:0 msgid "Soft bounce" @@ -424,10 +556,9 @@ msgid "State" msgstr "" #. module: mail_tracking -#. openerp-web -#: code:addons/mail_tracking/static/src/xml/mail_tracking.xml:96 +#: code:addons/mail_tracking/models/mail_message.py:76 #, python-format -msgid "Status: unknown" +msgid "Status: %s" msgstr "" #. module: mail_tracking @@ -436,6 +567,19 @@ msgstr "" msgid "Subject" msgstr "" +#. module: mail_tracking +#: model:ir.model.fields,help:mail_tracking.field_mail_compose_message__mail_tracking_needs_action +#: model:ir.model.fields,help:mail_tracking.field_mail_mail__mail_tracking_needs_action +#: model:ir.model.fields,help:mail_tracking.field_mail_message__mail_tracking_needs_action +msgid "The message tracking will be considered to filter tracking issues" +msgstr "" + +#. module: mail_tracking +#: code:addons/mail_tracking/models/mail_message.py:82 +#, python-format +msgid "The partner doesn't have a defined email" +msgstr "" + #. module: mail_tracking #: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_email__time #: model:ir.model.fields,field_description:mail_tracking.field_mail_tracking_event__time @@ -446,7 +590,7 @@ msgstr "" #. module: mail_tracking #. openerp-web -#: code:addons/mail_tracking/static/src/xml/mail_tracking.xml:53 +#: code:addons/mail_tracking/static/src/xml/mail_tracking.xml:54 #, python-format msgid "To:" msgstr "" @@ -472,7 +616,7 @@ msgstr "" #. module: mail_tracking #. openerp-web -#: code:addons/mail_tracking/static/src/js/mail_tracking.js:115 +#: code:addons/mail_tracking/static/src/js/mail_tracking.js:127 #, python-format msgid "Tracking partner" msgstr "" @@ -493,6 +637,12 @@ msgstr "" msgid "UTC timestamp" msgstr "" +#. module: mail_tracking +#: code:addons/mail_tracking/models/mail_message.py:75 +#, python-format +msgid "Unknown" +msgstr "" + #. module: mail_tracking #: model_terms:ir.ui.view,arch_db:mail_tracking.view_mail_tracking_event_search msgid "Unsubscribe" @@ -534,3 +684,9 @@ msgstr "" msgid "User country" msgstr "" +#. module: mail_tracking +#: code:addons/mail_tracking/models/mail_message.py:73 +#, python-format +msgid "Waiting" +msgstr "" + diff --git a/mail_tracking/migrations/12.0.2.0.0/post-migrate.py b/mail_tracking/migrations/12.0.2.0.0/post-migrate.py new file mode 100644 index 0000000..6b2993c --- /dev/null +++ b/mail_tracking/migrations/12.0.2.0.0/post-migrate.py @@ -0,0 +1,11 @@ +# Copyright 2019 Alexandre Díaz +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + + +from openupgradelib.openupgrade import migrate + + +@migrate() +def migrate(env, version): + cr = env.cr + cr.execute("UPDATE mail_tracking_email SET token = NULL") diff --git a/mail_tracking/models/__init__.py b/mail_tracking/models/__init__.py index aab92b3..896721a 100644 --- a/mail_tracking/models/__init__.py +++ b/mail_tracking/models/__init__.py @@ -6,6 +6,6 @@ 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 +from . import mail_resend_message diff --git a/mail_tracking/models/mail_bounced_mixin.py b/mail_tracking/models/mail_bounced_mixin.py index 9f26507..91737d4 100644 --- a/mail_tracking/models/mail_bounced_mixin.py +++ b/mail_tracking/models/mail_bounced_mixin.py @@ -1,12 +1,8 @@ # Copyright 2018 Tecnativa - Ernesto Tejeda # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -import logging - from odoo import api, fields, models -_logger = logging.getLogger(__name__) - class MailBouncedMixin(models.AbstractModel): """ A mixin class to use if you want to add is_bounced flag on a model. diff --git a/mail_tracking/models/mail_composer.py b/mail_tracking/models/mail_composer.py deleted file mode 100644 index f4f847e..0000000 --- a/mail_tracking/models/mail_composer.py +++ /dev/null @@ -1,30 +0,0 @@ -# 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_mail.py b/mail_tracking/models/mail_mail.py index 2342d1c..1d4f42b 100644 --- a/mail_tracking/models/mail_mail.py +++ b/mail_tracking/models/mail_mail.py @@ -12,6 +12,7 @@ class MailMail(models.Model): _inherit = 'mail.mail' def _tracking_email_prepare(self, partner, email): + """Prepare email.tracking.email record values""" ts = time.time() dt = datetime.utcfromtimestamp(ts) email_to_list = email.get('email_to', []) @@ -28,7 +29,9 @@ class MailMail(models.Model): } def _send_prepare_values(self, partner=None): - email = super(MailMail, self)._send_prepare_values(partner=partner) + """Creates the mail.tracking.email record and adds the image tracking + to the email""" + email = super()._send_prepare_values(partner=partner) vals = self._tracking_email_prepare(partner, email) tracking_email = self.env['mail.tracking.email'].sudo().create(vals) return tracking_email.tracking_img_add(email) diff --git a/mail_tracking/models/mail_message.py b/mail_tracking/models/mail_message.py index f67def5..f552b7b 100644 --- a/mail_tracking/models/mail_message.py +++ b/mail_tracking/models/mail_message.py @@ -22,12 +22,29 @@ class MailMessage(models.Model): " to filter tracking issues", default=False, ) + is_failed_message = fields.Boolean(compute="_compute_is_failed_message") @api.model def get_failed_states(self): + """The 'failed' states of the message""" return {'error', 'rejected', 'spam', 'bounced', 'soft-bounced'} + @api.depends('mail_tracking_needs_action', 'author_id', 'partner_ids', + 'mail_tracking_ids.state') + def _compute_is_failed_message(self): + """Compute 'is_failed_message' field for the active user""" + failed_states = self.get_failed_states() + for message in self: + needs_action = message.mail_tracking_needs_action + involves_me = self.env.user.partner_id in ( + message.author_id | message.partner_ids) + has_failed_trackings = failed_states.intersection( + message.mapped("mail_tracking_ids.state")) + message.is_failed_message = bool( + needs_action and involves_me and has_failed_trackings) + def _tracking_status_map_get(self): + """Map tracking states to be used in chatter""" return { 'False': 'waiting', 'error': 'error', @@ -43,6 +60,7 @@ class MailMessage(models.Model): } def _partner_tracking_status_get(self, tracking_email): + """Determine tracking status""" tracking_status_map = self._tracking_status_map_get() status = 'unknown' if tracking_email: @@ -51,12 +69,23 @@ class MailMessage(models.Model): return status def _partner_tracking_status_human_get(self, status): + """Translations for tracking statuses to be used on qweb""" statuses = {'waiting': _('Waiting'), 'error': _('Error'), 'sent': _('Sent'), 'delivered': _('Delivered'), 'opened': _('Opened'), 'unknown': _('Unknown')} return _("Status: %s") % statuses[status] + @api.model + def _get_error_description(self, tracking): + """Translations for error descriptions to be used on qweb""" + descriptions = { + 'no_recipient': _("The partner doesn't have a defined email"), + } + return descriptions.get(tracking.error_type, + tracking.error_description) + def tracking_status(self): + """Generates a complete status tracking of the messages by partner""" res = {} for message in self: partner_trackings = [] @@ -79,6 +108,9 @@ class MailMessage(models.Model): 'status': status, 'status_human': self._partner_tracking_status_human_get(status), + 'error_type': tracking.error_type, + 'error_description': + self._get_error_description(tracking), 'tracking_id': tracking.id, 'recipient': recipient, 'partner_id': tracking.partner_id.id, @@ -94,114 +126,132 @@ class MailMessage(models.Model): partners |= message.needaction_partner_ids # Remove recipients already included partners -= partners_already + tracking_unkown_values = { + 'status': 'unknown', + 'status_human': self._partner_tracking_status_human_get( + 'unknown'), + 'error_type': False, + 'error_description': False, + 'tracking_id': False, + } for partner in partners: # If there is partners not included, then status is 'unknown' - # Because can be an Cc recipient + # and perhaps a Cc recipient isCc = False if partner.email in email_cc_list: email_cc_list.discard(partner.email) isCc = True - partner_trackings.append({ - 'status': 'unknown', - 'status_human': - self._partner_tracking_status_human_get('unknown'), - 'tracking_id': False, + tracking_unkown_values.update({ 'recipient': partner.name, 'partner_id': partner.id, 'isCc': isCc, }) + partner_trackings.append(tracking_unkown_values.copy()) for email in email_cc_list: # If there is Cc without partner - partner_trackings.append({ - 'status': 'unknown', - 'status_human': - self._partner_tracking_status_human_get('unknown'), - 'tracking_id': False, + tracking_unkown_values.update({ 'recipient': email, 'partner_id': False, 'isCc': True, }) - res[message.id] = partner_trackings + partner_trackings.append(tracking_unkown_values.copy()) + res[message.id] = { + 'partner_trackings': partner_trackings, + 'is_failed_message': message.is_failed_message, + } return res @api.model def _message_read_dict_postprocess(self, messages, message_tree): - res = super(MailMessage, self)._message_read_dict_postprocess( + """Preare values to be used by the chatter widget""" + res = super()._message_read_dict_postprocess( messages, message_tree) 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() + tracking_statuses = mail_messages.tracking_status() for message_dict in messages: mail_message_id = message_dict.get('id', False) if mail_message_id: - message_dict.update({ - 'partner_trackings': partner_trackings[mail_message_id], - 'failed_message': failed_message[mail_message_id], - }) - message_dict['partner_trackings'] = \ - partner_trackings[mail_message_id] + message_dict.update(tracking_statuses[mail_message_id]) return res - @api.model - def _prepare_dict_failed_message(self, message): - failed_trackings = message.mail_tracking_ids.filtered( + @api.multi + def _prepare_dict_failed_message(self): + """Preare values to be used by the chatter widget""" + self.ensure_one() + failed_trackings = self.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() + if self.author_id: + author = self.author_id.name_get()[0] + else: + author = (-1, _('-Unknown Author-')) return { - 'id': message.id, - 'date': message.date, - 'author_id': message.author_id.name_get()[0], - 'body': message.body, + 'id': self.id, + 'date': self.date, + 'author': author, + 'body': self.body, 'failed_recipients': failed_recipients, } @api.multi def get_failed_messages(self): - return [self._prepare_dict_failed_message(msg) for msg in self] + """Returns the list of failed messages to be used by the + failed_messages widget""" + return [msg._prepare_dict_failed_message() + for msg in self.sorted('date', reverse=True)] @api.multi def toggle_tracking_status(self): - """Toggle message tracking action needed to ignore them in the tracking - issues filter""" + """Toggle message tracking action. + + This will mark them to be (or not) ignored in the tracking issues + filter. + """ + self.check_access_rule('read') self.mail_tracking_needs_action = not self.mail_tracking_needs_action - return self.mail_tracking_needs_action + notification = { + 'type': 'toggle_tracking_status', + 'message_ids': [self.id], + 'needs_actions': self.mail_tracking_needs_action + } + self.env['bus.bus'].sendone( + (self._cr.dbname, 'res.partner', self.env.user.partner_id.id), + notification) def _get_failed_message_domain(self): - return [ - ('mail_tracking_ids.state', 'in', list(self.get_failed_states())), - ('mail_tracking_needs_action', '=', True) + domain = self.env['mail.thread']._get_failed_message_domain() + domain += [ + '|', + ('partner_ids', 'in', [self.env.user.partner_id.id]), + ('author_id', '=', self.env.user.partner_id.id), ] + return domain @api.model def get_failed_count(self): - """ Gets the number of failed messages """ + """ Gets the number of failed messages used on discuss mailbox item""" 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) + def set_all_as_reviewed(self): + """ Sets all messages in the given domain as reviewed. - @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 + Used by Discuss """ + + unreviewed_messages = self.search(self._get_failed_message_domain()) + unreviewed_messages.write({'mail_tracking_needs_action': False}) + ids = unreviewed_messages.ids + + self.env['bus.bus'].sendone( + (self._cr.dbname, 'res.partner', + self.env.user.partner_id.id), + { + 'type': 'toggle_tracking_status', + 'message_ids': ids, + 'needs_actions': False, + } + ) + + return ids diff --git a/mail_tracking/models/mail_resend_message.py b/mail_tracking/models/mail_resend_message.py new file mode 100644 index 0000000..555e30b --- /dev/null +++ b/mail_tracking/models/mail_resend_message.py @@ -0,0 +1,64 @@ +# Copyright 2019 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import models, api + + +class MailResendMessage(models.TransientModel): + _inherit = "mail.resend.message" + + @api.model + def default_get(self, fields): + rec = super().default_get(fields) + message_id = self._context.get('mail_message_to_resend') + if message_id: + MailMessageObj = self.env['mail.message'] + mail_message_id = MailMessageObj.browse(message_id) + failed_states = MailMessageObj.get_failed_states() + tracking_ids = mail_message_id.mail_tracking_ids.filtered( + lambda x: x.state in failed_states) + if any(tracking_ids): + partner_ids = [(0, 0, { + "partner_id": tracking.partner_id.id, + "name": tracking.partner_id.name, + "email": tracking.partner_id.email, + "resend": True, + "message": tracking.error_description, + }) for tracking in tracking_ids] + rec['partner_ids'].extend(partner_ids) + return rec + + @api.multi + def resend_mail_action(self): + for wizard in self: + to_send = wizard.partner_ids.filtered("resend").mapped( + "partner_id") + if to_send: + # Set as reviewed + wizard.mail_message_id.mail_tracking_needs_action = False + # Reset mail.tracking.email state + tracking_ids = wizard.mail_message_id.mail_tracking_ids\ + .filtered(lambda x: x.partner_id in to_send) + tracking_ids.write({'state': False}) + # Send bus notifications to update Discuss and + # mail_failed_messages widget + notifications = [ + [ + (self._cr.dbname, 'res.partner', + self.env.user.partner_id.id), + { + 'type': 'update_failed_messages', + } + ], + [ + (self._cr.dbname, 'res.partner', + self.env.user.partner_id.id), + { + 'type': 'toggle_tracking_status', + 'message_ids': [self.mail_message_id.id], + 'needs_actions': False, + } + ] + ] + self.env['bus.bus'].sendmany(notifications) + super().resend_mail_action() diff --git a/mail_tracking/models/mail_thread.py b/mail_tracking/models/mail_thread.py index 920fa17..4a385b4 100644 --- a/mail_tracking/models/mail_thread.py +++ b/mail_tracking/models/mail_thread.py @@ -14,12 +14,25 @@ class MailThread(models.AbstractModel): 'mail.message', 'res_id', string='Failed Messages', domain=lambda self: [('model', '=', self._name)] - + self.env['mail.message']._get_failed_message_domain(), - auto_join=True) + + self._get_failed_message_domain()) + + def _get_failed_message_domain(self): + """Domain used to display failed messages on the 'failed_messages' + widget""" + failed_states = self.env['mail.message'].get_failed_states() + return [ + ('mail_tracking_needs_action', '=', True), + ('mail_tracking_ids.state', 'in', list(failed_states)), + ] @api.multi @api.returns('self', lambda value: value.id) def message_post(self, *args, **kwargs): + """Adds CC recipient to the message. + + Because Odoo implementation avoid store cc recipients we ensure that + this information its written into the mail.message record. + """ new_message = super().message_post(*args, **kwargs) email_cc = kwargs.get('cc') if email_cc: @@ -31,7 +44,9 @@ 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.""" + + If the recipient has a res.partner, use it. + """ res = super().message_get_suggested_recipients() ResPartnerObj = self.env['res.partner'] email_cc_formated_list = [] @@ -64,7 +79,7 @@ class MailThread(models.AbstractModel): res = super().fields_view_get( view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu) - if view_type != 'search' and view_type != 'form': + if view_type not in {'search', 'form'}: return res doc = etree.XML(res['arch']) if view_type == 'search': diff --git a/mail_tracking/models/mail_tracking_email.py b/mail_tracking/models/mail_tracking_email.py index f7bdcd8..c7e84f0 100644 --- a/mail_tracking/models/mail_tracking_email.py +++ b/mail_tracking/models/mail_tracking_email.py @@ -5,6 +5,7 @@ import logging import urllib.parse import time import re +import uuid from datetime import datetime from odoo import models, api, fields, tools @@ -91,14 +92,30 @@ class MailTrackingEmail(models.Model): tracking_event_ids = fields.One2many( string="Tracking events", comodel_name='mail.tracking.event', inverse_name='tracking_email_id', readonly=True) + # Token isn't generated here to have compatibility with older trackings. + # New trackings have token and older not + token = fields.Char(string="Security Token", readonly=True, + default=lambda s: uuid.uuid4().hex, + groups="base.group_system") + + @api.model_create_multi + def create(self, vals_list): + records = super().create(vals_list) + failed_states = self.env['mail.message'].get_failed_states() + records \ + .filtered(lambda one: one.state in failed_states) \ + .mapped("mail_message_id") \ + .write({'mail_tracking_needs_action': True}) + return records @api.multi def write(self, vals): - if vals.get('state') in self.env['mail.message'].get_failed_states(): + super().write(vals) + state = vals.get('state') + if state and 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 def email_is_bounced(self, email): @@ -162,7 +179,9 @@ class MailTrackingEmail(models.Model): @api.depends('recipient') def _compute_recipient_address(self): for email in self: - if email.recipient: + is_empty_recipient = (not email.recipient + or '' in email.recipient) + if not is_empty_recipient: matches = re.search(r'<(.*@.*)>', email.recipient) if matches: email.recipient_address = matches.group(1).lower() @@ -187,13 +206,23 @@ class MailTrackingEmail(models.Model): def _get_mail_tracking_img(self): m_config = self.env['ir.config_parameter'] - base_url = (m_config.get_param('mail_tracking.base.url') or - m_config.get_param('web.base.url')) - path_url = ( - 'mail/tracking/open/%(db)s/%(tracking_email_id)s/blank.gif' % { - 'db': self.env.cr.dbname, - 'tracking_email_id': self.id, - }) + base_url = (m_config.get_param('mail_tracking.base.url') + or m_config.get_param('web.base.url')) + if self.token: + path_url = ( + 'mail/tracking/open/%(db)s/%(tracking_email_id)s/%(token)s/' + 'blank.gif' % { + 'db': self.env.cr.dbname, + 'tracking_email_id': self.id, + 'token': self.token, + }) + else: + # This is here for compatibility with older records + path_url = ( + 'mail/tracking/open/%(db)s/%(tracking_email_id)s/blank.gif' % { + 'db': self.env.cr.dbname, + 'tracking_email_id': self.id, + }) track_url = urllib.parse.urljoin(base_url, path_url) return ( ' Technical > Email > Tracking emails * Settings > Technical > Email > Tracking events -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 +When the message generates an 'error' status, it will apear on discuss 'Failed' +channel. Any view with chatter can show the failed messages too. * Discuss @@ -56,3 +61,10 @@ too. * Chatter .. image:: ../static/img/failed_message_widget.png + +You can use "Failed sent messages" filter present in all views to show records +with messages in failed status and that needs an user action. + +* Filter + + .. image:: ../static/img/failed_message_filter.png diff --git a/mail_tracking/static/description/index.html b/mail_tracking/static/description/index.html index 72874c0..36ea5bc 100644 --- a/mail_tracking/static/description/index.html +++ b/mail_tracking/static/description/index.html @@ -376,24 +376,16 @@ right to his name.

  • Installation
  • Usage
  • -<<<<<<< HEAD -
  • Bug Tracker
  • -
  • Credits @@ -412,47 +404,42 @@ For example, --load=web,mail_trac form, then an email tracking is created for each email notification. Then a status icon will appear just right to name of notified partner.

    These are all available status icons:

    -<<<<<<< HEAD -

    unknown Unknown: No email tracking info available. Maybe this notified partner has ‘Receive Inbox Notifications by Email’ == ‘Never’

    -

    waiting Waiting: Waiting to be sent

    -

    error Error: Error while sending

    -

    sent Sent: Sent to SMTP server configured

    -

    delivered Delivered: Delivered to final MX server

    -

    opened Opened: Opened by partner

    -

    cc Cc: It’s a Carbon-Copy recipient. Can’t know the status so is ‘Unknown’

    +

    unknown Unknown: No email tracking info available. Maybe this notified partner has ‘Receive Inbox Notifications by Email’ == ‘Never’

    +

    waiting Waiting: Waiting to be sent

    +

    error Error: Error while sending

    +

    sent Sent: Sent to SMTP server configured

    +

    delivered Delivered: Delivered to final MX server

    +

    opened Opened: Opened by partner

    +

    cc Cc: It’s a Carbon-Copy recipient. Can’t know the status so is ‘Unknown’

    +

    noemail No Email: The partner doesn’t have a defined email

    If you want to see all tracking emails and events you can go to

    • Settings > Technical > Email > Tracking emails
    • Settings > Technical > Email > Tracking events
    • -======= -

      unknown Unknown: No email tracking info available. Maybe this notified partner has ‘Receive Inbox Notifications by Email’ == ‘Never’

      -

      waiting Waiting: Waiting to be sent

      -

      error Error: Error while sending

      -

      sent Sent: Sent to SMTP server configured

      -

      delivered Delivered: Delivered to final MX server

      -

      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 +

    +

    When the message generates an ‘error’ status, it will apear on discuss ‘Failed’ +channel. Any view with chatter can show the failed messages too.

    • Discuss

      -https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/img/failed_message_discuss.png +https://raw.githubusercontent.com/OCA/social/12.0/mail_tracking/static/img/failed_message_discuss.png
    • Chatter

      -https://raw.githubusercontent.com/OCA/social/11.0/mail_tracking/static/img/failed_message_widget.png +https://raw.githubusercontent.com/OCA/social/12.0/mail_tracking/static/img/failed_message_widget.png +
    • +
    +

    You can use “Failed sent messages” filter present in all views to show records +with messages in failed status and that needs an user action.

    +
      +
    • Filter

      +https://raw.githubusercontent.com/OCA/social/12.0/mail_tracking/static/img/failed_message_filter.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
    • ->>>>>>> 75b9662... [IMP] mail_tracking: Failed Messages (Discuss & View)
    @@ -486,9 +473,9 @@ If you spotted it first, help us smashing it by providing a detailed and welcome
-

Other credits

+

Other credits

-

Images

+

Images

  • Odoo Community Association: Icon.
  • Thanks to LlubNek and Openclipart for the icon.
  • @@ -496,11 +483,7 @@ If you spotted it first, help us smashing it by providing a detailed and welcome
-<<<<<<< HEAD -

Maintainers

-======= -

Maintainers

->>>>>>> 75b9662... [IMP] mail_tracking: Failed Messages (Discuss & View) +

Maintainers

This module is maintained by the OCA.

Odoo Community Association

OCA, or the Odoo Community Association, is a nonprofit organization whose diff --git a/mail_tracking/static/img/failed_message_filter.png b/mail_tracking/static/img/failed_message_filter.png new file mode 100644 index 0000000..7843d14 Binary files /dev/null and b/mail_tracking/static/img/failed_message_filter.png differ diff --git a/mail_tracking/static/src/css/failed_message.less b/mail_tracking/static/src/css/failed_message.less deleted file mode 100644 index 7457ccb..0000000 --- a/mail_tracking/static/src/css/failed_message.less +++ /dev/null @@ -1,108 +0,0 @@ -/* Copyright 2019 Alexandre Díaz - License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). */ -.o_mail_failed_message { - &.o_field_widget { - display: block; - } - - .o_thread_date_separator - { - margin-top: 1.5rem; - margin-bottom: 3rem; - @media (max-width: @screen-xs-max) { - margin-top: 0; - margin-bottom: 1.5rem; - } - border-bottom: 1px solid @gray-lighter-darker; - border-bottom-style: solid; - text-align: center; - - &.o_border_dashed { - border-bottom-style: dashed; - - &[data-toggle="collapse"] { - cursor: pointer; - - .o_chatter_failed_message_summary { - display: none; - } - - &.collapsed { - margin-bottom: 0; - .o-transition(margin, 0.8s); - - .o_chatter_failed_message_summary { - display: inline-block; - - span { - padding: 0 0.5rem; - border-radius: 100%; - font-size: 1.1rem; - } - } - - i.fa-caret-down:before { - content: '\f0da'; - } - } - } - } - - .o_thread_date { - position: relative; - top: 1rem; - margin: 0 auto; - padding: 0 1rem; - font-weight: bold; - background: white; - } - } - - .o_thread_message { - display: -ms-flexbox; - display: -moz-box; - display: -webkit-box; - display: -webkit-flex; - display: flex; - padding: 0.4rem @odoo-horizontal-padding; - margin-bottom: 0px; - - .o_thread_message_sidebar { - .o-flex(0, 0, @mail-thread-avatar-size); - margin-right: 1rem; - margin-top: 0.2rem; - text-align: center; - font-size: smaller; - - .o_avatar_stack { - position: relative; - text-align: left; - margin-bottom: 0.8rem; - - img { - .square(31px); - } - - .o_avatar_icon { - .o-position-absolute(@right: -5px, @bottom: -5px); - .square(25px); - padding: 0.6rem 0.5rem; - text-align: center; - line-height: 1.2; - color: white; - border-radius: 100%; - border: 2px solid white; - } - } - } - - .o_thread_message_core .o_mail_info { - .text-muted(); - } - } -} - -.o_mail_chat .o_mail_chat_sidebar .o_mail_failed_message_refresh { - margin-right: 0.5em; - margin-top: 0.2em; -} diff --git a/mail_tracking/static/src/css/failed_message.scss b/mail_tracking/static/src/css/failed_message.scss new file mode 100644 index 0000000..e9216e0 --- /dev/null +++ b/mail_tracking/static/src/css/failed_message.scss @@ -0,0 +1,343 @@ +/* Copyright 2019 Alexandre Díaz + License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). */ +// FIXME: More of these classes are cloned from other scss files. +.o_mail_failed_message { + &.o_field_widget { + display: block; + } + + .o_thread_date_separator.o_border_dashed { + border-bottom-style: dashed; + + &[data-toggle="collapse"] { + cursor: pointer; + + .o_chatter_failed_message_summary { + display: none; + } + + &.collapsed { + margin-bottom: 0; + transition: margin 0.8s ease 0s; + + .o_chatter_failed_message_summary { + display: inline-block; + + span { + padding: 0 5px; + border-radius: 100%; + font-size: 11px; + } + } + + i.fa-caret-down:before { + content: '\f0da'; + } + } + } + } + + .o_thread_show_more { + text-align: center; + } + + .o_mail_thread_content { + display: flex; + flex-direction: column; + min-height: 100%; + } + + .o_thread_bottom_free_space { + height: 15px; + } + + .o_thread_typing_notification_free_space { + flex-grow: 1, + } + + .o_thread_typing_notification_bar { + flex: 0, 0, 20px; + background-color: rgba($white, 0.75); + padding: 5px; + text-align: center; + color: gray('600'); + + &.o_thread_order_asc { + @include o-position-sticky($bottom: 0px); + } + + &.o_thread_order_desc { + @include o-position-sticky($top: 0px); + } + } + + .o_thread_tooltip_container { + display: inline; + position: relative; + } + + .o_thread_date_separator { + margin-top: 15px; + margin-bottom: 30px; + @include media-breakpoint-down(sm) { + margin-top: 0px; + margin-bottom: 15px; + } + border-bottom: 1px solid gray('400'); + text-align: center; + + .o_thread_date { + position: relative; + top: 10px; + margin: 0 auto; + padding: 0 10px; + font-weight: bold; + background: white; + } + } + + .o_thread_new_messages_separator { + margin-bottom: 15px; + border-bottom: solid lighten($o-brand-odoo, 15%) 1px; + text-align: right; + .o_thread_separator_label { + position: relative; + top: 8px; + padding: 0 10px; + background: white; + color: lighten($o-brand-odoo, 15%); + font-size: smaller; + } + } + + .o_thread_message { + display: flex; + padding: 4px $o-horizontal-padding; + margin-bottom: 0px; + + &.o_mail_not_discussion { + background-color: rgba(gray('300'), 0.5); + border-bottom: 1px solid gray('400'); + } + + .o_thread_message_sidebar { + flex: 0 0 $o-mail-thread-avatar-size; + margin-right: 10px; + margin-top: 2px; + text-align: center; + font-size: smaller; + + @include media-breakpoint-down(sm) { + margin-top: 4px; + font-size: x-small; + } + + .o_thread_message_avatar { + max-width: $o-mail-thread-avatar-size; + } + .o_thread_message_side_date { + margin-left: -5px; + } + .o_thread_message_star { + margin-right: -5px; + } + + .o_thread_message_side_date { + opacity: 0; + } + } + .o_thread_icon { + cursor: pointer; + opacity: 0; + &.fa-star { + opacity: $o-mail-thread-icon-opacity; + color: gold; + } + } + + &:hover, &.o_thread_selected_message { + .o_thread_message_side_date { + opacity: $o-mail-thread-side-date-opacity; + } + .o_thread_icon { + opacity: $o-mail-thread-icon-opacity; + &:hover { + opacity: 1; + } + } + } + + .o_mail_redirect { + cursor: pointer; + } + + .o_thread_message_core { + flex: 1 1 auto; + min-width: 0; + max-width: 100%; + word-wrap: break-word; + > pre { + white-space: pre-wrap; + word-break: break-word; + text-align: justify; + } + + + + .o_mail_subject { + font-style: italic; + } + + .o_mail_notification { + font-style: italic; + color: gray; + } + + [summary~=o_mail_notification] { // name conflicts with channel notifications, but is odoo notification buttons to hide in chatter if present + display: none; + } + + p { + margin: 0 0 9px; // Required by the old design to override a general rule on p's + &:last-child { + margin-bottom: 0; + } + } + a { + display: inline-block; + word-break: break-all; + } + :not(.o_image_box) > img { + max-width: 100%; + height: auto; + } + + .o_mail_body_long { + display: none; + } + + .o_mail_info { + margin-bottom: 2px; + + strong { + color: $headings-color; + } + } + + .o_thread_message_star, .o_thread_message_needaction, .o_thread_message_reply, .o_thread_message_email { + padding: 4px; + } + + i.o_thread_message_email { + &.o_thread_message_email_ready { + color: grey; + } + &.o_thread_message_email_exception, &.o_thread_message_email_bounce { + color: red; + opacity: 1; + cursor: pointer; + } + } + + .o_attachments_list, .o_attachments_previews { + &:last-child { + margin-bottom: $grid-gutter-width; + } + } + + .o_thread_tooltip_container { + display: inline; + position: relative; + } + } + } + .o_thread_title { + margin-top: 20px; + margin-bottom: 20px; + font-weight: bold; + font-size: 125%; + } + + .o_mail_no_content { + @include o-position-absolute(30%, 0, 0, 0); + text-align: center; + font-size: 115%; + } + + .o_thread_message .o_thread_message_core .o_mail_read_more { + display: block; + } + + #o_chatter_failed_message { + .o_thread_message { + .o_thread_message_sidebar { + .o_avatar_stack { + position: relative; + text-align: left; + margin-bottom: 8px; + + img { + width: 31px; + height: 31px; + } + + .o_avatar_icon { + @include o-position-absolute($right: -5px, $bottom: -5px); + width: 25px; + height: 25px; + padding: 6px 5px; + text-align: center; + line-height: 1.2; + color: white; + border-radius: 100%; + border: 2px solid white; + } + } + } + + .o_mail_info { + .o_activity_info { + vertical-align: baseline; + padding: 4px 6px; + background: theme-color('light'); + border-radius: 2px 2px 0 0; + @include o-hover-opacity(1, 1); + + &.collapsed { + @include o-hover-opacity(0.5, 1); + background: transparent; + } + } + } + + .o_thread_message_collapse .dl-horizontal.card { + display: inline-block; + margin-bottom: 0; + + dt { + max-width: 80px; + } + dd { + margin-left: 95px; + } + } + + .o_thread_message_note { + margin: 2px 0 5px; + padding: 0px; + } + .o_thread_message_warning { + margin: 2px 0 5px; + } + + .o_thread_message_tools { + .o_failed_message_link { + padding: 0 $input-btn-padding-x; + } + .o_failed_message_retry { + padding-left: 0; + } + } + } + } +} diff --git a/mail_tracking/static/src/css/mail_tracking.less b/mail_tracking/static/src/css/mail_tracking.scss similarity index 88% rename from mail_tracking/static/src/css/mail_tracking.less rename to mail_tracking/static/src/css/mail_tracking.scss index 4db2930..8f125cc 100644 --- a/mail_tracking/static/src/css/mail_tracking.less +++ b/mail_tracking/static/src/css/mail_tracking.scss @@ -4,10 +4,10 @@ .mail_tracking { span { - color: @odoo-color-0; + color: #909090; &.mail_tracking_opened { - color: @odoo-color-5; + color: #a34a8b; } } } diff --git a/mail_tracking/static/src/js/failed_message.js b/mail_tracking/static/src/js/failed_message.js deleted file mode 100644 index bdd9d74..0000000 --- a/mail_tracking/static/src/js/failed_message.js +++ /dev/null @@ -1,365 +0,0 @@ -/* Copyright 2019 Alexandre Díaz - License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). */ -odoo.define('mail_tracking.FailedMessage', function (require) { - "use strict"; - - var ChatAction = require('mail.chat_client_action'); - var AbstractField = require('web.AbstractField'); - var BasicModel = require('web.BasicModel'); - var BasicView = require('web.BasicView'); - var Chatter = require('mail.Chatter'); - var utils = require('mail.utils'); - var chat_manager = require('mail.chat_manager'); - var core = require('web.core'); - var field_registry = require('web.field_registry'); - var time = require('web.time'); - var session = require('web.session'); - var config = require('web.config'); - - var QWeb = core.qweb; - var _t = core._t; - - /* DISCUSS */ - var failed_counter = 0; - var is_channel_failed_outdated = false; - ChatAction.include({ - init: function () { - this._super.apply(this, arguments); - // HACK: Custom event to update messsages - core.bus.on('force_update_message', this, function (data) { - is_channel_failed_outdated = true; - this._onMessageUpdated(data); - this.throttledUpdateChannels(); - }); - }, - - _renderSidebar: function (options) { - options.failed_counter = chat_manager.get_failed_counter(); - return this._super.apply(this, arguments); - }, - _onMessageUpdated: function (message, type) { - var self = this; - var current_channel_id = this.channel.id; - // HACK: break inheritance because can't override properly - if (current_channel_id === "channel_failed" && - !message.is_failed) { - chat_manager.get_messages({ - channel_id: this.channel.id, - domain: this.domain, - }).then(function (messages) { - var options = self._getThreadRenderingOptions(messages); - self.thread.remove_message_and_render( - message.id, messages, options).then(function () { - self._updateButtonStatus(messages.length === 0, type); - }); - }); - } else { - this._super.apply(this, arguments); - } - }, - _updateChannels: function () { - var self = this; - // HACK: break inheritance because can't override properly - if (this.channel.id === "channel_failed") { - var $sidebar = this._renderSidebar({ - active_channel_id: - this.channel ? this.channel.id: undefined, - channels: chat_manager.get_channels(), - needaction_counter: chat_manager.get_needaction_counter(), - starred_counter: chat_manager.get_starred_counter(), - failed_counter: chat_manager.get_failed_counter(), - }); - this.$(".o_mail_chat_sidebar").html($sidebar.contents()); - _.each(['dm', 'public', 'private'], function (type) { - var $input = self.$( - '.o_mail_add_channel[data-type=' + type + '] input'); - self._prepareAddChannelInput($input, type); - }); - } else { - this._super.apply(this, arguments); - } - - // FIXME: Because can't refresh "channel_failed" we add a flag - // to indicate that the data is outdated - var refresh_elm = this.$( - ".o_mail_chat_sidebar .o_mail_failed_message_refresh"); - refresh_elm.click(function (event) { - event.preventDefault(); - event.stopPropagation(); - location.reload(); - }); - if (is_channel_failed_outdated) { - refresh_elm.removeClass('hidden'); - } - }, - }); - - chat_manager.get_failed_counter = function () { - return failed_counter; - }; - - chat_manager._onMailClientAction_failed_message_super = - chat_manager._onMailClientAction; - chat_manager._onMailClientAction = function (result) { - failed_counter = result.failed_counter; - return this._onMailClientAction_failed_message_super(result); - }; - - function add_channel_to_message (message, channel_id) { - message.channel_ids.push(channel_id); - message.channel_ids = _.uniq(message.channel_ids); - } - - chat_manager._make_message_failed_message_super = chat_manager.make_message; - chat_manager.make_message = function (data) { - var msg = this._make_message_failed_message_super(data); - function property_descr (channel) { - return { - enumerable: true, - get: function () { - return _.contains(msg.channel_ids, channel); - }, - set: function (bool) { - if (bool) { - add_channel_to_message(msg, channel); - } else { - msg.channel_ids = _.without(msg.channel_ids, channel); - } - }, - }; - } - - Object.defineProperties(msg, { - is_failed: property_descr("channel_failed"), - }); - msg.is_failed = data.failed_message; - return msg; - }; - - chat_manager._fetchFromChannel_failed_message_super = - chat_manager._fetchFromChannel; - chat_manager._fetchFromChannel = function (channel, options) { - if (channel.id !== "channel_failed") { - return this._fetchFromChannel_failed_message_super( - channel, options); - } - - // HACK: Can't override '_fetchFromChannel' properly to modify the - // domain, uses context instead and does it in python. - session.user_context.filter_failed_message = true; - var res = this._fetchFromChannel_failed_message_super( - channel, options); - res.then(function () { - delete session.user_context.filter_failed_message; - }); - return res; - }; - - // HACK: Get failed_counter. Because 'chat_manager' call 'start' need call - // to '/mail/client_action' again with overrided '_onMailClientAction' - session.is_bound.then(function () { - var context = _.extend({isMobile: config.device.isMobile}, - session.user_context); - return session.rpc('/mail/client_action', {context: context}); - }).then(chat_manager._onMailClientAction.bind(chat_manager)); - - - /* FAILED MESSAGES CHATTER WIDGET */ - // TODO: Use timeFromNow() in v12 - function time_from_now (date) { - if (moment().diff(date, 'seconds') < 45) { - return _t("now"); - } - return date.fromNow(); - } - - function _readMessages (self, ids) { - if (!ids.length) { - return $.when([]); - } - var context = self.record && self.record.getContext(); - return self._rpc({ - model: 'mail.message', - method: 'get_failed_messages', - args: [ids], - context: context || self.getSession().user_context, - }).then(function (messages) { - // Convert date to moment - _.each(messages, function (msg) { - msg.date = moment(time.auto_str_to_date(msg.date)); - msg.hour = time_from_now(msg.date); - }); - return _.sortBy(messages, 'date'); - }); - } - - BasicModel.include({ - _fetchSpecialFailedMessages: function (record, fieldName) { - var localID = record._changes && fieldName in record._changes - ? record._changes[fieldName] : record.data[fieldName]; - return _readMessages(this, this.localData[localID].res_ids); - }, - }); - - var AbstractFailedMessagesField = AbstractField.extend({ - _markFailedMessageReviewed: function (id) { - return this._rpc({ - model: 'mail.message', - method: 'toggle_tracking_status', - args: [[id]], - context: this.record.getContext(), - }).then(function (status) { - var fake_message = { - 'id': id, - 'is_failed': status, - }; - chat_manager.bus.trigger('update_message', fake_message); - core.bus.trigger('force_update_message', fake_message); - }); - }, - }); - - var FailedMessage = AbstractFailedMessagesField.extend({ - className: 'o_mail_failed_message', - events: { - 'click .o_failed_message_retry': '_onRetryFailedMessage', - 'click .o_failed_message_reviewed': '_onMarkFailedMessageReviewed', - }, - specialData: '_fetchSpecialFailedMessages', - - init: function () { - this._super.apply(this, arguments); - this.failed_messages = this.record.specialData[this.name]; - }, - _render: function () { - if (this.failed_messages.length) { - this.$el.html(QWeb.render( - 'mail_tracking.failed_message_items', { - failed_messages: this.failed_messages, - nbFailedMessages: this.failed_messages.length, - date_format: time.getLangDateFormat(), - datetime_format: time.getLangDatetimeFormat(), - })); - } else { - this.$el.empty(); - } - }, - _reset: function (record) { - this._super.apply(this, arguments); - this.failed_messages = this.record.specialData[this.name]; - this.res_id = record.res_id; - }, - - _reload: function (fieldsToReload) { - this.trigger_up('reload_mail_fields', fieldsToReload); - }, - - _openComposer: function (context) { - var self = this; - this.do_action({ - type: 'ir.actions.act_window', - res_model: 'mail.compose.message', - view_mode: 'form', - view_type: 'form', - views: [[false, 'form']], - target: 'new', - context: context, - }, { - on_close: function () { - self._reload({failed_message: true}); - self.trigger('need_refresh'); - chat_manager.get_messages({ - model: self.model, - res_id: self.res_id, - }); - }, - }).then(this.trigger.bind(this, 'close_composer')); - }, - - // Handlers - _onRetryFailedMessage: function (event) { - event.preventDefault(); - var message_id = $(event.currentTarget).data('message-id'); - var failed_msg = _.findWhere(this.failed_messages, - {'id': message_id}); - var failed_partner_ids = _.map(failed_msg.failed_recipients, - function (item) { - return item[0]; - }); - this._openComposer({ - default_body: utils.get_text2html(failed_msg.body), - default_partner_ids: failed_partner_ids, - default_is_log: false, - default_model: this.model, - default_res_id: this.res_id, - default_composition_mode: 'comment', - // Omit followers - default_hide_followers: true, - mail_post_autofollow: true, - message_id: message_id, - }); - - }, - - _onMarkFailedMessageReviewed: function (event) { - event.preventDefault(); - var message_id = $(event.currentTarget).data('message-id'); - this._markFailedMessageReviewed(message_id).then( - this._reload.bind(this, {failed_message: true})); - }, - }); - - field_registry.add('mail_failed_message', FailedMessage); - - var mailWidgets = ['mail_failed_message']; - BasicView.include({ - init: function (viewInfo) { - this._super.apply(this, arguments); - // Adds mail_failed_message as valid mail widget - var fieldsInfo = viewInfo.fieldsInfo[this.viewType]; - for (var fieldName in fieldsInfo) { - var fieldInfo = fieldsInfo[fieldName]; - if (_.contains(mailWidgets, fieldInfo.widget)) { - this.mailFields[fieldInfo.widget] = fieldName; - fieldInfo.__no_fetch = true; - } - } - Object.assign(this.rendererParams.mailFields, this.mailFields); - }, - }); - Chatter.include({ - init: function (parent, record, mailFields, options) { - this._super.apply(this, arguments); - // Initialize mail_failed_message widget - if (mailFields.mail_failed_message) { - this.fields.failed_message = new FailedMessage( - this, mailFields.mail_failed_message, record, options); - } - }, - - _render: function () { - var self = this; - return this._super.apply(this, arguments).then(function () { - if (self.fields.failed_message) { - self.fields.failed_message.$el.insertBefore( - self.$el.find('.o_mail_thread')); - } - }); - }, - - _onReloadMailFields: function (event) { - this._super.apply(this, arguments); - var fieldNames = []; - if (this.fields.failed_message && event.data.failed_message) { - fieldNames.push(this.fields.failed_message.name); - } - this.trigger_up('reload', { - fieldNames: fieldNames, - keepChanges: true, - }); - }, - }); - - return FailedMessage; - -}); diff --git a/mail_tracking/static/src/js/failed_message/discuss.js b/mail_tracking/static/src/js/failed_message/discuss.js new file mode 100644 index 0000000..715cc30 --- /dev/null +++ b/mail_tracking/static/src/js/failed_message/discuss.js @@ -0,0 +1,397 @@ +/* Copyright 2019 Alexandre Díaz + License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). */ +odoo.define('mail_tracking.FailedMessageDiscuss', function (require) { + "use strict"; + + // To be considered: + // - One message can be displayed in many threads + // - A thread can be a mailbox, channel, ... + // - A mailbox is a type of thread that is displayed on top of + // the discuss menu, has a counter, etc... + + var MailManagerNotif = require('mail.Manager.Notification'); + var AbstractMessage = require('mail.model.AbstractMessage'); + var Message = require('mail.model.Message'); + var Discuss = require('mail.Discuss'); + var MailManager = require('mail.Manager'); + var Mailbox = require('mail.model.Mailbox'); + var core = require('web.core'); + var session = require('web.session'); + + var QWeb = core.qweb; + var _t = core._t; + + /* The states to consider a message as failed message */ + var FAILED_STATES = [ + 'error', 'rejected', 'spam', 'bounced', 'soft-bounced', + ]; + + + AbstractMessage.include({ + + /** + * Abstract declaration to know if a message is included in the + * failed mailbox. By default it should be false. + * + * @returns {Boolean} + */ + isFailed: function () { + return false; + }, + }); + + Message.include({ + + /** + * Overrides to store information from server + * + * @override + */ + init: function (parent, data) { + this._isFailedMessage = data.is_failed_message; + return this._super.apply(this, arguments); + }, + + /** + * Implementation to know if a message is included in the + * failed mailbox. + * + * @override + */ + isFailed: function () { + return _.contains(this._threadIDs, 'mailbox_failed'); + }, + + /** + * Adds/Removes message to/from failed mailbox + * + * @param {Boolean} failed + */ + setFailed: function (failed) { + if (failed) { + this._addThread('mailbox_failed'); + } else { + this.removeThread('mailbox_failed'); + } + }, + + /** + * Include the message in the 'failed' mailbox if needed + * + * @override + */ + _processMailboxes: function () { + this.setFailed(this._isFailedMessage); + return this._super.apply(this, arguments); + }, + }); + + MailManagerNotif.include({ + + /** + * Overrides to handle changes in the 'mail_tracking_needs_action' flag + * + * @override + */ + _handlePartnerNotification: function (data) { + if (data.type === 'toggle_tracking_status') { + this._handlePartnerToggleFailedNotification(data); + } else { + // Workaround to avoid call '_handlePartnerChannelNotification' + // because this is related with the failed mailbox, not a + // channel. + this._super.apply(this, arguments); + } + }, + + /** + * This method updates messages in the failed mailbox when the flag + * 'mail_tracking_needs_action' is toggled. This can remove/add + * the message from/to failed mailbox and update mailbox counter. + * + * @private + * @param {Object} data + */ + _handlePartnerToggleFailedNotification: function (data) { + var self = this; + var failed = this.getMailbox('failed'); + _.each(data.message_ids, function (messageID) { + var message = _.find(self._messages, function (msg) { + return msg.getID() === messageID; + }); + if (message) { + message.setFailed(data.needs_actions); + if (message.isFailed() === false) { + self._removeMessageFromThread( + 'mailbox_failed', message); + } else { + self._addMessageToThreads(message, []); + var channelFailed = self.getMailbox('failed'); + channelFailed.invalidateCaches(); + } + self._mailBus.trigger('update_message', message, data.type); + } + }); + + if (data.needs_actions) { + // Increase failed counter if message is marked as failed + failed.incrementMailboxCounter(data.message_ids.length); + } else { + // Decrease failed counter if message is removed from failed + failed.decrementMailboxCounter(data.message_ids.length); + } + + // Trigger event to refresh threads + this._mailBus.trigger('update_failed', failed.getMailboxCounter()); + }, + }); + + Discuss.include({ + events: _.extend({}, Discuss.prototype.events, { + 'click .o_failed_message_retry': '_onRetryFailedMessage', + 'click .o_failed_message_reviewed': '_onMarkFailedMessageReviewed', + }), + + /** + * Paramaters used to render 'failed' mailbox entry in Discuss + * + * @private + * @returns {Object} + */ + _sidebarQWebParams: function () { + var failed = this.call('mail_service', 'getMailbox', 'failed'); + return { + activeThreadID: this._thread ? this._thread.getID() : undefined, + failedCounter: failed.getMailboxCounter(), + }; + }, + + /** + * Render 'failed' mailbox menu entry in Discuss + * + * @private + * @returns {jQueryElementt} + */ + _renderSidebar: function () { + var $sidebar = this._super.apply(this, arguments); + // Because Odoo implementation isn't designed to be inherited + // properly, we inject 'failed' button using jQuery. + var $failed_item = $(QWeb.render('mail_tracking.SidebarFailed', + this._sidebarQWebParams())); + $failed_item.insertAfter( + $sidebar.find(".o_mail_discuss_title_main").filter(":last")); + return $sidebar; + }, + + /** + * Overrides to listen click on 'Set all as reviewed' button + * + * @override + */ + _renderButtons: function () { + this._super.apply(this, arguments); + this.$btn_set_all_reviewed = this.$buttons.find( + '.o_mail_discuss_button_set_all_reviewed'); + this.$btn_set_all_reviewed + .on('click', $.proxy(this, "_onSetAllAsReviewedClicked")); + }, + + /** + * Show or hide 'set all as reviewed' button in discuss mailbox + * + * This means in which thread the button should be displayed. + * + * @override + */ + _updateControlPanelButtons: function (thread) { + this.$btn_set_all_reviewed + .toggleClass( + 'd-none d-md-none', + thread.getID() !== 'mailbox_failed'); + + return this._super.apply(this, arguments); + }, + + /** + * Overrides to update 'set all as reviewed' button. + * + * Disabled button if doesn't have more failed messages + * + * @override + */ + _updateButtonStatus: function (disabled, type) { + if (this._thread.getID() === 'mailbox_failed') { + this.$btn_set_all_reviewed + .toggleClass('disabled', disabled); + // Display Rainbowman when all failed messages are reviewed + // through 'TOGGLE TRACKING STATUS' or marking last failed + // message as reviewed + if (disabled && type === 'toggle_tracking_status') { + this.trigger_up('show_effect', { + message: _t( + "Congratulations, your failed mailbox is empty"), + type: 'rainbow_man', + }); + } + } + }, + + /** + * Overrides to update messages in 'failed' mailbox thread + * + * @override + */ + _onMessageUpdated: function (message, type) { + var self = this; + var currentThreadID = this._thread.getID(); + if (currentThreadID === 'mailbox_failed' && !message.isFailed()) { + this._thread.fetchMessages(this.domain) + .then(function () { + var options = self._getThreadRenderingOptions(); + self._threadWidget.removeMessageAndRender( + message.getID(), self._thread, options) + .then(function () { + self._updateButtonStatus( + !self._thread.hasMessages(), type); + }); + }); + } else { + // Workaround to avoid calling '_fetchAndRenderThread' and + // refetching thread messages because these messages are + // actually fetched above. + this._super.apply(this, arguments); + } + }, + + /** + * Hide reply feature in the 'failed' mailbox, where it has no sense. + * Show instead 'Retry' and 'Set as reviewed' buttons. + * + * @override + */ + _getThreadRenderingOptions: function () { + var values = this._super.apply(this, arguments); + if (this._thread.getID() === 'mailbox_failed') { + values.displayEmailIcons = true; + values.displayReplyIcons = false; + values.displayRetryButton = true; + values.displayReviewedButton = true; + } + return values; + }, + + /** + * Listen also to the event that refreshes thread messages + * + * @override + */ + _startListening: function () { + this._super.apply(this, arguments); + this.call('mail_service', 'getMailBus') + .on('update_failed', this, this._throttledUpdateThreads); + }, + + // Handlers + /** + * Open the resend mail.resend.message wizard + * + * @private + * @param {Event} event + */ + _onRetryFailedMessage: function (event) { + event.preventDefault(); + var messageID = $(event.currentTarget).data('message-id'); + this.do_action('mail.mail_resend_message_action', { + additional_context: { + mail_message_to_resend: messageID, + }, + }); + }, + + /** + * Toggle 'mail_tracking_needs_action' flag + * + * @private + * @param {Event} event + * @returns {Promise} + */ + _onMarkFailedMessageReviewed: function (event) { + event.preventDefault(); + var messageID = $(event.currentTarget).data('message-id'); + return this._rpc({ + model: 'mail.message', + method: 'toggle_tracking_status', + args: [[messageID]], + context: this.getSession().user_context, + }); + }, + + /** + * Inheritable method that call thread implementation + * + * @private + */ + _onSetAllAsReviewedClicked: function () { + this._thread.setAllMessagesAsReviewed(); + }, + }); + + MailManager.include({ + + /** + * Add the 'failed' mailbox + * + * @override + */ + _updateMailboxesFromServer: function (data) { + this._super.apply(this, arguments); + this._addMailbox({ + id: 'failed', + name: _t("Failed"), + mailboxCounter: data.failed_counter || 0, + }); + }, + }); + + Mailbox.include({ + + /** + * Overrides to add domain for 'failed' mailbox thread + * + * @override + */ + _getThreadDomain: function () { + if (this._id === 'mailbox_failed') { + return [ + ['mail_tracking_ids.state', 'in', FAILED_STATES], + ['mail_tracking_needs_action', '=', true], + '|', + ['partner_ids', 'in', [session.partner_id]], + ['author_id', '=', session.partner_id], + ]; + } + // Workaround to avoid throw 'Missing domain' exception. Call _super + // without a valid (hard-coded) thread id causes that exeception. + return this._super.apply(this, arguments); + }, + + /** + * Sets all messages from the mailbox as reviewed. + * + * At the moment, this method makes only sense for 'Failed'. + * + * @returns {$.Promise} resolved when all messages have been marked as + * reviewed on the server + */ + setAllMessagesAsReviewed: function () { + if (this._id === 'mailbox_failed' && this.getMailboxCounter() > 0) { + return this._rpc({ + model: 'mail.message', + method: 'set_all_as_reviewed', + }); + } + return $.when(); + }, + }); + +}); diff --git a/mail_tracking/static/src/js/failed_message/thread.js b/mail_tracking/static/src/js/failed_message/thread.js new file mode 100644 index 0000000..78e9ec6 --- /dev/null +++ b/mail_tracking/static/src/js/failed_message/thread.js @@ -0,0 +1,316 @@ +/* Copyright 2019 Alexandre Díaz + License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). */ +odoo.define('mail_tracking.FailedMessageThread', function (require) { + "use strict"; + + var AbstractField = require('web.AbstractField'); + var BasicModel = require('web.BasicModel'); + var BasicView = require('web.BasicView'); + var Chatter = require('mail.Chatter'); + var MailThread = require('mail.widget.Thread'); + var utils = require('mail.utils'); + var core = require('web.core'); + var field_registry = require('web.field_registry'); + var time = require('web.time'); + + var QWeb = core.qweb; + + + /** + * Helper method to fetch failed messages + * + * @private + * @param {Object} widget + * @param {Array} ids + * @returns {Array} + */ + function _readMessages (widget, ids) { + if (!ids.length) { + return $.when(); + } + var context = widget.record && widget.record.getContext(); + return widget._rpc({ + model: 'mail.message', + method: 'get_failed_messages', + args: [ids], + context: context || widget.getSession().user_context, + }).then(function (messages) { + // Convert date to moment + _.each(messages, function (msg) { + msg.date = moment(time.auto_str_to_date(msg.date)); + msg.hour = utils.timeFromNow(msg.date); + }); + return messages; + }); + } + + BasicModel.include({ + + /** + * Fetch data for the 'mail_failed_message' field widget in form views. + * + * @private + * @param {Object} record + * @param {String} fieldName + * @returns {Array} + */ + _fetchSpecialFailedMessages: function (record, fieldName) { + var localID = record._changes && fieldName in record._changes + ? record._changes[fieldName] : record.data[fieldName]; + return _readMessages(this, this.localData[localID].res_ids); + }, + }); + + var FailedMessage = AbstractField.extend({ + className: 'o_mail_failed_message', + events: { + 'click .o_failed_message_retry': '_onRetryFailedMessage', + 'click .o_failed_message_reviewed': '_onMarkFailedMessageReviewed', + }, + specialData: '_fetchSpecialFailedMessages', + + /** + * Overrides to reference failed messages in a easy way + * + * @override + */ + init: function () { + this._super.apply(this, arguments); + this.failed_messages = this.record.specialData[this.name] || []; + }, + + /** + * Overrides to listen bus notifications + * + * @override + */ + start: function () { + this._super.apply(this, arguments); + this.call( + 'bus_service', 'onNotification', this, this._onNotification); + }, + + /** + * Paremeters used to render widget + * + * @private + * @returns {Object} + */ + _failedItemsQWebParams: function () { + return { + failed_messages: this.failed_messages, + nbFailedMessages: this.failed_messages.length, + date_format: time.getLangDateFormat(), + datetime_format: time.getLangDatetimeFormat(), + }; + }, + + /** + * @private + */ + _render: function () { + if (this.failed_messages.length) { + this.$el.html(QWeb.render( + 'mail_tracking.failed_message_items', + this._failedItemsQWebParams())); + } else { + this.$el.empty(); + } + }, + + /** + * Reset widget data using selected record + * + * @private + * @param {Object} record + */ + _reset: function (record) { + this._super.apply(this, arguments); + this.failed_messages = this.record.specialData[this.name] || []; + this.res_id = record.res_id; + }, + + /** + * Trigger event to reload mail widgets + * + * @private + * @param {Array} fieldsToReload + */ + _reload: function (fieldsToReload) { + this.trigger_up('reload_mail_fields', fieldsToReload); + }, + + /** + * Mark failed message as reviewed + * + * @private + * @param {Int} id + * @returns {Promise} + */ + _markFailedMessageReviewed: function (id) { + return this._rpc({ + model: 'mail.message', + method: 'toggle_tracking_status', + args: [[id]], + context: this.record.getContext(), + }); + }, + + // Handlers + /** + * Listen bus notification to launch reload process. + * This bus notification is received when the user uses + * 'mail.resend.message' wizard. + * + * @private + * @param {Array} notifs + */ + _onNotification: function (notifs) { + var self = this; + _.each(notifs, function (notif) { + var model = notif[0][1]; + if (model === 'res.partner') { + var data = notif[1]; + if (data.type === 'update_failed_messages') { + // Reload 'mail_failed_message' widget + self._reload({failed_message: true}); + } + } + }); + }, + + /** + * Handle retry failed message event to open the mail.resend.message + * wizard. + * + * @private + * @param {Event} event + */ + _onRetryFailedMessage: function (event) { + event.preventDefault(); + var messageID = $(event.currentTarget).data('message-id'); + this.do_action('mail.mail_resend_message_action', { + additional_context: { + mail_message_to_resend: messageID, + }, + }); + }, + + /** + * Handle mark message as reviewed event + * + * @private + * @param {Event} event + */ + _onMarkFailedMessageReviewed: function (event) { + event.preventDefault(); + var messageID = $(event.currentTarget).data('message-id'); + this._markFailedMessageReviewed(messageID).then( + $.proxy(this, "_reload", {failed_message: true})); + }, + }); + + field_registry.add('mail_failed_message', FailedMessage); + + var mailWidgets = ['mail_failed_message']; + BasicView.include({ + + /** + * Overrides to add 'mail_failed_message' widget as "mail widget" used + * in Chatter. + * + * @override + */ + init: function () { + this._super.apply(this, arguments); + var fieldsInfo = this.fieldsInfo[this.viewType]; + for (var fieldName in fieldsInfo) { + var fieldInfo = fieldsInfo[fieldName]; + // Search fields using 'mail_failed_messsage' widget. + // Only one field can exists using the widget, the last + // found wins. + if (_.contains(mailWidgets, fieldInfo.widget)) { + // Add field as "mail field" shared with Chatter + this.mailFields[fieldInfo.widget] = fieldName; + // Avoid fetch x2many data, this will be done by widget + fieldInfo.__no_fetch = true; + } + } + // Update renderParmans mailFields to include the found field + // using 'mail_failed_messsage' widget. This info is used by the + // renderers [In Odoo vanilla by the form renderer to initialize + // Chatter widget]. + _.extend(this.rendererParams.mailFields, this.mailFields); + }, + }); + + Chatter.include({ + + /** + * Overrides to initialize 'mail_failed_message' widget. + * + * @override + */ + init: function (parent, record, mailFields, options) { + this._super.apply(this, arguments); + // Initialize mail_failed_message widget + if (mailFields.mail_failed_message) { + this.fields.failed_message = new FailedMessage( + this, mailFields.mail_failed_message, record, options); + } + }, + + /** + * Injects failed messages widget before the chatter + * + * @private + * @returns {Promise} + */ + _render: function () { + var self = this; + return this._super.apply(this, arguments).then(function () { + if (self.fields.failed_message) { + self.fields.failed_message.$el.insertBefore( + self.$el.find('.o_mail_thread')); + } + }); + }, + + /** + * Overrides to reload 'mail_failed_message' widget + * + * @override + */ + _onReloadMailFields: function (event) { + if (this.fields.failed_message && event.data.failed_message) { + this.trigger_up('reload', { + fieldNames: [this.fields.failed_message.name], + keepChanges: true, + }); + } else { + // Workaround to avoid trigger reload event twice (once for + // mail_failed_message and again with empty 'fieldNames'. + this._super.apply(this, arguments); + } + }, + }); + + MailThread.include({ + + /** + * Show 'retry' & 'Set as reviewed' buttons in the Chatter + * + * @override + */ + init: function () { + this._super.apply(this, arguments); + this._enabledOptions.displayRetryButton = true; + this._enabledOptions.displayReviewedButton = true; + this._disabledOptions.displayRetryButton = false; + this._disabledOptions.displayReviewedButton = false; + }, + }); + + return FailedMessage; + +}); diff --git a/mail_tracking/static/src/js/mail_tracking.js b/mail_tracking/static/src/js/mail_tracking.js index 3c98179..34148a7 100644 --- a/mail_tracking/static/src/js/mail_tracking.js +++ b/mail_tracking/static/src/js/mail_tracking.js @@ -2,7 +2,7 @@ Copyright 2018 David Vidal - License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). */ -odoo.define('mail_tracking.partner_tracking', function(require){ +odoo.define('mail_tracking.partner_tracking', function (require) { "use strict"; var core = require('web.core'); @@ -14,10 +14,11 @@ odoo.define('mail_tracking.partner_tracking', function(require){ var _t = core._t; AbstractMessage.include({ + /** * Messages do not have any PartnerTrackings. * - * @return {boolean} + * @returns {Boolean} */ hasPartnerTrackings: function () { return false; @@ -26,7 +27,7 @@ odoo.define('mail_tracking.partner_tracking', function(require){ /** * Messages do not have any email Cc values. * - * @return {boolean} + * @returns {Boolean} */ hasEmailCc: function () { return false; @@ -34,7 +35,7 @@ odoo.define('mail_tracking.partner_tracking', function(require){ }); Message.include({ - init: function (parent, data, emojis) { + init: function (parent, data) { this._super.apply(this, arguments); this._partnerTrackings = data.partner_trackings || []; this._emailCc = data.email_cc || []; @@ -45,7 +46,7 @@ odoo.define('mail_tracking.partner_tracking', function(require){ * State whether this message contains some PartnerTrackings values * * @override - * @return {boolean} + * @returns {Boolean} */ hasPartnerTrackings: function () { return _.some(this._partnerTrackings); @@ -54,7 +55,7 @@ odoo.define('mail_tracking.partner_tracking', function(require){ /** * State whether this message contains some email Cc values * - * @return {boolean} + * @returns {Boolean} */ hasEmailCc: function () { return _.some(this._emailCc); @@ -65,7 +66,7 @@ odoo.define('mail_tracking.partner_tracking', function(require){ * If this message has no PartnerTrackings values, returns [] * * @override - * @return {Object[]} + * @returns {Object[]} */ getPartnerTrackings: function () { if (!this.hasPartnerTrackings()) { @@ -78,7 +79,7 @@ odoo.define('mail_tracking.partner_tracking', function(require){ * Get the email Cc values of this message * If this message has no email Cc values, returns [] * - * @return {Array} + * @returns {Array} */ getEmailCc: function () { if (!this.hasEmailCc()) { @@ -91,7 +92,8 @@ odoo.define('mail_tracking.partner_tracking', function(require){ * Check if the email is an Cc * If this message has no email Cc values, returns false * - * @return {Boolean} + * @param {String} email + * @returns {Boolean} */ isEmailCc: function (email) { if (!this.hasEmailCc()) { @@ -104,31 +106,19 @@ odoo.define('mail_tracking.partner_tracking', function(require){ toggleTrackingStatus: function () { return this._rpc({ - model: 'mail.message', - method: 'toggle_tracking_status', - args: [[this.id]], - }); + model: 'mail.message', + method: 'toggle_tracking_status', + args: [[this.id]], + }); }, }); ThreadWidget.include({ events: _.extend(ThreadWidget.prototype.events, { - 'click .o_mail_action_tracking_partner': 'on_tracking_partner_click', + 'click .o_mail_action_tracking_partner': + 'on_tracking_partner_click', 'click .o_mail_action_tracking_status': 'on_tracking_status_click', }), - _preprocess_message: function () { - var msg = this._super.apply(this, arguments); - msg.partner_trackings = msg.partner_trackings || []; - var needs_action = msg.track_needs_action; - var message_track = _.findWhere(messages_tracked_changes, { - id: msg.id, - }); - if (message_track) { - needs_action = message_track.status; - } - msg.track_needs_action = needs_action; - return msg; - }, on_tracking_partner_click: function (event) { var partner_id = this.$el.find(event.currentTarget).data('partner'); var state = { @@ -171,7 +161,7 @@ odoo.define('mail_tracking.partner_tracking', function(require){ }, init: function () { this._super.apply(this, arguments); - this.action_manager = this.findAncestor(function(ancestor){ + this.action_manager = this.findAncestor(function (ancestor) { return ancestor instanceof ActionManager; }); }, diff --git a/mail_tracking/static/src/xml/client_action.xml b/mail_tracking/static/src/xml/client_action.xml deleted file mode 100644 index 28c3d35..0000000 --- a/mail_tracking/static/src/xml/client_action.xml +++ /dev/null @@ -1,31 +0,0 @@ - - diff --git a/mail_tracking/static/src/xml/failed_message.xml b/mail_tracking/static/src/xml/failed_message.xml deleted file mode 100644 index 367cb40..0000000 --- a/mail_tracking/static/src/xml/failed_message.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - -

-
-
- - -
-
-
-
- - - - -
- Failed Recipients: - - - - - - - - - - - -
-
- -
- -
-
- - - - -
- - - -
-
- - diff --git a/mail_tracking/static/src/xml/failed_message/common.xml b/mail_tracking/static/src/xml/failed_message/common.xml new file mode 100644 index 0000000..0961617 --- /dev/null +++ b/mail_tracking/static/src/xml/failed_message/common.xml @@ -0,0 +1,15 @@ + + + + + + + Set as Reviewed + + + Retry + + + + + diff --git a/mail_tracking/static/src/xml/failed_message/discuss.xml b/mail_tracking/static/src/xml/failed_message/discuss.xml new file mode 100644 index 0000000..45fb1f5 --- /dev/null +++ b/mail_tracking/static/src/xml/failed_message/discuss.xml @@ -0,0 +1,34 @@ + + + + + + + + + + +
+ Failed + + +
+
+ + + + +
Congratulations, you don't have any failed messages
+
Failed messages appear here.
+
+
+
+ + + + + + + +
diff --git a/mail_tracking/static/src/xml/failed_message/thread.xml b/mail_tracking/static/src/xml/failed_message/thread.xml new file mode 100644 index 0000000..85e008c --- /dev/null +++ b/mail_tracking/static/src/xml/failed_message/thread.xml @@ -0,0 +1,60 @@ + + + + + +
+ +
+
+
+ + +
+
+
+
+ + + + - + + + Set as Reviewed + + + Retry + + +
+ Failed Recipients: + + + - + + + + + +
+
+ +
+
+
+
+
+
+ +
diff --git a/mail_tracking/static/src/xml/mail_tracking.xml b/mail_tracking/static/src/xml/mail_tracking.xml index 3553d9f..5144d16 100644 --- a/mail_tracking/static/src/xml/mail_tracking.xml +++ b/mail_tracking/static/src/xml/mail_tracking.xml @@ -22,8 +22,9 @@ - - + + + @@ -51,7 +52,7 @@

To: - + - @@ -65,9 +66,11 @@ + + + t-att-title="title_status"> diff --git a/mail_tracking/tests/test_mail_tracking.py b/mail_tracking/tests/test_mail_tracking.py index 1659d6e..f9c6642 100644 --- a/mail_tracking/tests/test_mail_tracking.py +++ b/mail_tracking/tests/test_mail_tracking.py @@ -5,6 +5,8 @@ import mock from odoo.tools import mute_logger import time import base64 +import psycopg2 +import psycopg2.errorcodes from odoo import http from odoo.tests.common import TransactionCase from ..controllers.main import MailTrackingController, BLANK @@ -36,8 +38,9 @@ class TestMailTracking(TransactionCase): }) self.last_request = http.request http.request = type('obj', (object,), { - 'db': self.env.cr.dbname, 'env': self.env, + 'cr': self.env.cr, + 'db': self.env.cr.dbname, 'endpoint': type('obj', (object,), { 'routing': [], }), @@ -116,6 +119,30 @@ class TestMailTracking(TransactionCase): tracking_email.event_create('open', metadata) self.assertEqual(tracking_email.state, 'opened') + def test_message_post_partner_no_email(self): + # Create message with recipient without defined email + self.recipient.write({'email': False}) + message = self.env['mail.message'].create({ + 'subject': 'Message test', + 'author_id': self.sender.id, + 'email_from': self.sender.email, + 'message_type': 'comment', + 'model': 'res.partner', + 'res_id': self.recipient.id, + 'partner_ids': [(4, self.recipient.id)], + 'body': '

This is a test message

', + }) + message._notify(message, {}, force_send=True) + # Search tracking created + tracking_email = self.env['mail.tracking.email'].search([ + ('mail_message_id', '=', message.id), + ('partner_id', '=', self.recipient.id), + ]) + # No email should generate a error state: no_recipient + self.assertEqual(tracking_email.state, 'error') + self.assertEqual(tracking_email.error_type, 'no_recipient') + self.assertFalse(self.recipient.email_bounced) + def _check_partner_trackings(self, message): message_dict = message.message_format()[0] self.assertEqual(len(message_dict['partner_trackings']), 3) @@ -136,18 +163,15 @@ class TestMailTracking(TransactionCase): self.assertTrue(foundNoPartner) def test_email_cc(self): - message = self.env['mail.message'].create({ - 'subject': 'Message test', - 'author_id': self.sender.id, - 'email_from': self.sender.email, - 'message_type': 'comment', - 'model': 'res.partner', - 'res_id': self.recipient.id, - 'partner_ids': [(4, self.recipient.id)], - 'email_cc': 'unnamed@test.com, sender@example.com', - 'body': '

This is a test message

', + sender_user = self.env['res.users'].create({ + 'name': 'Sender User Test', + 'partner_id': self.sender.id, + 'login': 'sender-test', }) - self._check_partner_trackings(message) + message = self.recipient.sudo(user=sender_user).message_post( + body='

This is a test message

', + cc='unnamed@test.com, sender@example.com' + ) # suggested recipients recipients = self.recipient.message_get_suggested_recipients() suggested_mails = { @@ -168,31 +192,37 @@ class TestMailTracking(TransactionCase): ', recipient@example.com', 'body': '

This is another test message

', }) + message._notify(message, {}, force_send=True) recipients = self.recipient.message_get_suggested_recipients() self.assertEqual(len(recipients[self.recipient.id][0]), 3) self._check_partner_trackings(message) def test_failed_message(self): + MailMessageObj = self.env['mail.message'] # Create message mail, tracking = self.mail_send(self.recipient.email) self.assertFalse(tracking.mail_message_id.mail_tracking_needs_action) # Force error state tracking.state = 'error' self.assertTrue(tracking.mail_message_id.mail_tracking_needs_action) - failed_count = self.env['mail.message'].get_failed_count() + failed_count = MailMessageObj.get_failed_count() self.assertTrue(failed_count > 0) values = tracking.mail_message_id.get_failed_messages() self.assertEqual(values[0]['id'], tracking.mail_message_id.id) - messages = self.env['mail.message'].message_fetch([]) - messages_failed = self.env['mail.message'].with_context( - filter_failed_message=True).message_fetch([]) + messages = MailMessageObj.search([]) + messages_failed = MailMessageObj.search( + MailMessageObj._get_failed_message_domain()) self.assertTrue(messages) self.assertTrue(messages_failed) self.assertTrue(len(messages) > len(messages_failed)) tracking.mail_message_id.toggle_tracking_status() self.assertFalse(tracking.mail_message_id.mail_tracking_needs_action) self.assertTrue( - self.env['mail.message'].get_failed_count() < failed_count) + MailMessageObj.get_failed_count() < failed_count) + # No author_id + tracking.mail_message_id.author_id = False + values = tracking.mail_message_id.get_failed_messages()[0] + self.assertEqual(values['author'][0], -1) def mail_send(self, recipient): mail = self.env['mail.mail'].create({ @@ -215,15 +245,52 @@ class TestMailTracking(TransactionCase): mail, tracking = self.mail_send(self.recipient.email) self.assertEqual(mail.email_to, tracking.recipient) self.assertEqual(mail.email_from, tracking.sender) - res = controller.mail_tracking_open(db, tracking.id) - self.assertEqual(image, res.response[0]) - # Two events: sent and open - self.assertEqual(2, len(tracking.tracking_event_ids)) - # Fake event: tracking_email_id = False - res = controller.mail_tracking_open(db, False) - self.assertEqual(image, res.response[0]) - # Two events again because no tracking_email_id found for False - self.assertEqual(2, len(tracking.tracking_event_ids)) + with mock.patch('odoo.http.db_filter') as mock_client: + mock_client.return_value = True + res = controller.mail_tracking_open( + db, tracking.id, tracking.token) + self.assertEqual(image, res.response[0]) + # Two events: sent and open + self.assertEqual(2, len(tracking.tracking_event_ids)) + # Fake event: tracking_email_id = False + res = controller.mail_tracking_open(db, False, False) + self.assertEqual(image, res.response[0]) + # Two events again because no tracking_email_id found for False + self.assertEqual(2, len(tracking.tracking_event_ids)) + + def test_mail_tracking_open(self): + controller = MailTrackingController() + db = self.env.cr.dbname + with mock.patch('odoo.http.db_filter') as mock_client: + mock_client.return_value = True + mail, tracking = self.mail_send(self.recipient.email) + # Tracking is in sent or delivered state. But no token give. + # Don't generates tracking event + controller.mail_tracking_open(db, tracking.id) + self.assertEqual(1, len(tracking.tracking_event_ids)) + tracking.write({'state': 'opened'}) + # Tracking isn't in sent or delivered state. + # Don't generates tracking event + controller.mail_tracking_open(db, tracking.id, tracking.token) + self.assertEqual(1, len(tracking.tracking_event_ids)) + tracking.write({'state': 'sent'}) + # Tracking is in sent or delivered state and a token is given. + # Generates tracking event + controller.mail_tracking_open(db, tracking.id, tracking.token) + self.assertEqual(2, len(tracking.tracking_event_ids)) + # Generate new email due concurrent event filter + mail, tracking = self.mail_send(self.recipient.email) + tracking.write({'token': False}) + # Tracking is in sent or delivered state but a token is given for a + # record that doesn't have a token. + # Don't generates tracking event + controller.mail_tracking_open(db, tracking.id, 'tokentest') + self.assertEqual(1, len(tracking.tracking_event_ids)) + # Tracking is in sent or delivered state and not token is given for + # a record that doesn't have a token. + # Generates tracking event + controller.mail_tracking_open(db, tracking.id, False) + self.assertEqual(2, len(tracking.tracking_event_ids)) def test_concurrent_open(self): mail, tracking = self.mail_send(self.recipient.email) @@ -400,12 +467,14 @@ class TestMailTracking(TransactionCase): def test_db(self): db = self.env.cr.dbname controller = MailTrackingController() - not_found = controller.mail_tracking_all('not_found_db') - self.assertEqual(b'NOT FOUND', not_found.response[0]) - none = controller.mail_tracking_all(db) - self.assertEqual(b'NONE', none.response[0]) - none = controller.mail_tracking_event(db, 'open') - self.assertEqual(b'NONE', none.response[0]) + with mock.patch('odoo.http.db_filter') as mock_client: + mock_client.return_value = True + with self.assertRaises(psycopg2.OperationalError): + controller.mail_tracking_event('not_found_db') + none = controller.mail_tracking_event(db) + self.assertEqual(b'NONE', none.response[0]) + none = controller.mail_tracking_event(db, 'open') + self.assertEqual(b'NONE', none.response[0]) class TestMailTrackingViews(TransactionCase): diff --git a/mail_tracking/views/assets.xml b/mail_tracking/views/assets.xml index 72c66cd..5853ae1 100644 --- a/mail_tracking/views/assets.xml +++ b/mail_tracking/views/assets.xml @@ -7,13 +7,15 @@ inherit_id="web.assets_backend"> + href="/mail_tracking/static/src/css/mail_tracking.scss"/> + href="/mail_tracking/static/src/css/failed_message.scss"/>