mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-12 18:08:57 -04:00
Merge pull request #3233 from alphagov/returned-letters
Returned letters
This commit is contained in:
@@ -85,6 +85,7 @@ from app.notify_client.template_statistics_api_client import (
|
||||
from app.notify_client.user_api_client import user_api_client
|
||||
from app.url_converters import (
|
||||
LetterFileExtensionConverter,
|
||||
SimpleDateTypeConverter,
|
||||
TemplateTypeConverter,
|
||||
)
|
||||
from app.utils import format_thousands, get_logo_cdn_domain, id_safe
|
||||
@@ -221,6 +222,7 @@ def init_app(application):
|
||||
application.url_map.converters['uuid'].to_python = lambda self, value: value
|
||||
application.url_map.converters['template_type'] = TemplateTypeConverter
|
||||
application.url_map.converters['letter_file_extension'] = LetterFileExtensionConverter
|
||||
application.url_map.converters['simple_date'] = SimpleDateTypeConverter
|
||||
|
||||
|
||||
def convert_to_boolean(value):
|
||||
|
||||
@@ -29,6 +29,7 @@ from app.main.views import ( # noqa isort:skip
|
||||
platform_admin,
|
||||
providers,
|
||||
register,
|
||||
returned_letters,
|
||||
send,
|
||||
service_settings,
|
||||
sign_in,
|
||||
|
||||
46
app/main/views/returned_letters.py
Normal file
46
app/main/views/returned_letters.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from collections import OrderedDict
|
||||
|
||||
from flask import render_template
|
||||
|
||||
from app import service_api_client
|
||||
from app.main import main
|
||||
from app.utils import Spreadsheet, user_has_permissions
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/returned-letter-summary", methods=["GET"])
|
||||
@user_has_permissions('view_activity')
|
||||
def returned_letter_summary(service_id):
|
||||
summary = service_api_client.get_returned_letter_summary(service_id)
|
||||
return render_template(
|
||||
'views/returned-letter-summary.html',
|
||||
data=summary,
|
||||
)
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/returned-letters-csv/<simple_date:reported_at>", methods=["GET"])
|
||||
@user_has_permissions('view_activity')
|
||||
def returned_letters_report(service_id, reported_at):
|
||||
returned_letters = service_api_client.get_returned_letters(service_id, reported_at)
|
||||
column_names = OrderedDict([
|
||||
('notification_id', 'Notification ID'),
|
||||
('client_reference', 'Reference'),
|
||||
('created_at', 'Date sent'),
|
||||
('email_address', 'Sent by'),
|
||||
('template_name', 'Template name'),
|
||||
('template_id', 'Template ID'),
|
||||
('template_version', 'Template version'),
|
||||
('original_file_name', 'Spreadsheet file name'),
|
||||
('job_row_number', 'Spreadsheet row number'),
|
||||
('uploaded_letter_file_name', 'Uploaded letter file name')
|
||||
])
|
||||
|
||||
# initialise with header row
|
||||
data = [[x for x in column_names.values()]]
|
||||
|
||||
for row in returned_letters:
|
||||
data.append([row[key] for key in column_names.keys()])
|
||||
|
||||
return Spreadsheet.from_rows(data).as_csv_data, 200, {
|
||||
'Content-Type': 'text/csv; charset=utf-8',
|
||||
'Content-Disposition': 'inline; filename="{} returned letters.csv"'.format(reported_at)
|
||||
}
|
||||
@@ -242,6 +242,8 @@ class HeaderNavigation(Navigation):
|
||||
'resend_email_link',
|
||||
'resend_email_verification',
|
||||
'resume_service',
|
||||
'returned_letter_summary',
|
||||
'returned_letters_report',
|
||||
'revoke_api_key',
|
||||
'robots',
|
||||
'send_messages',
|
||||
@@ -564,6 +566,8 @@ class MainNavigation(Navigation):
|
||||
'resend_email_link',
|
||||
'resend_email_verification',
|
||||
'resume_service',
|
||||
'returned_letter_summary',
|
||||
'returned_letters_report',
|
||||
'roadmap',
|
||||
'robots',
|
||||
'security',
|
||||
@@ -801,6 +805,8 @@ class CaseworkNavigation(Navigation):
|
||||
'resend_email_link',
|
||||
'resend_email_verification',
|
||||
'resume_service',
|
||||
'returned_letter_summary',
|
||||
'returned_letters_report',
|
||||
'revoke_api_key',
|
||||
'roadmap',
|
||||
'robots',
|
||||
@@ -1077,6 +1083,8 @@ class OrgNavigation(Navigation):
|
||||
'resend_email_link',
|
||||
'resend_email_verification',
|
||||
'resume_service',
|
||||
'returned_letter_summary',
|
||||
'returned_letters_report',
|
||||
'revoke_api_key',
|
||||
'roadmap',
|
||||
'robots',
|
||||
|
||||
@@ -569,5 +569,11 @@ class ServiceAPIClient(NotifyAdminAPIClient):
|
||||
def get_service_data_retention(self, service_id):
|
||||
return self.get("/service/{}/data-retention".format(service_id))
|
||||
|
||||
def get_returned_letter_summary(self, service_id):
|
||||
return self.get("service/{}/returned-letter-summary".format(service_id))
|
||||
|
||||
def get_returned_letters(self, service_id, reported_at):
|
||||
return self.get("service/{}/returned-letters?reported_at={}".format(service_id, reported_at))
|
||||
|
||||
|
||||
service_api_client = ServiceAPIClient()
|
||||
|
||||
27
app/templates/views/returned-letter-summary.html
Normal file
27
app/templates/views/returned-letter-summary.html
Normal file
@@ -0,0 +1,27 @@
|
||||
{% from "components/table.html" import list_table, field %}
|
||||
{% from "components/message-count-label.html" import message_count_label %}
|
||||
{% extends "withnav_template.html" %}
|
||||
|
||||
{% block service_page_title %}
|
||||
Returned letters
|
||||
{% endblock %}
|
||||
|
||||
{% block maincolumn_content %}
|
||||
<h1 class="heading-large">
|
||||
Returned letters
|
||||
</h1>
|
||||
<div class="body-copy-table" id='pill-selected-item'>
|
||||
{% call(item, row_number) list_table(
|
||||
data,
|
||||
caption="Returned letters report",
|
||||
caption_visible=False,
|
||||
empty_message='If you have returned letter reports they will be listed here'
|
||||
) %}
|
||||
{% call field() %}
|
||||
<a target="_blank" class="govuk_link"
|
||||
href="{{url_for('.returned_letters_report', service_id=current_service.id, reported_at=item.reported_at)}}">Returned letters reported on {{ item.reported_at | format_date}} - {{ item.returned_letter_count}} {{ message_count_label(item.returned_letter_count, 'letter', suffix='')}}</a>
|
||||
{% endcall %}
|
||||
{% endcall %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -9,3 +9,7 @@ class TemplateTypeConverter(BaseConverter):
|
||||
class LetterFileExtensionConverter(BaseConverter):
|
||||
|
||||
regex = '(?:pdf|png)'
|
||||
|
||||
|
||||
class SimpleDateTypeConverter(BaseConverter):
|
||||
regex = r'([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))'
|
||||
|
||||
84
tests/app/main/views/test_returned_letters.py
Normal file
84
tests/app/main/views/test_returned_letters.py
Normal file
@@ -0,0 +1,84 @@
|
||||
from flask import url_for
|
||||
|
||||
from tests.conftest import SERVICE_ONE_ID, normalize_spaces
|
||||
|
||||
|
||||
def test_returned_letter_summary(
|
||||
client_request,
|
||||
mocker
|
||||
):
|
||||
summary_data = [{'returned_letter_count': 30, 'reported_at': '2019-12-24'}]
|
||||
mock = mocker.patch("app.service_api_client.get_returned_letter_summary",
|
||||
return_value=summary_data)
|
||||
|
||||
page = client_request.get("main.returned_letter_summary", service_id=SERVICE_ONE_ID)
|
||||
|
||||
mock.assert_called_once_with(SERVICE_ONE_ID)
|
||||
|
||||
expected_text = "Returned letters reported on Tuesday 24 December 2019 - 30 letters"
|
||||
assert page.h1.string.strip() == 'Returned letters'
|
||||
assert normalize_spaces(page.select('.table-field-left-aligned')[0].text) == expected_text
|
||||
assert page.select_one('.table-field-left-aligned a')['href'] == url_for('.returned_letters_report',
|
||||
service_id=SERVICE_ONE_ID,
|
||||
reported_at='2019-12-24')
|
||||
|
||||
|
||||
def test_returned_letter_summary_with_one_letter(
|
||||
client_request,
|
||||
mocker
|
||||
):
|
||||
summary_data = [{'returned_letter_count': 1, 'reported_at': '2019-12-24'}]
|
||||
mock = mocker.patch("app.service_api_client.get_returned_letter_summary",
|
||||
return_value=summary_data)
|
||||
|
||||
page = client_request.get("main.returned_letter_summary", service_id=SERVICE_ONE_ID)
|
||||
|
||||
mock.assert_called_once_with(SERVICE_ONE_ID)
|
||||
|
||||
expected_text = "Returned letters reported on Tuesday 24 December 2019 - 1 letter"
|
||||
assert page.h1.string.strip() == 'Returned letters'
|
||||
assert normalize_spaces(page.select('.table-field-left-aligned')[0].text) == expected_text
|
||||
|
||||
|
||||
def test_returned_letters_reports(
|
||||
client_request,
|
||||
mocker
|
||||
):
|
||||
data = [{
|
||||
'notification_id': '12345678',
|
||||
'client_reference': '2344567',
|
||||
'created_at': '2019-12-24 13:30',
|
||||
'email_address': 'test@gov.uk',
|
||||
'template_name': 'First letter template',
|
||||
'template_id': '3445667',
|
||||
'template_version': 2,
|
||||
'original_file_name': None,
|
||||
'job_row_number': None,
|
||||
'uploaded_letter_file_name': 'test_letter.pdf',
|
||||
}]
|
||||
mock = mocker.patch("app.service_api_client.get_returned_letters", return_value=data)
|
||||
|
||||
response = client_request.get_response("main.returned_letters_report",
|
||||
service_id=SERVICE_ONE_ID,
|
||||
reported_at='2019-12-24')
|
||||
|
||||
report = response.get_data(as_text=True)
|
||||
mock.assert_called_once_with(SERVICE_ONE_ID, '2019-12-24')
|
||||
assert report.strip() == (
|
||||
'Notification ID,Reference,Date sent,Sent by,Template name,Template ID,Template version,'
|
||||
+ 'Spreadsheet file name,Spreadsheet row number,Uploaded letter file name\r\n'
|
||||
+ '12345678,2344567,2019-12-24 13:30,test@gov.uk,'
|
||||
+ 'First letter template,3445667,2,,,test_letter.pdf'
|
||||
)
|
||||
|
||||
|
||||
def test_returned_letters_reports_returns_404_for_bad_date(
|
||||
client_request,
|
||||
mocker
|
||||
):
|
||||
mock = mocker.patch("app.service_api_client.get_returned_letters")
|
||||
client_request.get_response("main.returned_letters_report",
|
||||
service_id=SERVICE_ONE_ID,
|
||||
reported_at='19-12-2019',
|
||||
_expected_status=404)
|
||||
mock.assert_not_called()
|
||||
@@ -2921,6 +2921,19 @@ def client_request(
|
||||
assert_url_expected(resp.location, _expected_redirect)
|
||||
return BeautifulSoup(resp.data.decode('utf-8'), 'html.parser')
|
||||
|
||||
@staticmethod
|
||||
def get_response(
|
||||
endpoint,
|
||||
_expected_status=200,
|
||||
_optional_args="",
|
||||
**endpoint_kwargs
|
||||
):
|
||||
resp = logged_in_client.get(
|
||||
url_for(endpoint, **(endpoint_kwargs or {})) + _optional_args,
|
||||
)
|
||||
assert resp.status_code == _expected_status
|
||||
return resp
|
||||
|
||||
return ClientRequest
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user