This repository has been archived by the owner on May 12, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
YONK-949 - Reset notification digest timers management command added (#…
…187) * Django management command added to reset notification digest timers * setup.py version update
- Loading branch information
1 parent
b7cc7c7
commit 5640a6f
Showing
3 changed files
with
192 additions
and
1 deletion.
There are no files selected for viewing
118 changes: 118 additions & 0 deletions
118
edx_notifications/management/commands/reset_notification_timer.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
""" | ||
Django management command to fetch records from SQLNotificationCallbackTimer with name | ||
'daily-digest-timer' and 'weekly-digest-timer' and reset their 'callback_at' and 'last_ran' values | ||
to avoid sending old notification digest emails to users. | ||
""" | ||
|
||
import logging.config | ||
import sys | ||
import pytz | ||
|
||
from datetime import datetime, timedelta | ||
from django.core.management.base import BaseCommand | ||
|
||
from edx_notifications.exceptions import ItemNotFoundError | ||
from edx_notifications.stores.store import notification_store | ||
from edx_notifications.timer import PURGE_NOTIFICATIONS_TIMER_NAME | ||
from edx_notifications import const | ||
|
||
# Have all logging go to stdout with management commands | ||
# this must be up at the top otherwise the | ||
# configuration does not appear to take affect | ||
LOGGING = { | ||
'version': 1, | ||
'handlers': { | ||
'console': { | ||
'class': 'logging.StreamHandler', | ||
'stream': sys.stdout, | ||
} | ||
}, | ||
'root': { | ||
'handlers': ['console'], | ||
'level': 'INFO' | ||
} | ||
} | ||
logging.config.dictConfig(LOGGING) | ||
|
||
log = logging.getLogger(__file__) | ||
|
||
|
||
class Command(BaseCommand): | ||
""" | ||
Django management command to fetch records from SQLNotificationCallbackTimer with name | ||
'daily-digest-timer' and 'weekly-digest-timer' and reset their 'callback_at' and 'last_ran' | ||
values to avoid sending old notification digest emails to users | ||
""" | ||
|
||
help = 'Command to reset notification digest emails timer' | ||
INCLUDED_TIMERS = [ | ||
const.DAILY_DIGEST_TIMER_NAME, | ||
const.WEEKLY_DIGEST_TIMER_NAME, | ||
PURGE_NOTIFICATIONS_TIMER_NAME, | ||
] | ||
store = notification_store() | ||
|
||
def cancel_old_notification_timers(self): | ||
""" | ||
Get all the old timers, other than daily/weekly digests timers, that are not executed and | ||
cancel them as executing them will generate a lot of notifications. | ||
""" | ||
timers_not_executed = self.store.get_all_active_timers() | ||
|
||
for timer in timers_not_executed: | ||
if timer.name not in self.INCLUDED_TIMERS: | ||
log.info( | ||
'Cancelling timed Notification named {timer}...'.format(timer=str(timer.name))) | ||
|
||
timer.is_active = False | ||
self.store.save_notification_timer(timer) | ||
|
||
def reset_digest_notification_timer(self): | ||
context = { | ||
'last_ran': datetime.now(pytz.UTC), | ||
} | ||
|
||
try: | ||
digest_timer = self.store.get_notification_timer(const.DAILY_DIGEST_TIMER_NAME) | ||
if digest_timer: | ||
log.info("Resetting notification digest timer for daily digests.") | ||
|
||
rerun_delta = (digest_timer.periodicity_min | ||
if digest_timer.periodicity_min | ||
else const.MINUTES_IN_A_DAY) | ||
digest_timer.callback_at = datetime.now(pytz.UTC) + timedelta(minutes=rerun_delta) | ||
digest_timer.context.update(context) | ||
|
||
self.store.save_notification_timer(digest_timer) | ||
except ItemNotFoundError: | ||
log.info("Daily digests timer not found.") | ||
|
||
try: | ||
digest_timer = self.store.get_notification_timer(const.WEEKLY_DIGEST_TIMER_NAME) | ||
if digest_timer: | ||
log.info("Resetting notification digest timer for weekly digests.") | ||
|
||
rerun_delta = (digest_timer.periodicity_min | ||
if digest_timer.periodicity_min | ||
else const.MINUTES_IN_A_WEEK) | ||
digest_timer.callback_at = datetime.now(pytz.UTC) + timedelta(minutes=rerun_delta) | ||
digest_timer.context.update(context) | ||
|
||
self.store.save_notification_timer(digest_timer) | ||
except ItemNotFoundError: | ||
log.info("Weekly digests timer not found.") | ||
|
||
def handle(self, *args, **options): | ||
""" | ||
Management command entry point, simply calls: | ||
1. cancel_old_notification_timers to cancel all old notification timers other than | ||
digest timers | ||
2. store provider's get_notification_timer to update the callback_at and last_ran values. | ||
""" | ||
|
||
log.info("Running management command to reset notification digest timer.") | ||
|
||
self.cancel_old_notification_timers() | ||
self.reset_digest_notification_timer() | ||
|
||
log.info("Completed reset_notification_timer.") |
73 changes: 73 additions & 0 deletions
73
edx_notifications/management/commands/tests/test_reset_notification_timer.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
""" | ||
Tests for the Django management command reset_notification_timer | ||
""" | ||
|
||
import pytz | ||
from datetime import datetime, timedelta | ||
|
||
from django.test import TestCase | ||
|
||
from edx_notifications.management.commands import reset_notification_timer | ||
from edx_notifications.stores.store import notification_store | ||
from edx_notifications.data import NotificationCallbackTimer | ||
from edx_notifications import const | ||
|
||
|
||
class ResetTimerTest(TestCase): | ||
""" | ||
Test suite for the management command | ||
""" | ||
|
||
def setUp(self): | ||
""" | ||
Setup the tests values. | ||
""" | ||
|
||
self.store = notification_store() | ||
self.timer1 = self.register_timer('foo') | ||
self.timer2 = self.register_timer(const.DAILY_DIGEST_TIMER_NAME, const.MINUTES_IN_A_DAY) | ||
self.timer3 = self.register_timer(const.WEEKLY_DIGEST_TIMER_NAME, const.MINUTES_IN_A_WEEK) | ||
|
||
def register_timer(self, timer_name, periodicity_min=60): | ||
timer = NotificationCallbackTimer( | ||
name=timer_name, | ||
class_name='edx_notifications.tests.test_timer.NullNotificationCallbackTimerHandler', | ||
callback_at=datetime.now(pytz.UTC) - timedelta(days=1), | ||
context={ | ||
'foo': 'bar' | ||
}, | ||
is_active=True, | ||
periodicity_min=periodicity_min, | ||
) | ||
|
||
return self.store.save_notification_timer(timer) | ||
|
||
def test_timer_execution(self): | ||
""" | ||
Make sure that Django management command runs through the timers | ||
""" | ||
|
||
reset_notification_timer.Command().handle() | ||
|
||
readback_timer = self.store.get_notification_timer(self.timer1.name) | ||
|
||
self.assertIsNone(readback_timer.executed_at) | ||
self.assertFalse(readback_timer.is_active) | ||
self.assertIsNone(readback_timer.err_msg) | ||
|
||
daily_digest_timer = self.store.get_notification_timer(self.timer2.name) | ||
reset_time = datetime.now(pytz.UTC) + timedelta(minutes=daily_digest_timer.periodicity_min) | ||
|
||
self.assertIn('last_ran', daily_digest_timer.context) | ||
self.assertTrue(isinstance(daily_digest_timer.context['last_ran'], datetime)) | ||
self.assertTrue(daily_digest_timer.context['last_ran'] < reset_time) | ||
self.assertIsNone(daily_digest_timer.executed_at) | ||
self.assertTrue(daily_digest_timer.callback_at > datetime.now(pytz.UTC)) | ||
|
||
weekly_digest_timer = self.store.get_notification_timer(self.timer3.name) | ||
reset_time = datetime.now(pytz.UTC) + timedelta(days=6) | ||
|
||
self.assertIsNone(weekly_digest_timer.executed_at) | ||
self.assertTrue(weekly_digest_timer.is_active) | ||
self.assertIn('last_ran', weekly_digest_timer.context) | ||
self.assertTrue(weekly_digest_timer.callback_at > reset_time) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters