[MIG] mail_tracking: Migration to 15.0

This commit is contained in:
Jasmin Solanki 2022-03-25 17:01:13 +05:30
parent 021689d073
commit 680eee8074
27 changed files with 810 additions and 815 deletions

View File

@ -7,7 +7,7 @@
{ {
"name": "Email tracking", "name": "Email tracking",
"summary": "Email tracking system for all mails sent", "summary": "Email tracking system for all mails sent",
"version": "14.0.2.0.2", "version": "15.0.1.0.0",
"category": "Social Network", "category": "Social Network",
"website": "https://github.com/OCA/social", "website": "https://github.com/OCA/social",
"author": ("Tecnativa, " "Odoo Community Association (OCA)"), "author": ("Tecnativa, " "Odoo Community Association (OCA)"),
@ -19,18 +19,30 @@
"data/tracking_data.xml", "data/tracking_data.xml",
"security/mail_tracking_email_security.xml", "security/mail_tracking_email_security.xml",
"security/ir.model.access.csv", "security/ir.model.access.csv",
"views/assets.xml",
"views/mail_tracking_email_view.xml", "views/mail_tracking_email_view.xml",
"views/mail_tracking_event_view.xml", "views/mail_tracking_event_view.xml",
"views/mail_message_view.xml", "views/mail_message_view.xml",
"views/res_partner_view.xml", "views/res_partner_view.xml",
], ],
"qweb": [ "assets": {
"static/src/xml/mail_tracking.xml", "web.assets_backend": [
"static/src/xml/failed_message/common.xml", "mail_tracking/static/src/css/mail_tracking.scss",
"static/src/xml/failed_message/thread.xml", "mail_tracking/static/src/css/failed_message.scss",
"static/src/xml/failed_message/discuss.xml", "mail_tracking/static/src/js/mail_tracking.esm.js",
], "mail_tracking/static/src/js/message.esm.js",
"mail_tracking/static/src/js/failed_message/mail_failed_box.esm.js",
"mail_tracking/static/src/js/failed_message/thread.esm.js",
"mail_tracking/static/src/js/models/thread.esm.js",
"mail_tracking/static/src/js/chatter.esm.js",
"mail_tracking/static/src/js/discuss/discuss.esm.js",
],
"web.assets_qweb": [
"mail_tracking/static/src/xml/mail_tracking.xml",
"mail_tracking/static/src/xml/failed_message/common.xml",
"mail_tracking/static/src/xml/failed_message/thread.xml",
"mail_tracking/static/src/xml/failed_message/discuss.xml",
],
},
"demo": ["demo/demo.xml"], "demo": ["demo/demo.xml"],
"pre_init_hook": "pre_init_hook", "pre_init_hook": "pre_init_hook",
} }

View File

@ -10,7 +10,8 @@ import werkzeug
import odoo import odoo
from odoo import SUPERUSER_ID, api, http from odoo import SUPERUSER_ID, api, http
from odoo.addons.mail.controllers.main import MailController from odoo.addons.mail.controllers.discuss import DiscussController
from odoo.addons.mail.controllers.mail import MailController
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@ -26,11 +27,10 @@ def db_env(dbname):
cr = http.request.cr cr = http.request.cr
if not cr: if not cr:
cr = odoo.sql_db.db_connect(dbname).cursor() cr = odoo.sql_db.db_connect(dbname).cursor()
with api.Environment.manage(): yield api.Environment(cr, SUPERUSER_ID, {})
yield api.Environment(cr, SUPERUSER_ID, {})
class MailTrackingController(MailController): class MailTrackingController(MailController, DiscussController):
def _request_metadata(self): def _request_metadata(self):
"""Prepare remote info metadata""" """Prepare remote info metadata"""
request = http.request.httprequest request = http.request.httprequest
@ -59,8 +59,8 @@ class MailTrackingController(MailController):
res = env["mail.tracking.email"].event_process( res = env["mail.tracking.email"].event_process(
http.request, kw, metadata, event_type=event_type http.request, kw, metadata, event_type=event_type
) )
except Exception: except Exception as e:
pass _logger.warning(e)
if not res or res == "NOT FOUND": if not res or res == "NOT FOUND":
return werkzeug.exceptions.NotAcceptable() return werkzeug.exceptions.NotAcceptable()
return res return res
@ -89,8 +89,8 @@ class MailTrackingController(MailController):
) )
elif tracking_email.state in ("sent", "delivered"): elif tracking_email.state in ("sent", "delivered"):
tracking_email.event_create("open", metadata) tracking_email.event_create("open", metadata)
except Exception: except Exception as e:
pass _logger.warning(e)
# Always return GIF blank image # Always return GIF blank image
response = werkzeug.wrappers.Response() response = werkzeug.wrappers.Response()
@ -106,3 +106,12 @@ class MailTrackingController(MailController):
{"failed_counter": http.request.env["mail.message"].get_failed_count()} {"failed_counter": http.request.env["mail.message"].get_failed_count()}
) )
return values return values
@http.route("/mail/failed/messages", methods=["POST"], type="json", auth="user")
def discuss_failed_messages(self, max_id=None, min_id=None, limit=30, **kwargs):
return http.request.env["mail.message"]._message_fetch(
domain=[("is_failed_message", "=", True)],
max_id=max_id,
min_id=min_id,
limit=limit,
)

View File

@ -1,7 +1,5 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<data>
<!-- Message with CC --> <!-- Message with CC -->
<record id="mail_message_cc" model="mail.message"> <record id="mail_message_cc" model="mail.message">
<field name="model">res.partner</field> <field name="model">res.partner</field>
@ -9,17 +7,17 @@
<field name="message_type">comment</field> <field name="message_type">comment</field>
<field name="subtype_id" ref="mail.mt_comment" /> <field name="subtype_id" ref="mail.mt_comment" />
<field <field
name="email_cc" name="email_cc"
>acc@testmail.com,wood.corner26@example.com,toni.rhodes11@example.com</field> >acc@testmail.com,wood.corner26@example.com,toni.rhodes11@example.com</field>
<field name="mail_tracking_needs_action">1</field> <field name="mail_tracking_needs_action">1</field>
<field name="body"><![CDATA[<p>This is a message with CC</p>]]></field> <field name="body"><![CDATA[<p>This is a message with CC</p>]]></field>
<field name="email_from">wood.corner26@example.com</field> <field name="email_from">wood.corner26@example.com</field>
<field name="author_id" ref="base.res_partner_1" /> <field name="author_id" ref="base.res_partner_1" />
<field name="partner_ids" eval="[(6, 0, [ref('base.partner_demo')])]" /> <field name="partner_ids" eval="[(6, 0, [ref('base.partner_demo')])]" />
<field <field
name="notification_ids" name="notification_ids"
eval="[(0, 0, {'res_partner_id': ref('base.partner_demo')})]" eval="[(0, 0, {'res_partner_id': ref('base.partner_demo')})]"
/> />
<field name="subject">Message with CC</field> <field name="subject">Message with CC</field>
</record> </record>
@ -45,9 +43,9 @@
<field name="author_id" ref="base.res_partner_1" /> <field name="author_id" ref="base.res_partner_1" />
<field name="partner_ids" eval="[(6, 0, [ref('base.partner_demo')])]" /> <field name="partner_ids" eval="[(6, 0, [ref('base.partner_demo')])]" />
<field <field
name="notification_ids" name="notification_ids"
eval="[(0, 0, {'res_partner_id': ref('base.partner_demo')})]" eval="[(0, 0, {'res_partner_id': ref('base.partner_demo')})]"
/> />
<field name="subject">Failed Message</field> <field name="subject">Failed Message</field>
</record> </record>
@ -73,9 +71,9 @@
<field name="author_id" ref="base.res_partner_10" /> <field name="author_id" ref="base.res_partner_10" />
<field name="partner_ids" eval="[(6, 0, [ref('base.partner_demo')])]" /> <field name="partner_ids" eval="[(6, 0, [ref('base.partner_demo')])]" />
<field <field
name="notification_ids" name="notification_ids"
eval="[(0, 0, {'res_partner_id': ref('base.partner_demo')})]" eval="[(0, 0, {'res_partner_id': ref('base.partner_demo')})]"
/> />
<field name="subject">Failed Message</field> <field name="subject">Failed Message</field>
</record> </record>
@ -101,9 +99,9 @@
<field name="author_id" ref="base.partner_admin" /> <field name="author_id" ref="base.partner_admin" />
<field name="partner_ids" eval="[(6, 0, [ref('base.partner_demo')])]" /> <field name="partner_ids" eval="[(6, 0, [ref('base.partner_demo')])]" />
<field <field
name="notification_ids" name="notification_ids"
eval="[(0, 0, {'res_partner_id': ref('base.partner_demo')})]" eval="[(0, 0, {'res_partner_id': ref('base.partner_demo')})]"
/> />
<field name="subject">Failed Message</field> <field name="subject">Failed Message</field>
</record> </record>
@ -116,6 +114,4 @@
<field name="state">error</field> <field name="state">error</field>
<field name="time" eval="DateTime.today().strftime('%Y-%m-%d %H:%M')" /> <field name="time" eval="DateTime.today().strftime('%Y-%m-%d %H:%M')" />
</record> </record>
</data>
</odoo> </odoo>

