mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-09-11 10:28:41 -04:00
Add usage to the dashboard
Takes the number of emails and SMS fragments sent from: https://github.com/alphagov/notifications-api/pull/273 Using these numbers it’s possible to show: - how much of your allowance is left - or how much you have spent For now the allowance and rates are hard coded. Only for users that have manage service.
This commit is contained in:
@@ -35,6 +35,11 @@
|
|||||||
padding-right: 2px;
|
padding-right: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.align-with-heading-copy {
|
||||||
|
display: block;
|
||||||
|
margin-top: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
.global-cookie-message {
|
.global-cookie-message {
|
||||||
p {
|
p {
|
||||||
@extend %site-width-container;
|
@extend %site-width-container;
|
||||||
|
|||||||
@@ -11,6 +11,12 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.big-number-right-aligned {
|
||||||
|
@extend %big-number;
|
||||||
|
@include bold-36($tabular-numbers: true);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
.big-number-with-status {
|
.big-number-with-status {
|
||||||
|
|
||||||
@extend %big-number;
|
@extend %big-number;
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ $path: '/static/images/';
|
|||||||
@import 'views/job';
|
@import 'views/job';
|
||||||
@import 'views/edit-template';
|
@import 'views/edit-template';
|
||||||
@import 'views/documenation';
|
@import 'views/documenation';
|
||||||
|
@import 'views/dashboard';
|
||||||
|
|
||||||
// TODO: break this up
|
// TODO: break this up
|
||||||
@import 'app';
|
@import 'app';
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
.dashboard {
|
||||||
|
table th {
|
||||||
|
@include bold-19;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.keyline-block {
|
||||||
|
|
||||||
|
border-top: 1px solid $border-colour;
|
||||||
|
padding-top: $gutter-half;
|
||||||
|
|
||||||
|
&-show-more-link {
|
||||||
|
@include core-16;
|
||||||
|
border-top: 1px solid $border-colour;
|
||||||
|
border-bottom: 1px solid $border-colour;
|
||||||
|
padding: 10px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -80,6 +80,16 @@ def template_history(service_id):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@main.route("/services/<service_id>/usage")
|
||||||
|
@login_required
|
||||||
|
@user_has_permissions('manage_settings', admin_override=True)
|
||||||
|
def usage(service_id):
|
||||||
|
return render_template(
|
||||||
|
'views/usage.html',
|
||||||
|
**get_dashboard_statistics_for_service(service_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def add_rates_to(delivery_statistics):
|
def add_rates_to(delivery_statistics):
|
||||||
|
|
||||||
keys = [
|
keys = [
|
||||||
@@ -148,11 +158,25 @@ def aggregate_usage(template_statistics):
|
|||||||
|
|
||||||
|
|
||||||
def get_dashboard_statistics_for_service(service_id):
|
def get_dashboard_statistics_for_service(service_id):
|
||||||
|
|
||||||
|
usage = service_api_client.get_service_usage(service_id)
|
||||||
|
sms_free_allowance = 250000
|
||||||
|
sms_rate = 0.018
|
||||||
|
|
||||||
|
sms_sent = usage['data'].get('sms_count', 0)
|
||||||
|
emails_sent = usage['data'].get('email_count', 0)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'statistics': add_rates_to(
|
'statistics': add_rates_to(
|
||||||
statistics_api_client.get_statistics_for_service(service_id, limit_days=7)['data']
|
statistics_api_client.get_statistics_for_service(service_id, limit_days=7)['data']
|
||||||
),
|
),
|
||||||
'template_statistics': aggregate_usage(
|
'template_statistics': aggregate_usage(
|
||||||
template_statistics_client.get_template_statistics_for_service(service_id, limit_days=7)
|
template_statistics_client.get_template_statistics_for_service(service_id, limit_days=7)
|
||||||
)
|
),
|
||||||
|
'emails_sent': emails_sent,
|
||||||
|
'sms_free_allowance': sms_free_allowance,
|
||||||
|
'sms_sent': sms_sent,
|
||||||
|
'sms_allowance_remaining': max(0, (sms_free_allowance - sms_sent)),
|
||||||
|
'sms_chargeable': max(0, sms_sent - sms_free_allowance),
|
||||||
|
'sms_rate': sms_rate,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,6 +159,9 @@ class ServiceAPIClient(NotificationsAPIClient):
|
|||||||
def get_service_history(self, service_id):
|
def get_service_history(self, service_id):
|
||||||
return self.get('/service/{0}/history'.format(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))
|
||||||
|
|
||||||
|
|
||||||
class ServicesBrowsableItem(BrowsableItem):
|
class ServicesBrowsableItem(BrowsableItem):
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
{% macro big_number(number, label, label_link=None) %}
|
{% macro big_number(number, label, label_link=None, currency='', right_aligned=False) %}
|
||||||
<div class="big-number{% if right_aligned %}-right-aligned{% endif %}">
|
<div class="big-number{% if right_aligned %}-right-aligned{% endif %}">
|
||||||
{% if number is number %}
|
{% if number is number %}
|
||||||
{{ "{:,}".format(number) }}
|
{% if currency %}
|
||||||
|
{{ "{}{:,.2f}".format(currency, number) }}
|
||||||
|
{% else %}
|
||||||
|
{{ "{}{:,}".format(currency, number) }}
|
||||||
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ number }}
|
{{ number }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
{% block maincolumn_content %}
|
{% block maincolumn_content %}
|
||||||
<h1 class='heading-large'>
|
<h1 class='heading-large'>
|
||||||
Templates used this year
|
Templates sent this year
|
||||||
</h1>
|
</h1>
|
||||||
<p>
|
<p>
|
||||||
1 April 2016 to date
|
1 April 2016 to date
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
{% block maincolumn_content %}
|
{% block maincolumn_content %}
|
||||||
|
|
||||||
|
<div class="dashboard">
|
||||||
|
|
||||||
{% if not templates and current_user.has_permissions(['send_texts', 'send_emails', 'send_letters'], any_=True) %}
|
{% if not templates and current_user.has_permissions(['send_texts', 'send_emails', 'send_letters'], any_=True) %}
|
||||||
{% include 'views/dashboard/get-started.html' %}
|
{% include 'views/dashboard/get-started.html' %}
|
||||||
{% elif current_user.has_permissions([
|
{% elif current_user.has_permissions([
|
||||||
@@ -20,4 +22,6 @@
|
|||||||
|
|
||||||
{% include 'views/dashboard/today.html' %}
|
{% include 'views/dashboard/today.html' %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{% from "components/big-number.html" import big_number_with_status %}
|
{% from "components/big-number.html" import big_number, big_number_with_status %}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
data-module="update-content"
|
data-module="update-content"
|
||||||
@@ -38,6 +38,36 @@
|
|||||||
{% include 'views/dashboard/template-statistics.html' %}
|
{% include 'views/dashboard/template-statistics.html' %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
<p class='table-show-more-link'>
|
<p class='table-show-more-link'>
|
||||||
<a href="{{ url_for('.template_history', service_id=current_service.id) }}">See all templates used this year</a>
|
<a href="{{ url_for('.template_history', service_id=current_service.id) }}">See all templates sent this year</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
{% if current_user.has_permissions(['manage_settings'], admin_override=True) %}
|
||||||
|
<h2 class='heading-medium' style="margin-top: 30px;">This year</h2>
|
||||||
|
|
||||||
|
<div class='grid-row'>
|
||||||
|
<div class='column-half'>
|
||||||
|
<div class="keyline-block">
|
||||||
|
{{ big_number("Unlimited", 'free email allowance', right_aligned=True) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class='column-half'>
|
||||||
|
<div class="keyline-block">
|
||||||
|
{% if sms_chargeable %}
|
||||||
|
{{ big_number(
|
||||||
|
(sms_chargeable * sms_rate),
|
||||||
|
'spent on text messages',
|
||||||
|
currency="£",
|
||||||
|
right_aligned=True
|
||||||
|
) }}
|
||||||
|
{% else %}
|
||||||
|
{{ big_number(sms_allowance_remaining, 'free text messages left', right_aligned=True) }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="keyline-block-show-more-link">
|
||||||
|
<a href="{{ url_for(".usage", service_id=current_service['id']) }}">See usage</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
{% from "components/big-number.html" import big_number %}
|
||||||
|
|
||||||
|
{% extends "withnav_template.html" %}
|
||||||
|
|
||||||
|
{% block page_title %}
|
||||||
|
Billing – GOV.UK Notify
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block maincolumn_content %}
|
||||||
|
|
||||||
|
<div class='grid-row'>
|
||||||
|
<div class='column-half'>
|
||||||
|
<h2 class='heading-large'>Usage</h2>
|
||||||
|
</div>
|
||||||
|
<div class='column-half'>
|
||||||
|
<span class="align-with-heading-copy">1 April 2016 to date</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class='grid-row'>
|
||||||
|
<div class='column-half'>
|
||||||
|
<h2 class='heading-small'>Emails</h2>
|
||||||
|
<div class="keyline-block">
|
||||||
|
{{ big_number(emails_sent, 'sent', right_aligned=True) }}
|
||||||
|
{{ big_number("Unlimited", 'free allowance', right_aligned=True) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class='column-half'>
|
||||||
|
<h2 class='heading-small'>Text messages</h2>
|
||||||
|
<div class="keyline-block">
|
||||||
|
{{ big_number(sms_sent, 'sent', right_aligned=True) }}
|
||||||
|
{{ big_number(sms_free_allowance, 'free allowance', right_aligned=True) }}
|
||||||
|
{{ big_number(sms_allowance_remaining, 'free allowance remaining', right_aligned=True) }}
|
||||||
|
{% if sms_chargeable %}
|
||||||
|
{{ big_number(
|
||||||
|
sms_chargeable,
|
||||||
|
'at {:.1f}p per message'.format(sms_rate * 100),
|
||||||
|
right_aligned=True
|
||||||
|
) }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class='grid-row'>
|
||||||
|
<div class='column-half'>
|
||||||
|
<div class="keyline-block">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class='column-half'>
|
||||||
|
<div class="keyline-block bottom-gutter">
|
||||||
|
{{ big_number(
|
||||||
|
(sms_chargeable * sms_rate),
|
||||||
|
'spent',
|
||||||
|
currency="£",
|
||||||
|
right_aligned=True
|
||||||
|
) }}
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
What counts as 1 text message?<br />
|
||||||
|
See <a href="{{ url_for('.pricing') }}">pricing</a>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@@ -65,7 +65,8 @@ def test_should_show_recent_templates_on_dashboard(app_,
|
|||||||
mock_get_user_by_email,
|
mock_get_user_by_email,
|
||||||
mock_login,
|
mock_login,
|
||||||
mock_get_jobs,
|
mock_get_jobs,
|
||||||
mock_has_permissions):
|
mock_has_permissions,
|
||||||
|
mock_get_usage):
|
||||||
|
|
||||||
mock_template_stats = mocker.patch('app.template_statistics_client.get_template_statistics_for_service',
|
mock_template_stats = mocker.patch('app.template_statistics_client.get_template_statistics_for_service',
|
||||||
return_value=copy.deepcopy(stub_template_stats))
|
return_value=copy.deepcopy(stub_template_stats))
|
||||||
@@ -164,7 +165,8 @@ def test_menu_send_messages(mocker,
|
|||||||
service_one,
|
service_one,
|
||||||
mock_get_service_templates,
|
mock_get_service_templates,
|
||||||
mock_get_jobs,
|
mock_get_jobs,
|
||||||
mock_get_template_statistics):
|
mock_get_template_statistics,
|
||||||
|
mock_get_usage):
|
||||||
|
|
||||||
with app_.test_request_context():
|
with app_.test_request_context():
|
||||||
resp = _test_dashboard_menu(
|
resp = _test_dashboard_menu(
|
||||||
@@ -197,7 +199,8 @@ def test_menu_manage_service(mocker,
|
|||||||
service_one,
|
service_one,
|
||||||
mock_get_service_templates,
|
mock_get_service_templates,
|
||||||
mock_get_jobs,
|
mock_get_jobs,
|
||||||
mock_get_template_statistics):
|
mock_get_template_statistics,
|
||||||
|
mock_get_usage):
|
||||||
with app_.test_request_context():
|
with app_.test_request_context():
|
||||||
resp = _test_dashboard_menu(
|
resp = _test_dashboard_menu(
|
||||||
mocker,
|
mocker,
|
||||||
@@ -229,7 +232,8 @@ def test_menu_manage_api_keys(mocker,
|
|||||||
service_one,
|
service_one,
|
||||||
mock_get_service_templates,
|
mock_get_service_templates,
|
||||||
mock_get_jobs,
|
mock_get_jobs,
|
||||||
mock_get_template_statistics):
|
mock_get_template_statistics,
|
||||||
|
mock_get_usage):
|
||||||
with app_.test_request_context():
|
with app_.test_request_context():
|
||||||
resp = _test_dashboard_menu(
|
resp = _test_dashboard_menu(
|
||||||
mocker,
|
mocker,
|
||||||
@@ -260,7 +264,8 @@ def test_menu_all_services_for_platform_admin_user(mocker,
|
|||||||
service_one,
|
service_one,
|
||||||
mock_get_service_templates,
|
mock_get_service_templates,
|
||||||
mock_get_jobs,
|
mock_get_jobs,
|
||||||
mock_get_template_statistics):
|
mock_get_template_statistics,
|
||||||
|
mock_get_usage):
|
||||||
with app_.test_request_context():
|
with app_.test_request_context():
|
||||||
resp = _test_dashboard_menu(
|
resp = _test_dashboard_menu(
|
||||||
mocker,
|
mocker,
|
||||||
@@ -295,7 +300,8 @@ def test_route_for_service_permissions(mocker,
|
|||||||
mock_get_service_templates,
|
mock_get_service_templates,
|
||||||
mock_get_jobs,
|
mock_get_jobs,
|
||||||
mock_get_service_statistics,
|
mock_get_service_statistics,
|
||||||
mock_get_template_statistics):
|
mock_get_template_statistics,
|
||||||
|
mock_get_usage):
|
||||||
routes = [
|
routes = [
|
||||||
'main.service_dashboard']
|
'main.service_dashboard']
|
||||||
with app_.test_request_context():
|
with app_.test_request_context():
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ def test_sign_out_user(app_,
|
|||||||
mock_get_service_statistics,
|
mock_get_service_statistics,
|
||||||
mock_get_jobs,
|
mock_get_jobs,
|
||||||
mock_has_permissions,
|
mock_has_permissions,
|
||||||
mock_get_template_statistics):
|
mock_get_template_statistics,
|
||||||
|
mock_get_usage):
|
||||||
with app_.test_request_context():
|
with app_.test_request_context():
|
||||||
with app_.test_client() as client:
|
with app_.test_client() as client:
|
||||||
client.login(api_user_active)
|
client.login(api_user_active)
|
||||||
|
|||||||
@@ -898,6 +898,19 @@ def mock_get_template_statistics(mocker, service_one, fake_uuid):
|
|||||||
'app.template_statistics_client.get_template_statistics_for_service', side_effect=_get_stats)
|
'app.template_statistics_client.get_template_statistics_for_service', side_effect=_get_stats)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='function')
|
||||||
|
def mock_get_usage(mocker, service_one, fake_uuid):
|
||||||
|
|
||||||
|
def _get_usage(service_id):
|
||||||
|
return {'data': {
|
||||||
|
"sms_count": 123,
|
||||||
|
"email_count": 456
|
||||||
|
}}
|
||||||
|
|
||||||
|
return mocker.patch(
|
||||||
|
'app.service_api_client.get_service_usage', side_effect=_get_usage)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
@pytest.fixture(scope='function')
|
||||||
def mock_events(mocker):
|
def mock_events(mocker):
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user