Don’t break statistics down by sending/failed/delivered

This should:
- make the page load faster because it has to render less HTML for each
  service
- make the page easier to scan for services that are sending lots of
  text messages or letters

We used to scan this page to look for services with high failure rates,
and the design of the page was gear towards this. Now we have alerting
for high failure rates, so the page can focus on volumes instead.

This commit also puts the service name above the statistics, so that
long service names don’t break the layout of the page.
This commit is contained in:
Chris Hill-Scott
2020-06-24 11:20:22 +01:00
parent 2762be233d
commit fb4076dc5b
3 changed files with 35 additions and 126 deletions

View File

@@ -505,14 +505,7 @@ def format_stats_by_service(services):
yield { yield {
'id': service['id'], 'id': service['id'],
'name': service['name'], 'name': service['name'],
'stats': { 'stats': service['statistics'],
msg_type: {
'sending': stats['requested'] - stats['delivered'] - stats['failed'],
'delivered': stats['delivered'],
'failed': stats['failed'],
}
for msg_type, stats in service['statistics'].items()
},
'restricted': service['restricted'], 'restricted': service['restricted'],
'research_mode': service['research_mode'], 'research_mode': service['research_mode'],
'created_at': service['created_at'], 'created_at': service['created_at'],

View File

@@ -9,38 +9,16 @@
{% from "components/button/macro.njk" import govukButton %} {% from "components/button/macro.njk" import govukButton %}
{% from "components/details/macro.njk" import govukDetails %} {% from "components/details/macro.njk" import govukDetails %}
{% macro stats_fields(channel, data) -%}
{% call field(border=False) %}
<span class="heading-medium">{{ channel.title() }}</span>
{% endcall %}
{% call field(align='right', border=False) %}
{{ big_number(data[channel]['sending'], smaller=True) }}
{% endcall %}
{% call field(align='right', border=False) %}
{{ big_number(data[channel]['delivered'], smaller=True) }}
{% endcall %}
{% call field(align='right', status='error' if data[channel]['failed'], border=False) %}
{{ big_number(data[channel]['failed'], smaller=True) }}
{% endcall %}
{%- endmacro %}
{% macro services_table(services, caption) %} {% macro services_table(services, caption) %}
{% call(item, row_number) mapping_table( {% call(item, row_number) mapping_table(
caption=caption, caption=caption,
caption_visible=False, caption_visible=False,
field_headings=[ field_headings=[
'Service', right_aligned_field_heading('Emails'),
hidden_field_heading('Type'), right_aligned_field_heading('Text messages'),
right_aligned_field_heading('Sending'), right_aligned_field_heading('Letters')
right_aligned_field_heading('Delivered'),
right_aligned_field_heading('Failed')
], ],
field_headings_visible=True field_headings_visible=False,
) %} ) %}
{% for service in services %} {% for service in services %}
@@ -48,30 +26,25 @@
{% call row_group() %} {% call row_group() %}
{% call row() %} {% call row() %}
{% call field(border=False) %} {% call field(border=False, colspan=3) %}
<a href="{{ url_for('main.service_dashboard', service_id=service['id']) }}" class="govuk-link govuk-link--no-visited-state browse-list-link">{{ service['name'] }}</a> <a href="{{ url_for('main.service_dashboard', service_id=service['id']) }}" class="file-list-filename-large govuk-link govuk-link--no-visited-state govuk-!-padding-bottom-4">{{ service['name'] }}</a>
{% if not service['active'] %} {% if not service['active'] %}
<span class="heading-medium hint">&ensp;Archived</span> <span class="heading-medium hint">&ensp;Archived</span>
{% endif %} {% endif %}
{% endcall %} {% endcall %}
{{ stats_fields('email', service['stats']) }}
{% endcall %} {% endcall %}
{% call row() %} {% call row() %}
{% for channel in ('email', 'sms', 'letter') %}
{% call field(border=False) %} {% call field(border=False) %}
{{ big_number(
service['stats'][channel]['requested'],
smallest=True,
label=message_count_label(service['stats'][channel]['requested'], channel)
) }}
{% endcall %} {% endcall %}
{{ stats_fields('sms', service['stats']) }} {% endfor %}
{% endcall %}
{% call row() %}
{% call field(border=False) %}
{% endcall %}
{{ stats_fields('letter', service['stats']) }}
{% endcall %} {% endcall %}
{% endcall %} {% endcall %}

View File

@@ -48,39 +48,6 @@ def test_should_403_if_not_platform_admin(
client_request.get(endpoint, _expected_status=403) client_request.get(endpoint, _expected_status=403)
@pytest.mark.parametrize('endpoint, restricted, research_mode, displayed', [
('main.trial_services', True, False, ''),
('main.live_services', False, False, 'Live'),
('main.live_services', False, True, 'research mode'),
('main.trial_services', True, True, 'research mode')
])
def test_should_show_research_and_restricted_mode(
endpoint,
restricted,
research_mode,
displayed,
platform_admin_client,
mock_get_detailed_services,
fake_uuid,
):
services = [service_json(fake_uuid, 'My Service', [], restricted=restricted, research_mode=research_mode)]
services[0]['statistics'] = create_stats()
mock_get_detailed_services.return_value = {'data': services}
response = platform_admin_client.get(url_for(endpoint))
assert response.status_code == 200
mock_get_detailed_services.assert_called_once_with({'detailed': True,
'include_from_test_key': True,
'only_active': False})
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
# get first column in second row, which contains flags as text.
table_body = page.find_all('table')[0].find_all('tbody')[0]
service_mode = table_body.find_all('tbody')[0].find_all('tr')[1].find_all('td')[0].text.strip()
assert service_mode == displayed
@pytest.mark.parametrize('endpoint, expected_services_shown', [ @pytest.mark.parametrize('endpoint, expected_services_shown', [
('main.live_services', 1), ('main.live_services', 1),
('main.trial_services', 1), ('main.trial_services', 1),
@@ -94,7 +61,12 @@ def test_should_render_platform_admin_page(
response = platform_admin_client.get(url_for(endpoint)) response = platform_admin_client.get(url_for(endpoint))
assert response.status_code == 200 assert response.status_code == 200
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser') page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
assert len(page.select('tbody tr')) == expected_services_shown * 3 # one row for SMS, one for email, one for letter assert [
normalize_spaces(column.text)
for column in page.select('tbody tr')[1].select('td')
] == [
'0 emails sent', '0 text messages sent', '0 letters sent',
]
mock_get_detailed_services.assert_called_once_with({'detailed': True, mock_get_detailed_services.assert_called_once_with({'detailed': True,
'include_from_test_key': True, 'include_from_test_key': True,
'only_active': False}) 'only_active': False})
@@ -298,15 +270,15 @@ def test_format_stats_by_service_returns_correct_values(fake_uuid):
ret = list(format_stats_by_service(services)) ret = list(format_stats_by_service(services))
assert len(ret) == 1 assert len(ret) == 1
assert ret[0]['stats']['email']['sending'] == 2 assert ret[0]['stats']['email']['requested'] == 10
assert ret[0]['stats']['email']['delivered'] == 3 assert ret[0]['stats']['email']['delivered'] == 3
assert ret[0]['stats']['email']['failed'] == 5 assert ret[0]['stats']['email']['failed'] == 5
assert ret[0]['stats']['sms']['sending'] == 32 assert ret[0]['stats']['sms']['requested'] == 50
assert ret[0]['stats']['sms']['delivered'] == 7 assert ret[0]['stats']['sms']['delivered'] == 7
assert ret[0]['stats']['sms']['failed'] == 11 assert ret[0]['stats']['sms']['failed'] == 11
assert ret[0]['stats']['letter']['sending'] == 13 assert ret[0]['stats']['letter']['requested'] == 40
assert ret[0]['stats']['letter']['delivered'] == 20 assert ret[0]['stats']['letter']['delivered'] == 20
assert ret[0]['stats']['letter']['failed'] == 7 assert ret[0]['stats']['letter']['failed'] == 7
@@ -344,17 +316,11 @@ def test_should_show_email_and_sms_stats_for_all_service_types(
table_body = page.find_all('table')[0].find_all('tbody')[0] table_body = page.find_all('table')[0].find_all('tbody')[0]
service_row_group = table_body.find_all('tbody')[0].find_all('tr') service_row_group = table_body.find_all('tbody')[0].find_all('tr')
email_stats = service_row_group[0].find_all('div', class_='big-number-number') email_stats = service_row_group[1].select('.big-number-number')[0]
sms_stats = service_row_group[1].find_all('div', class_='big-number-number') sms_stats = service_row_group[1].select('.big-number-number')[1]
email_sending, email_delivered, email_failed = [int(x.text.strip()) for x in email_stats]
sms_sending, sms_delivered, sms_failed = [int(x.text.strip()) for x in sms_stats]
assert email_sending == 2 assert normalize_spaces(email_stats.text) == '10'
assert email_delivered == 3 assert normalize_spaces(sms_stats.text) == '50'
assert email_failed == 5
assert sms_sending == 32
assert sms_delivered == 7
assert sms_failed == 11
@pytest.mark.parametrize('endpoint, restricted', [ @pytest.mark.parametrize('endpoint, restricted', [
@@ -388,32 +354,9 @@ def test_should_show_archived_services_last(
table_body = page.find_all('table')[0].find_all('tbody')[0] table_body = page.find_all('table')[0].find_all('tbody')[0]
services = [service.tr for service in table_body.find_all('tbody')] services = [service.tr for service in table_body.find_all('tbody')]
assert len(services) == 3 assert len(services) == 3
assert services[0].td.text.strip() == 'A' assert normalize_spaces(services[0].td.text) == 'A'
assert services[1].td.text.strip() == 'B' assert normalize_spaces(services[1].td.text) == 'B'
assert services[2].td.text.strip() == 'C' assert normalize_spaces(services[2].td.text) == 'C Archived'
@pytest.mark.parametrize('research_mode', (True, False))
def test_shows_archived_label_instead_of_live_or_research_mode_label(
platform_admin_client,
mock_get_detailed_services,
research_mode,
):
services = [
service_json(restricted=False, research_mode=research_mode, active=False)
]
services[0]['statistics'] = create_stats()
mock_get_detailed_services.return_value = {'data': services}
response = platform_admin_client.get(url_for('main.live_services'))
assert response.status_code == 200
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
table_body = page.find_all('table')[0].find_all('tbody')[0]
service_mode = table_body.find_all('tbody')[0].find_all('tr')[1].td.text.strip()
# get second column, which contains flags as text.
assert service_mode == 'archived'
@pytest.mark.parametrize('endpoint, restricted, research_mode', [ @pytest.mark.parametrize('endpoint, restricted, research_mode', [
@@ -472,9 +415,9 @@ def test_should_order_services_by_usage_with_inactive_last(
table_body = page.find_all('table')[0].find_all('tbody')[0] table_body = page.find_all('table')[0].find_all('tbody')[0]
services = [service.tr for service in table_body.find_all('tbody')] services = [service.tr for service in table_body.find_all('tbody')]
assert len(services) == 3 assert len(services) == 3
assert services[0].td.text.strip() == 'My Service 2' assert normalize_spaces(services[0].td.text) == 'My Service 2'
assert services[1].td.text.strip() == 'My Service 1' assert normalize_spaces(services[1].td.text) == 'My Service 1'
assert services[2].td.text.strip() == 'My Service 3' assert normalize_spaces(services[2].td.text) == 'My Service 3 Archived'
def test_sum_service_usage_is_sum_of_all_activity(fake_uuid): def test_sum_service_usage_is_sum_of_all_activity(fake_uuid):