mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-01 20:29:49 -04:00
Merge pull request #1094 from alphagov/usage-by-year
Filter and navigate usage by financial year
This commit is contained in:
@@ -19,7 +19,7 @@ from app import (
|
||||
template_statistics_client
|
||||
)
|
||||
from app.statistics_utils import get_formatted_percentage, add_rate_to_job
|
||||
from app.utils import user_has_permissions
|
||||
from app.utils import user_has_permissions, get_current_financial_year
|
||||
|
||||
|
||||
# This is a placeholder view method to be replaced
|
||||
@@ -79,17 +79,26 @@ def template_history(service_id):
|
||||
@login_required
|
||||
@user_has_permissions('manage_settings', admin_override=True)
|
||||
def usage(service_id):
|
||||
current_financial_year = get_current_financial_year()
|
||||
try:
|
||||
year = int(request.args.get('year', 2016))
|
||||
year = int(request.args.get('year', current_financial_year))
|
||||
except ValueError:
|
||||
abort(404)
|
||||
return render_template(
|
||||
'views/usage.html',
|
||||
months=get_free_paid_breakdown_for_billable_units(
|
||||
months=list(get_free_paid_breakdown_for_billable_units(
|
||||
year, service_api_client.get_billable_units(service_id, year)
|
||||
),
|
||||
year=year,
|
||||
**calculate_usage(service_api_client.get_service_usage(service_id)['data'])
|
||||
)),
|
||||
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)
|
||||
],
|
||||
**calculate_usage(service_api_client.get_service_usage(service_id, year)['data'])
|
||||
)
|
||||
|
||||
|
||||
@@ -156,7 +165,10 @@ def get_dashboard_partials(service_id):
|
||||
'has_jobs': bool(immediate_jobs),
|
||||
'usage': render_template(
|
||||
'views/dashboard/_usage.html',
|
||||
**calculate_usage(service_api_client.get_service_usage(service_id)['data'])
|
||||
**calculate_usage(service_api_client.get_service_usage(
|
||||
service_id,
|
||||
get_current_financial_year(),
|
||||
)['data'])
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -213,8 +213,11 @@ class ServiceAPIClient(NotifyAdminAPIClient):
|
||||
def get_service_history(self, service_id):
|
||||
return self.get('/service/{0}/history'.format(service_id))
|
||||
|
||||
def get_service_usage(self, service_id):
|
||||
return self.get('/service/{0}/fragment/aggregate_statistics'.format(service_id))
|
||||
def get_service_usage(self, service_id, year=None):
|
||||
return self.get(
|
||||
'/service/{0}/fragment/aggregate_statistics'.format(service_id),
|
||||
params=dict(year=year)
|
||||
)
|
||||
|
||||
def get_weekly_notification_stats(self, service_id):
|
||||
return self.get(url='/service/{}/notifications/weekly'.format(service_id))
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
{% macro pill(
|
||||
title,
|
||||
items=[],
|
||||
current_value=None
|
||||
current_value=None,
|
||||
big_number_args={'smaller': True}
|
||||
) %}
|
||||
<nav role='navigation' class='pill' aria-labelledby="pill_{{title}}">
|
||||
<h2 id="pill_{{title}}" class="visuallyhidden">{{title}}</h2>
|
||||
@@ -13,8 +14,8 @@
|
||||
{% else %}
|
||||
<a href="{{ link }}">
|
||||
{% endif %}
|
||||
{{ big_number(count, smaller=True) }}
|
||||
<div class="pill-label">{{ label }}</div>
|
||||
{{ big_number(count, **big_number_args) }}
|
||||
<div class="pill-label">{{ label }}</div>
|
||||
{% if current_value == option %}
|
||||
</span>
|
||||
{% else %}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{% from "components/big-number.html" import big_number %}
|
||||
{% from "components/table.html" import list_table, field, hidden_field_heading, row_heading, text_field %}
|
||||
{% from "components/pill.html" import pill %}
|
||||
|
||||
{% extends "withnav_template.html" %}
|
||||
|
||||
@@ -9,15 +10,10 @@
|
||||
|
||||
{% block maincolumn_content %}
|
||||
|
||||
<div class='grid-row'>
|
||||
<div class='column-half'>
|
||||
<h2 class='heading-large'>Usage</h2>
|
||||
</div>
|
||||
<div class='column-half'>
|
||||
<div class='align-with-heading-copy'>
|
||||
Financial year {{ year }} to {{ year + 1 }}
|
||||
</div>
|
||||
</div>
|
||||
<h2 class='heading-large'>Usage</h2>
|
||||
|
||||
<div class="bottom-gutter">
|
||||
{{ pill('Year', years, selected_year, big_number_args={'smallest': True}) }}
|
||||
</div>
|
||||
|
||||
<div class='grid-row'>
|
||||
@@ -63,45 +59,51 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-table body-copy-table">
|
||||
{% call(month, row_index) list_table(
|
||||
months,
|
||||
caption="Total spend",
|
||||
caption_visible=False,
|
||||
empty_message='',
|
||||
field_headings=[
|
||||
'By month',
|
||||
hidden_field_heading('Cost'),
|
||||
],
|
||||
field_headings_visible=True
|
||||
) %}
|
||||
{% call row_heading() %}
|
||||
{{ month.name }}
|
||||
{% if months %}
|
||||
<div class="dashboard-table body-copy-table">
|
||||
{% call(month, row_index) list_table(
|
||||
months,
|
||||
caption="Total spend",
|
||||
caption_visible=False,
|
||||
empty_message='',
|
||||
field_headings=[
|
||||
'By month',
|
||||
hidden_field_heading('Cost'),
|
||||
],
|
||||
field_headings_visible=True
|
||||
) %}
|
||||
{% call row_heading() %}
|
||||
{{ month.name }}
|
||||
{% endcall %}
|
||||
{% call field(align='left') %}
|
||||
{{ big_number(
|
||||
sms_rate * month.paid,
|
||||
currency="£",
|
||||
smallest=True
|
||||
) }}
|
||||
<ul>
|
||||
{% if month.free %}
|
||||
<li class="tabular-numbers">{{ "{:,}".format(month.free) }} free text messages</li>
|
||||
{% endif %}
|
||||
{% if month.paid %}
|
||||
<li class="tabular-numbers">{{ "{:,}".format(month.paid) }} text messages at
|
||||
{{- ' {:.2f}p'.format(sms_rate * 100) }}</li>
|
||||
{% endif %}
|
||||
{% if not (month.free or month.paid) %}
|
||||
<li aria-hidden="true">–</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endcall %}
|
||||
{% endcall %}
|
||||
{% call field(align='left') %}
|
||||
{{ big_number(
|
||||
sms_rate * month.paid,
|
||||
currency="£",
|
||||
smallest=True
|
||||
) }}
|
||||
<ul>
|
||||
{% if month.free %}
|
||||
<li class="tabular-numbers">{{ "{:,}".format(month.free) }} free text messages</li>
|
||||
{% endif %}
|
||||
{% if month.paid %}
|
||||
<li class="tabular-numbers">{{ "{:,}".format(month.paid) }} text messages at
|
||||
{{- ' {:.2f}p'.format(sms_rate * 100) }}</li>
|
||||
{% endif %}
|
||||
{% if not (month.free or month.paid) %}
|
||||
<li aria-hidden="true">–</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endcall %}
|
||||
{% endcall %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="grid-row">
|
||||
<div class="column-half"> </div>
|
||||
<div class="column-half">
|
||||
<p class="align-with-heading-copy">
|
||||
Financial year ends 31 March.
|
||||
</p>
|
||||
</div>
|
||||
<div class="column-half">
|
||||
<p class="align-with-heading-copy">
|
||||
What counts as 1 text message?<br />
|
||||
|
||||
@@ -4,6 +4,7 @@ from io import StringIO, BytesIO
|
||||
from os import path
|
||||
from functools import wraps
|
||||
import unicodedata
|
||||
from datetime import datetime
|
||||
|
||||
from flask import (
|
||||
abort,
|
||||
@@ -322,3 +323,10 @@ def png_from_pdf(pdf_endpoint):
|
||||
filename_or_fp=output,
|
||||
mimetype='image/png',
|
||||
)
|
||||
|
||||
|
||||
def get_current_financial_year():
|
||||
now = datetime.utcnow()
|
||||
current_month = int(now.strftime('%-m'))
|
||||
current_year = int(now.strftime('%Y'))
|
||||
return current_year if current_month > 3 else current_year - 1
|
||||
|
||||
@@ -14,6 +14,7 @@ from app.main.views.dashboard import (
|
||||
|
||||
from tests import validate_route_permission
|
||||
from tests.conftest import SERVICE_ONE_ID
|
||||
from tests.app.test_utils import normalize_spaces
|
||||
|
||||
stub_template_stats = [
|
||||
{
|
||||
@@ -233,33 +234,34 @@ def test_should_show_recent_jobs_on_dashboard(
|
||||
assert table_rows[index].find_all('td')[column_index].text.strip() == str(count)
|
||||
|
||||
|
||||
@freeze_time("2016-12-31 11:09:00.061258")
|
||||
@freeze_time("2012-03-31 12:12:12")
|
||||
def test_usage_page(
|
||||
logged_in_client,
|
||||
api_user_active,
|
||||
mock_get_service,
|
||||
mock_get_user,
|
||||
mock_has_permissions,
|
||||
mock_get_usage,
|
||||
mock_get_billable_units,
|
||||
):
|
||||
response = logged_in_client.get(url_for('main.usage', service_id=SERVICE_ONE_ID, year=2000))
|
||||
response = logged_in_client.get(url_for('main.usage', service_id=SERVICE_ONE_ID))
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
mock_get_billable_units.assert_called_once_with(SERVICE_ONE_ID, 2000)
|
||||
mock_get_billable_units.assert_called_once_with(SERVICE_ONE_ID, 2011)
|
||||
mock_get_usage.assert_called_once_with(SERVICE_ONE_ID, 2011)
|
||||
|
||||
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
||||
|
||||
cols = page.find_all('div', {'class': 'column-half'})
|
||||
nav = page.find('nav', {'class': 'pill'})
|
||||
nav_links = nav.find_all('a')
|
||||
|
||||
assert cols[1].text.strip() == 'Financial year 2000 to 2001'
|
||||
assert normalize_spaces(nav_links[0].text) == '2010 to 2011 financial year'
|
||||
assert normalize_spaces(nav.find('span').text) == '2011 to 2012 financial year'
|
||||
assert normalize_spaces(nav_links[1].text) == '2012 to 2013 financial year'
|
||||
|
||||
assert '123' in cols[2].text
|
||||
assert 'Emails' in cols[2].text
|
||||
assert '123' in cols[0].text
|
||||
assert 'Emails' in cols[0].text
|
||||
|
||||
assert '456,123' in cols[3].text
|
||||
assert 'Text messages' in cols[3].text
|
||||
assert '456,123' in cols[1].text
|
||||
assert 'Text messages' in cols[1].text
|
||||
|
||||
table = page.find('table').text.strip()
|
||||
|
||||
@@ -271,13 +273,18 @@ def test_usage_page(
|
||||
assert '206,246 text messages at 1.65p' in table
|
||||
|
||||
|
||||
@freeze_time("2016-12-31 11:09:00.061258")
|
||||
def test_usage_page_with_year_argument(
|
||||
logged_in_client,
|
||||
mock_get_usage,
|
||||
mock_get_billable_units
|
||||
):
|
||||
assert logged_in_client.get(url_for('main.usage', service_id=SERVICE_ONE_ID, year=2000)).status_code == 200
|
||||
mock_get_billable_units.assert_called_once_with(SERVICE_ONE_ID, 2000)
|
||||
mock_get_usage.assert_called_once_with(SERVICE_ONE_ID, 2000)
|
||||
|
||||
|
||||
def test_usage_page_for_invalid_year(
|
||||
logged_in_client,
|
||||
api_user_active,
|
||||
mock_get_service,
|
||||
mock_get_user,
|
||||
mock_has_permissions,
|
||||
):
|
||||
assert logged_in_client.get(url_for('main.usage', service_id=SERVICE_ONE_ID, year='abcd')).status_code == 404
|
||||
|
||||
|
||||
@@ -148,3 +148,7 @@ def test_generate_notifications_csv_formats_row_number_correctly(mocker, row_num
|
||||
|
||||
assert len(csv_rows) == 1
|
||||
assert csv_rows[0].get('Row number') == expected_result
|
||||
|
||||
|
||||
def normalize_spaces(string):
|
||||
return ' '.join(string.split())
|
||||
|
||||
@@ -1256,7 +1256,7 @@ def mock_get_template_statistics_for_template(mocker, service_one):
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def mock_get_usage(mocker, service_one, fake_uuid):
|
||||
def _get_usage(service_id):
|
||||
def _get_usage(service_id, year=None):
|
||||
return {'data': {
|
||||
"sms_count": 456123,
|
||||
"email_count": 123
|
||||
|
||||
Reference in New Issue
Block a user