diff --git a/app/dao/fact_notification_status_dao.py b/app/dao/fact_notification_status_dao.py index 1dcdce594..df8e653ee 100644 --- a/app/dao/fact_notification_status_dao.py +++ b/app/dao/fact_notification_status_dao.py @@ -1,6 +1,6 @@ -from datetime import date, timedelta +from datetime import timedelta -from sqlalchemy import Date, case, func, select, union_all, cast +from sqlalchemy import Date, case, cast, func, select, union_all from sqlalchemy.dialects.postgresql import insert from sqlalchemy.orm import aliased from sqlalchemy.sql.expression import extract, literal @@ -130,12 +130,10 @@ def fetch_notification_status_for_service_for_day(fetch_day, service_id): def fetch_notification_status_for_service_for_today_and_7_previous_days( - service_id: str, - by_template: bool = False, - limit_days: int = 7 + service_id: str, by_template: bool = False, limit_days: int = 7 ) -> list[dict | None]: start_date = midnight_n_days_ago(limit_days) - now = utc_now() + now = get_midnight_in_utc(utc_now()) # Query for the last 7 days stats_for_7_days = select( @@ -145,7 +143,9 @@ def fetch_notification_status_for_service_for_today_and_7_previous_days( [ FactNotificationStatus.template_id.label("template_id"), FactNotificationStatus.local_date.label("date_used"), - ] if by_template else [] + ] + if by_template + else [] ), FactNotificationStatus.notification_count.label("count"), ).where( @@ -155,41 +155,53 @@ def fetch_notification_status_for_service_for_today_and_7_previous_days( ) # Query for today's stats - stats_for_today = select( - cast(Notification.notification_type, Text), - cast(Notification.status, Text), - *( - [ - Notification.template_id, - literal(date.today()).label("date_used"), - ] if by_template else [] - ), - func.count().label("count"), - ).where( - Notification.created_at >= get_midnight_in_utc(now), - Notification.service_id == service_id, - Notification.key_type != KeyType.TEST, - ).group_by( - Notification.notification_type, - *([Notification.template_id] if by_template else []), - Notification.status, + stats_for_today = ( + select( + cast(Notification.notification_type, Text), + cast(Notification.status, Text), + *( + [ + Notification.template_id, + literal(now).label("date_used"), + ] + if by_template + else [] + ), + func.count().label("count"), + ) + .where( + Notification.created_at >= now, + Notification.service_id == service_id, + Notification.key_type != KeyType.TEST, + ) + .group_by( + Notification.notification_type, + *([Notification.template_id] if by_template else []), + Notification.status, + ) ) # Combine the queries using union_all all_stats_union = union_all(stats_for_7_days, stats_for_today).subquery() - all_stats_alias = aliased(all_stats_union) + all_stats_alias = aliased(all_stats_union, name="all_stats") # Final query with optional template joins query = select( *( [ + TemplateFolder.name.label("folder"), Template.name.label("template_name"), False, # TODO: Handle `is_precompiled_letter` + template_folder_map.c.template_folder_id, all_stats_alias.c.template_id, - TemplateFolder.name.label("folder"), User.name.label("created_by"), - func.max(all_stats_alias.c.date_used).label("last_used"), # Get the most recent date - ] if by_template else [] + Template.created_by_id, + func.max(all_stats_alias.c.date_used).label( + "last_used" + ), # Get the most recent date + ] + if by_template + else [] ), all_stats_alias.c.notification_type, all_stats_alias.c.status, @@ -197,38 +209,36 @@ def fetch_notification_status_for_service_for_today_and_7_previous_days( ) if by_template: - query = query.join( - Template, - all_stats_alias.c.template_id == Template.id - ).join( - User, - Template.created_by_id == User.id - ).join( - template_folder_map, - Template.id == template_folder_map.c.template_id - ).join( - TemplateFolder, - TemplateFolder.id == template_folder_map.c.template_id + query = ( + query.join(Template, all_stats_alias.c.template_id == Template.id) + .join(User, Template.created_by_id == User.id) + .outerjoin( + template_folder_map, Template.id == template_folder_map.c.template_id + ) + .outerjoin( + TemplateFolder, + TemplateFolder.id == template_folder_map.c.template_folder_id, + ) ) # Group by all necessary fields except date_used query = query.group_by( *( [ + TemplateFolder.name, Template.name, all_stats_alias.c.template_id, - TemplateFolder.name, User.name, - ] if by_template else [] + template_folder_map.c.template_folder_id, + Template.created_by_id, + ] + if by_template + else [] ), all_stats_alias.c.notification_type, all_stats_alias.c.status, ) - print("*"*79) - print(query) - print("*"*79) - # Execute the query using Flask-SQLAlchemy's session result = db.session.execute(query) return result.mappings().all() diff --git a/tests/app/dao/test_fact_notification_status_dao.py b/tests/app/dao/test_fact_notification_status_dao.py index dc46de45d..ccb0c5a06 100644 --- a/tests/app/dao/test_fact_notification_status_dao.py +++ b/tests/app/dao/test_fact_notification_status_dao.py @@ -1,5 +1,4 @@ from datetime import date, datetime, timedelta -from unittest import mock from uuid import UUID import pytest @@ -26,6 +25,7 @@ from tests.app.db import ( create_notification, create_service, create_template, + create_template_folder, ) @@ -252,15 +252,18 @@ def test_fetch_notification_status_by_template_for_service_for_today_and_7_previ notify_db_session, ): service_1 = create_service(service_name="service_1") + test_folder = create_template_folder(service=service_1, name="Test_Folder_For_This") sms_template = create_template( template_name="sms Template 1", service=service_1, template_type=TemplateType.SMS, + folder=test_folder, ) sms_template_2 = create_template( template_name="sms Template 2", service=service_1, template_type=TemplateType.SMS, + folder=test_folder, ) email_template = create_template( service=service_1, template_type=TemplateType.EMAIL @@ -329,82 +332,152 @@ def test_fetch_notification_status_by_template_for_service_for_today_and_7_previ by_template=True, ) - assert [ - ( - "email Template Name", - False, - mock.ANY, - NotificationType.EMAIL, - NotificationStatus.DELIVERED, - 1, - ), - ( - "email Template Name", - False, - mock.ANY, - NotificationType.EMAIL, - NotificationStatus.DELIVERED, - 3, - ), - ( - "sms Template 1", - False, - mock.ANY, - NotificationType.SMS, - NotificationStatus.CREATED, - 1, - ), - ( - "sms Template Name", - False, - mock.ANY, - NotificationType.SMS, - NotificationStatus.CREATED, - 1, - ), - ( - "sms Template 1", - False, - mock.ANY, - NotificationType.SMS, - NotificationStatus.DELIVERED, - 1, - ), - ( - "sms Template 2", - False, - mock.ANY, - NotificationType.SMS, - NotificationStatus.DELIVERED, - 1, - ), - ( - "sms Template Name", - False, - mock.ANY, - NotificationType.SMS, - NotificationStatus.DELIVERED, - 8, - ), - ( - "sms Template Name", - False, - mock.ANY, - NotificationType.SMS, - NotificationStatus.DELIVERED, - 10, - ), - ( - "sms Template Name", - False, - mock.ANY, - NotificationType.SMS, - NotificationStatus.DELIVERED, - 11, - ), - ] == sorted( - results, key=lambda x: (x.notification_type, x.status, x.template_name, x.count) - ) + expected = [ + { + "folder": None, + "template_name": "email Template Name", + "_no_label": False, + "created_by": "Test User", + "last_used": datetime(2018, 10, 31, 0, 0), + "notification_type": NotificationType.EMAIL, + "status": NotificationStatus.DELIVERED, + "count": 1, + }, + { + "folder": None, + "template_name": "email Template Name", + "_no_label": False, + "created_by": "Test User", + "last_used": datetime(2018, 10, 29, 0, 0), + "notification_type": NotificationType.EMAIL, + "status": NotificationStatus.DELIVERED, + "count": 3, + }, + { + "folder": None, + "template_name": "sms Template Name", + "_no_label": False, + "created_by": "Test User", + "last_used": datetime(2018, 10, 29, 0, 0), + "notification_type": NotificationType.SMS, + "status": NotificationStatus.CREATED, + "count": 1, + }, + { + "folder": "Test_Folder_For_This", + "template_name": "sms Template 1", + "_no_label": False, + "created_by": "Test User", + "last_used": datetime(2018, 10, 31, 0, 0), + "notification_type": NotificationType.SMS, + "status": NotificationStatus.CREATED, + "count": 1, + }, + { + "folder": None, + "template_name": "sms Template Name", + "_no_label": False, + "created_by": "Test User", + "last_used": datetime(2018, 10, 29, 0, 0), + "notification_type": NotificationType.SMS, + "status": NotificationStatus.DELIVERED, + "count": 10, + }, + { + "folder": "Test_Folder_For_This", + "template_name": "sms Template 2", + "_no_label": False, + "created_by": "Test User", + "last_used": datetime(2018, 10, 31, 0, 0), + "notification_type": NotificationType.SMS, + "status": NotificationStatus.DELIVERED, + "count": 1, + }, + { + "folder": None, + "template_name": "sms Template Name", + "_no_label": False, + "created_by": "Test User", + "last_used": datetime(2018, 10, 25, 0, 0), + "notification_type": NotificationType.SMS, + "status": NotificationStatus.DELIVERED, + "count": 8, + }, + { + "folder": "Test_Folder_For_This", + "template_name": "sms Template 1", + "_no_label": False, + "created_by": "Test User", + "last_used": datetime(2018, 10, 31, 0, 0), + "notification_type": NotificationType.SMS, + "status": NotificationStatus.DELIVERED, + "count": 1, + }, + { + "folder": None, + "template_name": "sms Template Name", + "_no_label": False, + "created_by": "Test User", + "last_used": datetime(2018, 10, 29, 0, 0), + "notification_type": NotificationType.SMS, + "status": NotificationStatus.DELIVERED, + "count": 11, + }, + ] + + expected = [ + [ + str(row[k]) if k != "last_used" else row[k].strftime("%Y-%m-%d") + for k in ( + "folder", + "template_name", + "created_by", + "last_used", + "notification_type", + "status", + "count", + ) + ] + for row in sorted( + expected, + key=lambda x: ( + str(x["notification_type"]), + str(x["status"]), + x["folder"] if x["folder"] is not None else "", + x["template_name"], + x["count"], + x["last_used"], + ), + ) + ] + + results = [ + [ + str(row[k]) if k != "last_used" else row[k].strftime("%Y-%m-%d") + for k in ( + "folder", + "template_name", + "created_by", + "last_used", + "notification_type", + "status", + "count", + ) + ] + for row in sorted( + results, + key=lambda x: ( + x.notification_type, + x.status, + x.folder if x.folder is not None else "", + x.template_name, + x.count, + x.last_used, + ), + ) + ] + + assert expected == results @pytest.mark.parametrize(