Merge pull request #1324 from alphagov/one-off

One off status page
This commit is contained in:
Leo Hemsted
2017-06-21 15:05:55 +01:00
committed by GitHub
14 changed files with 375 additions and 50 deletions

View File

@@ -96,6 +96,7 @@ class Development(Config):
class Test(Development):
DEBUG = True
TESTING = True
STATSD_ENABLED = True
WTF_CSRF_ENABLED = False
CSV_UPLOAD_BUCKET_NAME = 'test-notifications-csv-upload'

View File

@@ -29,4 +29,5 @@ from app.main.views import (
platform_admin,
letter_jobs,
conversation,
notifications
)

View File

@@ -1,8 +1,5 @@
# -*- coding: utf-8 -*-
import ago
import dateutil
from orderedset import OrderedSet
from datetime import datetime, timedelta, timezone
from itertools import chain
from flask import (
@@ -35,6 +32,7 @@ from app.utils import (
generate_notifications_csv,
get_help_argument,
get_template,
get_time_left,
REQUESTED_STATUSES,
FAILURE_STATUSES,
SENDING_STATUSES,
@@ -364,7 +362,7 @@ def get_job_partials(job):
)
return {
'counts': render_template(
'partials/jobs/count.html',
'partials/count.html',
counts=_get_job_counts(job, request.args.get('help', 0)),
status=filter_args['status']
),
@@ -388,16 +386,3 @@ def get_job_partials(job):
job=job
),
}
def get_time_left(job_created_at):
return ago.human(
(
datetime.now(timezone.utc).replace(hour=23, minute=59, second=59)
) - (
dateutil.parser.parse(job_created_at) + timedelta(days=8)
),
future_tense='Data available for {}',
past_tense='Data no longer available', # No-one should ever see this
precision=1
)

View File

@@ -0,0 +1,137 @@
# -*- coding: utf-8 -*-
from flask import (
render_template,
jsonify,
request,
url_for,
current_app
)
from flask_login import login_required
from app import (
notification_api_client,
current_service
)
from app.main import main
from app.utils import (
user_has_permissions,
get_help_argument,
get_template,
get_time_left,
REQUESTED_STATUSES,
FAILURE_STATUSES,
SENDING_STATUSES,
DELIVERED_STATUSES,
)
def get_status_arg(filter_args):
if 'status' not in filter_args or not filter_args['status']:
return REQUESTED_STATUSES
elif filter_args['status'] == 'sending':
return SENDING_STATUSES
elif filter_args['status'] == 'delivered':
return DELIVERED_STATUSES
elif filter_args['status'] == 'failed':
return FAILURE_STATUSES
else:
current_app.logger.info('Unrecognised status filter: {}'.format(filter_args['status']))
return REQUESTED_STATUSES
@main.route("/services/<service_id>/one-off-notification/<notification_id>")
@login_required
@user_has_permissions('view_activity', admin_override=True)
def view_notification(service_id, notification_id):
notification = notification_api_client.get_notification(service_id, notification_id)
return render_template(
'views/notifications/notification.html',
finished=(notification['status'] in (DELIVERED_STATUSES + FAILURE_STATUSES)),
uploaded_file_name='Report',
template=get_template(
notification['template'],
current_service,
letter_preview_url=url_for(
'.view_template_version_preview',
service_id=service_id,
template_id=notification['template']['id'],
version=notification['template_version'],
filetype='png',
),
),
status=request.args.get('status'),
updates_url=url_for(
".view_notification_updates",
service_id=service_id,
notification_id=notification['id'],
status=request.args.get('status'),
help=get_help_argument()
),
partials=get_single_notification_partials(notification),
help=get_help_argument()
)
@main.route("/services/<service_id>/one-off-notification/<notification_id>.json")
@user_has_permissions('view_activity', admin_override=True)
def view_notification_updates(service_id, notification_id):
return jsonify(**get_single_notification_partials(
notification_api_client.get_notification(service_id, notification_id)
))
def _get_single_notification_counts(notification, help_argument):
return [
(
label,
query_param,
url_for(
".view_notification",
service_id=notification['service'],
notification_id=notification['id'],
status=query_param,
help=help_argument
),
count
) for label, query_param, count in [
[
'total', '',
1
],
[
'sending', 'sending',
int(notification['status'] in SENDING_STATUSES)
],
[
'delivered', 'delivered',
int(notification['status'] in DELIVERED_STATUSES)
],
[
'failed', 'failed',
int(notification['status'] in FAILURE_STATUSES)
]
]
]
def get_single_notification_partials(notification):
status_args = get_status_arg(request.args)
return {
'counts': render_template(
'partials/count.html',
counts=_get_single_notification_counts(notification, request.args.get('help', 0)),
status=status_args
),
'notifications': render_template(
'partials/notifications/notifications.html',
notification=notification,
more_than_one_page=False,
percentage_complete=100,
time_left=get_time_left(notification['created_at']),
),
'status': render_template(
'partials/notifications/status.html',
notification=notification
),
}

