Skip to content
This repository has been archived by the owner on May 12, 2022. It is now read-only.

Commit

Permalink
YONK-949 - Reset notification digest timers management command added (#…
Browse files Browse the repository at this point in the history
…187)

* Django management command added to reset notification digest timers

* setup.py version update
  • Loading branch information
shafqatfarhan authored Jan 1, 2019
1 parent b7cc7c7 commit 5640a6f
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 1 deletion.
118 changes: 118 additions & 0 deletions edx_notifications/management/commands/reset_notification_timer.py
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.")
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)
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def load_requirements(*requirements_paths):

setup(
name='edx-notifications',
version='0.8.1',
version='0.8.2',
description='Notification subsystem for Open edX',
long_description=open('README.md').read(),
author='edX',
Expand Down

0 comments on commit 5640a6f

Please sign in to comment.