diff --git a/app/__init__.py b/app/__init__.py index 3f3a1b6e0..f06598b62 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -29,6 +29,8 @@ from app.notify_client.notification_api_client import NotificationApiClient from app.notify_client.status_api_client import StatusApiClient from app.notify_client.invite_api_client import InviteApiClient from app.notify_client.statistics_api_client import StatisticsApiClient +from app.notify_client.template_statistics_api_client import TemplateStatisticsApiClient + from app.its_dangerous_session import ItsdangerousSessionInterface from app.asset_fingerprinter import AssetFingerprinter from utils.recipients import validate_phone_number, InvalidPhoneError @@ -51,6 +53,7 @@ notification_api_client = NotificationApiClient() status_api_client = StatusApiClient() invite_api_client = InviteApiClient() statistics_api_client = StatisticsApiClient() +template_statistics_client = TemplateStatisticsApiClient() asset_fingerprinter = AssetFingerprinter() # The current service attached to the request stack. @@ -74,6 +77,7 @@ def create_app(): status_api_client.init_app(application) invite_api_client.init_app(application) statistics_api_client.init_app(application) + template_statistics_client.init_app(application) login_manager.init_app(application) login_manager.login_view = 'main.sign_in' @@ -95,6 +99,7 @@ def create_app(): application.add_template_filter(syntax_highlight_json) application.add_template_filter(valid_phone_number) application.add_template_filter(linkable_name) + application.add_template_filter(format_date) application.after_request(useful_headers_after_request) application.after_request(save_service_after_request) @@ -175,6 +180,11 @@ def format_time(date): return native.strftime('%H:%M') +def format_date(date): + date = dateutil.parser.parse(date) + return date.strftime('%A %d %B %Y') + + def valid_phone_number(phone_number): try: validate_phone_number(phone_number) diff --git a/app/main/views/dashboard.py b/app/main/views/dashboard.py index 2fbb4c04f..14026ed16 100644 --- a/app/main/views/dashboard.py +++ b/app/main/views/dashboard.py @@ -1,15 +1,23 @@ +from datetime import date + from flask import ( render_template, session, flash, - jsonify, - request + jsonify ) -from datetime import date from flask_login import login_required + from app.main import main -from app import (job_api_client, statistics_api_client, service_api_client, current_service) +from app import ( + job_api_client, + statistics_api_client, + service_api_client, + template_statistics_client, + current_service +) + from app.utils import user_has_permissions @@ -27,6 +35,7 @@ def service_dashboard(service_id): flash(message, 'default_with_tick') statistics = statistics_api_client.get_statistics_for_service(service_id)['data'] + template_statistics = template_statistics_client.get_template_statistics_for_service(service_id) return render_template( 'views/dashboard/dashboard.html', @@ -36,7 +45,7 @@ def service_dashboard(service_id): spent_this_month='0.00', statistics=add_rates_to(statistics), templates=templates, - service_id=str(service_id)) + template_statistics=template_statistics) @main.route("/services//dashboard.json") @@ -44,11 +53,13 @@ def service_dashboard(service_id): def service_dashboard_updates(service_id): statistics = statistics_api_client.get_statistics_for_service(service_id)['data'] + template_statistics = template_statistics_client.get_template_statistics_for_service(service_id) return jsonify(**{ 'today': render_template( 'views/dashboard/today.html', statistics=add_rates_to(statistics), + template_statistics=template_statistics ) }) diff --git a/app/main/views/index.py b/app/main/views/index.py index 774400fe4..9b00d14e8 100644 --- a/app/main/views/index.py +++ b/app/main/views/index.py @@ -1,4 +1,4 @@ -from flask import render_template, url_for, redirect +from flask import render_template, url_for, redirect, jsonify from app.main import main from flask_login import login_required diff --git a/app/notify_client/template_statistics_api_client.py b/app/notify_client/template_statistics_api_client.py new file mode 100644 index 000000000..a47e934fb --- /dev/null +++ b/app/notify_client/template_statistics_api_client.py @@ -0,0 +1,18 @@ +from notifications_python_client.base import BaseAPIClient + + +class TemplateStatisticsApiClient(BaseAPIClient): + def __init__(self, base_url=None, client_id=None, secret=None): + super(self.__class__, self).__init__(base_url=base_url or 'base_url', + client_id=client_id or 'client_id', + secret=secret or 'secret') + + def init_app(self, app): + self.base_url = app.config['API_HOST_NAME'] + self.client_id = app.config['ADMIN_CLIENT_USER_NAME'] + self.secret = app.config['ADMIN_CLIENT_SECRET'] + + def get_template_statistics_for_service(self, service_id): + return self.get( + url='/service/{}/template-statistics'.format(service_id), + )['data'] diff --git a/app/templates/views/dashboard/dashboard.html b/app/templates/views/dashboard/dashboard.html index bd9c3149f..adabb0594 100644 --- a/app/templates/views/dashboard/dashboard.html +++ b/app/templates/views/dashboard/dashboard.html @@ -5,7 +5,7 @@ {% endblock %} {% block maincolumn_content %} - + {% if not templates and current_user.has_permissions(['send_texts', 'send_emails', 'send_letters'], any_=True) %} {% include 'views/dashboard/get-started.html' %} {% elif current_service.restricted %} @@ -22,8 +22,5 @@ > {% include 'views/dashboard/today.html' %} - - {% include 'views/dashboard/jobs.html' %} - {% endblock %} diff --git a/app/templates/views/dashboard/template-statistics.html b/app/templates/views/dashboard/template-statistics.html new file mode 100644 index 000000000..428269c96 --- /dev/null +++ b/app/templates/views/dashboard/template-statistics.html @@ -0,0 +1,20 @@ +{% from "components/table.html" import list_table, field, right_aligned_field_heading %} +{% call(item) list_table( + template_statistics, + caption="Recent templates used", + empty_message='You haven’t sent any batch messages yet', + field_headings=['Template', 'Type', right_aligned_field_heading('Usage')] +) %} + {% call field() %} + + {{ item.template.name }} + + {% endcall %} + {% call field() %} + {{item.template.template_type}} + {% endcall %} + {% call field(align='right') %} + {{ item.usage_count }} + {% endcall %} +{% endcall %} + diff --git a/app/templates/views/dashboard/today.html b/app/templates/views/dashboard/today.html index 2dc3e5f6f..1c4784fe5 100644 --- a/app/templates/views/dashboard/today.html +++ b/app/templates/views/dashboard/today.html @@ -23,3 +23,6 @@ ) }} + +{% include 'views/dashboard/template-statistics.html' %} + diff --git a/tests/app/main/notify_client/test_statistics_client.py b/tests/app/main/notify_client/test_statistics_client.py index 3dc24c16a..a4bcba8ce 100644 --- a/tests/app/main/notify_client/test_statistics_client.py +++ b/tests/app/main/notify_client/test_statistics_client.py @@ -1,13 +1,17 @@ +import uuid + from app.notify_client.statistics_api_client import StatisticsApiClient -def test_client_uses_correct_find_by_email(mocker, api_user_active): +def test_notifications_statistics_client_calls_correct_api_endpoint(mocker, api_user_active): - expected_url = '/service/a1b2c3d4/notifications-statistics' + some_service_id = uuid.uuid4() + expected_url = '/service/{}/notifications-statistics'.format(some_service_id) client = StatisticsApiClient() + mock_get = mocker.patch('app.notify_client.statistics_api_client.StatisticsApiClient.get') - client.get_statistics_for_service('a1b2c3d4') + client.get_statistics_for_service(some_service_id) mock_get.assert_called_once_with(url=expected_url) diff --git a/tests/app/main/notify_client/test_template_statistics_client.py b/tests/app/main/notify_client/test_template_statistics_client.py new file mode 100644 index 000000000..03fb33e4c --- /dev/null +++ b/tests/app/main/notify_client/test_template_statistics_client.py @@ -0,0 +1,17 @@ +import uuid + +from app.notify_client.template_statistics_api_client import TemplateStatisticsApiClient + + +def test_template_statistics_client_calls_correct_api_endpoint(mocker, api_user_active): + + some_service_id = uuid.uuid4() + expected_url = '/service/{}/template-statistics'.format(some_service_id) + + client = TemplateStatisticsApiClient() + + mock_get = mocker.patch('app.notify_client.template_statistics_api_client.TemplateStatisticsApiClient.get') + + client.get_template_statistics_for_service(some_service_id) + + mock_get.assert_called_once_with(url=expected_url) diff --git a/tests/app/main/views/test_accept_invite.py b/tests/app/main/views/test_accept_invite.py index 32a5c2db9..dbc5c1717 100644 --- a/tests/app/main/views/test_accept_invite.py +++ b/tests/app/main/views/test_accept_invite.py @@ -338,6 +338,7 @@ def test_new_invited_user_verifies_and_added_to_service(app_, mock_get_service, mock_get_service_templates, mock_get_service_statistics, + mock_get_template_statistics, mock_get_jobs, mock_has_permissions): diff --git a/tests/app/main/views/test_dashboard.py b/tests/app/main/views/test_dashboard.py index b59d474b8..418623bd1 100644 --- a/tests/app/main/views/test_dashboard.py +++ b/tests/app/main/views/test_dashboard.py @@ -1,5 +1,7 @@ from flask import url_for + from tests import validate_route_permission +from tests.conftest import SERVICE_ONE_ID def test_should_show_recent_jobs_on_dashboard(app_, @@ -7,20 +9,22 @@ def test_should_show_recent_jobs_on_dashboard(app_, mock_get_service, mock_get_service_templates, mock_get_service_statistics, + mock_get_template_statistics, mock_get_user, mock_get_user_by_email, mock_login, mock_get_jobs, mock_has_permissions): - with app_.test_request_context(): with app_.test_client() as client: client.login(api_user_active) - response = client.get(url_for('main.service_dashboard', service_id=123)) + response = client.get(url_for('main.service_dashboard', service_id=SERVICE_ONE_ID)) assert response.status_code == 200 text = response.get_data(as_text=True) assert 'Test Service' in text + mock_get_service_statistics.assert_called_once_with(SERVICE_ONE_ID) + mock_get_template_statistics.assert_called_once_with(SERVICE_ONE_ID) def _test_dashboard_menu(mocker, app_, usr, service, permissions): @@ -37,7 +41,14 @@ def _test_dashboard_menu(mocker, app_, usr, service, permissions): return client.get(url_for('main.service_dashboard', service_id=service['id'])) -def test_menu_send_messages(mocker, app_, api_user_active, service_one, mock_get_service_templates, mock_get_jobs): +def test_menu_send_messages(mocker, + app_, + api_user_active, + service_one, + mock_get_service_templates, + mock_get_jobs, + mock_get_template_statistics): + with app_.test_request_context(): resp = _test_dashboard_menu( mocker, @@ -63,7 +74,13 @@ def test_menu_send_messages(mocker, app_, api_user_active, service_one, mock_get assert url_for('main.show_all_services') not in page -def test_menu_manage_service(mocker, app_, api_user_active, service_one, mock_get_service_templates, mock_get_jobs): +def test_menu_manage_service(mocker, + app_, + api_user_active, + service_one, + mock_get_service_templates, + mock_get_jobs, + mock_get_template_statistics): with app_.test_request_context(): resp = _test_dashboard_menu( mocker, @@ -89,7 +106,13 @@ def test_menu_manage_service(mocker, app_, api_user_active, service_one, mock_ge assert url_for('main.show_all_services') not in page -def test_menu_manage_api_keys(mocker, app_, api_user_active, service_one, mock_get_service_templates, mock_get_jobs): +def test_menu_manage_api_keys(mocker, + app_, + api_user_active, + service_one, + mock_get_service_templates, + mock_get_jobs, + mock_get_template_statistics): with app_.test_request_context(): resp = _test_dashboard_menu( mocker, @@ -114,8 +137,13 @@ def test_menu_manage_api_keys(mocker, app_, api_user_active, service_one, mock_g assert url_for('main.api_keys', service_id=service_one['id']) in page -def test_menu_all_services_for_platform_admin_user(mocker, app_, platform_admin_user, service_one, - mock_get_service_templates, mock_get_jobs): +def test_menu_all_services_for_platform_admin_user(mocker, + app_, + platform_admin_user, + service_one, + mock_get_service_templates, + mock_get_jobs, + mock_get_template_statistics): with app_.test_request_context(): resp = _test_dashboard_menu( mocker, @@ -130,8 +158,8 @@ def test_menu_all_services_for_platform_admin_user(mocker, app_, platform_admin_ assert url_for('main.manage_users', service_id=service_one['id']) in page assert url_for('main.service_settings', service_id=service_one['id']) in page assert url_for('main.view_notifications', service_id=service_one['id']) in page - assert url_for('main.view_jobs', service_id=service_one['id']) in page assert url_for('main.api_keys', service_id=service_one['id']) not in page + assert url_for('main.edit_service_template', service_id=service_one['id'], template_id=1) in page def test_route_for_service_permissions(mocker, @@ -142,7 +170,8 @@ def test_route_for_service_permissions(mocker, mock_get_user, mock_get_service_templates, mock_get_jobs, - mock_get_service_statistics): + mock_get_service_statistics, + mock_get_template_statistics): routes = [ 'main.service_dashboard'] with app_.test_request_context(): diff --git a/tests/app/main/views/test_sign_out.py b/tests/app/main/views/test_sign_out.py index d3b099f58..e3f3510c0 100644 --- a/tests/app/main/views/test_sign_out.py +++ b/tests/app/main/views/test_sign_out.py @@ -15,11 +15,12 @@ def test_sign_out_user(app_, api_user_active, mock_get_user, mock_get_user_by_email, + mock_login, mock_get_service_templates, mock_get_service_statistics, - mock_login, mock_get_jobs, - mock_has_permissions): + mock_has_permissions, + mock_get_template_statistics): with app_.test_request_context(): with app_.test_client() as client: client.login(api_user_active) diff --git a/tests/conftest.py b/tests/conftest.py index ed20ec89f..706048193 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -783,7 +783,24 @@ def mock_remove_user_from_service(mocker): @pytest.fixture(scope='function') -def mock_get_service_statistics(mocker): +def mock_get_template_statistics(mocker, service_one): + + import uuid + template = template_json(service_one['id'], 1, "Test template", "sms", "Something very interesting") + data = { + "usage_count": 1, + "template": { + "name": template['name'], + "template_type": template['template_type'], + "id": template['id'] + }, + "service": template['service'], + "id": str(uuid.uuid4()), + "day": "2016-04-04" + } + + def _get_stats(service_id): + return [data] + return mocker.patch( - 'app.statistics_api_client.get_statistics_for_service', - return_value={'data': [{}]}) + 'app.template_statistics_client.get_template_statistics_for_service', side_effect=_get_stats)