summaryrefslogtreecommitdiff
path: root/addons/mail
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2019-08-20 12:17:04 +0200
committerChristophe Simonis <chs@odoo.com>2019-08-20 12:17:04 +0200
commitf26de445e5579913e27ea6a630bcee6d40eefc7d (patch)
tree8b8e06be8a9d5fd3c1e5c31634611127446b9948 /addons/mail
parentbc6accc5688d3c38a35949751779f808e9c2063f (diff)
parent6f55fd65da8c50d36ca97654a920fb075076f125 (diff)
[MERGE] forward port branch saas-12.3 up to 6f55fd65da8
Diffstat (limited to 'addons/mail')
-rw-r--r--addons/mail/models/mail_channel.py14
-rw-r--r--addons/mail/models/mail_thread.py4
-rw-r--r--addons/mail/static/src/js/models/threads/document_thread.js1
-rw-r--r--addons/mail/static/src/js/services/mail_manager.js3
-rw-r--r--addons/mail/static/src/js/services/mail_window_manager.js2
-rw-r--r--addons/mail/static/src/js/systray/systray_activity_menu.js1
-rw-r--r--addons/mail/static/src/scss/discuss.scss1
-rw-r--r--addons/mail/static/tests/chatter_tests.js65
-rw-r--r--addons/mail/static/tests/discuss_tests.js65
-rw-r--r--addons/mail/static/tests/systray/systray_activity_menu_tests.js8
-rw-r--r--addons/mail/static/tests/systray/systray_messaging_menu_tests.js57
-rw-r--r--addons/mail/wizard/mail_resend_message.py2
12 files changed, 209 insertions, 14 deletions
diff --git a/addons/mail/models/mail_channel.py b/addons/mail/models/mail_channel.py
index c68f6cc3f7f..df5455df4d6 100644
--- a/addons/mail/models/mail_channel.py
+++ b/addons/mail/models/mail_channel.py
@@ -151,23 +151,23 @@ class Channel(models.Model):
@api.constrains('moderator_ids')
def _check_moderator_email(self):
if any(not moderator.email for channel in self for moderator in channel.moderator_ids):
- raise ValidationError("Moderators must have an email address.")
+ raise ValidationError(_("Moderators must have an email address."))
@api.constrains('moderator_ids', 'channel_partner_ids', 'channel_last_seen_partner_ids')
def _check_moderator_is_member(self):
for channel in self:
if not (channel.mapped('moderator_ids.partner_id') <= channel.sudo().channel_partner_ids):
- raise ValidationError("Moderators should be members of the channel they moderate.")
+ raise ValidationError(_("Moderators should be members of the channel they moderate."))
@api.constrains('moderation', 'email_send')
def _check_moderation_parameters(self):
if any(not channel.email_send and channel.moderation for channel in self):
- raise ValidationError('Only mailing lists can be moderated.')
+ raise ValidationError(_('Only mailing lists can be moderated.'))
@api.constrains('moderator_ids')
def _check_moderator_existence(self):
if any(not channel.moderator_ids for channel in self if channel.moderation):
- raise ValidationError('Moderated channels must have moderators.')
+ raise ValidationError(_('Moderated channels must have moderators.'))
@api.multi
def _compute_is_member(self):
@@ -254,7 +254,7 @@ class Channel(models.Model):
# First checks if user tries to modify moderation fields and has not the right to do it.
if any(key for key in MODERATION_FIELDS if vals.get(key)) and any(self.env.user not in channel.moderator_ids for channel in self if channel.moderation):
if not self.env.user.has_group('base.group_system'):
- raise UserError("You do not possess the rights to modify fields related to moderation on one of the channels you are modifying.")
+ raise UserError(_("You do not have the rights to modify fields related to moderation on one of the channels you are modifying."))
tools.image_resize_images(vals)
result = super(Channel, self).write(vals)
@@ -426,9 +426,9 @@ class Channel(models.Model):
if self.env.user in self.moderator_ids or self.env.user.has_group('base.group_system'):
success = self._send_guidelines(self.channel_partner_ids)
if not success:
- raise UserError('View "mail.mail_channel_send_guidelines" was not found. No email has been sent. Please contact an administrator to fix this issue.')
+ raise UserError(_('View "mail.mail_channel_send_guidelines" was not found. No email has been sent. Please contact an administrator to fix this issue.'))
else:
- raise UserError("Only an administrator or a moderator can send guidelines to channel members!")
+ raise UserError(_("Only an administrator or a moderator can send guidelines to channel members!"))
@api.multi
def _send_guidelines(self, partners):
diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py
index 3536c4e6001..9d1bd8c4d01 100644
--- a/addons/mail/models/mail_thread.py
+++ b/addons/mail/models/mail_thread.py
@@ -937,7 +937,7 @@ class MailThread(models.AbstractModel):
# 1. Check if message is a reply on a thread
msg_references = [ref for ref in tools.mail_header_msgid_re.findall(thread_references) if 'reply_to' not in ref]
- mail_messages = MailMessage.sudo().search([('message_id', 'in', msg_references)], limit=1)
+ mail_messages = MailMessage.sudo().search([('message_id', 'in', msg_references)], limit=1, order='id desc, message_id')
is_a_reply = bool(mail_messages)
# 1.1 Handle forward to an alias with a different model: do not consider it as a reply
@@ -1365,7 +1365,7 @@ class MailThread(models.AbstractModel):
# Very unusual situation, be we should be fault-tolerant here
message_id = "<%s@localhost>" % time.time()
_logger.debug('Parsing Message without message-id, generating a random one: %s', message_id)
- msg_dict['message_id'] = message_id
+ msg_dict['message_id'] = message_id.strip()
if message.get('Subject'):
msg_dict['subject'] = tools.decode_smtp_header(message.get('Subject'))
diff --git a/addons/mail/static/src/js/models/threads/document_thread.js b/addons/mail/static/src/js/models/threads/document_thread.js
index b4f1df097cf..0ef02e5f57a 100644
--- a/addons/mail/static/src/js/models/threads/document_thread.js
+++ b/addons/mail/static/src/js/models/threads/document_thread.js
@@ -97,7 +97,6 @@ var DocumentThread = Thread.extend({
fetchMessages: function (options) {
var self = this;
return this._fetchMessages(options).then(function () {
- self.call('mail_service', 'markMessagesAsRead', self._messageIDs);
return self._messages;
});
},
diff --git a/addons/mail/static/src/js/services/mail_manager.js b/addons/mail/static/src/js/services/mail_manager.js
index 4a20d24af8d..3ed4031abeb 100644
--- a/addons/mail/static/src/js/services/mail_manager.js
+++ b/addons/mail/static/src/js/services/mail_manager.js
@@ -85,7 +85,7 @@ var MailManager = AbstractService.extend({
addMessage: function (data, options) {
options = options || {};
var message = this.getMessage(data.id);
- var prom = Promise.resolve();
+ var prom;
if (!message) {
prom = this._addNewMessage(data, options);
} else {
@@ -95,6 +95,7 @@ var MailManager = AbstractService.extend({
});
}
this._addMessageToThreads(message, options);
+ prom = Promise.resolve(message);
}
return prom;
},
diff --git a/addons/mail/static/src/js/services/mail_window_manager.js b/addons/mail/static/src/js/services/mail_window_manager.js
index 5cc965b5c7a..a832b0b0793 100644
--- a/addons/mail/static/src/js/services/mail_window_manager.js
+++ b/addons/mail/static/src/js/services/mail_window_manager.js
@@ -106,7 +106,7 @@ MailManager.include({
// This may happen due to concurrent calls to this method from
// messaging menu preview click and handling of longpolling chat
// window state.
- return;
+ return Promise.resolve();
}
var threadWindow = this._getThreadWindow(threadID);
var prom = Promise.resolve();
diff --git a/addons/mail/static/src/js/systray/systray_activity_menu.js b/addons/mail/static/src/js/systray/systray_activity_menu.js
index fae95a78c2f..641ff4be1bc 100644
--- a/addons/mail/static/src/js/systray/systray_activity_menu.js
+++ b/addons/mail/static/src/js/systray/systray_activity_menu.js
@@ -108,6 +108,7 @@ var ActivityMenu = Widget.extend({
*/
_onActivityActionClick: function (ev) {
ev.stopPropagation();
+ this.$('.dropdown-toggle').dropdown('toggle');
var targetAction = $(ev.currentTarget);
var actionXmlid = targetAction.data('action_xmlid');
if (actionXmlid) {
diff --git a/addons/mail/static/src/scss/discuss.scss b/addons/mail/static/src/scss/discuss.scss
index 6092ab733fc..8284fdc70b5 100644
--- a/addons/mail/static/src/scss/discuss.scss
+++ b/addons/mail/static/src/scss/discuss.scss
@@ -110,6 +110,7 @@
}
.o_mail_thread {
+ position: relative;
flex: 1 0 0;
overflow: auto;
-webkit-overflow-scrolling: touch; // smooth scrolling in iOS app (Safari)
diff --git a/addons/mail/static/tests/chatter_tests.js b/addons/mail/static/tests/chatter_tests.js
index bed4d682d10..4c9d55fcb75 100644
--- a/addons/mail/static/tests/chatter_tests.js
+++ b/addons/mail/static/tests/chatter_tests.js
@@ -1144,6 +1144,71 @@ QUnit.test('chatter: access document with some notifs', async function (assert)
form.destroy();
});
+QUnit.test('chatter: new message notification from document', async function (assert) {
+ // when receiving new messages from a document that is open, it should
+ // not mark the messages are read
+ assert.expect(4);
+
+ var form = await createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ services: this.services,
+ arch: '<form string="Partners">' +
+ '<sheet>' +
+ '<field name="foo"/>' +
+ '</sheet>' +
+ '<div class="oe_chatter">' +
+ '<field name="message_ids" widget="mail_thread"/>' +
+ '</div>' +
+ '</form>',
+ res_id: 2,
+ session: {
+ partner_id: 3,
+ },
+ mockRPC: function (route, args) {
+ if (args.method === 'set_message_done') {
+ throw new Error("should not mark message as read");
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ var thread = form.call('mail_service', 'getDocumentThread', 'partner', 2);
+ assert.strictEqual(
+ thread.getUnreadCounter(),
+ 0,
+ "document thread should have no unread messages initially");
+ assert.containsNone(
+ form,
+ '.o_thread_message',
+ "should have no message in the chatter");
+
+ // Simulate received needaction`` message on this document
+ var message = {
+ author_id: [1, "Me"],
+ body: '<p>test</p>',
+ channel_ids: [],
+ id: 2,
+ model: 'partner',
+ needaction_partner_ids: [3],
+ res_id: 2,
+ };
+ const notifications = [ [['myDB', 'ir.needaction'], message] ];
+ form.call('bus_service', 'trigger', 'notification', notifications);
+ await testUtils.nextTick();
+ assert.strictEqual(
+ thread.getUnreadCounter(),
+ 1,
+ "document thread should have one unread message (not marked as read)");
+ assert.containsOnce(
+ form,
+ '.o_thread_message',
+ "should have one message in the chatter");
+
+ form.destroy();
+});
+
QUnit.test('chatter: post a message and switch in edit mode', async function (assert) {
assert.expect(5);
diff --git a/addons/mail/static/tests/discuss_tests.js b/addons/mail/static/tests/discuss_tests.js
index 8c65266a392..226fd1e398a 100644
--- a/addons/mail/static/tests/discuss_tests.js
+++ b/addons/mail/static/tests/discuss_tests.js
@@ -59,6 +59,7 @@ QUnit.module('Discuss', {
type: 'integer',
},
},
+ records: [],
},
'res.partner': {
fields: {
@@ -1490,6 +1491,70 @@ QUnit.test('custom-named DM conversation', async function (assert) {
discuss.destroy();
});
+QUnit.test('receive channel message notification then delayed needaction notification', async function (assert) {
+ assert.expect(3);
+
+ const message = {
+ author_id: [5, 'Demo User'],
+ body: '<p>test</p>',
+ channel_ids: [1],
+ id: 100,
+ model: 'mail.channel',
+ needaction: true,
+ needaction_partner_ids: [3],
+ res_id: 1,
+ };
+ this.data.initMessaging = {
+ channel_slots: {
+ channel_channel: [{
+ id: 1,
+ channel_type: 'channel',
+ name: "general",
+ }],
+ },
+ };
+
+ const discuss = await createDiscuss({
+ context: {},
+ params: {},
+ data: this.data,
+ services: this.services,
+ session: {
+ partner_id: 3
+ },
+ });
+
+ const $inbox = discuss.$('.o_mail_discuss_item[data-thread-id="mailbox_inbox"]');
+ assert.hasClass(
+ $inbox,
+ 'o_active',
+ "'Inbox' should be the currently active thread");
+ assert.containsNone(
+ discuss,
+ '.o_thread_message',
+ "inbox should contain no messages initially");
+
+ // simulate new needaction message posted on channnel
+ this.data['mail.message'].records.push(message);
+ // simulate receiving channel notification
+ discuss.call('bus_service', 'trigger', 'notification', [
+ [['myDB', 'mail.channel', 1], message]
+ ]);
+ // short delay after receiving needaction notification
+ await testUtils.nextTick();
+ // simulate receiving needaction message notification after a short delay
+ discuss.call('bus_service', 'trigger', 'notification', [
+ [['myDB', 'ir.needaction', 3], message]
+ ]);
+ await testUtils.nextTick();
+ assert.containsOnce(
+ discuss,
+ '.o_thread_message',
+ "inbox should contain one message");
+
+ discuss.destroy();
+});
+
QUnit.test('input not cleared on unresolved message_post rpc', async function (assert) {
assert.expect(2);
diff --git a/addons/mail/static/tests/systray/systray_activity_menu_tests.js b/addons/mail/static/tests/systray/systray_activity_menu_tests.js
index b2f76feb832..7d24b5e05a4 100644
--- a/addons/mail/static/tests/systray/systray_activity_menu_tests.js
+++ b/addons/mail/static/tests/systray/systray_activity_menu_tests.js
@@ -153,7 +153,7 @@ QUnit.test('activity menu widget: activity menu with 3 records', async function
});
QUnit.test('activity menu widget: activity view icon', async function (assert) {
- assert.expect(10);
+ assert.expect(12);
var self = this;
var activityMenu = new ActivityMenu();
testUtils.mock.addMockEnvironment(activityMenu, {
@@ -193,9 +193,15 @@ QUnit.test('activity menu widget: activity view icon', async function (assert) {
// click on the "Issue" activity icon
await testUtils.dom.click(activityMenu.$('.dropdown-toggle'));
+ assert.hasClass(activityMenu.$('.dropdown-menu'), 'show',
+ "dropdown should be expanded");
+
await testUtils.dom.click(activityMenu.$(".o_mail_activity_action[data-model_name='Issue']"));
+ assert.doesNotHaveClass(activityMenu.$('.dropdown-menu'), 'show',
+ "dropdown should be collapsed");
// click on the "Note" activity icon
+ await testUtils.dom.click(activityMenu.$('.dropdown-toggle'));
await testUtils.dom.click(activityMenu.$(".o_mail_activity_action[data-model_name='Note']"));
assert.verifySteps([
diff --git a/addons/mail/static/tests/systray/systray_messaging_menu_tests.js b/addons/mail/static/tests/systray/systray_messaging_menu_tests.js
index b31b12f15b3..610e689ae4a 100644
--- a/addons/mail/static/tests/systray/systray_messaging_menu_tests.js
+++ b/addons/mail/static/tests/systray/systray_messaging_menu_tests.js
@@ -920,5 +920,62 @@ QUnit.test('messaging menu widget: expand on thread preview', async function (as
messagingMenu.destroy();
});
+QUnit.test('messaging menu widget: click twice preview on slow message_fetch should open chat window once', async function (assert) {
+ // This test assumes that a condition for opening chat window is to
+ // successfully fetch messages beforehand.
+ assert.expect(1);
+
+ const self = this;
+ // Used to pause `message_fetch` after opening the messaging menu.
+ // This is necessary `message_fetch` on mailbox_inbox is required to
+ // display the previews.
+ let lockMessageFetch = false;
+ const messageFetchProm = testUtils.makeTestPromise();
+
+ const messagingMenu = new MessagingMenu();
+ testUtils.addMockEnvironment(messagingMenu, {
+ services: this.services,
+ data: this.data,
+ session: { partner_id: 1 },
+ async mockRPC(route, args) {
+ if (args.method === 'message_fetch' && lockMessageFetch) {
+ const _super = this._super.bind(this);
+ await messageFetchProm;
+ return _super(route, args);
+ }
+ if (args.method === 'channel_minimize') {
+ // called to detach thread in chat window
+ // simulate longpolling response with new chat window state
+ const channelInfo = {
+ ...self.data['mail.channel'].records[0],
+ is_minimized: true,
+ state: 'open',
+ };
+ const notifications = [ [['myDB', 'res.partner'], channelInfo] ];
+ messagingMenu.call('bus_service', 'trigger', 'notification', notifications);
+ }
+ return this._super(...arguments);
+ },
+ });
+ await messagingMenu.appendTo($('#qunit-fixture'));
+
+ // Opening chat window 1st time from messaging menu (pending from `messageFetchDef`)
+ await testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
+ lockMessageFetch = true;
+ await testUtils.dom.click(messagingMenu.$('.o_mail_preview'));
+ // Click again on preview to open chat window
+ await testUtils.dom.click(messagingMenu.$('.dropdown-toggle'));
+ await testUtils.dom.click(messagingMenu.$('.o_mail_preview'));
+ messageFetchProm.resolve();
+ await testUtils.nextTick();
+
+ assert.containsOnce(
+ $,
+ '.o_thread_window',
+ "should only display a single chat window");
+
+ messagingMenu.destroy();
+});
+
});
});
diff --git a/addons/mail/wizard/mail_resend_message.py b/addons/mail/wizard/mail_resend_message.py
index bcff9dc9840..5235220240e 100644
--- a/addons/mail/wizard/mail_resend_message.py
+++ b/addons/mail/wizard/mail_resend_message.py
@@ -48,7 +48,7 @@ class MailResendMessage(models.TransientModel):
rec['mail_message_id'] = mail_message_id.id
rec['partner_ids'] = partner_ids
else:
- raise UserError('No message_id found in context')
+ raise UserError(_('No message_id found in context'))
return rec
@api.multi