2016-08-23 17:38:27 +01:00
|
|
|
|
import itertools
|
2018-09-06 16:34:23 +01:00
|
|
|
|
import re
|
2019-02-19 11:35:34 +00:00
|
|
|
|
from collections import OrderedDict
|
2017-01-03 10:45:06 +00:00
|
|
|
|
from datetime import datetime
|
2018-02-20 11:22:17 +00:00
|
|
|
|
|
2019-04-30 17:44:59 +01:00
|
|
|
|
from flask import abort, flash, redirect, render_template, request, url_for
|
2018-09-06 16:34:23 +01:00
|
|
|
|
from notifications_python_client.errors import HTTPError
|
2016-05-24 15:52:44 +01:00
|
|
|
|
|
2018-06-28 10:28:57 +01:00
|
|
|
|
from app import (
|
2019-08-23 17:14:04 +01:00
|
|
|
|
billing_api_client,
|
2018-06-28 10:28:57 +01:00
|
|
|
|
complaint_api_client,
|
2019-04-24 16:23:12 +01:00
|
|
|
|
format_date_numeric,
|
2018-09-06 16:34:23 +01:00
|
|
|
|
letter_jobs_client,
|
2019-07-22 13:09:03 +01:00
|
|
|
|
notification_api_client,
|
2018-06-28 10:28:57 +01:00
|
|
|
|
platform_stats_api_client,
|
|
|
|
|
|
service_api_client,
|
|
|
|
|
|
)
|
2019-11-27 16:25:25 +00:00
|
|
|
|
from app.extensions import redis_client
|
2016-05-24 15:52:44 +01:00
|
|
|
|
from app.main import main
|
2019-02-15 11:13:53 +00:00
|
|
|
|
from app.main.forms import (
|
|
|
|
|
|
ClearCacheForm,
|
|
|
|
|
|
DateFilterForm,
|
2019-07-22 13:09:03 +01:00
|
|
|
|
RequiredDateFilterForm,
|
2019-02-15 11:13:53 +00:00
|
|
|
|
ReturnedLettersForm,
|
|
|
|
|
|
)
|
2018-08-14 13:34:53 +01:00
|
|
|
|
from app.statistics_utils import (
|
|
|
|
|
|
get_formatted_percentage,
|
|
|
|
|
|
get_formatted_percentage_two_dp,
|
|
|
|
|
|
)
|
2018-07-03 10:54:33 +01:00
|
|
|
|
from app.utils import (
|
2019-04-24 16:23:12 +01:00
|
|
|
|
Spreadsheet,
|
2018-07-03 10:54:33 +01:00
|
|
|
|
generate_next_dict,
|
|
|
|
|
|
generate_previous_dict,
|
2018-07-04 10:38:46 +01:00
|
|
|
|
get_page_from_request,
|
2018-07-03 10:54:33 +01:00
|
|
|
|
user_is_platform_admin,
|
|
|
|
|
|
)
|
2016-05-26 14:36:23 +01:00
|
|
|
|
|
2018-06-25 13:34:54 +01:00
|
|
|
|
COMPLAINT_THRESHOLD = 0.02
|
|
|
|
|
|
FAILURE_THRESHOLD = 3
|
|
|
|
|
|
ZERO_FAILURE_THRESHOLD = 0
|
|
|
|
|
|
|
2016-05-24 15:52:44 +01:00
|
|
|
|
|
2016-12-28 15:23:19 +00:00
|
|
|
|
@main.route("/platform-admin")
|
2018-02-27 16:45:20 +00:00
|
|
|
|
@user_is_platform_admin
|
2020-03-19 10:49:40 +00:00
|
|
|
|
def platform_admin_splash_page():
|
|
|
|
|
|
return render_template(
|
|
|
|
|
|
'views/platform-admin/splash-page.html',
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@main.route("/platform-admin/summary")
|
|
|
|
|
|
@user_is_platform_admin
|
2016-05-24 15:52:44 +01:00
|
|
|
|
def platform_admin():
|
2018-06-29 15:28:25 +01:00
|
|
|
|
form = DateFilterForm(request.args, meta={'csrf': False})
|
2018-06-25 13:34:54 +01:00
|
|
|
|
api_args = {}
|
|
|
|
|
|
|
2018-06-29 15:28:25 +01:00
|
|
|
|
form.validate()
|
|
|
|
|
|
|
2018-06-25 13:34:54 +01:00
|
|
|
|
if form.start_date.data:
|
|
|
|
|
|
api_args['start_date'] = form.start_date.data
|
|
|
|
|
|
api_args['end_date'] = form.end_date.data or datetime.utcnow().date()
|
|
|
|
|
|
|
2018-06-28 10:28:57 +01:00
|
|
|
|
platform_stats = platform_stats_api_client.get_aggregate_platform_stats(api_args)
|
2018-06-25 13:34:54 +01:00
|
|
|
|
number_of_complaints = complaint_api_client.get_complaint_count(api_args)
|
|
|
|
|
|
|
|
|
|
|
|
return render_template(
|
2018-07-10 14:39:54 +01:00
|
|
|
|
'views/platform-admin/index.html',
|
2018-06-25 13:34:54 +01:00
|
|
|
|
form=form,
|
|
|
|
|
|
global_stats=make_columns(platform_stats, number_of_complaints)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def is_over_threshold(number, total, threshold):
|
|
|
|
|
|
percentage = number / total * 100 if total else 0
|
|
|
|
|
|
return percentage > threshold
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_status_box_data(stats, key, label, threshold=FAILURE_THRESHOLD):
|
|
|
|
|
|
return {
|
2018-08-14 13:34:53 +01:00
|
|
|
|
'number': "{:,}".format(stats['failures'][key]),
|
2018-06-25 13:34:54 +01:00
|
|
|
|
'label': label,
|
|
|
|
|
|
'failing': is_over_threshold(
|
|
|
|
|
|
stats['failures'][key],
|
|
|
|
|
|
stats['total'],
|
|
|
|
|
|
threshold
|
|
|
|
|
|
),
|
|
|
|
|
|
'percentage': get_formatted_percentage(stats['failures'][key], stats['total'])
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_tech_failure_status_box_data(stats):
|
|
|
|
|
|
stats = get_status_box_data(stats, 'technical-failure', 'technical failures', ZERO_FAILURE_THRESHOLD)
|
|
|
|
|
|
stats.pop('percentage')
|
|
|
|
|
|
return stats
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def make_columns(global_stats, complaints_number):
|
|
|
|
|
|
return [
|
|
|
|
|
|
# email
|
|
|
|
|
|
{
|
|
|
|
|
|
'black_box': {
|
|
|
|
|
|
'number': global_stats['email']['total'],
|
|
|
|
|
|
'notification_type': 'email'
|
|
|
|
|
|
},
|
|
|
|
|
|
'other_data': [
|
|
|
|
|
|
get_tech_failure_status_box_data(global_stats['email']),
|
|
|
|
|
|
get_status_box_data(global_stats['email'], 'permanent-failure', 'permanent failures'),
|
|
|
|
|
|
get_status_box_data(global_stats['email'], 'temporary-failure', 'temporary failures'),
|
|
|
|
|
|
{
|
|
|
|
|
|
'number': complaints_number,
|
|
|
|
|
|
'label': 'complaints',
|
|
|
|
|
|
'failing': is_over_threshold(complaints_number,
|
|
|
|
|
|
global_stats['email']['total'], COMPLAINT_THRESHOLD),
|
2018-08-14 13:34:53 +01:00
|
|
|
|
'percentage': get_formatted_percentage_two_dp(complaints_number, global_stats['email']['total']),
|
2018-06-25 13:34:54 +01:00
|
|
|
|
'url': url_for('main.platform_admin_list_complaints')
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
'test_data': {
|
|
|
|
|
|
'number': global_stats['email']['test-key'],
|
|
|
|
|
|
'label': 'test emails'
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
# sms
|
|
|
|
|
|
{
|
|
|
|
|
|
'black_box': {
|
|
|
|
|
|
'number': global_stats['sms']['total'],
|
|
|
|
|
|
'notification_type': 'sms'
|
|
|
|
|
|
},
|
|
|
|
|
|
'other_data': [
|
|
|
|
|
|
get_tech_failure_status_box_data(global_stats['sms']),
|
|
|
|
|
|
get_status_box_data(global_stats['sms'], 'permanent-failure', 'permanent failures'),
|
|
|
|
|
|
get_status_box_data(global_stats['sms'], 'temporary-failure', 'temporary failures')
|
|
|
|
|
|
],
|
|
|
|
|
|
'test_data': {
|
|
|
|
|
|
'number': global_stats['sms']['test-key'],
|
|
|
|
|
|
'label': 'test text messages'
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
# letter
|
|
|
|
|
|
{
|
|
|
|
|
|
'black_box': {
|
|
|
|
|
|
'number': global_stats['letter']['total'],
|
|
|
|
|
|
'notification_type': 'letter'
|
|
|
|
|
|
},
|
|
|
|
|
|
'other_data': [
|
|
|
|
|
|
get_tech_failure_status_box_data(global_stats['letter']),
|
|
|
|
|
|
get_status_box_data(global_stats['letter'],
|
|
|
|
|
|
'virus-scan-failed', 'virus scan failures', ZERO_FAILURE_THRESHOLD)
|
|
|
|
|
|
],
|
|
|
|
|
|
'test_data': {
|
|
|
|
|
|
'number': global_stats['letter']['test-key'],
|
|
|
|
|
|
'label': 'test letters'
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
2017-07-21 09:14:29 +01:00
|
|
|
|
@main.route("/platform-admin/live-services", endpoint='live_services')
|
|
|
|
|
|
@main.route("/platform-admin/trial-services", endpoint='trial_services')
|
2018-02-27 16:45:20 +00:00
|
|
|
|
@user_is_platform_admin
|
2017-07-21 09:14:29 +01:00
|
|
|
|
def platform_admin_services():
|
|
|
|
|
|
form = DateFilterForm(request.args)
|
2018-08-03 14:30:34 +01:00
|
|
|
|
if all((
|
|
|
|
|
|
request.args.get('include_from_test_key') is None,
|
|
|
|
|
|
request.args.get('start_date') is None,
|
|
|
|
|
|
request.args.get('end_date') is None,
|
|
|
|
|
|
)):
|
|
|
|
|
|
# Default to True if the user hasn’t done any filtering,
|
|
|
|
|
|
# otherwise respect their choice
|
|
|
|
|
|
form.include_from_test_key.data = True
|
2017-09-19 10:13:12 +01:00
|
|
|
|
api_args = {'detailed': True,
|
2017-09-22 15:46:52 +01:00
|
|
|
|
'only_active': False, # specifically DO get inactive services
|
2020-05-14 16:59:34 +01:00
|
|
|
|
'include_from_test_key': form.include_from_test_key.data,
|
2017-07-21 09:14:29 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if form.start_date.data:
|
|
|
|
|
|
api_args['start_date'] = form.start_date.data
|
|
|
|
|
|
api_args['end_date'] = form.end_date.data or datetime.utcnow().date()
|
|
|
|
|
|
|
|
|
|
|
|
services = filter_and_sort_services(
|
|
|
|
|
|
service_api_client.get_services(api_args)['data'],
|
|
|
|
|
|
trial_mode_services=request.endpoint == 'main.trial_services',
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return render_template(
|
|
|
|
|
|
'views/platform-admin/services.html',
|
2020-05-14 16:59:34 +01:00
|
|
|
|
include_from_test_key=form.include_from_test_key.data,
|
2017-07-21 09:14:29 +01:00
|
|
|
|
form=form,
|
|
|
|
|
|
services=list(format_stats_by_service(services)),
|
|
|
|
|
|
page_title='{} services'.format(
|
|
|
|
|
|
'Trial mode' if request.endpoint == 'main.trial_services' else 'Live'
|
|
|
|
|
|
),
|
|
|
|
|
|
global_stats=create_global_stats(services),
|
|
|
|
|
|
)
|
2019-04-24 10:27:57 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@main.route("/platform-admin/reports")
|
|
|
|
|
|
@user_is_platform_admin
|
|
|
|
|
|
def platform_admin_reports():
|
|
|
|
|
|
return render_template(
|
|
|
|
|
|
'views/platform-admin/reports.html'
|
|
|
|
|
|
)
|
2019-04-24 16:23:12 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@main.route("/platform-admin/reports/live-services.csv")
|
|
|
|
|
|
@user_is_platform_admin
|
|
|
|
|
|
def live_services_csv():
|
2019-04-25 18:07:40 +01:00
|
|
|
|
results = service_api_client.get_live_services_data()["data"]
|
2019-05-31 12:13:34 +01:00
|
|
|
|
|
|
|
|
|
|
column_names = OrderedDict([
|
|
|
|
|
|
('service_id', 'Service ID'),
|
|
|
|
|
|
('organisation_name', 'Organisation'),
|
|
|
|
|
|
('organisation_type', 'Organisation type'),
|
|
|
|
|
|
('service_name', 'Service name'),
|
|
|
|
|
|
('consent_to_research', 'Consent to research'),
|
|
|
|
|
|
('contact_name', 'Main contact'),
|
|
|
|
|
|
('contact_email', 'Contact email'),
|
|
|
|
|
|
('contact_mobile', 'Contact mobile'),
|
|
|
|
|
|
('live_date', 'Live date'),
|
|
|
|
|
|
('sms_volume_intent', 'SMS volume intent'),
|
|
|
|
|
|
('email_volume_intent', 'Email volume intent'),
|
|
|
|
|
|
('letter_volume_intent', 'Letter volume intent'),
|
|
|
|
|
|
('sms_totals', 'SMS sent this year'),
|
|
|
|
|
|
('email_totals', 'Emails sent this year'),
|
|
|
|
|
|
('letter_totals', 'Letters sent this year'),
|
2019-05-31 12:24:18 +01:00
|
|
|
|
('free_sms_fragment_limit', 'Free sms allowance'),
|
2019-05-31 12:13:34 +01:00
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
# initialise with header row
|
|
|
|
|
|
live_services_data = [[x for x in column_names.values()]]
|
|
|
|
|
|
|
2019-04-25 18:07:40 +01:00
|
|
|
|
for row in results:
|
2019-05-31 12:13:34 +01:00
|
|
|
|
if row['live_date']:
|
|
|
|
|
|
row['live_date'] = datetime.strptime(row["live_date"], '%a, %d %b %Y %X %Z').strftime("%d-%m-%Y")
|
|
|
|
|
|
|
|
|
|
|
|
live_services_data.append([row[api_key] for api_key in column_names.keys()])
|
2019-04-30 17:44:59 +01:00
|
|
|
|
|
|
|
|
|
|
return Spreadsheet.from_rows(live_services_data).as_csv_data, 200, {
|
|
|
|
|
|
'Content-Type': 'text/csv; charset=utf-8',
|
|
|
|
|
|
'Content-Disposition': 'inline; filename="{} live services report.csv"'.format(
|
|
|
|
|
|
format_date_numeric(datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%fZ")),
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-05-03 14:32:02 +01:00
|
|
|
|
@main.route("/platform-admin/reports/performance-platform.xlsx")
|
2019-04-30 17:44:59 +01:00
|
|
|
|
@user_is_platform_admin
|
2019-05-03 14:32:02 +01:00
|
|
|
|
def performance_platform_xlsx():
|
2019-04-30 17:44:59 +01:00
|
|
|
|
results = service_api_client.get_live_services_data()["data"]
|
|
|
|
|
|
live_services_columns = ["service_id", "agency", "service_name", "_timestamp", "service", "count"]
|
|
|
|
|
|
live_services_data = []
|
|
|
|
|
|
live_services_data.append(live_services_columns)
|
|
|
|
|
|
for row in results:
|
|
|
|
|
|
live_services_data.append([
|
|
|
|
|
|
row["service_id"],
|
|
|
|
|
|
row["organisation_name"],
|
|
|
|
|
|
row["service_name"],
|
2019-05-01 18:29:51 +01:00
|
|
|
|
datetime.strptime(
|
|
|
|
|
|
row["live_date"], '%a, %d %b %Y %X %Z'
|
|
|
|
|
|
).strftime("%Y-%m-%dT%H:%M:%S") + "Z" if row["live_date"] else None,
|
2019-04-30 17:44:59 +01:00
|
|
|
|
"govuk-notify",
|
|
|
|
|
|
1
|
|
|
|
|
|
])
|
|
|
|
|
|
|
2019-05-03 14:32:02 +01:00
|
|
|
|
return Spreadsheet.from_rows(live_services_data).as_excel_file, 200, {
|
|
|
|
|
|
'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
|
|
|
|
'Content-Disposition': 'attachment; filename="{} performance platform report.xlsx"'.format(
|
2019-04-30 17:44:59 +01:00
|
|
|
|
format_date_numeric(datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%fZ")),
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
2017-07-21 09:14:29 +01:00
|
|
|
|
|
|
|
|
|
|
|
2019-07-22 13:09:03 +01:00
|
|
|
|
@main.route("/platform-admin/reports/notifications-sent-by-service", methods=['GET', 'POST'])
|
|
|
|
|
|
@user_is_platform_admin
|
|
|
|
|
|
def notifications_sent_by_service():
|
|
|
|
|
|
form = RequiredDateFilterForm()
|
|
|
|
|
|
|
|
|
|
|
|
if form.validate_on_submit():
|
|
|
|
|
|
start_date = form.start_date.data
|
|
|
|
|
|
end_date = form.end_date.data
|
|
|
|
|
|
|
|
|
|
|
|
headers = [
|
|
|
|
|
|
'date_created', 'service_id', 'service_name', 'notification_type', 'count_sending', 'count_delivered',
|
|
|
|
|
|
'count_technical_failure', 'count_temporary_failure', 'count_permanent_failure', 'count_sent'
|
|
|
|
|
|
]
|
|
|
|
|
|
result = notification_api_client.get_notification_status_by_service(start_date, end_date)
|
|
|
|
|
|
|
|
|
|
|
|
for row in result:
|
|
|
|
|
|
row[0] = datetime.strptime(row[0], '%a, %d %b %Y %X %Z').strftime('%Y-%m-%d')
|
|
|
|
|
|
|
|
|
|
|
|
return Spreadsheet.from_rows([headers] + result).as_csv_data, 200, {
|
|
|
|
|
|
'Content-Type': 'text/csv; charset=utf-8',
|
|
|
|
|
|
'Content-Disposition': 'attachment; filename="{} to {} notification status per service report.csv"'.format(
|
|
|
|
|
|
start_date, end_date)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return render_template('views/platform-admin/notifications_by_service.html', form=form)
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-08-23 17:14:04 +01:00
|
|
|
|
@main.route("/platform-admin/reports/usage-for-all-services", methods=['GET', 'POST'])
|
|
|
|
|
|
@user_is_platform_admin
|
|
|
|
|
|
def usage_for_all_services():
|
|
|
|
|
|
form = RequiredDateFilterForm()
|
|
|
|
|
|
|
|
|
|
|
|
if form.validate_on_submit():
|
|
|
|
|
|
start_date = form.start_date.data
|
|
|
|
|
|
end_date = form.end_date.data
|
|
|
|
|
|
headers = ["organisation_id", "organisation_name", "service_id", "service_name",
|
|
|
|
|
|
"sms_cost", "sms_fragments", "letter_cost", "letter_breakdown"]
|
|
|
|
|
|
|
|
|
|
|
|
result = billing_api_client.get_usage_for_all_services(start_date, end_date)
|
2019-08-27 16:53:08 +01:00
|
|
|
|
rows = [
|
|
|
|
|
|
[
|
|
|
|
|
|
r['organisation_id'], r["organisation_name"], r["service_id"], r["service_name"],
|
|
|
|
|
|
r["sms_cost"], r['sms_fragments'], r["letter_cost"], r["letter_breakdown"].strip()
|
|
|
|
|
|
]
|
|
|
|
|
|
for r in result
|
|
|
|
|
|
]
|
2019-08-23 17:14:04 +01:00
|
|
|
|
if rows:
|
|
|
|
|
|
return Spreadsheet.from_rows([headers] + rows).as_csv_data, 200, {
|
|
|
|
|
|
'Content-Type': 'text/csv; charset=utf-8',
|
|
|
|
|
|
'Content-Disposition': 'attachment; filename="Usage for all services from {} to {}.csv"'.format(
|
|
|
|
|
|
start_date, end_date
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
else:
|
2019-08-27 16:53:08 +01:00
|
|
|
|
flash('No results for dates')
|
2019-08-23 17:14:04 +01:00
|
|
|
|
return render_template('views/platform-admin/usage_for_all_services.html', form=form)
|
|
|
|
|
|
|
|
|
|
|
|
|
2018-06-06 15:22:48 +01:00
|
|
|
|
@main.route("/platform-admin/complaints")
|
|
|
|
|
|
@user_is_platform_admin
|
|
|
|
|
|
def platform_admin_list_complaints():
|
2018-07-04 10:38:46 +01:00
|
|
|
|
page = get_page_from_request()
|
|
|
|
|
|
if page is None:
|
|
|
|
|
|
abort(404, "Invalid page argument ({}).".format(request.args.get('page')))
|
|
|
|
|
|
|
2018-07-03 10:54:33 +01:00
|
|
|
|
response = complaint_api_client.get_all_complaints(page=page)
|
|
|
|
|
|
|
|
|
|
|
|
prev_page = None
|
|
|
|
|
|
if response['links'].get('prev'):
|
|
|
|
|
|
prev_page = generate_previous_dict('main.platform_admin_list_complaints', None, page)
|
|
|
|
|
|
next_page = None
|
|
|
|
|
|
if response['links'].get('next'):
|
|
|
|
|
|
next_page = generate_next_dict('main.platform_admin_list_complaints', None, page)
|
|
|
|
|
|
|
2018-06-06 15:22:48 +01:00
|
|
|
|
return render_template(
|
|
|
|
|
|
'views/platform-admin/complaints.html',
|
2018-07-03 10:54:33 +01:00
|
|
|
|
complaints=response['complaints'],
|
2018-06-06 15:22:48 +01:00
|
|
|
|
page_title='All Complaints',
|
2018-07-03 10:54:33 +01:00
|
|
|
|
page=page,
|
|
|
|
|
|
prev_page=prev_page,
|
|
|
|
|
|
next_page=next_page,
|
2018-06-06 15:22:48 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2018-09-06 16:34:23 +01:00
|
|
|
|
@main.route("/platform-admin/returned-letters", methods=["GET", "POST"])
|
|
|
|
|
|
@user_is_platform_admin
|
|
|
|
|
|
def platform_admin_returned_letters():
|
|
|
|
|
|
form = ReturnedLettersForm()
|
|
|
|
|
|
|
|
|
|
|
|
if form.validate_on_submit():
|
|
|
|
|
|
references = [
|
|
|
|
|
|
re.sub('NOTIFY00[0-9]', '', r.strip())
|
|
|
|
|
|
for r in form.references.data.split('\n')
|
|
|
|
|
|
if r.strip()
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
letter_jobs_client.submit_returned_letters(references)
|
2020-03-03 17:40:50 +00:00
|
|
|
|
redis_client.delete_cache_keys_by_pattern(
|
|
|
|
|
|
'service-????????-????-????-????-????????????-returned-letters-statistics'
|
|
|
|
|
|
)
|
2020-02-12 11:42:06 +00:00
|
|
|
|
redis_client.delete_cache_keys_by_pattern(
|
|
|
|
|
|
'service-????????-????-????-????-????????????-returned-letters-summary'
|
|
|
|
|
|
)
|
2018-09-06 16:34:23 +01:00
|
|
|
|
except HTTPError as error:
|
|
|
|
|
|
if error.status_code == 400:
|
|
|
|
|
|
error_references = [
|
|
|
|
|
|
re.match('references (.*) does not match', e['message']).group(1)
|
|
|
|
|
|
for e in error.message
|
|
|
|
|
|
]
|
|
|
|
|
|
form.references.errors.append("Invalid references: {}".format(', '.join(error_references)))
|
|
|
|
|
|
else:
|
|
|
|
|
|
raise error
|
|
|
|
|
|
else:
|
|
|
|
|
|
flash('Submitted {} letter references'.format(len(references)), 'default')
|
|
|
|
|
|
return redirect(
|
|
|
|
|
|
url_for('.platform_admin_returned_letters')
|
|
|
|
|
|
)
|
|
|
|
|
|
return render_template(
|
|
|
|
|
|
'views/platform-admin/returned-letters.html',
|
|
|
|
|
|
form=form,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-02-15 10:27:38 +00:00
|
|
|
|
@main.route("/platform-admin/clear-cache", methods=['GET', 'POST'])
|
|
|
|
|
|
@user_is_platform_admin
|
|
|
|
|
|
def clear_cache():
|
|
|
|
|
|
# note: `service-{uuid}-templates` cache is cleared for both services and templates.
|
2019-02-19 11:35:34 +00:00
|
|
|
|
CACHE_KEYS = OrderedDict([
|
|
|
|
|
|
('user', [
|
2019-02-15 10:27:38 +00:00
|
|
|
|
'user-????????-????-????-????-????????????',
|
2019-02-19 11:35:34 +00:00
|
|
|
|
]),
|
|
|
|
|
|
('service', [
|
2019-02-15 10:27:38 +00:00
|
|
|
|
'has_jobs-????????-????-????-????-????????????',
|
|
|
|
|
|
'service-????????-????-????-????-????????????',
|
|
|
|
|
|
'service-????????-????-????-????-????????????-templates',
|
|
|
|
|
|
'service-????????-????-????-????-????????????-data-retention',
|
|
|
|
|
|
'service-????????-????-????-????-????????????-template-folders',
|
2020-03-31 14:01:28 +01:00
|
|
|
|
'service-????????-????-????-????-????????????-returned-letters-statistics',
|
|
|
|
|
|
'service-????????-????-????-????-????????????-returned-letters-summary',
|
2019-02-19 11:35:34 +00:00
|
|
|
|
]),
|
|
|
|
|
|
('template', [
|
2019-02-15 10:27:38 +00:00
|
|
|
|
'service-????????-????-????-????-????????????-templates',
|
|
|
|
|
|
'template-????????-????-????-????-????????????-version-*',
|
|
|
|
|
|
'template-????????-????-????-????-????????????-versions',
|
2019-02-19 11:35:34 +00:00
|
|
|
|
]),
|
|
|
|
|
|
('email_branding', [
|
2019-02-15 10:27:38 +00:00
|
|
|
|
'email_branding',
|
|
|
|
|
|
'email_branding-????????-????-????-????-????????????',
|
2019-02-19 11:35:34 +00:00
|
|
|
|
]),
|
|
|
|
|
|
('letter_branding', [
|
|
|
|
|
|
'letter_branding',
|
|
|
|
|
|
'letter_branding-????????-????-????-????-????????????',
|
2019-06-14 11:10:57 +01:00
|
|
|
|
]),
|
|
|
|
|
|
('organisation', [
|
|
|
|
|
|
'organisations',
|
|
|
|
|
|
'domains',
|
|
|
|
|
|
'live-service-and-organisation-counts',
|
2020-03-20 08:42:33 +00:00
|
|
|
|
'organisation-????????-????-????-????-????????????-name',
|
2019-06-14 11:10:57 +01:00
|
|
|
|
]),
|
2019-02-19 11:35:34 +00:00
|
|
|
|
])
|
|
|
|
|
|
|
2019-02-15 10:27:38 +00:00
|
|
|
|
form = ClearCacheForm()
|
2019-02-19 11:35:34 +00:00
|
|
|
|
form.model_type.choices = [(key, key.replace('_', ' ').title()) for key in CACHE_KEYS]
|
2019-02-15 10:27:38 +00:00
|
|
|
|
|
|
|
|
|
|
if form.validate_on_submit():
|
|
|
|
|
|
to_delete = form.model_type.data
|
|
|
|
|
|
|
|
|
|
|
|
num_deleted = max(
|
|
|
|
|
|
redis_client.delete_cache_keys_by_pattern(pattern)
|
|
|
|
|
|
for pattern in CACHE_KEYS[to_delete]
|
|
|
|
|
|
)
|
2019-02-19 11:35:34 +00:00
|
|
|
|
msg = 'Removed {} {} object{} from redis'
|
|
|
|
|
|
flash(msg.format(num_deleted, to_delete, 's' if num_deleted != 1 else ''), category='default')
|
2019-02-15 10:27:38 +00:00
|
|
|
|
|
|
|
|
|
|
return render_template(
|
|
|
|
|
|
'views/platform-admin/clear-cache.html',
|
|
|
|
|
|
form=form
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2017-04-25 12:54:58 +01:00
|
|
|
|
def sum_service_usage(service):
|
|
|
|
|
|
total = 0
|
|
|
|
|
|
for notification_type in service['statistics'].keys():
|
|
|
|
|
|
total += service['statistics'][notification_type]['requested']
|
|
|
|
|
|
return total
|
|
|
|
|
|
|
|
|
|
|
|
|
2017-07-21 09:14:29 +01:00
|
|
|
|
def filter_and_sort_services(services, trial_mode_services=False):
|
2017-07-31 11:29:04 +01:00
|
|
|
|
return [
|
2017-07-21 09:14:29 +01:00
|
|
|
|
service for service in sorted(
|
|
|
|
|
|
services,
|
|
|
|
|
|
key=lambda service: (
|
|
|
|
|
|
service['active'],
|
|
|
|
|
|
sum_service_usage(service),
|
|
|
|
|
|
service['created_at']
|
|
|
|
|
|
),
|
|
|
|
|
|
reverse=True,
|
|
|
|
|
|
)
|
|
|
|
|
|
if service['restricted'] == trial_mode_services
|
2017-07-31 11:29:04 +01:00
|
|
|
|
]
|
2017-07-21 09:14:29 +01:00
|
|
|
|
|
|
|
|
|
|
|
2016-08-23 17:38:27 +01:00
|
|
|
|
def create_global_stats(services):
|
|
|
|
|
|
stats = {
|
|
|
|
|
|
'email': {
|
|
|
|
|
|
'delivered': 0,
|
|
|
|
|
|
'failed': 0,
|
|
|
|
|
|
'requested': 0
|
|
|
|
|
|
},
|
|
|
|
|
|
'sms': {
|
|
|
|
|
|
'delivered': 0,
|
|
|
|
|
|
'failed': 0,
|
|
|
|
|
|
'requested': 0
|
2017-10-02 12:34:10 +01:00
|
|
|
|
},
|
|
|
|
|
|
'letter': {
|
|
|
|
|
|
'delivered': 0,
|
|
|
|
|
|
'failed': 0,
|
|
|
|
|
|
'requested': 0
|
2016-08-23 17:38:27 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
for service in services:
|
2017-10-02 12:34:10 +01:00
|
|
|
|
for msg_type, status in itertools.product(('sms', 'email', 'letter'), ('delivered', 'failed', 'requested')):
|
2016-08-23 17:38:27 +01:00
|
|
|
|
stats[msg_type][status] += service['statistics'][msg_type][status]
|
|
|
|
|
|
|
|
|
|
|
|
for stat in stats.values():
|
|
|
|
|
|
stat['failure_rate'] = get_formatted_percentage(stat['failed'], stat['requested'])
|
|
|
|
|
|
return stats
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def format_stats_by_service(services):
|
|
|
|
|
|
for service in services:
|
|
|
|
|
|
yield {
|
|
|
|
|
|
'id': service['id'],
|
|
|
|
|
|
'name': service['name'],
|
2020-06-24 11:20:22 +01:00
|
|
|
|
'stats': service['statistics'],
|
2016-08-23 17:38:27 +01:00
|
|
|
|
'restricted': service['restricted'],
|
2016-09-12 11:40:21 +01:00
|
|
|
|
'research_mode': service['research_mode'],
|
2016-11-04 16:27:27 +00:00
|
|
|
|
'created_at': service['created_at'],
|
|
|
|
|
|
'active': service['active']
|
2016-05-31 11:08:22 +01:00
|
|
|
|
}
|