From 81de71dc1fe2932d0dc76338b76719f72d9288f6 Mon Sep 17 00:00:00 2001 From: Katie Smith Date: Tue, 16 Jul 2019 15:42:51 +0100 Subject: [PATCH] Add dao function that gets the required data --- app/dao/fact_notification_status_dao.py | 74 ++++++++++++++++- .../dao/test_fact_notification_status_dao.py | 80 ++++++++++++++++++- 2 files changed, 152 insertions(+), 2 deletions(-) diff --git a/app/dao/fact_notification_status_dao.py b/app/dao/fact_notification_status_dao.py index 2836e62df..9adbce024 100644 --- a/app/dao/fact_notification_status_dao.py +++ b/app/dao/fact_notification_status_dao.py @@ -2,7 +2,7 @@ from datetime import datetime, timedelta, time from flask import current_app from notifications_utils.timezones import convert_bst_to_utc -from sqlalchemy import func +from sqlalchemy import case, func, Date from sqlalchemy.dialects.postgresql import insert from sqlalchemy.sql.expression import literal, extract from sqlalchemy.types import DateTime, Integer @@ -16,6 +16,14 @@ from app.models import ( Notification, NotificationHistory, NOTIFICATION_CANCELLED, + NOTIFICATION_CREATED, + NOTIFICATION_DELIVERED, + NOTIFICATION_FAILED, + NOTIFICATION_SENDING, + NOTIFICATION_SENT, + NOTIFICATION_TECHNICAL_FAILURE, + NOTIFICATION_TEMPORARY_FAILURE, + NOTIFICATION_PERMANENT_FAILURE, Service, SMS_TYPE, Template, @@ -440,3 +448,67 @@ def get_total_sent_notifications_for_day_and_type(day, notification_type): ).scalar() return result or 0 + + +def fetch_monthly_notification_statuses_per_service(start_date, end_date): + return db.session.query( + func.date_trunc('month', FactNotificationStatus.bst_date).cast(Date).label('date_created'), + Service.id.label('service_id'), + Service.name.label('service_name'), + FactNotificationStatus.notification_type, + func.sum(case( + [ + (FactNotificationStatus.notification_status == NOTIFICATION_SENDING, + FactNotificationStatus.notification_count) + ], + else_=0)).label('count_sending'), + func.sum(case( + [ + (FactNotificationStatus.notification_status == NOTIFICATION_DELIVERED, + FactNotificationStatus.notification_count) + ], + else_=0)).label('count_delivered'), + func.sum(case( + [ + (FactNotificationStatus.notification_status.in_([NOTIFICATION_TECHNICAL_FAILURE, NOTIFICATION_FAILED]), + FactNotificationStatus.notification_count) + ], + else_=0)).label('count_technical_failure'), + func.sum(case( + [ + (FactNotificationStatus.notification_status == NOTIFICATION_TEMPORARY_FAILURE, + FactNotificationStatus.notification_count) + ], + else_=0)).label('count_temporary_failure'), + func.sum(case( + [ + (FactNotificationStatus.notification_status == NOTIFICATION_PERMANENT_FAILURE, + FactNotificationStatus.notification_count) + ], + else_=0)).label('count_permanent_failure'), + func.sum(case( + [ + (FactNotificationStatus.notification_status == NOTIFICATION_SENT, + FactNotificationStatus.notification_count) + ], + else_=0)).label('count_sent'), + ).join( + Service, FactNotificationStatus.service_id == Service.id + ).filter( + FactNotificationStatus.notification_status != NOTIFICATION_CREATED, + Service.active.is_(True), + FactNotificationStatus.key_type != KEY_TYPE_TEST, + Service.research_mode.is_(False), + Service.restricted.is_(False), + FactNotificationStatus.bst_date >= start_date, + FactNotificationStatus.bst_date <= end_date, + ).group_by( + Service.id, + Service.name, + func.date_trunc('month', FactNotificationStatus.bst_date).cast(Date), + FactNotificationStatus.notification_type, + ).order_by( + func.date_trunc('month', FactNotificationStatus.bst_date).cast(Date), + Service.id, + FactNotificationStatus.notification_type, + ).all() diff --git a/tests/app/dao/test_fact_notification_status_dao.py b/tests/app/dao/test_fact_notification_status_dao.py index bc9ffa698..8e2ce9afd 100644 --- a/tests/app/dao/test_fact_notification_status_dao.py +++ b/tests/app/dao/test_fact_notification_status_dao.py @@ -6,6 +6,7 @@ import mock from app.dao.fact_notification_status_dao import ( update_fact_notification_status, + fetch_monthly_notification_statuses_per_service, fetch_notification_status_for_day, fetch_notification_status_for_service_by_month, fetch_notification_status_for_service_for_day, @@ -15,7 +16,22 @@ from app.dao.fact_notification_status_dao import ( fetch_stats_for_all_services_by_date_range, fetch_monthly_template_usage_for_service, get_total_sent_notifications_for_day_and_type ) -from app.models import FactNotificationStatus, KEY_TYPE_TEST, KEY_TYPE_TEAM, EMAIL_TYPE, SMS_TYPE, LETTER_TYPE +from app.models import ( + FactNotificationStatus, + KEY_TYPE_TEST, + KEY_TYPE_TEAM, + EMAIL_TYPE, + SMS_TYPE, + LETTER_TYPE, + NOTIFICATION_CREATED, + NOTIFICATION_DELIVERED, + NOTIFICATION_FAILED, + NOTIFICATION_PERMANENT_FAILURE, + NOTIFICATION_SENDING, + NOTIFICATION_SENT, + NOTIFICATION_TECHNICAL_FAILURE, + NOTIFICATION_TEMPORARY_FAILURE, +) from freezegun import freeze_time from tests.app.db import ( @@ -612,3 +628,65 @@ def test_get_total_sent_notifications_for_day_and_type_returns_zero_when_no_coun total = get_total_sent_notifications_for_day_and_type("2019-03-27", "sms") assert total == 0 + + +@freeze_time('2019-05-10 14:00') +def test_fetch_monthly_notification_statuses_per_service(notify_db_session): + service_one = create_service(service_name='service one', service_id=UUID('e4e34c4e-73c1-4802-811c-3dd273f21da4')) + service_two = create_service(service_name='service two', service_id=UUID('b19d7aad-6f09-4198-8b62-f6cf126b87e5')) + + create_ft_notification_status(date(2019, 4, 30), notification_type='letter', service=service_one, + notification_status=NOTIFICATION_DELIVERED) + create_ft_notification_status(date(2019, 3, 1), notification_type='email', service=service_one, + notification_status=NOTIFICATION_SENDING, count=4) + create_ft_notification_status(date(2019, 3, 2), notification_type='email', service=service_one, + notification_status=NOTIFICATION_TECHNICAL_FAILURE, count=2) + create_ft_notification_status(date(2019, 3, 7), notification_type='email', service=service_one, + notification_status=NOTIFICATION_FAILED, count=1) + create_ft_notification_status(date(2019, 3, 10), notification_type='letter', service=service_two, + notification_status=NOTIFICATION_PERMANENT_FAILURE, count=1) + create_ft_notification_status(date(2019, 3, 10), notification_type='letter', service=service_two, + notification_status=NOTIFICATION_PERMANENT_FAILURE, count=1) + create_ft_notification_status(date(2019, 3, 13), notification_type='sms', service=service_one, + notification_status=NOTIFICATION_SENT, count=1) + create_ft_notification_status(date(2019, 4, 1), notification_type='letter', service=service_two, + notification_status=NOTIFICATION_TEMPORARY_FAILURE, count=10) + create_ft_notification_status(date(2019, 3, 31), notification_type='letter', service=service_one, + notification_status=NOTIFICATION_DELIVERED) + + results = fetch_monthly_notification_statuses_per_service(date(2019, 3, 1), date(2019, 4, 30)) + + assert len(results) == 6 + # column order: date, service_id, service_name, notifaction_type, count_sending, count_delivered, + # count_technical_failure, count_temporary_failure, count_permanent_failure, count_sent + assert [x for x in results[0]] == [date(2019, 3, 1), service_two.id, 'service two', 'letter', 0, 0, 0, 0, 2, 0] + assert [x for x in results[1]] == [date(2019, 3, 1), service_one.id, 'service one', 'email', 4, 0, 3, 0, 0, 0] + assert [x for x in results[2]] == [date(2019, 3, 1), service_one.id, 'service one', 'letter', 0, 1, 0, 0, 0, 0] + assert [x for x in results[3]] == [date(2019, 3, 1), service_one.id, 'service one', 'sms', 0, 0, 0, 0, 0, 1] + assert [x for x in results[4]] == [date(2019, 4, 1), service_two.id, 'service two', 'letter', 0, 0, 0, 10, 0, 0] + assert [x for x in results[5]] == [date(2019, 4, 1), service_one.id, 'service one', 'letter', 0, 1, 0, 0, 0, 0] + + +@freeze_time('2019-04-10 14:00') +def test_fetch_monthly_notification_statuses_per_service_for_rows_that_should_be_excluded(notify_db_session): + valid_service = create_service(service_name='valid service') + inactive_service = create_service(service_name='inactive', active=False) + research_mode_service = create_service(service_name='research_mode', research_mode=True) + restricted_service = create_service(service_name='restricted', restricted=True) + + # notification in 'created' state + create_ft_notification_status(date(2019, 3, 15), service=valid_service, notification_status=NOTIFICATION_CREATED) + # notification created by inactive service + create_ft_notification_status(date(2019, 3, 15), service=inactive_service) + # notification created with test key + create_ft_notification_status(date(2019, 3, 12), service=valid_service, key_type=KEY_TYPE_TEST) + # notification created by research mode service + create_ft_notification_status(date(2019, 3, 2), service=research_mode_service) + # notification created by trial mode service + create_ft_notification_status(date(2019, 3, 19), service=restricted_service) + # notifications outside date range + create_ft_notification_status(date(2019, 2, 28), service=valid_service) + create_ft_notification_status(date(2019, 4, 1), service=valid_service) + + results = fetch_monthly_notification_statuses_per_service(date(2019, 3, 1), date(2019, 3, 31)) + assert len(results) == 0