[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) <arj@odoo.com>
This commit is contained in:
Gabriel de Paula Felix (gdpf)
2023-08-21 12:38:44 +02:00
parent 9ae8f7dda3
commit a395277fed
13 changed files with 136 additions and 26 deletions
@@ -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 {
+6 -5
View File
@@ -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)
@@ -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):
@@ -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.")
+17 -1
View File
@@ -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)
@@ -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."),
});
}
}
});
@@ -6,7 +6,13 @@
</xpath>
<xpath expr="//div[@id='microsoft_calendar_sync']" position="replace">
<div id="microsoft_calendar_sync" class="o_calendar_sync">
<button t-if="!model.microsoftIsSync" type="button" id="microsoft_sync_pending" class="o_microsoft_sync_button o_microsoft_sync_pending btn btn-secondary text-nowrap" t-on-click="onMicrosoftSyncCalendar">
<button t-if="!model.microsoftIsSync and model.microsoftIsPaused" type="button" id="microsoft_sync_paused" class="o_google_sync_button o_microsoft_sync_paused btn btn-warning" t-on-click="onMicrosoftSyncUnpause">
<b>
<i id="microsoft_paused" class='fa fa-pause'/>
<span class="mx-1">Outlook</span>
</b>
</button>
<button t-elif="!model.microsoftIsSync" type="button" id="microsoft_sync_pending" class="o_microsoft_sync_button o_microsoft_sync_pending btn btn-secondary text-nowrap" t-on-click="onMicrosoftSyncCalendar">
<b><i class='fa fa-refresh'/> Outlook</b>
</button>
<!-- class change on hover -->
@@ -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;
},
@@ -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")])
@@ -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()
@@ -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()
@@ -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()
@@ -15,6 +15,10 @@
<label for="cal_microsoft_client_secret" string="Client Secret" class="col-3 col-lg-3 o_light_label"/>
<field name="cal_microsoft_client_secret" password="True" nolabel="1"/>
</div>
<div class="mt16 d-flex">
<label for="cal_microsoft_sync_paused" string="Pause Synchronization" class="o_light_label"/>
<field name="cal_microsoft_sync_paused" class="ml16"/>
</div>
</div>
</div>
</field>