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

View File

@ -10,7 +10,8 @@ import werkzeug
import odoo
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__)
@ -26,11 +27,10 @@ def db_env(dbname):
cr = http.request.cr
if not cr:
cr = odoo.sql_db.db_connect(dbname).cursor()
with api.Environment.manage():
yield api.Environment(cr, SUPERUSER_ID, {})
yield api.Environment(cr, SUPERUSER_ID, {})
class MailTrackingController(MailController):
class MailTrackingController(MailController, DiscussController):
def _request_metadata(self):
"""Prepare remote info metadata"""
request = http.request.httprequest
@ -59,8 +59,8 @@ class MailTrackingController(MailController):
res = env["mail.tracking.email"].event_process(
http.request, kw, metadata, event_type=event_type
)
except Exception:
pass
except Exception as e:
_logger.warning(e)
if not res or res == "NOT FOUND":
return werkzeug.exceptions.NotAcceptable()
return res
@ -89,8 +89,8 @@ class MailTrackingController(MailController):
)
elif tracking_email.state in ("sent", "delivered"):
tracking_email.event_create("open", metadata)
except Exception:
pass
except Exception as e:
_logger.warning(e)
# Always return GIF blank image
response = werkzeug.wrappers.Response()
@ -106,3 +106,12 @@ class MailTrackingController(MailController):
{"failed_counter": http.request.env["mail.message"].get_failed_count()}
)
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" ?>
<odoo>
<data>
<!-- Message with CC -->
<record id="mail_message_cc" model="mail.message">
<field name="model">res.partner</field>
@ -9,17 +7,17 @@
<field name="message_type">comment</field>
<field name="subtype_id" ref="mail.mt_comment" />
<field
name="email_cc"
>acc@testmail.com,wood.corner26@example.com,toni.rhodes11@example.com</field>
name="email_cc"
>acc@testmail.com,wood.corner26@example.com,toni.rhodes11@example.com</field>
<field name="mail_tracking_needs_action">1</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="author_id" ref="base.res_partner_1" />
<field name="partner_ids" eval="[(6, 0, [ref('base.partner_demo')])]" />
<field
name="notification_ids"
eval="[(0, 0, {'res_partner_id': ref('base.partner_demo')})]"
/>
name="notification_ids"
eval="[(0, 0, {'res_partner_id': ref('base.partner_demo')})]"
/>
<field name="subject">Message with CC</field>
</record>
@ -45,9 +43,9 @@
<field name="author_id" ref="base.res_partner_1" />
<field name="partner_ids" eval="[(6, 0, [ref('base.partner_demo')])]" />
<field
name="notification_ids"
eval="[(0, 0, {'res_partner_id': ref('base.partner_demo')})]"
/>
name="notification_ids"
eval="[(0, 0, {'res_partner_id': ref('base.partner_demo')})]"
/>
<field name="subject">Failed Message</field>
</record>
@ -73,9 +71,9 @@
<field name="author_id" ref="base.res_partner_10" />
<field name="partner_ids" eval="[(6, 0, [ref('base.partner_demo')])]" />
<field
name="notification_ids"
eval="[(0, 0, {'res_partner_id': ref('base.partner_demo')})]"
/>
name="notification_ids"
eval="[(0, 0, {'res_partner_id': ref('base.partner_demo')})]"
/>
<field name="subject">Failed Message</field>
</record>
@ -101,9 +99,9 @@
<field name="author_id" ref="base.partner_admin" />
<field name="partner_ids" eval="[(6, 0, [ref('base.partner_demo')])]" />
<field
name="notification_ids"
eval="[(0, 0, {'res_partner_id': ref('base.partner_demo')})]"
/>
name="notification_ids"
eval="[(0, 0, {'res_partner_id': ref('base.partner_demo')})]"
/>
<field name="subject">Failed Message</field>
</record>
@ -116,6 +114,4 @@
<field name="state">error</field>
<field name="time" eval="DateTime.today().strftime('%Y-%m-%d %H:%M')" />
</record>
</data>
</odoo>

View File