View File

@ -224,9 +224,9 @@ class MailMessage(models.Model):
return list(filter(_filter_alias, mail_list)) return list(filter(_filter_alias, mail_list))
def message_format(self): def message_format(self, format_reply=True):
"""Preare values to be used by the chatter widget""" """Preare values to be used by the chatter widget"""
res = super().message_format() res = super().message_format(format_reply)
mail_message_ids = {m.get("id") for m in res if m.get("id")} mail_message_ids = {m.get("id") for m in res if m.get("id")}
mail_messages = self.browse(mail_message_ids) mail_messages = self.browse(mail_message_ids)
tracking_statuses = mail_messages.tracking_status() tracking_statuses = mail_messages.tracking_status()
@ -273,13 +273,8 @@ class MailMessage(models.Model):
""" """
self.check_access_rule("read") self.check_access_rule("read")
self.write({"mail_tracking_needs_action": False}) self.write({"mail_tracking_needs_action": False})
notification = { self.env["bus.bus"]._sendone(
"type": "toggle_tracking_status", self.env.user.partner_id, "toggle_tracking_status", self.ids
"message_ids": self.ids,
"needs_actions": False,
}
self.env["bus.bus"].sendone(
(self._cr.dbname, "res.partner", self.env.user.partner_id.id), notification
) )
@api.model @api.model

View File

@ -49,13 +49,9 @@ class MailResendMessage(models.TransientModel):
tracking_ids.sudo().write({"state": False}) tracking_ids.sudo().write({"state": False})
# Send bus notifications to update Discuss and # Send bus notifications to update Discuss and
# mail_failed_messages widget # mail_failed_messages widget
notification = { self.env["bus.bus"]._sendone(
"type": "toggle_tracking_status", self.env.user.partner_id.id,
"message_ids": [self.mail_message_id.id], "toggle_tracking_status",
"needs_actions": False, self.mail_message_id.id,
}
self.env["bus.bus"].sendone(
(self._cr.dbname, "res.partner", self.env.user.partner_id.id),
notification,
) )
super().resend_mail_action() return super().resend_mail_action()

View File

