mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-03-02 07:11:14 -05:00
add add one-off notification status
completely mimicks the job status page, and as such, all the code and templates have been taken from the job page. This page performs exactly the same as the job page for now * total, sending, delivered, failed blue boxes (though they'll just read 0/1 for now. * download report button (same as with job download, except without job or row number in file) * removed references to scheduled * kept references to help (aka tour/tutorial) as that'll eventually change over from a job to a one-off too
This commit is contained in:
@@ -29,4 +29,5 @@ from app.main.views import (
|
||||
platform_admin,
|
||||
letter_jobs,
|
||||
conversation,
|
||||
notifications
|
||||
)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
137
app/main/views/notifications.py
Normal file
137
app/main/views/notifications.py
Normal 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
|
||||
),
|
||||
}
|
||||
@@ -55,7 +55,5 @@ class NotificationApiClient(NotifyAdminAPIClient):
|
||||
params=params
|
||||
)
|
||||
|
||||
def get_notification(self, service_id, notification_id):
|
||||
return self.get(
|
||||
url='/service/{}/notifications/{}'.format(service_id, notification_id)
|
||||
)
|
||||
def get_notification(self, service_id, notification_id):m
|
||||
return self.get(url='/service/{}/notifications/{}'.format(service_id, notification_id))
|
||||
|
||||
39
app/templates/partials/notifications/notifications.html
Normal file
39
app/templates/partials/notifications/notifications.html
Normal file
@@ -0,0 +1,39 @@
|
||||
{% 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">
|
||||
|
||||
{% if not help %}
|
||||
<p class="bottom-gutter">
|
||||
<a href="{{ download_link }}" download="download" class="heading-small">Download this report</a>
|
||||
 
|
||||
<span id="time-left">{{ time_left }}</span>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% 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>
|
||||
6
app/templates/partials/notifications/status.html
Normal file
6
app/templates/partials/notifications/status.html
Normal 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>
|
||||
27
app/templates/views/notifications/notification.html
Normal file
27
app/templates/views/notifications/notification.html
Normal 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 %}
|
||||
50
app/utils.py
50
app/utils.py
@@ -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,31 @@ 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 generate_single_notification_csv(notification):
|
||||
fieldnames = ['Recipient', 'Template', 'Type', 'Status', 'Time']
|
||||
yield ','.join(fieldnames) + '\n'
|
||||
|
||||
values = [
|
||||
notification['to'],
|
||||
notification['template']['name'],
|
||||
notification['template']['template_type'],
|
||||
notification['status'],
|
||||
notification['created_at']
|
||||
]
|
||||
line = ','.join(str(i) for i in values) + '\n'
|
||||
yield line
|
||||
|
||||
return
|
||||
|
||||
|
||||
def get_page_from_request():
|
||||
@@ -301,3 +320,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
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user