2016-01-06 11:03:29 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
2016-08-05 10:18:27 +01:00
|
|
|
import ago
|
2016-07-18 09:05:00 +01:00
|
|
|
import dateutil
|
2016-08-05 10:59:36 +01:00
|
|
|
from orderedset import OrderedSet
|
2016-07-18 09:05:00 +01:00
|
|
|
from datetime import datetime, timedelta, timezone
|
2016-08-05 10:18:27 +01:00
|
|
|
from itertools import chain
|
2016-01-13 17:32:40 +00:00
|
|
|
|
|
|
|
|
from flask import (
|
|
|
|
|
render_template,
|
2016-03-02 17:36:20 +00:00
|
|
|
abort,
|
2016-03-16 16:57:10 +00:00
|
|
|
jsonify,
|
2016-04-13 12:50:28 +01:00
|
|
|
request,
|
2016-04-28 15:50:05 +01:00
|
|
|
url_for,
|
2016-09-01 15:40:49 +01:00
|
|
|
current_app,
|
2017-01-06 17:41:24 +00:00
|
|
|
redirect,
|
|
|
|
|
Response
|
2016-01-13 17:32:40 +00:00
|
|
|
)
|
2016-01-11 16:03:41 +00:00
|
|
|
from flask_login import login_required
|
2016-04-04 16:34:06 +01:00
|
|
|
from werkzeug.datastructures import MultiDict
|
2016-01-11 16:03:41 +00:00
|
|
|
|
2016-04-04 16:53:52 +01:00
|
|
|
from app import (
|
|
|
|
|
job_api_client,
|
|
|
|
|
notification_api_client,
|
|
|
|
|
service_api_client,
|
2016-05-24 09:52:43 +01:00
|
|
|
current_service,
|
|
|
|
|
format_datetime_short)
|
2015-12-17 11:14:19 +00:00
|
|
|
from app.main import main
|
2016-03-29 22:50:40 +01:00
|
|
|
from app.utils import (
|
|
|
|
|
get_page_from_request,
|
2016-10-10 14:50:59 +01:00
|
|
|
generate_next_dict,
|
|
|
|
|
generate_previous_dict,
|
2016-04-12 14:19:51 +01:00
|
|
|
user_has_permissions,
|
2016-10-05 17:45:04 +01:00
|
|
|
generate_notifications_csv,
|
2016-12-08 11:50:59 +00:00
|
|
|
get_help_argument,
|
|
|
|
|
get_template,
|
2016-10-05 17:45:04 +01:00
|
|
|
)
|
|
|
|
|
from app.statistics_utils import add_rate_to_job
|
2015-12-18 10:26:56 +00:00
|
|
|
|
2015-12-17 11:14:19 +00:00
|
|
|
|
2016-04-04 16:34:06 +01:00
|
|
|
def _parse_filter_args(filter_dict):
|
|
|
|
|
if not isinstance(filter_dict, MultiDict):
|
|
|
|
|
filter_dict = MultiDict(filter_dict)
|
2016-04-13 12:47:24 +01:00
|
|
|
|
|
|
|
|
return MultiDict(
|
|
|
|
|
(
|
|
|
|
|
key,
|
|
|
|
|
(','.join(filter_dict.getlist(key))).split(',')
|
|
|
|
|
)
|
|
|
|
|
for key in filter_dict.keys()
|
|
|
|
|
if ''.join(filter_dict.getlist(key))
|
|
|
|
|
)
|
2016-04-04 16:34:06 +01:00
|
|
|
|
|
|
|
|
|
2016-05-18 11:44:58 +01:00
|
|
|
def _set_status_filters(filter_args):
|
2016-08-05 10:18:27 +01:00
|
|
|
status_filters = filter_args.get('status', [])
|
Add filters for ‘processed’ and sending states
- _Processed_ is all the notifications that we know about, ie sending,
failed and delivered
- _Sending_ is notifications that we have either put into a queue or are
waiting to hear back from the provider about.
The big numbers on the dashboard are a count of all the messages we’ve
processed. So when you click them, the table of notifications you see
on the dashboard should contain that number of notifications.
This also gets the activity page one step closer to being like the job
page:
| Before | After
---------|----------------------------|---------------------------------
Activity | Sending, failed, both | Processed, sending, failed, delivered
Job page | Sending, failed, delivered | Sending, failed, delivered
2016-06-07 12:01:43 +01:00
|
|
|
all_failure_statuses = ['failed', 'temporary-failure', 'permanent-failure', 'technical-failure']
|
2016-08-03 09:47:12 +01:00
|
|
|
all_sending_statuses = ['created', 'sending']
|
2016-08-05 10:59:36 +01:00
|
|
|
return list(OrderedSet(chain(
|
|
|
|
|
(status_filters or all_sending_statuses + ['delivered'] + all_failure_statuses),
|
|
|
|
|
all_sending_statuses if 'sending' in status_filters else [],
|
|
|
|
|
all_failure_statuses if 'failed' in status_filters else []
|
|
|
|
|
)))
|
2016-05-18 11:44:58 +01:00
|
|
|
|
|
|
|
|
|
2016-02-02 14:24:08 +00:00
|
|
|
@main.route("/services/<service_id>/jobs")
|
2016-01-11 16:03:41 +00:00
|
|
|
@login_required
|
2016-03-29 13:23:36 +01:00
|
|
|
@user_has_permissions('view_activity', admin_override=True)
|
2016-01-15 17:46:09 +00:00
|
|
|
def view_jobs(service_id):
|
2016-10-10 14:50:59 +01:00
|
|
|
page = int(request.args.get('page', 1))
|
2016-10-05 17:45:04 +01:00
|
|
|
# all but scheduled and cancelled
|
2016-10-11 10:35:33 +01:00
|
|
|
statuses_to_display = job_api_client.JOB_STATUSES - {'scheduled', 'cancelled'}
|
2016-10-10 14:50:59 +01:00
|
|
|
jobs_response = job_api_client.get_jobs(service_id, statuses=statuses_to_display, page=page)
|
|
|
|
|
jobs = [
|
|
|
|
|
add_rate_to_job(job) for job in jobs_response['data']
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
prev_page = None
|
|
|
|
|
if jobs_response['links'].get('prev', None):
|
2016-10-10 17:15:57 +01:00
|
|
|
prev_page = generate_previous_dict('main.view_jobs', service_id, page)
|
2016-10-10 14:50:59 +01:00
|
|
|
next_page = None
|
|
|
|
|
if jobs_response['links'].get('next', None):
|
2016-10-10 17:15:57 +01:00
|
|
|
next_page = generate_next_dict('main.view_jobs', service_id, page)
|
2016-10-10 14:50:59 +01:00
|
|
|
|
2016-03-10 11:53:29 +00:00
|
|
|
return render_template(
|
|
|
|
|
'views/jobs/jobs.html',
|
2016-10-10 14:50:59 +01:00
|
|
|
jobs=jobs,
|
|
|
|
|
page=page,
|
|
|
|
|
prev_page=prev_page,
|
|
|
|
|
next_page=next_page,
|
2016-03-10 11:53:29 +00:00
|
|
|
)
|
2015-12-17 11:14:19 +00:00
|
|
|
|
|
|
|
|
|
2016-02-02 14:24:08 +00:00
|
|
|
@main.route("/services/<service_id>/jobs/<job_id>")
|
2016-01-11 16:03:41 +00:00
|
|
|
@login_required
|
2016-03-29 13:23:36 +01:00
|
|
|
@user_has_permissions('view_activity', admin_override=True)
|
2016-01-15 17:46:09 +00:00
|
|
|
def view_job(service_id, job_id):
|
2016-06-28 11:23:43 +01:00
|
|
|
job = job_api_client.get_job(service_id, job_id)['data']
|
2016-09-01 15:46:16 +01:00
|
|
|
|
|
|
|
|
if job['job_status'] == 'cancelled':
|
|
|
|
|
abort(404)
|
|
|
|
|
|
2016-06-08 16:34:26 +01:00
|
|
|
filter_args = _parse_filter_args(request.args)
|
2016-08-05 10:18:27 +01:00
|
|
|
filter_args['status'] = _set_status_filters(filter_args)
|
2016-06-28 11:23:43 +01:00
|
|
|
|
2016-08-25 09:15:55 +01:00
|
|
|
total_notifications = job.get('notification_count', 0)
|
|
|
|
|
processed_notifications = job.get('notifications_delivered', 0) + job.get('notifications_failed', 0)
|
2016-03-10 11:53:29 +00:00
|
|
|
return render_template(
|
|
|
|
|
'views/jobs/job.html',
|
2016-08-25 09:15:55 +01:00
|
|
|
finished=(total_notifications == processed_notifications),
|
2016-03-10 11:53:29 +00:00
|
|
|
uploaded_file_name=job['original_file_name'],
|
2016-12-08 11:50:59 +00:00
|
|
|
template=get_template(
|
2016-06-28 11:23:43 +01:00
|
|
|
service_api_client.get_service_template(
|
|
|
|
|
service_id=service_id,
|
|
|
|
|
template_id=job['template'],
|
|
|
|
|
version=job['template_version']
|
|
|
|
|
)['data'],
|
2016-12-08 11:50:59 +00:00
|
|
|
current_service,
|
2016-06-08 16:34:26 +01:00
|
|
|
),
|
2016-06-28 09:21:46 +01:00
|
|
|
status=request.args.get('status', ''),
|
|
|
|
|
updates_url=url_for(
|
|
|
|
|
".view_job_updates",
|
|
|
|
|
service_id=service_id,
|
|
|
|
|
job_id=job['id'],
|
2016-07-19 09:09:20 +01:00
|
|
|
status=request.args.get('status', ''),
|
|
|
|
|
help=get_help_argument()
|
2016-06-28 11:23:43 +01:00
|
|
|
),
|
2016-07-05 11:39:07 +01:00
|
|
|
partials=get_job_partials(job),
|
|
|
|
|
help=get_help_argument()
|
2016-03-10 11:53:29 +00:00
|
|
|
)
|
2015-12-17 11:14:19 +00:00
|
|
|
|
|
|
|
|
|
2016-06-07 11:41:20 +01:00
|
|
|
@main.route("/services/<service_id>/jobs/<job_id>.csv")
|
|
|
|
|
@login_required
|
|
|
|
|
@user_has_permissions('view_activity', admin_override=True)
|
|
|
|
|
def view_job_csv(service_id, job_id):
|
|
|
|
|
job = job_api_client.get_job(service_id, job_id)['data']
|
|
|
|
|
template = service_api_client.get_service_template(
|
|
|
|
|
service_id=service_id,
|
|
|
|
|
template_id=job['template'],
|
|
|
|
|
version=job['template_version']
|
|
|
|
|
)['data']
|
2016-06-08 16:34:26 +01:00
|
|
|
filter_args = _parse_filter_args(request.args)
|
2016-08-05 10:18:27 +01:00
|
|
|
filter_args['status'] = _set_status_filters(filter_args)
|
2016-06-07 11:41:20 +01:00
|
|
|
|
2017-01-06 17:41:24 +00:00
|
|
|
return Response(
|
2016-06-07 11:41:20 +01:00
|
|
|
generate_notifications_csv(
|
2017-01-06 17:41:24 +00:00
|
|
|
service_id=service_id,
|
|
|
|
|
job_id=job_id,
|
|
|
|
|
status=filter_args.get('status'),
|
2017-01-13 11:35:27 +00:00
|
|
|
page=request.args.get('page', 1),
|
|
|
|
|
page_size=5000
|
2016-06-07 11:41:20 +01:00
|
|
|
),
|
2017-01-06 17:41:24 +00:00
|
|
|
mimetype='text/csv',
|
|
|
|
|
headers={
|
2016-06-07 11:41:20 +01:00
|
|
|
'Content-Disposition': 'inline; filename="{} - {}.csv"'.format(
|
|
|
|
|
template['name'],
|
|
|
|
|
format_datetime_short(job['created_at'])
|
2017-01-06 17:41:24 +00:00
|
|
|
)}
|
2016-06-07 11:41:20 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2016-09-01 15:40:49 +01:00
|
|
|
@main.route("/services/<service_id>/jobs/<job_id>", methods=['POST'])
|
|
|
|
|
@login_required
|
|
|
|
|
@user_has_permissions('send_texts', 'send_emails', 'send_letters', admin_override=True)
|
|
|
|
|
def cancel_job(service_id, job_id):
|
|
|
|
|
job_api_client.cancel_job(service_id, job_id)
|
|
|
|
|
return redirect(url_for('main.service_dashboard', service_id=service_id))
|
|
|
|
|
|
|
|
|
|
|
2016-03-02 17:36:20 +00:00
|
|
|
@main.route("/services/<service_id>/jobs/<job_id>.json")
|
2016-05-27 19:13:01 +01:00
|
|
|
@user_has_permissions('view_activity', admin_override=True)
|
2016-03-02 17:36:20 +00:00
|
|
|
def view_job_updates(service_id, job_id):
|
2016-06-28 11:23:43 +01:00
|
|
|
return jsonify(**get_job_partials(
|
|
|
|
|
job_api_client.get_job(service_id, job_id)['data']
|
|
|
|
|
))
|
2016-03-02 17:36:20 +00:00
|
|
|
|
|
|
|
|
|
2016-06-07 11:17:33 +01:00
|
|
|
@main.route('/services/<service_id>/notifications/<message_type>')
|
2016-03-16 16:57:10 +00:00
|
|
|
@login_required
|
2016-03-29 13:23:36 +01:00
|
|
|
@user_has_permissions('view_activity', admin_override=True)
|
2016-06-07 11:17:33 +01:00
|
|
|
def view_notifications(service_id, message_type):
|
2016-09-27 15:07:40 +01:00
|
|
|
return render_template(
|
|
|
|
|
'views/notifications.html',
|
|
|
|
|
partials=get_notifications(service_id, message_type),
|
|
|
|
|
message_type=message_type,
|
2016-09-30 15:17:17 +01:00
|
|
|
status=request.args.get('status'),
|
|
|
|
|
page=request.args.get('page', 1)
|
2016-09-27 15:07:40 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@main.route('/services/<service_id>/notifications/<message_type>.json')
|
|
|
|
|
@user_has_permissions('view_activity', admin_override=True)
|
|
|
|
|
def get_notifications_as_json(service_id, message_type):
|
|
|
|
|
return jsonify(get_notifications(
|
|
|
|
|
service_id, message_type, status_override=request.args.get('status')
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@main.route('/services/<service_id>/notifications/<message_type>.csv', endpoint="view_notifications_csv")
|
|
|
|
|
@user_has_permissions('view_activity', admin_override=True)
|
|
|
|
|
def get_notifications(service_id, message_type, status_override=None):
|
2016-03-16 16:57:10 +00:00
|
|
|
# TODO get the api to return count of pages as well.
|
|
|
|
|
page = get_page_from_request()
|
|
|
|
|
if page is None:
|
|
|
|
|
abort(404, "Invalid page argument ({}) reverting to page 1.".format(request.args['page'], None))
|
2016-06-07 11:17:33 +01:00
|
|
|
if message_type not in ['email', 'sms']:
|
|
|
|
|
abort(404)
|
2016-04-04 16:34:06 +01:00
|
|
|
filter_args = _parse_filter_args(request.args)
|
2016-08-05 10:18:27 +01:00
|
|
|
filter_args['status'] = _set_status_filters(filter_args)
|
2017-01-13 11:35:27 +00:00
|
|
|
if request.path.endswith('csv'):
|
|
|
|
|
return Response(
|
|
|
|
|
generate_notifications_csv(
|
|
|
|
|
service_id=service_id,
|
|
|
|
|
page=page,
|
|
|
|
|
page_size=5000,
|
|
|
|
|
template_type=[message_type],
|
|
|
|
|
status=filter_args.get('status'),
|
|
|
|
|
limit_days=current_app.config['ACTIVITY_STATS_LIMIT_DAYS']
|
|
|
|
|
),
|
|
|
|
|
mimetype='text/csv',
|
|
|
|
|
headers={
|
|
|
|
|
'Content-Disposition': 'inline; filename="notifications.csv"'}
|
|
|
|
|
)
|
2016-04-28 15:50:05 +01:00
|
|
|
|
2016-04-04 16:34:06 +01:00
|
|
|
notifications = notification_api_client.get_notifications_for_service(
|
|
|
|
|
service_id=service_id,
|
|
|
|
|
page=page,
|
2016-06-07 11:17:33 +01:00
|
|
|
template_type=[message_type],
|
2016-05-18 11:44:58 +01:00
|
|
|
status=filter_args.get('status'),
|
2016-04-28 15:50:05 +01:00
|
|
|
limit_days=current_app.config['ACTIVITY_STATS_LIMIT_DAYS'])
|
2016-10-10 14:50:59 +01:00
|
|
|
|
|
|
|
|
url_args = {
|
|
|
|
|
'message_type': message_type,
|
|
|
|
|
'status': request.args.get('status')
|
|
|
|
|
}
|
2016-03-16 16:57:10 +00:00
|
|
|
prev_page = None
|
2017-01-13 11:35:27 +00:00
|
|
|
|
2016-03-16 16:57:10 +00:00
|
|
|
if notifications['links'].get('prev', None):
|
2016-10-10 17:15:57 +01:00
|
|
|
prev_page = generate_previous_dict('main.view_notifications', service_id, page, url_args=url_args)
|
2016-03-16 16:57:10 +00:00
|
|
|
next_page = None
|
2017-01-13 11:35:27 +00:00
|
|
|
|
2016-03-16 16:57:10 +00:00
|
|
|
if notifications['links'].get('next', None):
|
2016-10-10 17:15:57 +01:00
|
|
|
next_page = generate_next_dict('main.view_notifications', service_id, page, url_args)
|
2016-10-10 14:50:59 +01:00
|
|
|
|
2016-09-27 15:07:40 +01:00
|
|
|
return {
|
|
|
|
|
'counts': render_template(
|
|
|
|
|
'views/activity/counts.html',
|
|
|
|
|
status=request.args.get('status'),
|
|
|
|
|
status_filters=get_status_filters(
|
|
|
|
|
current_service,
|
|
|
|
|
message_type,
|
|
|
|
|
service_api_client.get_detailed_service(service_id)['data']['statistics']
|
|
|
|
|
)
|
|
|
|
|
),
|
|
|
|
|
'notifications': render_template(
|
|
|
|
|
'views/activity/notifications.html',
|
|
|
|
|
notifications=notifications['notifications'],
|
|
|
|
|
page=page,
|
|
|
|
|
prev_page=prev_page,
|
|
|
|
|
next_page=next_page,
|
|
|
|
|
status=request.args.get('status'),
|
2016-06-07 11:41:20 +01:00
|
|
|
message_type=message_type,
|
2016-09-27 15:07:40 +01:00
|
|
|
download_link=url_for(
|
|
|
|
|
'.view_notifications_csv',
|
|
|
|
|
service_id=current_service['id'],
|
|
|
|
|
message_type=message_type,
|
|
|
|
|
status=request.args.get('status')
|
|
|
|
|
)
|
2016-06-07 11:41:20 +01:00
|
|
|
),
|
2016-09-27 15:07:40 +01:00
|
|
|
}
|
2016-03-16 16:57:10 +00:00
|
|
|
|
|
|
|
|
|
2016-07-20 15:19:58 +01:00
|
|
|
def get_status_filters(service, message_type, statistics):
|
|
|
|
|
stats = statistics[message_type]
|
|
|
|
|
stats['sending'] = stats['requested'] - stats['delivered'] - stats['failed']
|
|
|
|
|
|
|
|
|
|
filters = [
|
|
|
|
|
# key, label, option
|
2016-08-03 09:47:27 +01:00
|
|
|
('requested', 'total', 'sending,delivered,failed'),
|
2016-07-20 15:19:58 +01:00
|
|
|
('sending', 'sending', 'sending'),
|
|
|
|
|
('delivered', 'delivered', 'delivered'),
|
|
|
|
|
('failed', 'failed', 'failed'),
|
|
|
|
|
]
|
|
|
|
|
return [
|
|
|
|
|
# return list containing label, option, link, count
|
|
|
|
|
(
|
|
|
|
|
label,
|
|
|
|
|
option,
|
|
|
|
|
url_for(
|
|
|
|
|
'.view_notifications',
|
|
|
|
|
service_id=service['id'],
|
|
|
|
|
message_type=message_type,
|
|
|
|
|
status=option
|
|
|
|
|
),
|
|
|
|
|
stats[key]
|
|
|
|
|
)
|
|
|
|
|
for key, label, option in filters
|
2017-01-06 17:41:24 +00:00
|
|
|
]
|
2016-07-20 15:19:58 +01:00
|
|
|
|
|
|
|
|
|
2016-06-08 16:34:26 +01:00
|
|
|
def _get_job_counts(job, help_argument):
|
2016-08-25 16:49:31 +01:00
|
|
|
sending = 0 if job['job_status'] == 'scheduled' else (
|
2016-08-09 10:39:57 +01:00
|
|
|
job.get('notification_count', 0) -
|
|
|
|
|
job.get('notifications_delivered', 0) -
|
|
|
|
|
job.get('notifications_failed', 0)
|
|
|
|
|
)
|
2016-06-08 16:34:26 +01:00
|
|
|
return [
|
|
|
|
|
(
|
|
|
|
|
label,
|
|
|
|
|
query_param,
|
|
|
|
|
url_for(
|
|
|
|
|
".view_job",
|
|
|
|
|
service_id=job['service'],
|
|
|
|
|
job_id=job['id'],
|
|
|
|
|
status=query_param,
|
|
|
|
|
help=help_argument
|
|
|
|
|
),
|
|
|
|
|
count
|
|
|
|
|
) for label, query_param, count in [
|
|
|
|
|
[
|
2016-09-07 10:31:33 +01:00
|
|
|
'total', '',
|
|
|
|
|
job.get('notification_count', 0)
|
2016-06-08 16:34:26 +01:00
|
|
|
],
|
|
|
|
|
[
|
2016-09-07 10:31:33 +01:00
|
|
|
'sending', 'sending',
|
|
|
|
|
sending
|
2016-06-08 16:34:26 +01:00
|
|
|
],
|
|
|
|
|
[
|
2016-09-07 10:31:33 +01:00
|
|
|
'delivered', 'delivered',
|
|
|
|
|
job.get('notifications_delivered', 0)
|
2016-06-08 16:34:26 +01:00
|
|
|
],
|
|
|
|
|
[
|
2016-09-07 10:31:33 +01:00
|
|
|
'failed', 'failed',
|
|
|
|
|
job.get('notifications_failed', 0)
|
2016-06-08 16:34:26 +01:00
|
|
|
]
|
|
|
|
|
]
|
2016-09-08 09:29:08 +01:00
|
|
|
]
|
2016-06-28 11:23:43 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_job_partials(job):
|
|
|
|
|
filter_args = _parse_filter_args(request.args)
|
2016-08-05 10:18:27 +01:00
|
|
|
filter_args['status'] = _set_status_filters(filter_args)
|
2016-08-02 21:27:23 +01:00
|
|
|
notifications = notification_api_client.get_notifications_for_service(
|
2016-08-05 10:18:27 +01:00
|
|
|
job['service'], job['id'], status=filter_args['status']
|
2016-08-02 21:27:23 +01:00
|
|
|
)
|
2016-06-28 11:23:43 +01:00
|
|
|
return {
|
|
|
|
|
'counts': render_template(
|
|
|
|
|
'partials/jobs/count.html',
|
|
|
|
|
counts=_get_job_counts(job, request.args.get('help', 0)),
|
2016-08-05 10:18:27 +01:00
|
|
|
status=filter_args['status']
|
2016-06-28 11:23:43 +01:00
|
|
|
),
|
|
|
|
|
'notifications': render_template(
|
|
|
|
|
'partials/jobs/notifications.html',
|
2016-08-02 21:27:23 +01:00
|
|
|
notifications=notifications['notifications'],
|
|
|
|
|
more_than_one_page=bool(notifications.get('links', {}).get('next')),
|
2016-08-30 15:13:37 +01:00
|
|
|
percentage_complete=(job['notifications_requested'] / job['notification_count'] * 100),
|
2016-07-18 09:04:43 +01:00
|
|
|
download_link=url_for(
|
|
|
|
|
'.view_job_csv',
|
|
|
|
|
service_id=current_service['id'],
|
|
|
|
|
job_id=job['id'],
|
2016-08-05 10:18:27 +01:00
|
|
|
status=request.args.get('status')
|
2016-07-18 09:04:43 +01:00
|
|
|
),
|
2016-07-18 09:05:00 +01:00
|
|
|
help=get_help_argument(),
|
2016-08-25 16:49:31 +01:00
|
|
|
time_left=get_time_left(job['created_at']),
|
|
|
|
|
job=job
|
2016-06-28 11:23:43 +01:00
|
|
|
),
|
|
|
|
|
'status': render_template(
|
|
|
|
|
'partials/jobs/status.html',
|
|
|
|
|
job=job
|
|
|
|
|
),
|
|
|
|
|
}
|
2016-07-20 11:50:22 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
)
|