diff options
| author | Christophe Simonis <chs@odoo.com> | 2019-08-20 12:17:04 +0200 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2019-08-20 12:17:04 +0200 |
| commit | f26de445e5579913e27ea6a630bcee6d40eefc7d (patch) | |
| tree | 8b8e06be8a9d5fd3c1e5c31634611127446b9948 /addons/mail | |
| parent | bc6accc5688d3c38a35949751779f808e9c2063f (diff) | |
| parent | 6f55fd65da8c50d36ca97654a920fb075076f125 (diff) | |
[MERGE] forward port branch saas-12.3 up to 6f55fd65da8
Diffstat (limited to 'addons/mail')
| -rw-r--r-- | addons/mail/models/mail_channel.py | 14 | ||||
| -rw-r--r-- | addons/mail/models/mail_thread.py | 4 | ||||
| -rw-r--r-- | addons/mail/static/src/js/models/threads/document_thread.js | 1 | ||||
| -rw-r--r-- | addons/mail/static/src/js/services/mail_manager.js | 3 | ||||
| -rw-r--r-- | addons/mail/static/src/js/services/mail_window_manager.js | 2 | ||||
| -rw-r--r-- | addons/mail/static/src/js/systray/systray_activity_menu.js | 1 | ||||
| -rw-r--r-- | addons/mail/static/src/scss/discuss.scss | 1 | ||||
| -rw-r--r-- | addons/mail/static/tests/chatter_tests.js | 65 | ||||
| -rw-r--r-- | addons/mail/static/tests/discuss_tests.js | 65 | ||||
| -rw-r--r-- | addons/mail/static/tests/systray/systray_activity_menu_tests.js | 8 | ||||
| -rw-r--r-- | addons/mail/static/tests/systray/systray_messaging_menu_tests.js | 57 | ||||
| -rw-r--r-- | addons/mail/wizard/mail_resend_message.py | 2 |
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 |
