mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-05-29 18:40:10 -04:00
Previously when a service had multiple "reply to" entries setup for email or SMS, we would show the one that was selected on all screens [1][2] except the final one, where the notification is actually sent. This fixes that, with the caveat that it will also show for services with only one "reply to" entry (see notes below) - we will look at making this consistent on the previous screens in the next commit. Here's a bit more detail on how this works: - If a service has multiple "reply to" entries, the journey to send a one-off message starts with a screen to select the "sender_id", which is otherwise "None" [3]. - The "sender_id" is subsequently resolved to an actual email / phone number by calling an API [4] and plucking it out of the response JSON. - The email / phone number then get rendered as part of the preview template [5][6]. - Unfortunately the "sender_id" is removed from the session by the time we get to the "view_notification" view [7]. - However, we can get back the equivalent text from the notification JSON, which is set by the API when the notification is created [8], give or take a bit of validation code [9][10]. - But the "reply_to_text" field is also set by the API when the service only has one "reply to" entry, so it will show then as well. We could add look at the number of "reply to" entries for the service, in order to consistently only show it when there is more the one. But it seems more useful to show it on previous screens, since it provides more information than is currently show (esp. for emails). [1]:93226ec5d6/app/main/views/send.py (L441-L442)[2]:93226ec5d6/app/main/views/send.py (L966-L967)[3]:93226ec5d6/app/main/views/send.py (L247)[4]:93226ec5d6/app/main/views/send.py (L1071-L1082)[5]:93226ec5d6/app/templates/views/notifications/notification.html (L80)[6]: https://github.com/alphagov/notifications-utils/blob/master/notifications_utils/jinja_templates/sms_preview_template.jinja2 [7]:93226ec5d6/app/main/views/send.py (L1059)[8]:f8b4c9151c/app/service/send_notification.py (L87-L93)[9]:f8b4c9151c/app/models.py (L653)[10]: https://github.com/alphagov/notifications-utils/blob/master/notifications_utils/recipients.py#L482
296 lines
10 KiB
Python
296 lines
10 KiB
Python
# -*- coding: utf-8 -*-
|
||
import base64
|
||
import io
|
||
import json
|
||
import os
|
||
from datetime import datetime
|
||
|
||
from dateutil import parser
|
||
from flask import (
|
||
Response,
|
||
flash,
|
||
jsonify,
|
||
redirect,
|
||
render_template,
|
||
request,
|
||
send_file,
|
||
stream_with_context,
|
||
url_for,
|
||
)
|
||
from notifications_python_client.errors import APIError
|
||
from notifications_utils.letter_timings import (
|
||
get_letter_timings,
|
||
letter_can_be_cancelled,
|
||
)
|
||
from notifications_utils.pdf import pdf_page_count
|
||
from PyPDF2.utils import PdfReadError
|
||
|
||
from app import (
|
||
current_service,
|
||
format_date_numeric,
|
||
job_api_client,
|
||
notification_api_client,
|
||
)
|
||
from app.main import main
|
||
from app.notify_client.api_key_api_client import KEY_TYPE_TEST
|
||
from app.template_previews import get_page_count_for_letter
|
||
from app.utils import (
|
||
DELIVERED_STATUSES,
|
||
FAILURE_STATUSES,
|
||
generate_notifications_csv,
|
||
get_help_argument,
|
||
get_letter_printing_statement,
|
||
get_letter_validation_error,
|
||
get_template,
|
||
parse_filter_args,
|
||
set_status_filters,
|
||
user_has_permissions,
|
||
)
|
||
|
||
|
||
@main.route("/services/<uuid:service_id>/notification/<uuid:notification_id>")
|
||
@user_has_permissions('view_activity', 'send_messages')
|
||
def view_notification(service_id, notification_id):
|
||
notification = notification_api_client.get_notification(service_id, str(notification_id))
|
||
notification['template'].update({'reply_to_text': notification['reply_to_text']})
|
||
|
||
personalisation = get_all_personalisation_from_notification(notification)
|
||
error_message = None
|
||
if notification['template']['is_precompiled_letter']:
|
||
try:
|
||
file_contents, metadata = get_letter_file_data(
|
||
service_id, notification_id, "pdf", with_metadata=True
|
||
)
|
||
page_count = int(
|
||
metadata["page_count"]
|
||
) if metadata.get("page_count") else pdf_page_count(io.BytesIO(file_contents))
|
||
if notification["status"] == "validation-failed":
|
||
invalid_pages = metadata.get("invalid_pages")
|
||
invalid_pages = json.loads(invalid_pages) if invalid_pages else invalid_pages
|
||
error_message = get_letter_validation_error(
|
||
metadata.get("message"), invalid_pages, page_count
|
||
)
|
||
except PdfReadError:
|
||
return render_template(
|
||
'views/notifications/invalid_precompiled_letter.html',
|
||
created_at=notification['created_at']
|
||
)
|
||
else:
|
||
page_count = get_page_count_for_letter(notification['template'], values=personalisation)
|
||
|
||
if notification.get('postage'):
|
||
if notification["status"] == "validation-failed":
|
||
notification['template']['postage'] = None
|
||
else:
|
||
notification['template']['postage'] = notification['postage']
|
||
template = get_template(
|
||
notification['template'],
|
||
current_service,
|
||
letter_preview_url=url_for(
|
||
'.view_letter_notification_as_preview',
|
||
service_id=service_id,
|
||
notification_id=notification_id,
|
||
filetype='png',
|
||
),
|
||
page_count=page_count,
|
||
show_recipient=True,
|
||
redact_missing_personalisation=True,
|
||
sms_sender=notification['reply_to_text'],
|
||
email_reply_to=notification['reply_to_text'],
|
||
)
|
||
template.values = personalisation
|
||
if notification['job']:
|
||
job = job_api_client.get_job(service_id, notification['job']['id'])['data']
|
||
else:
|
||
job = None
|
||
|
||
letter_print_day = get_letter_printing_statement(notification['status'], notification['created_at'])
|
||
|
||
notification_created = parser.parse(notification['created_at']).replace(tzinfo=None)
|
||
|
||
show_cancel_button = notification['notification_type'] == 'letter' and \
|
||
letter_can_be_cancelled(notification['status'], notification_created)
|
||
|
||
if get_help_argument() or request.args.get('help') == '0':
|
||
# help=0 is set when you’ve just sent a notification. We
|
||
# only want to show the back link when you’ve navigated to a
|
||
# notification, not when you’ve just sent it.
|
||
back_link = None
|
||
elif request.args.get('from_job'):
|
||
back_link = url_for(
|
||
'main.view_job',
|
||
service_id=current_service.id,
|
||
job_id=request.args.get('from_job'),
|
||
)
|
||
elif request.args.get('from_uploaded_letters'):
|
||
back_link = url_for(
|
||
'main.uploaded_letters',
|
||
service_id=current_service.id,
|
||
letter_print_day=request.args.get('from_uploaded_letters'),
|
||
)
|
||
else:
|
||
back_link = url_for(
|
||
'main.view_notifications',
|
||
service_id=current_service.id,
|
||
message_type=template.template_type,
|
||
status='sending,delivered,failed',
|
||
)
|
||
|
||
if notification['notification_type'] == 'letter':
|
||
estimated_letter_delivery_date = get_letter_timings(
|
||
notification['created_at'],
|
||
postage=notification['postage']
|
||
).earliest_delivery
|
||
else:
|
||
estimated_letter_delivery_date = None
|
||
|
||
return render_template(
|
||
'views/notifications/notification.html',
|
||
finished=(notification['status'] in (DELIVERED_STATUSES + FAILURE_STATUSES)),
|
||
notification_status=notification['status'],
|
||
message=error_message,
|
||
uploaded_file_name='Report',
|
||
template=template,
|
||
job=job,
|
||
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),
|
||
created_by=notification.get('created_by'),
|
||
created_at=notification['created_at'],
|
||
updated_at=notification['updated_at'],
|
||
help=get_help_argument(),
|
||
estimated_letter_delivery_date=estimated_letter_delivery_date,
|
||
notification_id=notification['id'],
|
||
postage=notification['postage'],
|
||
can_receive_inbound=(current_service.has_permission('inbound_sms')),
|
||
is_precompiled_letter=notification['template']['is_precompiled_letter'],
|
||
letter_print_day=letter_print_day,
|
||
show_cancel_button=show_cancel_button,
|
||
sent_with_test_key=(
|
||
notification.get('key_type') == KEY_TYPE_TEST
|
||
),
|
||
back_link=back_link,
|
||
)
|
||
|
||
|
||
@main.route("/services/<uuid:service_id>/notification/<uuid:notification_id>/cancel", methods=['GET', 'POST'])
|
||
@user_has_permissions('view_activity', 'send_messages')
|
||
def cancel_letter(service_id, notification_id):
|
||
|
||
if request.method == 'POST':
|
||
notification_api_client.update_notification_to_cancelled(current_service.id, notification_id)
|
||
return redirect(url_for('main.view_notification', service_id=service_id, notification_id=notification_id))
|
||
|
||
flash("Are you sure you want to cancel sending this letter?", 'cancel')
|
||
return view_notification(service_id, notification_id)
|
||
|
||
|
||
def get_preview_error_image():
|
||
path = os.path.join(os.path.dirname(__file__), "..", "..", "static", "images", "preview_error.png")
|
||
with open(path, "rb") as file:
|
||
return file.read()
|
||
|
||
|
||
@main.route("/services/<uuid:service_id>/notification/<uuid:notification_id>.<letter_file_extension:filetype>")
|
||
@user_has_permissions('view_activity')
|
||
def view_letter_notification_as_preview(
|
||
service_id, notification_id, filetype, with_metadata=False
|
||
):
|
||
image_data = get_letter_file_data(service_id, notification_id, filetype, with_metadata)
|
||
file = io.BytesIO(image_data)
|
||
|
||
mimetype = 'image/png' if filetype == 'png' else 'application/pdf'
|
||
|
||
return send_file(
|
||
filename_or_fp=file,
|
||
mimetype=mimetype,
|
||
)
|
||
|
||
|
||
def get_letter_file_data(service_id, notification_id, filetype, with_metadata=False):
|
||
try:
|
||
preview = notification_api_client.get_notification_letter_preview(
|
||
service_id,
|
||
notification_id,
|
||
filetype,
|
||
page=request.args.get('page')
|
||
)
|
||
|
||
display_file = base64.b64decode(preview['content'])
|
||
except APIError:
|
||
display_file = get_preview_error_image()
|
||
preview = {"metadata": {}}
|
||
|
||
if with_metadata:
|
||
return display_file, preview['metadata']
|
||
return display_file
|
||
|
||
|
||
@main.route("/services/<uuid:service_id>/notification/<uuid:notification_id>.json")
|
||
@user_has_permissions('view_activity', 'send_messages')
|
||
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_partials(notification):
|
||
return {
|
||
'status': render_template(
|
||
'partials/notifications/status.html',
|
||
notification=notification,
|
||
sent_with_test_key=(
|
||
notification.get('key_type') == KEY_TYPE_TEST
|
||
),
|
||
),
|
||
}
|
||
|
||
|
||
def get_all_personalisation_from_notification(notification):
|
||
|
||
if notification['template'].get('redact_personalisation'):
|
||
notification['personalisation'] = {}
|
||
|
||
if notification['template']['template_type'] == 'email':
|
||
notification['personalisation']['email_address'] = notification['to']
|
||
|
||
if notification['template']['template_type'] == 'sms':
|
||
notification['personalisation']['phone_number'] = notification['to']
|
||
|
||
return notification['personalisation']
|
||
|
||
|
||
@main.route("/services/<uuid:service_id>/download-notifications.csv")
|
||
@user_has_permissions('view_activity')
|
||
def download_notifications_csv(service_id):
|
||
filter_args = parse_filter_args(request.args)
|
||
filter_args['status'] = set_status_filters(filter_args)
|
||
|
||
service_data_retention_days = current_service.get_days_of_retention(filter_args.get('message_type')[0])
|
||
return Response(
|
||
stream_with_context(
|
||
generate_notifications_csv(
|
||
service_id=service_id,
|
||
job_id=None,
|
||
status=filter_args.get('status'),
|
||
page=request.args.get('page', 1),
|
||
page_size=10000,
|
||
format_for_csv=True,
|
||
template_type=filter_args.get('message_type'),
|
||
limit_days=service_data_retention_days,
|
||
)
|
||
),
|
||
mimetype='text/csv',
|
||
headers={
|
||
'Content-Disposition': 'inline; filename="{} - {} - {} report.csv"'.format(
|
||
format_date_numeric(datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%fZ")),
|
||
filter_args['message_type'][0],
|
||
current_service.name)
|
||
}
|
||
)
|