From ec1d9a49bf3b3a1fd0dcd1d16df28d0213274112 Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Mon, 8 Jan 2024 14:53:34 -0800 Subject: [PATCH] updated dashboard --- app/main/views/dashboard.py | 40 ++- app/templates/views/dashboard/dashboard.html | 12 +- tests/app/main/views/test_dashboard.py | 243 ++++++++++++++++++- tests/app/main/views/test_sign_out.py | 110 +++++++++ 4 files changed, 383 insertions(+), 22 deletions(-) diff --git a/app/main/views/dashboard.py b/app/main/views/dashboard.py index 4f8e6d5aa..8d190db3c 100644 --- a/app/main/views/dashboard.py +++ b/app/main/views/dashboard.py @@ -50,34 +50,52 @@ def service_dashboard(service_id): notifications = notification_api_client.get_notifications_for_service( service_id=service_id, - )['notifications'] + )["notifications"] - notificaton_job_ids = [notification['job']['id'] for notification in notifications if 'job' in notification] + notificaton_job_ids = [ + notification["job"]["id"] + for notification in notifications + if "job" in notification + ] jobs = [] for notificaton_job_id in notificaton_job_ids: job_data = job_api_client.get_job(service_id, notificaton_job_id)["data"] - jobs.append(job_data) + if job_data: + jobs.append(job_data) service_data_retention_days = None - download_availability = [] for job in jobs: - message_type = job.get('template_type') + message_type = job.get("template_type") if message_type is not None: - service_data_retention_days = current_service.get_days_of_retention(message_type) - time_left = get_time_left(job['created_at'], service_data_retention_days=service_data_retention_days) - download_availability.append({"job_id": job['id'], "time_left": time_left}) - + service_data_retention_days = current_service.get_days_of_retention( + message_type + ) + time_left = get_time_left(job["created_at"]) + download_link = ( + url_for( + ".view_job_csv", + service_id=current_service.id, + job_id=job["id"], + ), + ) + download_availability.append( + { + "job_id": job["id"], + "time_left": time_left, + "download_link": download_link, + } + ) return render_template( "views/dashboard/dashboard.html", updates_url=url_for(".service_dashboard_updates", service_id=service_id), partials=get_dashboard_partials(service_id), notifications=notifications, download_availability=download_availability, - service_data_retention_days=service_data_retention_days - ) + service_data_retention_days=service_data_retention_days, + ) @main.route("/services//dashboard.json") diff --git a/app/templates/views/dashboard/dashboard.html b/app/templates/views/dashboard/dashboard.html index eadef2119..5da5dc719 100644 --- a/app/templates/views/dashboard/dashboard.html +++ b/app/templates/views/dashboard/dashboard.html @@ -44,7 +44,7 @@ ) %} {% if item.job.original_file_name and item.job.id %} {% call row_heading() %} - {{ item.job.original_file_name|replace('.csv', '') if item.job.id else '' }} + {{ item.job.original_file_name|replace('.csv', '') if item.job.id else '' }} {% endcall %} {% call row_heading() %} {{ item.status|format_notification_status_as_time( @@ -53,11 +53,13 @@ }} {% endcall %} {% call row_heading() %} - {{ "Download" if item.job.original_file_name else '' }} {% set availability = download_availability|selectattr('job_id', 'equalto', item.job.id)|first %} - {% if availability %} + {% if availability and availability.time_left != "Data no longer available" %} + {{ "Download" if item.job.original_file_name else '' }} - {{ availability.time_left }} - {% endif %} + {% elif availability %} + {{ availability.time_left }} + {% endif %} {% endcall %} {% endif %} {% endcall %} @@ -77,7 +79,7 @@

Usage

Daily

Across all services

- +
diff --git a/tests/app/main/views/test_dashboard.py b/tests/app/main/views/test_dashboard.py index 63a9f99a5..fdb06a14d 100644 --- a/tests/app/main/views/test_dashboard.py +++ b/tests/app/main/views/test_dashboard.py @@ -28,6 +28,108 @@ from tests.conftest import ( normalize_spaces, ) +FAKE_ONE_OFF_NOTIFICATION = { + "links": {}, + "notifications": [ + { + "api_key": None, + "billable_units": 0, + "carrier": None, + "client_reference": None, + "created_at": "2023-12-14T20:35:55+00:00", + "created_by": { + "email_address": "grsrbsrgsrf@fake.gov", + "id": "de059e0a-42e5-48bb-939e-4f76804ab739", + "name": "grsrbsrgsrf", + }, + "document_download_count": None, + "id": "a3442b43-0ba1-4854-9e0a-d2fba1cc9b81", + "international": False, + "job": { + "id": "55b242b5-9f62-4271-aff7-039e9c320578", + "original_file_name": "1127b78e-a4a8-4b70-8f4f-9f4fbf03ece2.csv", + }, + "job_row_number": 0, + "key_name": None, + "key_type": "normal", + "normalised_to": "+16615555555", + "notification_type": "sms", + "personalisation": { + "dayofweek": "2", + "favecolor": "3", + "phonenumber": "+16615555555", + }, + "phone_prefix": "1", + "provider_response": None, + "rate_multiplier": 1.0, + "reference": None, + "reply_to_text": "development", + "sent_at": None, + "sent_by": None, + "service": "f62d840f-8bcb-4b36-b959-4687e16dd1a1", + "status": "created", + "template": { + "content": "((day of week)) and ((fave color))", + "id": "bd9caa7e-00ee-4c5a-839e-10ae1a7e6f73", + "name": "personalized", + "redact_personalisation": False, + "subject": None, + "template_type": "sms", + "version": 1, + }, + "to": "+16615555555", + "updated_at": None, + } + ], + "page_size": 50, + "total": 1, +} + +MOCK_ONE_OFF_JOB = { + "data": { + "api_key": "mocked_api_key", + "billable_units": 1, + "carrier": "mocked_carrier", + "client_reference": "mocked_client_reference", + "created_at": "2024-01-04T20:43:52+00:00", + "created_by": { + "email_address": "mocked_email@example.com", + "id": "mocked_user_id", + "name": "mocked_user", + }, + "document_download_count": None, + "id": "mocked_notification_id", + "international": False, + "job": {"id": "mocked_job_id", "original_file_name": "mocked_file.txt"}, + "job_row_number": 0, + "key_name": "mocked_key_name", + "key_type": "normal", + "normalised_to": "+12133166548", + "notification_type": "sms", + "personalisation": {"phonenumber": "+12133166548"}, + "phone_prefix": "1", + "provider_response": "mocked_provider_response", + "rate_multiplier": 1.0, + "reference": "mocked_reference", + "reply_to_text": "mocked_reply_text", + "sent_at": "2024-01-04T20:43:53+00:00", + "sent_by": "mocked_sender", + "service": "mocked_service_id", + "status": "sending", + "template": { + "content": "((day of week)) and ((fave color))", + "id": "bd9caa7e-00ee-4c5a-839e-10ae1a7e6f73", + "name": "personalized", + "redact_personalisation": False, + "subject": None, + "template_type": "sms", + "version": 1, + }, + "to": "+12133166548", + "updated_at": "2024-01-04T20:43:53+00:00", + } +} + stub_template_stats = [ { "template_type": "sms", @@ -116,7 +218,11 @@ def test_get_started( "app.template_statistics_client.get_template_statistics_for_service", return_value=copy.deepcopy(stub_template_stats), ) - + mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB) + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=FAKE_ONE_OFF_NOTIFICATION, + ) page = client_request.get( "main.service_dashboard", service_id=SERVICE_ONE_ID, @@ -142,6 +248,11 @@ def test_get_started_is_hidden_once_templates_exist( "app.template_statistics_client.get_template_statistics_for_service", return_value=copy.deepcopy(stub_template_stats), ) + mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB) + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=FAKE_ONE_OFF_NOTIFICATION, + ) page = client_request.get( "main.service_dashboard", service_id=SERVICE_ONE_ID, @@ -164,7 +275,11 @@ def test_inbound_messages_not_visible_to_service_without_permissions( mock_get_inbound_sms_summary, ): service_one["permissions"] = [] - + mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB) + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=FAKE_ONE_OFF_NOTIFICATION, + ) page = client_request.get( "main.service_dashboard", service_id=SERVICE_ONE_ID, @@ -188,6 +303,11 @@ def test_inbound_messages_shows_count_of_messages_when_there_are_messages( mock_get_inbound_sms_summary, ): service_one["permissions"] = ["inbound_sms"] + mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB) + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=FAKE_ONE_OFF_NOTIFICATION, + ) page = client_request.get( "main.service_dashboard", service_id=SERVICE_ONE_ID, @@ -215,6 +335,11 @@ def test_inbound_messages_shows_count_of_messages_when_there_are_no_messages( mock_get_inbound_sms_summary_with_no_messages, ): service_one["permissions"] = ["inbound_sms"] + mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB) + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=FAKE_ONE_OFF_NOTIFICATION, + ) page = client_request.get( "main.service_dashboard", service_id=SERVICE_ONE_ID, @@ -460,7 +585,11 @@ def test_should_show_recent_templates_on_dashboard( "app.template_statistics_client.get_template_statistics_for_service", return_value=copy.deepcopy(stub_template_stats), ) - + mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB) + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=FAKE_ONE_OFF_NOTIFICATION, + ) page = client_request.get( "main.service_dashboard", service_id=SERVICE_ONE_ID, @@ -513,7 +642,11 @@ def test_should_not_show_recent_templates_on_dashboard_if_only_one_template_used "app.template_statistics_client.get_template_statistics_for_service", return_value=stats, ) - + mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB) + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=FAKE_ONE_OFF_NOTIFICATION, + ) page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID) main = page.select_one("main").text @@ -638,6 +771,7 @@ def test_monthly_has_equal_length_tables( @freeze_time("2016-01-01 11:09:00.061258") def test_should_show_upcoming_jobs_on_dashboard( + mocker, client_request, mock_get_service_templates, mock_get_template_statistics, @@ -648,6 +782,11 @@ def test_should_show_upcoming_jobs_on_dashboard( mock_get_free_sms_fragment_limit, mock_get_inbound_sms_summary, ): + mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB) + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=FAKE_ONE_OFF_NOTIFICATION, + ) page = client_request.get( "main.service_dashboard", service_id=SERVICE_ONE_ID, @@ -685,6 +824,11 @@ def test_should_not_show_upcoming_jobs_on_dashboard_if_count_is_0( "soonest_scheduled_for": None, }, ) + mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB) + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=FAKE_ONE_OFF_NOTIFICATION, + ) page = client_request.get( "main.service_dashboard", service_id=SERVICE_ONE_ID, @@ -706,6 +850,11 @@ def test_should_not_show_upcoming_jobs_on_dashboard_if_service_has_no_jobs( mock_get_free_sms_fragment_limit, mock_get_inbound_sms_summary, ): + mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB) + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=FAKE_ONE_OFF_NOTIFICATION, + ) page = client_request.get( "main.service_dashboard", service_id=SERVICE_ONE_ID, @@ -755,7 +904,11 @@ def test_correct_font_size_for_big_numbers( service_one["permissions"] = permissions mocker.patch("app.main.views.dashboard.get_dashboard_totals", return_value=totals) - + mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB) + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=FAKE_ONE_OFF_NOTIFICATION, + ) page = client_request.get( "main.service_dashboard", service_id=service_one["id"], @@ -773,6 +926,7 @@ def test_correct_font_size_for_big_numbers( def test_should_not_show_jobs_on_dashboard_for_users_with_uploads_page( + mocker, client_request, service_one, mock_get_service_templates, @@ -784,6 +938,11 @@ def test_should_not_show_jobs_on_dashboard_for_users_with_uploads_page( mock_get_free_sms_fragment_limit, mock_get_inbound_sms_summary, ): + mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB) + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=FAKE_ONE_OFF_NOTIFICATION, + ) page = client_request.get( "main.service_dashboard", service_id=SERVICE_ONE_ID, @@ -1036,6 +1195,11 @@ def test_menu_send_messages( ): service_one["permissions"] = ["email", "sms"] + mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB) + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=FAKE_ONE_OFF_NOTIFICATION, + ) page = _test_dashboard_menu( client_request, mocker, @@ -1071,6 +1235,11 @@ def test_menu_manage_service( mock_get_inbound_sms_summary, mock_get_free_sms_fragment_limit, ): + mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB) + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=FAKE_ONE_OFF_NOTIFICATION, + ) page = _test_dashboard_menu( client_request, mocker, @@ -1105,6 +1274,11 @@ def test_menu_manage_api_keys( mock_get_inbound_sms_summary, mock_get_free_sms_fragment_limit, ): + mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB) + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=FAKE_ONE_OFF_NOTIFICATION, + ) page = _test_dashboard_menu( client_request, mocker, @@ -1140,6 +1314,11 @@ def test_menu_all_services_for_platform_admin_user( mock_get_inbound_sms_summary, mock_get_free_sms_fragment_limit, ): + mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB) + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=FAKE_ONE_OFF_NOTIFICATION, + ) page = _test_dashboard_menu( client_request, mocker, platform_admin_user, service_one, [] ) @@ -1180,7 +1359,11 @@ def test_route_for_service_permissions( mocker.patch( "app.service_api_client.get_global_notification_count", side_effect=_get ) - + mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB) + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=FAKE_ONE_OFF_NOTIFICATION, + ) validate_route_permission( mocker, notify_admin, @@ -1232,6 +1415,11 @@ def test_service_dashboard_updates_gets_dashboard_totals( "sms": {"requested": 456, "delivered": 0, "failed": 0}, }, ) + mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB) + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=FAKE_ONE_OFF_NOTIFICATION, + ) page = client_request.get( "main.service_dashboard", @@ -1345,6 +1533,7 @@ def test_get_tuples_of_financial_years_defaults_to_2015(): def test_org_breadcrumbs_do_not_show_if_service_has_no_org( + mocker, client_request, mock_get_template_statistics, mock_get_service_templates_when_no_templates_exist, @@ -1352,6 +1541,11 @@ def test_org_breadcrumbs_do_not_show_if_service_has_no_org( mock_get_annual_usage_for_service, mock_get_free_sms_fragment_limit, ): + mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB) + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=FAKE_ONE_OFF_NOTIFICATION, + ) page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID) assert not page.select(".navigation-organization-link") @@ -1414,6 +1608,11 @@ def test_org_breadcrumbs_show_if_user_is_a_member_of_the_services_org( id_=ORGANISATION_ID, ), ) + mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB) + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=FAKE_ONE_OFF_NOTIFICATION, + ) page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID) assert page.select_one(".navigation-organization-link")["href"] == url_for( @@ -1445,6 +1644,13 @@ def test_org_breadcrumbs_do_not_show_if_user_is_a_member_of_the_services_org_but ) mocker.patch("app.models.service.Organization") + mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB) + + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=FAKE_ONE_OFF_NOTIFICATION, + ) + page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID) assert not page.select(".navigation-breadcrumb") @@ -1476,6 +1682,13 @@ def test_org_breadcrumbs_show_if_user_is_platform_admin( ), ) + mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB) + + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=FAKE_ONE_OFF_NOTIFICATION, + ) + client_request.login(platform_admin_user, service_one_json) page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID) @@ -1504,6 +1717,13 @@ def test_breadcrumb_shows_if_service_is_suspended( mocker.patch( "app.service_api_client.get_service", return_value={"data": service_one_json} ) + + mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB) + + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=FAKE_ONE_OFF_NOTIFICATION, + ) page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID) assert "Suspended" in page.select_one(".navigation-service-name").text @@ -1532,6 +1752,11 @@ def test_service_dashboard_shows_usage( "count": 500, }, ) + mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB) + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=FAKE_ONE_OFF_NOTIFICATION, + ) service_one["permissions"] = permissions page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID) @@ -1569,6 +1794,12 @@ def test_service_dashboard_shows_free_allowance( } ], ) + mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB) + + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=FAKE_ONE_OFF_NOTIFICATION, + ) page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID) diff --git a/tests/app/main/views/test_sign_out.py b/tests/app/main/views/test_sign_out.py index 9ab3ad3cc..899216cd6 100644 --- a/tests/app/main/views/test_sign_out.py +++ b/tests/app/main/views/test_sign_out.py @@ -1,5 +1,107 @@ from tests.conftest import SERVICE_ONE_ID +FAKE_ONE_OFF_NOTIFICATION = { + "links": {}, + "notifications": [ + { + "api_key": None, + "billable_units": 0, + "carrier": None, + "client_reference": None, + "created_at": "2023-12-14T20:35:55+00:00", + "created_by": { + "email_address": "grsrbsrgsrf@fake.gov", + "id": "de059e0a-42e5-48bb-939e-4f76804ab739", + "name": "grsrbsrgsrf", + }, + "document_download_count": None, + "id": "a3442b43-0ba1-4854-9e0a-d2fba1cc9b81", + "international": False, + "job": { + "id": "55b242b5-9f62-4271-aff7-039e9c320578", + "original_file_name": "1127b78e-a4a8-4b70-8f4f-9f4fbf03ece2.csv", + }, + "job_row_number": 0, + "key_name": None, + "key_type": "normal", + "normalised_to": "+16615555555", + "notification_type": "sms", + "personalisation": { + "dayofweek": "2", + "favecolor": "3", + "phonenumber": "+16615555555", + }, + "phone_prefix": "1", + "provider_response": None, + "rate_multiplier": 1.0, + "reference": None, + "reply_to_text": "development", + "sent_at": None, + "sent_by": None, + "service": "f62d840f-8bcb-4b36-b959-4687e16dd1a1", + "status": "created", + "template": { + "content": "((day of week)) and ((fave color))", + "id": "bd9caa7e-00ee-4c5a-839e-10ae1a7e6f73", + "name": "personalized", + "redact_personalisation": False, + "subject": None, + "template_type": "sms", + "version": 1, + }, + "to": "+16615555555", + "updated_at": None, + } + ], + "page_size": 50, + "total": 1, +} + +MOCK_ONE_OFF_JOB = { + "data": { + "api_key": "mocked_api_key", + "billable_units": 1, + "carrier": "mocked_carrier", + "client_reference": "mocked_client_reference", + "created_at": "2024-01-04T20:43:52+00:00", + "created_by": { + "email_address": "mocked_email@example.com", + "id": "mocked_user_id", + "name": "mocked_user", + }, + "document_download_count": None, + "id": "mocked_notification_id", + "international": False, + "job": {"id": "mocked_job_id", "original_file_name": "mocked_file.txt"}, + "job_row_number": 0, + "key_name": "mocked_key_name", + "key_type": "normal", + "normalised_to": "+12133166548", + "notification_type": "sms", + "personalisation": {"phonenumber": "+12133166548"}, + "phone_prefix": "1", + "provider_response": "mocked_provider_response", + "rate_multiplier": 1.0, + "reference": "mocked_reference", + "reply_to_text": "mocked_reply_text", + "sent_at": "2024-01-04T20:43:53+00:00", + "sent_by": "mocked_sender", + "service": "mocked_service_id", + "status": "sending", + "template": { + "content": "((day of week)) and ((fave color))", + "id": "bd9caa7e-00ee-4c5a-839e-10ae1a7e6f73", + "name": "personalized", + "redact_personalisation": False, + "subject": None, + "template_type": "sms", + "version": 1, + }, + "to": "+12133166548", + "updated_at": "2024-01-04T20:43:53+00:00", + } +} + def test_render_sign_out_redirects_to_sign_in(client_request): # TODO with the change to using login.gov, we no longer redirect directly to the sign in page. @@ -18,6 +120,7 @@ def test_render_sign_out_redirects_to_sign_in(client_request): def test_sign_out_user( + mocker, client_request, mock_get_service, api_user_active, @@ -36,6 +139,13 @@ def test_sign_out_user( with client_request.session_transaction() as session: assert session.get("user_id") is not None # Check we are logged in + mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB) + + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=FAKE_ONE_OFF_NOTIFICATION, + ) + client_request.get( "main.service_dashboard", service_id=SERVICE_ONE_ID,
Daily