mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-20 22:40:31 -04:00
@@ -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'
|
||||
|
||||
@@ -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
|
||||
),
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
31
app/templates/partials/notifications/notifications.html
Normal file
31
app/templates/partials/notifications/notifications.html
Normal 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>
|
||||
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 %}
|
||||
33
app/utils.py
33
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,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
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user