@ -30,7 +30,6 @@ class MailTrackingEmail(models.Model):
# - state: Search and group_by in tree view # - state: Search and group_by in tree view
name = fields.Char(string="Subject", readonly=True, index=True) name = fields.Char(string="Subject", readonly=True, index=True)
display_name = fields.Char( display_name = fields.Char(
string="Display name",
readonly=True, readonly=True,
store=True, store=True,
compute="_compute_tracking_display_name", compute="_compute_tracking_display_name",
@ -38,10 +37,8 @@ class MailTrackingEmail(models.Model):
timestamp = fields.Float( timestamp = fields.Float(
string="UTC timestamp", readonly=True, digits="MailTracking Timestamp" string="UTC timestamp", readonly=True, digits="MailTracking Timestamp"
) )
time = fields.Datetime(string="Time", readonly=True, index=True) time = fields.Datetime(readonly=True, index=True)
date = fields.Date( date = fields.Date(readonly=True, compute="_compute_date", store=True)
string="Date", readonly=True, compute="_compute_date", store=True
)
mail_message_id = fields.Many2one( mail_message_id = fields.Many2one(
string="Message", comodel_name="mail.message", readonly=True, index=True string="Message", comodel_name="mail.message", readonly=True, index=True
) )
@ -71,7 +68,6 @@ class MailTrackingEmail(models.Model):
("bounced", "Bounced"), ("bounced", "Bounced"),
("soft-bounced", "Soft bounced"), ("soft-bounced", "Soft bounced"),
], ],
string="State",
index=True, index=True,
readonly=True, readonly=True,
default=False, default=False,
@ -97,10 +93,10 @@ class MailTrackingEmail(models.Model):
"bounced by recipient Mail Exchange (MX) server.\n", "bounced by recipient Mail Exchange (MX) server.\n",
) )
error_smtp_server = fields.Char(string="Error SMTP server", readonly=True) error_smtp_server = fields.Char(string="Error SMTP server", readonly=True)
error_type = fields.Char(string="Error type", readonly=True) error_type = fields.Char(readonly=True)
error_description = fields.Char(string="Error description", readonly=True) error_description = fields.Char(readonly=True)
bounce_type = fields.Char(string="Bounce type", readonly=True) bounce_type = fields.Char(readonly=True)
bounce_description = fields.Char(string="Bounce description", readonly=True) bounce_description = fields.Char(readonly=True)
tracking_event_ids = fields.One2many( tracking_event_ids = fields.One2many(
string="Tracking events", string="Tracking events",
comodel_name="mail.tracking.event", comodel_name="mail.tracking.event",
@ -126,10 +122,11 @@ class MailTrackingEmail(models.Model):
return records return records
def write(self, vals): def write(self, vals):
super().write(vals) res = super().write(vals)
state = vals.get("state") state = vals.get("state")
if state and state in self.env["mail.message"].get_failed_states(): if state and state in self.env["mail.message"].get_failed_states():
self.mapped("mail_message_id").write({"mail_tracking_needs_action": True}) self.mapped("mail_message_id").write({"mail_tracking_needs_action": True})
return res
@api.model @api.model
def email_is_bounced(self, email): def email_is_bounced(self, email):

View File

@ -14,7 +14,7 @@ class MailTrackingEvent(models.Model):
_rec_name = "event_type" _rec_name = "event_type"
_description = "MailTracking event" _description = "MailTracking event"
recipient = fields.Char(string="Recipient", readonly=True) recipient = fields.Char(readonly=True)
recipient_address = fields.Char( recipient_address = fields.Char(
string="Recipient email address", string="Recipient email address",
readonly=True, readonly=True,
@ -25,10 +25,8 @@ class MailTrackingEvent(models.Model):
timestamp = fields.Float( timestamp = fields.Float(
string="UTC timestamp", readonly=True, digits="MailTracking Timestamp" string="UTC timestamp", readonly=True, digits="MailTracking Timestamp"
) )
time = fields.Datetime(string="Time", readonly=True) time = fields.Datetime(readonly=True)
date = fields.Date( date = fields.Date(readonly=True, compute="_compute_date", store=True)
string="Date", readonly=True, compute="_compute_date", store=True
)
tracking_email_id = fields.Many2one( tracking_email_id = fields.Many2one(
string="Message", string="Message",
readonly=True, readonly=True,
@ -38,7 +36,6 @@ class MailTrackingEvent(models.Model):
index=True, index=True,
) )
event_type = fields.Selection( event_type = fields.Selection(
string="Event type",
selection=[ selection=[
("sent", "Sent"), ("sent", "Sent"),
("delivered", "Delivered"), ("delivered", "Delivered"),
@ -56,7 +53,7 @@ class MailTrackingEvent(models.Model):
smtp_server = fields.Char(string="SMTP server", readonly=True) smtp_server = fields.Char(string="SMTP server", readonly=True)
url = fields.Char(string="Clicked URL", readonly=True) url = fields.Char(string="Clicked URL", readonly=True)
ip = fields.Char(string="User IP", readonly=True) ip = fields.Char(string="User IP", readonly=True)
user_agent = fields.Char(string="User agent", readonly=True) user_agent = fields.Char(readonly=True)
mobile = fields.Boolean(string="Is mobile?", readonly=True) mobile = fields.Boolean(string="Is mobile?", readonly=True)
os_family = fields.Char(string="Operating system family", readonly=True) os_family = fields.Char(string="Operating system family", readonly=True)
ua_family = fields.Char(string="User agent family", readonly=True) ua_family = fields.Char(string="User agent family", readonly=True)
@ -64,9 +61,9 @@ class MailTrackingEvent(models.Model):
user_country_id = fields.Many2one( user_country_id = fields.Many2one(
string="User country", readonly=True, comodel_name="res.country" string="User country", readonly=True, comodel_name="res.country"
) )
error_type = fields.Char(string="Error type", readonly=True) error_type = fields.Char(readonly=True)
error_description = fields.Char(string="Error description", readonly=True) error_description = fields.Char(readonly=True)
error_details = fields.Text(string="Error details", readonly=True) error_details = fields.Text(readonly=True)
@api.depends("recipient") @api.depends("recipient")
def _compute_recipient_address(self): def _compute_recipient_address(self):

View File

@ -0,0 +1,32 @@
/** @odoo-module **/
import {attr} from "@mail/model/model_field";
import {
registerFieldPatchModel,
registerInstancePatchModel,
} from "@mail/model/model_core";
registerInstancePatchModel(
"mail.chatter",
"mail/static/src/models/chatter/chatter.js",
{
async refresh() {
this._super(...arguments);
this.thread.refreshMessagefailed();
},
toggleMessageFailedBoxVisibility() {
this.update({
isMessageFailedBoxVisible: !this.isMessageFailedBoxVisible,
});
},
_onThreadIdOrThreadModelChanged() {
this._super(...arguments);
this.thread.refreshMessagefailed();
},
}
);
registerFieldPatchModel("mail.chatter", "mail/static/src/models/chatter/chatter.js", {
isMessageFailedBoxVisible: attr({
default: true,
}),
});

View File

@ -1,38 +0,0 @@
odoo.define("mail_tracking/static/src/js/chatter.js", function (require) {
"use strict";
const {attr} = require("mail/static/src/model/model_field.js");
const {
registerInstancePatchModel,
registerFieldPatchModel,
} = require("mail/static/src/model/model_core.js");
registerInstancePatchModel(
"mail.chatter",
"mail/static/src/models/chatter/chatter.js",
{
async refresh() {
this._super(...arguments);
this.thread.refreshMessagefailed();
},
toggleMessageFailedBoxVisibility() {
this.update({
isMessageFailedBoxVisible: !this.isMessageFailedBoxVisible,
});
},
_onThreadIdOrThreadModelChanged() {
this._super(...arguments);
this.thread.refreshMessagefailed();
},
}
);
registerFieldPatchModel(
"mail.chatter",
"mail/static/src/models/chatter/chatter.js",
{
isMessageFailedBoxVisible: attr({
default: true,
}),
}
);
});

View File

@ -0,0 +1,156 @@
/** @odoo-module **/
import {attr, many2one, one2one} from "@mail/model/model_field";
import {insertAndReplace, replace} from "@mail/model/model_field_command";
import {
registerClassPatchModel,
registerFieldPatchModel,
registerInstancePatchModel,
} from "@mail/model/model_core";
registerInstancePatchModel(
"mail.messaging_initializer",
"mail/static/src/models/messaging_initializer/messaging_initializer.js",
{
async start() {
this.messaging.update({
failedmsg: insertAndReplace({
id: "failedmsg",
isServerPinned: true,
model: "mail.box",
name: this.env._t("Failed"),
}),
});
return this._super(...arguments);
},
async _init({
channels,
companyName,
current_partner,
currentGuest,
current_user_id,
current_user_settings,
mail_failures = [],
menu_id,
needaction_inbox_counter = 0,
partner_root,
public_partners,
shortcodes = [],
starred_counter = 0,
failed_counter = 0,
}) {
const discuss = this.messaging.discuss;
// Partners first because the rest of the code relies on them
this._initPartners({
currentGuest,
current_partner,
current_user_id,
partner_root,
public_partners,
});
// Mailboxes after partners and before other initializers that might
// manipulate threads or messages
this._initMailboxes({
needaction_inbox_counter,
starred_counter,
failed_counter,
});
// Init mail user settings
if (current_user_settings) {
this._initResUsersSettings(current_user_settings);
} else {
this.messaging.update({
userSetting: insertAndReplace({
id: -1, // Fake id for guest
}),
});
}
// Various suggestions in no particular order
this._initCannedResponses(shortcodes);
// FIXME: guests should have (at least some) commands available
if (!this.messaging.isCurrentUserGuest) {
this._initCommands();
}
// Channels when the rest of messaging is ready
await this.async(() => this._initChannels(channels));
// Failures after channels
this._initMailFailures(mail_failures);
discuss.update({menu_id});
// Company related data
this.messaging.update({companyName});
},
_initMailboxes({needaction_inbox_counter, starred_counter, failed_counter}) {
this.messaging.inbox.update({counter: needaction_inbox_counter});
this.messaging.starred.update({counter: starred_counter});
this.messaging.failedmsg.update({counter: failed_counter});
},
}
);
registerFieldPatchModel(
"mail.messaging",
"mail/static/src/models/messaging/messaging.js",
{
failedmsg: one2one("mail.thread"),
}
);
registerInstancePatchModel(
"mail.thread_cache",
"mail/static/src/models/thread_cache/thread_cache.js",
{
_extendMessageDomain(domain) {
const thread = this.thread;
if (thread === this.env.messaging.failedmsg) {
return domain.concat([["is_failed_message", "=", true]]);
}
return this._super(...arguments);
},
}
);
registerFieldPatchModel("mail.message", "mail/static/src/models/message/message.js", {
messagingFailedmsg: many2one("mail.thread", {
related: "messaging.failedmsg",
}),
isFailed: attr({
default: false,
}),
});
registerClassPatchModel("mail.message", "mail/static/src/models/message/message.js", {
convertData(data) {
const data2 = this._super(data);
if ("is_failed_message" in data) {
data2.isFailed = data.is_failed_message;
}
return data2;
},
});
registerInstancePatchModel(
"mail.message",
"mail/static/src/models/message/message.js",
{
_computeThreads() {
const threads = [];
if (this.isHistory && this.messaging.history) {
threads.push(this.messaging.history);
}
if (this.isNeedaction && this.messaging.inbox) {
threads.push(this.messaging.inbox);
}
if (this.isStarred && this.messaging.starred) {
threads.push(this.messaging.starred);
}
if (this.isFailed && this.messaging.failedmsg) {
threads.push(this.messaging.failedmsg);
}
if (this.originThread) {
threads.push(this.originThread);
}
return replace(threads);
},
}
);

View File

@ -1,148 +0,0 @@
odoo.define("mail_tracking/static/src/js/discuss/discuss.js", function (require) {
"use strict";
const {attr} = require("mail/static/src/model/model_field.js");
const {
registerInstancePatchModel,
registerFieldPatchModel,
registerClassPatchModel,
} = require("mail/static/src/model/model_core.js");
const {one2one, many2one} = require("mail/static/src/model/model_field.js");
registerInstancePatchModel(
"mail.messaging_initializer",
"mail/static/src/models/messaging_initializer/messaging_initializer.js",
{
async start() {
this.messaging.update({
failedmsg: [
[
"create",
{
id: "failedmsg",
isServerPinned: true,
model: "mail.box",
name: this.env._t("Failed"),
},
],
],
});
return this._super(...arguments);
},
async _init({
channel_slots,
commands = [],
current_partner,
current_user_id,
mail_failures = {},
mention_partner_suggestions = [],
menu_id,
moderation_channel_ids = [],
moderation_counter = 0,
needaction_inbox_counter = 0,
partner_root,
public_partner,
public_partners,
shortcodes = [],
starred_counter = 0,
failed_counter = 0,
}) {
const discuss = this.messaging.discuss;
// Partners first because the rest of the code relies on them
this._initPartners({
current_partner,
current_user_id,
moderation_channel_ids,
partner_root,
public_partner,
public_partners,
});
// Mailboxes after partners and before other initializers that might
// manipulate threads or messages
this._initMailboxes({
moderation_channel_ids,
moderation_counter,
needaction_inbox_counter,
starred_counter,
failed_counter,
});
// Various suggestions in no particular order
this._initCannedResponses(shortcodes);
this._initCommands(commands);
this._initMentionPartnerSuggestions(mention_partner_suggestions);
// Channels when the rest of messaging is ready
await this.async(() => this._initChannels(channel_slots));
// Failures after channels
this._initMailFailures(mail_failures);
discuss.update({menu_id});
},
_initMailboxes({
moderation_channel_ids,
moderation_counter,
needaction_inbox_counter,
starred_counter,
failed_counter,
}) {
this.env.messaging.inbox.update({counter: needaction_inbox_counter});
this.env.messaging.starred.update({counter: starred_counter});
this.env.messaging.failedmsg.update({counter: failed_counter});
if (moderation_channel_ids.length > 0) {
this.messaging.moderation.update({
counter: moderation_counter,
isServerPinned: true,
});
}
},
}
);
registerFieldPatchModel(
"mail.messaging",
"mail/static/src/models/messaging/messaging.js",
{
failedmsg: one2one("mail.thread"),
}
);
registerInstancePatchModel(
"mail.thread_cache",
"mail/static/src/models/thread_cache/thread_cache.js",
{
_extendMessageDomain(domain) {
const thread = this.thread;
if (thread === this.env.messaging.failedmsg) {
return domain.concat([["is_failed_message", "=", true]]);
}
return this._super(...arguments);
},
}
);
registerFieldPatchModel(
"mail.message",
"mail/static/src/models/message/message.js",
{
messagingFailedmsg: many2one("mail.thread", {
related: "messaging.failedmsg",
}),
isFailed: attr({
default: false,
}),
}
);
registerClassPatchModel(
"mail.message",
"mail/static/src/models/message/message.js",
{
convertData(data) {
const data2 = this._super(data);
if ("is_failed_message" in data) {
data2.isFailed = data.is_failed_message;
}
return data2;
},
}
);
});

View File

@ -0,0 +1,62 @@
/** @odoo-module **/
// const chatter = require("mail/static/src/models/chatter/chatter.js");
// const useStore = require("mail/static/src/component_hooks/use_store/use_store.js");
import {registerMessagingComponent} from "@mail/utils/messaging_component";
const {Component} = owl;
export class MessageFailedBox extends Component {
constructor(...args) {
super(...args);
}
// Get chatter() {
// return this.env.models["mail.chatter"].get(this.props.chatterLocalId);
// }
_onClickTitle() {
this.chatter.toggleMessageFailedBoxVisibility();
}
_markFailedMessageReviewed(id) {
return this.env.services.rpc({
model: "mail.message",
method: "set_need_action_done",
args: [[id]],
});
}
_onRetryFailedMessage(event) {
event.preventDefault();
var messageID = $(event.currentTarget).data("message-id");
const thread = this.chatter.thread;
var self = this;
this.env.bus.trigger("do-action", {
action: "mail.mail_resend_message_action",
options: {
additional_context: {
mail_message_to_resend: messageID,
},
on_close: () => {
self.trigger("reload", {keepChanges: true});
thread.refresh();
},
},
});
}
_onMarkFailedMessageReviewed(event) {
event.preventDefault();
var messageID = $(event.currentTarget).data("message-id");
this._markFailedMessageReviewed(messageID);
this.trigger("reload", {keepChanges: true});
this.chatter.thread.refreshMessagefailed();
this.chatter.thread.refresh();
}
}
Object.assign(MessageFailedBox, {
props: {
chatterLocalId: String,
},
template: "mail_tracking.MessageFailedBox",
});
registerMessagingComponent(MessageFailedBox);

View File

@ -1,76 +0,0 @@
odoo.define(
"mail_tracking/static/src/js/failed_message/mail_failed_box.js",
function (require) {
"use strict";
const chatter = require("mail/static/src/components/chatter/chatter.js");
const useStore = require("mail/static/src/component_hooks/use_store/use_store.js");
const {Component} = owl;
class MessageFailedBox extends Component {
constructor(...args) {
super(...args);
useStore((props) => {
const chatter = this.env.models["mail.chatter"].get(
props.chatterLocalId
);
const thread = chatter && chatter.thread;
return {
chatter: chatter ? chatter.__state : undefined,
thread: thread && thread.__state,
};
});
}
get chatter() {
return this.env.models["mail.chatter"].get(this.props.chatterLocalId);
}
_onClickTitle() {
this.chatter.toggleMessageFailedBoxVisibility();
}
_markFailedMessageReviewed(id) {
return this.env.services.rpc({
model: "mail.message",
method: "set_need_action_done",
args: [[id]],
});
}
_onRetryFailedMessage(event) {
event.preventDefault();
var messageID = $(event.currentTarget).data("message-id");
const thread = this.chatter.thread;
var self = this;
this.env.bus.trigger("do-action", {
action: "mail.mail_resend_message_action",
options: {
additional_context: {
mail_message_to_resend: messageID,
},
on_close: () => {
self.trigger("reload", {keepChanges: true});
thread.refresh();
},
},
});
}
_onMarkFailedMessageReviewed(event) {
event.preventDefault();
var messageID = $(event.currentTarget).data("message-id");
this._markFailedMessageReviewed(messageID);
this.trigger("reload", {keepChanges: true});
this.chatter.thread.refreshMessagefailed();
this.chatter.thread.refresh();
}
}
MessageFailedBox.template = "mail_tracking.MessageFailedBox";
MessageFailedBox.props = {
chatterLocalId: String,
};
chatter.components = Object.assign({}, chatter.components, {
MessageFailedBox,
});
return MessageFailedBox;
}
);

View File

@ -0,0 +1,58 @@
/** @odoo-module **/
import {
registerFieldPatchModel,
registerInstancePatchModel,
} from "@mail/model/model_core";
import {one2many} from "@mail/model/model_field";
registerInstancePatchModel(
"mail.thread",
"mail_tracking/static/src/js/failed_message/thread.js",
{
async refreshMessagefailed() {
var id = this.__values.id;
var model = this.__values.model;
const messagefailedData = await this.async(() =>
this.env.services.rpc(
{
model: "mail.message",
method: "get_failed_messsage_info",
args: [id, model],
},
{
shadow: true,
}
)
);
const messagefailed = this.messaging.models["mail.message.failed"].insert(
messagefailedData.map((messageData) =>
this.messaging.models["mail.message.failed"].convertData(
messageData
)
)
);
this.update({
messagefailed: [["replace", messagefailed]],
});
},
_computeFetchMessagesUrl() {
switch (this) {
case this.messaging.failedmsg:
return "/mail/failed/messages";
}
return this._super();
},
}
);
registerFieldPatchModel(
"mail.thread",
"mail_tracking/static/src/js/failed_message/thread.js",
{
messagefailed: one2many("mail.message.failed", {
inverse: "thread",
}),
}
);

View File

@ -1,50 +0,0 @@
odoo.define("mail_tracking/static/src/js/failed_message/thread.js", function (require) {
"use strict";
const {
registerInstancePatchModel,
registerFieldPatchModel,
} = require("mail/static/src/model/model_core.js");
const {one2many} = require("mail/static/src/model/model_field.js");
registerInstancePatchModel(
"mail.thread",
"mail_tracking/static/src/js/failed_message/thread.js",
{
async refreshMessagefailed() {
var id = this.__values.id;
var model = this.__values.model;
const messagefailedData = await this.async(() =>
this.env.services.rpc(
{
model: "mail.message",
method: "get_failed_messsage_info",
args: [id, model],
},
{
shadow: true,
}
)
);
const messagefailed = this.env.models["mail.message.failed"].insert(
messagefailedData.map((messageData) =>
this.env.models["mail.message.failed"].convertData(messageData)
)
);
this.update({
messagefailed: [["replace", messagefailed]],
});
},
}
);
registerFieldPatchModel(
"mail.thread",
"mail_tracking/static/src/js/failed_message/thread.js",
{
messagefailed: one2many("mail.message.failed", {
inverse: "thread",
}),
}
);
});

View File

@ -0,0 +1,51 @@
/** @odoo-module **/
import {
registerClassPatchModel,
registerFieldPatchModel,
registerInstancePatchModel,
} from "@mail/model/model_core";
import {attr} from "@mail/model/model_field";
registerClassPatchModel(
"mail.message",
"mail_tracking/static/src/js/mail_tracking.js",
{
convertData(data) {
const data2 = this._super(data);
if ("partner_trackings" in data) {
data2.partner_trackings = data.partner_trackings;
}
return data2;
},
}
);
registerFieldPatchModel(
"mail.message",
"mail_tracking/static/src/js/mail_tracking.js",
{
partner_trackings: attr(),
}
);
registerInstancePatchModel(
"mail.model",
"mail_tracking/static/src/js/mail_tracking.js",
{
hasPartnerTrackings() {
return _.some(this.__values.partner_trackings);
},
hasEmailCc() {
return _.some(this._emailCc);
},
getPartnerTrackings: function () {
if (!this.hasPartnerTrackings()) {
return [];
}
return this.__values.partner_trackings;
},
}
);

View File

@ -1,53 +0,0 @@
odoo.define("mail_tracking/static/src/js/mail_tracking.js", function (require) {
"use strict";
const {
registerClassPatchModel,
registerFieldPatchModel,
registerInstancePatchModel,
} = require("mail/static/src/model/model_core.js");
const {attr} = require("mail/static/src/model/model_field.js");
registerClassPatchModel(
"mail.message",
"mail_tracking/static/src/js/mail_tracking.js",
{
convertData(data) {
const data2 = this._super(data);
if ("partner_trackings" in data) {
data2.partner_trackings = data.partner_trackings;
}
return data2;
},
}
);
registerFieldPatchModel(
"mail.message",
"mail_tracking/static/src/js/mail_tracking.js",
{
partner_trackings: attr(),
}
);
registerInstancePatchModel(
"mail.model",
"mail_tracking/static/src/js/mail_tracking.js",
{
hasPartnerTrackings() {
return _.some(this.__values.partner_trackings);
},
hasEmailCc() {
return _.some(this._emailCc);
},
getPartnerTrackings: function () {
if (!this.hasPartnerTrackings()) {
return [];
}
return this.__values.partner_trackings;
},
}
);
});

View File

@ -0,0 +1,55 @@
/** @odoo-module **/
import {Message} from "@mail/components/message/message";
import {patch} from "web.utils";
patch(Message.prototype, "mail_tracking/static/src/js/message.js", {
constructor() {
this._super(...arguments);
},
_onTrackingStatusClick(event) {
var tracking_email_id = $(event.currentTarget).data("tracking");
event.preventDefault();
return this.env.bus.trigger("do-action", {
action: {
type: "ir.actions.act_window",
view_type: "form",
view_mode: "form",
res_model: "mail.tracking.email",
views: [[false, "form"]],
target: "new",
res_id: tracking_email_id,
},
});
},
// For discuss
_onMarkFailedMessageReviewed(event) {
event.preventDefault();
var messageID = $(event.currentTarget).data("message-id");
this._markFailedMessageReviewed(messageID);
window.location.reload();
},
_onRetryFailedMessage(event) {
event.preventDefault();
var messageID = $(event.currentTarget).data("message-id");
this.env.bus.trigger("do-action", {
action: "mail.mail_resend_message_action",
options: {
additional_context: {
mail_message_to_resend: messageID,
},
on_close: () => {
window.location.reload();
},
},
});
},
_markFailedMessageReviewed(id) {
return this.env.services.rpc({
model: "mail.message",
method: "set_need_action_done",
args: [[id]],
});
},
});

View File

@ -1,58 +0,0 @@
odoo.define("mail_tracking/static/src/js/message.js", function (require) {
"use strict";
const Message = require("mail/static/src/components/message/message.js");
const MessageList = require("mail/static/src/components/message_list/message_list.js");
class MessageTracking extends Message {
constructor(parent, props) {
super(parent, props);
}
_onTrackingStatusClick(event) {
var tracking_email_id = $(event.currentTarget).data("tracking");
event.preventDefault();
return this.env.bus.trigger("do-action", {
action: {
type: "ir.actions.act_window",
view_type: "form",
view_mode: "form",
res_model: "mail.tracking.email",
views: [[false, "form"]],
target: "new",
res_id: tracking_email_id,
},
});
}
// For discuss
_onMarkFailedMessageReviewed(event) {
event.preventDefault();
var messageID = $(event.currentTarget).data("message-id");
this._markFailedMessageReviewed(messageID);
window.location.reload();
}
_onRetryFailedMessage(event) {
event.preventDefault();
var messageID = $(event.currentTarget).data("message-id");
this.env.bus.trigger("do-action", {
action: "mail.mail_resend_message_action",
options: {
additional_context: {
mail_message_to_resend: messageID,
},
on_close: () => {
window.location.reload();
},
},
});
}
_markFailedMessageReviewed(id) {
return this.env.services.rpc({
model: "mail.message",
method: "set_need_action_done",
args: [[id]],
});
}
}
MessageList.components.Message = MessageTracking;
});

View File

@ -0,0 +1,53 @@
/** @odoo-module **/
import {registerNewModel} from "@mail/model/model_core";
import {attr, many2one} from "@mail/model/model_field";
function factory(dependencies) {
class MessageFailed extends dependencies["mail.model"] {
static convertData(data) {
const data2 = {};
if ("author" in data) {
if (!data.author) {
data2.author = [["unlink-all"]];
} else {
data2.author = data.author[1];
data2.author_id = data.author[0];
}
}
if ("body" in data) {
data2.body = data.body;
}
if ("date" in data) {
data2.date = data.date;
}
if ("failed_recipients" in data) {
data2.failed_recipients = data.failed_recipients;
}
if ("id" in data) {
data2.id = data.id;
}
return data2;
}
}
MessageFailed.fields = {
thread: many2one("mail.thread", {
inverse: "messagefailed",
}),
body: attr(),
author: attr(),
author_id: attr(),
date: attr(),
failed_recipients: attr(),
id: attr({
readonly: true,
required: true,
}),
};
MessageFailed.modelName = "mail.message.failed";
MessageFailed.identifyingFields = ["id"];
return MessageFailed;
}
registerNewModel("mail.message.failed", factory);

View File

@ -1,53 +0,0 @@
odoo.define("mail_tracking/static/src/js/models/thread.js", function (require) {
"use strict";
const {registerNewModel} = require("mail/static/src/model/model_core.js");
const {attr, many2one} = require("mail/static/src/model/model_field.js");
function factory(dependencies) {
class MessageFailed extends dependencies["mail.model"] {
static convertData(data) {
const data2 = {};
if ("author" in data) {
if (!data.author) {
data2.author = [["unlink-all"]];
} else {
data2.author = data.author[1];
data2.author_id = data.author[0];
}
}
if ("body" in data) {
data2.body = data.body;
}
if ("date" in data) {
data2.date = data.date;
}
if ("failed_recipients" in data) {
data2.failed_recipients = data.failed_recipients;
}
if ("id" in data) {
data2.id = data.id;
}
return data2;
}
}
MessageFailed.fields = {
thread: many2one("mail.thread", {
inverse: "messagefailed",
}),
body: attr(),
author: attr(),
author_id: attr(),
date: attr(),
failed_recipients: attr(),
id: attr(),
};
MessageFailed.modelName = "mail.message.failed";
return MessageFailed;
}
registerNewModel("mail.message.failed", factory);
});

View File

@ -1,24 +1,42 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<templates> <templates>
<t t-inherit="mail.ThreadIcon" t-inherit-mode="extension"> <t t-inherit="mail.ThreadIcon" t-inherit-mode="extension">
<xpath <xpath expr="//t[@t-elif='thread === messaging.history']" position="after">
expr="//t[@t-elif='thread === env.messaging.moderation']" <t t-elif="thread === messaging.failedmsg">
position="after"
>
<t t-elif="thread === env.messaging.failedmsg">
<div class="o_ThreadIcon_mailboxModeration fa fa-exclamation" /> <div class="o_ThreadIcon_mailboxModeration fa fa-exclamation" />
</t> </t>
</xpath> </xpath>
</t> </t>
<t t-inherit="mail.DiscussSidebar" t-inherit-mode="extension">
<xpath
expr="//div[hasclass('o_DiscussSidebar_categoryMailbox')]"
position="inside"
>
<DiscussSidebarMailbox threadLocalId="messaging.failedmsg.localId" />
</xpath>
</t>
<t t-inherit="mail.MessageList" t-inherit-mode="extension">
<t t-elif="threadView.thread === messaging.history" position="after">
<t t-elif="threadView.thread === messaging.failedmsg">
<div class="o_MessageList_emptyTitle o-neutral-face-icon">
No failed messages
</div>
Failed messages will be appeared here.
</t>
</t>
</t>
<t t-inherit="mail.Message" t-inherit-mode="extension"> <t t-inherit="mail.Message" t-inherit-mode="extension">
<xpath expr="//div[hasclass('o_Message_originThread')]" position="inside"> <xpath expr="//div[hasclass('o_Message_originThread')]" position="inside">
<t t-if="message.isFailed"> <t t-if="messageView.message.isFailed">
<span t-attf-class="o_thread_icons"> <span t-attf-class="o_thread_icons">
<a <a
href="#" href="#"
class="btn btn-link btn-success o_thread_icon text-muted btn-sm o_failed_message_reviewed o_activity_link" class="btn btn-link btn-success o_thread_icon text-muted btn-sm o_failed_message_reviewed o_activity_link"
t-on-click="_onMarkFailedMessageReviewed" t-on-click="_onMarkFailedMessageReviewed"
t-att-data-message-id="message.id" t-att-data-message-id="messageView.message.id"
> >
<i class="fa fa-check" /> <i class="fa fa-check" />
Set as Reviewed Set as Reviewed
@ -27,7 +45,7 @@
href="#" href="#"
class="btn btn-link btn-success o_thread_icon text-muted btn-sm o_failed_message_retry o_activity_link" class="btn btn-link btn-success o_thread_icon text-muted btn-sm o_failed_message_retry o_activity_link"
t-on-click="_onRetryFailedMessage" t-on-click="_onRetryFailedMessage"
t-att-data-message-id="message.id" t-att-data-message-id="messageView.message.id"
> >
<i class="fa fa-retweet" /> <i class="fa fa-retweet" />
Retry Retry

View File

@ -54,12 +54,17 @@
</span> </span>
</t> </t>
</t> </t>
<t t-inherit="mail.Message" t-inherit-mode="extension"> <t t-name="mail.MessageTracking" t-inherit="mail.Message" t-inherit-mode="extension">
<xpath expr="//div[hasclass('o_Message_header')]" position="after"> <xpath expr="//div[hasclass('o_Message_header')]" position="after">
<t t-if="message.hasPartnerTrackings() || message.hasEmailCc()"> <t
t-if="messageView.message.hasPartnerTrackings() || messageView.message.hasEmailCc()"
>
<p class="o_mail_tracking"> <p class="o_mail_tracking">
<strong>To:</strong> <strong>To:</strong>
<t t-foreach="message.getPartnerTrackings()" t-as="tracking"> <t
t-foreach="messageView.message.getPartnerTrackings()"
t-as="tracking"
>
<t t-if="!tracking_first"> <t t-if="!tracking_first">
- -
</t> </t>
@ -91,6 +96,7 @@
class="mail_tracking o_mail_action_tracking_status" class="mail_tracking o_mail_action_tracking_status"
t-att-data-tracking="tracking['tracking_id']" t-att-data-tracking="tracking['tracking_id']"
t-att-title="title_status" t-att-title="title_status"
type="button"
t-on-click="_onTrackingStatusClick" t-on-click="_onTrackingStatusClick"
> >
<t t-call="mail.tracking.status" /> <t t-call="mail.tracking.status" />

View File

@ -88,7 +88,8 @@ class TestMailTracking(TransactionCase):
"body": "<p>This is a test message</p>", "body": "<p>This is a test message</p>",
} }
) )
message._moderate_accept() if message.is_thread_message():
self.env[message.model].browse(message.res_id)._notify_thread(message)
# Search tracking created # Search tracking created
tracking_email = self.env["mail.tracking.email"].search( tracking_email = self.env["mail.tracking.email"].search(
[ [
@ -138,7 +139,10 @@ class TestMailTracking(TransactionCase):
"body": "<p>This is a test message</p>", "body": "<p>This is a test message</p>",
} }
) )
message.with_context(do_not_send_copy=True)._moderate_accept() if message.is_thread_message():
self.env[message.model].browse(message.res_id).with_context(
do_not_send_copy=True
)._notify_thread(message)
# Search tracking created # Search tracking created
tracking_email = self.env["mail.tracking.email"].search( tracking_email = self.env["mail.tracking.email"].search(
[ [
@ -203,7 +207,8 @@ class TestMailTracking(TransactionCase):
"body": "<p>This is another test message</p>", "body": "<p>This is another test message</p>",
} }
) )
message._moderate_accept() if message.is_thread_message():
self.env[message.model].browse(message.res_id)._notify_thread(message)
recipients = self.recipient._message_get_suggested_recipients() recipients = self.recipient._message_get_suggested_recipients()
self.assertEqual(len(recipients[self.recipient.id]), 3) self.assertEqual(len(recipients[self.recipient.id]), 3)
self._check_partner_trackings_cc(message) self._check_partner_trackings_cc(message)
@ -257,7 +262,8 @@ class TestMailTracking(TransactionCase):
"body": "<p>This is another test message</p>", "body": "<p>This is another test message</p>",
} }
) )
message._moderate_accept() if message.is_thread_message():
self.env[message.model].browse(message.res_id)._notify_thread(message)
recipients = self.recipient._message_get_suggested_recipients() recipients = self.recipient._message_get_suggested_recipients()
self.assertEqual(len(recipients[self.recipient.id]), 4) self.assertEqual(len(recipients[self.recipient.id]), 4)
self._check_partner_trackings_to(message) self._check_partner_trackings_to(message)
@ -316,7 +322,8 @@ class TestMailTracking(TransactionCase):
"body": "<p>This is a test message</p>", "body": "<p>This is a test message</p>",
} }
) )
message._moderate_accept() if message.is_thread_message():
self.env[message.model].browse(message.res_id)._notify_thread(message)
# Search tracking created # Search tracking created
tracking_email = self.env["mail.tracking.email"].search( tracking_email = self.env["mail.tracking.email"].search(
[ [
@ -330,7 +337,7 @@ class TestMailTracking(TransactionCase):
wizard = ( wizard = (
self.env["mail.resend.message"] self.env["mail.resend.message"]
.sudo() .sudo()
.with_context({"mail_message_to_resend": message.id}) .with_context(mail_message_to_resend=message.id)
.create({}) .create({})
) )
# Check failed recipient)s # Check failed recipient)s

View File

@ -1,49 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -->
<odoo>
<template
id="assets_backend"
name="mail_tracking assets"
inherit_id="web.assets_backend"
>
<xpath expr="." position="inside">
<link
rel="stylesheet"
href="/mail_tracking/static/src/css/mail_tracking.scss"
/>
<link
rel="stylesheet"
href="/mail_tracking/static/src/css/failed_message.scss"
/>
<script
type="text/javascript"
src="/mail_tracking/static/src/js/mail_tracking.js"
/>
<script
type="text/javascript"
src="/mail_tracking/static/src/js/message.js"
/>
<script
type="text/javascript"
src="/mail_tracking/static/src/js/failed_message/mail_failed_box.js"
/>
<script
type="text/javascript"
src="/mail_tracking/static/src/js/failed_message/thread.js"
/>
<script
type="text/javascript"
src="/mail_tracking/static/src/js/models/thread.js"
/>
<script
type="text/javascript"
src="/mail_tracking/static/src/js/chatter.js"
/>
<script
type="text/javascript"
src="/mail_tracking/static/src/js/discuss/discuss.js"
/>
</xpath>
</template>
</odoo>

View File

@ -3,74 +3,78 @@
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). --> License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -->
<odoo> <odoo>
<record model="ir.ui.view" id="view_mail_tracking_email_form"> <record model="ir.ui.view" id="view_mail_tracking_email_form">
<field name="name">mail.tracking.email.form</field> <field name="name">mail.tracking.email.form</field>
<field name="model">mail.tracking.email</field> <field name="model">mail.tracking.email</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="MailTracking event" create="false" edit="false" delete="false"> <form
<header> string="MailTracking event"
<field name="state" widget="statusbar" /> create="false"
</header> edit="false"
<sheet> delete="false"
<group> >
<field name="name" /> <header>
</group> <field name="state" widget="statusbar" />
<group> </header>
<sheet>
<group> <group>
<field name="mail_message_id" /> <field name="name" />
<field name="mail_id" />
<field name="partner_id" />
<field name="recipient" />
<field name="sender" />
</group> </group>
<group> <group>
<field name="timestamp" /> <group>
<field name="time" /> <field name="mail_message_id" />
<field name="date" /> <field name="mail_id" />
<field name="partner_id" />
<field name="recipient" />
<field name="sender" />
</group>
<group>
<field name="timestamp" />
<field name="time" />
<field name="date" />
</group>
</group> </group>
</group> <group attrs="{'invisible': [('bounce_type', '=', False)]}">
<group attrs="{'invisible': [('bounce_type', '=', False)]}"> <field name="bounce_type" />
<field name="bounce_type" /> <field name="bounce_description" />
<field name="bounce_description" /> </group>
</group> <group attrs="{'invisible': [('error_type', '=', False)]}">
<group attrs="{'invisible': [('error_type', '=', False)]}"> <field
<field
name="error_smtp_server" name="error_smtp_server"
attrs="{'invisible': [('error_smtp_server', '=', False)]}" attrs="{'invisible': [('error_smtp_server', '=', False)]}"
/> />
<field name="error_type" /> <field name="error_type" />
<field name="error_description" /> <field name="error_description" />
</group> </group>
<label for="tracking_event_ids" /> <label for="tracking_event_ids" />
<div> <div>
<field name="tracking_event_ids"> <field name="tracking_event_ids">
<tree <tree
string="Tracking events"
decoration-muted="event_type == 'deferral'" decoration-muted="event_type == 'deferral'"
decoration-danger="event_type in ('hard_bounce', 'soft_bounce', 'spam', 'reject')" decoration-danger="event_type in ('hard_bounce', 'soft_bounce', 'spam', 'reject')"
decoration-info="event_type in ('unsub', 'click', 'open')" decoration-info="event_type in ('unsub', 'click', 'open')"
> >
<field name="time" /> <field name="time" />
<field name="event_type" /> <field name="event_type" />
<field name="ip" /> <field name="ip" />
<field name="url" /> <field name="url" />
<field name="user_country_id" string="Country" /> <field name="user_country_id" string="Country" />
<field name="os_family" string="OS" /> <field name="os_family" string="OS" />
<field name="ua_family" string="User agent" /> <field name="ua_family" string="User agent" />
</tree> </tree>
</field> </field>
</div> </div>
</sheet> </sheet>
</form> <footer />
</field> </form>
</record> </field>
</record>
<record model="ir.ui.view" id="view_mail_tracking_email_tree"> <record model="ir.ui.view" id="view_mail_tracking_email_tree">
<field name="name">mail.tracking.email.tree</field> <field name="name">mail.tracking.email.tree</field>
<field name="model">mail.tracking.email</field> <field name="model">mail.tracking.email</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree <tree
string="MailTracking emails"
create="false" create="false"
edit="false" edit="false"
delete="false" delete="false"
@ -79,101 +83,105 @@
decoration-danger="state in ('rejected', 'spam', 'bounced', 'soft-bounced', 'error')" decoration-danger="state in ('rejected', 'spam', 'bounced', 'soft-bounced', 'error')"
decoration-info="state == 'unsub'" decoration-info="state == 'unsub'"
> >
<field name="time" /> <field name="time" />
<field name="date" invisible="1" /> <field name="date" invisible="1" />
<field name="name" /> <field name="name" />
<field name="sender" string="Sender" /> <field name="sender" string="Sender" />
<field name="recipient" string="Recipient" /> <field name="recipient" string="Recipient" />
<field name="state" /> <field name="state" />
</tree> </tree>
</field> </field>
</record> </record>
<record model="ir.ui.view" id="view_mail_tracking_email_search"> <record model="ir.ui.view" id="view_mail_tracking_email_search">
<field name="name">mail.tracking.email.search</field> <field name="name">mail.tracking.email.search</field>
<field name="model">mail.tracking.email</field> <field name="model">mail.tracking.email</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<search string="MailTracking email search"> <search string="MailTracking email search">
<field <field
name="display_name" name="display_name"
string="Email" string="Email"
filter_domain="['|', ('sender', 'ilike', self), ('recipient', 'ilike', self)]" filter_domain="['|', ('sender', 'ilike', self), ('recipient', 'ilike', self)]"
/> />
<field <field
name="sender" name="sender"
string="Sender" string="Sender"
filter_domain="[('sender', 'ilike', self)]" filter_domain="[('sender', 'ilike', self)]"
/> />
<field <field
name="recipient_address" name="recipient_address"
string="Recipient Address" string="Recipient Address"
filter_domain="[('recipient_address', '=', self)]" filter_domain="[('recipient_address', '=', self)]"
/> />
<field name="name" string="Subject" /> <field name="name" string="Subject" />
<field name="time" string="Time" /> <field name="time" string="Time" />
<field name="date" string="Date" /> <field name="date" string="Date" />
<filter name="sent" string="Sent" domain="[('state', 'in', ('sent',))]" /> <filter
<filter name="sent"
string="Sent"
domain="[('state', 'in', ('sent',))]"
/>
<filter
name="deferred" name="deferred"
string="Deferred" string="Deferred"
domain="[('state', '=', 'deferred')]" domain="[('state', '=', 'deferred')]"
/> />
<filter <filter
name="delivered" name="delivered"
string="Delivered" string="Delivered"
domain="[('state', 'in', ('delivered', 'opened'))]" domain="[('state', 'in', ('delivered', 'opened'))]"
/> />
<filter <filter
name="unsub" name="unsub"
string="Unsubscribed" string="Unsubscribed"
domain="[('state', '=', 'unsub')]" domain="[('state', '=', 'unsub')]"
/> />
<filter <filter
name="exception" name="exception"
string="Failed" string="Failed"
domain="[('state', 'in', ('error', 'rejected', 'spam', 'bounced', 'soft-bounced'))]" domain="[('state', 'in', ('error', 'rejected', 'spam', 'bounced', 'soft-bounced'))]"
/> />
<separator /> <separator />
<group expand="0" string="Group By"> <group expand="0" string="Group By">
<filter <filter
string="State" string="State"
name="group_by_state" name="group_by_state"
domain="[]" domain="[]"
context="{'group_by': 'state'}" context="{'group_by': 'state'}"
/> />
<filter <filter
string="Subject" string="Subject"
name="group_by_subject" name="group_by_subject"
domain="[]" domain="[]"
context="{'group_by': 'name'}" context="{'group_by': 'name'}"
/> />
<filter <filter
string="Sender" string="Sender"
name="group_by_sender" name="group_by_sender"
domain="[]" domain="[]"
context="{'group_by': 'sender'}" context="{'group_by': 'sender'}"
/> />
<filter <filter
string="Month" string="Month"
name="group_by_month" name="group_by_month"
domain="[]" domain="[]"
context="{'group_by': 'date'}" context="{'group_by': 'date'}"
/> />
</group> </group>
</search> </search>
</field> </field>
</record> </record>
<record id="action_view_mail_tracking_email" model="ir.actions.act_window"> <record id="action_view_mail_tracking_email" model="ir.actions.act_window">
<field name="name">MailTracking emails</field> <field name="name">MailTracking emails</field>
<field name="res_model">mail.tracking.email</field> <field name="res_model">mail.tracking.email</field>
<field name="view_mode">tree,form</field> <field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_mail_tracking_email_search" /> <field name="search_view_id" ref="view_mail_tracking_email_search" />
</record> </record>
<!-- Add menu entry in Settings/Email --> <!-- Add menu entry in Settings/Email -->
<menuitem <menuitem
name="Tracking emails" name="Tracking emails"
id="menu_mail_tracking_email" id="menu_mail_tracking_email"
parent="base.menu_email" parent="base.menu_email"

View File

@ -3,66 +3,70 @@
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). --> License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -->
<odoo> <odoo>
<record model="ir.ui.view" id="view_mail_tracking_event_form"> <record model="ir.ui.view" id="view_mail_tracking_event_form">
<field name="name">mail.tracking.event.form</field> <field name="name">mail.tracking.event.form</field>
<field name="model">mail.tracking.event</field> <field name="model">mail.tracking.event</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="MailTracking event" create="false" edit="false" delete="false"> <form
<sheet> string="MailTracking event"
<group> create="false"
edit="false"
delete="false"
>
<sheet>
<group> <group>
<field name="tracking_email_id" /> <group>
<field name="recipient" /> <field name="tracking_email_id" />
<field name="event_type" /> <field name="recipient" />
<field name="event_type" />
</group>
<group>
<field name="timestamp" />
<field name="time" />
<field name="date" />
</group>
</group> </group>
<group> <group attrs="{'invisible': [('event_type', 'not in', ('sent',))]}">
<field name="timestamp" /> <field name="smtp_server" />
<field name="time" />
<field name="date" />
</group> </group>
</group> <group
<group attrs="{'invisible': [('event_type', 'not in', ('sent',))]}">
<field name="smtp_server" />
</group>
<group
attrs="{'invisible': [('event_type', 'not in', ('open', 'click'))]}" attrs="{'invisible': [('event_type', 'not in', ('open', 'click'))]}"
> >
<field name="url" /> <field name="url" />
</group> </group>
<group <group
attrs="{'invisible': [('event_type', 'not in', ('open', 'click'))]}" attrs="{'invisible': [('event_type', 'not in', ('open', 'click'))]}"
> >
<group> <group>
<field name="mobile" /> <field name="mobile" />
<field name="ip" /> <field name="ip" />
<field name="user_country_id" /> <field name="user_country_id" />
</group>
<group>
<field name="user_agent" />
<field name="ua_family" />
<field name="ua_type" />
<field name="os_family" />
</group>
</group> </group>
<group> <group
<field name="user_agent" />
<field name="ua_family" />
<field name="ua_type" />
<field name="os_family" />
</group>
</group>
<group
string="Error" string="Error"
attrs="{'invisible': [('error_type', '=', False)]}" attrs="{'invisible': [('error_type', '=', False)]}"
> >
<field name="error_type" /> <field name="error_type" />
<field name="error_description" /> <field name="error_description" />
<field name="error_details" /> <field name="error_details" />
</group> </group>
</sheet> </sheet>
</form> </form>
</field> </field>
</record> </record>
<record model="ir.ui.view" id="view_mail_tracking_event_tree"> <record model="ir.ui.view" id="view_mail_tracking_event_tree">
<field name="name">mail.tracking.event.tree</field> <field name="name">mail.tracking.event.tree</field>
<field name="model">mail.tracking.event</field> <field name="model">mail.tracking.event</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree <tree
string="MailTracking events"
create="false" create="false"
edit="false" edit="false"
delete="false" delete="false"
@ -70,125 +74,133 @@
decoration-danger="event_type in ('hard_bounce', 'soft_bounce', 'spam', 'reject')" decoration-danger="event_type in ('hard_bounce', 'soft_bounce', 'spam', 'reject')"
decoration-info="event_type in ('unsub', 'click', 'open')" decoration-info="event_type in ('unsub', 'click', 'open')"
> >
<field name="time" /> <field name="time" />
<field name="tracking_email_id" /> <field name="tracking_email_id" />
<field name="recipient" /> <field name="recipient" />
<field name="event_type" /> <field name="event_type" />
<field <field
name="error_details" name="error_details"
invisible="not context.get('event_error_filter', False)" invisible="not context.get('event_error_filter', False)"
/> />
<field name="date" invisible="1" /> <field name="date" invisible="1" />
<field name="ip" /> <field name="ip" />
<field name="url" /> <field name="url" />
<field name="user_country_id" string="Country" /> <field name="user_country_id" string="Country" />
<field name="os_family" string="OS" /> <field name="os_family" string="OS" />
<field name="ua_family" string="User agent" /> <field name="ua_family" string="User agent" />
</tree> </tree>
</field> </field>
</record> </record>
<record model="ir.ui.view" id="view_mail_tracking_event_search"> <record model="ir.ui.view" id="view_mail_tracking_event_search">
<field name="name">mail.tracking.event.search</field> <field name="name">mail.tracking.event.search</field>
<field name="model">mail.tracking.event</field> <field name="model">mail.tracking.event</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<search string="MailTracking event search"> <search string="MailTracking event search">
<field <field
name="tracking_email_id" name="tracking_email_id"
string="Message" string="Message"
filter_domain="[('tracking_email_id', 'ilike', self)]" filter_domain="[('tracking_email_id', 'ilike', self)]"
/> />
<field name="recipient" string="Recipient" /> <field name="recipient" string="Recipient" />
<field name="time" string="Time" /> <field name="time" string="Time" />
<field name="date" string="Date" /> <field name="date" string="Date" />
<field name="ip" string="IP" /> <field name="ip" string="IP" />
<field name="url" string="URL" /> <field name="url" string="URL" />
<filter name="sent" string="Sent" domain="[('event_type', '=', 'sent')]" /> <filter
<filter name="sent"
string="Sent"
domain="[('event_type', '=', 'sent')]"
/>
<filter
name="delivered" name="delivered"
string="Delivered" string="Delivered"
domain="[('event_type', '=', 'delivered')]" domain="[('event_type', '=', 'delivered')]"
/> />
<filter <filter
name="click" name="click"
string="Click" string="Click"
domain="[('event_type', '=', 'click')]" domain="[('event_type', '=', 'click')]"
/> />
<filter name="open" string="Open" domain="[('event_type', '=', 'open')]" /> <filter
<filter name="open"
string="Open"
domain="[('event_type', '=', 'open')]"
/>
<filter
name="unsub" name="unsub"
string="Unsubscribe" string="Unsubscribe"
domain="[('event_type', '=', 'unsub')]" domain="[('event_type', '=', 'unsub')]"
/> />
<filter <filter
name="bounce" name="bounce"
string="Bounce" string="Bounce"
domain="[('event_type', 'in', ('hard_bounce', 'soft_bounce'))]" domain="[('event_type', 'in', ('hard_bounce', 'soft_bounce'))]"
/> />
<filter <filter
name="exception" name="exception"
string="Failed" string="Failed"
domain="[('event_type', 'in', ('reject', 'spam'))]" domain="[('event_type', 'in', ('reject', 'spam'))]"
context="{'event_error_filter': True}" context="{'event_error_filter': True}"
/> />
<separator /> <separator />
<group expand="0" string="Group By"> <group expand="0" string="Group By">
<filter <filter
string="Type" string="Type"
name="group_by_type" name="group_by_type"
domain="[]" domain="[]"
context="{'group_by': 'event_type'}" context="{'group_by': 'event_type'}"
/> />
<filter <filter
string="Message" string="Message"
name="group_by_message" name="group_by_message"
domain="[]" domain="[]"
context="{'group_by': 'tracking_email_id'}" context="{'group_by': 'tracking_email_id'}"
/> />
<filter <filter
string="OS" string="OS"
name="group_by_os" name="group_by_os"
domain="[('os_family', '!=', False)]" domain="[('os_family', '!=', False)]"
context="{'group_by': 'os_family'}" context="{'group_by': 'os_family'}"
/> />
<filter <filter
string="User agent" string="User agent"
name="group_by_user_agent" name="group_by_user_agent"
domain="[('ua_family', '!=', False)]" domain="[('ua_family', '!=', False)]"
context="{'group_by': 'ua_family'}" context="{'group_by': 'ua_family'}"
/> />
<filter <filter
string="User agent type" string="User agent type"
name="group_by_user_agent_type" name="group_by_user_agent_type"
domain="[('ua_type', '!=', False)]" domain="[('ua_type', '!=', False)]"
context="{'group_by': 'ua_type'}" context="{'group_by': 'ua_type'}"
/> />
<filter <filter
string="Country" string="Country"
name="group_by_country" name="group_by_country"
domain="[('user_country_id', '!=', False)]" domain="[('user_country_id', '!=', False)]"
context="{'group_by': 'user_country_id'}" context="{'group_by': 'user_country_id'}"
/> />
<filter <filter
string="Month" string="Month"
name="group_by_date" name="group_by_date"
domain="[]" domain="[]"
context="{'group_by': 'date'}" context="{'group_by': 'date'}"
/> />
</group> </group>
</search> </search>
</field> </field>
</record> </record>
<record id="action_view_mail_tracking_event" model="ir.actions.act_window"> <record id="action_view_mail_tracking_event" model="ir.actions.act_window">
<field name="name">MailTracking events</field> <field name="name">MailTracking events</field>
<field name="res_model">mail.tracking.event</field> <field name="res_model">mail.tracking.event</field>
<field name="view_mode">tree,form</field> <field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_mail_tracking_event_search" /> <field name="search_view_id" ref="view_mail_tracking_event_search" />
</record> </record>
<!-- Add menu entry in Settings/Email --> <!-- Add menu entry in Settings/Email -->
<menuitem <menuitem
name="Tracking events" name="Tracking events"
id="menu_mail_tracking_event" id="menu_mail_tracking_event"
parent="base.menu_email" parent="base.menu_email"