From 971cb745c9e6ba04edc9da07c77c6444a0d78960 Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Mon, 7 Mar 2022 14:30:11 +0000 Subject: [PATCH 1/2] Report for total notifications sent per day for each channel. Daily volumes report: total volumes across the platform aggregated by whole business day (bst_date) Volumes by service report: total volumes per service aggregated by the date range given. NB: start and end dates are inclusive --- app/main/views/platform_admin.py | 68 +++++++++++++++++++ app/navigation.py | 2 + app/notify_client/billing_api_client.py | 14 ++++ .../platform-admin/daily-volumes-report.html | 48 +++++++++++++ .../views/platform-admin/reports.html | 6 ++ .../volumes-by-service-report.html | 46 +++++++++++++ .../app/notify_client/test_billing_client.py | 18 +++++ tests/app/test_navigation.py | 2 + 8 files changed, 204 insertions(+) create mode 100644 app/templates/views/platform-admin/daily-volumes-report.html create mode 100644 app/templates/views/platform-admin/volumes-by-service-report.html diff --git a/app/main/views/platform_admin.py b/app/main/views/platform_admin.py index 10e43fb4b..303a1fea5 100644 --- a/app/main/views/platform_admin.py +++ b/app/main/views/platform_admin.py @@ -313,6 +313,74 @@ def get_billing_report(): return render_template('views/platform-admin/get-billing-report.html', form=form) +@main.route("/platform-admin/reports/volumes-by-service", methods=['GET', 'POST']) +@user_is_platform_admin +def get_volumes_by_service(): + form = BillingReportDateFilterForm() + + if form.validate_on_submit(): + start_date = form.start_date.data + end_date = form.end_date.data + headers = [ + "organisation_id", "organisation_name", "service_id", "service_name", + "free_allowance", "sms_notifications", "sms chargeable units", "email_totals", + "letter_totals", "letter_cost", "letter_sheet_totals" + ] + result = billing_api_client.get_data_for_volumes_by_service_report(start_date, end_date) + + rows = [ + [ + r["organisation_id"], r["organisation_name"], r["service_id"], r["service_name"], + r["free_allowance"], r["sms_notifications"], r["sms_chargeable_units"], r["email_totals"], + r["letter_totals"], r["letter_cost"], r["letter_sheet_totals"] + ] + for r in result + ] + if rows: + return Spreadsheet.from_rows([headers] + rows).as_csv_data, 200, { + 'Content-Type': 'text/csv; charset=utf-8', + 'Content-Disposition': 'attachment; filename="Volumes by service report from {} to {}.csv"'.format( + start_date, end_date + ) + } + else: + flash('No results for dates') + return render_template('views/platform-admin/volumes-by-service-report.html', form=form) + + +@main.route("/platform-admin/reports/daily-volumes-report", methods=['GET', 'POST']) +@user_is_platform_admin +def get_daily_volumes(): + form = BillingReportDateFilterForm() + + if form.validate_on_submit(): + start_date = form.start_date.data + end_date = form.end_date.data + headers = [ + "day", "sms totals", "sms fragment totals", "sms chargeable units", + "email totals", "letter totals", "letter sheet totals" + ] + result = billing_api_client.get_data_for_daily_volumes_report(start_date, end_date) + + rows = [ + [ + r["day"], r["sms_totals"], r["sms_fragment_totals"], r["sms_chargeable_units"], + r["email_totals"], r["letter_totals"], r["letter_sheet_totals"] + ] + for r in result + ] + if rows: + return Spreadsheet.from_rows([headers] + rows).as_csv_data, 200, { + 'Content-Type': 'text/csv; charset=utf-8', + 'Content-Disposition': 'attachment; filename="Daily volumes report from {} to {}.csv"'.format( + start_date, end_date + ) + } + else: + flash('No results for dates') + return render_template('views/platform-admin/daily-volumes-report.html', form=form) + + @main.route("/platform-admin/complaints") @user_is_platform_admin def platform_admin_list_complaints(): diff --git a/app/navigation.py b/app/navigation.py index 7a72749c6..110a1557a 100644 --- a/app/navigation.py +++ b/app/navigation.py @@ -92,6 +92,8 @@ class HeaderNavigation(Navigation): 'live_services_csv', 'notifications_sent_by_service', 'get_billing_report', + 'get_daily_volumes', + 'get_volumes_by_service', 'organisations', 'platform_admin', 'platform_admin_list_complaints', diff --git a/app/notify_client/billing_api_client.py b/app/notify_client/billing_api_client.py index ed083692e..08b5ce52c 100644 --- a/app/notify_client/billing_api_client.py +++ b/app/notify_client/billing_api_client.py @@ -41,5 +41,19 @@ class BillingAPIClient(NotifyAdminAPIClient): 'end_date': str(end_date), }) + def get_data_for_volumes_by_service_report(self, start_date, end_date): + return self.get(url='/platform-stats/volumes-by-service', + params={ + 'start_date': str(start_date), + 'end_date': str(end_date), + }) + + def get_data_for_daily_volumes_report(self, start_date, end_date): + return self.get(url='/platform-stats/daily-volumes-report', + params={ + 'start_date': str(start_date), + 'end_date': str(end_date), + }) + billing_api_client = BillingAPIClient() diff --git a/app/templates/views/platform-admin/daily-volumes-report.html b/app/templates/views/platform-admin/daily-volumes-report.html new file mode 100644 index 000000000..dbe24918b --- /dev/null +++ b/app/templates/views/platform-admin/daily-volumes-report.html @@ -0,0 +1,48 @@ +{% extends "views/platform-admin/_base_template.html" %} +{% from "components/form.html" import form_wrapper %} +{% from "components/table.html" import mapping_table, row, text_field %} + +{% block per_page_title %} + Daily volumes report +{% endblock %} + +{% block platform_admin_content %} + +

