From 845bb6cc93d7e67c69aef8811e06b0ed6c4100ce Mon Sep 17 00:00:00 2001 From: Julien Giannone Date: Thu, 26 Aug 2021 15:10:45 +0000 Subject: [PATCH] [IMP] mail: add group chat feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit task-2360934 closes odoo/odoo#75633 Signed-off-by: Alexandre Kühn (aku) --- addons/mail/models/mail_channel.py | 27 +- addons/mail/models/res_partner.py | 4 +- .../channel_invitation_form.xml | 8 +- .../tests/channel_invitation_form_tests.js | 152 ++++++++++ .../discuss/tests/discuss_pinned_tests.js | 12 +- .../components/thread_icon/thread_icon.xml | 3 + .../thread_view_topbar.scss | 1 + .../thread_view_topbar/thread_view_topbar.xml | 4 +- .../channel_invitation_form.js | 42 ++- .../discuss_sidebar_category_item.js | 43 ++- .../messaging_initializer.js | 2 +- .../mail/static/src/models/thread/thread.js | 14 +- .../thread_view_topbar/thread_view_topbar.js | 5 +- .../mail/static/tests/helpers/mock_server.js | 72 +++++ .../tests/test_performance.py | 285 +++++++++++------- 15 files changed, 520 insertions(+), 154 deletions(-) create mode 100644 addons/mail/static/src/components/channel_invitation_form/tests/channel_invitation_form_tests.js diff --git a/addons/mail/models/mail_channel.py b/addons/mail/models/mail_channel.py index 975d3ce35dc..0e2f9f04608 100644 --- a/addons/mail/models/mail_channel.py +++ b/addons/mail/models/mail_channel.py @@ -39,9 +39,10 @@ class Channel(models.Model): name = fields.Char('Name', required=True, translate=True) active = fields.Boolean(default=True, help="Set active to false to hide the channel without removing it.") channel_type = fields.Selection([ - ('chat', 'Chat Discussion'), - ('channel', 'Channel')], - string='Channel Type', default='channel') + ('chat', 'Chat'), + ('channel', 'Channel'), + ('group', 'Group')], + string='Channel Type', default='channel', help="Chat is private and unique between 2 persons. Group is private among invited persons. Channel can be freely joined (depending on its configuration).") is_chat = fields.Boolean(string='Is a chat', compute='_compute_is_chat') description = fields.Text('Description') image_128 = fields.Image("Image", max_width=128, max_height=128, default=_get_default_image) @@ -630,6 +631,8 @@ class Channel(models.Model): """ if self.env.user.partner_id.id not in partners_to: partners_to.append(self.env.user.partner_id.id) + if len(partners_to) > 2: + raise UserError(_("A chat should not be created with more than 2 persons. Create a group instead.")) # determine type according to the number of partner in the channel self.flush() self.env.cr.execute(""" @@ -889,6 +892,22 @@ class Channel(models.Model): self.env['bus.bus'].sendone((self._cr.dbname, 'res.partner', self.env.user.partner_id.id), channel_info) return channel_info + @api.model + def create_group(self, partners_to): + """ Create a group channel. + :param partners_to : list of res.partner ids to add to the conversation + :returns: channel_info of the created channel + :rtype: dict + """ + channel = self.create({ + 'channel_last_seen_partner_ids': [Command.create({'partner_id': partner_id}) for partner_id in partners_to], + 'channel_type': 'group', + 'name': '', # default name is computed client side from the list of members + 'public': 'private', + }) + channel._broadcast(partners_to) + return channel.channel_info()[0] + @api.model def get_mention_suggestions(self, search, limit=8): """ Return 'limit'-first channels' id, name and public fields such that the name matches a @@ -988,7 +1007,7 @@ class Channel(models.Model): return msg def execute_command_leave(self, **kwargs): - if self.channel_type == 'channel': + if self.channel_type in ('channel', 'group'): self.action_unfollow() else: self.channel_pin(self.uuid, False) diff --git a/addons/mail/models/res_partner.py b/addons/mail/models/res_partner.py index 20580946cc7..c9247554794 100644 --- a/addons/mail/models/res_partner.py +++ b/addons/mail/models/res_partner.py @@ -135,9 +135,9 @@ class Partner(models.Model): """Returns the channels of the partner.""" self.ensure_one() channels = self.env['mail.channel'] - # get the channels + # get the channels and groups channels |= self.env['mail.channel'].search([ - ('channel_type', '=', 'channel'), + ('channel_type', 'in', ('channel', 'group')), ('channel_partner_ids', 'in', [self.id]), ]) # get the pinned direct messages diff --git a/addons/mail/static/src/components/channel_invitation_form/channel_invitation_form.xml b/addons/mail/static/src/components/channel_invitation_form/channel_invitation_form.xml index ee8ee32a930..dc6fdb9b6d3 100644 --- a/addons/mail/static/src/components/channel_invitation_form/channel_invitation_form.xml +++ b/addons/mail/static/src/components/channel_invitation_form/channel_invitation_form.xml @@ -5,11 +5,11 @@

Invite people

