diff --git a/app/assets/stylesheets/components/table.scss b/app/assets/stylesheets/components/table.scss
index 8f908ddde..085b16e6f 100644
--- a/app/assets/stylesheets/components/table.scss
+++ b/app/assets/stylesheets/components/table.scss
@@ -189,3 +189,13 @@
a.table-show-more-link {
color: $link-colour;
}
+
+.table-no-data {
+ @include core-16;
+ color: $secondary-text-colour;
+ margin-top: 10px;
+ margin-bottom: $gutter * 1.3333;
+ border-top: 1px solid $border-colour;
+ border-bottom: 1px solid $border-colour;
+ padding: 0.75em 0 0.5625em 0;
+}
diff --git a/app/assets/stylesheets/views/dashboard.scss b/app/assets/stylesheets/views/dashboard.scss
index 0eee9993d..a99623384 100644
--- a/app/assets/stylesheets/views/dashboard.scss
+++ b/app/assets/stylesheets/views/dashboard.scss
@@ -81,3 +81,16 @@
@include bold-19;
color: $error-colour;
}
+
+.template-usage-table {
+
+ border-top: 1px solid $border-colour;
+ border-bottom: 1px solid $border-colour;
+ margin-top: 10px;
+ margin-bottom: $gutter * 1.3333;
+
+ .table {
+ margin-bottom: 5px;
+ }
+
+}
diff --git a/app/main/views/dashboard.py b/app/main/views/dashboard.py
index bca543a31..c3000324e 100644
--- a/app/main/views/dashboard.py
+++ b/app/main/views/dashboard.py
@@ -1,5 +1,5 @@
from datetime import datetime
-
+from functools import partial
from flask import (
render_template,
url_for,
@@ -65,16 +65,35 @@ def service_dashboard_updates(service_id):
@login_required
@user_has_permissions('view_activity', admin_override=True)
def template_history(service_id):
- template_statistics = aggregate_usage(
- template_statistics_client.get_template_statistics_for_service(service_id)
- )
+
+ year, current_financial_year = requested_and_current_financial_year(request)
+ stats = template_statistics_client.get_monthly_template_statistics_for_service(service_id, year)
+
+ months = [
+ {
+ 'name': YYYY_MM_to_datetime(month).strftime('%B'),
+ 'templates_used': aggregate_usage(
+ format_template_stats_to_list(stats.get(month)), sort_key='requested_count'
+ ),
+ }
+ for month in get_months_for_financial_year(year, time_format='%Y-%m')
+ ]
return render_template(
'views/dashboard/all-template-statistics.html',
- template_statistics=template_statistics,
+ months=months,
most_used_template_count=max(
- [row['count'] for row in template_statistics] or [0]
- )
+ max((
+ template['requested_count']
+ for template in month['templates_used']
+ ), default=0)
+ for month in months
+ ),
+ years=get_tuples_of_financial_years(
+ partial(url_for, '.template_history', service_id=service_id),
+ end=current_financial_year,
+ ),
+ selected_year=year,
)
@@ -89,14 +108,11 @@ def usage(service_id):
year, service_api_client.get_billable_units(service_id, year)
)),
selected_year=year,
- years=[
- (
- 'financial year',
- year,
- url_for('.usage', service_id=service_id, year=year),
- '{} to {}'.format(year, year + 1),
- ) for year in range(current_financial_year - 1, current_financial_year + 2)
- ],
+ years=get_tuples_of_financial_years(
+ partial(url_for, '.usage', service_id=service_id),
+ start=current_financial_year - 1,
+ end=current_financial_year + 1,
+ ),
**calculate_usage(service_api_client.get_service_usage(service_id, year)['data'])
)
@@ -111,22 +127,18 @@ def monthly(service_id):
months=format_monthly_stats_to_list(
service_api_client.get_monthly_notification_stats(service_id, year)['data']
),
- years=[
- (
- 'financial year',
- year,
- url_for('.monthly', service_id=service_id, year=year),
- '{} to {}'.format(year, year + 1),
- ) for year in range(2015, current_financial_year + 1)
- ],
+ years=get_tuples_of_financial_years(
+ partial_url=partial(url_for, '.monthly', service_id=service_id),
+ end=current_financial_year,
+ ),
selected_year=year,
)
-def aggregate_usage(template_statistics):
+def aggregate_usage(template_statistics, sort_key='count'):
return sorted(
template_statistics,
- key=lambda template_statistic: template_statistic['count'],
+ key=lambda template_statistic: template_statistic[sort_key],
reverse=True
)
@@ -235,9 +247,9 @@ def aggregate_status_types(counts_dict):
})
-def get_months_for_financial_year(year):
+def get_months_for_financial_year(year, time_format='%B'):
return [
- month.strftime('%B')
+ month.strftime(time_format)
for month in (
get_months_for_year(4, 13, year) +
get_months_for_year(1, 4, year + 1)
@@ -298,3 +310,32 @@ def requested_and_current_financial_year(request):
)
except ValueError:
abort(404)
+
+
+def format_template_stats_to_list(stats_dict):
+ if not stats_dict:
+ return []
+ for template_id, template in stats_dict.items():
+ yield dict(
+ requested_count=sum(
+ template['counts'].get(status, 0)
+ for status in REQUESTED_STATUSES
+ ),
+ **template
+ )
+
+
+def get_tuples_of_financial_years(
+ partial_url,
+ start=2015,
+ end=None,
+):
+ return (
+ (
+ 'financial year',
+ year,
+ partial_url(year=year),
+ '{} to {}'.format(year, year + 1),
+ )
+ for year in range(start, end + 1)
+ )
diff --git a/app/notify_client/template_statistics_api_client.py b/app/notify_client/template_statistics_api_client.py
index 91dc7446c..fc44fa7b6 100644
--- a/app/notify_client/template_statistics_api_client.py
+++ b/app/notify_client/template_statistics_api_client.py
@@ -20,6 +20,12 @@ class TemplateStatisticsApiClient(NotifyAdminAPIClient):
params=params
)['data']
+ def get_monthly_template_statistics_for_service(self, service_id, year):
+
+ return self.get(
+ url='/service/{}/notifications/templates/monthly?year={}'.format(service_id, year)
+ )['data']
+
def get_template_statistics_for_template(self, service_id, template_id):
return self.get(
diff --git a/app/templates/components/table.html b/app/templates/components/table.html
index 51974483e..62f74e851 100644
--- a/app/templates/components/table.html
+++ b/app/templates/components/table.html
@@ -1,3 +1,5 @@
+{% from "components/big-number.html" import big_number %}
+
{% macro mapping_table(caption='', field_headings=[], field_headings_visible=True, caption_visible=True) -%}
@@ -129,3 +131,20 @@
{% endcall %}
{% endmacro %}
+
+
+{% macro spark_bar_field(
+ count,
+ max_count
+) %}
+ {% call field(align='right') %}
+
+
+ {{ big_number(
+ count,
+ smallest=True
+ ) }}
+
+
+ {% endcall %}
+{% endmacro %}
diff --git a/app/templates/views/dashboard/all-template-statistics.html b/app/templates/views/dashboard/all-template-statistics.html
index 39857108f..a0c803fce 100644
--- a/app/templates/views/dashboard/all-template-statistics.html
+++ b/app/templates/views/dashboard/all-template-statistics.html
@@ -1,13 +1,58 @@
+{% from "components/message-count-label.html" import message_count_label %}
+{% from "components/pill.html" import pill %}
+{% from "components/table.html" import list_table, field, right_aligned_field_heading, row_heading, spark_bar_field %}
+
{% extends "withnav_template.html" %}
{% block service_page_title %}
- All templates used this year
+ Templates used
{% endblock %}
{% block maincolumn_content %}
- All templates used this year
+ Templates used
- {% include 'views/dashboard/template-statistics.html' %}
+
+ {{ pill(
+ items=years,
+ current_value=selected_year,
+ big_number_args={'smallest': True},
+ ) }}
+
+
+
+ {% for month in months %}
+
{{ month.name }}
+ {% if not month.templates_used %}
+
+ No messages sent
+
+ {% else %}
+
+ {% call(item, row_number) list_table(
+ month.templates_used,
+ caption=month.name,
+ caption_visible=False,
+ empty_message='',
+ field_headings=[
+ 'Template',
+ 'Messages sent'
+ ],
+ field_headings_visible=False
+ ) %}
+ {% call row_heading() %}
+
+ {{ item.name }}
+
+ {{ message_count_label(1, item.type, suffix='template')|capitalize }}
+
+
+ {% endcall %}
+ {{ spark_bar_field(item.requested_count, most_used_template_count) }}
+ {% endcall %}
+
+ {% endif %}
+ {% endfor %}
+
{% endblock %}
diff --git a/app/templates/views/dashboard/template-statistics.html b/app/templates/views/dashboard/template-statistics.html
index c94ec4e6b..9f3e0fab7 100644
--- a/app/templates/views/dashboard/template-statistics.html
+++ b/app/templates/views/dashboard/template-statistics.html
@@ -1,7 +1,7 @@
{% from "components/big-number.html" import big_number %}
{% from "components/message-count-label.html" import message_count_label %}
{% from "components/big-number.html" import big_number %}
-{% from "components/table.html" import list_table, field, right_aligned_field_heading, row_heading %}
+{% from "components/table.html" import list_table, field, right_aligned_field_heading, row_heading, spark_bar_field %}
{% call(item, row_number) list_table(
@@ -25,16 +25,7 @@
{% endcall %}
{% if template_statistics|length > 1 %}
- {% call field(align='right') %}
-
-
- {{ big_number(
- item.count,
- smallest=True
- ) }}
-
-
- {% endcall %}
+ {{ spark_bar_field(item.count, most_used_template_count) }}
{% else %}
{% call field() %}
diff --git a/tests/app/main/views/test_dashboard.py b/tests/app/main/views/test_dashboard.py
index 781ab73f7..b7143a7d3 100644
--- a/tests/app/main/views/test_dashboard.py
+++ b/tests/app/main/views/test_dashboard.py
@@ -1,4 +1,5 @@
from datetime import datetime
+from functools import partial
import copy
from flask import url_for
@@ -120,39 +121,40 @@ def test_should_show_recent_templates_on_dashboard(
assert '100' in table_rows[1].find_all('td')[0].text
-def test_should_show_all_templates_on_template_statistics_page(
+@freeze_time("2016-07-01 12:00") # 4 months into 2016 financial year
+@pytest.mark.parametrize('partial_url', [
+ partial(url_for),
+ partial(url_for, year='2016'),
+])
+def test_should_show_monthly_breakdown_of_template_usage(
logged_in_client,
mocker,
api_user_active,
mock_get_service,
- mock_get_service_templates,
+ mock_get_monthly_template_statistics,
mock_get_user,
mock_get_user_by_email,
- mock_login,
- mock_get_jobs,
mock_has_permissions,
+ partial_url,
):
- mock_template_stats = mocker.patch('app.template_statistics_client.get_template_statistics_for_service',
- return_value=copy.deepcopy(stub_template_stats))
-
- response = logged_in_client.get(url_for('main.template_history', service_id=SERVICE_ONE_ID))
+ response = logged_in_client.get(
+ partial_url('main.template_history', service_id=SERVICE_ONE_ID, _external=True)
+ )
assert response.status_code == 200
- response.get_data(as_text=True)
- mock_template_stats.assert_called_once_with(SERVICE_ONE_ID)
+ mock_get_monthly_template_statistics.assert_called_once_with(SERVICE_ONE_ID, 2016)
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
- table_rows = page.find_all('tbody')[0].find_all('tr')
+ table_rows = page.select('tbody tr')
- assert len(table_rows) == 2
+ assert ' '.join(table_rows[0].text.split()) == (
+ 'My first template '
+ 'Text message template '
+ '2'
+ )
- assert 'two' in table_rows[0].find_all('th')[0].text
- assert 'Email template' in table_rows[0].find_all('th')[0].text
- assert '200' in table_rows[0].find_all('td')[0].text
-
- assert 'one' in table_rows[1].find_all('th')[0].text
- assert 'Text message template' in table_rows[1].find_all('th')[0].text
- assert '100' in table_rows[1].find_all('td')[0].text
+ assert len(table_rows) == len(['April'])
+ assert len(page.select('.table-no-data')) == len(['May', 'June', 'July'])
@freeze_time("2016-01-01 11:09:00.061258")
diff --git a/tests/conftest.py b/tests/conftest.py
index 0da1bcf65..c9c4d1c63 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1277,6 +1277,28 @@ def mock_get_template_statistics(mocker, service_one, fake_uuid):
'app.template_statistics_client.get_template_statistics_for_service', side_effect=_get_stats)
+@pytest.fixture(scope='function')
+def mock_get_monthly_template_statistics(mocker, service_one, fake_uuid):
+ def _stats(service_id, year):
+ return {
+ datetime.utcnow().strftime('%Y-%m'): {
+ fake_uuid: {
+ "counts": {
+ "sending": 1,
+ "delivered": 1,
+ },
+ "name": 'My first template',
+ "type": 'sms',
+ "id": fake_uuid,
+ }
+ }
+ }
+ return mocker.patch(
+ 'app.template_statistics_client.get_monthly_template_statistics_for_service',
+ side_effect=_stats
+ )
+
+
@pytest.fixture(scope='function')
def mock_get_template_statistics_for_template(mocker, service_one):
def _get_stats(service_id, template_id):