[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:
Renaud Thiry
2023-05-05 09:25:42 +02:00
parent c9f55bb227
commit 39f14ca2c9
9 changed files with 115 additions and 11 deletions
+1
View File
@@ -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 "
+13 -5
View File
@@ -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");
}
+4 -4
View File
@@ -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({