Merge pull request #4206 from alphagov/daily-sms-vols-reports

add new daily sms provider volumes report
This commit is contained in:
Leo Hemsted
2022-04-12 15:48:36 +01:00
committed by GitHub
12 changed files with 197 additions and 52 deletions

View File

@@ -381,6 +381,46 @@ def get_daily_volumes():
return render_template('views/platform-admin/daily-volumes-report.html', form=form)
@main.route("/platform-admin/reports/daily-sms-provider-volumes-report", methods=['GET', 'POST'])
@user_is_platform_admin
def get_daily_sms_provider_volumes():
form = BillingReportDateFilterForm()
if form.validate_on_submit():
start_date = form.start_date.data
end_date = form.end_date.data
headers = [
"day",
"provider",
"sms totals",
"sms fragment totals",
"sms chargeable units",
"sms cost",
]
result = billing_api_client.get_data_for_daily_sms_provider_volumes_report(start_date, end_date)
rows = [
[
r["day"],
r["provider"],
r["sms_totals"],
r["sms_fragment_totals"],
r["sms_chargeable_units"],
r["sms_cost"]
]
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':
f'attachment; filename="Daily SMS provider volumes report from {start_date} to {end_date}.csv"'
}
else:
flash('No results for dates')
return render_template('views/platform-admin/daily-sms-provider-volumes-report.html', form=form)
@main.route("/platform-admin/complaints")
@user_is_platform_admin
def platform_admin_list_complaints():

View File

@@ -95,6 +95,7 @@ class HeaderNavigation(Navigation):
'notifications_sent_by_service',
'get_billing_report',
'get_daily_volumes',
'get_daily_sms_provider_volumes',
'get_volumes_by_service',
'organisations',
'platform_admin',

View File

@@ -55,5 +55,14 @@ class BillingAPIClient(NotifyAdminAPIClient):
'end_date': str(end_date),
})
def get_data_for_daily_sms_provider_volumes_report(self, start_date, end_date):
return self.get(
url='/platform-stats/daily-sms-provider-volumes-report',
params={
'start_date': str(start_date),
'end_date': str(end_date),
}
)
billing_api_client = BillingAPIClient()

View File

