diff --git a/addons/im_livechat/static/src/models/messaging_initializer/messaging_initializer.js b/addons/im_livechat/static/src/models/messaging_initializer/messaging_initializer.js index 6a98302e668..634fbb5dbfe 100644 --- a/addons/im_livechat/static/src/models/messaging_initializer/messaging_initializer.js +++ b/addons/im_livechat/static/src/models/messaging_initializer/messaging_initializer.js @@ -26,7 +26,7 @@ registerInstancePatchModel('mail.messaging_initializer', 'im_livechat/static/src // flux specific: channels received at init have to be // considered pinned. task-2284357 if (!channel.isPinned) { - channel.update({ isPendingPinned: true }); + channel.pin(); } } }, diff --git a/addons/mail/models/mail_channel.py b/addons/mail/models/mail_channel.py index eb5fca1bdfe..f0c4f81015b 100644 --- a/addons/mail/models/mail_channel.py +++ b/addons/mail/models/mail_channel.py @@ -264,8 +264,15 @@ class Channel(models.Model): return values def _subscribe_users(self): + to_create = [] for mail_channel in self: - mail_channel.write({'channel_partner_ids': [(4, pid) for pid in mail_channel.mapped('group_ids').mapped('users').mapped('partner_id').ids]}) + partners_to_add = mail_channel.group_ids.users.partner_id - mail_channel.channel_partner_ids + to_create += [{ + 'channel_id': mail_channel.id, + 'partner_id': partner.id, + } for partner in partners_to_add] + + self.env['mail.channel.partner'].create(to_create) def action_follow(self): self.ensure_one() @@ -672,6 +679,7 @@ class Channel(models.Model): # pin up the channel for the current partner if pin: self.env['mail.channel.partner'].search([('partner_id', '=', self.env.user.partner_id.id), ('channel_id', '=', channel.id)]).write({'is_pinned': True}) + channel._broadcast(self.env.user.partner_id.ids) else: # create a new one channel = self.create({ @@ -681,7 +689,6 @@ class Channel(models.Model): 'email_send': False, 'name': ', '.join(self.env['res.partner'].sudo().browse(partners_to).mapped('name')), }) - # broadcast the channel header to the other partner (not me) channel._broadcast(partners_to) return channel.channel_info()[0] @@ -735,8 +742,7 @@ class Channel(models.Model): self.ensure_one() channel_partners = self.env['mail.channel.partner'].search( [('partner_id', '=', self.env.user.partner_id.id), ('channel_id', '=', self.id)]) - if not pinned: - self.env['bus.bus'].sendone((self._cr.dbname, 'res.partner', self.env.user.partner_id.id), self.channel_info('unsubscribe')[0]) + self.env['bus.bus'].sendone((self._cr.dbname, 'res.partner', self.env.user.partner_id.id), self.channel_info('unsubscribe' if not pinned else False)[0]) if channel_partners: channel_partners.write({'is_pinned': pinned}) 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 a373b25cd49..108e7a28d1d 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 @@ -93,7 +93,7 @@ QUnit.test('sidebar: pinned channel 3: open pinned channel and unpin it', async assert.deepEqual(args.args[0], [20], "The right id is sent to the server to remove" ); - assert.strictEqual(args.args[1], 'leave', + assert.strictEqual(args.kwargs.command, 'leave', "The right command is sent to the server" ); } 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 13da76dbb38..da2f51757be 100644 --- a/addons/mail/static/src/models/messaging_initializer/messaging_initializer.js +++ b/addons/mail/static/src/models/messaging_initializer/messaging_initializer.js @@ -169,7 +169,7 @@ function factory(dependencies) { // flux specific: channels received at init have to be // considered pinned. task-2284357 if (!channel.isPinned) { - channel.update({ isPendingPinned: true }); + channel.pin(); } } } diff --git a/addons/mail/static/src/models/messaging_notification_handler/messaging_notification_handler.js b/addons/mail/static/src/models/messaging_notification_handler/messaging_notification_handler.js index 783b43e4f20..affa3c05a30 100644 --- a/addons/mail/static/src/models/messaging_notification_handler/messaging_notification_handler.js +++ b/addons/mail/static/src/models/messaging_notification_handler/messaging_notification_handler.js @@ -205,7 +205,7 @@ function factory(dependencies) { ))[0]; } if (!channel.isPinned) { - channel.update({ isPendingPinned: true }); + channel.pin(); } const message = this.env.models['mail.message'].insert(convertedData); diff --git a/addons/mail/static/src/models/thread/thread.js b/addons/mail/static/src/models/thread/thread.js index cd1ee77b1ce..4de44f8b3ec 100644 --- a/addons/mail/static/src/models/thread/thread.js +++ b/addons/mail/static/src/models/thread/thread.js @@ -341,7 +341,6 @@ function factory(dependencies) { return channels; } - /** * Performs the `channel_seen` RPC on `mail.channel`. * @@ -361,6 +360,25 @@ function factory(dependencies) { }, { shadow: true }); } + /** + * Performs the `channel_pin` RPC on `mail.channel`. + * + * @static + * @param {Object} param0 + * @param {boolean} [param0.pinned=false] + * @param {string} param0.uuid + */ + static async performRpcChannelPin({ pinned = false, uuid }) { + return this.env.services.rpc({ + model: 'mail.channel', + method: 'channel_pin', + kwargs: { + uuid, + pinned, + }, + }, { shadow: true }); + } + /** * Performs the `channel_create` RPC on `mail.channel`. * @@ -452,6 +470,25 @@ function factory(dependencies) { ); } + /** + * Performs the `execute_command` RPC on `mail.channel`. + * + * @static + * @param {Object} param0 + * @param {string} param0.command the command to execute + * @param {integer[]} param0.ids list of id of channels + */ + static async performRpcExecuteCommand({ command, ids }) { + return this.env.services.rpc({ + model: 'mail.channel', + method: 'execute_command', + args: [ids], + kwargs: { + command, + } + }); + } + /** * Performs RPC on the route `/mail/get_suggested_recipients`. * @@ -633,23 +670,17 @@ function factory(dependencies) { * * Only makes sense if isPendingPinned is set to the desired value. */ - notifyPinStateToServer() { - // method is called from _updateAfter so it cannot be async + async notifyPinStateToServer() { if (this.isPendingPinned) { - this.env.services.rpc({ - model: 'mail.channel', - method: 'channel_pin', - kwargs: { - uuid: this.uuid, - pinned: true, - }, - }, { shadow: true }); + await this.env.models['mail.thread'].performRpcChannelPin({ + pinned: true, + uuid: this.uuid, + }); } else { - this.env.services.rpc({ - model: 'mail.channel', - method: 'execute_command', - args: [[this.id], 'leave'] - }, { shadow: true }); + await this.env.models['mail.thread'].performRpcExecuteCommand({ + command: 'leave', + ids: [this.id], + }); } } @@ -700,6 +731,14 @@ function factory(dependencies) { }); } + /** + * Pin this thread and notify server of the change. + */ + async pin() { + this.update({ isPendingPinned: true }); + await this.notifyPinStateToServer(); + } + /** * Open a dialog to add channels as followers. */ @@ -867,6 +906,14 @@ function factory(dependencies) { await this.async(() => currentPartnerFollower.remove()); } + /** + * Unpin this thread and notify server of the change. + */ + async unpin() { + this.update({ isPendingPinned: false }); + await this.notifyPinStateToServer(); + } + /** * Called when current partner has explicitly stopped inserting some * input in composer. Useful to notify current partner has currently @@ -921,7 +968,7 @@ function factory(dependencies) { */ unsubscribe() { this.env.messaging.chatWindowManager.closeThread(this); - this.update({ isPendingPinned: false }); + this.unpin(); } //---------------------------------------------------------------------- @@ -1296,6 +1343,19 @@ function factory(dependencies) { this._currentPartnerLastNotifiedIsTyping = isTyping; } + /** + * Handles change of pinned state coming from the server. Useful to + * clear pending state once server acknowledged the change. + * + * @private + * @see isPendingPinned + */ + _onIsServerPinnedChanged() { + if (this.isServerPinned === this.isPendingPinned) { + this.update({ isPendingPinned: clear() }); + } + } + /** * Handles change of fold state coming from the server. Useful to * synchronize corresponding chat window. @@ -1350,34 +1410,6 @@ function factory(dependencies) { }); } - /** - * @override - */ - _updateAfter(previous) { - if (this.model !== 'mail.channel') { - // pin state only makes sense on channels - return; - } - if ( - this.isPendingPinned !== undefined && - previous.isPendingPinned !== this.isPendingPinned - ) { - this.notifyPinStateToServer(); - } - if (this.isServerPinned === this.isPendingPinned) { - this.update({ isPendingPinned: clear() }); - } - } - - /** - * @override - */ - _updateBefore() { - return { - isPendingPinned: this.isPendingPinned, - }; - } - //---------------------------------------------------------------------- // Handlers //---------------------------------------------------------------------- @@ -1701,6 +1733,16 @@ function factory(dependencies) { compute: '_computeNeedactionMessages', dependencies: ['messages'], }), + /** + * Not a real field, used to trigger `_onIsServerPinnedChanged` when one of + * the dependencies changes. + */ + onIsServerPinnedChanged: attr({ + compute: '_onIsServerPinnedChanged', + dependencies: [ + 'isServerPinned', + ], + }), /** * Not a real field, used to trigger `_onServerFoldStateChanged` when one of * the dependencies changes. diff --git a/addons/mail/static/src/models/user/user.js b/addons/mail/static/src/models/user/user.js index 2decc8efd92..172b8c5b780 100644 --- a/addons/mail/static/src/models/user/user.js +++ b/addons/mail/static/src/models/user/user.js @@ -113,7 +113,9 @@ function factory(dependencies) { thread.model === 'mail.channel' && thread.public === 'private' ); - if (!chat) { + if (!chat ||!chat.isPinned) { + // if chat is not pinned then it has to be pinned client-side + // and server-side, which is a side effect of following rpc chat = await this.async(() => this.env.models['mail.thread'].performRpcCreateChat({ partnerIds: [this.partner.id],