When the message generates and ‘error’ status, it will apear on discuss ‘Failed’
+channel. Any view that uses ‘mail_thread’ widget can show the failed messages
+too.
+
+<<<<<<< HEAD
+=======
+
+>>>>>>> 75b9662... [IMP] mail_tracking: Failed Messages (Discuss & View)
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose
diff --git a/mail_tracking/static/img/failed_message_discuss.png b/mail_tracking/static/img/failed_message_discuss.png
new file mode 100644
index 000000000..a810ee3a9
Binary files /dev/null and b/mail_tracking/static/img/failed_message_discuss.png differ
diff --git a/mail_tracking/static/img/failed_message_widget.png b/mail_tracking/static/img/failed_message_widget.png
new file mode 100644
index 000000000..794bce784
Binary files /dev/null and b/mail_tracking/static/img/failed_message_widget.png differ
diff --git a/mail_tracking/static/src/css/failed_message.less b/mail_tracking/static/src/css/failed_message.less
new file mode 100644
index 000000000..7457ccb10
--- /dev/null
+++ b/mail_tracking/static/src/css/failed_message.less
@@ -0,0 +1,108 @@
+/* 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/mail_tracking.css b/mail_tracking/static/src/css/mail_tracking.css
deleted file mode 100644
index 9f372225b..000000000
--- a/mail_tracking/static/src/css/mail_tracking.css
+++ /dev/null
@@ -1,21 +0,0 @@
-/* Copyright 2016 Antonio Espinosa -
- License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). */
-
-.mail_tracking span {
- color: #909090;
-}
-.mail_tracking_pointer {
- cursor: pointer;
-}
-
-.mail_tracking span.mail_tracking_opened {
- color: #a34a8b;
-}
-
-.o_mail_thread .o_thread_message .o_thread_message_core .o_mail_info {
- margin: 0;
-}
-
-.o_mail_thread .o_thread_message .o_thread_message_core .o_mail_tracking {
- margin: 0 0 2px 0;
-}
diff --git a/mail_tracking/static/src/css/mail_tracking.less b/mail_tracking/static/src/css/mail_tracking.less
new file mode 100644
index 000000000..4db2930b8
--- /dev/null
+++ b/mail_tracking/static/src/css/mail_tracking.less
@@ -0,0 +1,25 @@
+/* Copyright 2016 Antonio Espinosa -
+ Copyright 2019 Alexandre Díaz
+ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). */
+
+.mail_tracking {
+ span {
+ color: @odoo-color-0;
+
+ &.mail_tracking_opened {
+ color: @odoo-color-5;
+ }
+ }
+}
+.mail_tracking_pointer {
+ cursor: pointer;
+}
+
+.o_mail_thread .o_thread_message .o_thread_message_core {
+ .o_mail_info {
+ margin: 0;
+ }
+ .o_mail_tracking {
+ margin: 0 0 0.2rem 0;
+ }
+}
diff --git a/mail_tracking/static/src/js/failed_message.js b/mail_tracking/static/src/js/failed_message.js
new file mode 100644
index 000000000..bdd9d747c
--- /dev/null
+++ b/mail_tracking/static/src/js/failed_message.js
@@ -0,0 +1,365 @@
+/* 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/mail_tracking.js b/mail_tracking/static/src/js/mail_tracking.js
index 745de41c7..84f0bed2f 100644
--- a/mail_tracking/static/src/js/mail_tracking.js
+++ b/mail_tracking/static/src/js/mail_tracking.js
@@ -38,6 +38,7 @@ odoo.define('mail_tracking.partner_tracking', function(require){
this._super.apply(this, arguments);
this._partnerTrackings = data.partner_trackings || [];
this._emailCc = data.email_cc || [];
+ this._trackNeedsAction = data.track_needs_action || false;
},
/**
@@ -100,6 +101,14 @@ odoo.define('mail_tracking.partner_tracking', function(require){
return item[0] === email;
});
},
+
+ toggleTrackingStatus: function () {
+ return this._rpc({
+ model: 'mail.message',
+ method: 'toggle_tracking_status',
+ args: [[this.id]],
+ });
+ },
});
ThreadWidget.include({
@@ -107,6 +116,20 @@ odoo.define('mail_tracking.partner_tracking', function(require){
'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 || [];
+ msg.email_cc = msg.email_cc || [];
+ 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 = {
@@ -147,7 +170,7 @@ odoo.define('mail_tracking.partner_tracking', function(require){
};
this.do_action(action);
},
- init: function (parent, options) {
+ init: function () {
this._super.apply(this, arguments);
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
new file mode 100644
index 000000000..28c3d353b
--- /dev/null
+++ b/mail_tracking/static/src/xml/client_action.xml
@@ -0,0 +1,31 @@
+
+
+
+
+ Outdated
+
+
+
+
+
+
+
+
+ Failed
+
+
+
+
+
+
+
+
+
+ Congratulations, doesn't have failed messages
+ Failed messages appear here.
+
+
+
+
+
diff --git a/mail_tracking/static/src/xml/failed_message.xml b/mail_tracking/static/src/xml/failed_message.xml
new file mode 100644
index 000000000..367cb4098
--- /dev/null
+++ b/mail_tracking/static/src/xml/failed_message.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+ -
+
Failed Recipients:
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Failed messages
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mail_tracking/tests/test_mail_tracking.py b/mail_tracking/tests/test_mail_tracking.py
index d0f1e02a7..b1a48d613 100644
--- a/mail_tracking/tests/test_mail_tracking.py
+++ b/mail_tracking/tests/test_mail_tracking.py
@@ -8,6 +8,7 @@ import base64
from odoo import http
from odoo.tests.common import TransactionCase
from ..controllers.main import MailTrackingController, BLANK
+from lxml import etree
mock_send_email = ('odoo.addons.base.models.ir_mail_server.'
'IrMailServer.send_email')
@@ -159,6 +160,28 @@ class TestMailTracking(TransactionCase):
recipients = self.recipient.message_get_suggested_recipients()
self.assertEqual(len(recipients[self.recipient.id][0]), 3)
+ def test_failed_message(self):
+ # 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()
+ 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([])
+ 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)
+
def mail_send(self, recipient):
mail = self.env['mail.mail'].create({
'subject': 'Test subject',
@@ -371,3 +394,21 @@ class TestMailTracking(TransactionCase):
self.assertEqual(b'NONE', none.response[0])
none = controller.mail_tracking_event(db, 'open')
self.assertEqual(b'NONE', none.response[0])
+
+
+class TestMailTrackingViews(TransactionCase):
+ def test_fields_view_get(self):
+ result = self.env['res.partner'].fields_view_get(
+ view_id=self.env.ref('base.view_partner_form').id,
+ view_type='form')
+ doc = etree.XML(result['arch'])
+ nodes = doc.xpath(
+ "//field[@name='failed_message_ids'"
+ " and @widget='mail_failed_message']")
+ self.assertTrue(nodes)
+ result = self.env['res.partner'].fields_view_get(
+ view_id=self.env.ref('base.view_res_partner_filter').id,
+ view_type='search')
+ doc = etree.XML(result['arch'])
+ nodes = doc.xpath("//filter[@name='failed_message_ids']")
+ self.assertTrue(nodes)
diff --git a/mail_tracking/views/assets.xml b/mail_tracking/views/assets.xml
index 1b9875607..72c66cd9a 100644
--- a/mail_tracking/views/assets.xml
+++ b/mail_tracking/views/assets.xml
@@ -7,9 +7,13 @@
inherit_id="web.assets_backend">
+ href="/mail_tracking/static/src/css/mail_tracking.less"/>
+
+
diff --git a/mail_tracking/views/mail_message_view.xml b/mail_tracking/views/mail_message_view.xml
new file mode 100644
index 000000000..44c7fae0a
--- /dev/null
+++ b/mail_tracking/views/mail_message_view.xml
@@ -0,0 +1,14 @@
+
+
+
+
+ mail.message
+
+
+
+
+
+
+
+
+
diff --git a/mail_tracking/wizard/mail_compose_message_view.xml b/mail_tracking/wizard/mail_compose_message_view.xml
new file mode 100644
index 000000000..2e6faf12b
--- /dev/null
+++ b/mail_tracking/wizard/mail_compose_message_view.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+ mail.compose.message
+
+
+
+
+
+
+ {'invisible':['|', '|', ('model', '=', False), ('composition_mode', '=', 'mass_mail'), ('hide_followers', '=', True)]}
+
+
+
+
+
+