[IMP] mass_mailing: improve A/B testing usability
PURPOSE Provide various fixes and UX improvements to ease usage of Emails and SMS marketing applications. SPECIFICATIONS - Add a compute field ab_testing_sms_count to compute the counts related to A/B testing SMS. - Add an alert message for A/B testing when no mail is sent in the form view with the chatter. - Add "(final)" to the source name of the final and winner A/B test mail. - The buttons are now correctly display based on the mailing_type (the auto mode can now send a winner manually) - Change the copy override of `utm.source.mixin` to consider the name param of default arg. when getting a unique name for the record. Task-2713198 closes odoo/odoo#88997 Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
@@ -108,13 +108,16 @@
|
||||
<data noupdate="0">
|
||||
<template id="ab_testing_description" name="Mass Mailing: A/B Test Description">
|
||||
<div name="ab_testing_description" class="mb-2">
|
||||
<t t-if="mailing.state == 'done' and mailing.sent == 0 and mailing.failed == 0 and mailing.canceled == 0">
|
||||
<span>There wasn't enough recipients left for this mailing</span>
|
||||
</t>
|
||||
<t t-if="mailing.ab_testing_completed">
|
||||
<p t-if="mailing.ab_testing_is_winner_mailing">
|
||||
This <t t-out="mailing.mailing_type_description"/> is the winner of the A/B testing campaign and has been sent to all remaining recipients.
|
||||
</p>
|
||||
<p t-else="">The winner has already been sent. Use <b>Compare Version</b> to get an overview of this A/B testing campaign.</p>
|
||||
</t>
|
||||
<t t-elif="mailing.ab_testing_mailings_count >= 2">
|
||||
<t t-elif="ab_testing_count >= 2">
|
||||
<p>
|
||||
A sample of <b><t t-out="mailing.ab_testing_pc"/>% of recipients</b> will receive this version.<br/>
|
||||
<t t-if="total_ab_testing_pc > 100 and mailing.active">
|
||||
|
||||
@@ -203,6 +203,7 @@ class MassMailing(models.Model):
|
||||
related="campaign_id.ab_testing_winner_selection", readonly=False,
|
||||
default="opened_ratio",
|
||||
copy=True)
|
||||
is_ab_test_sent = fields.Boolean(compute="_compute_is_ab_test_sent")
|
||||
kpi_mail_required = fields.Boolean('KPI mail required', copy=False)
|
||||
# statistics data
|
||||
mailing_trace_ids = fields.One2many('mailing.trace', 'mass_mailing_id', string='Emails Statistics')
|
||||
@@ -472,6 +473,13 @@ class MassMailing(models.Model):
|
||||
mailing._get_ab_testing_description_values()
|
||||
)
|
||||
|
||||
@api.depends('campaign_id.mailing_mail_ids.state')
|
||||
def _compute_is_ab_test_sent(self):
|
||||
for rec in self:
|
||||
ab_testing_mailings = rec._get_ab_testing_siblings_mailings()
|
||||
selected_mailings = ab_testing_mailings.filtered(lambda m: m.state == 'done')
|
||||
rec.is_ab_test_sent = bool(selected_mailings)
|
||||
|
||||
def _get_ab_testing_description_modifying_fields(self):
|
||||
return ['ab_testing_enabled', 'ab_testing_pc', 'ab_testing_schedule_datetime', 'ab_testing_winner_selection', 'campaign_id']
|
||||
|
||||
@@ -853,6 +861,7 @@ class MassMailing(models.Model):
|
||||
raise ValueError(_("A/B test option has not been enabled"))
|
||||
final_mailing = self.copy({
|
||||
'ab_testing_pc': 100,
|
||||
'name': _(" %(subject)s (final)", subject=self.name) # Add suffix on name to show it's the final mailing
|
||||
})
|
||||
self.campaign_id.ab_testing_winner_mailing_id = final_mailing
|
||||
final_mailing.action_launch()
|
||||
@@ -868,6 +877,7 @@ class MassMailing(models.Model):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'mailing': self,
|
||||
'ab_testing_count': self.ab_testing_mailings_count,
|
||||
'ab_testing_winner_selection_description': self._get_ab_testing_winner_selection()['description'],
|
||||
'total_ab_testing_pc': sum([
|
||||
mailing.ab_testing_pc for mailing in self._get_ab_testing_siblings_mailings()
|
||||
|
||||
@@ -48,6 +48,11 @@
|
||||
width: 135px;
|
||||
}
|
||||
}
|
||||
|
||||
.o_notebook .tab-content .tab-pane .alert {
|
||||
margin-left: -$o-sheet-cancel-hpadding;
|
||||
margin-right: -$o-sheet-cancel-hpadding;
|
||||
}
|
||||
}
|
||||
|
||||
.o_form_view.o_mass_mailing_mailing_form .o_form_renderer {
|
||||
@@ -178,15 +183,11 @@
|
||||
}
|
||||
|
||||
.o_mass_mailing_mailing_form.o_mass_mailing_form_full_width .alert.alert-info:not(.o_invisible_modifier) {
|
||||
margin: 0 0 0 12px;
|
||||
flex: 1;
|
||||
padding: unset;
|
||||
padding: 0.5rem 0.5rem;
|
||||
border-left-width: 1px;
|
||||
border-left-color: #dee2e6;
|
||||
|
||||
> div {
|
||||
min-height: 100%;
|
||||
padding: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.o_form_view.o_xxl_form_view .o_mass_mailing_mailing_form {
|
||||
|
||||
@@ -72,61 +72,63 @@
|
||||
<button name="action_cancel" type="object" attrs="{'invisible': [('state', '!=', 'in_queue')]}" class="btn-secondary" string="Cancel" data-hotkey="z"/>
|
||||
<button name="action_retry_failed" type="object" attrs="{'invisible': ['|', ('state', '!=', 'done'), ('failed', '=', 0)]}" class="oe_highlight" string="Retry" data-hotkey="y"/>
|
||||
|
||||
<div class="alert alert-info mb-2 text-center" role="alert"
|
||||
attrs="{'invisible': ['&','&','&','&','&',('state', 'not in', ['in_queue', 'done']),('sent', '=', 0),('canceled', '=', 0),('scheduled', '=', 0),('failed', '=', 0),('warning_message', '=', False)]}">
|
||||
<div class="o_mails_canceled" attrs="{'invisible': [('canceled', '=', 0)]}">
|
||||
<button class="btn-link py-0"
|
||||
name="action_view_traces_canceled"
|
||||
type="object">
|
||||
<strong>
|
||||
<field name="canceled" class="oe_inline me-2"/>
|
||||
<span name="canceled_text">emails have been canceled and will not be sent.</span>
|
||||
</strong>
|
||||
</button>
|
||||
</div>
|
||||
<div class="o_mails_scheduled" attrs="{'invisible': [('scheduled', '=', 0)]}">
|
||||
<button class="btn-link py-0"
|
||||
name="action_view_traces_scheduled"
|
||||
type="object">
|
||||
<strong>
|
||||
<field name="scheduled" class="oe_inline me-2"/>
|
||||
<span name="scheduled_text">emails are in queue and will be sent soon.</span>
|
||||
</strong>
|
||||
</button>
|
||||
</div>
|
||||
<div class="o_mails_sent" attrs="{'invisible': ['&', ('sent', '=', 0), ('state', 'in', ('draft', 'test', 'in_queue'))]}">
|
||||
<button class="btn-link py-0"
|
||||
name="action_view_traces_sent"
|
||||
type="object">
|
||||
<strong>
|
||||
<field name="sent" class="oe_inline me-2"/>
|
||||
<span name="sent">emails have been sent.</span>
|
||||
</strong>
|
||||
</button>
|
||||
<strong class="d-block" attrs="{'invisible': ['|', ('mailing_type', '=', 'mail'), '|', ('ab_testing_enabled', '=', False), '|', ('state', '!=', 'done'), '|', ('sent', '!=', 0), '|', ('failed', '!=', 0), ('canceled', '!=', 0)]}">
|
||||
<span name="ab_test_text">There wasn't enough recipients given to this mailing. </span>
|
||||
</strong>
|
||||
</div>
|
||||
<div class="o_mails_failed" attrs="{'invisible': ['|', ('state', '!=', 'done'), ('failed', '=', 0)]}">
|
||||
<button class="btn-link py-0"
|
||||
name="action_view_traces_failed"
|
||||
type="object">
|
||||
<strong>
|
||||
<field name="failed" class="oe_inline me-2"/>
|
||||
<span name="failed_text">emails could not be sent.</span>
|
||||
</strong>
|
||||
</button>
|
||||
</div>
|
||||
<div class="o_mails_in_queue" attrs="{'invisible': [('state', '!=', 'in_queue')]}">
|
||||
<strong>
|
||||
<span name="next_departure_text">This mailing is scheduled for </span>
|
||||
<field name="next_departure" class="oe_inline"/>.
|
||||
</strong>
|
||||
</div>
|
||||
<div attrs="{'invisible': [('warning_message', '=', False)]}">
|
||||
<strong><field name="warning_message"/></strong>
|
||||
</div>
|
||||
</div>
|
||||
<field name="state" readonly="1" widget="statusbar"/>
|
||||
</header>
|
||||
<div class="alert alert-info mb-2 text-center" role="alert"
|
||||
attrs="{'invisible': ['&','&','&','&','&',('state', '!=', 'in_queue'),('sent', '=', 0),('canceled', '=', 0),('scheduled', '=', 0),('failed', '=', 0),('warning_message', '=', False)]}">
|
||||
<div class="o_mails_canceled" attrs="{'invisible': [('canceled', '=', 0)]}">
|
||||
<button class="btn-link py-0"
|
||||
name="action_view_traces_canceled"
|
||||
type="object">
|
||||
<strong>
|
||||
<field name="canceled" class="oe_inline me-2"/>
|
||||
<span name="canceled_text">emails have been canceled and will not be sent.</span>
|
||||
</strong>
|
||||
</button>
|
||||
</div>
|
||||
<div class="o_mails_scheduled" attrs="{'invisible': [('scheduled', '=', 0)]}">
|
||||
<button class="btn-link py-0"
|
||||
name="action_view_traces_scheduled"
|
||||
type="object">
|
||||
<strong>
|
||||
<field name="scheduled" class="oe_inline me-2"/>
|
||||
<span name="scheduled_text">emails are in queue and will be sent soon.</span>
|
||||
</strong>
|
||||
</button>
|
||||
</div>
|
||||
<div class="o_mails_sent" attrs="{'invisible': ['&', ('sent', '=', 0), ('state', 'in', ('draft', 'test', 'in_queue'))]}">
|
||||
<button class="btn-link py-0"
|
||||
name="action_view_traces_sent"
|
||||
type="object">
|
||||
<strong>
|
||||
<field name="sent" class="oe_inline me-2"/>
|
||||
<span name="sent">emails have been sent.</span>
|
||||
</strong>
|
||||
</button>
|
||||
</div>
|
||||
<div class="o_mails_failed" attrs="{'invisible': ['|', ('state', '!=', 'done'), ('failed', '=', 0)]}">
|
||||
<button class="btn-link py-0"
|
||||
name="action_view_traces_failed"
|
||||
type="object">
|
||||
<strong>
|
||||
<field name="failed" class="oe_inline me-2"/>
|
||||
<span name="failed_text">emails could not be sent.</span>
|
||||
</strong>
|
||||
</button>
|
||||
</div>
|
||||
<div class="o_mails_in_queue" attrs="{'invisible': [('state', '!=', 'in_queue')]}">
|
||||
<strong>
|
||||
<span name="next_departure_text">This mailing is scheduled for </span>
|
||||
<field name="next_departure" class="oe_inline"/>.
|
||||
</strong>
|
||||
</div>
|
||||
<div attrs="{'invisible': [('warning_message', '=', False)]}">
|
||||
<strong><field name="warning_message"/></strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="action_view_delivered"
|
||||
@@ -274,22 +276,21 @@
|
||||
attrs="{'required': [('ab_testing_enabled', '=', True), ('mailing_type', '=', 'mail')], 'invisible': ['|', ('ab_testing_enabled', '=', False), ('mailing_type', '!=', 'mail')], 'readonly': [('state', '!=', 'draft')]}"/>
|
||||
<field name="ab_testing_schedule_datetime"
|
||||
attrs="{'required': [('ab_testing_enabled', '=', True), ('ab_testing_winner_selection', '!=', 'manual')], 'readonly': ['|', ('ab_testing_enabled', '=', False), ('state', '!=', 'draft')], 'invisible': ['|', ('ab_testing_enabled', '=', False), ('ab_testing_winner_selection', '=', 'manual')]}"/>
|
||||
<field name="is_ab_test_sent" invisible="1"/>
|
||||
</group>
|
||||
<div>
|
||||
<field name="ab_testing_mailings_count" invisible="1"/>
|
||||
<field name="ab_testing_completed" invisible="1"/>
|
||||
<field name="ab_testing_description" nolabel="1"/>
|
||||
<div attrs="{'invisible': ['|', ('ab_testing_mailings_count', '<', 2), ('ab_testing_enabled', '=', False)]}">
|
||||
<div id="mailing_form_ab_buttons" attrs="{'invisible': ['|', ('ab_testing_mailings_count', '<', 2), ('ab_testing_enabled', '=', False)]}">
|
||||
<button name="action_compare_versions" type="object" class="btn btn-link d-block">
|
||||
<i class="fa fa-bar-chart"/> Compare Version
|
||||
</button>
|
||||
<button name="action_duplicate" type="object" class="btn btn-link d-block" attrs="{'invisible': [('ab_testing_completed', '=', True)]}">
|
||||
<i class="fa fa-copy"/> Create an Alternative
|
||||
</button>
|
||||
<button name="action_send_winner_mailing" type="object" class="btn btn-link d-block" attrs="{'invisible': [('ab_testing_completed', '=', True)]}">
|
||||
<i class="fa fa-envelope"/> <span name="ab_test_manual" attrs="{'invisible': [('ab_testing_winner_selection', '!=', 'manual')]}">
|
||||
Send this version to remaining recipients
|
||||
</span> <span name="ab_test_auto" attrs="{'invisible': [('ab_testing_winner_selection', '=', 'manual')]}">
|
||||
<button name="action_send_winner_mailing" type="object" class="btn btn-link d-block" attrs="{'invisible': ['|', ('is_ab_test_sent', '!=', True), '|', ('ab_testing_completed', '=', True), ('ab_testing_winner_selection', '=', 'manual')]}">
|
||||
<i class="fa fa-envelope"/><span name="ab_test_auto">
|
||||
Send Winner Now
|
||||
</span>
|
||||
</button>
|
||||
|
||||
@@ -52,6 +52,7 @@ class Mailing(models.Model):
|
||||
ab_testing_sms_winner_selection = fields.Selection(
|
||||
related="campaign_id.ab_testing_sms_winner_selection",
|
||||
default="clicks_ratio", readonly=False, copy=True)
|
||||
ab_testing_mailings_sms_count = fields.Integer(related="campaign_id.ab_testing_mailings_sms_count")
|
||||
|
||||
@api.depends('mailing_type')
|
||||
def _compute_medium_id(self):
|
||||
@@ -337,6 +338,7 @@ class Mailing(models.Model):
|
||||
values = super()._get_ab_testing_description_values()
|
||||
if self.mailing_type == 'sms':
|
||||
values.update({
|
||||
'ab_testing_count': self.ab_testing_mailings_sms_count,
|
||||
'ab_testing_winner_selection': self.ab_testing_sms_winner_selection,
|
||||
})
|
||||
return values
|
||||
|
||||
@@ -159,6 +159,9 @@
|
||||
('mail_server_available', '=', False)], 'readonly': [('state', 'in', ('sending', 'done'))]}</attribute>
|
||||
</xpath>
|
||||
<!-- A/B Testing -->
|
||||
<xpath expr="//field[@name='ab_testing_mailings_count']" position="after">
|
||||
<field name="ab_testing_mailings_sms_count" invisible="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='ab_testing_winner_selection']" position="after">
|
||||
<label for="ab_testing_sms_winner_selection" string="Winner Selection"
|
||||
attrs="{'invisible': ['|', ('ab_testing_enabled', '=', False), ('mailing_type', '!=', 'sms')]}"/>
|
||||
@@ -169,19 +172,21 @@
|
||||
<field name="ab_testing_schedule_datetime"
|
||||
attrs="{'required': [('ab_testing_enabled', '=', True), ('ab_testing_winner_selection', '!=', 'manual'), ('ab_testing_sms_winner_selection', '!=', 'manual')], 'readonly': ['|', ('ab_testing_enabled', '=', False), ('state', '!=', 'draft')], 'invisible': ['|', '|', ('ab_testing_enabled', '=', False), ('ab_testing_winner_selection', '=', 'manual'), ('ab_testing_sms_winner_selection', '=', 'manual')]}"/>
|
||||
</xpath>
|
||||
<xpath expr="//span[@name='ab_test_manual']" position="attributes">
|
||||
<attribute name="attrs">{'invisible': ['|', ('ab_testing_winner_selection', '!=', 'manual'),
|
||||
('ab_testing_sms_winner_selection', '!=', 'manual')]}</attribute>
|
||||
<xpath expr="//div[@id='mailing_form_ab_buttons']" position="attributes">
|
||||
<attribute name="attrs">{'invisible': ['|', ('ab_testing_enabled', '=', False), '&',
|
||||
('ab_testing_mailings_count', '<', 2),
|
||||
('ab_testing_mailings_sms_count', '<', 2)]}</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//span[@name='ab_test_auto']" position="attributes">
|
||||
<attribute name="attrs">{'invisible': [('ab_testing_winner_selection', '=', 'manual'),
|
||||
('ab_testing_sms_winner_selection', '=', 'manual')]}</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//button[@name='action_select_as_winner']" position="attributes">
|
||||
<attribute name="attrs">{'invisible': ['|', ('ab_testing_completed', '!=', False), '|',
|
||||
<xpath expr="//button[@name='action_send_winner_mailing']" position="attributes">
|
||||
<attribute name="attrs">{'invisible': ['|', ('is_ab_test_sent', '!=', True), '|', ('ab_testing_completed', '=', True), '|',
|
||||
('ab_testing_winner_selection', '=', 'manual'),
|
||||
('ab_testing_sms_winner_selection', '=', 'manual')]}</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//button[@name='action_duplicate'][hasclass('btn-primary')]" position="attributes">
|
||||
<attribute name="attrs">{'invisible': ['|', '|', ('ab_testing_enabled', '=', False),
|
||||
('ab_testing_mailings_count', '>=', 2),
|
||||
('ab_testing_mailings_sms_count', '>=', 2)]}</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -82,5 +82,6 @@ class UtmSourceMixin(models.AbstractModel):
|
||||
def copy(self, default=None):
|
||||
"""Increment the counter when duplicating the source."""
|
||||
default = default or {}
|
||||
default['name'] = self.env['utm.mixin']._get_unique_names(self._name, [self.name])[0]
|
||||
default_name = default.get('name')
|
||||
default['name'] = self.env['utm.mixin']._get_unique_names(self._name, [default_name or self.name])[0]
|
||||
return super().copy(default)
|
||||
|
||||
Reference in New Issue
Block a user