From 84445d154dbd87620ee863fe2b234e5ee30298d2 Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Wed, 6 Jun 2018 15:22:48 +0100 Subject: [PATCH] When someone complains about an email from the platform we get a callback from SES. A new platform admin page Email complaints has been added to surface those complaints. Eventually the complaints will be visible to the services so they can remove the email address from their mailing list. Next thing to implement is "x email complaints" warning on the platform admin summary page. --- app/__init__.py | 3 ++ app/main/views/platform_admin.py | 14 +++++++- app/navigation.py | 3 ++ app/notify_client/complaint_api_client.py | 11 ++++++ .../views/platform-admin/_base_template.html | 3 +- .../views/platform-admin/complaints.html | 36 +++++++++++++++++++ tests/app/main/views/test_platform_admin.py | 30 ++++++++++++++++ .../notify_client/test_compliant_client.py | 10 ++++++ 8 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 app/notify_client/complaint_api_client.py create mode 100644 app/templates/views/platform-admin/complaints.html create mode 100644 tests/app/notify_client/test_compliant_client.py diff --git a/app/__init__.py b/app/__init__.py index 376c976c5..5b37c57bb 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -58,6 +58,7 @@ from app.notify_client.org_invite_api_client import OrgInviteApiClient from app.notify_client.letter_jobs_client import LetterJobsClient from app.notify_client.inbound_number_client import InboundNumberClient from app.notify_client.billing_api_client import BillingAPIClient +from app.notify_client.complaint_api_client import ComplaintApiClient from app.commands import setup_commands from app.utils import get_cdn_domain from app.utils import gmt_timezones @@ -84,6 +85,7 @@ zendesk_client = ZendeskClient() letter_jobs_client = LetterJobsClient() inbound_number_client = InboundNumberClient() billing_api_client = BillingAPIClient() +complaint_api_client = ComplaintApiClient() # The current service attached to the request stack. current_service = LocalProxy(partial(_lookup_req_object, 'service')) @@ -128,6 +130,7 @@ def create_app(application): letter_jobs_client.init_app(application) inbound_number_client.init_app(application) billing_api_client.init_app(application) + complaint_api_client.init_app(application) login_manager.init_app(application) login_manager.login_view = 'main.sign_in' diff --git a/app/main/views/platform_admin.py b/app/main/views/platform_admin.py index 872a85095..6321d862d 100644 --- a/app/main/views/platform_admin.py +++ b/app/main/views/platform_admin.py @@ -4,7 +4,7 @@ from datetime import datetime from flask import render_template, request from flask_login import login_required -from app import service_api_client +from app import complaint_api_client, service_api_client from app.main import main from app.main.forms import DateFilterForm from app.statistics_utils import get_formatted_percentage @@ -70,6 +70,18 @@ def platform_admin_services(): ) +@main.route("/platform-admin/complaints") +@login_required +@user_is_platform_admin +def platform_admin_list_complaints(): + complaints = complaint_api_client.get_all_complaints() + return render_template( + 'views/platform-admin/complaints.html', + complaints=complaints, + page_title='All Complaints', + ) + + def sum_service_usage(service): total = 0 for notification_type in service['statistics'].keys(): diff --git a/app/navigation.py b/app/navigation.py index d724d2bfa..7f223265d 100644 --- a/app/navigation.py +++ b/app/navigation.py @@ -84,6 +84,7 @@ class HeaderNavigation(Navigation): 'update_email_branding', 'view_provider', 'view_providers', + 'platform_admin_list_complaints', }, 'sign-in': { 'sign_in', @@ -418,6 +419,7 @@ class MainNavigation(Navigation): 'organisation_settings', 'organisations', 'platform_admin', + 'platform_admin_list_complaints', 'pricing', 'privacy', 'public_agreement', @@ -586,6 +588,7 @@ class OrgNavigation(Navigation): 'old_using_notify', 'organisations', 'platform_admin', + 'platform_admin_list_complaints', 'pricing', 'privacy', 'public_agreement', diff --git a/app/notify_client/complaint_api_client.py b/app/notify_client/complaint_api_client.py new file mode 100644 index 000000000..6e657396c --- /dev/null +++ b/app/notify_client/complaint_api_client.py @@ -0,0 +1,11 @@ +from app.notify_client import NotifyAdminAPIClient + + +class ComplaintApiClient(NotifyAdminAPIClient): + # Fudge assert in the super __init__ so + # we can set those variables later. + def __init__(self): + super().__init__("a" * 73, "b") + + def get_all_complaints(self): + return self.get('/complaint') diff --git a/app/templates/views/platform-admin/_base_template.html b/app/templates/views/platform-admin/_base_template.html index 786a79dfb..4434534f0 100644 --- a/app/templates/views/platform-admin/_base_template.html +++ b/app/templates/views/platform-admin/_base_template.html @@ -19,7 +19,8 @@ ('Providers', url_for('main.view_providers')), ('Email branding', url_for('main.email_branding')), ('Letter jobs', url_for('main.letter_jobs')), - ('Inbound SMS numbers', url_for('main.inbound_sms_admin')) + ('Inbound SMS numbers', url_for('main.inbound_sms_admin')), + ('Email Complaints', url_for('main.platform_admin_list_complaints')) ] %}
  • diff --git a/app/templates/views/platform-admin/complaints.html b/app/templates/views/platform-admin/complaints.html new file mode 100644 index 000000000..ff7c90a4a --- /dev/null +++ b/app/templates/views/platform-admin/complaints.html @@ -0,0 +1,36 @@ +{% extends "views/platform-admin/_base_template.html" %} +{% from "components/page-footer.html" import page_footer %} +{% from "components/table.html" import list_table, field, text_field, link_field, right_aligned_field_heading, hidden_field_heading %} + +{% block per_page_title %} + {{ page_title|capitalize }} +{% endblock %} + +{% block platform_admin_content %} + +

    + Email complaints +

    + + + {% call(item, row_number) list_table( + complaints, + caption="Complaints", + caption_visible=False, + empty_message='No complaints', + field_headings=['Notification Id', 'Service', 'Complaint type', 'Complaint Date'], + field_headings_visible=True + ) %} + + {{ link_field(item.notification_id, url_for('main.view_notification', service_id=item.service_id, notification_id=item.notification_id)) }} + + {{ link_field(item.service_name, url_for('main.service_dashboard', service_id=item.service_id)) }} + + {{ text_field(item.complaint_type) }} + + {{ text_field(item.complaint_date|format_datetime_short) }} + + {% endcall %} + + +{% endblock %} diff --git a/tests/app/main/views/test_platform_admin.py b/tests/app/main/views/test_platform_admin.py index f475a1cac..3749a2a0f 100644 --- a/tests/app/main/views/test_platform_admin.py +++ b/tests/app/main/views/test_platform_admin.py @@ -1,4 +1,5 @@ import datetime +import uuid from unittest.mock import ANY import pytest @@ -637,3 +638,32 @@ def test_sum_service_usage_with_zeros(fake_uuid): sms_failed=0 ) assert sum_service_usage(service) == 0 + + +def test_platform_admin_list_complaints( + client, + platform_admin_user, + mocker +): + mock_get_user(mocker, user=platform_admin_user) + client.login(platform_admin_user) + complaint = { + 'id': str(uuid.uuid4()), + 'notification_id': str(uuid.uuid4()), + 'service_id': str(uuid.uuid4()), + 'service_name': 'Sample service', + 'ses_feedback_id': 'Some ses id', + 'complaint_type': 'abuse', + 'complaint_date': '2018-06-05T13:50:30.012354', + 'created_at': '2018-06-05T13:50:30.012354', + } + mock = mocker.patch('app.complaint_api_client.get_all_complaints', + return_value=[complaint]) + + client.login(platform_admin_user) + response = client.get(url_for('main.platform_admin_list_complaints')) + + assert response.status_code == 200 + resp_data = response.get_data(as_text=True) + assert 'Email complaints' in resp_data + mock.assert_called_once() diff --git a/tests/app/notify_client/test_compliant_client.py b/tests/app/notify_client/test_compliant_client.py new file mode 100644 index 000000000..9d05d1490 --- /dev/null +++ b/tests/app/notify_client/test_compliant_client.py @@ -0,0 +1,10 @@ +from app import ComplaintApiClient + + +def test_get_all_complaints(mocker): + client = ComplaintApiClient() + + mock = mocker.patch('app.notify_client.complaint_api_client.ComplaintApiClient.get') + + client.get_all_complaints() + mock.assert_called_once_with('/complaint')