[IMP] mail: add auto_comment message type
Currently some auto_reply templates are internal as a means to prevent notifying users everytime we send a reply to somebody. This raises the issue that since responses to internal notifications are themselves internal notifications, often nobody will be notified of responses to these automated messages. We fix this by marking these messages as auto_comments, which will ensure responses to these messages are considered non-internal (and thus 'discussions', by default). task-2834304 closes odoo/odoo#94018 Related: odoo/enterprise#35466 Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
@@ -110,6 +110,7 @@ class Message(models.Model):
|
||||
('email', 'Email'),
|
||||
('comment', 'Comment'),
|
||||
('notification', 'System notification'),
|
||||
('auto_comment', 'Automated Targeted Notification'),
|
||||
('user_notification', 'User Specific Notification')],
|
||||
'Type', required=True, default='email',
|
||||
help="Message type: email for email message, notification for system "
|
||||
|
||||
@@ -670,6 +670,7 @@ class MailThread(models.AbstractModel):
|
||||
continue
|
||||
|
||||
composition_mode = post_kwargs.pop('composition_mode', default_composition_mode)
|
||||
post_kwargs.setdefault('message_type', 'auto_comment')
|
||||
if composition_mode == 'mass_mail':
|
||||
cleaned_self.message_mail_with_source(template, **post_kwargs)
|
||||
else:
|
||||
@@ -1646,17 +1647,24 @@ class MailThread(models.AbstractModel):
|
||||
stored_date = datetime.datetime.now()
|
||||
msg_dict['date'] = stored_date.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
|
||||
parent_message = self._get_parent_message(msg_dict)
|
||||
if parent_message:
|
||||
msg_dict['parent_id'] = parent_message.id
|
||||
msg_dict['is_internal'] = bool(parent_message.subtype_id and parent_message.subtype_id.internal)
|
||||
|
||||
msg_dict.update(self._message_parse_extract_from_parent(self._get_parent_message(msg_dict)))
|
||||
msg_dict['is_bounce'] = self._message_parse_is_bounce(message, msg_dict)
|
||||
msg_dict.update(self._message_parse_extract_payload(message, msg_dict, save_original=save_original))
|
||||
if msg_dict['is_bounce']:
|
||||
msg_dict.update(self._message_parse_extract_bounce(message, msg_dict))
|
||||
return msg_dict
|
||||
|
||||
def _message_parse_extract_from_parent(self, parent_message):
|
||||
"""Derive message values from the parent."""
|
||||
if parent_message:
|
||||
parent_is_internal = bool(parent_message.subtype_id and parent_message.subtype_id.internal)
|
||||
parent_is_auto_comment = parent_message.message_type == 'auto_comment'
|
||||
return {
|
||||
'parent_id': parent_message.id,
|
||||
'is_internal': parent_is_internal and not parent_is_auto_comment
|
||||
}
|
||||
return {}
|
||||
|
||||
def _get_bounced_message_data(self, message, message_dict):
|
||||
"""Find the original <mail.message> and the bounced email references based on an incoming email.
|
||||
|
||||
|
||||
@@ -118,6 +118,10 @@ export class Message {
|
||||
return this.datetime.toLocaleString(DateTime.DATETIME_SHORT);
|
||||
}
|
||||
|
||||
get isAutomatedMessage() {
|
||||
return this.type === "auto_comment";
|
||||
}
|
||||
|
||||
get isSelfMentioned() {
|
||||
return this.recipients.some((recipient) => recipient === this._store.self);
|
||||
}
|
||||
|
||||
@@ -190,6 +190,9 @@ export class Message extends Component {
|
||||
if (this.props.message.type === "notification") {
|
||||
return _t("System notification");
|
||||
}
|
||||
if (this.props.message.type === "auto_comment") {
|
||||
return _t("Automated message");
|
||||
}
|
||||
if (!this.props.message.isDiscussion && this.props.message.type !== "user_notification") {
|
||||
return _t("Note");
|
||||
}
|
||||
|
||||
@@ -132,9 +132,9 @@
|
||||
'position-absolute top-0': !env.inDiscussApp,
|
||||
'start-0 ms-3': isAlignedRight,
|
||||
'end-0 me-3': env.inChatWindow and !isAlignedRight or env.inChatter,
|
||||
'mt-n4': env.inChatter and (message.isDiscussion or message.isNotification),
|
||||
'mt-n5': env.inChatter and !(message.isDiscussion or message.isNotification),
|
||||
'mt-2': env.inDiscussApp and (message.isDiscussion or message.isNotification),
|
||||
'mt-n4': env.inChatter and (message.isDiscussion or message.isNotification or message.isAutomatedMessage),
|
||||
'mt-n5': env.inChatter and !(message.isDiscussion or message.isNotification or message.isAutomatedMessage),
|
||||
'mt-2': env.inDiscussApp and (message.isDiscussion or message.isNotification or message.isAutomatedMessage),
|
||||
'mt-n3': env.inChatWindow,
|
||||
'ms-2': env.inDiscussApp,
|
||||
'invisible': !isActive,
|
||||
@@ -142,7 +142,7 @@
|
||||
}"
|
||||
>
|
||||
<div class="d-flex rounded-1 shadow-sm overflow-hidden alignedRight" t-att-class="{
|
||||
'mt-3': env.inChatter and !(message.isDiscussion or message.isNotification),
|
||||
'mt-3': env.inChatter and !(message.isDiscussion or message.isNotification or message.isAutomatedMessage),
|
||||
'flex-row-reverse': env.inChatWindow and isAlignedRight,
|
||||
}"
|
||||
>
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
<div class="position-relative d-flex flex-wrap"
|
||||
t-att-class="{
|
||||
'flex-row-reverse me-3': env.inChatWindow and env.alignedRight,
|
||||
'ms-3': !(env.inChatWindow and env.alignedRight) and props.message.isDiscussion,
|
||||
'ms-3': !(env.inChatWindow and env.alignedRight) and (props.message.isDiscussion or props.message.isAutomatedMessage),
|
||||
}"
|
||||
t-attf-class="{{ props.message.isDiscussion ? 'mt-n2' : 'mt-1' }}">
|
||||
t-attf-class="{{ (props.message.isDiscussion or props.message.isAutomatedMessage) ? 'mt-n2' : 'mt-1' }}">
|
||||
<button t-foreach="props.message.reactions" t-as="reaction" t-key="reaction.content" class="o-mail-MessageReaction btn d-flex p-0 border rounded-1 mb-1"
|
||||
t-on-click="() => this.onClickReaction(reaction)"
|
||||
t-on-contextmenu="onContextMenu"
|
||||
|
||||
@@ -130,6 +130,7 @@ class MailComposer(models.TransientModel):
|
||||
compute='_compute_record_name', readonly=False, store=True) # useful only in monorecord comment mode
|
||||
# characteristics
|
||||
message_type = fields.Selection([
|
||||
('auto_comment', 'Automated Targeted Notification'),
|
||||
('comment', 'Comment'),
|
||||
('notification', 'System notification')],
|
||||
'Type', required=True, default='comment',
|
||||
|
||||
@@ -1170,6 +1170,48 @@ class TestMailgateway(MailCommon):
|
||||
# Thread formation
|
||||
# --------------------------------------------------
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
|
||||
def test_message_process_external_notification_reply(self):
|
||||
"""Ensure responses bot messages are discussions."""
|
||||
bot_notification_message = self._create_gateway_message(
|
||||
self.test_record,
|
||||
'bot_notif_message',
|
||||
author_id=self.env.ref('base.partner_root').id,
|
||||
message_type='auto_comment',
|
||||
is_internal=True,
|
||||
subtype_id=self.env.ref('mail.mt_note').id,
|
||||
)
|
||||
|
||||
self.format_and_process(
|
||||
MAIL_TEMPLATE, self.email_from, '',
|
||||
subject='Reply to bot notif',
|
||||
extra=f'References: {bot_notification_message.message_id}'
|
||||
)
|
||||
new_msg = self.test_record.message_ids[0]
|
||||
self.assertFalse(new_msg.is_internal, "Responses to messages sent by odoobot should always be public.")
|
||||
self.assertEqual(new_msg.parent_id, bot_notification_message)
|
||||
self.assertEqual(new_msg.subtype_id, self.env.ref('mail.mt_comment'))
|
||||
|
||||
# Also check the regular case
|
||||
some_notification_message = self._create_gateway_message(
|
||||
self.test_record,
|
||||
'some_notif_message',
|
||||
message_type='notification',
|
||||
is_internal=True,
|
||||
subtype_id=self.env.ref('mail.mt_note').id,
|
||||
)
|
||||
|
||||
self.format_and_process(
|
||||
MAIL_TEMPLATE, self.email_from, '',
|
||||
subject='Reply to some notif',
|
||||
extra=f'References: {some_notification_message.message_id}'
|
||||
)
|
||||
new_msg = self.test_record.message_ids[0]
|
||||
self.assertTrue(new_msg.is_internal, "Responses to messages sent by anyone but odoobot should keep"
|
||||
"the 'is_internal' value of the parent.")
|
||||
self.assertEqual(new_msg.parent_id, some_notification_message)
|
||||
self.assertEqual(new_msg.subtype_id, self.env.ref('mail.mt_note'))
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models')
|
||||
def test_message_process_in_reply_to(self):
|
||||
""" Incoming email using in-rely-to should go into the right destination even with a wrong destination """
|
||||
|
||||
@@ -35,6 +35,51 @@ class TestTracking(MailCommon):
|
||||
|
||||
self.assertEqual(self.record.message_ids.author_id, self.partner_admin)
|
||||
|
||||
def test_message_track_message_type(self):
|
||||
"""Check that the right message type is applied for track templates."""
|
||||
self.record.message_subscribe(
|
||||
partner_ids=[self.user_admin.partner_id.id],
|
||||
subtype_ids=[self.env.ref('mail.mt_comment').id]
|
||||
)
|
||||
mail_templates = self.env['mail.template'].create([{
|
||||
'name': f'Template {n}',
|
||||
'subject': f'Template {n}',
|
||||
'model_id': self.env.ref('test_mail.model_mail_test_ticket').id,
|
||||
'body_html': f'<p>Template {n}</p>',
|
||||
} for n in range(2)])
|
||||
|
||||
def _track_subtype(self, init_values):
|
||||
return self.env.ref('mail.mt_note')
|
||||
self.patch(self.registry('mail.test.ticket'), '_track_subtype', _track_subtype)
|
||||
|
||||
def _track_template(self, changes):
|
||||
if 'email_from' in changes:
|
||||
return {'email_from': (mail_templates[0], {})}
|
||||
elif 'container_id' in changes:
|
||||
return {'container_id': (mail_templates[1], {'message_type': 'notification'})}
|
||||
return {}
|
||||
self.patch(self.registry('mail.test.ticket'), '_track_template', _track_template)
|
||||
|
||||
container = self.env['mail.test.container'].create({'name': 'Container'})
|
||||
|
||||
# default is auto_comment
|
||||
with self.mock_mail_gateway():
|
||||
self.record.email_from = 'test@test.lan'
|
||||
self.flush_tracking()
|
||||
|
||||
first_message = self.record.message_ids.filtered(lambda message: message.subject == 'Template 0')
|
||||
self.assertEqual(len(self.record.message_ids), 2, 'Should be one change message and one automated template')
|
||||
self.assertEqual(first_message.message_type, 'auto_comment')
|
||||
|
||||
# auto_comment can be overriden by _track_template
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
self.record.container_id = container
|
||||
self.flush_tracking()
|
||||
|
||||
second_message = self.record.message_ids.filtered(lambda message: message.subject == 'Template 1')
|
||||
self.assertEqual(len(self.record.message_ids), 4, 'Should have added one change message and one automated template')
|
||||
self.assertEqual(second_message.message_type, 'notification')
|
||||
|
||||
def test_message_track_no_tracking(self):
|
||||
""" Update a set of non tracked fields -> no message, no tracking """
|
||||
self.record.write({
|
||||
|
||||
Reference in New Issue
Block a user