- +
-
+
Avatar @@ -27,7 +27,7 @@
- +
@@ -52,7 +52,7 @@
-
diff --git a/addons/mail/static/src/components/channel_invitation_form/tests/channel_invitation_form_tests.js b/addons/mail/static/src/components/channel_invitation_form/tests/channel_invitation_form_tests.js new file mode 100644 index 00000000000..254c81b4076 --- /dev/null +++ b/addons/mail/static/src/components/channel_invitation_form/tests/channel_invitation_form_tests.js @@ -0,0 +1,152 @@ +/** @odoo-module **/ + +import { + afterEach, + afterNextRender, + beforeEach, + start, +} from '@mail/utils/test_utils'; + +QUnit.module('mail', {}, function () { +QUnit.module('components', {}, function () { +QUnit.module('channel_invitation_form', {}, function () { +QUnit.module('channel_invitation_form_tests.js', { + beforeEach() { + beforeEach(this); + + this.start = async params => { + const { env, widget } = await start(Object.assign({}, params, { + autoOpenDiscuss: true, + data: this.data, + hasDiscuss: true, + })); + this.env = env; + this.widget = widget; + }; + }, + afterEach() { + afterEach(this); + }, +}); + +QUnit.test('should display the channel invitation form after clicking on the invite button of a chat', async function (assert) { + assert.expect(1); + + this.data['res.partner'].records.push({ + id: 11, + email: "testpartner@odoo.com", + name: "TestPartner", + }); + this.data['res.users'].records.push({ + partner_id: 11, + }); + this.data['mail.channel'].records.push({ + channel_type: 'chat', + id: 13, + members: [this.data.currentPartnerId, 11], + public: 'private', + }); + await this.start({ + discuss: { + context: { + active_id: 13, + }, + }, + }); + await afterNextRender(() => document.querySelector(`.o_ThreadViewTopbar_inviteButton`).click()); + assert.containsOnce( + document.body, + '.o_ChannelInvitationForm', + "should display the channel invitation form after clicking on the invite button of a chat" + ); +}); + +QUnit.test('should be able to search for a new user to invite from an existing chat', async function (assert) { + assert.expect(1); + + this.data['res.partner'].records.push({ + id: 11, + email: "testpartner@odoo.com", + name: "TestPartner", + }); + this.data['res.partner'].records.push({ + id: 12, + email: "testpartner2@odoo.com", + name: "TestPartner2", + }); + this.data['res.users'].records.push({ + partner_id: 11, + }); + this.data['res.users'].records.push({ + partner_id: 12, + }); + this.data['mail.channel'].records.push({ + channel_type: 'chat', + id: 13, + members: [this.data.currentPartnerId, 11], + public: 'private', + }); + await this.start({ + discuss: { + context: { + active_id: 13, + }, + }, + }); + await afterNextRender(() => document.querySelector(`.o_ThreadViewTopbar_inviteButton`).click()); + await afterNextRender(() => document.execCommand('insertText', false, "TestPartner2")); + assert.strictEqual( + document.querySelector(`.o_ChannelInvitationForm_selectablePartnerName`).textContent, + "TestPartner2", + "should display 'TestPartner2' as it matches search term", + ); +}); + +QUnit.test('should be able to create a new group chat from an existing chat', async function (assert) { + assert.expect(1); + + this.data['res.partner'].records.push({ + id: 11, + email: "testpartner@odoo.com", + name: "TestPartner", + }); + this.data['res.partner'].records.push({ + id: 12, + email: "testpartner2@odoo.com", + name: "TestPartner2", + }); + this.data['res.users'].records.push({ + partner_id: 11, + }); + this.data['res.users'].records.push({ + partner_id: 12, + }); + this.data['mail.channel'].records.push({ + channel_type: 'chat', + id: 13, + members: [this.data.currentPartnerId, 11], + public: 'private', + }); + await this.start({ + discuss: { + context: { + active_id: 13, + }, + }, + }); + + await afterNextRender(() => document.querySelector(`.o_ThreadViewTopbar_inviteButton`).click()); + await afterNextRender(() => document.execCommand('insertText', false, "TestPartner2")); + document.querySelector(`.o_ChannelInvitationForm_selectablePartnerCheckbox`).click(); + await afterNextRender(() => document.querySelector(`.o_ChannelInvitationForm_inviteButton`).click()); + await afterNextRender(() => document.querySelector(`.o_ChannelInvitationForm_inviteButton`).click()); + assert.strictEqual( + document.querySelector(`.o_ThreadViewTopbar_threadName`).textContent, + 'Mitchell Admin, TestPartner, TestPartner2', + "should have created a new group chat with the existing chat members and the selected user", + ); +}); + +}); +}); +}); diff --git a/addons/mail/static/src/components/discuss/tests/discuss_pinned_tests.js b/addons/mail/static/src/components/discuss/tests/discuss_pinned_tests.js index b315cf2ccb9..3586857b5d8 100644 --- a/addons/mail/static/src/components/discuss/tests/discuss_pinned_tests.js +++ b/addons/mail/static/src/components/discuss/tests/discuss_pinned_tests.js @@ -77,8 +77,8 @@ QUnit.test('sidebar: pinned channel 2: open pinned channel', async function (ass ); }); -QUnit.test('sidebar: pinned channel 3: open pinned channel and unpin it', async function (assert) { - assert.expect(7); +QUnit.test('sidebar: pinned channel 3: open channel and leave it', async function (assert) { + assert.expect(6); // channel that is expected to be found in the sidebar // with a random unique id that will be referenced in the test @@ -95,9 +95,6 @@ QUnit.test('sidebar: pinned channel 3: open pinned channel and unpin it', async "The right id is sent to the server to remove" ); } - if (args.method === 'channel_fold') { - assert.step('channel_fold'); - } return this._super(...arguments); }, }); @@ -111,16 +108,15 @@ QUnit.test('sidebar: pinned channel 3: open pinned channel and unpin it', async threadGeneral.localId }"]`).click() ); - assert.verifySteps([], "neither channel_fold nor action_unfollow are called yet"); + assert.verifySteps([], "action_unfollow is not called yet"); await afterNextRender(() => document.querySelector('.o_DiscussSidebarCategoryItem_commandLeave').click() ); assert.verifySteps( [ - 'channel_fold', 'action_unfollow' ], - "both channel_fold and action_unfollow have been called when unpinning a channel" + "action_unfollow has been called when leaving a channel" ); assert.containsNone( document.body, diff --git a/addons/mail/static/src/components/thread_icon/thread_icon.xml b/addons/mail/static/src/components/thread_icon/thread_icon.xml index d872866e782..41cbbb225ee 100644 --- a/addons/mail/static/src/components/thread_icon/thread_icon.xml +++ b/addons/mail/static/src/components/thread_icon/thread_icon.xml @@ -37,6 +37,9 @@
+ +
+
diff --git a/addons/mail/static/src/components/thread_view_topbar/thread_view_topbar.scss b/addons/mail/static/src/components/thread_view_topbar/thread_view_topbar.scss index db639744fb0..f93597b75e4 100644 --- a/addons/mail/static/src/components/thread_view_topbar/thread_view_topbar.scss +++ b/addons/mail/static/src/components/thread_view_topbar/thread_view_topbar.scss @@ -55,6 +55,7 @@ } .o_ThreadViewTopbar_threadName { + max-width: map-get($sizes, 75); &:not(.o-isMouseOverThreadName) { border-color: transparent; // presence of border even if invisible to prevent flicker diff --git a/addons/mail/static/src/components/thread_view_topbar/thread_view_topbar.xml b/addons/mail/static/src/components/thread_view_topbar/thread_view_topbar.xml index a88566606ed..47cb89b819d 100644 --- a/addons/mail/static/src/components/thread_view_topbar/thread_view_topbar.xml +++ b/addons/mail/static/src/components/thread_view_topbar/thread_view_topbar.xml @@ -10,7 +10,7 @@
-
+
@@ -19,7 +19,7 @@ -
+
Discuss
diff --git a/addons/mail/static/src/models/channel_invitation_form/channel_invitation_form.js b/addons/mail/static/src/models/channel_invitation_form/channel_invitation_form.js index 90ddc25164b..bc95e828587 100644 --- a/addons/mail/static/src/models/channel_invitation_form/channel_invitation_form.js +++ b/addons/mail/static/src/models/channel_invitation_form/channel_invitation_form.js @@ -34,14 +34,32 @@ function factory(dependencies) { * @param {MouseEvent} ev */ async onClickInvite(ev) { - await this.env.services.rpc(({ - model: 'mail.channel', - method: 'add_members', - args: [this.thread.id], - kwargs: { - partner_ids: this.selectedPartners.map(partner => partner.id), - }, - })); + if (this.thread.channel_type === 'chat') { + const channelData = await this.env.services.rpc(({ + model: 'mail.channel', + method: 'create_group', + kwargs: { + partners_to: [...new Set([ + this.messaging.currentPartner.id, + ...this.thread.members.map(member => member.id), + ...this.selectedPartners.map(partner => partner.id), + ])], + }, + })); + const channel = this.messaging.models['mail.thread'].insert( + this.messaging.models['mail.thread'].convertData(channelData) + ); + channel.open(); + } else { + await this.env.services.rpc(({ + model: 'mail.channel', + method: 'add_members', + args: [[this.thread.id]], + kwargs: { + partner_ids: this.selectedPartners.map(partner => partner.id), + }, + })); + } this.update({ searchTerm: "", selectedPartners: unlinkAll(), @@ -148,9 +166,15 @@ function factory(dependencies) { * @returns {string} */ _computeInviteButtonText() { - if (!this.thread || this.thread.channel_type !== 'channel') { + if (!this.thread) { return clear(); } + switch (this.thread.channel_type) { + case 'chat': + return this.env._t("Create group chat"); + case 'group': + return this.env._t("Invite to group chat"); + } return this.env._t("Invite to Channel"); } diff --git a/addons/mail/static/src/models/discuss_sidebar_category_item/discuss_sidebar_category_item.js b/addons/mail/static/src/models/discuss_sidebar_category_item/discuss_sidebar_category_item.js index 96d49874249..6def43878e6 100644 --- a/addons/mail/static/src/models/discuss_sidebar_category_item/discuss_sidebar_category_item.js +++ b/addons/mail/static/src/models/discuss_sidebar_category_item/discuss_sidebar_category_item.js @@ -38,6 +38,7 @@ function factory(dependencies) { _computeAvatarUrl() { switch (this.channelType) { case 'channel': + case 'group': return `/web/image/mail.channel/${this.channelId}/image_128`; case 'chat': return this.channel.correspondent.avatarUrl; @@ -64,6 +65,7 @@ function factory(dependencies) { case 'channel': return this.channel.message_needaction_counter; case 'chat': + case 'group': return this.channel.localMessageUnreadCounter; } } @@ -73,9 +75,11 @@ function factory(dependencies) { * @returns {boolean} */ _computeHasLeaveCommand() { - return this.channelType === 'channel' && + return ( + ['channel', 'group'].includes(this.channelType) && !this.channel.message_needaction_counter && - !this.channel.group_based_subscription; + !this.channel.group_based_subscription + ); } /** @@ -121,6 +125,7 @@ function factory(dependencies) { switch (this.channelType) { case 'channel': case 'chat': + case 'group': return true; } } @@ -144,10 +149,13 @@ function factory(dependencies) { */ async onClickCommandLeave(ev) { ev.stopPropagation(); - if (this.channel.creator === this.messaging.currentUser) { + if (this.channel.channel_type !== 'group' && this.channel.creator === this.messaging.currentUser) { await this._askAdminConfirmation(); } - this.channel.unsubscribe(); + if (this.channel.channel_type === 'group') { + await this._askLeaveGroupConfirmation(); + } + this.channel.leave(); } /** @@ -201,6 +209,33 @@ function factory(dependencies) { ); }); } + + /** + * @private + * @returns {Promise} + */ + _askLeaveGroupConfirmation() { + return new Promise(resolve => { + Dialog.confirm(this, + this.env._t("You are about to leave this group conversation and will no longer have access to it unless you are invited again. Are you sure you want to continue?"), + { + buttons: [ + { + text: this.env._t("Leave"), + classes: 'btn-primary', + close: true, + click: resolve + }, + { + text: this.env._t("Discard"), + close: true + } + ] + } + ); + }); + } + } DiscussSidebarCategoryItem.fields = { diff --git a/addons/mail/static/src/models/messaging_initializer/messaging_initializer.js b/addons/mail/static/src/models/messaging_initializer/messaging_initializer.js index 81f8e928462..e251880a896 100644 --- a/addons/mail/static/src/models/messaging_initializer/messaging_initializer.js +++ b/addons/mail/static/src/models/messaging_initializer/messaging_initializer.js @@ -241,7 +241,7 @@ function factory(dependencies) { newItemPlaceholderText: this.env._t("Find or start a conversation..."), serverStateKey: 'is_discuss_sidebar_category_chat_open', sortComputeMethod: 'last_action', - supportedChannelTypes: ['chat'], + supportedChannelTypes: ['chat', 'group'], }), }); } diff --git a/addons/mail/static/src/models/thread/thread.js b/addons/mail/static/src/models/thread/thread.js index 36c3cd8fa9e..df5390b80b7 100644 --- a/addons/mail/static/src/models/thread/thread.js +++ b/addons/mail/static/src/models/thread/thread.js @@ -1210,6 +1210,9 @@ function factory(dependencies) { if (this.channel_type === 'chat' && this.correspondent) { return this.custom_channel_name || this.correspondent.nameOrDisplayName; } + if (this.channel_type === 'group') { + return this.name || this.members.map(partner => partner.nameOrDisplayName).join(', '); + } return this.name; } @@ -1226,10 +1229,7 @@ function factory(dependencies) { * @returns {boolean} */ _computeHasInviteFeature() { - if (this.model !== 'mail.channel') { - return false; - } - return this.channel_type === 'channel'; + return this.model === 'mail.channel'; } /** @@ -1248,7 +1248,7 @@ function factory(dependencies) { * @returns {boolean} */ _computeIsChannelDescriptionChangeable() { - return this.model === 'mail.channel' && this.channel_type === 'channel'; + return this.model === 'mail.channel' && ['channel', 'group'].includes(this.channel_type); } /** @@ -1258,7 +1258,7 @@ function factory(dependencies) { _computeIsChannelRenamable() { return ( this.model === 'mail.channel' && - ['chat', 'channel'].includes(this.channel_type) + ['chat', 'channel', 'group'].includes(this.channel_type) ); } @@ -1267,7 +1267,7 @@ function factory(dependencies) { * @returns {boolean} */ _computeHasMemberListFeature() { - return this.model === 'mail.channel' && ['channel'].includes(this.channel_type); + return this.model === 'mail.channel' && ['channel', 'group'].includes(this.channel_type); } /** diff --git a/addons/mail/static/src/models/thread_view_topbar/thread_view_topbar.js b/addons/mail/static/src/models/thread_view_topbar/thread_view_topbar.js index 225ce57b8ea..1d2b3920932 100644 --- a/addons/mail/static/src/models/thread_view_topbar/thread_view_topbar.js +++ b/addons/mail/static/src/models/thread_view_topbar/thread_view_topbar.js @@ -294,6 +294,9 @@ function factory(dependencies) { if (newName && this.thread.channel_type === 'channel' && newName !== this.thread.name) { this.thread.rename(newName); } + if (this.thread.channel_type === 'group' && newName !== this.thread.name) { + this.thread.rename(newName); + } } /** @@ -305,7 +308,7 @@ function factory(dependencies) { isEditingThreadDescription: false, pendingThreadDescription: clear(), }); - if (this.thread.channel_type === 'channel' && newDescription !== this.thread.description) { + if (newDescription !== this.thread.description) { this.thread.changeDescription(newDescription); } } diff --git a/addons/mail/static/tests/helpers/mock_server.js b/addons/mail/static/tests/helpers/mock_server.js index 6cf05d11c82..0c40fe2362f 100644 --- a/addons/mail/static/tests/helpers/mock_server.js +++ b/addons/mail/static/tests/helpers/mock_server.js @@ -200,6 +200,10 @@ MockServer.include({ const name = args.args[1] || args.kwargs.name; return this._mockMailChannelChannelSetCustomName(ids, name); } + if (args.model === 'mail.channel' && args.method === 'create_group') { + const partners_to = args.args[0] || args.kwargs.partners_to; + return this._mockMailChannelCreateGroup(partners_to); + } if (args.model === 'mail.channel' && args.method === 'execute_command_leave') { return this._mockMailChannelExecuteCommandLeave(args); } @@ -270,6 +274,12 @@ MockServer.include({ const limit = args.args[1] || args.kwargs.limit; return this._mockResPartnerImSearch(name, limit); } + if (args.model === 'res.partner' && args.method === 'search_for_channel_invite') { + const search_term = args.args[0] || args.kwargs.search_term; + const channel_id = args.args[1] || args.kwargs.channel_id; + const limit = args.args[2] || args.kwargs.limit; + return this._mockResPartnerSearchForChannelInvite(search_term, channel_id, limit); + } // mail.thread methods (can work on any model) if (args.method === 'message_subscribe') { const ids = args.args[0]; @@ -875,6 +885,26 @@ MockServer.include({ ]); this._mockMailChannel_broadcast([channel.id], [this.currentPartnerId]); }, + /** + * Simulates the `/mail/create_group` route. + * + * @private + * @param {integer[]} partners_to + * @returns {Object} + */ + async _mockMailChannelCreateGroup(partners_to) { + const partners = this._getRecords('res.partner', [['id', 'in', partners_to]]); + const id = this._mockCreate('mail.channel', { + channel_type: 'group', + is_pinned: true, + members: [[6, 0, partners.map(partner => partner.id)]], + name: '', + public: 'private', + state: 'open', + }); + this._mockMailChannel_broadcast(id, partners.map(partner => partner.id)); + return this._mockMailChannelChannelInfo([id])[0]; + }, /** * Simulates `execute_command_leave` on `mail.channel`. * @@ -1841,6 +1871,48 @@ MockServer.include({ } ])); }, + /** + * Simulates `search_for_channel_invite` on `res.partner`. + * + * @private + * @param {string} [search_term=''] + * @param {integer} [channel_id] + * @param {integer} [limit=30] + * @returns {Object[]} + */ + _mockResPartnerSearchForChannelInvite(search_term, channel_id, limit = 30) { + search_term = search_term.toLowerCase(); // simulates ILIKE + // simulates domain with relational parts (not supported by mock server) + const matchingPartners = [...this._mockResPartnerMailPartnerFormat( + this._getRecords('res.users', []) + .filter(user => { + const partner = this._getRecords('res.partner', [['id', '=', user.partner_id]])[0]; + // user must have a partner + if (!partner) { + return false; + } + // not current partner + if (partner.id === this.currentPartnerId) { + return false; + } + // no name is considered as return all + if (!search_term) { + return true; + } + if (partner.name && partner.name.toLowerCase().includes(search_term)) { + return true; + } + return false; + }) + .map(user => user.partner_id) + ).values()]; + const count = matchingPartners.length; + matchingPartners.length = Math.min(count, limit); + return { + count, + partners: matchingPartners + }; + }, /** * Simulates `_message_fetch_failed` on `res.partner`. * diff --git a/addons/test_discuss_full/tests/test_performance.py b/addons/test_discuss_full/tests/test_performance.py index 746dc4aec6c..c46458cf5a3 100644 --- a/addons/test_discuss_full/tests/test_performance.py +++ b/addons/test_discuss_full/tests/test_performance.py @@ -62,25 +62,27 @@ class TestDiscussFullPerformance(TransactionCase): self.env['mail.channel'].search([('id', '!=', channel_general.id)]).unlink() user_root = self.env.ref('base.user_root') # create public channels - channel_public_1 = self.env['mail.channel'].browse(self.env['mail.channel'].channel_create(name='public 1', privacy='public')['id']) - channel_public_1.add_members((self.users[0] + self.users[2] + self.users[3] + self.users[4] + self.users[8]).partner_id.ids) - channel_public_2 = self.env['mail.channel'].browse(self.env['mail.channel'].channel_create(name='public 2', privacy='public')['id']) - channel_public_2.add_members((self.users[0] + self.users[2] + self.users[4] + self.users[7] + self.users[9]).partner_id.ids) + channel_channel_public_1 = self.env['mail.channel'].browse(self.env['mail.channel'].channel_create(name='public 1', privacy='public')['id']) + channel_channel_public_1.add_members((self.users[0] + self.users[2] + self.users[3] + self.users[4] + self.users[8]).partner_id.ids) + channel_channel_public_2 = self.env['mail.channel'].browse(self.env['mail.channel'].channel_create(name='public 2', privacy='public')['id']) + channel_channel_public_2.add_members((self.users[0] + self.users[2] + self.users[4] + self.users[7] + self.users[9]).partner_id.ids) # create groups channels - channel_group_1 = self.env['mail.channel'].browse(self.env['mail.channel'].channel_create(name='group 1', privacy='groups')['id']) - channel_group_1.add_members((self.users[0] + self.users[2] + self.users[3] + self.users[6] + self.users[12]).partner_id.ids) - channel_group_2 = self.env['mail.channel'].browse(self.env['mail.channel'].channel_create(name='group 2', privacy='groups')['id']) - channel_group_2.add_members((self.users[0] + self.users[2] + self.users[6] + self.users[7] + self.users[13]).partner_id.ids) + channel_channel_group_1 = self.env['mail.channel'].browse(self.env['mail.channel'].channel_create(name='group 1', privacy='groups')['id']) + channel_channel_group_1.add_members((self.users[0] + self.users[2] + self.users[3] + self.users[6] + self.users[12]).partner_id.ids) + channel_channel_group_2 = self.env['mail.channel'].browse(self.env['mail.channel'].channel_create(name='group 2', privacy='groups')['id']) + channel_channel_group_2.add_members((self.users[0] + self.users[2] + self.users[6] + self.users[7] + self.users[13]).partner_id.ids) # create private channels - channel_private_1 = self.env['mail.channel'].browse(self.env['mail.channel'].channel_create(name='private 1', privacy='private')['id']) - channel_private_1.add_members((self.users[0] + self.users[2] + self.users[3] + self.users[5] + self.users[10]).partner_id.ids) - channel_private_2 = self.env['mail.channel'].browse(self.env['mail.channel'].channel_create(name='private 2', privacy='private')['id']) - channel_private_2.add_members((self.users[0] + self.users[2] + self.users[5] + self.users[7] + self.users[11]).partner_id.ids) + channel_channel_private_1 = self.env['mail.channel'].browse(self.env['mail.channel'].channel_create(name='private 1', privacy='private')['id']) + channel_channel_private_1.add_members((self.users[0] + self.users[2] + self.users[3] + self.users[5] + self.users[10]).partner_id.ids) + channel_channel_private_2 = self.env['mail.channel'].browse(self.env['mail.channel'].channel_create(name='private 2', privacy='private')['id']) + channel_channel_private_2.add_members((self.users[0] + self.users[2] + self.users[5] + self.users[7] + self.users[11]).partner_id.ids) # create chats - channel_dm_1 = self.env['mail.channel'].browse(self.env['mail.channel'].channel_get((self.users[0] + self.users[14]).partner_id.ids)['id']) - channel_dm_2 = self.env['mail.channel'].browse(self.env['mail.channel'].channel_get((self.users[0] + self.users[15]).partner_id.ids)['id']) - channel_dm_3 = self.env['mail.channel'].browse(self.env['mail.channel'].channel_get((self.users[0] + self.users[2]).partner_id.ids)['id']) - channel_dm_4 = self.env['mail.channel'].browse(self.env['mail.channel'].channel_get((self.users[0] + self.users[3]).partner_id.ids)['id']) + channel_chat_1 = self.env['mail.channel'].browse(self.env['mail.channel'].channel_get((self.users[0] + self.users[14]).partner_id.ids)['id']) + channel_chat_2 = self.env['mail.channel'].browse(self.env['mail.channel'].channel_get((self.users[0] + self.users[15]).partner_id.ids)['id']) + channel_chat_3 = self.env['mail.channel'].browse(self.env['mail.channel'].channel_get((self.users[0] + self.users[2]).partner_id.ids)['id']) + channel_chat_4 = self.env['mail.channel'].browse(self.env['mail.channel'].channel_get((self.users[0] + self.users[3]).partner_id.ids)['id']) + # create groups + channel_group_1 = self.env['mail.channel'].browse(self.env['mail.channel'].create_group((self.users[0] + self.users[12]).partner_id.ids)['id']) # create livechats im_livechat_channel = self.env['im_livechat.channel'].sudo().create({'name': 'support', 'user_ids': [Command.link(self.users[0].id)]}) self.users[0].im_status = 'online' # make available for livechat (ignore leave) @@ -90,10 +92,11 @@ class TestDiscussFullPerformance(TransactionCase): channel_livechat_2.with_user(self.env.ref('base.public_user')).sudo().message_post(body="test") # add needaction self.users[0].notification_type = 'inbox' - message = channel_public_1.message_post(body='test', message_type='comment', author_id=self.users[2].partner_id.id, partner_ids=self.users[0].partner_id.ids) + message = channel_channel_public_1.message_post(body='test', message_type='comment', author_id=self.users[2].partner_id.id, partner_ids=self.users[0].partner_id.ids) # add star message.toggle_message_starred() + self.maxDiff = None self.users[0].flush() self.users[0].invalidate_cache() with self.assertQueryCount(emp=48): @@ -129,19 +132,19 @@ class TestDiscussFullPerformance(TransactionCase): 'custom_channel_name': False, 'description': False, 'group_based_subscription': False, - 'id': channel_public_1.id, + 'id': channel_channel_public_1.id, 'is_minimized': False, 'is_pinned': True, - 'last_interest_dt': channel_public_1.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT), - 'last_message_id': next(res['message_id'] for res in channel_public_1._channel_last_message_ids()), + 'last_interest_dt': channel_channel_public_1.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT), + 'last_message_id': next(res['message_id'] for res in channel_channel_public_1._channel_last_message_ids()), 'message_needaction_counter': 1, 'memberCount': 5, 'message_unread_counter': 0, 'name': 'public 1', 'public': 'public', - 'seen_message_id': next(res['message_id'] for res in channel_public_1._channel_last_message_ids()), + 'seen_message_id': next(res['message_id'] for res in channel_channel_public_1._channel_last_message_ids()), 'state': 'open', - 'uuid': channel_public_1.uuid, + 'uuid': channel_channel_public_1.uuid, }, { 'channel_type': 'channel', @@ -149,19 +152,19 @@ class TestDiscussFullPerformance(TransactionCase): 'custom_channel_name': False, 'description': False, 'group_based_subscription': False, - 'id': channel_public_2.id, + 'id': channel_channel_public_2.id, 'is_minimized': False, 'is_pinned': True, - 'last_interest_dt': channel_public_2.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT), - 'last_message_id': next(res['message_id'] for res in channel_public_2._channel_last_message_ids()), + 'last_interest_dt': channel_channel_public_2.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT), + 'last_message_id': next(res['message_id'] for res in channel_channel_public_2._channel_last_message_ids()), 'memberCount': 5, 'message_needaction_counter': 0, 'message_unread_counter': 0, 'name': 'public 2', 'public': 'public', - 'seen_message_id': next(res['message_id'] for res in channel_public_2._channel_last_message_ids()), + 'seen_message_id': next(res['message_id'] for res in channel_channel_public_2._channel_last_message_ids()), 'state': 'open', - 'uuid': channel_public_2.uuid, + 'uuid': channel_channel_public_2.uuid, }, { 'channel_type': 'channel', @@ -169,90 +172,148 @@ class TestDiscussFullPerformance(TransactionCase): 'custom_channel_name': False, 'description': False, 'group_based_subscription': False, + 'id': channel_channel_group_1.id, + 'is_minimized': False, + 'is_pinned': True, + 'last_interest_dt': channel_channel_group_1.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT), + 'last_message_id': next(res['message_id'] for res in channel_channel_group_1._channel_last_message_ids()), + 'memberCount': 5, + 'message_needaction_counter': 0, + 'message_unread_counter': 0, + 'name': 'group 1', + 'public': 'groups', + 'seen_message_id': next(res['message_id'] for res in channel_channel_group_1._channel_last_message_ids()), + 'state': 'open', + 'uuid': channel_channel_group_1.uuid, + }, + { + 'channel_type': 'channel', + 'create_uid': self.env.user.id, + 'custom_channel_name': False, + 'description': False, + 'group_based_subscription': False, + 'id': channel_channel_group_2.id, + 'is_minimized': False, + 'is_pinned': True, + 'last_interest_dt': channel_channel_group_2.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT), + 'last_message_id': next(res['message_id'] for res in channel_channel_group_2._channel_last_message_ids()), + 'memberCount': 5, + 'message_needaction_counter': 0, + 'message_unread_counter': 0, + 'name': 'group 2', + 'public': 'groups', + 'seen_message_id': next(res['message_id'] for res in channel_channel_group_2._channel_last_message_ids()), + 'state': 'open', + 'uuid': channel_channel_group_2.uuid, + }, + { + 'channel_type': 'channel', + 'create_uid': self.env.user.id, + 'custom_channel_name': False, + 'description': False, + 'group_based_subscription': False, + 'id': channel_channel_private_1.id, + 'is_minimized': False, + 'is_pinned': True, + 'last_interest_dt': channel_channel_private_1.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT), + 'last_message_id': next(res['message_id'] for res in channel_channel_private_1._channel_last_message_ids()), + 'memberCount': 5, + 'message_needaction_counter': 0, + 'message_unread_counter': 0, + 'name': 'private 1', + 'public': 'private', + 'seen_message_id': next(res['message_id'] for res in channel_channel_private_1._channel_last_message_ids()), + 'state': 'open', + 'uuid': channel_channel_private_1.uuid, + }, + { + 'channel_type': 'channel', + 'create_uid': self.env.user.id, + 'custom_channel_name': False, + 'description': False, + 'group_based_subscription': False, + 'id': channel_channel_private_2.id, + 'is_minimized': False, + 'is_pinned': True, + 'last_interest_dt': channel_channel_private_2.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT), + 'last_message_id': next(res['message_id'] for res in channel_channel_private_2._channel_last_message_ids()), + 'memberCount': 5, + 'message_needaction_counter': 0, + 'message_unread_counter': 0, + 'name': 'private 2', + 'public': 'private', + 'seen_message_id': next(res['message_id'] for res in channel_channel_private_2._channel_last_message_ids()), + 'state': 'open', + 'uuid': channel_channel_private_2.uuid, + }, + { + 'channel_type': 'group', + 'create_uid': self.env.user.id, + 'custom_channel_name': False, + 'description': False, + 'group_based_subscription': False, 'id': channel_group_1.id, 'is_minimized': False, 'is_pinned': True, 'last_interest_dt': channel_group_1.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT), - 'last_message_id': next(res['message_id'] for res in channel_group_1._channel_last_message_ids()), - 'memberCount': 5, + 'last_message_id': False, + 'memberCount': 2, + 'members': [ + { + 'active': True, + 'display_name': 'Ernest Employee', + 'email': 'e.e@example.com', + 'id': self.users[0].partner_id.id, + 'im_status': 'leave_offline', + 'is_internal_user': True, + 'name': 'Ernest Employee', + 'out_of_office_date_end': self.leaves.filtered(lambda l: l.employee_id.user_id == self.users[0]).date_to.strftime(DEFAULT_SERVER_DATE_FORMAT), + 'user_id': self.users[0].id, + }, + { + 'active': True, + 'display_name': 'test12', + 'email': False, + 'id': self.users[12].partner_id.id, + 'im_status': 'leave_offline', + 'is_internal_user': True, + 'name': 'test12', + 'out_of_office_date_end': self.leaves.filtered(lambda l: l.employee_id.user_id == self.users[12]).date_to.strftime(DEFAULT_SERVER_DATE_FORMAT), + 'user_id': self.users[12].id, + }, + ], 'message_needaction_counter': 0, 'message_unread_counter': 0, - 'name': 'group 1', - 'public': 'groups', - 'seen_message_id': next(res['message_id'] for res in channel_group_1._channel_last_message_ids()), + 'name': '', + 'public': 'private', + 'seen_message_id': False, + 'seen_partners_info': [ + { + 'fetched_message_id': False, + 'id': channel_group_1.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).id, + 'partner_id': self.users[0].partner_id.id, + 'seen_message_id': False, + }, + { + 'fetched_message_id': False, + 'id': channel_group_1.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[12].partner_id).id, + 'partner_id': self.users[12].partner_id.id, + 'seen_message_id': False, + } + ], 'state': 'open', 'uuid': channel_group_1.uuid, }, - { - 'channel_type': 'channel', - 'create_uid': self.env.user.id, - 'custom_channel_name': False, - 'description': False, - 'group_based_subscription': False, - 'id': channel_group_2.id, - 'is_minimized': False, - 'is_pinned': True, - 'last_interest_dt': channel_group_2.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT), - 'last_message_id': next(res['message_id'] for res in channel_group_2._channel_last_message_ids()), - 'memberCount': 5, - 'message_needaction_counter': 0, - 'message_unread_counter': 0, - 'name': 'group 2', - 'public': 'groups', - 'seen_message_id': next(res['message_id'] for res in channel_group_2._channel_last_message_ids()), - 'state': 'open', - 'uuid': channel_group_2.uuid, - }, - { - 'channel_type': 'channel', - 'create_uid': self.env.user.id, - 'custom_channel_name': False, - 'description': False, - 'group_based_subscription': False, - 'id': channel_private_1.id, - 'is_minimized': False, - 'is_pinned': True, - 'last_interest_dt': channel_private_1.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT), - 'last_message_id': next(res['message_id'] for res in channel_private_1._channel_last_message_ids()), - 'memberCount': 5, - 'message_needaction_counter': 0, - 'message_unread_counter': 0, - 'name': 'private 1', - 'public': 'private', - 'seen_message_id': next(res['message_id'] for res in channel_private_1._channel_last_message_ids()), - 'state': 'open', - 'uuid': channel_private_1.uuid, - }, - { - 'channel_type': 'channel', - 'create_uid': self.env.user.id, - 'custom_channel_name': False, - 'description': False, - 'group_based_subscription': False, - 'id': channel_private_2.id, - 'is_minimized': False, - 'is_pinned': True, - 'last_interest_dt': channel_private_2.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT), - 'last_message_id': next(res['message_id'] for res in channel_private_2._channel_last_message_ids()), - 'memberCount': 5, - 'message_needaction_counter': 0, - 'message_unread_counter': 0, - 'name': 'private 2', - 'public': 'private', - 'seen_message_id': next(res['message_id'] for res in channel_private_2._channel_last_message_ids()), - 'state': 'open', - 'uuid': channel_private_2.uuid, - }, { 'channel_type': 'chat', 'create_uid': self.env.user.id, 'custom_channel_name': False, 'description': False, 'group_based_subscription': False, - 'id': channel_dm_1.id, + 'id': channel_chat_1.id, 'is_minimized': False, 'is_pinned': True, - 'last_interest_dt': channel_dm_1.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT), + 'last_interest_dt': channel_chat_1.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT), 'last_message_id': False, 'memberCount': 2, 'members': [ @@ -286,20 +347,20 @@ class TestDiscussFullPerformance(TransactionCase): 'seen_partners_info': [ { 'fetched_message_id': False, - 'id': channel_dm_1.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).id, + 'id': channel_chat_1.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).id, 'partner_id': self.users[0].partner_id.id, 'seen_message_id': False, }, { 'fetched_message_id': False, - 'id': channel_dm_1.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[14].partner_id).id, + 'id': channel_chat_1.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[14].partner_id).id, 'partner_id': self.users[14].partner_id.id, 'seen_message_id': False, }, ], 'seen_message_id': False, 'state': 'open', - 'uuid': channel_dm_1.uuid, + 'uuid': channel_chat_1.uuid, }, { 'channel_type': 'chat', @@ -307,10 +368,10 @@ class TestDiscussFullPerformance(TransactionCase): 'custom_channel_name': False, 'description': False, 'group_based_subscription': False, - 'id': channel_dm_2.id, + 'id': channel_chat_2.id, 'is_minimized': False, 'is_pinned': True, - 'last_interest_dt': channel_dm_2.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT), + 'last_interest_dt': channel_chat_2.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT), 'last_message_id': False, 'memberCount': 2, 'members': [ @@ -344,20 +405,20 @@ class TestDiscussFullPerformance(TransactionCase): 'seen_partners_info': [ { 'fetched_message_id': False, - 'id': channel_dm_2.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).id, + 'id': channel_chat_2.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).id, 'partner_id': self.users[0].partner_id.id, 'seen_message_id': False, }, { 'fetched_message_id': False, - 'id': channel_dm_2.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[15].partner_id).id, + 'id': channel_chat_2.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[15].partner_id).id, 'partner_id': self.users[15].partner_id.id, 'seen_message_id': False, }, ], 'seen_message_id': False, 'state': 'open', - 'uuid': channel_dm_2.uuid, + 'uuid': channel_chat_2.uuid, }, { 'channel_type': 'chat', @@ -365,10 +426,10 @@ class TestDiscussFullPerformance(TransactionCase): 'custom_channel_name': False, 'description': False, 'group_based_subscription': False, - 'id': channel_dm_3.id, + 'id': channel_chat_3.id, 'is_minimized': False, 'is_pinned': True, - 'last_interest_dt': channel_dm_3.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT), + 'last_interest_dt': channel_chat_3.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT), 'last_message_id': False, 'memberCount': 2, 'members': [ @@ -402,20 +463,20 @@ class TestDiscussFullPerformance(TransactionCase): 'seen_partners_info': [ { 'fetched_message_id': False, - 'id': channel_dm_3.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).id, + 'id': channel_chat_3.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).id, 'partner_id': self.users[0].partner_id.id, 'seen_message_id': False, }, { 'fetched_message_id': False, - 'id': channel_dm_3.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[2].partner_id).id, + 'id': channel_chat_3.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[2].partner_id).id, 'partner_id': self.users[2].partner_id.id, 'seen_message_id': False, }, ], 'seen_message_id': False, 'state': 'open', - 'uuid': channel_dm_3.uuid, + 'uuid': channel_chat_3.uuid, }, { 'channel_type': 'chat', @@ -423,10 +484,10 @@ class TestDiscussFullPerformance(TransactionCase): 'custom_channel_name': False, 'description': False, 'group_based_subscription': False, - 'id': channel_dm_4.id, + 'id': channel_chat_4.id, 'is_minimized': False, 'is_pinned': True, - 'last_interest_dt': channel_dm_4.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT), + 'last_interest_dt': channel_chat_4.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT), 'last_message_id': False, 'memberCount': 2, 'members': [ @@ -460,20 +521,20 @@ class TestDiscussFullPerformance(TransactionCase): 'seen_partners_info': [ { 'fetched_message_id': False, - 'id': channel_dm_4.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).id, + 'id': channel_chat_4.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).id, 'partner_id': self.users[0].partner_id.id, 'seen_message_id': False, }, { 'fetched_message_id': False, - 'id': channel_dm_4.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[3].partner_id).id, + 'id': channel_chat_4.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[3].partner_id).id, 'partner_id': self.users[3].partner_id.id, 'seen_message_id': False, }, ], 'seen_message_id': False, 'state': 'open', - 'uuid': channel_dm_4.uuid, + 'uuid': channel_chat_4.uuid, }, { 'channel_type': 'livechat',