[IMP] mail: clean up code of RTC join/leave

Part of task-2366708

closes odoo/odoo#76528

X-original-commit: 9a05dfe06d9d5706d0e45ad14d6ef8902d6b276e
Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
This commit is contained in:
Sébastien Theys
2021-09-14 17:53:43 +00:00
parent ac4965e104
commit 6d948bdb47
13 changed files with 844 additions and 312 deletions
+3 -3
View File
@@ -418,7 +418,7 @@ class DiscussController(http.Controller):
:param int channel_id: id of the channel to join
"""
channel_partner_sudo = request.env['mail.channel.partner']._get_as_sudo_from_request_or_raise(request=request, channel_id=int(channel_id))
return channel_partner_sudo.channel_id._join_call()
return channel_partner_sudo._rtc_join_call()
@http.route('/mail/rtc/channel/leave_call', methods=['POST'], type="json", auth="public")
def channel_call_leave(self, channel_id):
@@ -426,7 +426,7 @@ class DiscussController(http.Controller):
:param int channel_id: id of the channel from which to disconnect
"""
channel_partner_sudo = request.env['mail.channel.partner']._get_as_sudo_from_request_or_raise(request=request, channel_id=int(channel_id))
channel_partner_sudo.channel_id._leave_call()
return channel_partner_sudo._rtc_leave_call()
@http.route('/mail/rtc/channel/cancel_call_invitation', methods=['POST'], type="json", auth="public")
def channel_call_cancel_invitation(self, channel_id, partner_ids=None, guest_ids=None):
@@ -437,7 +437,7 @@ class DiscussController(http.Controller):
if either partner_ids or guest_ids is set, only the specified ids will be invited.
"""
channel_partner_sudo = request.env['mail.channel.partner']._get_as_sudo_from_request_or_raise(request=request, channel_id=int(channel_id))
channel_partner_sudo.channel_id._cancel_rtc_invitations(partner_ids=partner_ids, guest_ids=guest_ids, inviting_member=channel_partner_sudo)
return channel_partner_sudo.channel_id._rtc_cancel_invitations(partner_ids=partner_ids, guest_ids=guest_ids)
@http.route('/mail/rtc/audio_worklet_processor', methods=['GET'], type='http', auth='public')
def audio_worklet_processor(self):
+59 -160
View File
@@ -294,8 +294,8 @@ class Channel(models.Model):
""" Adds the given partner_ids and guest_ids as member of self channels. """
self.check_access_rights('write')
self.check_access_rule('write')
partners = self.env['res.partner'].browse(partner_ids or [])
guests = self.env['mail.guest'].browse(guest_ids or [])
partners = self.env['res.partner'].browse(partner_ids or []).exists()
guests = self.env['mail.guest'].browse(guest_ids or []).exists()
members_to_create = []
for channel in self:
if channel.public == 'groups':
@@ -318,8 +318,6 @@ class Channel(models.Model):
'channel_id': channel.id,
} for partner in guests - existing_guests]
new_members = self.env['mail.channel.partner'].sudo().create(members_to_create)
if invite_to_rtc_call:
self._invite_members_to_rtc(partner_ids=partner_ids, guest_ids=guest_ids)
for channel_partner in new_members.filtered(lambda channel_partner: channel_partner.partner_id):
user = channel_partner.partner_id.user_ids[0] if channel_partner.partner_id.user_ids else self.env['res.users']
# notify invited members through the bus
@@ -352,9 +350,19 @@ class Channel(models.Model):
'id': channel_partner.channel_id.id,
'memberCount': channel_partner.channel_id.member_count,
'members': [('insert', new_partner_data)],
'invitedPartners': [('insert', [{'id': new_partner_data['id']}] if invite_to_rtc_call else [])]
},
})
if invite_to_rtc_call:
if self.env.user._is_public() and 'guest' in self.env.context:
guest = self.env.context.get('guest')
partner = self.env['res.partner']
else:
guest = self.env['mail.guest']
partner = self.env.user.partner_id
for channel in self:
current_channel_partner = self.env['mail.channel.partner'].sudo().search([('channel_id', '=', channel.id), ('partner_id', '=', partner.id), ('guest_id', '=', guest.id)])
if current_channel_partner and current_channel_partner.rtc_session_ids:
current_channel_partner._rtc_invite_members(partner_ids=partners.ids, guest_ids=guests.ids)
def _action_remove_members(self, partners):
""" Private implementation to remove members from channels. Done as sudo
@@ -389,165 +397,53 @@ class Channel(models.Model):
# RTC
# ------------------------------------------------------------
def _cancel_rtc_invitations(self, partner_ids=None, guest_ids=None, inviting_member=None):
def _rtc_cancel_invitations(self, partner_ids=None, guest_ids=None):
""" Cancels the invitations of the RTC call from all invited members (or the specified partner_ids).
:param list partner_ids: list of the partner ids from which the invitation has to be removed
:param list guest_ids: list of the guest ids from which the invitation has to be removed
if either partner_ids or guest_ids is set, only the specified ids will be invited.
:param inviting_member: if specified, only removes invitations if they are coming
from this channel member
"""
self.ensure_one()
notifications = []
domain = ['&', ('rtc_inviting_session_id', '!=', False), ('channel_id', '=', self.id)]
if inviting_member:
domain = expression.AND([domain, [('rtc_inviting_session_id.channel_partner_id', '=', inviting_member.id)]])
channel_partner_domain = [
('channel_id', '=', self.id),
('rtc_inviting_session_id', '!=', False),
]
if partner_ids or guest_ids:
domain = expression.AND([domain, ['|', ('partner_id', 'in', partner_ids or []), ('guest_id', 'in', guest_ids or [])]])
channel_partners = self.env['mail.channel.partner'].search(domain)
for member in channel_partners:
member.write({'rtc_inviting_session_id': False})
model, record_id = ('mail.guest', member.guest_id.id) if member.guest_id else ('res.partner', member.partner_id.id)
notifications.append([
(self._cr.dbname, model, record_id),
{
'type': 'rtc_incoming_invitation_update',
'payload': {
'channelId': self.id,
},
},
])
self.env['bus.bus'].sendmany(notifications)
def _invite_members_to_rtc(self, partner_ids=None, guest_ids=None):
""" Sends invitations to join the RTC call to all connected members of the thread who are not already invited.
:param list partner_ids: list of the partner ids to invite
:param list guest_ids: list of the guest ids to invite
if either partner_ids or guest_ids is set, only the specified ids will be invited.
"""
self.ensure_one()
if self.env.user._is_public():
guest = self.env.context.get('guest')
partner = self.env['res.partner']
else:
guest = self.env['mail.guest']
partner = self.env.user.partner_id
def is_current_user(r):
return (guest and r.guest_id == guest) or r.partner_id == partner
current_rtc_session = self.rtc_session_ids.filtered(is_current_user)
if not current_rtc_session:
return [], []
current_sessions_channel_partners = self.rtc_session_ids.channel_partner_id
notifications = []
invited_partners = []
invited_guests = []
domain = ['&', '&', ('rtc_inviting_session_id', '=', False), ('channel_id', '=', self.id), ('id', 'not in', current_sessions_channel_partners.ids)]
if partner:
domain = expression.AND([domain, [('partner_id', '!=', partner.id)]])
if guest:
domain = expression.AND([domain, [('guest_id', '!=', guest.id)]])
if partner_ids or guest_ids:
domain = expression.AND([domain, ['|', ('partner_id', 'in', partner_ids or []), ('guest_id', 'in', guest_ids or [])]])
channel_partners = self.env['mail.channel.partner'].search(domain)
for member in channel_partners:
member.rtc_inviting_session_id = current_rtc_session.id
channel_partner_domain = expression.AND([channel_partner_domain, [
'|',
('partner_id', 'in', partner_ids or []),
('guest_id', 'in', guest_ids or []),
]])
invited_partners = self.env['res.partner']
invited_guests = self.env['mail.guest']
invitation_notifications = []
for member in self.env['mail.channel.partner'].search(channel_partner_domain):
member.rtc_inviting_session_id = False
if member.partner_id:
invited_partners.append({
'id': member.partner_id.id,
'name': member.partner_id.name,
})
elif member.guest_id:
invited_guests.append({
'id': member.guest_id.id,
'name': member.guest_id.name,
})
model, record_id = ('mail.guest', member.guest_id.id) if member.guest_id else ('res.partner', member.partner_id.id)
notifications.append([
(self._cr.dbname, model, record_id),
{
'type': 'rtc_incoming_invitation_update',
'payload': {
'channelId': self.id,
'rtcSession': current_rtc_session._mail_rtc_session_format(),
},
},
])
notification = _("%s started a live conference", partner.name or guest.name)
self.message_post(body=notification, message_type='notification')
self.env['bus.bus'].sendmany(notifications)
return invited_partners, invited_guests
def _join_call(self):
self.ensure_one()
session_data, session_id = self._update_call_participation(joining=True)
if not session_id:
return
ice_servers = self.env['mail.ice.server']._get_ice_servers()
invited_partners = []
invited_guests = []
if len(self.rtc_session_ids) == 1 and self.channel_type in {'chat', 'group'}:
invited_partners, invited_guests = self._invite_members_to_rtc()
return {
'rtcSessions': session_data,
'iceServers': ice_servers or False,
'invitedGuests': invited_guests,
'invitedPartners': invited_partners,
'sessionId': session_id,
}
def _leave_call(self):
self.ensure_one()
self._update_call_participation(joining=False)
def _update_call_participation(self, joining=True):
""" Updates the call participation of the current partner and notifies members of
the channel if necessary.
:param bool joining : true if joining the call, false if leaving.
"""
if self.env.user._is_public():
guest = self.env.context.get('guest')
partner = self.env['res.partner']
else:
guest = self.env['mail.guest']
partner = self.env.user.partner_id
new_session_id = None
current_channel_partner = self.env['mail.channel.partner'].search([('channel_id', '=', self.id), ('partner_id', '=', partner.id), ('guest_id', '=', guest.id)], limit=1)
if not current_channel_partner:
return
current_channel_partner._remove_rtc_invitation()
old_sessions = self.rtc_session_ids.filtered(lambda s: s.channel_partner_id == current_channel_partner)
old_sessions._disconnect()
if joining:
new_session = self.env['mail.channel.rtc.session'].create({'channel_partner_id': current_channel_partner.id})
new_session_id = new_session.id
elif not old_sessions:
return
session_data_by_channel = self._notify_rtc_sessions_change()
return session_data_by_channel.get(self.id, []), new_session_id
def _notify_rtc_sessions_change(self):
session_data_by_channel = self.rtc_session_ids._mail_rtc_session_format_by_channel()
notifications = []
for record in self:
sessions_data = session_data_by_channel.get(record.id, [])
if not sessions_data:
# if there is no member left in the rtc call, all invitations are reset
record._cancel_rtc_invitations()
notifications.append([(self._cr.dbname, 'mail.channel', record.id), {
'type': 'rtc_sessions_update',
invited_partners |= member.partner_id
target = (self._cr.dbname, 'res.partner', member.partner_id.id)
else:
invited_guests |= member.guest_id
target = (self._cr.dbname, 'mail.guest', member.guest_id.id)
invitation_notifications.append((target, {
'type': 'mail.channel_update',
'payload': {
'channelId': record.id,
'rtcSessions': sessions_data,
'id': self.id,
'rtcInvitingSession': [('unlink',)],
},
}])
self.env['bus.bus'].sendmany(notifications)
return session_data_by_channel
}))
self.env['bus.bus'].sendmany(invitation_notifications)
channel_data = {'id': self.id}
if invited_guests:
channel_data['invitedGuests'] = [('insert-and-unlink', [{'id': guest.id} for guest in invited_guests])]
if invited_partners:
channel_data['invitedPartners'] = [('insert-and-unlink', [{'id': partner.id} for partner in invited_partners])]
if invited_partners or invited_guests:
self.env['bus.bus'].sendone((self._cr.dbname, 'mail.channel', self.id), {
'type': 'mail.channel_update',
'payload': channel_data,
})
return channel_data
# ------------------------------------------------------------
# MAILING
@@ -660,9 +556,9 @@ class Channel(models.Model):
self.env['bus.bus'].sudo().sendmany(bus_notifications)
# Last interest is updated for a chat when posting a message.
# So a notification is needed to update UI.
if self.is_chat:
if self.is_chat or self.channel_type == 'group':
notifications = []
for channel_partners in self.channel_last_seen_partner_ids:
for channel_partners in self.channel_last_seen_partner_ids.filtered('partner_id'):
notif = {
'type': 'mail.channel_last_interest_dt_changed',
'payload': {
@@ -873,7 +769,7 @@ class Channel(models.Model):
info['last_interest_dt'] = partner_channel.last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
if partner_channel.rtc_inviting_session_id:
info['rtc_inviting_session'] = {'id': partner_channel.rtc_inviting_session_id.id}
# add members infos
# add members info
if channel.channel_type != 'channel':
# avoid sending potentially a lot of members for big channels
# exclude chat and other small channels from this optimization because they are
@@ -886,9 +782,12 @@ class Channel(models.Model):
'seen_message_id': cp.seen_message_id.id,
} for cp in channel_partners], key=lambda p: p['partner_id'])
# add rtc sessions infos
if rtc_sessions_by_channel.get(channel.id):
info['rtc_sessions'] = rtc_sessions_by_channel[channel.id]
# add RTC sessions info
info.update({
'invitedGuests': [('insert-and-replace', [{'id': guest.id, 'name': guest.name} for guest in channel_partners.filtered('rtc_inviting_session_id').guest_id])],
'invitedPartners': [('insert-and-replace', [{'id': partner.id, 'name': partner.name} for partner in channel_partners.filtered('rtc_inviting_session_id').partner_id])],
'rtcSessions': [('insert-and-replace', rtc_sessions_by_channel.get(channel, []))],
})
channel_infos.append(info)
return channel_infos
+83 -23
View File
@@ -5,6 +5,7 @@ from werkzeug.exceptions import NotFound
from odoo import api, fields, models, _
from odoo.exceptions import AccessError
from odoo.osv import expression
class ChannelPartner(models.Model):
@@ -26,34 +27,13 @@ class ChannelPartner(models.Model):
is_minimized = fields.Boolean("Conversation is minimized")
is_pinned = fields.Boolean("Is pinned on the interface", default=True)
last_interest_dt = fields.Datetime("Last Interest", default=fields.Datetime.now, help="Contains the date and time of the last interesting event that happened in this channel for this partner. This includes: creating, joining, pinning, and new message posted.")
# RTC
rtc_session_ids = fields.One2many(string="RTC Sessions", comodel_name='mail.channel.rtc.session', inverse_name='channel_partner_id')
rtc_inviting_session_id = fields.Many2one('mail.channel.rtc.session', string='Ringing session')
def name_get(self):
return [(record.id, record.partner_id.name or record.guest_id.name) for record in self]
def _remove_rtc_invitation(self):
""" Removes the invitation to the rtc call and notifies the inviting partner if removed. """
notifications = []
for record in self:
if not record.rtc_inviting_session_id:
continue
model, record_id = ('mail.guest', record.rtc_inviting_session_id.guest_id.id) if record.rtc_inviting_session_id.guest_id else (
'res.partner', record.rtc_inviting_session_id.partner_id.id)
payload = {'channelId': record.channel_id.id}
if record.partner_id:
payload['partnerId'] = record.partner_id.id
else:
payload['guestId'] = record.guest_id.id
notifications.append([
(self._cr.dbname, model, record_id),
{
'type': 'rtc_outgoing_invitation_ended',
'payload': payload,
},
])
self.write({'rtc_inviting_session_id': False})
self.env['bus.bus'].sendmany(notifications)
def init(self):
self.env.cr.execute("CREATE UNIQUE INDEX IF NOT EXISTS mail_channel_partner_partner_unique ON %s (channel_id, partner_id) WHERE partner_id IS NOT NULL" % self._table)
self.env.cr.execute("CREATE UNIQUE INDEX IF NOT EXISTS mail_channel_partner_guest_unique ON %s (channel_id, guest_id) WHERE guest_id IS NOT NULL" % self._table)
@@ -111,3 +91,83 @@ class ChannelPartner(models.Model):
if guest:
return guest.env['mail.channel.partner'].sudo().search([('channel_id', '=', channel_id), ('guest_id', '=', guest.id)], limit=1)
return self.env['mail.channel.partner'].sudo()
# --------------------------------------------------------------------------
# RTC (voice/video)
# --------------------------------------------------------------------------
def _rtc_join_call(self):
self.ensure_one()
self.channel_id._rtc_cancel_invitations(partner_ids=self.partner_id.ids, guest_ids=self.guest_id.ids)
self.rtc_session_ids._disconnect()
rtc_session = self.env['mail.channel.rtc.session'].create({'channel_partner_id': self.id})
res = {
'iceServers': self.env['mail.ice.server']._get_ice_servers() or False,
'rtcSessions': [('insert-and-replace', self.channel_id.rtc_session_ids._mail_rtc_session_format_by_channel().get(self.channel_id))],
'sessionId': rtc_session.id,
}
if len(self.channel_id.rtc_session_ids) == 1 and self.channel_id.channel_type in {'chat', 'group'}:
self.channel_id.message_post(body=_("%s started a live conference", self.partner_id.name or self.guest_id.name), message_type='notification')
invited_partners, invited_guests = self._rtc_invite_members()
if invited_guests:
res['invitedGuests'] = [('insert', [{'id': guest.id, 'name': guest.name} for guest in invited_guests])]
if invited_partners:
res['invitedPartners'] = [('insert', [{'id': partner.id, 'name': partner.name} for partner in invited_partners])]
return res
def _rtc_leave_call(self):
self.ensure_one()
if self.rtc_session_ids:
self.rtc_session_ids.unlink()
else:
return self.channel_id._rtc_cancel_invitations(partner_ids=self.partner_id.ids, guest_ids=self.guest_id.ids)
def _rtc_invite_members(self, partner_ids=None, guest_ids=None):
""" Sends invitations to join the RTC call to all connected members of the thread who are not already invited.
:param list partner_ids: list of the partner ids to invite
:param list guest_ids: list of the guest ids to invite
if either partner_ids or guest_ids is set, only the specified ids will be invited.
"""
self.ensure_one()
channel_partner_domain = [
('channel_id', '=', self.channel_id.id),
('rtc_inviting_session_id', '=', False),
('rtc_session_ids', '=', False),
]
if partner_ids or guest_ids:
channel_partner_domain = expression.AND([channel_partner_domain, [
'|',
('partner_id', 'in', partner_ids or []),
('guest_id', 'in', guest_ids or []),
]])
invitation_notifications = []
invited_partners = self.env['res.partner']
invited_guests = self.env['mail.guest']
for member in self.env['mail.channel.partner'].search(channel_partner_domain):
member.rtc_inviting_session_id = self.rtc_session_ids.id
if member.partner_id:
invited_partners |= member.partner_id
target = (self._cr.dbname, 'res.partner', member.partner_id.id)
else:
invited_guests |= member.guest_id
target = (self._cr.dbname, 'mail.guest', member.guest_id.id)
invitation_notifications.append((target, {
'type': 'mail.channel_update',
'payload': {
'id': self.channel_id.id,
'rtcInvitingSession': [('insert', self.rtc_session_ids._mail_rtc_session_format())],
},
}))
self.env['bus.bus'].sendmany(invitation_notifications)
if invited_guests or invited_partners:
channel_data = {'id': self.channel_id.id}
if invited_guests:
channel_data['invitedGuests'] = [('insert', [{'id': guest.id, 'name': guest.name} for guest in invited_guests])]
if invited_partners:
channel_data['invitedPartners'] = [('insert', [{'id': partner.id, 'name': partner.name} for partner in invited_partners])]
self.env['bus.bus'].sendone((self._cr.dbname, 'mail.channel', self.channel_id.id), {
'type': 'mail.channel_update',
'payload': channel_data,
})
return invited_partners, invited_guests
+48 -29
View File
@@ -27,6 +27,35 @@ class MailRtcSession(models.Model):
'There can only be one rtc session per channel partner')
]
@api.model_create_multi
def create(self, vals_list):
rtc_sessions = super().create(vals_list)
self.env['bus.bus'].sendmany([((self._cr.dbname, 'mail.channel', channel.id), {
'type': 'rtc_sessions_update',
'payload': {
'id': channel.id,
'rtcSessions': [('insert', sessions_data)],
},
}) for channel, sessions_data in rtc_sessions._mail_rtc_session_format_by_channel().items()])
return rtc_sessions
def unlink(self):
channels = self.channel_id
for channel in channels:
if channel.rtc_session_ids and len(channel.rtc_session_ids - self) == 0:
# If there is no member left in the RTC call, all invitations are cancelled.
# Note: invitation depends on field `rtc_inviting_session_id` so the cancel must be
# done before the delete to be able to know who was invited.
channel._rtc_cancel_invitations()
self.env['bus.bus'].sendmany([((self._cr.dbname, 'mail.channel', channel.id), {
'type': 'rtc_sessions_update',
'payload': {
'id': channel.id,
'rtcSessions': [('insert-and-unlink', [{'id': session_data['id']} for session_data in sessions_data])],
},
}) for channel, sessions_data in self._mail_rtc_session_format_by_channel().items()])
return super().unlink()
def _update_and_broadcast(self, values):
""" Updates the session and notifies all members of the channel
of the change.
@@ -35,10 +64,8 @@ class MailRtcSession(models.Model):
self.write({key: values[key] for key in valid_values if key in valid_values})
session_data = self._mail_rtc_session_format()
self.env['bus.bus'].sendone((self._cr.dbname, 'mail.channel', self.channel_id.id), {
'type': 'rtc_session_data_update',
'payload': {
'rtcSession': session_data,
},
'type': 'mail.rtc_session_update',
'payload': session_data,
})
@api.autovacuum
@@ -47,34 +74,25 @@ class MailRtcSession(models.Model):
this can happen when the server or the user's browser crash
or when the user's odoo session ends.
"""
sessions = self.search([
('write_date', '<', fields.Datetime.now() - relativedelta(days=1))
])
if not sessions:
return
channel_ids = sessions.channel_id
sessions.unlink()
channel_ids._notify_rtc_sessions_change()
rtc_sessions = self.search([('write_date', '<', fields.Datetime.now() - relativedelta(days=1))])
rtc_sessions._disconnect()
def action_disconnect(self):
channels = self.channel_id
self._disconnect()
if channels:
channels._notify_rtc_sessions_change()
def _disconnect(self):
""" Unlinks the sessions and notifies the associated partners/guests that
their session ended.
"""
notifications = []
for record in self:
model, record_id = ('mail.guest', record.guest_id.id) if record.guest_id else ('res.partner', record.partner_id.id)
for rtc_session in self:
model_name, record_id = ('mail.guest', rtc_session.guest_id.id) if rtc_session.guest_id else ('res.partner', rtc_session.partner_id.id)
notifications.append([
(self._cr.dbname, model, record_id),
(self._cr.dbname, model_name, record_id),
{
'type': 'rtc_session_ended',
'payload': {
'sessionId': record.id,
'sessionId': rtc_session.id,
},
},
])
@@ -105,27 +123,28 @@ class MailRtcSession(models.Model):
return self.env['bus.bus'].sendmany(notifications)
def _mail_rtc_session_format(self):
self.ensure_one()
vals = {
'id': self.id,
'is_screen_sharing_on': self.is_screen_sharing_on,
'is_muted': self.is_muted,
'is_deaf': self.is_deaf,
'is_camera_on': self.is_camera_on,
'isCameraOn': self.is_camera_on,
'isDeaf': self.is_deaf,
'isMuted': self.is_muted,
'isScreenSharingOn': self.is_screen_sharing_on,
}
if self.guest_id:
vals['guest'] = {
vals['guest'] = [('insert', {
'id': self.guest_id.id,
'name': self.guest_id.name,
}
})]
else:
vals['partner'] = {
vals['partner'] = [('insert', {
'id': self.partner_id.id,
'name': self.partner_id.name,
}
})]
return vals
def _mail_rtc_session_format_by_channel(self):
data = {}
for record in self:
data.setdefault(record.channel_id.id, []).append(record._mail_rtc_session_format())
for rtc_session in self:
data.setdefault(rtc_session.channel_id, []).append(rtc_session._mail_rtc_session_format())
return data
@@ -335,13 +335,14 @@ export class ModelManager {
* @param {Listener} listener
*/
removeListener(listener) {
this._listeners.delete(listener);
this._listenersToNotifyInUpdateCycle.delete(listener);
this._listenersToNotifyAfterUpdateCycle.delete(listener);
for (const localId of this._localIdsObservedByListener.get(listener) || []) {
this._listenersObservingLocalId.get(localId).delete(listener);
if (this._listenersObservingFieldOfLocalId.has(localId)) {
for (const [, componentsUsingField] of this._listenersObservingFieldOfLocalId.get(localId)) {
componentsUsingField.delete(listener);
for (const [, listenersUsingField] of this._listenersObservingFieldOfLocalId.get(localId)) {
listenersUsingField.delete(listener);
}
}
}
@@ -794,7 +795,6 @@ export class ModelManager {
this._createdRecords.delete(record);
for (const listener of this._listenersObservingLocalId.get(record.localId)) {
this._markListenerToNotify(listener);
this._localIdsObservedByListener.get(listener).delete(record.localId);
}
for (const listener of this._listenersObservingAllByModel.get(Model)) {
this._markListenerToNotify(listener);
@@ -93,20 +93,18 @@ function factory(dependencies) {
return this.messaging.models['mail.guest'].insert(message.payload);
case 'mail.message_update':
return this.messaging.models['mail.message'].insert(message.payload);
case 'mail.rtc_session_update':
return this.messaging.models['mail.rtc_session'].insert(message.payload);
case 'res.users_settings_changed':
return this._handleNotificationResUsersSettings(message.payload);
case 'rtc_peer_notification':
return this._handleNotificationRtcPeerToPeer(message.payload);
case 'rtc_incoming_invitation_update':
return this._handleNotificationRtcInvitation(message.payload);
case 'rtc_outgoing_invitation_ended':
return this._handleNotificationRtcInvitationEnded(message.payload);
case 'rtc_sessions_update':
return this._handleNotificationRtcSessionUpdate(message.payload);
case 'rtc_session_ended':
return this._handleNotificationRtcSessionEnded(message.payload);
case 'rtc_session_data_update':
return this._handleNotificationRtcSessionDataUpdate(message.payload);
case 'res_users_settings_volumes_update':
return this._handleNotificationVolumeSettingUpdate(message.payload);
}
@@ -545,25 +543,6 @@ function factory(dependencies) {
return channel.update({ rtcInvitingSession: unlink() });
}
/**
* @private
* @param {Object} data
* @param {number} [data.channelId]
* @param {number} [data.partnerId]
*/
async _handleNotificationRtcInvitationEnded({ channelId, partnerId, guestId }) {
const channel = this.messaging.models['mail.thread'].findFromIdentifyingData({ id: channelId, model: 'mail.channel' });
if (!channel) {
return;
}
const partner = partnerId && this.messaging.models['mail.partner'].findFromIdentifyingData({ id: partnerId });
const guest = guestId && this.messaging.models['mail.guest'].findFromIdentifyingData({ id: guestId });
channel.update({
invitedPartners: partner && unlink(partner),
invitedGuests: guest && unlink(guest),
});
}
/**
* @private
* @param {Object} data
@@ -574,16 +553,6 @@ function factory(dependencies) {
this.messaging.mailRtc.handleNotification(sender, content);
}
/**
* @private
* @param {Object} data
* @param {Object} [data.rtcSession]
*/
async _handleNotificationRtcSessionDataUpdate({ rtcSession }) {
const rtcSessionModel = this.messaging.models['mail.rtc_session'];
rtcSessionModel.insert(rtcSessionModel.convertData(rtcSession));
}
/**
* @private
* @param {Object} data
@@ -614,11 +583,11 @@ function factory(dependencies) {
/**
* @private
* @param {Object} data
* @param {string} [data.channelId]
* @param {string} [data.id]
* @param {Object} [data.rtcSessions]
*/
async _handleNotificationRtcSessionUpdate({ channelId, rtcSessions }) {
const channel = this.messaging.models['mail.thread'].findFromIdentifyingData({ id: channelId, model: 'mail.channel' });
async _handleNotificationRtcSessionUpdate({ id, rtcSessions }) {
const channel = this.messaging.models['mail.thread'].findFromIdentifyingData({ id, model: 'mail.channel' });
if (!channel) {
return;
}
+4 -5
View File
@@ -101,17 +101,16 @@ function factory(dependencies) {
*/
async filterCallees(currentSessions) {
const currentSessionsTokens = new Set(currentSessions.map(session => session.peerToken));
if (this.currentRtcSession && !currentSessionsTokens.has(this.currentRtcSession.peerToken)) {
// if the current RTC session is not in the channel sessions, this call is no longer valid.
this.channel && this.channel.endCall();
return;
}
for (const token of Object.keys(this._peerConnections)) {
if (!currentSessionsTokens.has(token)) {
this._addLogEntry(token, 'session removed from the server');
this._removePeer(token);
}
}
if (this.channel && this.currentRtcSession && !currentSessionsTokens.has(this.currentRtcSession.peerToken)) {
// if the current RTC session is not in the channel sessions, this call is no longer valid.
this.channel.endCall();
}
}
/**
@@ -2,7 +2,6 @@
import { registerNewModel } from '@mail/model/model_core';
import { attr, many2one, one2one } from '@mail/model/model_field';
import { unlink } from '@mail/model/model_field_command';
function factory(dependencies) {
@@ -33,10 +32,11 @@ function factory(dependencies) {
* @param {MouseEvent} ev
*/
async onClick(ev) {
if (!this.invitedPartner || this.invitedGuest) {
if (!this.invitedPartner && !this.invitedGuest) {
return;
}
await this.env.services.rpc(({
const channel = this.channel;
const channelData = await this.env.services.rpc(({
route: '/mail/rtc/channel/cancel_call_invitation',
params: {
channel_id: this.channel.id,
@@ -44,10 +44,10 @@ function factory(dependencies) {
guest_ids: this.invitedGuest && [this.invitedGuest.id],
},
}));
this.channel.update({
invitedGuests: this.invitedGuest && unlink(this.invitedGuest),
invitedPartners: this.invitedPartner && unlink(this.invitedPartner),
});
if (!channel.exists()) {
return;
}
channel.update(channelData);
}
/**
@@ -31,42 +31,6 @@ function factory(dependencies) {
// Public
//----------------------------------------------------------------------
/**
* @static
* @param {Object} data
* @return {Object}
*/
static convertData(data) {
const data2 = {};
if ('id' in data) {
data2.id = data.id;
}
if ('is_camera_on' in data) {
data2.isCameraOn = data.is_camera_on;
}
if ('is_deaf' in data) {
data2.isDeaf = data.is_deaf;
}
if ('is_muted' in data) {
data2.isMuted = data.is_muted;
}
if ('is_screen_sharing_on' in data) {
data2.isScreenSharingOn = data.is_screen_sharing_on;
}
// relations
if ('partner' in data) {
data2.partner = insert(data.partner);
}
if ('guest' in data) {
data2.guest = insert(data.guest);
}
if ('channel' in data) {
data2.channel = insert(data.channel);
}
return data2;
}
/**
* restores the session to its default values
*/
+15 -10
View File
@@ -200,6 +200,12 @@ function factory(dependencies) {
if ('id' in data) {
data2.id = data.id;
}
if ('invitedGuests' in data) {
data2.invitedGuests = data.invitedGuests;
}
if ('invitedPartners' in data) {
data2.invitedPartners = data.invitedPartners;
}
if ('is_minimized' in data && 'state' in data) {
data2.serverFoldState = data.is_minimized ? data.state : 'closed';
}
@@ -261,8 +267,8 @@ function factory(dependencies) {
if ('rtc_inviting_session' in data) {
data2.rtcInvitingSession = insert(data.rtc_inviting_session);
}
if ('rtc_sessions' in data) {
data2.rtcSessions = insert(data.rtc_sessions.map(record => this.messaging.models['mail.rtc_session'].convertData(record)));
if ('rtcSessions' in data) {
data2.rtcSessions = data.rtcSessions;
}
if ('seen_partners_info' in data) {
if (!data.seen_partners_info) {
@@ -325,8 +331,6 @@ function factory(dependencies) {
this.messaging.soundEffects.channelLeave.play();
}
this.update({
invitedGuests: clear(),
invitedPartners: clear(),
mailRtc: unlink(),
rtcInvitingSession: unlink(),
});
@@ -776,12 +780,15 @@ function factory(dependencies) {
channel_id: this.id,
},
}, { shadow: true }));
if (!this.exists()) {
return;
}
this.update({
mailRtc: link(this.messaging.mailRtc),
rtcInvitingSession: unlink(),
rtcSessions: insertAndReplace(rtcSessions.map(record => this.messaging.models['mail.rtc_session'].convertData(record))),
invitedGuests: insertAndReplace(invitedGuests),
invitedPartners: insertAndReplace(invitedPartners),
rtcSessions,
invitedGuests,
invitedPartners,
});
await this.async(() => this.messaging.mailRtc.initSession({
currentSessionId: sessionId,
@@ -806,9 +813,7 @@ function factory(dependencies) {
*/
updateRtcSessions(rtcSessions) {
const oldCount = this.rtcSessions.length;
this.update({
rtcSessions: insertAndReplace(rtcSessions.map(record => this.messaging.models['mail.rtc_session'].convertData(record)))
});
this.update({ rtcSessions });
if (this.mailRtc) {
const newCount = this.rtcSessions.length;
if (newCount > oldCount) {
+1
View File
@@ -10,4 +10,5 @@ from . import test_mail_template
from . import test_mail_tools
from . import test_res_partner
from . import test_res_users_settings
from . import test_rtc
from . import test_update_notification
+574
View File
@@ -0,0 +1,574 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from dateutil.relativedelta import relativedelta
from odoo import fields
from odoo.addons.mail.tests.common import MailCommon
from odoo.tests import tagged
from odoo.tests.common import users
from odoo.tools import mute_logger
@tagged('RTC')
class TestChannelInternals(MailCommon):
@users('employee')
@mute_logger('odoo.models.unlink')
def test_01_join_call(self):
"""Join call should remove existing sessions, remove invitation, create a new session, and return data."""
channel = self.env['mail.channel'].browse(self.env['mail.channel'].channel_create(name='Test Channel')['id'])
channel_partner = channel.sudo().channel_last_seen_partner_ids.filtered(lambda channel_partner: channel_partner.partner_id == self.user_employee.partner_id)
channel_partner._rtc_join_call()
self.env['bus.bus'].sudo().search([]).unlink()
with self.assertBus(
[
(self.cr.dbname, 'res.partner', self.user_employee.partner_id.id), # end of previous session
(self.cr.dbname, 'mail.channel', channel.id), # update sessions
(self.cr.dbname, 'mail.channel', channel.id), # update sessions
],
[
{
'type': 'rtc_session_ended',
'payload': {
'sessionId': channel_partner.rtc_session_ids.id,
},
},
{
'type': 'rtc_sessions_update',
'payload': {
'id': channel.id,
'rtcSessions': [('insert-and-unlink', [{'id': channel_partner.rtc_session_ids.id}])],
},
},
{
'type': 'rtc_sessions_update',
'payload': {
'id': channel.id,
'rtcSessions': [('insert', [{
'id': channel_partner.rtc_session_ids.id + 1,
'isCameraOn': False,
'isDeaf': False,
'isMuted': False,
'isScreenSharingOn': False,
'partner': [('insert', {
'id': self.user_employee.partner_id.id,
'name': "Ernest Employee",
})],
}])],
},
},
]
):
res = channel_partner._rtc_join_call()
self.assertEqual(res, {
'iceServers': False,
'rtcSessions': [('insert-and-replace', [{
'id': channel_partner.rtc_session_ids.id,
'isCameraOn': False,
'isDeaf': False,
'isMuted': False,
'isScreenSharingOn': False,
'partner': [('insert', {
'id': self.user_employee.partner_id.id,
'name': "Ernest Employee",
})],
}])],
'sessionId': channel_partner.rtc_session_ids.id,
})
@users('employee')
@mute_logger('odoo.models.unlink')
def test_10_start_call_in_chat_should_invite_all_members_to_call(self):
test_user = self.env['res.users'].sudo().create({'name': "Test User", 'login': 'test'})
channel = self.env['mail.channel'].browse(self.env['mail.channel'].channel_get(partners_to=(self.user_employee.partner_id + test_user.partner_id).ids)['id'])
channel_partner = channel.sudo().channel_last_seen_partner_ids.filtered(lambda channel_partner: channel_partner.partner_id == self.user_employee.partner_id)
channel_partner._rtc_join_call()
last_rtc_session_id = channel_partner.rtc_session_ids.id
channel_partner._rtc_leave_call()
self.env['bus.bus'].sudo().search([]).unlink()
with self.assertBus(
[
(self.cr.dbname, 'mail.channel', channel.id), # update new session
(self.cr.dbname, 'mail.channel', channel.id), # message_post "started a live conference" (not asserted below)
(self.cr.dbname, 'res.partner', self.user_employee.partner_id.id), # update of last interest (not asserted below)
(self.cr.dbname, 'res.partner', test_user.partner_id.id), # update of last interest (not asserted below)
(self.cr.dbname, 'res.partner', test_user.partner_id.id), # incoming invitation
(self.cr.dbname, 'mail.channel', channel.id), # update list of invitations
],
[
{
'type': 'rtc_sessions_update',
'payload': {
'id': channel.id,
'rtcSessions': [('insert', [{
'id': last_rtc_session_id + 1,
'isCameraOn': False,
'isDeaf': False,
'isMuted': False,
'isScreenSharingOn': False,
'partner': [('insert', {
'id': self.user_employee.partner_id.id,
'name': "Ernest Employee",
})],
}])],
},
},
{
'type': 'mail.channel_update',
'payload': {
'id': channel.id,
'invitedPartners': [('insert', [{'id': test_user.partner_id.id, 'name': 'Test User'}])],
},
},
]
):
res = channel_partner._rtc_join_call()
self.assertNotIn('invitedGuests', res)
self.assertIn('invitedPartners', res)
self.assertEqual(res['invitedPartners'], [('insert', [{'id': test_user.partner_id.id, 'name': "Test User"}])])
@users('employee')
@mute_logger('odoo.models.unlink')
def test_11_start_call_in_group_should_invite_all_members_to_call(self):
test_user = self.env['res.users'].sudo().create({'name': "Test User", 'login': 'test'})
test_guest = self.env['mail.guest'].sudo().create({'name': "Test Guest"})
channel = self.env['mail.channel'].browse(self.env['mail.channel'].create_group(partners_to=(self.user_employee.partner_id + test_user.partner_id).ids)['id'])
channel.add_members(guest_ids=test_guest.ids)
channel_partner = channel.sudo().channel_last_seen_partner_ids.filtered(lambda channel_partner: channel_partner.partner_id == self.user_employee.partner_id)
channel_partner._rtc_join_call()
last_rtc_session_id = channel_partner.rtc_session_ids.id
channel_partner._rtc_leave_call()
self.env['bus.bus'].sudo().search([]).unlink()
with self.assertBus(
[
(self.cr.dbname, 'mail.channel', channel.id), # update new session
(self.cr.dbname, 'mail.channel', channel.id), # message_post "started a live conference" (not asserted below)
(self.cr.dbname, 'res.partner', self.user_employee.partner_id.id), # update of last interest (not asserted below)
(self.cr.dbname, 'res.partner', test_user.partner_id.id), # update of last interest (not asserted below)
(self.cr.dbname, 'res.partner', test_user.partner_id.id), # incoming invitation
(self.cr.dbname, 'mail.guest', test_guest.id), # incoming invitation
(self.cr.dbname, 'mail.channel', channel.id), # update list of invitations
],
[
{
'type': 'rtc_sessions_update',
'payload': {
'id': channel.id,
'rtcSessions': [('insert', [{
'id': last_rtc_session_id + 1,
'isCameraOn': False,
'isDeaf': False,
'isMuted': False,
'isScreenSharingOn': False,
'partner': [('insert', {
'id': self.user_employee.partner_id.id,
'name': "Ernest Employee",
})],
}])],
},
},
{
'type': 'rtc_sessions_update',
'payload': {
'id': channel.id,
'rtcSessions': [('insert', [{
'id': last_rtc_session_id + 1,
'isCameraOn': False,
'isDeaf': False,
'isMuted': False,
'isScreenSharingOn': False,
'partner': [('insert', {
'id': self.user_employee.partner_id.id,
'name': "Ernest Employee",
})],
}])],
},
},
{
'type': 'mail.channel_update',
'payload': {
'id': channel.id,
'invitedGuests': [('insert', [{'id': test_guest.id, 'name': 'Test Guest'}])],
'invitedPartners': [('insert', [{'id': test_user.partner_id.id, 'name': 'Test User'}])],
},
},
]
):
res = channel_partner._rtc_join_call()
self.assertIn('invitedGuests', res)
self.assertEqual(res['invitedGuests'], [('insert', [{'id': test_guest.id, 'name': 'Test Guest'}])])
self.assertIn('invitedPartners', res)
self.assertEqual(res['invitedPartners'], [('insert', [{'id': test_user.partner_id.id, 'name': "Test User"}])])
@users('employee')
@mute_logger('odoo.models.unlink')
def test_20_join_call_should_cancel_pending_invitations(self):
test_user = self.env['res.users'].sudo().create({'name': "Test User", 'login': 'test'})
test_guest = self.env['mail.guest'].sudo().create({'name': "Test Guest"})
channel = self.env['mail.channel'].browse(self.env['mail.channel'].create_group(partners_to=(self.user_employee.partner_id + test_user.partner_id).ids)['id'])
channel.add_members(guest_ids=test_guest.ids)
channel_partner = channel.sudo().channel_last_seen_partner_ids.filtered(lambda channel_partner: channel_partner.partner_id == self.user_employee.partner_id)
channel_partner._rtc_join_call()
channel_partner_test_user = channel.sudo().channel_last_seen_partner_ids.filtered(lambda channel_partner: channel_partner.partner_id == test_user.partner_id)
self.env['bus.bus'].sudo().search([]).unlink()
with self.assertBus(
[
(self.cr.dbname, 'res.partner', test_user.partner_id.id), # update invitation
(self.cr.dbname, 'mail.channel', channel.id), # update list of invitations
(self.cr.dbname, 'mail.channel', channel.id), # update sessions
],
[
{
'type': 'mail.channel_update',
'payload': {
'id': channel.id,
'rtcInvitingSession': [('unlink',)],
},
},
{
'type': 'mail.channel_update',
'payload': {
'id': channel.id,
'invitedPartners': [('insert-and-unlink', [{'id': test_user.partner_id.id}])],
},
},
{
'type': 'rtc_sessions_update',
'payload': {
'id': channel.id,
'rtcSessions': [('insert', [
{
'id': channel_partner.rtc_session_ids.id + 1,
'isCameraOn': False,
'isDeaf': False,
'isMuted': False,
'isScreenSharingOn': False,
'partner': [('insert', {
'id': test_user.partner_id.id,
'name': 'Test User',
})],
},
])],
},
},
]
):
channel_partner_test_user._rtc_join_call()
channel_partner_test_guest = channel.sudo().channel_last_seen_partner_ids.filtered(lambda channel_partner: channel_partner.guest_id == test_guest)
self.env['bus.bus'].sudo().search([]).unlink()
with self.assertBus(
[
(self.cr.dbname, 'mail.guest', test_guest.id), # update invitation
(self.cr.dbname, 'mail.channel', channel.id), # update list of invitations
(self.cr.dbname, 'mail.channel', channel.id), # update sessions
],
[
{
'type': 'mail.channel_update',
'payload': {
'id': channel.id,
'rtcInvitingSession': [('unlink',)],
},
},
{
'type': 'mail.channel_update',
'payload': {
'id': channel.id,
'invitedGuests': [('insert-and-unlink', [{'id': test_guest.id}])],
},
},
{
'type': 'rtc_sessions_update',
'payload': {
'id': channel.id,
'rtcSessions': [('insert', [
{
'id': channel_partner.rtc_session_ids.id + 2,
'isCameraOn': False,
'isDeaf': False,
'isMuted': False,
'isScreenSharingOn': False,
'guest': [('insert', {
'id': test_guest.id,
'name': 'Test Guest',
})],
},
])],
},
},
]
):
channel_partner_test_guest._rtc_join_call()
@users('employee')
@mute_logger('odoo.models.unlink')
def test_21_leave_call_should_cancel_pending_invitations(self):
test_user = self.env['res.users'].sudo().create({'name': "Test User", 'login': 'test'})
test_guest = self.env['mail.guest'].sudo().create({'name': "Test Guest"})
channel = self.env['mail.channel'].browse(self.env['mail.channel'].create_group(partners_to=(self.user_employee.partner_id + test_user.partner_id).ids)['id'])
channel.add_members(guest_ids=test_guest.ids)
channel_partner = channel.sudo().channel_last_seen_partner_ids.filtered(lambda channel_partner: channel_partner.partner_id == self.user_employee.partner_id)
channel_partner._rtc_join_call()
channel_partner_test_user = channel.sudo().channel_last_seen_partner_ids.filtered(lambda channel_partner: channel_partner.partner_id == test_user.partner_id)
self.env['bus.bus'].sudo().search([]).unlink()
with self.assertBus(
[
(self.cr.dbname, 'res.partner', test_user.partner_id.id), # update invitation
(self.cr.dbname, 'mail.channel', channel.id), # update list of invitations
],
[
{
'type': 'mail.channel_update',
'payload': {
'id': channel.id,
'rtcInvitingSession': [('unlink',)],
},
},
{
'type': 'mail.channel_update',
'payload': {
'id': channel.id,
'invitedPartners': [('insert-and-unlink', [{'id': test_user.partner_id.id}])],
},
},
]
):
channel_partner_test_user._rtc_leave_call()
channel_partner_test_guest = channel.sudo().channel_last_seen_partner_ids.filtered(lambda channel_partner: channel_partner.guest_id == test_guest)
self.env['bus.bus'].sudo().search([]).unlink()
with self.assertBus(
[
(self.cr.dbname, 'mail.guest', test_guest.id), # update invitation
(self.cr.dbname, 'mail.channel', channel.id), # update list of invitations
],
[
{
'type': 'mail.channel_update',
'payload': {
'id': channel.id,
'rtcInvitingSession': [('unlink',)],
},
},
{
'type': 'mail.channel_update',
'payload': {
'id': channel.id,
'invitedGuests': [('insert-and-unlink', [{'id': test_guest.id}])],
},
},
]
):
channel_partner_test_guest._rtc_leave_call()
@users('employee')
@mute_logger('odoo.models.unlink')
def test_25_lone_call_participant_leaving_call_should_cancel_pending_invitations(self):
test_user = self.env['res.users'].sudo().create({'name': "Test User", 'login': 'test'})
test_guest = self.env['mail.guest'].sudo().create({'name': "Test Guest"})
channel = self.env['mail.channel'].browse(self.env['mail.channel'].create_group(partners_to=(self.user_employee.partner_id + test_user.partner_id).ids)['id'])
channel.add_members(guest_ids=test_guest.ids)
channel_partner = channel.sudo().channel_last_seen_partner_ids.filtered(lambda channel_partner: channel_partner.partner_id == self.user_employee.partner_id)
channel_partner._rtc_join_call()
self.env['bus.bus'].sudo().search([]).unlink()
with self.assertBus(
[
(self.cr.dbname, 'res.partner', test_user.partner_id.id), # update invitation
(self.cr.dbname, 'mail.guest', test_guest.id), # update invitation
(self.cr.dbname, 'mail.channel', channel.id), # update list of invitations
(self.cr.dbname, 'mail.channel', channel.id), # update sessions
],
[
{
'type': 'mail.channel_update',
'payload': {
'id': channel.id,
'rtcInvitingSession': [('unlink',)],
},
},
{
'type': 'mail.channel_update',
'payload': {
'id': channel.id,
'rtcInvitingSession': [('unlink',)],
},
},
{
'type': 'mail.channel_update',
'payload': {
'id': channel.id,
'invitedGuests': [('insert-and-unlink', [{'id': test_guest.id}])],
'invitedPartners': [('insert-and-unlink', [{'id': test_user.partner_id.id}])],
},
},
{
'type': 'rtc_sessions_update',
'payload': {
'id': channel.id,
'rtcSessions': [('insert-and-unlink', [{'id': channel_partner.rtc_session_ids.id}])],
},
},
]
):
channel_partner._rtc_leave_call()
@users('employee')
@mute_logger('odoo.models.unlink')
def test_30_add_members_while_in_call_should_invite_new_members_to_call(self):
test_user = self.env['res.users'].sudo().create({'name': "Test User", 'login': 'test'})
test_guest = self.env['mail.guest'].sudo().create({'name': "Test Guest"})
channel = self.env['mail.channel'].browse(self.env['mail.channel'].create_group(partners_to=self.user_employee.partner_id.ids)['id'])
channel_partner = channel.sudo().channel_last_seen_partner_ids.filtered(lambda channel_partner: channel_partner.partner_id == self.user_employee.partner_id)
channel_partner._rtc_join_call()
self.env['bus.bus'].sudo().search([]).unlink()
with self.assertBus(
[
(self.cr.dbname, 'res.partner', test_user.partner_id.id), # channel joined (not asserted below)
(self.cr.dbname, 'mail.channel', channel.id), # message_post "invited" (not asserted below)
(self.cr.dbname, 'res.partner', self.user_employee.partner_id.id), # update of last interest (not asserted below)
(self.cr.dbname, 'res.partner', test_user.partner_id.id), # update of last interest (not asserted below)
(self.cr.dbname, 'mail.channel', channel.id), # new members (not asserted below)
(self.cr.dbname, 'res.partner', test_user.partner_id.id), # incoming invitation
(self.cr.dbname, 'mail.guest', test_guest.id), # incoming invitation
(self.cr.dbname, 'mail.channel', channel.id), # update list of invitations
],
[
{
'type': 'mail.channel_update',
'payload': {
'id': channel.id,
'rtcInvitingSession': [('insert', {
'id': channel_partner.rtc_session_ids.id,
'isCameraOn': False,
'isDeaf': False,
'isMuted': False,
'isScreenSharingOn': False,
'partner': [('insert', {
'id': self.user_employee.partner_id.id,
'name': "Ernest Employee",
})],
})],
},
},
{
'type': 'mail.channel_update',
'payload': {
'id': channel.id,
'rtcInvitingSession': [('insert', {
'id': channel_partner.rtc_session_ids.id,
'isCameraOn': False,
'isDeaf': False,
'isMuted': False,
'isScreenSharingOn': False,
'partner': [('insert', {
'id': self.user_employee.partner_id.id,
'name': "Ernest Employee",
})],
})],
},
},
{
'type': 'mail.channel_update',
'payload': {
'id': channel.id,
'invitedGuests': [('insert', [{'id': test_guest.id, 'name': 'Test Guest'}])],
'invitedPartners': [('insert', [{'id': test_user.partner_id.id, 'name': 'Test User'}])],
},
},
],
):
channel.add_members(partner_ids=test_user.partner_id.ids, guest_ids=test_guest.ids, invite_to_rtc_call=True)
@users('employee')
@mute_logger('odoo.models.unlink')
def test_40_leave_call_should_remove_existing_sessions_of_user_in_channel_and_return_data(self):
channel = self.env['mail.channel'].browse(self.env['mail.channel'].create_group(partners_to=self.user_employee.partner_id.ids)['id'])
channel_partner = channel.sudo().channel_last_seen_partner_ids.filtered(lambda channel_partner: channel_partner.partner_id == self.user_employee.partner_id)
channel_partner._rtc_join_call()
self.env['bus.bus'].sudo().search([]).unlink()
with self.assertBus(
[
(self.cr.dbname, 'mail.channel', channel.id), # update list of sessions
],
[
{
'type': 'rtc_sessions_update',
'payload': {
'id': channel.id,
'rtcSessions': [('insert-and-unlink', [{'id': channel_partner.rtc_session_ids.id}])],
},
},
],
):
channel_partner._rtc_leave_call()
@users('employee')
@mute_logger('odoo.models.unlink')
def test_50_garbage_collect_should_remove_old_sessions_and_notify_data(self):
channel = self.env['mail.channel'].browse(self.env['mail.channel'].create_group(partners_to=self.user_employee.partner_id.ids)['id'])
channel_partner = channel.sudo().channel_last_seen_partner_ids.filtered(lambda channel_partner: channel_partner.partner_id == self.user_employee.partner_id)
channel_partner._rtc_join_call()
channel_partner.rtc_session_ids.flush()
channel_partner.rtc_session_ids._write({'write_date': fields.Datetime.now() - relativedelta(days=2)})
self.env['bus.bus'].sudo().search([]).unlink()
with self.assertBus(
[
(self.cr.dbname, 'res.partner', self.user_employee.partner_id.id), # session ended
(self.cr.dbname, 'mail.channel', channel.id), # update list of sessions
],
[
{
'type': 'rtc_session_ended',
'payload': {
'sessionId': channel_partner.rtc_session_ids.id,
},
},
{
'type': 'rtc_sessions_update',
'payload': {
'id': channel.id,
'rtcSessions': [('insert-and-unlink', [{'id': channel_partner.rtc_session_ids.id}])],
},
},
],
):
self.env['mail.channel.rtc.session'].sudo()._gc_inactive_sessions()
self.assertFalse(channel_partner.rtc_session_ids)
@users('employee')
@mute_logger('odoo.models.unlink')
def test_51_action_disconnect_should_remove_selected_session_and_notify_data(self):
channel = self.env['mail.channel'].browse(self.env['mail.channel'].create_group(partners_to=self.user_employee.partner_id.ids)['id'])
channel_partner = channel.sudo().channel_last_seen_partner_ids.filtered(lambda channel_partner: channel_partner.partner_id == self.user_employee.partner_id)
channel_partner._rtc_join_call()
self.env['bus.bus'].sudo().search([]).unlink()
with self.assertBus(
[
(self.cr.dbname, 'res.partner', self.user_employee.partner_id.id), # session ended
(self.cr.dbname, 'mail.channel', channel.id), # update list of sessions
],
[
{
'type': 'rtc_session_ended',
'payload': {
'sessionId': channel_partner.rtc_session_ids.id,
},
},
{
'type': 'rtc_sessions_update',
'payload': {
'id': channel.id,
'rtcSessions': [('insert-and-unlink', [{'id': channel_partner.rtc_session_ids.id}])],
},
},
],
):
channel_partner.rtc_session_ids.action_disconnect()
self.assertFalse(channel_partner.rtc_session_ids)
@@ -115,6 +115,8 @@ class TestDiscussFullPerformance(TransactionCase):
'description': 'General announcements for all employees.',
'group_based_subscription': True,
'id': channel_general.id,
'invitedGuests': [('insert-and-replace', [])],
'invitedPartners': [('insert-and-replace', [])],
'is_minimized': False,
'is_pinned': True,
'last_interest_dt': channel_general.channel_last_seen_partner_ids.filtered(lambda p: p.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
@@ -124,6 +126,7 @@ class TestDiscussFullPerformance(TransactionCase):
'message_unread_counter': 5,
'name': 'general',
'public': 'groups',
'rtcSessions': [('insert-and-replace', [])],
'seen_message_id': False,
'state': 'open',
'uuid': channel_general.uuid,
@@ -137,6 +140,8 @@ class TestDiscussFullPerformance(TransactionCase):
'description': False,
'group_based_subscription': False,
'id': channel_channel_public_1.id,
'invitedGuests': [('insert-and-replace', [])],
'invitedPartners': [('insert-and-replace', [])],
'is_minimized': False,
'is_pinned': True,
'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),
@@ -146,6 +151,7 @@ class TestDiscussFullPerformance(TransactionCase):
'message_unread_counter': 0,
'name': 'public 1',
'public': 'public',
'rtcSessions': [('insert-and-replace', [])],
'seen_message_id': next(res['message_id'] for res in channel_channel_public_1._channel_last_message_ids()),
'state': 'open',
'uuid': channel_channel_public_1.uuid,
@@ -159,6 +165,8 @@ class TestDiscussFullPerformance(TransactionCase):
'description': False,
'group_based_subscription': False,
'id': channel_channel_public_2.id,
'invitedGuests': [('insert-and-replace', [])],
'invitedPartners': [('insert-and-replace', [])],
'is_minimized': False,
'is_pinned': True,
'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),
@@ -168,6 +176,7 @@ class TestDiscussFullPerformance(TransactionCase):
'message_unread_counter': 0,
'name': 'public 2',
'public': 'public',
'rtcSessions': [('insert-and-replace', [])],
'seen_message_id': next(res['message_id'] for res in channel_channel_public_2._channel_last_message_ids()),
'state': 'open',
'uuid': channel_channel_public_2.uuid,
@@ -181,6 +190,8 @@ class TestDiscussFullPerformance(TransactionCase):
'description': False,
'group_based_subscription': False,
'id': channel_channel_group_1.id,
'invitedGuests': [('insert-and-replace', [])],
'invitedPartners': [('insert-and-replace', [])],
'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),
@@ -190,6 +201,7 @@ class TestDiscussFullPerformance(TransactionCase):
'message_unread_counter': 0,
'name': 'group 1',
'public': 'groups',
'rtcSessions': [('insert-and-replace', [])],
'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,
@@ -203,6 +215,8 @@ class TestDiscussFullPerformance(TransactionCase):
'description': False,
'group_based_subscription': False,
'id': channel_channel_group_2.id,
'invitedGuests': [('insert-and-replace', [])],
'invitedPartners': [('insert-and-replace', [])],
'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),
@@ -212,6 +226,7 @@ class TestDiscussFullPerformance(TransactionCase):
'message_unread_counter': 0,
'name': 'group 2',
'public': 'groups',
'rtcSessions': [('insert-and-replace', [])],
'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,
@@ -225,6 +240,8 @@ class TestDiscussFullPerformance(TransactionCase):
'description': False,
'group_based_subscription': False,
'id': channel_channel_private_1.id,
'invitedGuests': [('insert-and-replace', [])],
'invitedPartners': [('insert-and-replace', [])],
'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),
@@ -234,6 +251,7 @@ class TestDiscussFullPerformance(TransactionCase):
'message_unread_counter': 0,
'name': 'private 1',
'public': 'private',
'rtcSessions': [('insert-and-replace', [])],
'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,
@@ -247,6 +265,8 @@ class TestDiscussFullPerformance(TransactionCase):
'description': False,
'group_based_subscription': False,
'id': channel_channel_private_2.id,
'invitedGuests': [('insert-and-replace', [])],
'invitedPartners': [('insert-and-replace', [])],
'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),
@@ -256,6 +276,7 @@ class TestDiscussFullPerformance(TransactionCase):
'message_unread_counter': 0,
'name': 'private 2',
'public': 'private',
'rtcSessions': [('insert-and-replace', [])],
'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,
@@ -269,6 +290,8 @@ class TestDiscussFullPerformance(TransactionCase):
'description': False,
'group_based_subscription': False,
'id': channel_group_1.id,
'invitedGuests': [('insert-and-replace', [])],
'invitedPartners': [('insert-and-replace', [])],
'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),
@@ -302,6 +325,7 @@ class TestDiscussFullPerformance(TransactionCase):
'message_unread_counter': 0,
'name': '',
'public': 'private',
'rtcSessions': [('insert-and-replace', [])],
'seen_message_id': False,
'seen_partners_info': [
{
@@ -329,6 +353,8 @@ class TestDiscussFullPerformance(TransactionCase):
'description': False,
'group_based_subscription': False,
'id': channel_chat_1.id,
'invitedGuests': [('insert-and-replace', [])],
'invitedPartners': [('insert-and-replace', [])],
'is_minimized': False,
'is_pinned': True,
'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),
@@ -362,6 +388,7 @@ class TestDiscussFullPerformance(TransactionCase):
'message_unread_counter': 0,
'name': 'Ernest Employee, test14',
'public': 'private',
'rtcSessions': [('insert-and-replace', [])],
'seen_partners_info': [
{
'fetched_message_id': False,
@@ -389,6 +416,8 @@ class TestDiscussFullPerformance(TransactionCase):
'description': False,
'group_based_subscription': False,
'id': channel_chat_2.id,
'invitedGuests': [('insert-and-replace', [])],
'invitedPartners': [('insert-and-replace', [])],
'is_minimized': False,
'is_pinned': True,
'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),
@@ -422,6 +451,7 @@ class TestDiscussFullPerformance(TransactionCase):
'message_unread_counter': 0,
'name': 'Ernest Employee, test15',
'public': 'private',
'rtcSessions': [('insert-and-replace', [])],
'seen_partners_info': [
{
'fetched_message_id': False,
@@ -449,6 +479,8 @@ class TestDiscussFullPerformance(TransactionCase):
'description': False,
'group_based_subscription': False,
'id': channel_chat_3.id,
'invitedGuests': [('insert-and-replace', [])],
'invitedPartners': [('insert-and-replace', [])],
'is_minimized': False,
'is_pinned': True,
'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),
@@ -482,6 +514,7 @@ class TestDiscussFullPerformance(TransactionCase):
'message_unread_counter': 0,
'name': 'Ernest Employee, test2',
'public': 'private',
'rtcSessions': [('insert-and-replace', [])],
'seen_partners_info': [
{
'fetched_message_id': False,
@@ -509,6 +542,8 @@ class TestDiscussFullPerformance(TransactionCase):
'description': False,
'group_based_subscription': False,
'id': channel_chat_4.id,
'invitedGuests': [('insert-and-replace', [])],
'invitedPartners': [('insert-and-replace', [])],
'is_minimized': False,
'is_pinned': True,
'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),
@@ -542,6 +577,7 @@ class TestDiscussFullPerformance(TransactionCase):
'message_unread_counter': 0,
'name': 'Ernest Employee, test3',
'public': 'private',
'rtcSessions': [('insert-and-replace', [])],
'seen_partners_info': [
{
'fetched_message_id': False,
@@ -569,6 +605,8 @@ class TestDiscussFullPerformance(TransactionCase):
'description': False,
'group_based_subscription': False,
'id': channel_livechat_1.id,
'invitedGuests': [('insert-and-replace', [])],
'invitedPartners': [('insert-and-replace', [])],
'is_minimized': False,
'is_pinned': True,
'last_interest_dt': channel_livechat_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),
@@ -608,6 +646,7 @@ class TestDiscussFullPerformance(TransactionCase):
'name': 'test1 Ernest Employee',
'operator_pid': (self.users[0].partner_id.id, 'Ernest Employee'),
'public': 'private',
'rtcSessions': [('insert-and-replace', [])],
'seen_partners_info': [
{
'fetched_message_id': False,
@@ -635,6 +674,8 @@ class TestDiscussFullPerformance(TransactionCase):
'description': False,
'group_based_subscription': False,
'id': channel_livechat_2.id,
'invitedGuests': [('insert-and-replace', [])],
'invitedPartners': [('insert-and-replace', [])],
'is_minimized': False,
'is_pinned': True,
'last_interest_dt': channel_livechat_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),
@@ -674,6 +715,7 @@ class TestDiscussFullPerformance(TransactionCase):
'name': 'anon 2 Ernest Employee',
'operator_pid': (self.users[0].partner_id.id, 'Ernest Employee'),
'public': 'private',
'rtcSessions': [('insert-and-replace', [])],
'seen_partners_info': [
{
'fetched_message_id': next(res['message_id'] for res in channel_livechat_2._channel_last_message_ids()),