@@ -22,16 +22,16 @@
field_headings_visible=True,
caption_visible=False
) %}
{% for message_length, charge in [
{% for message_status, description in [
('Sending', 'Notify has sent the message to the provider. The provider will try to deliver the message to the recipient for up to 72 hours. Notify is waiting for delivery information.'),
('Delivered', 'The message was successfully delivered. Notify will not tell you if a user has opened or read a message.'),
('Email address does not exist', 'The provider could not deliver the message because the email address was wrong. You should remove these email addresses from your database.'),
('Inbox not accepting messages right now', 'The provider could not deliver the message. This can happen when the recipients inbox is full or their anti-spam filter rejects your email. <a class="govuk-link govuk-link--no-visited-state" href="https://www.gov.uk/service-manual/design/sending-emails-and-text-messages#protect-your-users-from-spam-and-phishing">Check your content does not look like spam</a> before you try to send the message again.'),
('Inbox not accepting messages right now', 'The provider could not deliver the message. This can happen when the recipients inbox is full or their anti-spam filter rejects your email. <a class="govuk-link govuk-link--no-visited-state" href="https://www.gov.uk/service-manual/design/sending-emails-and-text-messages#protect-your-users-from-spam-and-phishing">Check your content does not look like spam</a> before you try to send the message again.' | safe),
('Technical failure', 'Your message was not sent because there was a problem between Notify and the provider. Youll have to try sending your messages again.'),
] %}
{% call row() %}
{{ text_field(message_length) }}
{{ text_field(charge | safe) }}
{{ text_field(message_status) }}
{{ text_field(description) }}
{% endcall %}
{% endfor %}
{% endcall %}
@@ -51,7 +51,7 @@
field_headings_visible=True,
caption_visible=False
) %}
{% for message_length, charge in [
{% for message_status, description in [
('Sending', 'Notify has sent the message to the provider. The provider will try to deliver the message to the recipient for up to 72 hours. Notify is waiting for delivery information.'),
('Sent to an international number', 'The mobile networks in some countries do not provide any more delivery information.'),
('Delivered', 'The message was successfully delivered. Notify will not tell you if a user has opened or read a message.'),
@@ -60,8 +60,8 @@
('Technical failure', 'Your message was not sent because there was a problem between Notify and the provider. Youll have to try sending your messages again. You will not be charged for text messages that are affected by a technical failure.'),
] %}
{% call row() %}
{{ text_field(message_length) }}
{{ text_field(charge) }}
{{ text_field(message_status) }}
{{ text_field(description) }}
{% endcall %}
{% endfor %}
{% endcall %}
@@ -75,7 +75,7 @@
field_headings_visible=True,
caption_visible=False
) %}
{% for message_length, charge in [
{% for message_status, description in [
('Sent', 'Notify has sent the letter to the provider to be printed.'),
('Printed', 'The provider has printed the letter. Letters are printed at 5:30pm and dispatched the next working day.'),
('Cancelled', 'Sending cancelled. Your letter will not be printed or dispatched.'),
@@ -83,8 +83,8 @@
('Permanent failure', 'The provider cannot print the letter. Your letter will not be dispatched.')
] %}
{% call row() %}
{{ text_field(message_length) }}
{{ text_field(charge) }}
{{ text_field(message_status) }}
{{ text_field(description) }}
{% endcall %}
{% endfor %}
{% endcall %}

View File

@@ -0,0 +1,47 @@
{% 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 SMS provider volumes Report
{% endblock %}
{% block platform_admin_content %}
<h1 class="heading-large">
Daily SMS provider volumes Report
</h1>
{% 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 %}
<h2 class="heading-medium">
Data included in the report
</h2>
<div class="bottom-gutter-3-2">
{% call mapping_table(
caption='Descriptions of daily SMS provider volumes data',
field_headings=['Name', 'Description'],
field_headings_visible=True,
caption_visible=False
) %}
{% for column_heading, description in [
('day', 'The whole business day in BST'),
('provider', 'The SMS provider'),
('sms totals', 'The number of text messages sent'),
('sms fragments', 'The number of text message fragments sent'),
('sms chargeable units', 'The number of text message fragments sent times the rate multiplier'),
('sms cost', 'The cost of text messages sent'),
] %}
{% call row() %}
{{ text_field(column_heading) }}
{{ text_field(description) }}
{% endcall %}
{% endfor %}
{% endcall %}
</div>
{% endblock %}

View File

@@ -28,7 +28,7 @@
field_headings_visible=True,
caption_visible=False
) %}
{% for message_length, charge in [
{% for column_heading, description 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'),
@@ -38,8 +38,8 @@
('letter sheet totals', 'The number of sheets sent')
] %}
{% call row() %}
{{ text_field(message_length) }}
{{ text_field(charge | safe) }}
{{ text_field(column_heading) }}
{{ text_field(description) }}
{% endcall %}
{% endfor %}
{% endcall %}

View File

@@ -28,16 +28,16 @@
field_headings_visible=True,
caption_visible=False
) %}
{% for message_length, charge in [
{% for column_heading, description in [
('sms cost', 'The total cost of text messages sent after a service has used its free allowance.'),
('sms chargeable units', 'The number of fragments sent after a service has used its free allowance. This number takes into account the cost multiplier for <a class="govuk-link govuk-link--no-visited-state" href="https://www.notifications.service.gov.uk/pricing#international-numbers">sending international text messages</a>.'),
('sms chargeable units', 'The number of fragments sent after a service has used its free allowance. This number takes into account the cost multiplier for <a class="govuk-link govuk-link--no-visited-state" href="https://www.notifications.service.gov.uk/pricing#international-numbers">sending international text messages</a>.' | safe),
('letter cost', 'The total cost of letters sent by a service.'),
('letter breakdown', 'The number of letters sent by a service, grouped by postage and unit cost.'),
('purchase order number, contact names, contact email addresses and billing reference', 'We add this data manually based on the information we get from services. You can help by adding it to the service settings page.'),
] %}
{% call row() %}
{{ text_field(message_length) }}
{{ text_field(charge | safe) }}
{{ text_field(column_heading) }}
{{ text_field(description) }}
{% endcall %}
{% endfor %}
{% endcall %}

View File

@@ -25,4 +25,7 @@
<p class="govuk-body">
<a class="govuk-link govuk-link--no-visited-state" href="{{ url_for('main.get_daily_volumes') }}">Daily volumes Report</a>
</p>
<p class="govuk-body">
<a class="govuk-link govuk-link--no-visited-state" href="{{ url_for('main.get_daily_sms_provider_volumes') }}">Daily SMS provider volumes Report</a>
</p>
{% endblock %}

View File

@@ -28,7 +28,7 @@
field_headings_visible=True,
caption_visible=False
) %}
{% for message_length, charge in [
{% for column_heading, description 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 chargeable units', 'The number of text message fragments times the rate multiplier sent by the service.'),
@@ -38,8 +38,8 @@
('letter sheet totals', 'The number of sheet sent by a service')
] %}
{% call row() %}
{{ text_field(message_length) }}
{{ text_field(charge | safe) }}
{{ text_field(column_heading) }}
{{ text_field(description) }}
{% endcall %}
{% endfor %}
{% endcall %}

View File

@@ -854,14 +854,14 @@ def test_get_live_services_report(
)
report = response.get_data(as_text=True)
assert report.strip() == (
'Service ID,Organisation,Organisation type,Service name,Consent to research,Main contact,Contact email,'
+ 'Contact mobile,Live date,SMS volume intent,Email volume intent,Letter volume intent,SMS sent this year,'
+ 'Emails sent this year,Letters sent this year,Free sms allowance\r\n'
'Service ID,Organisation,Organisation type,Service name,Consent to research,Main contact,Contact email,' +
'Contact mobile,Live date,SMS volume intent,Email volume intent,Letter volume intent,SMS sent this year,' +
'Emails sent this year,Letters sent this year,Free sms allowance\r\n' +
+ '1,Forest,Ecosystem,jessie the oak tree,True,Forest fairy,forest.fairy@digital.cabinet-office.gov.uk,'
+ '+447700900986,29-03-2014,100,50,20,300,1200,0,100\r\n'
'1,Forest,Ecosystem,jessie the oak tree,True,Forest fairy,forest.fairy@digital.cabinet-office.gov.uk,' +
'+447700900986,29-03-2014,100,50,20,300,1200,0,100\r\n' +
+ '2,Forest,Ecosystem,james the pine tree,,,,,,,60,0,0,0,0,200'
'2,Forest,Ecosystem,james the pine tree,,,,,,,60,0,0,0,0,200'
)
@@ -920,7 +920,7 @@ def test_get_billing_report_when_no_results_for_date(client_request, platform_ad
assert normalize_spaces(error.text) == 'No results for dates'
def test_get_billing_report_when_calls_api_and_download_data(
def test_get_billing_report_calls_api_and_download_data(
client_request,
platform_admin_user,
mocker
@@ -957,10 +957,9 @@ def test_get_billing_report_when_calls_api_and_download_data(
)
assert response.get_data(as_text=True) == (
'organisation_id,organisation_name,service_id,service_name,sms_cost,sms_chargeable_units' +
',total_letters,letter_cost,letter_breakdown,purchase_order_number,contact_names,contact_email_addresses' +
',billing_reference\r\n' +
'organisation_id,organisation_name,service_id,service_name,sms_cost,sms_chargeable_units,' +
'total_letters,letter_cost,letter_breakdown,purchase_order_number,contact_names,contact_email_addresses,' +
'billing_reference\r\n' +
'7832a1be-a1f0-4f2a-982f-05adfd3d6354,' +
'Org for a - with sms and letter,' +
@@ -973,7 +972,7 @@ def test_get_billing_report_when_calls_api_and_download_data(
'"6 second class letters at 45p' +
'\n' +
'2 first class letters at 35p",' +
'PO1234,"Anne, Marie, Josh","billing@example.com, accounts@example.com",Notify2020'
'PO1234,"Anne, Marie, Josh","billing@example.com, accounts@example.com",Notify2020' +
'\r\n'
)
@@ -1008,15 +1007,16 @@ def test_get_notifications_sent_by_service_calls_api_and_downloads_data(
'attachment; filename="{} to {} notification status per service report.csv"'.format(start_date, end_date)
)
assert response.get_data(as_text=True) == (
'date_created,service_id,service_name,notification_type,count_sending,count_delivered,count_technical_failure,'
'count_temporary_failure,count_permanent_failure,count_sent\r\n'
'2019-01-01,596364a0-858e-42c8-9062-a8fe822260eb,service one,email,191,0,0,14,0,0\r\n'
'2019-01-01,596364a0-858e-42c8-9062-a8fe822260eb,service one,sms,42,0,0,8,0,0\r\n'
'date_created,service_id,service_name,notification_type,count_sending,count_delivered,' +
'count_technical_failure,count_temporary_failure,count_permanent_failure,count_sent\r\n' +
'2019-01-01,596364a0-858e-42c8-9062-a8fe822260eb,service one,email,191,0,0,14,0,0\r\n' +
'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(
def test_get_volumes_by_service_report_calls_api_and_download_data(
client_request,
platform_admin_user,
mocker
@@ -1064,13 +1064,13 @@ def test_get_volumes_by_service_report_when_calls_api_and_download_data(
'8,' +
'10,' +
'4.5,' +
'10'
'10' +
'\r\n'
)
def test_get_daily_volumes_report_when_calls_api_and_download_data(
def test_get_daily_volumes_report_calls_api_and_download_data(
client_request,
platform_admin_user,
mocker
@@ -1109,7 +1109,50 @@ def test_get_daily_volumes_report_when_calls_api_and_download_data(
'60,' +
'100,' +
'10,' +
'20'
'20' +
'\r\n'
)
def test_get_daily_sms_provider_volumes_report_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_sms_provider_volumes_report",
return_value=[{
"day": '2019-01-01',
"provider": 'foo',
"sms_totals": 20,
"sms_fragment_totals": 40,
"sms_chargeable_units": 60,
"sms_cost": 80,
}]
)
client_request.login(platform_admin_user)
response = client_request.post_response(
'main.get_daily_sms_provider_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 SMS provider volumes report from {} to {}.csv"'.format('2019-01-01', '2019-03-31')
)
assert response.get_data(as_text=True) == (
"day,provider,sms totals,sms fragment totals,sms chargeable units,sms cost\r\n" +
'2019-01-01,' +
'foo,' +
'20,' +
'40,' +
'60,' +
'80' +
'\r\n'
)

View File

@@ -1,5 +1,7 @@
import uuid
import pytest
from app.notify_client.billing_api_client import BillingAPIClient
@@ -43,19 +45,18 @@ def test_post_free_sms_fragment_limit_for_year_endpoint(mocker, api_user_active)
)
def test_get_data_for_volumes_by_service_report(mocker, api_user_active):
@pytest.mark.parametrize('func, expected_url', [
(BillingAPIClient.get_data_for_volumes_by_service_report, '/platform-stats/volumes-by-service'),
(BillingAPIClient.get_data_for_daily_volumes_report, '/platform-stats/daily-volumes-report'),
(
BillingAPIClient.get_data_for_daily_sms_provider_volumes_report,
'/platform-stats/daily-sms-provider-volumes-report'
),
])
def test_get_data_for_volume_reports(mocker, api_user_active, func, expected_url):
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'})
func(client, '2022-03-01', '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'})
mock_get.assert_called_once_with(url=expected_url, params={'start_date': '2022-03-01', 'end_date': '2022-03-31'})

View File

@@ -131,6 +131,7 @@ EXCLUDED_ENDPOINTS = tuple(map(Navigation.get_endpoint_with_blueprint, {
'forgot_password',
'get_billing_report',
'get_daily_volumes',
'get_daily_sms_provider_volumes',
'get_volumes_by_service',
'get_example_csv',
'get_notifications_as_json',