diff --git a/app/dao/services_dao.py b/app/dao/services_dao.py index 8946d6735..045daec0d 100644 --- a/app/dao/services_dao.py +++ b/app/dao/services_dao.py @@ -29,6 +29,7 @@ from app.models import ( NOTIFICATION_STATUS_TYPES, TEMPLATE_TYPES, ) +from app.service.statistics import format_monthly_template_notification_stats from app.statsd_decorators import statsd from app.utils import get_london_month_from_utc_column @@ -224,6 +225,38 @@ def _stats_for_service_query(service_id): ) +@statsd(namespace="dao") +def dao_fetch_monthly_historical_stats_by_template_for_service(service_id, year): + month = get_london_month_from_utc_column(NotificationHistory.created_at) + + sq = db.session.query( + NotificationHistory.template_id, + NotificationHistory.status, + month.label('month'), + func.count().label('count') + ).filter( + NotificationHistory.service_id == service_id, + NotificationHistory.created_at.between(*get_financial_year(year)) + ).group_by( + month, + NotificationHistory.template_id, + NotificationHistory.status + ).subquery() + + rows = db.session.query( + Template.id.label('template_id'), + Template.name, + sq.c.status, + sq.c.count.label('count'), + sq.c.month + ).join( + sq, + sq.c.template_id == Template.id + ).all() + + return format_monthly_template_notification_stats(year, rows) + + @statsd(namespace="dao") def dao_fetch_monthly_historical_stats_for_service(service_id, year): month = get_london_month_from_utc_column(NotificationHistory.created_at) diff --git a/app/service/statistics.py b/app/service/statistics.py index e2a236990..40b3268f1 100644 --- a/app/service/statistics.py +++ b/app/service/statistics.py @@ -1,7 +1,7 @@ import itertools from datetime import datetime, timedelta -from app.models import TEMPLATE_TYPES +from app.models import NOTIFICATION_STATUS_TYPES, TEMPLATE_TYPES def format_statistics(statistics): @@ -15,6 +15,28 @@ def format_statistics(statistics): return counts +def format_monthly_template_notification_stats(year, rows): + dict = { + datetime.strftime(date, '%Y-%m'): {} + for date in [ + datetime(year, month, 1) for month in range(4, 13) + ] + [ + datetime(year + 1, month, 1) for month in range(1, 4) + ] + } + + for row in rows: + formatted_month = row.month.strftime('%Y-%m') + if str(row.template_id) not in dict[formatted_month]: + dict[formatted_month][str(row.template_id)] = { + "name": row.name, + "counts": dict.fromkeys(NOTIFICATION_STATUS_TYPES, 0) + } + dict[formatted_month][str(row.template_id)]["counts"][row.status] += row.count + + return dict + + def create_zeroed_stats_dicts(): return { template_type: { diff --git a/tests/app/celery/test_scheduled_tasks.py b/tests/app/celery/test_scheduled_tasks.py index 06ab343fa..da21bb616 100644 --- a/tests/app/celery/test_scheduled_tasks.py +++ b/tests/app/celery/test_scheduled_tasks.py @@ -29,7 +29,8 @@ from app.utils import get_london_midnight_in_utc from tests.app.db import create_notification, create_service, create_template from tests.app.conftest import ( sample_job as create_sample_job, - sample_notification_history as create_notification_history + sample_notification_history as create_notification_history, + create_notify_template ) from tests.conftest import set_config_values from unittest.mock import call, patch, PropertyMock @@ -43,13 +44,19 @@ def _create_slow_delivery_notification(provider='mmg'): service = create_service( service_id=current_app.config.get('FUNCTIONAL_TEST_PROVIDER_SERVICE_ID') ) - template = Template.query.get(current_app.config['FUNCTIONAL_TEST_PROVIDER_SMS_TEMPLATE_ID']) if not template: - template = create_template( - template_id=current_app.config.get('FUNCTIONAL_TEST_PROVIDER_SMS_TEMPLATE_ID'), - service=service + template = create_notify_template( + service=service, + user=service.users[0], + template_config_name='FUNCTIONAL_TEST_PROVIDER_SMS_TEMPLATE_ID', + content='', + template_type='sms' ) + # template = create_template( + # template_id=current_app.config.get('FUNCTIONAL_TEST_PROVIDER_SMS_TEMPLATE_ID'), + # service=service + # ) create_notification( template=template, diff --git a/tests/app/conftest.py b/tests/app/conftest.py index 23e7bcede..3dbad5272 100644 --- a/tests/app/conftest.py +++ b/tests/app/conftest.py @@ -167,6 +167,7 @@ def sample_template( service = sample_service(notify_db, notify_db_session) if created_by is None: created_by = create_user() + data = { 'name': template_name, 'template_type': template_type, @@ -870,6 +871,7 @@ def create_notify_template(service, user, template_config_name, content, templat } template = Template(**data) db.session.add(template) + db.session.commit() return template diff --git a/tests/app/dao/test_services_dao.py b/tests/app/dao/test_services_dao.py index 06a06dec9..4cfd0b040 100644 --- a/tests/app/dao/test_services_dao.py +++ b/tests/app/dao/test_services_dao.py @@ -6,7 +6,6 @@ import pytest from sqlalchemy.orm.exc import FlushError, NoResultFound from sqlalchemy.exc import IntegrityError from freezegun import freeze_time - from app import db from app.dao.services_dao import ( dao_create_service, @@ -20,11 +19,13 @@ from app.dao.services_dao import ( dao_fetch_stats_for_service, dao_fetch_todays_stats_for_service, dao_fetch_monthly_historical_stats_for_service, + dao_fetch_monthly_historical_stats_by_template_for_service, fetch_todays_total_message_count, dao_fetch_todays_stats_for_all_services, fetch_stats_by_date_range_for_all_services, dao_suspend_service, - dao_resume_service) + dao_resume_service +) from app.dao.users_dao import save_model_user from app.models import ( NotificationStatistics, @@ -50,7 +51,8 @@ from app.models import ( from tests.app.conftest import ( sample_notification as create_notification, sample_notification_history as create_notification_history, - sample_email_template as create_email_template + sample_email_template as create_email_template, + sample_template as create_sample_template ) @@ -655,3 +657,90 @@ def test_dao_resume_service_marks_service_as_active_and_api_keys_are_still_revok api_key = ApiKey.query.get(sample_api_key.id) assert api_key.expiry_date == datetime(2001, 1, 1, 23, 59, 00) + + +def test_fetch_monthly_historical_template_stats_for_service_includes_financial_year_only( + notify_db, + notify_db_session, + sample_template +): + notification_history = functools.partial( + create_notification_history, + notify_db, + notify_db_session, + sample_template + ) + + notification_history(created_at=datetime(2016, 4, 1), status='sending') # Start of financial year + notification_history(created_at=datetime(2016, 5, 30), status='created') + notification_history(created_at=datetime(2016, 6, 1), status='created') + notification_history(created_at=datetime(2017, 3, 31), status='created') # End of financial year + notification_history(created_at=datetime(2017, 4, 1), status='created') + notification_history(created_at=datetime(2017, 5, 1), status='created') + + result = dao_fetch_monthly_historical_stats_by_template_for_service(sample_template.service_id, 2016) + + notifications_count = 0 + for dict in result.values(): + notifications_count += sum(dict.get(str(sample_template.id), {}).get("counts", {}).values()) + + assert '2017-04' not in result + assert '2017-05' not in result + assert notifications_count == 4 + + +def test_fetch_monthly_historical_template_stats_for_service_separates_months( + notify_db, + notify_db_session, + sample_template +): + notification_history = functools.partial( + create_notification_history, + notify_db, + notify_db_session, + sample_template + ) + + notification_history(created_at=datetime(2016, 4, 1), status='sending') # Start of financial year + notification_history(created_at=datetime(2016, 5, 30), status='created') + notification_history(created_at=datetime(2016, 6, 1), status='delivered') + notification_history(created_at=datetime(2016, 6, 1), status='created') + notification_history(created_at=datetime(2016, 12, 1), status='created') + notification_history(created_at=datetime(2017, 3, 30), status='sending') + notification_history(created_at=datetime(2017, 3, 31), status='sending') + + result = dao_fetch_monthly_historical_stats_by_template_for_service(sample_template.service_id, 2016) + + financial_year_month_keys = \ + ['2016-{:02}'.format(month) for month in range(4, 13)] + ['2017-{:02}'.format(month) for month in range(1, 4)] + + assert set(financial_year_month_keys) == set(result.keys()) + assert sum(result["2016-04"][str(sample_template.id)]["counts"].values()) == 1 + assert sum(result["2016-05"][str(sample_template.id)]["counts"].values()) == 1 + assert sum(result["2016-06"][str(sample_template.id)]["counts"].values()) == 2 + assert sum(result["2016-12"][str(sample_template.id)]["counts"].values()) == 1 + assert sum(result["2017-03"][str(sample_template.id)]["counts"].values()) == 2 + + +def test_fetch_monthly_historical_template_stats_for_service_separates_templates( + notify_db, + notify_db_session +): + notification_history = functools.partial( + create_notification_history, + notify_db, + notify_db_session, + status='delivered' + ) + + template_one = create_sample_template(notify_db, notify_db_session) + template_two = create_sample_template(notify_db, notify_db_session) + + notification_history(created_at=datetime(2016, 4, 1), sample_template=template_one) + notification_history(created_at=datetime(2016, 4, 1), sample_template=template_two) + + result = dao_fetch_monthly_historical_stats_by_template_for_service(template_one.service_id, 2016) + + assert len(result.get('2016-04').keys()) == 2 + assert str(template_one.id) in result.get('2016-04').keys() + assert str(template_two.id) in result.get('2016-04').keys()