From a395277fedbd8f56a95a793fbbbfbbf669de5730 Mon Sep 17 00:00:00 2001 From: "Gabriel de Paula Felix (gdpf)" Date: Mon, 14 Aug 2023 12:27:44 +0000 Subject: [PATCH] [IMP] microsoft_calendar: add synchronization pause button and tests Configuration and tests added for 'pause synchronization' button (Outlook module). closes odoo/odoo#126560 Task-id: 3290898 Signed-off-by: Arnaud Joset (arj) --- addons/microsoft_calendar/controllers/main.py | 7 ++-- addons/microsoft_calendar/models/calendar.py | 11 +++--- .../models/microsoft_sync.py | 34 +++++++++++-------- .../models/res_config_settings.py | 2 ++ addons/microsoft_calendar/models/res_users.py | 18 +++++++++- .../microsoft_calendar_controller.js | 11 ++++++ .../microsoft_calendar_controller.xml | 8 ++++- .../microsoft_calendar_model.js | 4 ++- addons/microsoft_calendar/tests/common.py | 1 + .../tests/test_create_events.py | 21 ++++++++++++ .../tests/test_delete_events.py | 20 +++++++++++ .../tests/test_update_events.py | 21 ++++++++++++ .../views/res_config_settings_views.xml | 4 +++ 13 files changed, 136 insertions(+), 26 deletions(-) diff --git a/addons/microsoft_calendar/controllers/main.py b/addons/microsoft_calendar/controllers/main.py index fc9b307df6b..a93cbc95967 100644 --- a/addons/microsoft_calendar/controllers/main.py +++ b/addons/microsoft_calendar/controllers/main.py @@ -42,10 +42,11 @@ class MicrosoftCalendarController(http.Controller): # If App authorized, and user access accepted, We launch the synchronization need_refresh = request.env.user.sudo()._sync_microsoft_calendar() - # If synchronization has been stopped - if not need_refresh and request.env.user.microsoft_synchronization_stopped: + # If synchronization has been stopped or paused + sync_status = request.env.user._get_microsoft_sync_status() + if not need_refresh and sync_status != "sync_active": return { - "status": "sync_stopped", + "status": sync_status, "url": '' } return { diff --git a/addons/microsoft_calendar/models/calendar.py b/addons/microsoft_calendar/models/calendar.py index aba66fabb6d..2bbbd4be589 100644 --- a/addons/microsoft_calendar/models/calendar.py +++ b/addons/microsoft_calendar/models/calendar.py @@ -150,11 +150,12 @@ class Meeting(models.Model): self._check_recurrence_overlapping(values['start']) # if a single event becomes the base event of a recurrency, it should be first - # removed from the Outlook calendar. - if 'recurrency' in values and values['recurrency']: - for e in self.filtered(lambda e: not e.recurrency and not e.recurrence_id): - e._microsoft_delete(e._get_organizer(), e.ms_organizer_event_id, timeout=3) - e.microsoft_id = False + # removed from the Outlook calendar. Additionaly, checks if synchronization is not paused. + if self.env.user._get_microsoft_sync_status() != "sync_paused" and values.get('recurrency'): + for event in self: + if not event.recurrency and not event.recurrence_id: + event._microsoft_delete(event._get_organizer(), event.ms_organizer_event_id, timeout=3) + event.microsoft_id = False res = super(Meeting, self.with_context(dont_notify=notify_context)).write(values) diff --git a/addons/microsoft_calendar/models/microsoft_sync.py b/addons/microsoft_calendar/models/microsoft_sync.py index 0a99f63b3d7..6ed89e94aac 100644 --- a/addons/microsoft_calendar/models/microsoft_sync.py +++ b/addons/microsoft_calendar/models/microsoft_sync.py @@ -76,20 +76,22 @@ class MicrosoftSync(models.AbstractModel): def write(self, vals): fields_to_sync = [x for x in vals if x in self._get_microsoft_synced_fields()] - if fields_to_sync and 'need_sync_m' not in vals and not self.env.user.microsoft_synchronization_stopped: + if fields_to_sync and 'need_sync_m' not in vals and self.env.user._get_microsoft_sync_status() == "sync_active": vals['need_sync_m'] = True result = super().write(vals) - for record in self.filtered(lambda e: e.need_sync_m and e.ms_organizer_event_id): - if not vals.get('active', True): - # We need to delete the event. Cancel is not sufficant. Errors may occurs - record._microsoft_delete(record._get_organizer(), record.ms_organizer_event_id, timeout=3) - elif fields_to_sync: - values = record._microsoft_values(fields_to_sync) - if not values: - continue - record._microsoft_patch(record._get_organizer(), record.ms_organizer_event_id, values, timeout=3) + if self.env.user._get_microsoft_sync_status() != "sync_paused": + for record in self: + if record.need_sync_m and record.ms_organizer_event_id: + if not vals.get('active', True): + # We need to delete the event. Cancel is not sufficient. Errors may occur. + record._microsoft_delete(record._get_organizer(), record.ms_organizer_event_id, timeout=3) + elif fields_to_sync: + values = record._microsoft_values(fields_to_sync) + if not values: + continue + record._microsoft_patch(record._get_organizer(), record.ms_organizer_event_id, values, timeout=3) return result @@ -100,9 +102,10 @@ class MicrosoftSync(models.AbstractModel): vals.update({'need_sync_m': False}) records = super().create(vals_list) - records_to_sync = records.filtered(lambda r: r.need_sync_m and r.active) - for record in records_to_sync: - record._microsoft_insert(record._microsoft_values(self._get_microsoft_synced_fields()), timeout=3) + if self.env.user._get_microsoft_sync_status() != "sync_paused": + for record in records: + if record.need_sync_m and record.active: + record._microsoft_insert(record._microsoft_values(self._get_microsoft_synced_fields()), timeout=3) return records @api.depends('microsoft_id') @@ -159,8 +162,9 @@ class MicrosoftSync(models.AbstractModel): def unlink(self): synced = self._get_synced_events() - for ev in synced: - ev._microsoft_delete(ev._get_organizer(), ev.ms_organizer_event_id) + if self.env.user._get_microsoft_sync_status() != "sync_paused": + for ev in synced: + ev._microsoft_delete(ev._get_organizer(), ev.ms_organizer_event_id) return super().unlink() def _write_from_microsoft(self, microsoft_event, vals): diff --git a/addons/microsoft_calendar/models/res_config_settings.py b/addons/microsoft_calendar/models/res_config_settings.py index 38ac9565fb1..7198c24572e 100644 --- a/addons/microsoft_calendar/models/res_config_settings.py +++ b/addons/microsoft_calendar/models/res_config_settings.py @@ -9,3 +9,5 @@ class ResConfigSettings(models.TransientModel): cal_microsoft_client_id = fields.Char("Microsoft Client_id", config_parameter='microsoft_calendar_client_id', default='') cal_microsoft_client_secret = fields.Char("Microsoft Client_key", config_parameter='microsoft_calendar_client_secret', default='') + cal_microsoft_sync_paused = fields.Boolean("Microsoft Synchronization Paused", config_parameter='microsoft_calendar_sync_paused', + help="Indicates if synchronization with Outlook Calendar is paused or not.") diff --git a/addons/microsoft_calendar/models/res_users.py b/addons/microsoft_calendar/models/res_users.py index 9169f6ad290..a13e126f52c 100644 --- a/addons/microsoft_calendar/models/res_users.py +++ b/addons/microsoft_calendar/models/res_users.py @@ -11,6 +11,7 @@ from odoo.exceptions import UserError from odoo.loglevels import exception_to_unicode from odoo.addons.microsoft_account.models.microsoft_service import DEFAULT_MICROSOFT_TOKEN_ENDPOINT from odoo.addons.microsoft_calendar.utils.microsoft_calendar import InvalidSyncToken +from odoo.tools import str2bool _logger = logging.getLogger(__name__) @@ -88,9 +89,18 @@ class User(models.Model): error_key) raise UserError(error_msg) + def _get_microsoft_sync_status(self): + """ Returns the calendar synchronization status (active, paused or stopped). """ + status = "sync_active" + if str2bool(self.env['ir.config_parameter'].sudo().get_param("microsoft_calendar_sync_paused"), default=False): + status = "sync_paused" + elif self.microsoft_synchronization_stopped: + status = "sync_stopped" + return status + def _sync_microsoft_calendar(self): self.ensure_one() - if self.microsoft_synchronization_stopped: + if self._get_microsoft_sync_status() != "sync_active": return False calendar_service = self.env["calendar.event"]._get_microsoft_service() full_sync = not bool(self.microsoft_calendar_sync_token) @@ -138,3 +148,9 @@ class User(models.Model): self.microsoft_synchronization_stopped = False self.env['calendar.recurrence']._restart_microsoft_sync() self.env['calendar.event']._restart_microsoft_sync() + + def unpause_microsoft_synchronization(self): + self.env['ir.config_parameter'].sudo().set_param("microsoft_calendar_sync_paused", False) + + def pause_microsoft_synchronization(self): + self.env['ir.config_parameter'].sudo().set_param("microsoft_calendar_sync_paused", True) diff --git a/addons/microsoft_calendar/static/src/views/microsoft_calendar/microsoft_calendar_controller.js b/addons/microsoft_calendar/static/src/views/microsoft_calendar/microsoft_calendar_controller.js index 29992f5dc88..253e1f1bd51 100644 --- a/addons/microsoft_calendar/static/src/views/microsoft_calendar/microsoft_calendar_controller.js +++ b/addons/microsoft_calendar/static/src/views/microsoft_calendar/microsoft_calendar_controller.js @@ -59,5 +59,16 @@ patch(AttendeeCalendarController.prototype, { await this.model.load(); }, }); + }, + + onMicrosoftSyncUnpause() { + if (this.isSystemUser) { + this.env.services.action.doAction("base_setup.action_general_configuration"); + } else { + this.dialog.add(AlertDialog, { + title: this.env._t("Configuration"), + body: this.env._t("Your administrator paused the synchronization with Outlook Calendar."), + }); + } } }); diff --git a/addons/microsoft_calendar/static/src/views/microsoft_calendar/microsoft_calendar_controller.xml b/addons/microsoft_calendar/static/src/views/microsoft_calendar/microsoft_calendar_controller.xml index b51bbe26139..6f5a24c7fbe 100644 --- a/addons/microsoft_calendar/static/src/views/microsoft_calendar/microsoft_calendar_controller.xml +++ b/addons/microsoft_calendar/static/src/views/microsoft_calendar/microsoft_calendar_controller.xml @@ -6,7 +6,13 @@
- + diff --git a/addons/microsoft_calendar/static/src/views/microsoft_calendar/microsoft_calendar_model.js b/addons/microsoft_calendar/static/src/views/microsoft_calendar/microsoft_calendar_model.js index e214e91542d..4ddbf42589c 100644 --- a/addons/microsoft_calendar/static/src/views/microsoft_calendar/microsoft_calendar_model.js +++ b/addons/microsoft_calendar/static/src/views/microsoft_calendar/microsoft_calendar_model.js @@ -13,6 +13,7 @@ patch(AttendeeCalendarModel.prototype, { this.rpc = rpc; this.microsoftIsSync = true; this.microsoftPendingSync = false; + this.microsoftIsPaused = false; }, /** @@ -49,11 +50,12 @@ patch(AttendeeCalendarModel.prototype, { silent, }, ); - if (["need_config_from_admin", "need_auth", "sync_stopped"].includes(result.status)) { + if (["need_config_from_admin", "need_auth", "sync_stopped", "sync_paused"].includes(result.status)) { this.microsoftIsSync = false; } else if (result.status === "no_new_event_from_microsoft" || result.status === "need_refresh") { this.microsoftIsSync = true; } + this.microsoftIsPaused = result.status == "sync_paused"; this.microsoftPendingSync = false; return result; }, diff --git a/addons/microsoft_calendar/tests/common.py b/addons/microsoft_calendar/tests/common.py index 9a370f1e3fd..ecfa818052a 100644 --- a/addons/microsoft_calendar/tests/common.py +++ b/addons/microsoft_calendar/tests/common.py @@ -35,6 +35,7 @@ class TestCommon(HttpCase): @patch_api def setUp(self): super(TestCommon, self).setUp() + self.env.user.unpause_microsoft_synchronization() # prepare users self.organizer_user = self.env["res.users"].search([("name", "=", "Mike Organizer")]) diff --git a/addons/microsoft_calendar/tests/test_create_events.py b/addons/microsoft_calendar/tests/test_create_events.py index 43568784a5c..40aaa3e1a78 100644 --- a/addons/microsoft_calendar/tests/test_create_events.py +++ b/addons/microsoft_calendar/tests/test_create_events.py @@ -278,3 +278,24 @@ class TestCreateEvents(TestCommon): ) # Assert that no insert call was made. mock_insert.assert_not_called() + + @patch.object(MicrosoftCalendarService, 'insert') + def test_create_event_with_sync_config_paused(self, mock_insert): + """ + Creates an event with the synchronization paused, the event must have its field 'need_sync_m' as True + for later synchronizing it with Outlook Calendar. + """ + # Set user sync configuration as active and then pause the synchronization. + self.organizer_user.microsoft_synchronization_stopped = False + self.organizer_user.pause_microsoft_synchronization() + + # Try to create a simple event in Odoo Calendar. + record = self.env["calendar.event"].with_user(self.organizer_user).create(self.simple_event_values) + self.call_post_commit_hooks() + record.invalidate_recordset() + + # Ensure that synchronization is paused, insert wasn't called and record is waiting to be synced. + self.assertFalse(self.organizer_user.microsoft_synchronization_stopped) + self.assertEqual(self.organizer_user._get_microsoft_sync_status(), "sync_paused") + self.assertTrue(record.need_sync_m, "Sync variable must be true for updating event when sync re-activates") + mock_insert.assert_not_called() diff --git a/addons/microsoft_calendar/tests/test_delete_events.py b/addons/microsoft_calendar/tests/test_delete_events.py index 4fed7cd5120..b7605511988 100644 --- a/addons/microsoft_calendar/tests/test_delete_events.py +++ b/addons/microsoft_calendar/tests/test_delete_events.py @@ -297,3 +297,23 @@ class TestDeleteEvents(TestCommon): token=mock_get_token(self.organizer_user), timeout=ANY ) + + @patch.object(MicrosoftCalendarService, 'delete') + def test_delete_synced_event_with_sync_config_paused(self, mock_delete): + """ + Deletes an event with the Outlook Calendar synchronization paused, the event must be archived completely. + """ + # Set user synchronization configuration as active and pause it. + self.organizer_user.microsoft_synchronization_stopped = False + self.organizer_user.pause_microsoft_synchronization() + + # Try to delete a simple event in Odoo Calendar. + self.simple_event.with_user(self.organizer_user).unlink() + self.call_post_commit_hooks() + self.simple_event.invalidate_recordset() + + # Ensure that synchronization is paused, delete wasn't called and record doesn't exist anymore. + self.assertFalse(self.organizer_user.microsoft_synchronization_stopped) + self.assertEqual(self.organizer_user._get_microsoft_sync_status(), "sync_paused") + self.assertFalse(self.simple_event.exists(), "Event must be deleted from Odoo even though sync configuration is off") + mock_delete.assert_not_called() diff --git a/addons/microsoft_calendar/tests/test_update_events.py b/addons/microsoft_calendar/tests/test_update_events.py index bd025dc0d8d..59791a0c6ec 100644 --- a/addons/microsoft_calendar/tests/test_update_events.py +++ b/addons/microsoft_calendar/tests/test_update_events.py @@ -1314,3 +1314,24 @@ class TestUpdateEvents(TestCommon): # Assert that no patch call was made due to the recurrence update forbiddance. mock_patch.assert_not_called() + + @patch.object(MicrosoftCalendarService, 'patch') + def test_update_synced_event_with_sync_config_paused(self, mock_patch): + """ + Updates an event with the synchronization paused, the event must have its field 'need_sync_m' as True + for later synchronizing it with Outlook Calendar. + """ + # Set user synchronization configuration as active and pause it. + self.organizer_user.microsoft_synchronization_stopped = False + self.organizer_user.pause_microsoft_synchronization() + + # Try to update a simple event in Odoo Calendar. + self.simple_event.with_user(self.organizer_user).write({"name": "updated simple event"}) + self.call_post_commit_hooks() + self.simple_event.invalidate_recordset() + + # Ensure that synchronization is paused, delete wasn't called and record is waiting to be synced again. + self.assertFalse(self.organizer_user.microsoft_synchronization_stopped) + self.assertEqual(self.organizer_user._get_microsoft_sync_status(), "sync_paused") + self.assertTrue(self.simple_event.need_sync_m, "Sync variable must be true for updating event when sync re-activates") + mock_patch.assert_not_called() diff --git a/addons/microsoft_calendar/views/res_config_settings_views.xml b/addons/microsoft_calendar/views/res_config_settings_views.xml index 14f2504ef04..2b88df4e1a9 100644 --- a/addons/microsoft_calendar/views/res_config_settings_views.xml +++ b/addons/microsoft_calendar/views/res_config_settings_views.xml @@ -15,6 +15,10 @@
+
+