Merge pull request #471 from alphagov/craig-david

Show the last 7 days of statistics on the dashboard
This commit is contained in:
Chris Hill-Scott
2016-04-20 11:13:51 +01:00
7 changed files with 66 additions and 50 deletions
+39 -34
View File
@@ -1,6 +1,7 @@
from datetime import date from datetime import date
from collections import namedtuple from collections import namedtuple
from itertools import groupby from itertools import groupby
from functools import reduce
from flask import ( from flask import (
render_template, render_template,
@@ -29,40 +30,36 @@ from app.utils import user_has_permissions
@login_required @login_required
@user_has_permissions('view_activity', admin_override=True) @user_has_permissions('view_activity', admin_override=True)
def service_dashboard(service_id): def service_dashboard(service_id):
templates = service_api_client.get_service_templates(service_id)['data']
jobs = job_api_client.get_job(service_id)['data']
if session.get('invited_user'): if session.get('invited_user'):
session.pop('invited_user', None) session.pop('invited_user', None)
session['service_id'] = service_id session['service_id'] = service_id
return redirect(url_for("main.tour", page=1)) return redirect(url_for("main.tour", page=1))
statistics = statistics_api_client.get_statistics_for_service(service_id)['data']
template_statistics = aggregate_usage(template_statistics_client.get_template_statistics_for_service(service_id))
return render_template( return render_template(
'views/dashboard/dashboard.html', 'views/dashboard/dashboard.html',
jobs=jobs[:5], statistics=add_rates_to(
more_jobs_to_show=(len(jobs) > 5), statistics_api_client.get_statistics_for_service(service_id, limit_days=7)['data']
free_text_messages_remaining='250,000', ),
spent_this_month='0.00', templates=service_api_client.get_service_templates(service_id)['data'],
statistics=add_rates_to(statistics), template_statistics=aggregate_usage(
templates=templates, template_statistics_client.get_template_statistics_for_service(service_id)
template_statistics=template_statistics) )
)
@main.route("/services/<service_id>/dashboard.json") @main.route("/services/<service_id>/dashboard.json")
@login_required @login_required
def service_dashboard_updates(service_id): def service_dashboard_updates(service_id):
statistics = statistics_api_client.get_statistics_for_service(service_id)['data']
template_statistics = aggregate_usage(template_statistics_client.get_template_statistics_for_service(service_id))
return jsonify(**{ return jsonify(**{
'today': render_template( 'today': render_template(
'views/dashboard/today.html', 'views/dashboard/today.html',
statistics=add_rates_to(statistics), statistics=add_rates_to(
template_statistics=template_statistics statistics_api_client.get_statistics_for_service(service_id, limit_days=7)['data']
),
template_statistics=aggregate_usage(
template_statistics_client.get_template_statistics_for_service(service_id)
)
) )
}) })
@@ -72,24 +69,32 @@ def add_rates_to(delivery_statistics):
if not delivery_statistics or not delivery_statistics[0]: if not delivery_statistics or not delivery_statistics[0]:
return {} return {}
today = None sum_of_statistics = reduce(
latest_stats = {} lambda x, y: {
if delivery_statistics[0]['day'] == date.today().strftime('%Y-%m-%d'): key: x.get(key, 0) + y.get(key, 0)
today = delivery_statistics[0] for key in [
latest_stats = delivery_statistics[0] 'emails_delivered',
'emails_requested',
'emails_failed',
'sms_requested',
'sms_delivered',
'sms_failed'
]
},
delivery_statistics
)
latest_stats.update({ return dict(
'emails_failure_rate': ( emails_failure_rate=(
"{0:.1f}".format((float(today['emails_failed']) / today['emails_requested'] * 100)) "{0:.1f}".format((float(sum_of_statistics['emails_failed']) / sum_of_statistics['emails_requested'] * 100))
if today and today['emails_requested'] else 0 if sum_of_statistics.get('emails_requested') else 0
), ),
'sms_failure_rate': ( sms_failure_rate=(
"{0:.1f}".format((float(today['sms_failed']) / today['sms_requested'] * 100)) "{0:.1f}".format((float(sum_of_statistics['sms_failed']) / sum_of_statistics['sms_requested'] * 100))
if today and today['sms_requested'] else 0 if sum_of_statistics.get('sms_requested') else 0
) ),
}) **sum_of_statistics
)
return latest_stats
def aggregate_usage(template_statistics): def aggregate_usage(template_statistics):
+4 -1
View File
@@ -12,7 +12,10 @@ class StatisticsApiClient(BaseAPIClient):
self.client_id = app.config['ADMIN_CLIENT_USER_NAME'] self.client_id = app.config['ADMIN_CLIENT_USER_NAME']
self.secret = app.config['ADMIN_CLIENT_SECRET'] self.secret = app.config['ADMIN_CLIENT_SECRET']
def get_statistics_for_service(self, service_id): def get_statistics_for_service(self, service_id, limit_days=None):
params = {}
if limit_days is not None:
params['limit_days'] = limit_days
return self.get( return self.get(
url='/service/{}/notifications-statistics'.format(service_id), url='/service/{}/notifications-statistics'.format(service_id),
) )
+8 -2
View File
@@ -6,12 +6,18 @@
{% endmacro %} {% endmacro %}
{% macro big_number_with_status(number, label, failures, failure_percentage, danger_zone=False) %} {% macro big_number_with_status(number, label, failures, failure_percentage, danger_zone=False, failure_link=None) %}
<div class="big-number-with-status"> <div class="big-number-with-status">
{{ big_number(number, label) }} {{ big_number(number, label) }}
<div class="big-number-status{% if danger_zone %}-failing{% endif %}"> <div class="big-number-status{% if danger_zone %}-failing{% endif %}">
{% if failures %} {% if failures %}
{{ failures }} failed {{ failure_percentage }}% {% if failure_link %}
<a href="{{ failure_link }}">
{{ failures }} failed {{ failure_percentage }}%
</a>
{% else %}
{{ failures }} failed {{ failure_percentage }}%
{% endif %}
{% else %} {% else %}
No failures No failures
{% endif %} {% endif %}
@@ -1,9 +1,10 @@
{% from "components/table.html" import list_table, field, hidden_field_heading, right_aligned_field_heading %} {% from "components/table.html" import list_table, field, hidden_field_heading, right_aligned_field_heading %}
{% call(item, row_number) list_table( {% call(item, row_number) list_table(
template_statistics, template_statistics,
caption="In the last 7 days", caption="By template",
caption_visible=False,
empty_message='You havent set up any templates yet', empty_message='You havent set up any templates yet',
field_headings=['Template', hidden_field_heading('Type'), right_aligned_field_heading('Messages sent')] field_headings=['Template', hidden_field_heading('Type'), right_aligned_field_heading('Messages processed')]
) %} ) %}
{% call field() %} {% call field() %}
<a href="{{ url_for('.view_template', service_id=current_service.id, template_id=item.template.id) }}"> <a href="{{ url_for('.view_template', service_id=current_service.id, template_id=item.template.id) }}">
+8 -7
View File
@@ -1,16 +1,17 @@
{% from "components/big-number.html" import big_number_with_status %} {% from "components/big-number.html" import big_number_with_status %}
<h2 class="heading-medium"> <h2 class="heading-medium">
Sent today In the last 7 days
</h2> </h2>
<div class="grid-row"> <div class="grid-row bottom-gutter">
<div class="column-half"> <div class="column-half">
{{ big_number_with_status( {{ big_number_with_status(
statistics.get('emails_requested', 0), statistics.get('emails_delivered', 0),
'email' if statistics.get('emails_requested') == 1 else 'emails', 'email' if statistics.get('emails_delivered') == 1 else 'emails',
statistics.get('emails_failed'), statistics.get('emails_failed'),
statistics.get('emails_failure_rate', 0.0), statistics.get('emails_failure_rate', 0.0),
statistics.get('emails_failure_rate', 0)|float > 3 statistics.get('emails_failure_rate', 0)|float > 3,
failure_link=url_for(".view_notifications", service_id=current_service.id, template_type='email', status='failed')
) }} ) }}
</div> </div>
<div class="column-half"> <div class="column-half">
@@ -19,10 +20,10 @@
'text message' if statistics.get('sms_requested') == 1 else 'text messages', 'text message' if statistics.get('sms_requested') == 1 else 'text messages',
statistics.get('sms_failed'), statistics.get('sms_failed'),
statistics.get('sms_failure_rate', 0.0), statistics.get('sms_failure_rate', 0.0),
statistics.get('sms_failure_rate', 0)|float > 3 statistics.get('sms_failure_rate', 0)|float > 3,
failure_link=url_for(".view_notifications", service_id=current_service.id, template_type='sms', status='failed')
) }} ) }}
</div> </div>
</div> </div>
{% include 'views/dashboard/template-statistics.html' %} {% include 'views/dashboard/template-statistics.html' %}
+3 -3
View File
@@ -77,15 +77,15 @@ def test_should_show_recent_templates_on_dashboard(app_,
assert response.status_code == 200 assert response.status_code == 200
response.get_data(as_text=True) response.get_data(as_text=True)
mock_get_service_statistics.assert_called_once_with(SERVICE_ONE_ID) mock_get_service_statistics.assert_called_once_with(SERVICE_ONE_ID, limit_days=7)
mock_template_stats.assert_called_once_with(SERVICE_ONE_ID) mock_template_stats.assert_called_once_with(SERVICE_ONE_ID)
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser') page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
headers = [header.text.strip() for header in page.find_all('h2')] headers = [header.text.strip() for header in page.find_all('h2')]
assert 'Test Service' in headers assert 'Test Service' in headers
assert 'Sent today' in headers assert 'In the last 7 days' in headers
template_usage_headers = [th.text.strip() for th in page.thead.find_all('th')] template_usage_headers = [th.text.strip() for th in page.thead.find_all('th')]
for th in ['Template', 'Type', 'Messages sent']: for th in ['Template', 'Type', 'Messages processed']:
assert th in template_usage_headers assert th in template_usage_headers
table_rows = page.tbody.find_all('tr') table_rows = page.tbody.find_all('tr')
+1 -1
View File
@@ -172,7 +172,7 @@ def mock_delete_service(mocker, mock_get_service):
@pytest.fixture(scope='function') @pytest.fixture(scope='function')
def mock_get_service_statistics(mocker): def mock_get_service_statistics(mocker):
def _create(service_id): def _create(service_id, limit_days=None):
return {'data': [{}]} return {'data': [{}]}
return mocker.patch( return mocker.patch(