View File

@@ -56,6 +56,4 @@ class NotificationApiClient(NotifyAdminAPIClient):
)
def get_notification(self, service_id, notification_id):
return self.get(
url='/service/{}/notifications/{}'.format(service_id, notification_id)
)
return self.get(url='/service/{}/notifications/{}'.format(service_id, notification_id))

View File

@@ -0,0 +1,31 @@
{% from "components/table.html" import list_table, field, right_aligned_field_heading, row_heading, notification_status_field %}
{% from "components/page-footer.html" import page_footer %}
<div class="ajax-block-container" aria-labelledby='pill-selected-item'>
<div class="dashboard-table bottom-gutter-3-2">
{% call(item, row_number) list_table(
[notification],
caption=None,
caption_visible=False,
empty_message=None,
field_headings=[
'Recipient',
'Status'
],
field_headings_visible=False
) %}
{% call row_heading() %}
<p>{{ item.to }}</p>
{% endcall %}
{{ notification_status_field(item) }}
{% endcall %}
{% if more_than_one_page %}
<p class="table-show-more-link">
Only showing the first 50 rows
</p>
{% endif %}
</div>
</div>

View File

@@ -0,0 +1,6 @@
<div class="ajax-block-container">
<p class='heading-small bottom-gutter'>
Sent {% if notification.created_by %}by {{ notification.created_by.name }} {% endif %}
on {{ notification.created_at|format_datetime_short }}
</p>
</div>

View File

@@ -0,0 +1,27 @@
{% extends "withnav_template.html" %}
{% from "components/banner.html" import banner %}
{% from "components/ajax-block.html" import ajax_block %}
{% from "components/page-footer.html" import page_footer %}
{% block service_page_title %}
Report
{% endblock %}
{% block maincolumn_content %}
<h1 class="heading-large">
Report
</h1>
{{ template|string }}
{{ ajax_block(partials, updates_url, 'status', finished=finished) }}
{{ ajax_block(partials, updates_url, 'counts', finished=finished) }}
{{ ajax_block(partials, updates_url, 'notifications', finished=finished) }}
{{ page_footer(
secondary_link=url_for('.view_template', service_id=current_service.id, template_id=template.id),
secondary_link_text='Back to {}'.format(template.name)
) }}
{% endblock %}

View File

@@ -1,11 +1,13 @@
import re
import csv
from io import StringIO, BytesIO
from io import StringIO
from os import path
from functools import wraps
import unicodedata
from datetime import datetime
from datetime import datetime, timedelta, timezone
import dateutil
import ago
from flask import (
abort,
current_app,
@@ -15,6 +17,11 @@ from flask import (
url_for
)
from flask_login import current_user
import pyexcel
import pyexcel.ext.io
import pyexcel.ext.xls
import pyexcel.ext.xlsx
import pyexcel.ext.ods3
from notifications_utils.template import (
SMSPreviewTemplate,
@@ -23,12 +30,6 @@ from notifications_utils.template import (
LetterPreviewTemplate,
)
import pyexcel
import pyexcel.ext.io
import pyexcel.ext.xls
import pyexcel.ext.xlsx
import pyexcel.ext.ods3
SENDING_STATUSES = ['created', 'pending', 'sending']
DELIVERED_STATUSES = ['delivered', 'sent']
@@ -144,13 +145,14 @@ def generate_notifications_csv(**kwargs):
notification['status'],
notification['created_at']
]
line = ','.join([str(i) for i in values]) + '\n'
line = ','.join(str(i) for i in values) + '\n'
yield line
if notifications_resp['links'].get('next'):
kwargs['page'] += 1
else:
return
raise Exception("Should never reach here")
def get_page_from_request():
@@ -301,3 +303,16 @@ def get_current_financial_year():
current_month = int(now.strftime('%-m'))
current_year = int(now.strftime('%Y'))
return current_year if current_month > 3 else current_year - 1
def get_time_left(created_at):
return ago.human(
(
datetime.now(timezone.utc).replace(hour=23, minute=59, second=59)
) - (
dateutil.parser.parse(created_at) + timedelta(days=8)
),
future_tense='Data available for {}',
past_tense='Data no longer available', # No-one should ever see this
precision=1
)