From 042527e74c204d7a294c71efe3255685cf271b64 Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Wed, 10 Mar 2021 10:35:58 +0000 Subject: [PATCH 01/15] Start to build a page to performance platform page. --- app/__init__.py | 4 ++++ app/main/__init__.py | 1 + app/main/views/performance_platform.py | 21 +++++++++++++++++++ .../performance_platform_api_client.py | 10 +++++++++ .../test_performance_platform_api_client.py | 12 +++++++++++ 5 files changed, 48 insertions(+) create mode 100644 app/main/views/performance_platform.py create mode 100644 app/notify_client/performance_platform_api_client.py create mode 100644 tests/app/notify_client/test_performance_platform_api_client.py diff --git a/app/__init__.py b/app/__init__.py index 958a324f8..204b03af9 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -100,6 +100,9 @@ from app.notify_client.letter_jobs_client import letter_jobs_client from app.notify_client.notification_api_client import notification_api_client from app.notify_client.org_invite_api_client import org_invite_api_client from app.notify_client.organisations_api_client import organisations_client +from app.notify_client.performance_platform_api_client import ( + performance_platform_api_client, +) from app.notify_client.platform_stats_api_client import ( platform_stats_api_client, ) @@ -185,6 +188,7 @@ def create_app(application): notification_api_client, org_invite_api_client, organisations_client, + performance_platform_api_client, platform_stats_api_client, provider_client, service_api_client, diff --git a/app/main/__init__.py b/app/main/__init__.py index d5620b705..c77effb0e 100644 --- a/app/main/__init__.py +++ b/app/main/__init__.py @@ -27,6 +27,7 @@ from app.main.views import ( # noqa isort:skip new_password, notifications, organisations, + performance_platform, platform_admin, providers, register, diff --git a/app/main/views/performance_platform.py b/app/main/views/performance_platform.py new file mode 100644 index 000000000..b0857dc1b --- /dev/null +++ b/app/main/views/performance_platform.py @@ -0,0 +1,21 @@ +from datetime import datetime, timedelta + +from flask import jsonify, request + +from app import performance_platform_api_client +from app.main import main +from app.main.forms import DateFilterForm + + +@main.route("/performance-platform") +def performance_platform(): + form = DateFilterForm(request.args, meta={'csrf': False}) + api_args = {} + + form.validate() + # default date range is 3 months + api_args['start_date'] = form.start_date.data or datetime.utcnow().date - timedelta(months=3) + api_args['end_date'] = form.end_date.data or datetime.utcnow().date() + + stats = performance_platform_api_client.get_performance_platform_stats(api_args) + return jsonify(stats) diff --git a/app/notify_client/performance_platform_api_client.py b/app/notify_client/performance_platform_api_client.py new file mode 100644 index 000000000..a82b77e64 --- /dev/null +++ b/app/notify_client/performance_platform_api_client.py @@ -0,0 +1,10 @@ +from app.notify_client import NotifyAdminAPIClient + + +class PerformancePlatformAPIClient(NotifyAdminAPIClient): + + def get_performance_platform_stats(self, params_dict=None): + return self.get("/performance-platform", params=params_dict) + + +performance_platform_api_client = PerformancePlatformAPIClient() diff --git a/tests/app/notify_client/test_performance_platform_api_client.py b/tests/app/notify_client/test_performance_platform_api_client.py new file mode 100644 index 000000000..d2ffecdd4 --- /dev/null +++ b/tests/app/notify_client/test_performance_platform_api_client.py @@ -0,0 +1,12 @@ +from app.notify_client.performance_platform_api_client import ( + PerformancePlatformAPIClient, +) + + +def test_get_aggregate_platform_stats(mocker): + client = PerformancePlatformAPIClient() + mock = mocker.patch.object(client, 'get') + params_dict = {'start_date': '2021-03-01', 'end_date': '2021-03-31'} + + client.get_performance_platform_stats(params_dict=params_dict) + mock.assert_called_once_with('/performance-platform', params=params_dict) From 3ca2840652a879000bf67076bb3d39ce74609c15 Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Wed, 10 Mar 2021 11:01:15 +0000 Subject: [PATCH 02/15] Rename to performance-dashboard --- app/__init__.py | 6 +++--- app/main/views/performance_platform.py | 18 +++++++----------- .../performance_dashboard_api_client.py | 10 ++++++++++ .../performance_platform_api_client.py | 10 ---------- .../test_performance_platform_api_client.py | 8 ++++---- 5 files changed, 24 insertions(+), 28 deletions(-) create mode 100644 app/notify_client/performance_dashboard_api_client.py delete mode 100644 app/notify_client/performance_platform_api_client.py diff --git a/app/__init__.py b/app/__init__.py index 204b03af9..e1ce3ccd5 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -100,8 +100,8 @@ from app.notify_client.letter_jobs_client import letter_jobs_client from app.notify_client.notification_api_client import notification_api_client from app.notify_client.org_invite_api_client import org_invite_api_client from app.notify_client.organisations_api_client import organisations_client -from app.notify_client.performance_platform_api_client import ( - performance_platform_api_client, +from app.notify_client.performance_dashboard_api_client import ( + performance_dashboard_api_client, ) from app.notify_client.platform_stats_api_client import ( platform_stats_api_client, @@ -188,7 +188,7 @@ def create_app(application): notification_api_client, org_invite_api_client, organisations_client, - performance_platform_api_client, + performance_dashboard_api_client, platform_stats_api_client, provider_client, service_api_client, diff --git a/app/main/views/performance_platform.py b/app/main/views/performance_platform.py index b0857dc1b..9e777ae09 100644 --- a/app/main/views/performance_platform.py +++ b/app/main/views/performance_platform.py @@ -1,21 +1,17 @@ from datetime import datetime, timedelta -from flask import jsonify, request +from flask import jsonify -from app import performance_platform_api_client +from app import performance_dashboard_api_client from app.main import main -from app.main.forms import DateFilterForm -@main.route("/performance-platform") -def performance_platform(): - form = DateFilterForm(request.args, meta={'csrf': False}) +@main.route("/performance-dashboard") +def performance_dashboard(): api_args = {} - form.validate() - # default date range is 3 months - api_args['start_date'] = form.start_date.data or datetime.utcnow().date - timedelta(months=3) - api_args['end_date'] = form.end_date.data or datetime.utcnow().date() + api_args['start_date'] = (datetime.utcnow() - timedelta(days=90)).date() + api_args['end_date'] = datetime.utcnow().date() - stats = performance_platform_api_client.get_performance_platform_stats(api_args) + stats = performance_dashboard_api_client.get_performance_dashboard_stats(api_args) return jsonify(stats) diff --git a/app/notify_client/performance_dashboard_api_client.py b/app/notify_client/performance_dashboard_api_client.py new file mode 100644 index 000000000..20f7d895c --- /dev/null +++ b/app/notify_client/performance_dashboard_api_client.py @@ -0,0 +1,10 @@ +from app.notify_client import NotifyAdminAPIClient + + +class PerformanceDashboardAPIClient(NotifyAdminAPIClient): + + def get_performance_dashboard_stats(self, params_dict=None): + return self.get("/performance-dashboard", params=params_dict) + + +performance_dashboard_api_client = PerformanceDashboardAPIClient() diff --git a/app/notify_client/performance_platform_api_client.py b/app/notify_client/performance_platform_api_client.py deleted file mode 100644 index a82b77e64..000000000 --- a/app/notify_client/performance_platform_api_client.py +++ /dev/null @@ -1,10 +0,0 @@ -from app.notify_client import NotifyAdminAPIClient - - -class PerformancePlatformAPIClient(NotifyAdminAPIClient): - - def get_performance_platform_stats(self, params_dict=None): - return self.get("/performance-platform", params=params_dict) - - -performance_platform_api_client = PerformancePlatformAPIClient() diff --git a/tests/app/notify_client/test_performance_platform_api_client.py b/tests/app/notify_client/test_performance_platform_api_client.py index d2ffecdd4..25c1639b9 100644 --- a/tests/app/notify_client/test_performance_platform_api_client.py +++ b/tests/app/notify_client/test_performance_platform_api_client.py @@ -1,12 +1,12 @@ -from app.notify_client.performance_platform_api_client import ( - PerformancePlatformAPIClient, +from app.notify_client.performance_dashboard_api_client import ( + PerformanceDashboardAPIClient, ) def test_get_aggregate_platform_stats(mocker): - client = PerformancePlatformAPIClient() + client = PerformanceDashboardAPIClient() mock = mocker.patch.object(client, 'get') params_dict = {'start_date': '2021-03-01', 'end_date': '2021-03-31'} - client.get_performance_platform_stats(params_dict=params_dict) + client.get_performance_dashboard_stats(params_dict=params_dict) mock.assert_called_once_with('/performance-platform', params=params_dict) From fce4082ffffebfde2809dd47992b7ddaab986a2d Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Thu, 11 Mar 2021 10:40:44 +0000 Subject: [PATCH 03/15] Rename to performance This is consistent with the old performance platform URLs, which were gov.uk/performance, and others that we have like /pricing and /features --- app/main/__init__.py | 2 +- app/main/views/{performance_platform.py => performance.py} | 4 ++-- .../app/notify_client/test_performance_platform_api_client.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename app/main/views/{performance_platform.py => performance.py} (85%) diff --git a/app/main/__init__.py b/app/main/__init__.py index c77effb0e..8e0542676 100644 --- a/app/main/__init__.py +++ b/app/main/__init__.py @@ -27,7 +27,7 @@ from app.main.views import ( # noqa isort:skip new_password, notifications, organisations, - performance_platform, + performance, platform_admin, providers, register, diff --git a/app/main/views/performance_platform.py b/app/main/views/performance.py similarity index 85% rename from app/main/views/performance_platform.py rename to app/main/views/performance.py index 9e777ae09..9dce4c4e7 100644 --- a/app/main/views/performance_platform.py +++ b/app/main/views/performance.py @@ -6,8 +6,8 @@ from app import performance_dashboard_api_client from app.main import main -@main.route("/performance-dashboard") -def performance_dashboard(): +@main.route("/performance") +def performance(): api_args = {} api_args['start_date'] = (datetime.utcnow() - timedelta(days=90)).date() diff --git a/tests/app/notify_client/test_performance_platform_api_client.py b/tests/app/notify_client/test_performance_platform_api_client.py index 25c1639b9..ed516c73c 100644 --- a/tests/app/notify_client/test_performance_platform_api_client.py +++ b/tests/app/notify_client/test_performance_platform_api_client.py @@ -9,4 +9,4 @@ def test_get_aggregate_platform_stats(mocker): params_dict = {'start_date': '2021-03-01', 'end_date': '2021-03-31'} client.get_performance_dashboard_stats(params_dict=params_dict) - mock.assert_called_once_with('/performance-platform', params=params_dict) + mock.assert_called_once_with('/performance-dashboard', params=params_dict) From 25a6788d66422ec5fc29e326c3e718ced4b7c6f7 Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Thu, 11 Mar 2021 10:52:03 +0000 Subject: [PATCH 04/15] Use arguments rather than passing around a dict This makes it harder to write code which will pass tests but fail in real life. --- app/main/views/performance.py | 17 +++++++++-------- app/navigation.py | 4 ++++ .../performance_dashboard_api_client.py | 15 +++++++++++++-- .../test_performance_platform_api_client.py | 14 +++++++++++--- 4 files changed, 37 insertions(+), 13 deletions(-) diff --git a/app/main/views/performance.py b/app/main/views/performance.py index 9dce4c4e7..74e3f4835 100644 --- a/app/main/views/performance.py +++ b/app/main/views/performance.py @@ -1,6 +1,6 @@ from datetime import datetime, timedelta -from flask import jsonify +from flask import render_template from app import performance_dashboard_api_client from app.main import main @@ -8,10 +8,11 @@ from app.main import main @main.route("/performance") def performance(): - api_args = {} - - api_args['start_date'] = (datetime.utcnow() - timedelta(days=90)).date() - api_args['end_date'] = datetime.utcnow().date() - - stats = performance_dashboard_api_client.get_performance_dashboard_stats(api_args) - return jsonify(stats) + stats = performance_dashboard_api_client.get_performance_dashboard_stats( + start_date=(datetime.utcnow() - timedelta(days=90)).date(), + end_date=datetime.utcnow().date(), + ) + return render_template( + 'views/performance.html', + **stats + ) diff --git a/app/navigation.py b/app/navigation.py index 58027c8c8..a3bd4c29f 100644 --- a/app/navigation.py +++ b/app/navigation.py @@ -246,6 +246,7 @@ class HeaderNavigation(Navigation): 'organisation_settings', 'organisation_preview_email_branding', 'organisation_preview_letter_branding', + 'performance', 'privacy', 'public_agreement', 'public_download_agreement', @@ -648,6 +649,7 @@ class MainNavigation(Navigation): 'organisation_preview_letter_branding', 'organisation_settings', 'organisations', + 'performance', 'performance_platform_xlsx', 'platform_admin', 'platform_admin_list_complaints', @@ -916,6 +918,7 @@ class CaseworkNavigation(Navigation): 'organisation_preview_letter_branding', 'organisation_settings', 'organisations', + 'performance', 'performance_platform_xlsx', 'platform_admin_list_complaints', 'platform_admin_reports', @@ -1235,6 +1238,7 @@ class OrgNavigation(Navigation): 'old_terms', 'old_using_notify', 'organisations', + 'performance', 'performance_platform_xlsx', 'platform_admin', 'platform_admin_list_complaints', diff --git a/app/notify_client/performance_dashboard_api_client.py b/app/notify_client/performance_dashboard_api_client.py index 20f7d895c..ffb913ca1 100644 --- a/app/notify_client/performance_dashboard_api_client.py +++ b/app/notify_client/performance_dashboard_api_client.py @@ -3,8 +3,19 @@ from app.notify_client import NotifyAdminAPIClient class PerformanceDashboardAPIClient(NotifyAdminAPIClient): - def get_performance_dashboard_stats(self, params_dict=None): - return self.get("/performance-dashboard", params=params_dict) + def get_performance_dashboard_stats( + self, + *, + start_date, + end_date, + ): + return self.get( + '/performance-dashboard', + params={ + 'start_date': str(start_date), + 'end_date': str(end_date), + } + ) performance_dashboard_api_client = PerformanceDashboardAPIClient() diff --git a/tests/app/notify_client/test_performance_platform_api_client.py b/tests/app/notify_client/test_performance_platform_api_client.py index ed516c73c..b9e8cfcd9 100644 --- a/tests/app/notify_client/test_performance_platform_api_client.py +++ b/tests/app/notify_client/test_performance_platform_api_client.py @@ -1,3 +1,5 @@ +from datetime import date + from app.notify_client.performance_dashboard_api_client import ( PerformanceDashboardAPIClient, ) @@ -6,7 +8,13 @@ from app.notify_client.performance_dashboard_api_client import ( def test_get_aggregate_platform_stats(mocker): client = PerformanceDashboardAPIClient() mock = mocker.patch.object(client, 'get') - params_dict = {'start_date': '2021-03-01', 'end_date': '2021-03-31'} - client.get_performance_dashboard_stats(params_dict=params_dict) - mock.assert_called_once_with('/performance-dashboard', params=params_dict) + client.get_performance_dashboard_stats( + start_date=date(2021, 3, 1), + end_date=date(2021, 3, 31), + ) + + mock.assert_called_once_with('/performance-dashboard', params={ + 'start_date': '2021-03-01', + 'end_date': '2021-03-31' + }) From 545f933e37944ee184f2fbe91a4deae90dbf604f Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Thu, 11 Mar 2021 10:53:12 +0000 Subject: [PATCH 05/15] Cache the JSON in Redis The endpoints are probably fast enough, but it seems risky to have a public URL which causes a new query every time the page is loaded. --- .../performance_dashboard_api_client.py | 3 +- .../test_performance_platform_api_client.py | 67 ++++++++++++++++++- 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/app/notify_client/performance_dashboard_api_client.py b/app/notify_client/performance_dashboard_api_client.py index ffb913ca1..c08734aff 100644 --- a/app/notify_client/performance_dashboard_api_client.py +++ b/app/notify_client/performance_dashboard_api_client.py @@ -1,8 +1,9 @@ -from app.notify_client import NotifyAdminAPIClient +from app.notify_client import NotifyAdminAPIClient, cache class PerformanceDashboardAPIClient(NotifyAdminAPIClient): + @cache.set('performance-stats-{start_date}-to-{end_date}') def get_performance_dashboard_stats( self, *, diff --git a/tests/app/notify_client/test_performance_platform_api_client.py b/tests/app/notify_client/test_performance_platform_api_client.py index b9e8cfcd9..dbdfbd6a1 100644 --- a/tests/app/notify_client/test_performance_platform_api_client.py +++ b/tests/app/notify_client/test_performance_platform_api_client.py @@ -1,4 +1,5 @@ from datetime import date +from unittest.mock import call from app.notify_client.performance_dashboard_api_client import ( PerformanceDashboardAPIClient, @@ -6,8 +7,9 @@ from app.notify_client.performance_dashboard_api_client import ( def test_get_aggregate_platform_stats(mocker): + mocker.patch('app.extensions.RedisClient.get', return_value=None) client = PerformanceDashboardAPIClient() - mock = mocker.patch.object(client, 'get') + mock = mocker.patch.object(client, 'get', return_value={}) client.get_performance_dashboard_stats( start_date=date(2021, 3, 1), @@ -18,3 +20,66 @@ def test_get_aggregate_platform_stats(mocker): 'start_date': '2021-03-01', 'end_date': '2021-03-31' }) + + +def test_sets_value_in_cache(mocker): + client = PerformanceDashboardAPIClient() + + mock_redis_get = mocker.patch( + 'app.extensions.RedisClient.get', + return_value=None, + ) + mock_api_get = mocker.patch( + 'app.notify_client.NotifyAdminAPIClient.get', + return_value={'data_from': 'api'}, + ) + mock_redis_set = mocker.patch( + 'app.extensions.RedisClient.set', + ) + + assert client.get_performance_dashboard_stats( + start_date=date(2021, 1, 1), + end_date=date(2022, 2, 2), + ) == {'data_from': 'api'} + + assert mock_redis_get.call_args_list == [ + call('performance-stats-2021-01-01-to-2022-02-02'), + ] + assert mock_api_get.call_args_list == [ + call('/performance-dashboard', params={ + 'start_date': '2021-01-01', 'end_date': '2022-02-02' + }), + ] + assert mock_redis_set.call_args_list == [ + call( + 'performance-stats-2021-01-01-to-2022-02-02', + '{"data_from": "api"}', + ex=604800, + ), + ] + + +def test_returns_value_from_cache(mocker): + client = PerformanceDashboardAPIClient() + + mock_redis_get = mocker.patch( + 'app.extensions.RedisClient.get', + return_value=b'{"data_from": "cache"}', + ) + mock_api_get = mocker.patch( + 'app.notify_client.NotifyAdminAPIClient.get', + ) + mock_redis_set = mocker.patch( + 'app.extensions.RedisClient.set', + ) + + assert client.get_performance_dashboard_stats( + start_date=date(2021, 1, 1), + end_date=date(2022, 2, 2), + ) == {'data_from': 'cache'} + + assert mock_redis_get.call_args_list == [ + call('performance-stats-2021-01-01-to-2022-02-02'), + ] + assert mock_api_get.called is False + assert mock_redis_set.called is False From 646075f61f83856efd3110dcc34b3d19156d1b61 Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Thu, 11 Mar 2021 12:34:22 +0000 Subject: [PATCH 06/15] Put some stuff on the page This basically copies the same sections from the existing performance platform page, with the frontend cobbled together from our existing patterns. --- app/main/views/performance.py | 20 +++ app/templates/views/performance.html | 133 ++++++++++++++++++++ tests/app/main/views/test_performance.py | 150 +++++++++++++++++++++++ 3 files changed, 303 insertions(+) create mode 100644 app/templates/views/performance.html create mode 100644 tests/app/main/views/test_performance.py diff --git a/app/main/views/performance.py b/app/main/views/performance.py index 74e3f4835..14e8e3d65 100644 --- a/app/main/views/performance.py +++ b/app/main/views/performance.py @@ -1,4 +1,7 @@ from datetime import datetime, timedelta +from itertools import groupby +from operator import itemgetter +from statistics import mean from flask import render_template @@ -12,6 +15,23 @@ def performance(): start_date=(datetime.utcnow() - timedelta(days=90)).date(), end_date=datetime.utcnow().date(), ) + stats['organisations_using_notify'] = sorted( + [ + { + 'organisation_name': organisation_name, + 'count_of_live_services': len(list(group)), + } + for organisation_name, group in groupby( + stats['services_using_notify'], + itemgetter('organisation_name'), + ) + ], + key=itemgetter('organisation_name'), + ) + stats['average_percentage_under_10_seconds'] = mean([ + row['percentage_under_10_seconds'] + for row in stats['processing_time'] + ] or [0]) return render_template( 'views/performance.html', **stats diff --git a/app/templates/views/performance.html b/app/templates/views/performance.html new file mode 100644 index 000000000..20e70551b --- /dev/null +++ b/app/templates/views/performance.html @@ -0,0 +1,133 @@ +{% extends "withoutnav_template.html" %} +{% from "components/big-number.html" import big_number %} +{% from "components/page-header.html" import page_header %} +{% from "components/table.html" import field, list_table %} + +{% block per_page_title %} + Performance data +{% endblock %} + +{% block maincolumn_content %} + +
+
+ {{ page_header('Performance data') }} +
+
+ +

+ Messages sent since May 2016 +

+ +
+
+

Total messages

+
{{ total_notifications }}
+ total +
+
+
+
+ {{ big_number( + email_notifications, + label=email_notifications|message_count_noun('email'), + smallest=True, + ) }} +
+
+ {{ big_number( + sms_notifications, + label=sms_notifications|message_count_noun('sms'), + smallest=True, + ) }} +
+
+ {{ big_number( + letter_notifications, + label=letter_notifications|message_count_noun('letter'), + smallest=True, + ) }} +
+
+
+
+ +
+ {% call(item, row_number) list_table( + notifications_by_type, + caption_visible=False, + field_headings=[ + 'Date', + 99|message_count_noun('email')|capitalize, + 99|message_count_noun('sms')|capitalize, + 99|message_count_noun('letter')|capitalize, + ], + empty_message='No data to show' + ) %} + {% call field() %} + {{ item.date | format_date_normal }} + {% endcall %} + {% call field() %} + {{ item.emails|format_thousands }} + {% endcall %} + {% call field() %} + {{ item.sms|format_thousands }} + {% endcall %} + {% call field() %} + {{ item.letters|format_thousands }} + {% endcall %} + {% endcall %} +
+ +

+ Messages sent within 10 seconds +

+
+
+

Average

+ {{ big_number( + '{:.1f}%'.format(average_percentage_under_10_seconds), + label='on average', + ) }} +
+
+
+ {% call(item, row_number) list_table( + processing_time | reverse, + caption_visible=False, + field_headings=[ + 'Date', 'Percentage' + ], + empty_message='No data to show' + ) %} + {% call field() %} + {{ item.date | format_date_normal }} + {% endcall %} + {% call field() %} + {{ item.percentage_under_10_seconds }}% + {% endcall %} + {% endcall %} +
+ +

+ Organisations using Notify +

+
+ {% call(item, row_number) list_table( + organisations_using_notify, + caption_visible=False, + field_headings=[ + 'Organisation', 'Number of live services' + ], + empty_message='No data to show' + ) %} + {% call field() %} + {{ item.organisation_name }} + {% endcall %} + {% call field() %} + {{ item.count_of_live_services }} + {% endcall %} + {% endcall %} +
+ +{% endblock %} diff --git a/tests/app/main/views/test_performance.py b/tests/app/main/views/test_performance.py new file mode 100644 index 000000000..c6dc25074 --- /dev/null +++ b/tests/app/main/views/test_performance.py @@ -0,0 +1,150 @@ +import random +import uuid +from datetime import date + +from freezegun import freeze_time + +from tests.conftest import normalize_spaces + + +def _get_example_performance_data(): + return { + "total_notifications": 1_789_000_000, + "email_notifications": 1_123_000_000, + "sms_notifications": 987_654_321, + "letter_notifications": 1_234_567, + "live_service_count": random.randrange(1, 1000), + "notifications_by_type": [ + { + "date": "2021-02-21", + "emails": 1_234_567, "sms": 123_456, "letters": 123, + }, + { + "date": "2021-02-22", + "emails": 1, "sms": 2, "letters": 3, + }, + { + "date": "2021-02-23", + "emails": 1, "sms": 2, "letters": 3, + }, + { + "date": "2021-02-24", + "emails": 1, "sms": 2, "letters": 3, + }, + { + "date": "2021-02-25", + "emails": 1, "sms": 2, "letters": 3, + }, + { + "date": "2021-02-26", + "emails": 1, "sms": 2, "letters": 3, + }, + { + "date": "2021-02-27", + "emails": 1, "sms": 2, "letters": 3, + }, + ], + "processing_time": [ + { + "date": "2021-02-21", + "percentage_under_10_seconds": 99.2 + }, + { + "date": "2021-02-22", + "percentage_under_10_seconds": 95.3 + }, + { + "date": "2021-02-23", + "percentage_under_10_seconds": 95.6 + }, + { + "date": "2021-02-24", + "percentage_under_10_seconds": 96.7 + }, + { + "date": "2021-02-25", + "percentage_under_10_seconds": 95.7 + }, + { + "date": "2021-02-26", + "percentage_under_10_seconds": 96.5 + }, + { + "date": "2021-02-27", + "percentage_under_10_seconds": 98.6 + }, + ], + "services_using_notify": [ + { + "organisation_id": uuid.uuid4(), + "organisation_name": "Department of Examples and Patterns", + "service_id": uuid.uuid4(), + "service_name": "Example service" + }, + { + "organisation_id": uuid.uuid4(), + "organisation_name": "Department of Examples and Patterns", + "service_id": uuid.uuid4(), + "service_name": "Example service 2" + }, + { + "organisation_id": uuid.uuid4(), + "organisation_name": "Department of One Service", + "service_id": uuid.uuid4(), + "service_name": "Example service 3" + }, + ], + } + + +@freeze_time('2021-01-01') +def test_should_render_performance_page( + mocker, + client_request, + mock_get_service_and_organisation_counts, +): + mock_get_performance_data = mocker.patch( + 'app.performance_dashboard_api_client.get_performance_dashboard_stats', + return_value=_get_example_performance_data(), + ) + page = client_request.get('main.performance') + mock_get_performance_data.assert_called_once_with( + start_date=date(2020, 10, 3), + end_date=date(2021, 1, 1), + ) + assert normalize_spaces(page.select_one('main').text) == ( + 'Performance data ' + '' + 'Messages sent since May 2016 ' + 'Total messages ' + '1789000000 total ' + '1,123,000,000 emails ' + '987,654,321 text messages ' + '1,234,567 letters ' + '' + 'Date Emails Text messages Letters ' + '21 February 2021 1,234,567 123,456 123 ' + '22 February 2021 1 2 3 ' + '23 February 2021 1 2 3 ' + '24 February 2021 1 2 3 ' + '25 February 2021 1 2 3 ' + '26 February 2021 1 2 3 ' + '27 February 2021 1 2 3 ' + '' + 'Messages sent within 10 seconds ' + 'Average ' + '96.8% on average ' + 'Date Percentage ' + '27 February 2021 98.6% ' + '26 February 2021 96.5% ' + '25 February 2021 95.7% ' + '24 February 2021 96.7% ' + '23 February 2021 95.6% ' + '22 February 2021 95.3% ' + '21 February 2021 99.2% ' + '' + 'Organisations using Notify ' + 'Organisation Number of live services ' + 'Department of Examples and Patterns 2 ' + 'Department of One Service 1' + ) From c3699e0e359bb519ff373caeee67e88d89546cf7 Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Thu, 11 Mar 2021 13:23:02 +0000 Subject: [PATCH 07/15] Format numbers as millions or billions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This matches the existing performance platform page, and I think is a bit easier to read for high-level numbers where you don’t need to see that they’re changing second-by-second. --- app/__init__.py | 2 ++ app/formatters.py | 4 ++++ app/templates/views/performance.html | 8 ++++---- tests/app/main/views/test_performance.py | 8 ++++---- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index e1ce3ccd5..fc9ad0a2d 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -38,6 +38,7 @@ from app.config import configs from app.extensions import antivirus_client, redis_client, zendesk_client from app.formatters import ( convert_to_boolean, + format_billions, format_date, format_date_human, format_date_normal, @@ -527,6 +528,7 @@ def setup_event_handlers(): def add_template_filters(application): for fn in [ + format_billions, format_datetime, format_datetime_24h, format_datetime_normal, diff --git a/app/formatters.py b/app/formatters.py index 2f9223a44..612e45113 100644 --- a/app/formatters.py +++ b/app/formatters.py @@ -515,3 +515,7 @@ def format_mobile_network(network): if network in ('three', 'vodafone', 'o2'): return network.capitalize() return 'EE' + + +def format_billions(count): + return humanize.intword(count) diff --git a/app/templates/views/performance.html b/app/templates/views/performance.html index 20e70551b..05235134c 100644 --- a/app/templates/views/performance.html +++ b/app/templates/views/performance.html @@ -22,28 +22,28 @@

Total messages

-
{{ total_notifications }}
+
{{ total_notifications|format_billions }}
total
{{ big_number( - email_notifications, + email_notifications|format_billions, label=email_notifications|message_count_noun('email'), smallest=True, ) }}
{{ big_number( - sms_notifications, + sms_notifications|format_billions, label=sms_notifications|message_count_noun('sms'), smallest=True, ) }}
{{ big_number( - letter_notifications, + letter_notifications|format_billions, label=letter_notifications|message_count_noun('letter'), smallest=True, ) }} diff --git a/tests/app/main/views/test_performance.py b/tests/app/main/views/test_performance.py index c6dc25074..6709d8b73 100644 --- a/tests/app/main/views/test_performance.py +++ b/tests/app/main/views/test_performance.py @@ -117,10 +117,10 @@ def test_should_render_performance_page( '' 'Messages sent since May 2016 ' 'Total messages ' - '1789000000 total ' - '1,123,000,000 emails ' - '987,654,321 text messages ' - '1,234,567 letters ' + '1.8 billion total ' + '1.1 billion emails ' + '987.7 million text messages ' + '1.2 million letters ' '' 'Date Emails Text messages Letters ' '21 February 2021 1,234,567 123,456 123 ' From 8106d6fdf26ae263296c7a2737029be70849a47d Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Thu, 11 Mar 2021 13:11:39 +0000 Subject: [PATCH 08/15] Limit data to last 7 days Otherwise the page gets really long. Also matches what we have on our dashboards by default. --- app/main/views/performance.py | 2 +- app/templates/views/performance.html | 6 ++++++ tests/app/main/views/test_performance.py | 4 +++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/main/views/performance.py b/app/main/views/performance.py index 14e8e3d65..e8d0eef0d 100644 --- a/app/main/views/performance.py +++ b/app/main/views/performance.py @@ -12,7 +12,7 @@ from app.main import main @main.route("/performance") def performance(): stats = performance_dashboard_api_client.get_performance_dashboard_stats( - start_date=(datetime.utcnow() - timedelta(days=90)).date(), + start_date=(datetime.utcnow() - timedelta(days=7)).date(), end_date=datetime.utcnow().date(), ) stats['organisations_using_notify'] = sorted( diff --git a/app/templates/views/performance.html b/app/templates/views/performance.html index 05235134c..609ce9de4 100644 --- a/app/templates/views/performance.html +++ b/app/templates/views/performance.html @@ -77,6 +77,9 @@ {{ item.letters|format_thousands }} {% endcall %} {% endcall %} +

@@ -107,6 +110,9 @@ {{ item.percentage_under_10_seconds }}% {% endcall %} {% endcall %} +

diff --git a/tests/app/main/views/test_performance.py b/tests/app/main/views/test_performance.py index 6709d8b73..a48153aef 100644 --- a/tests/app/main/views/test_performance.py +++ b/tests/app/main/views/test_performance.py @@ -109,7 +109,7 @@ def test_should_render_performance_page( ) page = client_request.get('main.performance') mock_get_performance_data.assert_called_once_with( - start_date=date(2020, 10, 3), + start_date=date(2020, 12, 25), end_date=date(2021, 1, 1), ) assert normalize_spaces(page.select_one('main').text) == ( @@ -130,6 +130,7 @@ def test_should_render_performance_page( '25 February 2021 1 2 3 ' '26 February 2021 1 2 3 ' '27 February 2021 1 2 3 ' + 'Only showing the last 7 days ' '' 'Messages sent within 10 seconds ' 'Average ' @@ -142,6 +143,7 @@ def test_should_render_performance_page( '23 February 2021 95.6% ' '22 February 2021 95.3% ' '21 February 2021 99.2% ' + 'Only showing the last 7 days ' '' 'Organisations using Notify ' 'Organisation Number of live services ' From 5fa6639b52f83d8a61758c4d72cc49a01d8341c9 Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Thu, 11 Mar 2021 13:29:46 +0000 Subject: [PATCH 09/15] Add count of orgs and services MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This uses the existing endpoint so it matches what’s on the homepage. It will be more up-to-date than the list of services, but no-one’s going to be adding things up to check they match exactly. --- app/main/views/performance.py | 5 ++++- app/templates/views/performance.html | 12 ++++++++++++ tests/app/main/views/test_performance.py | 2 ++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/app/main/views/performance.py b/app/main/views/performance.py index e8d0eef0d..86f3b8bc9 100644 --- a/app/main/views/performance.py +++ b/app/main/views/performance.py @@ -5,7 +5,7 @@ from statistics import mean from flask import render_template -from app import performance_dashboard_api_client +from app import performance_dashboard_api_client, status_api_client from app.main import main @@ -32,6 +32,9 @@ def performance(): row['percentage_under_10_seconds'] for row in stats['processing_time'] ] or [0]) + stats['count_of_live_services_and_organisations'] = ( + status_api_client.get_count_of_live_services_and_organisations() + ) return render_template( 'views/performance.html', **stats diff --git a/app/templates/views/performance.html b/app/templates/views/performance.html index 609ce9de4..e3ab343bd 100644 --- a/app/templates/views/performance.html +++ b/app/templates/views/performance.html @@ -118,6 +118,18 @@

Organisations using Notify

+
+
+

Organisations

+
{{ count_of_live_services_and_organisations.organisations|format_thousands }}
+ organisations +
+
+

Services

+
{{ count_of_live_services_and_organisations.services|format_thousands }}
+ services +
+
{% call(item, row_number) list_table( organisations_using_notify, diff --git a/tests/app/main/views/test_performance.py b/tests/app/main/views/test_performance.py index a48153aef..cd691276b 100644 --- a/tests/app/main/views/test_performance.py +++ b/tests/app/main/views/test_performance.py @@ -146,6 +146,8 @@ def test_should_render_performance_page( 'Only showing the last 7 days ' '' 'Organisations using Notify ' + 'Organisations 111 organisations ' + 'Services 9,999 services ' 'Organisation Number of live services ' 'Department of Examples and Patterns 2 ' 'Department of One Service 1' From 4d0264d19622b265ad6528bd166a3eb8000d206f Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Thu, 11 Mar 2021 13:30:38 +0000 Subject: [PATCH 10/15] Switch org and service order on home page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This matches what’s on the performance page – orgs on the left, services on the right. --- app/templates/views/signedout.html | 10 +++++----- tests/app/main/views/test_index.py | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/templates/views/signedout.html b/app/templates/views/signedout.html index 4f6e6467a..4fc7ff2f6 100644 --- a/app/templates/views/signedout.html +++ b/app/templates/views/signedout.html @@ -122,16 +122,16 @@

Who’s using GOV.UK Notify

-
-

Services

-
{{ counts.services|format_thousands }}
- services -

Organisations

{{ counts.organisations|format_thousands }}
organisations
+
+

Services

+
{{ counts.services|format_thousands }}
+ services +

See the diff --git a/tests/app/main/views/test_index.py b/tests/app/main/views/test_index.py index c4b0dcc5d..ea94317c7 100644 --- a/tests/app/main/views/test_index.py +++ b/tests/app/main/views/test_index.py @@ -34,10 +34,10 @@ def test_non_logged_in_user_can_see_homepage( assert normalize_spaces(page.select_one('#whos-using-notify').text) == ( 'Who’s using GOV.UK Notify ' - 'Services ' - '9,999 services ' 'Organisations ' '111 organisations ' + 'Services ' + '9,999 services ' 'See the list of services and organisations.' ) assert page.select_one('#whos-using-notify a')['href'] == ( From 67316df3aa4f94656476586a7cc6eb34412da8be Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Thu, 11 Mar 2021 12:40:02 +0000 Subject: [PATCH 11/15] Repoint footer and homepage links MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Performance Platform is going away at 10am on Monday, so let’s point to our Performance Page instead. --- app/templates/admin_template.html | 2 +- app/templates/views/signedout.html | 2 +- tests/app/main/views/test_index.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/templates/admin_template.html b/app/templates/admin_template.html index a492c9716..f8de636a8 100644 --- a/app/templates/admin_template.html +++ b/app/templates/admin_template.html @@ -172,7 +172,7 @@ "text": "System status" }, { - "href": "https://www.gov.uk/performance/govuk-notify", + "href": url_for('main.performance'), "text": "Performance data" }, { diff --git a/app/templates/views/signedout.html b/app/templates/views/signedout.html index 4fc7ff2f6..3214b3e95 100644 --- a/app/templates/views/signedout.html +++ b/app/templates/views/signedout.html @@ -135,7 +135,7 @@

See the - list of services and organisations. + list of services and organisations.

diff --git a/tests/app/main/views/test_index.py b/tests/app/main/views/test_index.py index ea94317c7..db21a84bc 100644 --- a/tests/app/main/views/test_index.py +++ b/tests/app/main/views/test_index.py @@ -40,8 +40,8 @@ def test_non_logged_in_user_can_see_homepage( '9,999 services ' 'See the list of services and organisations.' ) - assert page.select_one('#whos-using-notify a')['href'] == ( - 'https://www.gov.uk/performance/govuk-notify/government-services' + assert page.select_one('#whos-using-notify a')['href'] == url_for( + 'main.performance' ) From f7989b84cb8b2042152a73981312c65bd6c8ad66 Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Fri, 12 Mar 2021 16:18:55 +0000 Subject: [PATCH 12/15] Add captions to tables Co-authored-by: Tom Byers --- app/templates/views/performance.html | 3 +++ tests/app/main/views/test_performance.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/app/templates/views/performance.html b/app/templates/views/performance.html index e3ab343bd..663db2f46 100644 --- a/app/templates/views/performance.html +++ b/app/templates/views/performance.html @@ -55,6 +55,7 @@
{% call(item, row_number) list_table( notifications_by_type, + caption='Messages sent since May 2016', caption_visible=False, field_headings=[ 'Date', @@ -97,6 +98,7 @@
{% call(item, row_number) list_table( processing_time | reverse, + caption='Messages sent within 10 seconds', caption_visible=False, field_headings=[ 'Date', 'Percentage' @@ -133,6 +135,7 @@
{% call(item, row_number) list_table( organisations_using_notify, + caption='Organisations using Notify', caption_visible=False, field_headings=[ 'Organisation', 'Number of live services' diff --git a/tests/app/main/views/test_performance.py b/tests/app/main/views/test_performance.py index cd691276b..7a544e6f0 100644 --- a/tests/app/main/views/test_performance.py +++ b/tests/app/main/views/test_performance.py @@ -122,6 +122,7 @@ def test_should_render_performance_page( '987.7 million text messages ' '1.2 million letters ' '' + 'Messages sent since May 2016 ' 'Date Emails Text messages Letters ' '21 February 2021 1,234,567 123,456 123 ' '22 February 2021 1 2 3 ' @@ -135,6 +136,7 @@ def test_should_render_performance_page( 'Messages sent within 10 seconds ' 'Average ' '96.8% on average ' + 'Messages sent within 10 seconds ' 'Date Percentage ' '27 February 2021 98.6% ' '26 February 2021 96.5% ' @@ -148,6 +150,7 @@ def test_should_render_performance_page( 'Organisations using Notify ' 'Organisations 111 organisations ' 'Services 9,999 services ' + 'Organisations using Notify ' 'Organisation Number of live services ' 'Department of Examples and Patterns 2 ' 'Department of One Service 1' From 7583c8d9faacf3cfbea3b1a525a4ec1806c93b58 Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Fri, 12 Mar 2021 17:12:47 +0000 Subject: [PATCH 13/15] Remove redundant hidden H3s This commit replaces the H3s which only repeated information with some hidden text that will make it read nicer for screenreaders. --- app/templates/views/performance.html | 7 +++---- app/templates/views/signedout.html | 5 +++-- tests/app/main/views/test_index.py | 5 +---- tests/app/main/views/test_performance.py | 5 +---- 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/app/templates/views/performance.html b/app/templates/views/performance.html index 663db2f46..d8ad5e34a 100644 --- a/app/templates/views/performance.html +++ b/app/templates/views/performance.html @@ -21,7 +21,6 @@
-

Total messages

{{ total_notifications|format_billions }}
total
@@ -88,7 +87,6 @@
-

Average

{{ big_number( '{:.1f}%'.format(average_percentage_under_10_seconds), label='on average', @@ -122,14 +120,15 @@
-

Organisations

+ There are
{{ count_of_live_services_and_organisations.organisations|format_thousands }}
organisations
-

Services

+ and
{{ count_of_live_services_and_organisations.services|format_thousands }}
services + using Notify.
diff --git a/app/templates/views/signedout.html b/app/templates/views/signedout.html index 3214b3e95..c90321418 100644 --- a/app/templates/views/signedout.html +++ b/app/templates/views/signedout.html @@ -123,14 +123,15 @@

Who’s using GOV.UK Notify

-

Organisations

+ There are
{{ counts.organisations|format_thousands }}
organisations
-

Services

+ and
{{ counts.services|format_thousands }}
services + using Notify.

diff --git a/tests/app/main/views/test_index.py b/tests/app/main/views/test_index.py index db21a84bc..998ef65d5 100644 --- a/tests/app/main/views/test_index.py +++ b/tests/app/main/views/test_index.py @@ -34,10 +34,7 @@ def test_non_logged_in_user_can_see_homepage( assert normalize_spaces(page.select_one('#whos-using-notify').text) == ( 'Who’s using GOV.UK Notify ' - 'Organisations ' - '111 organisations ' - 'Services ' - '9,999 services ' + 'There are 111 organisations and 9,999 services using Notify. ' 'See the list of services and organisations.' ) assert page.select_one('#whos-using-notify a')['href'] == url_for( diff --git a/tests/app/main/views/test_performance.py b/tests/app/main/views/test_performance.py index 7a544e6f0..17519a7a9 100644 --- a/tests/app/main/views/test_performance.py +++ b/tests/app/main/views/test_performance.py @@ -116,7 +116,6 @@ def test_should_render_performance_page( 'Performance data ' '' 'Messages sent since May 2016 ' - 'Total messages ' '1.8 billion total ' '1.1 billion emails ' '987.7 million text messages ' @@ -134,7 +133,6 @@ def test_should_render_performance_page( 'Only showing the last 7 days ' '' 'Messages sent within 10 seconds ' - 'Average ' '96.8% on average ' 'Messages sent within 10 seconds ' 'Date Percentage ' @@ -148,8 +146,7 @@ def test_should_render_performance_page( 'Only showing the last 7 days ' '' 'Organisations using Notify ' - 'Organisations 111 organisations ' - 'Services 9,999 services ' + 'There are 111 organisations and 9,999 services using Notify. ' 'Organisations using Notify ' 'Organisation Number of live services ' 'Department of Examples and Patterns 2 ' From f959985b81a1ebac38c404f100885f8d549d993c Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Fri, 12 Mar 2021 17:23:55 +0000 Subject: [PATCH 14/15] Repoint other links to the performance page --- app/templates/views/features.html | 2 +- app/templates/views/terms-of-use.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/templates/views/features.html b/app/templates/views/features.html index 21dcd8ee6..4c34242f4 100644 --- a/app/templates/views/features.html +++ b/app/templates/views/features.html @@ -58,7 +58,7 @@

We send messages through several different providers. If one provider fails, Notify switches to another so that your messages are not affected.

-

Visit GOV.UK Performance to see how Notify is performing.

+

Visit our performance page see how Notify is performing.

Security

Notify protects and manages data to meet the needs of government services.

diff --git a/app/templates/views/terms-of-use.html b/app/templates/views/terms-of-use.html index 358b776a6..60e0ab6fc 100644 --- a/app/templates/views/terms-of-use.html +++ b/app/templates/views/terms-of-use.html @@ -31,7 +31,7 @@
  • send all the messages you pass to us, as long as they meet our guidelines
  • - show how Notify is performing (through our performance and status pages) + show how Notify is performing (through our performance and status pages)
  • keep your data secure
  • give you one month’s notice by email if we change our terms of use or delivery providers
  • From 42e69c5e8cb0103d155a8fac451d0e20871e72f9 Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Mon, 15 Mar 2021 09:08:03 +0000 Subject: [PATCH 15/15] Add a word --- app/templates/views/features.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/templates/views/features.html b/app/templates/views/features.html index 4c34242f4..29c84c843 100644 --- a/app/templates/views/features.html +++ b/app/templates/views/features.html @@ -58,7 +58,7 @@

    We send messages through several different providers. If one provider fails, Notify switches to another so that your messages are not affected.

    -

    Visit our performance page see how Notify is performing.

    +

    Visit our performance data page to see how Notify is performing.

    Security

    Notify protects and manages data to meet the needs of government services.