mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-23 15:57:23 -04:00
Merge pull request #3063 from alphagov/new-report
Add new report to show monthly notification stats for each service
This commit is contained in:
@@ -1120,6 +1120,11 @@ class DateFilterForm(StripWhitespaceForm):
|
|||||||
include_from_test_key = BooleanField("Include test keys", default="checked", false_values={"N"})
|
include_from_test_key = BooleanField("Include test keys", default="checked", false_values={"N"})
|
||||||
|
|
||||||
|
|
||||||
|
class RequiredDateFilterForm(StripWhitespaceForm):
|
||||||
|
start_date = DateField("Start Date")
|
||||||
|
end_date = DateField("End Date")
|
||||||
|
|
||||||
|
|
||||||
class SearchByNameForm(StripWhitespaceForm):
|
class SearchByNameForm(StripWhitespaceForm):
|
||||||
|
|
||||||
search = SearchField('Search by name')
|
search = SearchField('Search by name')
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from app import (
|
|||||||
complaint_api_client,
|
complaint_api_client,
|
||||||
format_date_numeric,
|
format_date_numeric,
|
||||||
letter_jobs_client,
|
letter_jobs_client,
|
||||||
|
notification_api_client,
|
||||||
platform_stats_api_client,
|
platform_stats_api_client,
|
||||||
service_api_client,
|
service_api_client,
|
||||||
)
|
)
|
||||||
@@ -20,6 +21,7 @@ from app.main.forms import (
|
|||||||
ClearCacheForm,
|
ClearCacheForm,
|
||||||
DateFilterForm,
|
DateFilterForm,
|
||||||
PDFUploadForm,
|
PDFUploadForm,
|
||||||
|
RequiredDateFilterForm,
|
||||||
ReturnedLettersForm,
|
ReturnedLettersForm,
|
||||||
)
|
)
|
||||||
from app.statistics_utils import (
|
from app.statistics_utils import (
|
||||||
@@ -263,6 +265,33 @@ def performance_platform_xlsx():
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@main.route("/platform-admin/reports/notifications-sent-by-service", methods=['GET', 'POST'])
|
||||||
|
@user_is_platform_admin
|
||||||
|
def notifications_sent_by_service():
|
||||||
|
form = RequiredDateFilterForm()
|
||||||
|
|
||||||
|
if form.validate_on_submit():
|
||||||
|
start_date = form.start_date.data
|
||||||
|
end_date = form.end_date.data
|
||||||
|
|
||||||
|
headers = [
|
||||||
|
'date_created', 'service_id', 'service_name', 'notification_type', 'count_sending', 'count_delivered',
|
||||||
|
'count_technical_failure', 'count_temporary_failure', 'count_permanent_failure', 'count_sent'
|
||||||
|
]
|
||||||
|
result = notification_api_client.get_notification_status_by_service(start_date, end_date)
|
||||||
|
|
||||||
|
for row in result:
|
||||||
|
row[0] = datetime.strptime(row[0], '%a, %d %b %Y %X %Z').strftime('%Y-%m-%d')
|
||||||
|
|
||||||
|
return Spreadsheet.from_rows([headers] + result).as_csv_data, 200, {
|
||||||
|
'Content-Type': 'text/csv; charset=utf-8',
|
||||||
|
'Content-Disposition': 'attachment; filename="{} to {} notification status per service report.csv"'.format(
|
||||||
|
start_date, end_date)
|
||||||
|
}
|
||||||
|
|
||||||
|
return render_template('views/platform-admin/notifications_by_service.html', form=form)
|
||||||
|
|
||||||
|
|
||||||
@main.route("/platform-admin/complaints")
|
@main.route("/platform-admin/complaints")
|
||||||
@user_is_platform_admin
|
@user_is_platform_admin
|
||||||
def platform_admin_list_complaints():
|
def platform_admin_list_complaints():
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ class HeaderNavigation(Navigation):
|
|||||||
'letter_branding',
|
'letter_branding',
|
||||||
'live_services',
|
'live_services',
|
||||||
'live_services_csv',
|
'live_services_csv',
|
||||||
|
'notifications_sent_by_service',
|
||||||
'performance_platform_xlsx',
|
'performance_platform_xlsx',
|
||||||
'platform_admin',
|
'platform_admin',
|
||||||
'platform_admin_letter_validation_preview',
|
'platform_admin_letter_validation_preview',
|
||||||
@@ -499,6 +500,7 @@ class MainNavigation(Navigation):
|
|||||||
'message_status',
|
'message_status',
|
||||||
'manage_org_users',
|
'manage_org_users',
|
||||||
'new_password',
|
'new_password',
|
||||||
|
'notifications_sent_by_service',
|
||||||
'old_integration_testing',
|
'old_integration_testing',
|
||||||
'old_roadmap',
|
'old_roadmap',
|
||||||
'old_service_dashboard',
|
'old_service_dashboard',
|
||||||
@@ -721,6 +723,7 @@ class CaseworkNavigation(Navigation):
|
|||||||
'message_status',
|
'message_status',
|
||||||
'monthly',
|
'monthly',
|
||||||
'new_password',
|
'new_password',
|
||||||
|
'notifications_sent_by_service',
|
||||||
'old_integration_testing',
|
'old_integration_testing',
|
||||||
'old_roadmap',
|
'old_roadmap',
|
||||||
'old_service_dashboard',
|
'old_service_dashboard',
|
||||||
@@ -993,6 +996,7 @@ class OrgNavigation(Navigation):
|
|||||||
'message_status',
|
'message_status',
|
||||||
'monthly',
|
'monthly',
|
||||||
'new_password',
|
'new_password',
|
||||||
|
'notifications_sent_by_service',
|
||||||
'old_integration_testing',
|
'old_integration_testing',
|
||||||
'old_roadmap',
|
'old_roadmap',
|
||||||
'old_service_dashboard',
|
'old_service_dashboard',
|
||||||
|
|||||||
@@ -110,5 +110,14 @@ class NotificationApiClient(NotifyAdminAPIClient):
|
|||||||
url='/service/{}/notifications/{}/cancel'.format(service_id, notification_id),
|
url='/service/{}/notifications/{}/cancel'.format(service_id, notification_id),
|
||||||
data={})
|
data={})
|
||||||
|
|
||||||
|
def get_notification_status_by_service(self, start_date, end_date):
|
||||||
|
return self.get(
|
||||||
|
url='service/monthly-data-by-service',
|
||||||
|
params={
|
||||||
|
'start_date': str(start_date),
|
||||||
|
'end_date': str(end_date),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
notification_api_client = NotificationApiClient()
|
notification_api_client = NotificationApiClient()
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{% extends "views/platform-admin/_base_template.html" %}
|
||||||
|
{% from "components/form.html" import form_wrapper %}
|
||||||
|
|
||||||
|
{% block per_page_title %}
|
||||||
|
Monthly notification statuses for live services
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block platform_admin_content %}
|
||||||
|
|
||||||
|
<h1 class="heading-large">
|
||||||
|
Monthly notification statuses for live services
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
{% call form_wrapper() %}
|
||||||
|
{{ textbox(form.start_date, hint="Enter start date in format YYYY-MM-DD") }}
|
||||||
|
{{ textbox(form.end_date, hint="Enter end date in format YYYY-MM-DD") }}
|
||||||
|
{{ page_footer('Download report') }}
|
||||||
|
{% endcall %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@@ -17,4 +17,8 @@
|
|||||||
<p>
|
<p>
|
||||||
<a target="_blank" href="{{ url_for('main.performance_platform_xlsx') }}">Download performance platform report (.xlsx)</a>
|
<a target="_blank" href="{{ url_for('main.performance_platform_xlsx') }}">Download performance platform report (.xlsx)</a>
|
||||||
<p>
|
<p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="{{ url_for('main.notifications_sent_by_service') }}">Monthly notification statuses for live services</a>
|
||||||
|
<p>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -19,7 +19,12 @@ from app.main.views.platform_admin import (
|
|||||||
sum_service_usage,
|
sum_service_usage,
|
||||||
)
|
)
|
||||||
from tests import service_json
|
from tests import service_json
|
||||||
from tests.conftest import SERVICE_ONE_ID, mock_get_user, normalize_spaces
|
from tests.conftest import (
|
||||||
|
SERVICE_ONE_ID,
|
||||||
|
SERVICE_TWO_ID,
|
||||||
|
mock_get_user,
|
||||||
|
normalize_spaces,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('endpoint', [
|
@pytest.mark.parametrize('endpoint', [
|
||||||
@@ -968,6 +973,10 @@ def test_reports_page(
|
|||||||
'a', text="Download performance platform report (.xlsx)"
|
'a', text="Download performance platform report (.xlsx)"
|
||||||
).attrs['href'] == '/platform-admin/reports/performance-platform.xlsx'
|
).attrs['href'] == '/platform-admin/reports/performance-platform.xlsx'
|
||||||
|
|
||||||
|
assert page.find(
|
||||||
|
'a', text="Monthly notification statuses for live services"
|
||||||
|
).attrs['href'] == url_for('main.notifications_sent_by_service')
|
||||||
|
|
||||||
|
|
||||||
def test_get_live_services_report(client, platform_admin_user, mocker):
|
def test_get_live_services_report(client, platform_admin_user, mocker):
|
||||||
mock_get_user(mocker, user=platform_admin_user)
|
mock_get_user(mocker, user=platform_admin_user)
|
||||||
@@ -1034,3 +1043,76 @@ def test_get_performance_platform_report(client, platform_admin_user, mocker):
|
|||||||
['abc123', 'Forest', 'jessie the oak tree', '2014-03-29T00:00:00Z', 'govuk-notify', 1],
|
['abc123', 'Forest', 'jessie the oak tree', '2014-03-29T00:00:00Z', 'govuk-notify', 1],
|
||||||
['def456', 'Forest', 'james the pine tree', '', 'govuk-notify', 1],
|
['def456', 'Forest', 'james the pine tree', '', 'govuk-notify', 1],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_notifications_sent_by_service_shows_date_form(client_request, platform_admin_user):
|
||||||
|
client_request.login(platform_admin_user)
|
||||||
|
page = client_request.get('main.notifications_sent_by_service')
|
||||||
|
|
||||||
|
assert [
|
||||||
|
(input['type'], input['name'], input['value'])
|
||||||
|
for input in page.select('input')
|
||||||
|
] == [
|
||||||
|
('text', 'start_date', ''),
|
||||||
|
('text', 'end_date', ''),
|
||||||
|
('hidden', 'csrf_token', ANY)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_notifications_sent_by_service_validates_form(mocker, client_request, platform_admin_user):
|
||||||
|
mock_get_stats_from_api = mocker.patch('app.main.views.platform_admin.notification_api_client')
|
||||||
|
|
||||||
|
client_request.login(platform_admin_user)
|
||||||
|
|
||||||
|
page = client_request.post(
|
||||||
|
'main.notifications_sent_by_service',
|
||||||
|
_expected_status=200,
|
||||||
|
_data={'start_date': '', 'end_date': '20190101'}
|
||||||
|
)
|
||||||
|
|
||||||
|
errors = page.select('.error-message')
|
||||||
|
assert len(errors) == 2
|
||||||
|
|
||||||
|
for error in errors:
|
||||||
|
assert normalize_spaces(error.text) == 'Not a valid date value'
|
||||||
|
|
||||||
|
mock_get_stats_from_api.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_notifications_sent_by_service_calls_api_and_downloads_data(
|
||||||
|
mocker,
|
||||||
|
client,
|
||||||
|
platform_admin_user,
|
||||||
|
service_one,
|
||||||
|
service_two,
|
||||||
|
):
|
||||||
|
mock_get_user(mocker, user=platform_admin_user)
|
||||||
|
api_data = [
|
||||||
|
['Tue, 01 Jan 2019 00:00:00 GMT', SERVICE_ONE_ID, service_one['name'], 'email', 191, 0, 0, 14, 0, 0],
|
||||||
|
['Tue, 01 Jan 2019 00:00:00 GMT', SERVICE_ONE_ID, service_one['name'], 'sms', 42, 0, 0, 8, 0, 0],
|
||||||
|
['Tue, 01 Jan 2019 00:00:00 GMT', SERVICE_TWO_ID, service_two['name'], 'email', 3, 1, 0, 2, 0, 0],
|
||||||
|
]
|
||||||
|
mocker.patch('app.main.views.platform_admin.notification_api_client.get_notification_status_by_service',
|
||||||
|
return_value=api_data)
|
||||||
|
start_date = datetime.date(2019, 1, 1)
|
||||||
|
end_date = datetime.date(2019, 1, 31)
|
||||||
|
|
||||||
|
client.login(platform_admin_user)
|
||||||
|
|
||||||
|
response = client.post(
|
||||||
|
url_for('main.notifications_sent_by_service'),
|
||||||
|
data={'start_date': start_date, 'end_date': end_date}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.content_type == 'text/csv; charset=utf-8'
|
||||||
|
assert response.headers['Content-Disposition'] == (
|
||||||
|
'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'
|
||||||
|
'2019-01-01,147ad62a-2951-4fa1-9ca0-093cd1a52c52,service two,email,3,1,0,2,0,0\r\n'
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import uuid
|
import uuid
|
||||||
|
from datetime import date
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -6,6 +7,7 @@ import werkzeug
|
|||||||
|
|
||||||
from app.models.service import Service
|
from app.models.service import Service
|
||||||
from app.notify_client import NotifyAdminAPIClient
|
from app.notify_client import NotifyAdminAPIClient
|
||||||
|
from app.notify_client.notification_api_client import notification_api_client
|
||||||
from tests import service_json
|
from tests import service_json
|
||||||
from tests.conftest import api_user_active, platform_admin_user, set_config
|
from tests.conftest import api_user_active, platform_admin_user, set_config
|
||||||
|
|
||||||
@@ -102,3 +104,16 @@ def test_generate_headers_sets_request_id_if_in_request_context(app_):
|
|||||||
}
|
}
|
||||||
assert headers['X-B3-TraceId'] == request_context.request.request_id
|
assert headers['X-B3-TraceId'] == request_context.request.request_id
|
||||||
assert headers['X-B3-SpanId'] == request_context.request.span_id
|
assert headers['X-B3-SpanId'] == request_context.request.span_id
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_notification_status_by_service(mocker):
|
||||||
|
mock_get = mocker.patch.object(notification_api_client, 'get')
|
||||||
|
start_date = date(2019, 4, 1)
|
||||||
|
end_date = date(2019, 4, 30)
|
||||||
|
|
||||||
|
notification_api_client.get_notification_status_by_service(start_date, end_date)
|
||||||
|
|
||||||
|
mock_get.assert_called_once_with(
|
||||||
|
url='service/monthly-data-by-service',
|
||||||
|
params={'start_date': '2019-04-01', 'end_date': '2019-04-30'}
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user