@ -224,9 +224,9 @@ class MailMessage(models.Model):
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"""
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_messages = self.browse(mail_message_ids)
tracking_statuses = mail_messages.tracking_status()
@ -273,13 +273,8 @@ class MailMessage(models.Model):
"""
self.check_access_rule("read")
self.write({"mail_tracking_needs_action": False})
notification = {
"type": "toggle_tracking_status",
"message_ids": self.ids,
"needs_actions": False,
}
self.env["bus.bus"].sendone(
(self._cr.dbname, "res.partner", self.env.user.partner_id.id), notification
self.env["bus.bus"]._sendone(
self.env.user.partner_id, "toggle_tracking_status", self.ids
)
@api.model

View File

@ -49,13 +49,9 @@ class MailResendMessage(models.TransientModel):
tracking_ids.sudo().write({"state": False})
# Send bus notifications to update Discuss and
# mail_failed_messages widget
notification = {
"type": "toggle_tracking_status",
"message_ids": [self.mail_message_id.id],
"needs_actions": False,
}
self.env["bus.bus"].sendone(
(self._cr.dbname, "res.partner", self.env.user.partner_id.id),
notification,
self.env["bus.bus"]._sendone(
self.env.user.partner_id.id,
"toggle_tracking_status",
self.mail_message_id.id,
)
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
name = fields.Char(string="Subject", readonly=True, index=True)
display_name = fields.Char(
string="Display name",
readonly=True,
store=True,
compute="_compute_tracking_display_name",
@ -38,10 +37,8 @@ class MailTrackingEmail(models.Model):
timestamp = fields.Float(
string="UTC timestamp", readonly=True, digits="MailTracking Timestamp"
)
time = fields.Datetime(string="Time", readonly=True, index=True)
date = fields.Date(
string="Date", readonly=True, compute="_compute_date", store=True
)
time = fields.Datetime(readonly=True, index=True)
date = fields.Date(readonly=True, compute="_compute_date", store=True)
mail_message_id = fields.Many2one(
string="Message", comodel_name="mail.message", readonly=True, index=True
)
@ -71,7 +68,6 @@ class MailTrackingEmail(models.Model):
("bounced", "Bounced"),
("soft-bounced", "Soft bounced"),
],
string="State",
index=True,
readonly=True,
default=False,
@ -97,10 +93,10 @@ class MailTrackingEmail(models.Model):
"bounced by recipient Mail Exchange (MX) server.\n",
)
error_smtp_server = fields.Char(string="Error SMTP server", readonly=True)
error_type = fields.Char(string="Error type", readonly=True)
error_description = fields.Char(string="Error description", readonly=True)
bounce_type = fields.Char(string="Bounce type", readonly=True)
bounce_description = fields.Char(string="Bounce description", readonly=True)
error_type = fields.Char(readonly=True)
error_description = fields.Char(readonly=True)
bounce_type = fields.Char(readonly=True)
bounce_description = fields.Char(readonly=True)
tracking_event_ids = fields.One2many(
string="Tracking events",
comodel_name="mail.tracking.event",
@ -126,10 +122,11 @@ class MailTrackingEmail(models.Model):
return records
def write(self, vals):
super().write(vals)
res = super().write(vals)
state = vals.get("state")
if state and state in self.env["mail.message"].get_failed_states():
self.mapped("mail_message_id").write({"mail_tracking_needs_action": True})
return res
@api.model
def email_is_bounced(self, email):

View File

@ -14,7 +14,7 @@ class MailTrackingEvent(models.Model):
_rec_name = "event_type"
_description = "MailTracking event"
recipient = fields.Char(string="Recipient", readonly=True)
recipient = fields.Char(readonly=True)
recipient_address = fields.Char(
string="Recipient email address",
readonly=True,
@ -25,10 +25,8 @@ class MailTrackingEvent(models.Model):
timestamp = fields.Float(
string="UTC timestamp", readonly=True, digits="MailTracking Timestamp"
)
time = fields.Datetime(string="Time", readonly=True)
date = fields.Date(
string="Date", readonly=True, compute="_compute_date", store=True
)
time = fields.Datetime(readonly=True)
date = fields.Date(readonly=True, compute="_compute_date", store=True)
tracking_email_id = fields.Many2one(
string="Message",
readonly=True,
@ -38,7 +36,6 @@ class MailTrackingEvent(models.Model):
index=True,
)
event_type = fields.Selection(
string="Event type",
selection=[
("sent", "Sent"),
("delivered", "Delivered"),
@ -56,7 +53,7 @@ class MailTrackingEvent(models.Model):
smtp_server = fields.Char(string="SMTP server", readonly=True)
url = fields.Char(string="Clicked URL", 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)
os_family = fields.Char(string="Operating system 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(
string="User country", readonly=True, comodel_name="res.country"
)
error_type = fields.Char(string="Error type", readonly=True)
error_description = fields.Char(string="Error description", readonly=True)
error_details = fields.Text(string="Error details", readonly=True)
error_type = fields.Char(readonly=True)
error_description = fields.Char(readonly=True)
error_details = fields.Text(readonly=True)
@api.depends("recipient")
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" ?>
<templates>
<t t-inherit="mail.ThreadIcon" t-inherit-mode="extension">
<xpath
expr="//t[@t-elif='thread === env.messaging.moderation']"
position="after"
>
<t t-elif="thread === env.messaging.failedmsg">
<xpath expr="//t[@t-elif='thread === messaging.history']" position="after">
<t t-elif="thread === messaging.failedmsg">
<div class="o_ThreadIcon_mailboxModeration fa fa-exclamation" />
</t>
</xpath>
</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">
<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">
<a
href="#"
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-att-data-message-id="message.id"
t-att-data-message-id="messageView.message.id"
>
<i class="fa fa-check" />
Set as Reviewed
@ -27,7 +45,7 @@
href="#"
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-att-data-message-id="message.id"
t-att-data-message-id="messageView.message.id"
>
<i class="fa fa-retweet" />
Retry

View File

@ -54,12 +54,17 @@
</span>
</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">
<t t-if="message.hasPartnerTrackings() || message.hasEmailCc()">
<t
t-if="messageView.message.hasPartnerTrackings() || messageView.message.hasEmailCc()"
>
<p class="o_mail_tracking">
<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>
@ -91,6 +96,7 @@
class="mail_tracking o_mail_action_tracking_status"
t-att-data-tracking="tracking['tracking_id']"
t-att-title="title_status"
type="button"
t-on-click="_onTrackingStatusClick"
>
<t t-call="mail.tracking.status" />

View File

@ -88,7 +88,8 @@ class TestMailTracking(TransactionCase):
"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
tracking_email = self.env["mail.tracking.email"].search(
[
@ -138,7 +139,10 @@ class TestMailTracking(TransactionCase):
"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
tracking_email = self.env["mail.tracking.email"].search(
[
@ -203,7 +207,8 @@ class TestMailTracking(TransactionCase):
"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()
self.assertEqual(len(recipients[self.recipient.id]), 3)
self._check_partner_trackings_cc(message)
@ -257,7 +262,8 @@ class TestMailTracking(TransactionCase):
"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()
self.assertEqual(len(recipients[self.recipient.id]), 4)
self._check_partner_trackings_to(message)
@ -316,7 +322,8 @@ class TestMailTracking(TransactionCase):
"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
tracking_email = self.env["mail.tracking.email"].search(
[
@ -330,7 +337,7 @@ class TestMailTracking(TransactionCase):
wizard = (
self.env["mail.resend.message"]
.sudo()
.with_context({"mail_message_to_resend": message.id})
.with_context(mail_message_to_resend=message.id)
.create({})
)
# 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). -->
<odoo>
<record model="ir.ui.view" id="view_mail_tracking_email_form">
<field name="name">mail.tracking.email.form</field>
<field name="model">mail.tracking.email</field>
<field name="arch" type="xml">
<form string="MailTracking event" create="false" edit="false" delete="false">
<header>
<field name="state" widget="statusbar" />
</header>
<sheet>
<group>
<field name="name" />
</group>
<group>
<record model="ir.ui.view" id="view_mail_tracking_email_form">
<field name="name">mail.tracking.email.form</field>
<field name="model">mail.tracking.email</field>
<field name="arch" type="xml">
<form
string="MailTracking event"
create="false"
edit="false"
delete="false"
>
<header>
<field name="state" widget="statusbar" />
</header>
<sheet>
<group>
<field name="mail_message_id" />
<field name="mail_id" />
<field name="partner_id" />
<field name="recipient" />
<field name="sender" />
<field name="name" />
</group>
<group>
<field name="timestamp" />
<field name="time" />
<field name="date" />
<group>
<field name="mail_message_id" />
<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 attrs="{'invisible': [('bounce_type', '=', False)]}">
<field name="bounce_type" />
<field name="bounce_description" />
</group>
<group attrs="{'invisible': [('error_type', '=', False)]}">
<field
<group attrs="{'invisible': [('bounce_type', '=', False)]}">
<field name="bounce_type" />
<field name="bounce_description" />
</group>
<group attrs="{'invisible': [('error_type', '=', False)]}">
<field
name="error_smtp_server"
attrs="{'invisible': [('error_smtp_server', '=', False)]}"
/>
<field name="error_type" />
<field name="error_description" />
</group>
<label for="tracking_event_ids" />
<div>
<field name="tracking_event_ids">
<tree
string="Tracking events"
<field name="error_type" />
<field name="error_description" />
</group>
<label for="tracking_event_ids" />
<div>
<field name="tracking_event_ids">
<tree
decoration-muted="event_type == 'deferral'"
decoration-danger="event_type in ('hard_bounce', 'soft_bounce', 'spam', 'reject')"
decoration-info="event_type in ('unsub', 'click', 'open')"
>
<field name="time" />
<field name="event_type" />
<field name="ip" />
<field name="url" />
<field name="user_country_id" string="Country" />
<field name="os_family" string="OS" />
<field name="ua_family" string="User agent" />
</tree>
</field>
</div>
</sheet>
</form>
</field>
</record>
<field name="time" />
<field name="event_type" />
<field name="ip" />
<field name="url" />
<field name="user_country_id" string="Country" />
<field name="os_family" string="OS" />
<field name="ua_family" string="User agent" />
</tree>
</field>
</div>
</sheet>
<footer />
</form>
</field>
</record>
<record model="ir.ui.view" id="view_mail_tracking_email_tree">
<field name="name">mail.tracking.email.tree</field>
<field name="model">mail.tracking.email</field>
<field name="arch" type="xml">
<tree
string="MailTracking emails"
<record model="ir.ui.view" id="view_mail_tracking_email_tree">
<field name="name">mail.tracking.email.tree</field>
<field name="model">mail.tracking.email</field>
<field name="arch" type="xml">
<tree
create="false"
edit="false"
delete="false"
@ -79,101 +83,105 @@
decoration-danger="state in ('rejected', 'spam', 'bounced', 'soft-bounced', 'error')"
decoration-info="state == 'unsub'"
>
<field name="time" />
<field name="date" invisible="1" />
<field name="name" />
<field name="sender" string="Sender" />
<field name="recipient" string="Recipient" />
<field name="state" />
</tree>
</field>
</record>
<field name="time" />
<field name="date" invisible="1" />
<field name="name" />
<field name="sender" string="Sender" />
<field name="recipient" string="Recipient" />
<field name="state" />
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_mail_tracking_email_search">
<field name="name">mail.tracking.email.search</field>
<field name="model">mail.tracking.email</field>
<field name="arch" type="xml">
<search string="MailTracking email search">
<field
<record model="ir.ui.view" id="view_mail_tracking_email_search">
<field name="name">mail.tracking.email.search</field>
<field name="model">mail.tracking.email</field>
<field name="arch" type="xml">
<search string="MailTracking email search">
<field
name="display_name"
string="Email"
filter_domain="['|', ('sender', 'ilike', self), ('recipient', 'ilike', self)]"
/>
<field
<field
name="sender"
string="Sender"
filter_domain="[('sender', 'ilike', self)]"
/>
<field
<field
name="recipient_address"
string="Recipient Address"
filter_domain="[('recipient_address', '=', self)]"
/>
<field name="name" string="Subject" />
<field name="time" string="Time" />
<field name="date" string="Date" />
<filter name="sent" string="Sent" domain="[('state', 'in', ('sent',))]" />
<filter
<field name="name" string="Subject" />
<field name="time" string="Time" />
<field name="date" string="Date" />
<filter
name="sent"
string="Sent"
domain="[('state', 'in', ('sent',))]"
/>
<filter
name="deferred"
string="Deferred"
domain="[('state', '=', 'deferred')]"
/>
<filter
<filter
name="delivered"
string="Delivered"
domain="[('state', 'in', ('delivered', 'opened'))]"
/>
<filter
<filter
name="unsub"
string="Unsubscribed"
domain="[('state', '=', 'unsub')]"
/>
<filter
<filter
name="exception"
string="Failed"
domain="[('state', 'in', ('error', 'rejected', 'spam', 'bounced', 'soft-bounced'))]"
/>
<separator />
<group expand="0" string="Group By">
<filter
<separator />
<group expand="0" string="Group By">
<filter
string="State"
name="group_by_state"
domain="[]"
context="{'group_by': 'state'}"
/>
<filter
<filter
string="Subject"
name="group_by_subject"
domain="[]"
context="{'group_by': 'name'}"
/>
<filter
<filter
string="Sender"
name="group_by_sender"
domain="[]"
context="{'group_by': 'sender'}"
/>
<filter
<filter
string="Month"
name="group_by_month"
domain="[]"
context="{'group_by': 'date'}"
/>
</group>
</search>
</field>
</record>
</group>
</search>
</field>
</record>
<record id="action_view_mail_tracking_email" model="ir.actions.act_window">
<field name="name">MailTracking emails</field>
<field name="res_model">mail.tracking.email</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_mail_tracking_email_search" />
</record>
<record id="action_view_mail_tracking_email" model="ir.actions.act_window">
<field name="name">MailTracking emails</field>
<field name="res_model">mail.tracking.email</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_mail_tracking_email_search" />
</record>
<!-- Add menu entry in Settings/Email -->
<menuitem
<!-- Add menu entry in Settings/Email -->
<menuitem
name="Tracking emails"
id="menu_mail_tracking_email"
parent="base.menu_email"

View File

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