mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-25 08:44:23 -04:00
Merge pull request #681 from alphagov/tables-of-notifications
Make dashboard and activity pages consistently clickable
This commit is contained in:
@@ -21,7 +21,7 @@
|
|||||||
@extend %big-number;
|
@extend %big-number;
|
||||||
|
|
||||||
.big-number-number {
|
.big-number-number {
|
||||||
@include bold-36($tabular-numbers: true);
|
@include bold-36();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -39,31 +39,13 @@
|
|||||||
.big-number-with-status {
|
.big-number-with-status {
|
||||||
|
|
||||||
@extend %big-number;
|
@extend %big-number;
|
||||||
background: $text-colour;
|
|
||||||
color: $white;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-bottom: $gutter-half;
|
margin-bottom: $gutter-half;
|
||||||
|
|
||||||
.big-number {
|
.big-number {
|
||||||
padding: 15px;
|
padding: $gutter-half;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
}
|
|
||||||
|
|
||||||
.big-number-overlay-link {
|
|
||||||
|
|
||||||
background: transparent;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
background: transparent;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba($text-colour, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.big-number-label {
|
.big-number-label {
|
||||||
@@ -72,9 +54,31 @@
|
|||||||
|
|
||||||
&:link,
|
&:link,
|
||||||
&:visited {
|
&:visited {
|
||||||
color: $white;
|
color: $link-colour;
|
||||||
text-decoration: none;
|
}
|
||||||
border-bottom: 1px solid $white;
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.big-number-link {
|
||||||
|
|
||||||
|
text-decoration: none;
|
||||||
|
background: $link-colour;
|
||||||
|
color: $white;
|
||||||
|
display: block;
|
||||||
|
border: 2px solid $link-colour;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $light-blue-25;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active,
|
||||||
|
&:focus {
|
||||||
|
outline: 3px solid $yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.big-number-label {
|
||||||
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -85,6 +89,7 @@
|
|||||||
@include core-19;
|
@include core-19;
|
||||||
display: block;
|
display: block;
|
||||||
background: $green;
|
background: $green;
|
||||||
|
color: $white;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@@ -94,8 +99,13 @@
|
|||||||
&:active,
|
&:active,
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $white;
|
color: $white;
|
||||||
text-decoration: none;
|
text-decoration: underline;
|
||||||
border-bottom: 1px solid $white;
|
}
|
||||||
|
|
||||||
|
&:active,
|
||||||
|
&:focus {
|
||||||
|
color: $black;
|
||||||
|
border-bottom: 1px solid $black;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
a,
|
a,
|
||||||
span {
|
span {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
text-align: center;
|
text-align: left;
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
@@ -20,13 +20,25 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
background: $panel-colour;
|
$background: $link-colour;
|
||||||
color: $link-colour;
|
background: $background;
|
||||||
border: 1px solid $panel-colour;
|
color: $white;
|
||||||
|
border: 2px solid $background;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.pill-label {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:link,
|
||||||
|
&:visited {
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $text-colour;
|
color: $light-blue-25;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active,
|
&:active,
|
||||||
@@ -36,7 +48,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
border: 1px solid $grey-1;
|
border: 2px solid $black;
|
||||||
|
outline: 1px solid rgba($white, 0.1);
|
||||||
|
position: relative;
|
||||||
|
z-index: 1000;
|
||||||
color: $text-colour;
|
color: $text-colour;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
table {
|
table {
|
||||||
th {
|
th {
|
||||||
@include core-16;
|
@include core-19;
|
||||||
border-bottom: 0;
|
border-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,16 +26,16 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: $gutter-half;
|
margin-bottom: $gutter-half;
|
||||||
height: $gutter-half;
|
height: $gutter-half;
|
||||||
color: $govuk-blue;
|
color: $text-colour;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: block;
|
display: inline-block;
|
||||||
background: $govuk-blue;
|
overflow: visible;
|
||||||
color: $white;
|
background: $panel-colour;
|
||||||
height: $gutter-half;
|
color: $black;
|
||||||
padding-left: 5px;
|
padding: 5px 5px 2px 5px;
|
||||||
padding-right: 5px;
|
text-indent: -2px;
|
||||||
margin: 3px 0 5px 0;
|
margin: 3px 0 5px 0;
|
||||||
transition: width 0.6s ease-in-out;
|
transition: width 0.6s ease-in-out;
|
||||||
}
|
}
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
color: $govuk-blue;
|
color: $govuk-blue;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
background: $white;
|
background: $white;
|
||||||
margin-bottom: $gutter;
|
margin-bottom: $gutter-two-thirds;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from app import (
|
|||||||
job_api_client,
|
job_api_client,
|
||||||
notification_api_client,
|
notification_api_client,
|
||||||
service_api_client,
|
service_api_client,
|
||||||
|
statistics_api_client,
|
||||||
current_service,
|
current_service,
|
||||||
format_datetime_short)
|
format_datetime_short)
|
||||||
from app.main import main
|
from app.main import main
|
||||||
@@ -26,6 +27,7 @@ from app.utils import (
|
|||||||
generate_previous_next_dict,
|
generate_previous_next_dict,
|
||||||
user_has_permissions,
|
user_has_permissions,
|
||||||
generate_notifications_csv)
|
generate_notifications_csv)
|
||||||
|
from app.statistics_utils import sum_of_statistics, statistics_by_state
|
||||||
|
|
||||||
|
|
||||||
def _parse_filter_args(filter_dict):
|
def _parse_filter_args(filter_dict):
|
||||||
@@ -46,7 +48,7 @@ def _set_status_filters(filter_args):
|
|||||||
all_failure_statuses = ['failed', 'temporary-failure', 'permanent-failure', 'technical-failure']
|
all_failure_statuses = ['failed', 'temporary-failure', 'permanent-failure', 'technical-failure']
|
||||||
all_statuses = ['sending', 'delivered'] + all_failure_statuses
|
all_statuses = ['sending', 'delivered'] + all_failure_statuses
|
||||||
if filter_args.get('status'):
|
if filter_args.get('status'):
|
||||||
if 'processed' in filter_args.get('status'):
|
if 'processed' in filter_args.get('status') or not filter_args.get('status'):
|
||||||
filter_args['status'] = all_statuses
|
filter_args['status'] = all_statuses
|
||||||
elif 'failed' in filter_args.get('status'):
|
elif 'failed' in filter_args.get('status'):
|
||||||
filter_args['status'].extend(all_failure_statuses[1:])
|
filter_args['status'].extend(all_failure_statuses[1:])
|
||||||
@@ -73,7 +75,12 @@ def view_job(service_id, job_id):
|
|||||||
template = service_api_client.get_service_template(service_id=service_id,
|
template = service_api_client.get_service_template(service_id=service_id,
|
||||||
template_id=job['template'],
|
template_id=job['template'],
|
||||||
version=job['template_version'])['data']
|
version=job['template_version'])['data']
|
||||||
notifications = notification_api_client.get_notifications_for_service(service_id, job_id)
|
|
||||||
|
filter_args = _parse_filter_args(request.args)
|
||||||
|
_set_status_filters(filter_args)
|
||||||
|
notifications = notification_api_client.get_notifications_for_service(
|
||||||
|
service_id, job_id, status=filter_args.get('status'),
|
||||||
|
)
|
||||||
finished = job['status'] == 'finished'
|
finished = job['status'] == 'finished'
|
||||||
return render_template(
|
return render_template(
|
||||||
'views/jobs/job.html',
|
'views/jobs/job.html',
|
||||||
@@ -93,7 +100,9 @@ def view_job(service_id, job_id):
|
|||||||
template=Template(
|
template=Template(
|
||||||
template,
|
template,
|
||||||
prefix=current_service['name']
|
prefix=current_service['name']
|
||||||
)
|
),
|
||||||
|
counts=_get_job_counts(job, request.args.get('help', 0)),
|
||||||
|
status=request.args.get('status', '')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -107,12 +116,15 @@ def view_job_csv(service_id, job_id):
|
|||||||
template_id=job['template'],
|
template_id=job['template'],
|
||||||
version=job['template_version']
|
version=job['template_version']
|
||||||
)['data']
|
)['data']
|
||||||
|
filter_args = _parse_filter_args(request.args)
|
||||||
|
_set_status_filters(filter_args)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
generate_notifications_csv(
|
generate_notifications_csv(
|
||||||
notification_api_client.get_notifications_for_service(
|
notification_api_client.get_notifications_for_service(
|
||||||
service_id,
|
service_id,
|
||||||
job_id,
|
job_id,
|
||||||
|
status=filter_args.get('status'),
|
||||||
page_size=job['notification_count']
|
page_size=job['notification_count']
|
||||||
)['notifications']
|
)['notifications']
|
||||||
),
|
),
|
||||||
@@ -132,7 +144,11 @@ def view_job_csv(service_id, job_id):
|
|||||||
@user_has_permissions('view_activity', admin_override=True)
|
@user_has_permissions('view_activity', admin_override=True)
|
||||||
def view_job_updates(service_id, job_id):
|
def view_job_updates(service_id, job_id):
|
||||||
job = job_api_client.get_job(service_id, job_id)['data']
|
job = job_api_client.get_job(service_id, job_id)['data']
|
||||||
notifications = notification_api_client.get_notifications_for_service(service_id, job_id)
|
filter_args = _parse_filter_args(request.args)
|
||||||
|
_set_status_filters(filter_args)
|
||||||
|
notifications = notification_api_client.get_notifications_for_service(
|
||||||
|
service_id, job_id, status=filter_args.get('status')
|
||||||
|
)
|
||||||
finished = (
|
finished = (
|
||||||
job.get('notifications_sent', 0) -
|
job.get('notifications_sent', 0) -
|
||||||
job.get('notifications_delivered', 0) -
|
job.get('notifications_delivered', 0) -
|
||||||
@@ -142,13 +158,16 @@ def view_job_updates(service_id, job_id):
|
|||||||
'counts': render_template(
|
'counts': render_template(
|
||||||
'partials/jobs/count.html',
|
'partials/jobs/count.html',
|
||||||
job=job,
|
job=job,
|
||||||
finished=finished
|
finished=finished,
|
||||||
|
counts=_get_job_counts(job, request.args.get('help', 0)),
|
||||||
|
status=request.args.get('status', '')
|
||||||
),
|
),
|
||||||
'notifications': render_template(
|
'notifications': render_template(
|
||||||
'partials/jobs/notifications.html',
|
'partials/jobs/notifications.html',
|
||||||
job=job,
|
job=job,
|
||||||
notifications=notifications['notifications'],
|
notifications=notifications['notifications'],
|
||||||
finished=finished
|
finished=finished,
|
||||||
|
status=request.args.get('status', '')
|
||||||
),
|
),
|
||||||
'status': render_template(
|
'status': render_template(
|
||||||
'partials/jobs/status.html',
|
'partials/jobs/status.html',
|
||||||
@@ -179,6 +198,9 @@ def view_notifications(service_id, message_type):
|
|||||||
template_type=[message_type],
|
template_type=[message_type],
|
||||||
status=filter_args.get('status'),
|
status=filter_args.get('status'),
|
||||||
limit_days=current_app.config['ACTIVITY_STATS_LIMIT_DAYS'])
|
limit_days=current_app.config['ACTIVITY_STATS_LIMIT_DAYS'])
|
||||||
|
service_statistics_by_state = statistics_by_state(sum_of_statistics(
|
||||||
|
statistics_api_client.get_statistics_for_service(service_id, limit_days=7)['data']
|
||||||
|
))
|
||||||
view_dict = dict(
|
view_dict = dict(
|
||||||
message_type=message_type,
|
message_type=message_type,
|
||||||
status=request.args.get('status')
|
status=request.args.get('status')
|
||||||
@@ -229,16 +251,20 @@ def view_notifications(service_id, message_type):
|
|||||||
status=request.args.get('status')
|
status=request.args.get('status')
|
||||||
),
|
),
|
||||||
status_filters=[
|
status_filters=[
|
||||||
[item[0], item[1], url_for(
|
[
|
||||||
'.view_notifications',
|
item[0], item[1],
|
||||||
service_id=current_service['id'],
|
url_for(
|
||||||
message_type=message_type,
|
'.view_notifications',
|
||||||
status=item[1]
|
service_id=current_service['id'],
|
||||||
)] for item in [
|
message_type=message_type,
|
||||||
['Processed', 'sending,delivered,failed'],
|
status=item[1]
|
||||||
['Sending', 'sending'],
|
),
|
||||||
['Delivered', 'delivered'],
|
service_statistics_by_state[message_type][item[0]]
|
||||||
['Failed', 'failed'],
|
] for item in [
|
||||||
|
['processed', 'sending,delivered,failed'],
|
||||||
|
['sending', 'sending'],
|
||||||
|
['delivered', 'delivered'],
|
||||||
|
['failed', 'failed'],
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -260,3 +286,41 @@ def view_notification(service_id, job_id, notification_id):
|
|||||||
uploaded_at=now,
|
uploaded_at=now,
|
||||||
job_id=job_id
|
job_id=job_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_job_counts(job, help_argument):
|
||||||
|
return [
|
||||||
|
(
|
||||||
|
label,
|
||||||
|
query_param,
|
||||||
|
url_for(
|
||||||
|
".view_job",
|
||||||
|
service_id=job['service'],
|
||||||
|
job_id=job['id'],
|
||||||
|
status=query_param,
|
||||||
|
help=help_argument
|
||||||
|
),
|
||||||
|
count
|
||||||
|
) for label, query_param, count in [
|
||||||
|
[
|
||||||
|
'Processed', '',
|
||||||
|
job.get('notifications_sent', 0)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Sending', 'sending',
|
||||||
|
(
|
||||||
|
job.get('notifications_sent', 0) -
|
||||||
|
job.get('notifications_delivered', 0) -
|
||||||
|
job.get('notifications_failed', 0)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Delivered', 'delivered',
|
||||||
|
job.get('notifications_delivered', 0)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Failed', 'failed',
|
||||||
|
job.get('notifications_failed')
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|||||||
@@ -14,9 +14,12 @@ class JobApiClient(BaseAPIClient):
|
|||||||
self.client_id = app.config['ADMIN_CLIENT_USER_NAME']
|
self.client_id = app.config['ADMIN_CLIENT_USER_NAME']
|
||||||
self.secret = app.config['ADMIN_CLIENT_SECRET']
|
self.secret = app.config['ADMIN_CLIENT_SECRET']
|
||||||
|
|
||||||
def get_job(self, service_id, job_id=None, limit_days=None):
|
def get_job(self, service_id, job_id=None, limit_days=None, status=None):
|
||||||
if job_id:
|
if job_id:
|
||||||
return self.get(url='/service/{}/job/{}'.format(service_id, job_id))
|
params = {}
|
||||||
|
if status is not None:
|
||||||
|
params['status'] = status
|
||||||
|
return self.get(url='/service/{}/job/{}'.format(service_id, job_id), params=params)
|
||||||
params = {}
|
params = {}
|
||||||
if limit_days is not None:
|
if limit_days is not None:
|
||||||
params['limit_days'] = limit_days
|
params['limit_days'] = limit_days
|
||||||
|
|||||||
@@ -48,3 +48,24 @@ def add_rates_to(delivery_statistics):
|
|||||||
),
|
),
|
||||||
**delivery_statistics
|
**delivery_statistics
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def statistics_by_state(statistics):
|
||||||
|
return {
|
||||||
|
'sms': {
|
||||||
|
'processed': statistics['sms_requested'],
|
||||||
|
'sending': (
|
||||||
|
statistics['sms_requested'] - statistics['sms_failed'] - statistics['sms_delivered']
|
||||||
|
),
|
||||||
|
'delivered': statistics['sms_delivered'],
|
||||||
|
'failed': statistics['sms_failed']
|
||||||
|
},
|
||||||
|
'email': {
|
||||||
|
'processed': statistics['emails_requested'],
|
||||||
|
'sending': (
|
||||||
|
statistics['emails_requested'] - statistics['emails_failed'] - statistics['emails_delivered']
|
||||||
|
),
|
||||||
|
'delivered': statistics['emails_delivered'],
|
||||||
|
'failed': statistics['emails_failed']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
{% macro big_number(number, label, label_link=None, currency='', smaller=False, smallest=False) %}
|
{% macro big_number(number, label, link=None, currency='', smaller=False, smallest=False) %}
|
||||||
|
{% if link %}
|
||||||
|
<a class="big-number-link" href="{{ link }}">
|
||||||
|
{% endif %}
|
||||||
<div class="big-number{% if smaller %}-smaller{% endif %}{% if smallest %}-smallest{% endif %}">
|
<div class="big-number{% if smaller %}-smaller{% endif %}{% if smallest %}-smallest{% endif %}">
|
||||||
<div class="big-number-number">
|
<div class="big-number-number">
|
||||||
{% if number is number %}
|
{% if number is number %}
|
||||||
@@ -11,13 +14,13 @@
|
|||||||
{{ number }}
|
{{ number }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% if label_link %}
|
{% if label %}
|
||||||
<a class="big-number-label" href="{{ label_link }}">{{ label }}</a>
|
|
||||||
<a class="big-number-overlay-link" href="{{ label_link }}" aria-hidden="true"></a>
|
|
||||||
{% elif label %}
|
|
||||||
<span class="big-number-label">{{ label }}</span>
|
<span class="big-number-label">{{ label }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
{% if link %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
|
||||||
@@ -28,12 +31,12 @@
|
|||||||
failure_percentage,
|
failure_percentage,
|
||||||
danger_zone=False,
|
danger_zone=False,
|
||||||
failure_link=None,
|
failure_link=None,
|
||||||
label_link=None,
|
link=None,
|
||||||
show_more_link=None,
|
show_more_link=None,
|
||||||
show_more_text=''
|
show_more_text=''
|
||||||
) %}
|
) %}
|
||||||
<div class="big-number-with-status">
|
<div class="big-number-with-status">
|
||||||
{{ big_number(number, label, label_link) }}
|
{{ big_number(number, label, link=link) }}
|
||||||
<div class="big-number-status{% if danger_zone %}-failing{% endif %}">
|
<div class="big-number-status{% if danger_zone %}-failing{% endif %}">
|
||||||
{% if failures %}
|
{% if failures %}
|
||||||
{% if failure_link %}
|
{% if failure_link %}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
{% from 'components/big-number.html' import big_number %}
|
||||||
|
|
||||||
{% macro pill(
|
{% macro pill(
|
||||||
title,
|
title,
|
||||||
items=[],
|
items=[],
|
||||||
@@ -5,12 +7,19 @@
|
|||||||
) %}
|
) %}
|
||||||
<nav role='navigation' class='pill' aria-labelledby="pill_{{title}}">
|
<nav role='navigation' class='pill' aria-labelledby="pill_{{title}}">
|
||||||
<h2 id="pill_{{title}}" class="visuallyhidden">{{title}}</h2>
|
<h2 id="pill_{{title}}" class="visuallyhidden">{{title}}</h2>
|
||||||
{% for label, option, link in items %}
|
{% for label, option, link, count in items %}
|
||||||
{% if current_value == option %}
|
{% if current_value == option %}
|
||||||
<span aria-hidden="true">{{ label }}</span>
|
<span aria-hidden="true">
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{{ link }}">{{ label }}</a>
|
<a href="{{ link }}">
|
||||||
|
{% endif %}
|
||||||
|
{{ big_number(count, smaller=True) }}
|
||||||
|
<div class="pill-label">{{ label }}</div>
|
||||||
|
{% if current_value == option %}
|
||||||
|
</span>
|
||||||
|
{% else %}
|
||||||
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</nav>
|
</nav>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|||||||
@@ -1,30 +1,16 @@
|
|||||||
{% from "components/big-number.html" import big_number %}
|
{% from "components/pill.html" import pill %}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
{% if not finished %}
|
{% if not finished %}
|
||||||
data-module="update-content"
|
data-module="update-content"
|
||||||
data-resource="{{url_for(".view_job_updates", service_id=current_service.id, job_id=job.id)}}"
|
data-resource="{{url_for(".view_job_updates", service_id=current_service.id, job_id=job.id, status=status, help=request.args.get('help', 0))}}"
|
||||||
data-key="counts"
|
data-key="counts"
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
{% endif %}
|
{% endif %}
|
||||||
>
|
>
|
||||||
|
|
||||||
<ul class="grid-row job-totals">
|
<div class="bottom-gutter">
|
||||||
<li class="column-one-quarter">
|
{{ pill('Status', counts, status) }}
|
||||||
{{ big_number(
|
</div>
|
||||||
job.get('notifications_sent', 0) - job.get('notifications_delivered', 0) - job.get('notifications_failed', 0), 'sending'
|
|
||||||
)}}
|
|
||||||
</li>
|
|
||||||
<li class="column-one-quarter">
|
|
||||||
{{ big_number(
|
|
||||||
job.get('notifications_delivered', 0), 'delivered'
|
|
||||||
)}}
|
|
||||||
</li>
|
|
||||||
<li class="column-one-quarter">
|
|
||||||
{{ big_number(
|
|
||||||
job.notifications_failed, 'failed'
|
|
||||||
)}}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,19 +1,30 @@
|
|||||||
{% from "components/table.html" import list_table, field, right_aligned_field_heading, date_field, row_heading %}
|
{% from "components/table.html" import list_table, field, right_aligned_field_heading, date_field, row_heading %}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
{% if notifications %}
|
||||||
|
class='dashboard-table'
|
||||||
|
{% endif %}
|
||||||
{% if not finished %}
|
{% if not finished %}
|
||||||
data-module="update-content"
|
data-module="update-content"
|
||||||
data-resource="{{url_for(".view_job_updates", service_id=current_service.id, job_id=job.id)}}"
|
data-resource="{{url_for(".view_job_updates", service_id=current_service.id, job_id=job.id, status=request.args.get('status'), help=request.args.get('help'))}}"
|
||||||
data-key="notifications"
|
data-key="notifications"
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
{% endif %}
|
{% endif %}
|
||||||
>
|
>
|
||||||
|
|
||||||
|
{% if notifications %}
|
||||||
|
<p class="bottom-gutter">
|
||||||
|
<a href="{{ url_for('.view_job_csv', service_id=current_service.id, job_id=job.id, status=status) }}" download="download" class="heading-small">Download as a CSV file</a>
|
||||||
|
 
|
||||||
|
Delivery information is available for 7 days
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% call(item, row_number) list_table(
|
{% call(item, row_number) list_table(
|
||||||
notifications,
|
notifications,
|
||||||
caption=uploaded_file_name,
|
caption=uploaded_file_name,
|
||||||
caption_visible=False,
|
caption_visible=False,
|
||||||
empty_message="No messages to show yet",
|
empty_message="No messages to show",
|
||||||
field_headings=[
|
field_headings=[
|
||||||
'Recipient',
|
'Recipient',
|
||||||
'Time',
|
'Time',
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<div
|
<div
|
||||||
{% if not finished %}
|
{% if not finished %}
|
||||||
data-module="update-content"
|
data-module="update-content"
|
||||||
data-resource="{{url_for(".view_job_updates", service_id=current_service.id, job_id=job.id)}}"
|
data-resource="{{url_for(".view_job_updates", service_id=current_service.id, job_id=job.id, status=status, help=request.args.get('help', 0))}}"
|
||||||
data-key="status"
|
data-key="status"
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
{% endif %}
|
{% endif %}
|
||||||
>
|
>
|
||||||
<p class='heading-small'>
|
<p class='heading-small bottom-gutter'>
|
||||||
Uploaded by {{ job.created_by.name }} on {{ job.created_at|format_datetime_short }}
|
Uploaded by {{ job.created_by.name }} on {{ job.created_at|format_datetime_short }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,26 +1,41 @@
|
|||||||
|
{% from "components/big-number.html" import big_number %}
|
||||||
{% from "components/message-count-label.html" import message_count_label %}
|
{% from "components/message-count-label.html" import message_count_label %}
|
||||||
|
{% from "components/big-number.html" import big_number %}
|
||||||
|
|
||||||
<dl>
|
<dl>
|
||||||
{% for item in template_statistics %}
|
{% for item in template_statistics %}
|
||||||
<div class="grid-row">
|
<div class="grid-row">
|
||||||
|
|
||||||
<dt class="column-half">
|
<dt class="column-half">
|
||||||
<span class="spark-bar-label">
|
<span class="spark-bar-label">
|
||||||
<a href="{{ url_for('.view_template', service_id=current_service.id, template_id=item.template.id) }}">{{ item.template.name }}</a>
|
<a href="{{ url_for('.view_template', service_id=current_service.id, template_id=item.template.id) }}">{{ item.template.name }}</a>
|
||||||
|
<span class="file-list-hint">
|
||||||
|
{{ message_count_label(1, item.template.template_type, suffix='template')|capitalize }}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</dt>
|
</dt>
|
||||||
|
|
||||||
<dd class="column-half">
|
<dd class="column-half">
|
||||||
{% if template_statistics|length > 1 %}
|
{% if template_statistics|length > 1 %}
|
||||||
<span class="spark-bar">
|
<span class="spark-bar">
|
||||||
<span style="width: {{ item.usage_count / most_used_template_count * 100 }}%"></span>
|
<span style="width: {{ item.usage_count / most_used_template_count * 100 }}%">
|
||||||
{{ item.usage_count }} {{ message_count_label(item.usage_count, item.template.template_type) }}
|
{{ big_number(
|
||||||
</span>
|
item.usage_count,
|
||||||
|
smallest=True
|
||||||
|
) }}
|
||||||
|
<span class="visually-hidden">
|
||||||
|
{{ message_count_label(item.usage_count, item.template.template_type) }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ item.usage_count }} {{ message_count_label(item.usage_count, item.template.template_type) }}
|
<span class="heading-small">
|
||||||
|
{{ item.usage_count }}
|
||||||
|
{{ message_count_label(item.usage_count, item.template.template_type) }}
|
||||||
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</dd>
|
</dd>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</dl>
|
</dl>
|
||||||
|
|||||||
@@ -14,23 +14,23 @@
|
|||||||
<div class="column-half">
|
<div class="column-half">
|
||||||
{{ big_number_with_status(
|
{{ big_number_with_status(
|
||||||
statistics.emails_requested,
|
statistics.emails_requested,
|
||||||
message_count_label(statistics.emails_requested, 'email'),
|
message_count_label(statistics.emails_requested, 'email', suffix=''),
|
||||||
statistics.emails_failed,
|
statistics.emails_failed,
|
||||||
statistics.get('emails_failure_rate', 0.0),
|
statistics.get('emails_failure_rate', 0.0),
|
||||||
statistics.get('emails_failure_rate', 0)|float > 3,
|
statistics.get('emails_failure_rate', 0)|float > 3,
|
||||||
failure_link=url_for(".view_notifications", service_id=current_service.id, message_type='email', status='failed'),
|
failure_link=url_for(".view_notifications", service_id=current_service.id, message_type='email', status='failed'),
|
||||||
label_link=url_for(".view_notifications", service_id=current_service.id, message_type='email', status='sending,delivered,failed')
|
link=url_for(".view_notifications", service_id=current_service.id, message_type='email', status='sending,delivered,failed')
|
||||||
) }}
|
) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="column-half">
|
<div class="column-half">
|
||||||
{{ big_number_with_status(
|
{{ big_number_with_status(
|
||||||
statistics.sms_requested,
|
statistics.sms_requested,
|
||||||
message_count_label(statistics.sms_requested, 'sms'),
|
message_count_label(statistics.sms_requested, 'sms', suffix=''),
|
||||||
statistics.sms_failed,
|
statistics.sms_failed,
|
||||||
statistics.get('sms_failure_rate', 0.0),
|
statistics.get('sms_failure_rate', 0.0),
|
||||||
statistics.get('sms_failure_rate', 0)|float > 3,
|
statistics.get('sms_failure_rate', 0)|float > 3,
|
||||||
failure_link=url_for(".view_notifications", service_id=current_service.id, message_type='sms', status='failed'),
|
failure_link=url_for(".view_notifications", service_id=current_service.id, message_type='sms', status='failed'),
|
||||||
label_link=url_for(".view_notifications", service_id=current_service.id, message_type='sms', status='sending,delivered,failed')
|
link=url_for(".view_notifications", service_id=current_service.id, message_type='sms', status='sending,delivered,failed')
|
||||||
) }}
|
) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="column-whole">
|
<div class="column-whole">
|
||||||
|
|||||||
@@ -36,5 +36,4 @@
|
|||||||
|
|
||||||
{% include 'partials/jobs/notifications.html' %}
|
{% include 'partials/jobs/notifications.html' %}
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
{% from "components/previous-next-navigation.html" import previous_next_navigation %}
|
{% from "components/previous-next-navigation.html" import previous_next_navigation %}
|
||||||
{% from "components/page-footer.html" import page_footer %}
|
{% from "components/page-footer.html" import page_footer %}
|
||||||
{% from "components/pill.html" import pill %}
|
{% from "components/pill.html" import pill %}
|
||||||
{% from "components/message-count-label.html" import message_count_label %}
|
{% from "components/message-count-label.html" import message_count_label, recipient_count_label %}
|
||||||
|
|
||||||
{% block page_title %}
|
{% block page_title %}
|
||||||
{{ message_count_label(99, message_type, suffix='') | capitalize }} – GOV.UK Notify
|
{{ message_count_label(99, message_type, suffix='') | capitalize }} – GOV.UK Notify
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
<span class="visually-hidden">
|
<span class="visually-hidden">
|
||||||
{%- if request_args.get('status') != 'delivered,failed' -%}
|
{%- if request_args.get('status') != 'delivered,failed' -%}
|
||||||
{%- for label, option, _ in status_filters -%}
|
{%- for label, option, _, _ in status_filters -%}
|
||||||
{%- if request_args.get('status', 'delivered,failed') == option -%}{{label}} {% endif -%}
|
{%- if request_args.get('status', 'delivered,failed') == option -%}{{label}} {% endif -%}
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
|
|||||||
@@ -91,11 +91,15 @@ def test_should_show_recent_templates_on_dashboard(app_,
|
|||||||
|
|
||||||
assert len(table_rows) == 2
|
assert len(table_rows) == 2
|
||||||
|
|
||||||
assert page.find_all('dt')[0].text.strip() == 'Pickle feet'
|
assert 'Pickle feet' in page.find_all('dt')[0].text
|
||||||
assert page.find_all('dd')[0].text.strip() == '206 text messages sent'
|
assert 'Text message template' in page.find_all('dt')[0].text
|
||||||
|
assert '206' in page.find_all('dd')[0].text
|
||||||
|
assert 'text messages sent' in page.find_all('dd')[0].text
|
||||||
|
|
||||||
assert page.find_all('dt')[1].text.strip() == 'Brine Shrimp'
|
assert 'Brine Shrimp' in page.find_all('dt')[1].text
|
||||||
assert page.find_all('dd')[1].text.strip() == '13 text messages sent'
|
assert 'Text message template' in page.find_all('dt')[1].text
|
||||||
|
assert '13' in page.find_all('dd')[1].text
|
||||||
|
assert 'text messages sent' in page.find_all('dd')[1].text
|
||||||
|
|
||||||
|
|
||||||
def test_should_show_all_templates_on_template_statistics_page(
|
def test_should_show_all_templates_on_template_statistics_page(
|
||||||
@@ -129,11 +133,15 @@ def test_should_show_all_templates_on_template_statistics_page(
|
|||||||
|
|
||||||
assert len(table_rows) == 2
|
assert len(table_rows) == 2
|
||||||
|
|
||||||
assert page.find_all('dt')[0].text.strip() == 'Pickle feet'
|
assert 'Pickle feet' in page.find_all('dt')[0].text
|
||||||
assert page.find_all('dd')[0].text.strip() == '206 text messages sent'
|
assert 'Text message template' in page.find_all('dt')[0].text
|
||||||
|
assert '206' in page.find_all('dd')[0].text
|
||||||
|
assert 'text messages sent' in page.find_all('dd')[0].text
|
||||||
|
|
||||||
assert page.find_all('dt')[1].text.strip() == 'Brine Shrimp'
|
assert 'Brine Shrimp' in page.find_all('dt')[1].text
|
||||||
assert page.find_all('dd')[1].text.strip() == '13 text messages sent'
|
assert 'Text message template' in page.find_all('dt')[1].text
|
||||||
|
assert '13' in page.find_all('dd')[1].text
|
||||||
|
assert 'text messages sent' in page.find_all('dd')[1].text
|
||||||
|
|
||||||
|
|
||||||
def _test_dashboard_menu(mocker, app_, usr, service, permissions):
|
def _test_dashboard_menu(mocker, app_, usr, service, permissions):
|
||||||
|
|||||||
@@ -26,22 +26,54 @@ def test_should_return_list_of_all_jobs(app_,
|
|||||||
assert len(jobs) == 5
|
assert len(jobs) == 5
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"status_argument, expected_api_call", [
|
||||||
|
(
|
||||||
|
'',
|
||||||
|
['sending', 'delivered', 'failed', 'temporary-failure', 'permanent-failure', 'technical-failure']
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'processed',
|
||||||
|
['sending', 'delivered', 'failed', 'temporary-failure', 'permanent-failure', 'technical-failure']
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'sending',
|
||||||
|
['sending']
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'delivered',
|
||||||
|
['delivered']
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'failed',
|
||||||
|
['failed', 'temporary-failure', 'permanent-failure', 'technical-failure']
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
@freeze_time("2016-01-01 11:09:00.061258")
|
@freeze_time("2016-01-01 11:09:00.061258")
|
||||||
def test_should_show_page_for_one_job(
|
def test_should_show_page_for_one_job(
|
||||||
app_,
|
app_,
|
||||||
service_one,
|
service_one,
|
||||||
active_user_with_permissions,
|
active_user_with_permissions,
|
||||||
mock_get_service_template,
|
mock_get_service_template,
|
||||||
|
mock_get_service_statistics,
|
||||||
mock_get_job,
|
mock_get_job,
|
||||||
mocker,
|
mocker,
|
||||||
mock_get_notifications,
|
mock_get_notifications,
|
||||||
fake_uuid
|
fake_uuid,
|
||||||
|
status_argument,
|
||||||
|
expected_api_call
|
||||||
):
|
):
|
||||||
file_name = mock_get_job(service_one['id'], fake_uuid)['data']['original_file_name']
|
file_name = mock_get_job(service_one['id'], fake_uuid)['data']['original_file_name']
|
||||||
with app_.test_request_context():
|
with app_.test_request_context():
|
||||||
with app_.test_client() as client:
|
with app_.test_client() as client:
|
||||||
client.login(active_user_with_permissions, mocker, service_one)
|
client.login(active_user_with_permissions, mocker, service_one)
|
||||||
response = client.get(url_for('main.view_job', service_id=service_one['id'], job_id=fake_uuid))
|
response = client.get(url_for(
|
||||||
|
'main.view_job',
|
||||||
|
service_id=service_one['id'],
|
||||||
|
job_id=fake_uuid,
|
||||||
|
status=status_argument
|
||||||
|
))
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
content = response.get_data(as_text=True)
|
content = response.get_data(as_text=True)
|
||||||
@@ -49,6 +81,22 @@ def test_should_show_page_for_one_job(
|
|||||||
assert file_name in content
|
assert file_name in content
|
||||||
assert 'Delivered' in content
|
assert 'Delivered' in content
|
||||||
assert '11:10' in content
|
assert '11:10' in content
|
||||||
|
assert url_for(
|
||||||
|
'main.view_job_updates',
|
||||||
|
service_id=service_one['id'],
|
||||||
|
job_id=fake_uuid,
|
||||||
|
status=status_argument,
|
||||||
|
) in content
|
||||||
|
assert url_for(
|
||||||
|
'main.view_job_csv',
|
||||||
|
service_id=service_one['id'],
|
||||||
|
job_id=fake_uuid
|
||||||
|
) in content
|
||||||
|
mock_get_notifications.assert_called_with(
|
||||||
|
service_one['id'],
|
||||||
|
fake_uuid,
|
||||||
|
status=expected_api_call
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@freeze_time("2016-01-01 11:09:00.061258")
|
@freeze_time("2016-01-01 11:09:00.061258")
|
||||||
@@ -57,6 +105,7 @@ def test_should_show_updates_for_one_job_as_json(
|
|||||||
service_one,
|
service_one,
|
||||||
active_user_with_permissions,
|
active_user_with_permissions,
|
||||||
mock_get_notifications,
|
mock_get_notifications,
|
||||||
|
mock_get_service_statistics,
|
||||||
mock_get_job,
|
mock_get_job,
|
||||||
mocker,
|
mocker,
|
||||||
fake_uuid
|
fake_uuid
|
||||||
@@ -112,6 +161,7 @@ def test_can_show_notifications(
|
|||||||
service_one,
|
service_one,
|
||||||
active_user_with_permissions,
|
active_user_with_permissions,
|
||||||
mock_get_notifications,
|
mock_get_notifications,
|
||||||
|
mock_get_service_statistics,
|
||||||
mocker,
|
mocker,
|
||||||
message_type,
|
message_type,
|
||||||
page_title,
|
page_title,
|
||||||
@@ -166,11 +216,14 @@ def test_can_show_notifications(
|
|||||||
assert 'text/csv' in csv_response.headers['Content-Type']
|
assert 'text/csv' in csv_response.headers['Content-Type']
|
||||||
|
|
||||||
|
|
||||||
def test_should_show_notifications_for_a_service_with_next_previous(app_,
|
def test_should_show_notifications_for_a_service_with_next_previous(
|
||||||
service_one,
|
app_,
|
||||||
active_user_with_permissions,
|
service_one,
|
||||||
mock_get_notifications_with_previous_next,
|
active_user_with_permissions,
|
||||||
mocker):
|
mock_get_notifications_with_previous_next,
|
||||||
|
mock_get_service_statistics,
|
||||||
|
mocker
|
||||||
|
):
|
||||||
with app_.test_request_context():
|
with app_.test_request_context():
|
||||||
with app_.test_client() as client:
|
with app_.test_client() as client:
|
||||||
client.login(active_user_with_permissions, mocker, service_one)
|
client.login(active_user_with_permissions, mocker, service_one)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from app.statistics_utils import sum_of_statistics, add_rates_to
|
from app.statistics_utils import sum_of_statistics, add_rates_to, statistics_by_state
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('delivery_statistics', [
|
@pytest.mark.parametrize('delivery_statistics', [
|
||||||
@@ -96,3 +96,20 @@ def test_add_rates_keeps_original_raw_data():
|
|||||||
assert resp['emails_requested'] == 2
|
assert resp['emails_requested'] == 2
|
||||||
assert resp['sms_failed'] == 3
|
assert resp['sms_failed'] == 3
|
||||||
assert resp['sms_requested'] == 4
|
assert resp['sms_requested'] == 4
|
||||||
|
|
||||||
|
|
||||||
|
def test_service_statistics_by_state():
|
||||||
|
resp = statistics_by_state({
|
||||||
|
'emails_requested': 3,
|
||||||
|
'emails_failed': 1,
|
||||||
|
'emails_delivered': 1,
|
||||||
|
'sms_requested': 3,
|
||||||
|
'sms_failed': 1,
|
||||||
|
'sms_delivered': 1
|
||||||
|
})
|
||||||
|
|
||||||
|
for message_type in ['email', 'sms']:
|
||||||
|
assert resp[message_type]['processed'] == 3
|
||||||
|
assert resp[message_type]['sending'] == 1
|
||||||
|
assert resp[message_type]['delivered'] == 1
|
||||||
|
assert resp[message_type]['failed'] == 1
|
||||||
|
|||||||
Reference in New Issue
Block a user