[IMP] mass_mailing: improve UX of favorite filter
If you do not have favorite filters available in the mass mailing for selected model, we display empty drop down, which is not ideal. This commit improves the behavior with following changes: - If there is no filter available (for this target) and no condition set in the mailing domain, do not display anything - If mailing domain is set but there is not filter available, show a custom label "Save Filter as Favorite" save icon next to it Ent PR: https://github.com/odoo/enterprise/pull/26308 task-2711258 closes odoo/odoo#87673 Related: odoo/enterprise#26308 Signed-off-by: Warnon Aurélien (awa) <awa@odoo.com>
This commit is contained in:
@@ -126,11 +126,6 @@ class MassMailing(models.Model):
|
||||
mailing_domain = fields.Char(
|
||||
string='Domain', compute='_compute_mailing_domain',
|
||||
readonly=False, store=True)
|
||||
mailing_filter_id = fields.Many2one(
|
||||
'mailing.filter', string='Favorite Filter',
|
||||
domain="[('mailing_model_name', '=', mailing_model_name)]",
|
||||
compute='_compute_mailing_filter_id', readonly=False, store=True)
|
||||
mailing_filter_domain = fields.Char('Favorite filter domain', related='mailing_filter_id.mailing_domain')
|
||||
mail_server_available = fields.Boolean(
|
||||
compute='_compute_mail_server_available',
|
||||
help="Technical field used to know if the user has activated the outgoing mail server option in the settings")
|
||||
@@ -138,6 +133,13 @@ class MassMailing(models.Model):
|
||||
default=_get_default_mail_server_id,
|
||||
help="Use a specific mail server in priority. Otherwise Odoo relies on the first outgoing mail server available (based on their sequencing) as it does for normal mails.")
|
||||
contact_list_ids = fields.Many2many('mailing.list', 'mail_mass_mailing_list_rel', string='Mailing Lists')
|
||||
# Mailing Filter
|
||||
mailing_filter_id = fields.Many2one(
|
||||
'mailing.filter', string='Favorite Filter',
|
||||
domain="[('mailing_model_name', '=', mailing_model_name)]",
|
||||
compute='_compute_mailing_filter_id', readonly=False, store=True)
|
||||
mailing_filter_domain = fields.Char('Favorite filter domain', related='mailing_filter_id.mailing_domain')
|
||||
mailing_filter_count = fields.Integer('# Favorite Filters', compute='_compute_mailing_filter_count')
|
||||
# A/B Testing
|
||||
ab_testing_completed = fields.Boolean(related='campaign_id.ab_testing_completed', store=True)
|
||||
ab_testing_description = fields.Html('A/B Testing Description', compute="_compute_ab_testing_description")
|
||||
@@ -331,6 +333,15 @@ class MassMailing(models.Model):
|
||||
elif mailing.reply_to_mode == 'update':
|
||||
mailing.reply_to = False
|
||||
|
||||
@api.depends('mailing_model_id', 'mailing_domain')
|
||||
def _compute_mailing_filter_count(self):
|
||||
filter_data = self.env['mailing.filter']._read_group([
|
||||
('mailing_model_id', 'in', self.mailing_model_id.ids)
|
||||
], ['mailing_model_id'], ['mailing_model_id'])
|
||||
mapped_data = {data['mailing_model_id'][0]: data['mailing_model_id_count'] for data in filter_data}
|
||||
for mailing in self:
|
||||
mailing.mailing_filter_count = mapped_data.get(mailing.mailing_model_id.id, 0)
|
||||
|
||||
@api.depends('mailing_model_id')
|
||||
def _compute_mailing_model_real(self):
|
||||
for mailing in self:
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { FieldMany2One } from 'web.relational_fields';
|
||||
import core from 'web.core';
|
||||
import Domain from 'web.Domain';
|
||||
import fieldRegistry from 'web.field_registry';
|
||||
|
||||
const _t = core._t;
|
||||
@@ -67,8 +68,9 @@ const FieldMailingFilter = FieldMany2One.extend({
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the 'Add to favorite' / 'Remove' star icons' visibility based on
|
||||
* the current state.
|
||||
* Updates the 'Add to favorite' / 'Remove' icons' visibility based on the
|
||||
* current state, and shows the custom message when no filter is available
|
||||
* for the selected model.
|
||||
*
|
||||
* The filter can be saved if one of those conditions is matched:
|
||||
* - No favorite filter is currently set
|
||||
@@ -80,12 +82,18 @@ const FieldMailingFilter = FieldMany2One.extend({
|
||||
* @private
|
||||
*/
|
||||
_updateFilterIcons: function () {
|
||||
const hasDomain = this.recordData[this.domainFieldName] && this.recordData[this.domainFieldName] !== '[]';
|
||||
this.el.querySelector('.o_mass_mailing_filter_container').classList.toggle('invisible', !hasDomain);
|
||||
const filterCount = this.recordData.mailing_filter_count;
|
||||
this.el.querySelector('.o_mass_mailing_no_filter').classList.toggle('d-none', filterCount);
|
||||
this.el.querySelector('.o_field_many2one_selection > .o_input_dropdown').classList.toggle('d-none', !filterCount);
|
||||
// By default, domains in recordData are in string format, but adding / removing a leaf from domain widget converts
|
||||
// value into object, so we use 'Domain' class to convert them in same (string) format, allowing proper comparison.
|
||||
const recordDomain = new Domain(this.recordData[this.domainFieldName] || []).toString();
|
||||
const filterDomain = new Domain(this.recordData.mailing_filter_domain || []).toString();
|
||||
this.el.querySelector('.o_mass_mailing_filter_container').classList.toggle('d-none', recordDomain === '[]');
|
||||
const canSaveFilter = !this.recordData.mailing_filter_id
|
||||
|| !this.$input.val().length
|
||||
|| this.floating
|
||||
|| this.recordData.mailing_filter_domain !== this.recordData[this.domainFieldName];
|
||||
|| filterDomain !== recordDomain;
|
||||
this.el.querySelector('.o_mass_mailing_save_filter_container').classList.toggle('d-none', !canSaveFilter);
|
||||
this.el.querySelector('.o_mass_mailing_remove_filter').classList.toggle('d-none', canSaveFilter);
|
||||
},
|
||||
@@ -158,7 +166,7 @@ const FieldMailingFilter = FieldMany2One.extend({
|
||||
// Prevent multiple clicks to avoid trying to deleting same record multiple times.
|
||||
ev.target.disabled = true;
|
||||
// Avoid calling any 'onchange' (that depends on filter and might reset the domain) immediately
|
||||
// after filter is removed. It should have any side effects because we don't want to change
|
||||
// after filter is removed. It shouldn't have any side effects because we don't want to change
|
||||
// anything in any case when filter is unliked with with this widget.
|
||||
this._setValue(false, {notifyChange: false});
|
||||
await this._rpc({
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
<t t-jquery="div.o_field_many2one_selection" t-operation="append">
|
||||
<div class="o_mass_mailing_filter_container">
|
||||
<div class="o_mass_mailing_save_filter_container pt-1">
|
||||
<i class="o_mass_mailing_add_filter fa fa-floppy-o px-3 py-1" data-toggle="dropdown" title="Add to favorite filters"/>
|
||||
<span class="o_mass_mailing_add_filter" data-toggle="dropdown" title="Add to favorite filters">
|
||||
<span class="o_mass_mailing_no_filter">Save as Favorite Filter</span>
|
||||
<i class="fa fa-floppy-o px-3 py-1"/>
|
||||
</span>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<span class="ml-2 text-muted">Create Favorite Filter</span><br/>
|
||||
<div class="d-inline-flex">
|
||||
|
||||
@@ -40,6 +40,10 @@ QUnit.module('favorite filter widget', {
|
||||
type: 'char',
|
||||
related: 'mailing_filter_id.mailing_domain',
|
||||
},
|
||||
mailing_filter_count: {
|
||||
string: 'filter Count',
|
||||
type: 'integer',
|
||||
},
|
||||
},
|
||||
records: [{
|
||||
id: 1,
|
||||
@@ -49,12 +53,14 @@ QUnit.module('favorite filter widget', {
|
||||
mailing_model_name: 'event',
|
||||
mailing_domain: '[["country","=","be"]]',
|
||||
mailing_filter_id: 1,
|
||||
mailing_filter_count: 1,
|
||||
mailing_filter_domain: '[["country","=","be"]]',
|
||||
}, {
|
||||
id: 2,
|
||||
display_name: 'New Users Promotion',
|
||||
subject: 'Early bird discount for new users! Register Now!',
|
||||
mailing_model_id: 1,
|
||||
mailing_filter_count: 1,
|
||||
mailing_model_name: 'event',
|
||||
mailing_domain: '[["new_user","=",True]]',
|
||||
mailing_filter_domain: '[["new_user","=",True]]',
|
||||
@@ -111,6 +117,7 @@ QUnit.module('favorite filter widget', {
|
||||
<field name="mailing_domain"/>
|
||||
<field name="mailing_model_name" invisible="1"/>
|
||||
<field name="mailing_model_id"/>
|
||||
<field name="mailing_filter_count"/>
|
||||
<field name="mailing_filter_domain" invisible="1"/>
|
||||
<field name="mailing_filter_id"
|
||||
widget="mailing_filter"
|
||||
@@ -174,6 +181,7 @@ QUnit.module('favorite filter widget', {
|
||||
<field name="mailing_domain"/>
|
||||
<field name="mailing_model_id"/>
|
||||
<field name="mailing_filter_domain" invisible="1"/>
|
||||
<field name="mailing_filter_count"/>
|
||||
<field name="mailing_filter_id"
|
||||
widget="mailing_filter"
|
||||
options="{'no_create': '1', 'no_open': '1', 'domain_field': 'mailing_domain', 'model': 'mailing_model_id'}"/>
|
||||
@@ -246,6 +254,7 @@ QUnit.module('favorite filter widget', {
|
||||
subject: 'Early bird discount for Partners!',
|
||||
mailing_model_id: 2,
|
||||
mailing_model_name: 'partner',
|
||||
mailing_filter_count: 1,
|
||||
mailing_domain: "[['name','!=', 'Azure Interior']]",
|
||||
});
|
||||
|
||||
@@ -264,6 +273,7 @@ QUnit.module('favorite filter widget', {
|
||||
<field name="subject"/>
|
||||
<field name="mailing_model_name" invisible="1"/>
|
||||
<field name="mailing_model_id"/>
|
||||
<field name="mailing_filter_count" />
|
||||
<field name="mailing_filter_id" widget="mailing_filter" options="{'no_create': '1', 'no_open': '1', 'domain_field': 'mailing_domain', 'model': 'mailing_model_id'}"/>
|
||||
<group>
|
||||
<field name="mailing_domain"
|
||||
@@ -287,5 +297,121 @@ QUnit.module('favorite filter widget', {
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('filter drop-down and filter icons visibility toggles properly based on filters available', async function (assert) {
|
||||
assert.expect(11);
|
||||
|
||||
this.data.partner = {
|
||||
fields: {
|
||||
name: {string: 'Name', type: 'char', searchable: true},
|
||||
},
|
||||
records: [
|
||||
{id: 1, name: 'Azure Interior'},
|
||||
]
|
||||
};
|
||||
this.data.event = {
|
||||
fields: {
|
||||
name: {string: 'Name', type: 'char', searchable: true},
|
||||
country: {string: 'Country', type: 'char', searchable: true},
|
||||
},
|
||||
records: [
|
||||
{id: 1, name: 'BE Event', country: 'be'},
|
||||
]
|
||||
};
|
||||
|
||||
this.data['mailing.filter'].records = [{
|
||||
id: 2,
|
||||
name: 'Azure partner',
|
||||
mailing_domain: '[["name","=","Azure Interior"]]',
|
||||
mailing_model_id: 2,
|
||||
}, {
|
||||
id: 3,
|
||||
name: 'Ready Mat partner',
|
||||
mailing_domain: '[["name","=","Ready Mat"]]',
|
||||
mailing_model_id: 2,
|
||||
}];
|
||||
|
||||
this.data['mailing.mailing'].records = [{
|
||||
id: 1,
|
||||
display_name: 'Belgian Event promotion',
|
||||
subject: 'Early bird discount for Belgian Events! Register Now!',
|
||||
mailing_model_id: 1,
|
||||
mailing_model_name: 'event',
|
||||
mailing_domain: '[["country","=","be"]]',
|
||||
mailing_filter_id: false,
|
||||
mailing_filter_count: 0,
|
||||
}];
|
||||
|
||||
this.data['mailing.mailing'].onchanges = {
|
||||
mailing_model_id: obj => {
|
||||
obj.mailing_filter_count = this.data['mailing.filter'].records.filter(r => r.mailing_model_id === obj.mailing_model_id).length;
|
||||
},
|
||||
mailing_filter_id: obj => {
|
||||
const filterDomain = this.data['mailing.filter'].records.filter(r => r.id === obj.mailing_filter_id)[0].mailing_domain;
|
||||
obj.mailing_domain = filterDomain, obj.mailing_filter_domain = filterDomain;
|
||||
},
|
||||
};
|
||||
|
||||
const form = await testUtils.createView({
|
||||
View: FormView,
|
||||
model: 'mailing.mailing',
|
||||
data: this.data,
|
||||
arch: `<form>
|
||||
<field name="display_name"/>
|
||||
<field name="subject"/>
|
||||
<field name="mailing_model_name" invisible="1"/>
|
||||
<field name="mailing_model_id"/>
|
||||
<field name="mailing_filter_count" />
|
||||
<field name="mailing_filter_id" widget="mailing_filter"
|
||||
options="{'no_create': '1', 'no_open': '1', 'domain_field': 'mailing_domain', 'model': 'mailing_model_id'}"/>
|
||||
<field name="mailing_filter_domain" invisible="1"/>
|
||||
<group>
|
||||
<field name="mailing_domain"
|
||||
widget="domain"
|
||||
options="{'model': 'mailing_model_name'}"/>
|
||||
</group>
|
||||
</form>`,
|
||||
res_id: 1,
|
||||
});
|
||||
|
||||
await testUtils.form.clickEdit(form);
|
||||
assert.isNotVisible(form.$('.o_field_many2one[name="mailing_filter_id"] .o_input_dropdown'),
|
||||
"should hide the drop-down to select a filter because there is no filter available for 'Event'");
|
||||
assert.isVisible(form.$('.o_mass_mailing_no_filter'),
|
||||
"should show custom message because there is no filter available for 'Event'");
|
||||
assert.isVisible(form.$('.o_mass_mailing_save_filter_container'),
|
||||
"should show icon to save the filter because domain is set in the mailing");
|
||||
|
||||
// If domain is not set on mailing and no filter available, both drop-down and icon container are hidden
|
||||
await testUtils.dom.click(form.$('.o_domain_delete_node_button'));
|
||||
assert.isNotVisible(form.$('.o_field_many2one[name="mailing_filter_id"] .o_input_dropdown'),
|
||||
"should not display drop-down because there is still no filter available to select from");
|
||||
assert.isNotVisible(form.$('.o_mass_mailing_filter_container'),
|
||||
"should not show filter container because there is no filter available and no domain set in mailing");
|
||||
|
||||
// If domain is not set on mailing but filters available, display drop-down but hide the icon container
|
||||
await testUtils.fields.many2one.clickOpenDropdown('mailing_model_id');
|
||||
await testUtils.fields.many2one.clickItem('mailing_model_id', 'Partner');
|
||||
assert.isVisible(form.$('.o_field_many2one[name="mailing_filter_id"] .o_input_dropdown'),
|
||||
"should show the drop-down to select a filter because there are filters available for 'Partner'");
|
||||
assert.isNotVisible(form.$('.o_mass_mailing_filter_container'),
|
||||
"should not show filter container because there is no filter selected and no domain set in mailing");
|
||||
|
||||
// Save / Remove icons visibility
|
||||
await testUtils.dom.click('.o_field_many2one[name="mailing_filter_id"] input');
|
||||
await testUtils.fields.many2one.clickItem('mailing_filter_id', 'Azure partner');
|
||||
assert.isVisible(form.$('.o_mass_mailing_remove_filter'),
|
||||
"should have option to remove filter if filter is selected");
|
||||
assert.isNotVisible(form.$('.o_mass_mailing_save_filter_container'),
|
||||
"should not have option to save filter if filter is selected");
|
||||
|
||||
await testUtils.dom.click(form.$('.o_domain_add_node_button').first());
|
||||
assert.isVisible(form.$('.o_mass_mailing_save_filter_container'),
|
||||
"should have option to save filter because mailing domain is changed");
|
||||
assert.isNotVisible(form.$('.o_mass_mailing_remove_filter'),
|
||||
"should not have option to remove filter because mailing domain is changed");
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -219,6 +219,7 @@
|
||||
options="{'no_create': 1, 'no_open': 1, 'domain_field': 'mailing_domain', 'model_field': 'mailing_model_id'}"
|
||||
attrs="{'readonly': [('state', 'in', ('sending', 'done'))]}"/>
|
||||
</div>
|
||||
<field name="mailing_filter_count" invisible="1"/>
|
||||
<field name="mailing_filter_domain" invisible="1"/>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user