[FIX] calendar: improve calendar recurrency creation performances

Turn off calendar.attendees notification during synced_events creation
to improve performances. The calendar notification are only computed
once after all the synced events have been created.

Add a flag in google_calendar.js to avoid retriggering synchronization
while navigating the calendar view if the past synchronization is still
pending.

opw: 2450259 + fixes/adaptations to 14.3 branch

FW of https://github.com/odoo/odoo/pull/69619

closes odoo/odoo#70035

X-original-commit: 28ccb2b88a7793058c81aabffb43071165886f40
Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
Signed-off-by: Arnaud Joset <arj-odoo@users.noreply.github.com>
This commit is contained in:
fw-bot
2021-04-30 06:47:00 +00:00
committed by Arnaud Joset
parent fa90f97627
commit f6c03b7097
8 changed files with 102 additions and 24 deletions
+12 -9
View File
@@ -389,11 +389,12 @@ class Meeting(models.Model):
events = super().create(other_vals)
for vals in recurring_vals:
recurrence_values = {field: vals.pop(field) for field in recurrence_fields if field in vals}
vals['follow_recurrence'] = True
event = super().create(vals)
events |= event
recurring_events = super().create(recurring_vals)
events += recurring_events
for event, vals in zip(recurring_events, recurring_vals):
recurrence_values = {field: vals.pop(field) for field in recurrence_fields if field in vals}
if vals.get('recurrency'):
detached_events = event._apply_recurrence_values(recurrence_values)
detached_events.active = False
@@ -402,8 +403,8 @@ class Meeting(models.Model):
self.env.ref('calendar.calendar_template_meeting_invitation', raise_if_not_found=False)
)
events._sync_activities(fields={f for vals in vals_list for f in vals.keys()})
events._setup_alarms()
if not self.env.context.get('dont_notify'):
events._setup_alarms()
return events
@@ -488,8 +489,8 @@ class Meeting(models.Model):
(detached_events & self).active = False
(detached_events - self).with_context(archive_on_error=True).unlink()
self._setup_alarms()
if not self.env.context.get('dont_notify'):
self._setup_alarms()
current_attendees = self.filtered('active').attendee_ids
if 'partner_ids' in values:
@@ -656,7 +657,9 @@ class Meeting(models.Model):
# Don't trigger for past alarms, they would be skipped by design
cron._trigger(at=at)
if any(alarm.alarm_type == 'notification' for alarm in event.alarm_ids):
alarm_manager._notify_next_alarm(event.partner_ids.ids)
# filter events before notifying attendees through calendar_alarm_manager
need_notifs = event.filtered(lambda ev: ev.alarm_ids and ev.stop >= fields.Datetime.now())
alarm_manager._notify_next_alarm(need_notifs.partner_ids.ids)
# ------------------------------------------------------------
# RECURRENCY
@@ -292,7 +292,7 @@ class RecurrenceRule(models.Model):
:param dstart: if provided, only write events starting from this point in time
"""
events = self._get_events_from(dtstart) if dtstart else self.calendar_event_ids
return events.with_context(no_mail_to_attendees=True).write(dict(values, recurrence_update='self_only'))
return events.with_context(no_mail_to_attendees=True, dont_notify=True).write(dict(values, recurrence_update='self_only'))
def _rrule_serialize(self):
"""
+4 -2
View File
@@ -41,7 +41,8 @@ class Meeting(models.Model):
@api.model_create_multi
def create(self, vals_list):
return super().create([
notify_context = self.env.context.get('dont_notify', False)
return super(Meeting, self.with_context(dont_notify=notify_context)).create([
dict(vals, need_sync=False) if vals.get('recurrence_id') or vals.get('recurrency') else vals
for vals in vals_list
])
@@ -50,7 +51,8 @@ class Meeting(models.Model):
recurrence_update_setting = values.get('recurrence_update')
if recurrence_update_setting in ('all_events', 'future_events') and len(self) == 1:
values = dict(values, need_sync=False)
res = super().write(values)
notify_context = self.env.context.get('dont_notify', False)
res = super(Meeting, self.with_context(dont_notify=notify_context)).write(values)
if recurrence_update_setting in ('all_events',) and len(self) == 1 and values.keys() & self._get_google_synced_fields():
self.recurrence_id.need_sync = True
return res
@@ -2,8 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import re
from odoo import api, fields, models
from odoo import api, models
from odoo.addons.google_calendar.utils.google_calendar import GoogleCalendarService
@@ -137,12 +136,16 @@ class RecurrenceRule(models.Model):
vals['calendar_event_ids'] = [(4, base_event.id)]
# event_tz is written on event in Google but on recurrence in Odoo
vals['event_tz'] = gevent.start.get('timeZone')
recurrence = super()._create_from_google(gevents, vals_list)
recurrence._apply_recurrence()
recurrence = super(RecurrenceRule, self.with_context(dont_notify=True))._create_from_google(gevents, vals_list)
recurrence.with_context(dont_notify=True)._apply_recurrence()
return recurrence
def _get_sync_domain(self):
return [('calendar_event_ids.user_id', '=', self.env.user.id)]
# Empty rrule may exists in historical data. It is not a desired behavior but it could have been created with
# older versions of the module. When synced, these recurrency may come back from Google after database cleaning
# and trigger errors as the records are not properly populated.
# We also prevent sync of other user recurrent events.
return [('calendar_event_ids.user_id', '=', self.env.user.id), ('rrule', '!=', False)]
@api.model
def _odoo_values(self, google_recurrence, default_reminders=()):
+2 -2
View File
@@ -148,7 +148,7 @@ class GoogleSync(models.AbstractModel):
dict(self._odoo_values(e, default_reminders), need_sync=False)
for e in new
]
new_odoo = self._create_from_google(new, odoo_values)
new_odoo = self.with_context(dont_notify=True)._create_from_google(new, odoo_values)
cancelled = existing.cancelled()
cancelled_odoo = self.browse(cancelled.odoo_ids(self.env))
cancelled_odoo._cancel()
@@ -161,7 +161,7 @@ class GoogleSync(models.AbstractModel):
# Migration from 13.4 does not fill write_date. Therefore, we force the update from Google.
if not odoo_record.write_date or updated >= pytz.utc.localize(odoo_record.write_date):
vals = dict(self._odoo_values(gevent, default_reminders), need_sync=False)
odoo_record._write_from_google(gevent, vals)
odoo_record.with_context(dont_notify=True)._write_from_google(gevent, vals)
synced_records |= odoo_record
return synced_records
@@ -21,6 +21,7 @@ const GoogleCalendarModel = CalendarModel.include({
init: function () {
this._super.apply(this, arguments);
this.google_is_sync = true;
this.google_pending_sync = false;
},
/**
@@ -39,6 +40,10 @@ const GoogleCalendarModel = CalendarModel.include({
*/
async _loadCalendar() {
const _super = this._super.bind(this);
// When the calendar synchronization takes some time, prevents retriggering the sync while navigating the calendar.
if (this.google_pending_sync) {
return _super(...arguments);
}
try {
await Promise.race([
new Promise(resolve => setTimeout(resolve, 1000)),
@@ -49,6 +54,7 @@ const GoogleCalendarModel = CalendarModel.include({
error.event.preventDefault();
}
console.error("Could not synchronize Google events now.", error);
this.google_pending_sync = false;
}
return _super(...arguments);
},
@@ -56,6 +62,7 @@ const GoogleCalendarModel = CalendarModel.include({
_syncGoogleCalendar(shadow = false) {
var self = this;
var context = this.getSession().user_context;
this.google_pending_sync = true;
return this._rpc({
route: '/google_calendar/sync_data',
params: {
@@ -69,6 +76,7 @@ const GoogleCalendarModel = CalendarModel.include({
} else if (result.status === "no_new_event_from_google" || result.status === "need_refresh") {
self.google_is_sync = true;
}
self.google_pending_sync = false;
return result
});
},
@@ -681,3 +681,68 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
event = self.env['calendar.event'].search([('google_id', '=', values.get('id'))])
self.assertFalse(event.exists(), "The old event should not exits anymore")
self.assertGoogleAPINotCalled()
@patch_api
def test_new_google_notifications(self):
""" Event from Google should not create notifications and trigger. It ruins the perfs on large databases """
cron_id = self.env.ref('calendar.ir_cron_scheduler_alarm').id
triggers_before = self.env['ir.cron.trigger'].search([('cron_id', '=', cron_id)])
google_id = 'oj44nep1ldf8a3ll02uip0c9aa'
start = datetime.today() + relativedelta(months=1, day=1, hours=1)
end = datetime.today() + relativedelta(months=1, day=1, hours=2)
updated = datetime.today() + relativedelta(minutes=1)
values = {
'id': google_id,
'description': 'Small mini desc',
'organizer': {'email': 'odoocalendarref@gmail.com', 'self': True},
'summary': 'Pricing new update',
'visibility': 'public',
'attendees': [{
'displayName': 'Mitchell Admin',
'email': 'admin@yourcompany.example.com',
'responseStatus': 'needsAction'
}, ],
'reminders': {'overrides': [{"method": "email", "minutes": 10}], 'useDefault': False},
'start': {
'dateTime': pytz.utc.localize(start).isoformat(),
'timeZone': 'Europe/Brussels'
},
'end': {
'dateTime': pytz.utc.localize(end).isoformat(),
'timeZone': 'Europe/Brussels'
},
}
self.env['calendar.event']._sync_google2odoo(GoogleEvent([values]))
triggers_after = self.env['ir.cron.trigger'].search([('cron_id', '=', cron_id)])
new_triggers = triggers_after - triggers_before
self.assertFalse(new_triggers, "The event should not be created with triggers.")
# Event was created from Google and now it will be Updated from Google.
# No further notifications should be created.
values = {
'id': google_id,
'updated': pytz.utc.localize(updated).isoformat(),
'description': 'New Super description',
'organizer': {'email': 'odoocalendarref@gmail.com', 'self': True},
'summary': 'Pricing was not good, now it is correct',
'visibility': 'public',
'attendees': [{
'displayName': 'Mitchell Admin',
'email': 'admin@yourcompany.example.com',
'responseStatus': 'needsAction'
}, ],
'reminders': {'overrides': [{"method": "email", "minutes": 10}], 'useDefault': False},
'start': {
'dateTime': pytz.utc.localize(start).isoformat(),
'timeZone': 'Europe/Brussels'
},
'end': {
'dateTime': pytz.utc.localize(end).isoformat(),
'timeZone': 'Europe/Brussels'
},
}
self.env['calendar.event']._sync_google2odoo(GoogleEvent([values]))
triggers_after = self.env['ir.cron.trigger'].search([('cron_id', '=', cron_id)])
new_triggers = triggers_after - triggers_before
self.assertFalse(new_triggers, "The event should not be created with triggers.")
self.assertGoogleAPINotCalled()
@@ -1,15 +1,12 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime, date
from datetime import datetime
from dateutil.relativedelta import relativedelta
from unittest.mock import MagicMock, patch
from unittest.mock import patch
from odoo.addons.google_calendar.utils.google_calendar import GoogleCalendarService
from odoo.addons.google_calendar.models.res_users import User
from odoo.addons.google_calendar.models.google_sync import GoogleSync
from odoo.modules.registry import Registry
from odoo.addons.google_account.models.google_service import TIMEOUT
from odoo.addons.google_calendar.tests.test_sync_common import TestSyncGoogle, patch_api