+ Daily volumes Report +

+ + {% call form_wrapper() %} + {{ form.start_date(param_extensions={"hint": {"text": "Use the format YYYY-MM-DD"}}) }} + {{ form.end_date(param_extensions={"hint": {"text": "Use the format YYYY-MM-DD"}}) }} + {{ page_footer('Download report') }} + {% endcall %} + +

+ Data included in the report +

+
+ {% call mapping_table( + caption='Descriptions of daily volumes data', + field_headings=['Name', 'Description'], + field_headings_visible=True, + caption_visible=False + ) %} + {% for message_length, charge in [ + ('day', 'The whole business day in BST.'), + ('sms totals', 'The number of text messages sent'), + ('sms chargeable units', 'The number of text message fragments sent'), + ('sms_fragments_times_multiplier', 'The number of text message fragments sent times the rate multiplier'), + ('email totals', 'The number of emails sent'), + ('letter totals', 'The number of letters sent'), + ('letter sheet totals', The number of sheets sent) + ] %} + {% call row() %} + {{ text_field(message_length) }} + {{ text_field(charge | safe) }} + {% endcall %} + {% endfor %} + {% endcall %} +
+ +{% endblock %} diff --git a/app/templates/views/platform-admin/reports.html b/app/templates/views/platform-admin/reports.html index 8d56460a9..dda3f18ff 100644 --- a/app/templates/views/platform-admin/reports.html +++ b/app/templates/views/platform-admin/reports.html @@ -19,4 +19,10 @@

Billing Report

+

+ Volumes by service Report +

+

+ Daily volumes Report +

{% endblock %} diff --git a/app/templates/views/platform-admin/volumes-by-service-report.html b/app/templates/views/platform-admin/volumes-by-service-report.html new file mode 100644 index 000000000..1ae33d8b2 --- /dev/null +++ b/app/templates/views/platform-admin/volumes-by-service-report.html @@ -0,0 +1,46 @@ +{% extends "views/platform-admin/_base_template.html" %} +{% from "components/form.html" import form_wrapper %} +{% from "components/table.html" import mapping_table, row, text_field %} + +{% block per_page_title %} + Volumes by service report +{% endblock %} + +{% block platform_admin_content %} + +

+ Volumes by service Report +

+ + {% call form_wrapper() %} + {{ form.start_date(param_extensions={"hint": {"text": "Use the format YYYY-MM-DD"}}) }} + {{ form.end_date(param_extensions={"hint": {"text": "Use the format YYYY-MM-DD"}}) }} + {{ page_footer('Download report') }} + {% endcall %} + +

+ Data included in the report +

