From aa85870ee1093a697a13ee07f7f8c33d33dba662 Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Fri, 13 Mar 2020 16:58:43 +0000 Subject: [PATCH] Add a page to view a single contact list --- app/main/views/uploads.py | 5 +- app/models/contact_list.py | 29 ++++++++++++ app/notify_client/contact_list_api_client.py | 3 ++ .../uploads/contact-list/contact-list.html | 47 +++++++++++++++++++ tests/app/main/views/test_uploads.py | 44 ++++++++++++++++- tests/conftest.py | 27 +++++++++++ 6 files changed, 152 insertions(+), 3 deletions(-) create mode 100644 app/templates/views/uploads/contact-list/contact-list.html diff --git a/app/main/views/uploads.py b/app/main/views/uploads.py index ba53d0ec6..b3a1021ef 100644 --- a/app/main/views/uploads.py +++ b/app/main/views/uploads.py @@ -433,4 +433,7 @@ def save_contact_list(service_id, upload_id): @main.route("/services//contact-list/", methods=['GET']) @user_has_permissions('send_messages') def contact_list(service_id, contact_list_id): - return 'page for contact list {}'.format(contact_list_id) + return render_template( + 'views/uploads/contact-list/contact-list.html', + contact_list=ContactList.from_id(contact_list_id, service_id=service_id), + ) diff --git a/app/models/contact_list.py b/app/models/contact_list.py index db20313f3..f61ee1861 100644 --- a/app/models/contact_list.py +++ b/app/models/contact_list.py @@ -1,5 +1,7 @@ from flask import abort, current_app from notifications_utils.formatters import strip_whitespace +from notifications_utils.recipients import RecipientCSV +from werkzeug.utils import cached_property from app.models import JSONModel from app.notify_client.contact_list_api_client import contact_list_api_client @@ -13,6 +15,24 @@ from app.s3_client.s3_csv_client import ( class ContactList(JSONModel): + ALLOWED_PROPERTIES = { + 'id', + 'created_at', + 'created_by', + 'service_id', + 'original_file_name', + 'row_count', + 'template_type', + } + + @classmethod + def from_id(cls, contact_list_id, *, service_id): + # This is temporary until we have a get single list endpoint + for contact_list in contact_list_api_client.get_contact_lists(service_id): + if contact_list['id'] == contact_list_id: + return cls(contact_list) + abort(404) + @staticmethod def get_bucket_name(): return current_app.config['CONTACT_LIST_UPLOAD_BUCKET_NAME'] @@ -82,3 +102,12 @@ class ContactList(JSONModel): row_count=int(metadata['row_count']), template_type=metadata['template_type'], )) + + @cached_property + def recipients(self): + return RecipientCSV( + self.download(self.service_id, self.id), + template_type=self.template_type, + international_sms=True, + max_initial_rows_shown=50, + ) diff --git a/app/notify_client/contact_list_api_client.py b/app/notify_client/contact_list_api_client.py index 67f55950f..9062addf0 100644 --- a/app/notify_client/contact_list_api_client.py +++ b/app/notify_client/contact_list_api_client.py @@ -24,5 +24,8 @@ class ContactListApiClient(NotifyAdminAPIClient): return job + def get_contact_lists(self, service_id): + return self.get('/service/{}/contact-list'.format(service_id)) + contact_list_api_client = ContactListApiClient() diff --git a/app/templates/views/uploads/contact-list/contact-list.html b/app/templates/views/uploads/contact-list/contact-list.html new file mode 100644 index 000000000..f674e99ed --- /dev/null +++ b/app/templates/views/uploads/contact-list/contact-list.html @@ -0,0 +1,47 @@ +{% extends "withnav_template.html" %} +{% from "components/banner.html" import banner_wrapper %} +{% from "components/radios.html" import radio_select %} +{% from "components/table.html" import list_table, field, text_field, index_field, hidden_field_heading %} +{% from "components/page-header.html" import page_header %} +{% from "components/message-count-label.html" import message_count_label, recipient_count_label %} +{% from "components/button/macro.njk" import govukButton %} + +{% block service_page_title %} + {{ contact_list.original_file_name }} +{% endblock %} + +{% block maincolumn_content %} + + {{ page_header( + contact_list.original_file_name, + back_link=url_for('main.uploads', service_id=current_service.id) + ) }} + +

+ Uploaded by {{ contact_list.created_by }} {{ contact_list.created_at|format_datetime_human }} +

+ +

+ {{ contact_list.recipients|length|format_thousands }} + {{ recipient_count_label(contact_list.recipients|length, contact_list.recipients.template_type) }} +

+ + {% set recipient_column = contact_list.recipients.column_headers[0] %} + + {% call(item, row_number) list_table( + contact_list.recipients.displayed_rows, + caption=recipient_count_label(contact_list.recipients|length, contact_list.template_type)|capitalize, + caption_visible=False, + field_headings=['1', recipient_column], + ) %} + {{ index_field(row_number) }} + {{ text_field(item[recipient_column].data) }} + {% endcall %} + + {% if contact_list.recipients.displayed_rows|list|length < contact_list.recipients|length %} + + {% endif %} + +{% endblock %} diff --git a/tests/app/main/views/test_uploads.py b/tests/app/main/views/test_uploads.py index de794df60..ecc739d02 100644 --- a/tests/app/main/views/test_uploads.py +++ b/tests/app/main/views/test_uploads.py @@ -1,5 +1,6 @@ import re import urllib +import uuid from io import BytesIO from unittest.mock import ANY, Mock @@ -1103,14 +1104,53 @@ def test_cant_save_bad_contact_list( assert mock_create_contact_list.called is False +@freeze_time('2020-03-13 16:51:56') def test_view_contact_list( + mocker, client_request, + mock_get_contact_lists, fake_uuid, ): + mocker.patch('app.models.contact_list.s3download', return_value='\n'.join( + ['email address'] + ['test@example.com'] * 51 + )) page = client_request.get( 'main.contact_list', service_id=SERVICE_ONE_ID, contact_list_id=fake_uuid, - _test_page_title=False, ) - assert page.text == 'page for contact list 6ce466d0-fd6a-11e5-82f5-e0accb9d11a6' + assert normalize_spaces(page.select_one('h1').text) == ( + 'EmergencyContactList.xls' + ) + assert normalize_spaces(page.select('main p')[0].text) == ( + 'Uploaded by Test User today at 10:59am' + ) + assert normalize_spaces(page.select('main p')[1].text) == ( + '51 email addresses' + ) + assert normalize_spaces(page.select_one('table').text).startswith( + 'Email addresses ' + '1 email address ' + '2 test@example.com ' + '3 test@example.com ' + ) + assert normalize_spaces(page.select_one('table').text).endswith( + '50 test@example.com ' + '51 test@example.com' + ) + assert normalize_spaces(page.select_one('.table-show-more-link').text) == ( + 'Only showing the first 50 rows' + ) + + +def test_view_contact_list_404s_for_non_existing_list( + client_request, + mock_get_contact_lists, + fake_uuid, +): + client_request.get( + 'main.contact_list', + service_id=SERVICE_ONE_ID, + contact_list_id=uuid.uuid4(), + _expected_status=404, + ) diff --git a/tests/conftest.py b/tests/conftest.py index d44aa12eb..9874c4c0b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1842,6 +1842,33 @@ def mock_create_contact_list(mocker, api_user_active): ) +@pytest.fixture(scope='function') +def mock_get_contact_lists(mocker, api_user_active, fake_uuid): + def _get(service_id): + return [{ + 'created_at': '2020-03-13 10:59:56', + 'created_by': 'Test User', + 'id': fake_uuid, + 'original_file_name': 'EmergencyContactList.xls', + 'row_count': 100, + 'service_id': service_id, + 'template_type': 'email', + }, { + 'created_at': '2020-03-13 13:00:00', + 'created_by': 'Test User', + 'id': uuid4(), + 'original_file_name': 'another ist.csv', + 'row_count': 123, + 'service_id': service_id, + 'template_type': 'sms', + }] + + return mocker.patch( + 'app.contact_list_api_client.get_contact_lists', + side_effect=_get, + ) + + @pytest.fixture(scope='function') def mock_get_notifications( mocker,