mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-24 08:16:51 -04:00
Merge pull request #3335 from alphagov/style-org-usage
Style the organisation usage page
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
from flask import flash, redirect, render_template, request, session, url_for
|
from flask import flash, redirect, render_template, request, session, url_for
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
@@ -33,6 +34,10 @@ from app.main.forms import (
|
|||||||
SetEmailBranding,
|
SetEmailBranding,
|
||||||
SetLetterBranding,
|
SetLetterBranding,
|
||||||
)
|
)
|
||||||
|
from app.main.views.dashboard import (
|
||||||
|
get_tuples_of_financial_years,
|
||||||
|
requested_and_current_financial_year,
|
||||||
|
)
|
||||||
from app.main.views.service_settings import get_branding_as_value_and_label
|
from app.main.views.service_settings import get_branding_as_value_and_label
|
||||||
from app.models.organisation import Organisation, Organisations
|
from app.models.organisation import Organisation, Organisations
|
||||||
from app.models.user import InvitedOrgUser, User
|
from app.models.user import InvitedOrgUser, User
|
||||||
@@ -125,10 +130,23 @@ def add_organisation_from_nhs_local_service(service_id):
|
|||||||
@main.route("/organisations/<uuid:org_id>", methods=['GET'])
|
@main.route("/organisations/<uuid:org_id>", methods=['GET'])
|
||||||
@user_has_permissions()
|
@user_has_permissions()
|
||||||
def organisation_dashboard(org_id):
|
def organisation_dashboard(org_id):
|
||||||
services = current_organisation.services_and_usage()
|
year, current_financial_year = requested_and_current_financial_year(request)
|
||||||
|
services = current_organisation.services_and_usage(
|
||||||
|
financial_year=year
|
||||||
|
)['services']
|
||||||
return render_template(
|
return render_template(
|
||||||
'views/organisations/organisation/index.html',
|
'views/organisations/organisation/index.html',
|
||||||
services=services
|
services=services,
|
||||||
|
years=get_tuples_of_financial_years(
|
||||||
|
partial(url_for, '.organisation_dashboard', org_id=current_organisation.id),
|
||||||
|
start=current_financial_year - 1,
|
||||||
|
end=current_financial_year + 1,
|
||||||
|
),
|
||||||
|
selected_year=year,
|
||||||
|
**{
|
||||||
|
f'total_{key}': sum(service[key] for service in services)
|
||||||
|
for key in ('emails_sent', 'sms_cost', 'letter_cost')
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ from app.models import JSONModel, ModelList
|
|||||||
from app.notify_client.email_branding_client import email_branding_client
|
from app.notify_client.email_branding_client import email_branding_client
|
||||||
from app.notify_client.letter_branding_client import letter_branding_client
|
from app.notify_client.letter_branding_client import letter_branding_client
|
||||||
from app.notify_client.organisations_api_client import organisations_client
|
from app.notify_client.organisations_api_client import organisations_client
|
||||||
from app.utils import get_current_financial_year
|
|
||||||
|
|
||||||
|
|
||||||
class Organisation(JSONModel):
|
class Organisation(JSONModel):
|
||||||
@@ -199,8 +198,8 @@ class Organisation(JSONModel):
|
|||||||
self.id
|
self.id
|
||||||
)
|
)
|
||||||
|
|
||||||
def services_and_usage(self):
|
def services_and_usage(self, financial_year):
|
||||||
return organisations_client.get_services_and_usage(self.id, get_current_financial_year())
|
return organisations_client.get_services_and_usage(self.id, financial_year)
|
||||||
|
|
||||||
|
|
||||||
class Organisations(ModelList):
|
class Organisations(ModelList):
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
{% from "components/big-number.html" import big_number %}
|
||||||
|
{% from "components/pill.html" import pill %}
|
||||||
{% extends "org_template.html" %}
|
{% extends "org_template.html" %}
|
||||||
|
|
||||||
{% block org_page_title %}
|
{% block org_page_title %}
|
||||||
@@ -10,17 +12,81 @@
|
|||||||
Usage
|
Usage
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<ul>
|
<div class="bottom-gutter-3-2">
|
||||||
{% for service in services['services'] %}
|
{{ pill(years, selected_year, big_number_args={'smallest': True}) }}
|
||||||
<li class="browse-list-item">
|
</div>
|
||||||
<a href="{{ url_for('main.usage', service_id=service.service_id) }}" class="govuk-link govuk-link--no-visited-state browse-list-link">{{ service.service_name }}</a>
|
|
||||||
</li>
|
<div class="govuk-grid-row bottom-gutter">
|
||||||
<div class="grid-row">
|
<div class="govuk-grid-column-one-third">
|
||||||
<div class="column-one-third">{{ service.emails_sent|format_thousands }} emails sent</div>
|
<div class="keyline-block">
|
||||||
<div class="column-one-third">{{ "£{:,.2f}".format(service.sms_cost) }} spent on text messages</div>
|
{{ big_number(
|
||||||
<div class="column-one-third">{{ "£{:,.2f}".format(service.letter_cost) }} spent on letters</div>
|
total_emails_sent,
|
||||||
|
label='emails sent',
|
||||||
|
smaller=True
|
||||||
|
) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="govuk-grid-column-one-third">
|
||||||
|
<div class="keyline-block">
|
||||||
|
{{ big_number(
|
||||||
|
total_sms_cost,
|
||||||
|
'spent on text messages',
|
||||||
|
currency="£",
|
||||||
|
smaller=True
|
||||||
|
) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="govuk-grid-column-one-third">
|
||||||
|
<div class="keyline-block">
|
||||||
|
{{ big_number(
|
||||||
|
total_letter_cost,
|
||||||
|
'spent on letters',
|
||||||
|
currency="£",
|
||||||
|
smaller=True
|
||||||
|
) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% for service in services %}
|
||||||
|
<div class="keyline-block govuk-!-margin-top-2">
|
||||||
|
<h2 class="govuk-!-font-weight-bold govuk-!-font-size-24 govuk-!-margin-bottom-1 govuk-!-margin-top-1">
|
||||||
|
<a href="{{ url_for('main.usage', service_id=service.service_id) }}" class="govuk-link govuk-link--no-visited-state browse-list-link">{{ service.service_name }}</a>
|
||||||
|
</h2>
|
||||||
|
<div class="govuk-grid-row">
|
||||||
|
<div class="govuk-grid-column-one-third">
|
||||||
|
{{ big_number(
|
||||||
|
service.emails_sent,
|
||||||
|
label='emails sent',
|
||||||
|
smallest=True
|
||||||
|
) }}
|
||||||
|
</div>
|
||||||
|
<div class="govuk-grid-column-one-third">
|
||||||
|
{% if service.sms_cost %}
|
||||||
|
{{ big_number(
|
||||||
|
service.sms_cost,
|
||||||
|
'spent on text messages',
|
||||||
|
currency="£",
|
||||||
|
smallest=True
|
||||||
|
) }}
|
||||||
|
{% else %}
|
||||||
|
{{ big_number(
|
||||||
|
service.free_sms_limit - service.sms_remainder,
|
||||||
|
'free text messages sent',
|
||||||
|
smallest=True
|
||||||
|
) }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="govuk-grid-column-one-third">
|
||||||
|
{{ big_number(
|
||||||
|
service.letter_cost,
|
||||||
|
'spent on letters',
|
||||||
|
currency="£",
|
||||||
|
smallest=True
|
||||||
|
) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
<div class="keyline-block govuk-!-margin-top-2"></div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from unittest.mock import ANY, Mock
|
|||||||
import pytest
|
import pytest
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
from freezegun import freeze_time
|
||||||
from notifications_python_client.errors import HTTPError
|
from notifications_python_client.errors import HTTPError
|
||||||
|
|
||||||
from tests import organisation_json, service_json
|
from tests import organisation_json, service_json
|
||||||
@@ -64,7 +65,7 @@ def test_view_organisation_shows_the_correct_organisation(
|
|||||||
'app.organisations_client.get_organisation', return_value=org
|
'app.organisations_client.get_organisation', return_value=org
|
||||||
)
|
)
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
'app.organisations_client.get_services_and_usage', return_value=[]
|
'app.organisations_client.get_services_and_usage', return_value={'services': {}}
|
||||||
)
|
)
|
||||||
|
|
||||||
page = client_request.get(
|
page = client_request.get(
|
||||||
@@ -389,6 +390,7 @@ def test_nhs_local_assigns_to_selected_organisation(
|
|||||||
mock_update_service_organisation.assert_called_once_with(SERVICE_ONE_ID, ORGANISATION_ID)
|
mock_update_service_organisation.assert_called_once_with(SERVICE_ONE_ID, ORGANISATION_ID)
|
||||||
|
|
||||||
|
|
||||||
|
@freeze_time("2020-02-20 20:20")
|
||||||
def test_organisation_services_shows_live_services_and_usage(
|
def test_organisation_services_shows_live_services_and_usage(
|
||||||
client_request,
|
client_request,
|
||||||
mock_get_organisation,
|
mock_get_organisation,
|
||||||
@@ -403,7 +405,7 @@ def test_organisation_services_shows_live_services_and_usage(
|
|||||||
'free_sms_limit': 250000, 'letter_cost': 30.50, 'sms_billable_units': 122, 'sms_cost': 1.93,
|
'free_sms_limit': 250000, 'letter_cost': 30.50, 'sms_billable_units': 122, 'sms_cost': 1.93,
|
||||||
'sms_remainder': None},
|
'sms_remainder': None},
|
||||||
{'service_id': SERVICE_TWO_ID, 'service_name': '5', 'chargeable_billable_sms': 0, 'emails_sent': 20000,
|
{'service_id': SERVICE_TWO_ID, 'service_name': '5', 'chargeable_billable_sms': 0, 'emails_sent': 20000,
|
||||||
'free_sms_limit': 250000, 'letter_cost': 0, 'sms_billable_units': 2500, 'sms_cost': 0.0,
|
'free_sms_limit': 250000, 'letter_cost': 0, 'sms_billable_units': 2500, 'sms_cost': 42.0,
|
||||||
'sms_remainder': None}
|
'sms_remainder': None}
|
||||||
]}
|
]}
|
||||||
)
|
)
|
||||||
@@ -412,20 +414,61 @@ def test_organisation_services_shows_live_services_and_usage(
|
|||||||
page = client_request.get('.organisation_dashboard', org_id=ORGANISATION_ID)
|
page = client_request.get('.organisation_dashboard', org_id=ORGANISATION_ID)
|
||||||
mock.assert_called_once_with(ORGANISATION_ID, 2019)
|
mock.assert_called_once_with(ORGANISATION_ID, 2019)
|
||||||
|
|
||||||
services = page.select('.browse-list-item')
|
services = page.select('main h2')
|
||||||
|
usage_rows = page.select('main .govuk-grid-column-one-third')
|
||||||
assert len(services) == 2
|
assert len(services) == 2
|
||||||
|
|
||||||
|
# Totals
|
||||||
|
assert normalize_spaces(usage_rows[0].text) == "33,000 emails sent"
|
||||||
|
assert normalize_spaces(usage_rows[1].text) == "£43.93 spent on text messages"
|
||||||
|
assert normalize_spaces(usage_rows[2].text) == "£30.50 spent on letters"
|
||||||
|
|
||||||
assert normalize_spaces(services[0].text) == '1'
|
assert normalize_spaces(services[0].text) == '1'
|
||||||
assert normalize_spaces(services[1].text) == '5'
|
assert normalize_spaces(services[1].text) == '5'
|
||||||
assert services[0].find('a')['href'] == url_for('main.usage', service_id=SERVICE_ONE_ID)
|
assert services[0].find('a')['href'] == url_for('main.usage', service_id=SERVICE_ONE_ID)
|
||||||
usage_rows = page.find_all("div", class_="column-one-third")
|
|
||||||
assert normalize_spaces(usage_rows[0].text) == "13,000 emails sent"
|
assert normalize_spaces(usage_rows[3].text) == "13,000 emails sent"
|
||||||
assert normalize_spaces(usage_rows[1].text) == "£1.93 spent on text messages"
|
assert normalize_spaces(usage_rows[4].text) == "£1.93 spent on text messages"
|
||||||
assert normalize_spaces(usage_rows[2].text) == "£30.50 spent on letters"
|
assert normalize_spaces(usage_rows[5].text) == "£30.50 spent on letters"
|
||||||
assert services[1].find('a')['href'] == url_for('main.usage', service_id=SERVICE_TWO_ID)
|
assert services[1].find('a')['href'] == url_for('main.usage', service_id=SERVICE_TWO_ID)
|
||||||
assert normalize_spaces(usage_rows[3].text) == "20,000 emails sent"
|
assert normalize_spaces(usage_rows[6].text) == "20,000 emails sent"
|
||||||
assert normalize_spaces(usage_rows[4].text) == "£0.00 spent on text messages"
|
assert normalize_spaces(usage_rows[7].text) == "£42.00 spent on text messages"
|
||||||
assert normalize_spaces(usage_rows[5].text) == "£0.00 spent on letters"
|
assert normalize_spaces(usage_rows[8].text) == "£0.00 spent on letters"
|
||||||
|
|
||||||
|
|
||||||
|
@freeze_time("2020-02-20 20:20")
|
||||||
|
@pytest.mark.parametrize('financial_year, expected_selected', (
|
||||||
|
(2018, '2018 to 2019 financial year'),
|
||||||
|
(2019, '2019 to 2020 financial year'),
|
||||||
|
(2020, '2020 to 2021 financial year'),
|
||||||
|
))
|
||||||
|
def test_organisation_services_filters_by_financial_year(
|
||||||
|
client_request,
|
||||||
|
mock_get_organisation,
|
||||||
|
mocker,
|
||||||
|
active_user_with_permissions,
|
||||||
|
fake_uuid,
|
||||||
|
financial_year,
|
||||||
|
expected_selected,
|
||||||
|
):
|
||||||
|
mock = mocker.patch(
|
||||||
|
'app.organisations_client.get_services_and_usage',
|
||||||
|
return_value={"services": []}
|
||||||
|
)
|
||||||
|
page = client_request.get(
|
||||||
|
'.organisation_dashboard',
|
||||||
|
org_id=ORGANISATION_ID,
|
||||||
|
year=financial_year,
|
||||||
|
)
|
||||||
|
mock.assert_called_once_with(ORGANISATION_ID, financial_year)
|
||||||
|
assert normalize_spaces(page.select_one('.pill').text) == (
|
||||||
|
'2018 to 2019 financial year '
|
||||||
|
'2019 to 2020 financial year '
|
||||||
|
'2020 to 2021 financial year'
|
||||||
|
)
|
||||||
|
assert normalize_spaces(page.select_one('.pill-selected-item').text) == (
|
||||||
|
expected_selected
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_organisation_trial_mode_services_shows_all_non_live_services(
|
def test_organisation_trial_mode_services_shows_all_non_live_services(
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ def test_a_page_should_nave_selected_org_navigation_item(
|
|||||||
mocker
|
mocker
|
||||||
):
|
):
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
'app.organisations_client.get_services_and_usage', return_value=[]
|
'app.organisations_client.get_services_and_usage', return_value={'services': {}}
|
||||||
)
|
)
|
||||||
page = client_request.get(endpoint, org_id=ORGANISATION_ID)
|
page = client_request.get(endpoint, org_id=ORGANISATION_ID)
|
||||||
selected_nav_items = page.select('.navigation a.selected')
|
selected_nav_items = page.select('.navigation a.selected')
|
||||||
|
|||||||
Reference in New Issue
Block a user