+
+ {% call mapping_table( + caption='Descriptions of volumes by service data', + field_headings=['Name', 'Description'], + field_headings_visible=True, + caption_visible=False + ) %} + {% for message_length, charge in [ + ('free allowance', 'Free allowance set for the service. This is the latest free allowance for the date range given'), + ('sms notifications', 'The number of text messages sent by the service.'), + ('sms volume', 'The number of text message fragments times the rate multiplier sent by the service.'), + ('email totals', 'The number of emails sent by a service'), + ('letter totals', 'The number of letters sent by a service'), + ] %} + {% call row() %} + {{ text_field(message_length) }} + {{ text_field(charge | safe) }} + {% endcall %} + {% endfor %} + {% endcall %} +
+ +{% endblock %} diff --git a/tests/app/notify_client/test_billing_client.py b/tests/app/notify_client/test_billing_client.py index e29a80bdc..b3c29509b 100644 --- a/tests/app/notify_client/test_billing_client.py +++ b/tests/app/notify_client/test_billing_client.py @@ -41,3 +41,21 @@ def test_post_free_sms_fragment_limit_for_year_endpoint(mocker, api_user_active) url='/service/{}/billing/free-sms-fragment-limit'.format(service_id), data=sms_limit_data ) + + +def test_get_data_for_volumes_by_service_report(mocker, api_user_active): + mock_get = mocker.patch('app.notify_client.billing_api_client.BillingAPIClient.get') + client = BillingAPIClient() + + client.get_data_for_volumes_by_service_report('2022-03-01', '2022-03-31') + mock_get.assert_called_once_with(url='/platform-stats/volumes-by-service', + params={'start_date': '2022-03-01', 'end_date': '2022-03-31'}) + + +def test_get_data_for_daily_volumes_report(mocker, api_user_active): + mock_get = mocker.patch('app.notify_client.billing_api_client.BillingAPIClient.get') + client = BillingAPIClient() + + client.get_data_for_daily_volumes_report('2022-03-01', '2022-03-31') + mock_get.assert_called_once_with(url='/platform-stats/daily-volumes-report', + params={'start_date': '2022-03-01', 'end_date': '2022-03-31'}) diff --git a/tests/app/test_navigation.py b/tests/app/test_navigation.py index 4cb615f34..8477a9581 100644 --- a/tests/app/test_navigation.py +++ b/tests/app/test_navigation.py @@ -131,6 +131,8 @@ EXCLUDED_ENDPOINTS = tuple(map(Navigation.get_endpoint_with_blueprint, { 'find_users_by_email', 'forgot_password', 'get_billing_report', + 'get_daily_volumes', + 'get_volumes_by_service', 'get_example_csv', 'get_notifications_as_json', 'get_started', From c6e67d1690f8a5a5af1f180f37297890f2628fce Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Wed, 9 Mar 2022 15:02:52 +0000 Subject: [PATCH 2/2] Fix column names Add unit test --- app/main/views/platform_admin.py | 6 +- .../platform-admin/daily-volumes-report.html | 4 +- .../volumes-by-service-report.html | 4 +- tests/app/main/views/test_platform_admin.py | 99 +++++++++++++++++++ 4 files changed, 107 insertions(+), 6 deletions(-) diff --git a/app/main/views/platform_admin.py b/app/main/views/platform_admin.py index 303a1fea5..230171598 100644 --- a/app/main/views/platform_admin.py +++ b/app/main/views/platform_admin.py @@ -322,9 +322,9 @@ def get_volumes_by_service(): start_date = form.start_date.data end_date = form.end_date.data headers = [ - "organisation_id", "organisation_name", "service_id", "service_name", - "free_allowance", "sms_notifications", "sms chargeable units", "email_totals", - "letter_totals", "letter_cost", "letter_sheet_totals" + "organisation id", "organisation name", "service id", "service name", + "free allowance", "sms notifications", "sms chargeable units", "email totals", + "letter totals", "letter cost", "letter sheet totals" ] result = billing_api_client.get_data_for_volumes_by_service_report(start_date, end_date) diff --git a/app/templates/views/platform-admin/daily-volumes-report.html b/app/templates/views/platform-admin/daily-volumes-report.html index dbe24918b..827720e26 100644 --- a/app/templates/views/platform-admin/daily-volumes-report.html +++ b/app/templates/views/platform-admin/daily-volumes-report.html @@ -31,11 +31,11 @@ {% for message_length, charge in [ ('day', 'The whole business day in BST.'), ('sms totals', 'The number of text messages sent'), + ('sms fragments', 'The number of text message fragments sent times the rate multiplier'), ('sms chargeable units', 'The number of text message fragments sent'), - ('sms_fragments_times_multiplier', 'The number of text message fragments sent times the rate multiplier'), ('email totals', 'The number of emails sent'), ('letter totals', 'The number of letters sent'), - ('letter sheet totals', The number of sheets sent) + ('letter sheet totals', 'The number of sheets sent') ] %} {% call row() %} {{ text_field(message_length) }} diff --git a/app/templates/views/platform-admin/volumes-by-service-report.html b/app/templates/views/platform-admin/volumes-by-service-report.html index 1ae33d8b2..91c70fca2 100644 --- a/app/templates/views/platform-admin/volumes-by-service-report.html +++ b/app/templates/views/platform-admin/volumes-by-service-report.html @@ -31,9 +31,11 @@ {% for message_length, charge in [ ('free allowance', 'Free allowance set for the service. This is the latest free allowance for the date range given'), ('sms notifications', 'The number of text messages sent by the service.'), - ('sms volume', 'The number of text message fragments times the rate multiplier sent by the service.'), + ('sms chargeable units', 'The number of text message fragments times the rate multiplier sent by the service.'), ('email totals', 'The number of emails sent by a service'), ('letter totals', 'The number of letters sent by a service'), + ('letter costs', 'The cost of the letters, rate * letter totals'), + ('letter sheet totals', 'The number of sheet sent by a service') ] %} {% call row() %} {{ text_field(message_length) }} diff --git a/tests/app/main/views/test_platform_admin.py b/tests/app/main/views/test_platform_admin.py index fb4cba007..d8320da9b 100644 --- a/tests/app/main/views/test_platform_admin.py +++ b/tests/app/main/views/test_platform_admin.py @@ -1014,3 +1014,102 @@ def test_get_notifications_sent_by_service_calls_api_and_downloads_data( '2019-01-01,596364a0-858e-42c8-9062-a8fe822260eb,service one,sms,42,0,0,8,0,0\r\n' '2019-01-01,147ad62a-2951-4fa1-9ca0-093cd1a52c52,service two,email,3,1,0,2,0,0\r\n' ) + + +def test_get_volumes_by_service_report_when_calls_api_and_download_data( + client_request, + platform_admin_user, + mocker +): + mocker.patch( + "app.main.views.platform_admin.billing_api_client.get_data_for_volumes_by_service_report", + return_value=[{ + "organisation_id": "7832a1be-a1f0-4f2a-982f-05adfd3d6354", + "organisation_name": "Org name", + "service_id": "48e82ac0-c8c4-4e46-8712-c83c35a94006", + "service_name": "service name", + "free_allowance": 10000, + "sms_notifications": 10, + "sms_chargeable_units": 20, + "email_totals": 8, + "letter_totals": 10, + "letter_cost": 4.5, + "letter_sheet_totals": 10 + }] + ) + + client_request.login(platform_admin_user) + response = client_request.post_response( + 'main.get_volumes_by_service', + _data={'start_date': '2019-01-01', 'end_date': '2019-03-31'}, + _expected_status=200, + ) + + assert response.content_type == 'text/csv; charset=utf-8' + assert response.headers['Content-Disposition'] == ( + 'attachment; filename="Volumes by service report from {} to {}.csv"'.format('2019-01-01', '2019-03-31') + ) + + assert response.get_data(as_text=True) == ( + "organisation id,organisation name,service id,service name,free allowance,sms notifications," + + "sms chargeable units,email totals,letter totals,letter cost,letter sheet totals\r\n" + + + '7832a1be-a1f0-4f2a-982f-05adfd3d6354,' + + 'Org name,' + + '48e82ac0-c8c4-4e46-8712-c83c35a94006,' + + 'service name,' + + '10000,' + + '10,' + + '20,' + + '8,' + + '10,' + + '4.5,' + + '10' + + '\r\n' + ) + + +def test_get_daily_volumes_report_when_calls_api_and_download_data( + client_request, + platform_admin_user, + mocker +): + mocker.patch( + "app.main.views.platform_admin.billing_api_client.get_data_for_daily_volumes_report", + return_value=[{ + "day": '2019-01-01', + "sms_totals": 20, + "sms_fragment_totals": 40, + "sms_chargeable_units": 60, + "email_totals": 100, + "letter_totals": 10, + "letter_sheet_totals": 20 + }] + ) + + client_request.login(platform_admin_user) + response = client_request.post_response( + 'main.get_daily_volumes', + _data={'start_date': '2019-01-01', 'end_date': '2019-03-31'}, + _expected_status=200, + ) + + assert response.content_type == 'text/csv; charset=utf-8' + assert response.headers['Content-Disposition'] == ( + 'attachment; filename="Daily volumes report from {} to {}.csv"'.format('2019-01-01', '2019-03-31') + ) + + assert response.get_data(as_text=True) == ( + "day,sms totals,sms fragment totals,sms chargeable units,email totals,letter totals,letter sheet totals\r\n" + + + '2019-01-01,' + + '20,' + + '40,' + + '60,' + + '100,' + + '10,' + + '20' + + '\r\n' + )