Maintainers
-======= -Maintainers
->>>>>>> 75b9662... [IMP] mail_tracking: Failed Messages (Discuss & View) +Maintainers
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_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 -
To:
- This is a test message This is a test message This is a test message This is another test message