mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-02-05 10:53:28 -05:00
Make job page poll for updates
This is a first go at having the job page update without refreshing. The approach I’ve taken is to do all the rendering of HTML on the server side, rather than use a Javascipt templating engine like mustache. This ensures that we don’t have to maintain two sets of templates. So the approach is to split the job page into partials. These partials can then: - be included in the job page to render the whole page - be rendered indivudually and then returned as a blob of HTML inside a JSON response Then I’ve added a Javascript module which looks for areas of the page that should be reloaded. For each area of the page it will poll a URL and re-render that section of the page when it gets new HTML. It implements some throttling so that API calls will never happen more frequently than 0.67 times/second.
This commit is contained in:
44
app/assets/javascripts/updateContent.js
Normal file
44
app/assets/javascripts/updateContent.js
Normal file
@@ -0,0 +1,44 @@
|
||||
(function(GOVUK, Modules) {
|
||||
"use strict";
|
||||
|
||||
const interval = 1500; // milliseconds
|
||||
|
||||
GOVUK.timeCache = {};
|
||||
GOVUK.resultCache = {};
|
||||
|
||||
let getter = function(resource, render) {
|
||||
|
||||
if (
|
||||
GOVUK.resultCache[resource] &&
|
||||
(Date.now() < GOVUK.timeCache[resource])
|
||||
) {
|
||||
render(GOVUK.resultCache[resource]);
|
||||
} else {
|
||||
GOVUK.timeCache[resource] = Date.now() + interval;
|
||||
$.get(
|
||||
resource,
|
||||
response => render(GOVUK.resultCache[resource] = response)
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
let poller = (resource, key, component) => () => getter(
|
||||
resource, response => component.html(response[key])
|
||||
);
|
||||
|
||||
Modules.UpdateContent = function() {
|
||||
|
||||
this.start = function(component) {
|
||||
|
||||
const $component = $(component);
|
||||
|
||||
setInterval(
|
||||
poller($component.data('resource'), $component.data('key'), $component),
|
||||
interval / 5
|
||||
);
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
})(window.GOVUK, window.GOVUK.Modules);
|
||||
@@ -4,7 +4,8 @@ import time
|
||||
|
||||
from flask import (
|
||||
render_template,
|
||||
abort
|
||||
abort,
|
||||
jsonify
|
||||
)
|
||||
from flask_login import login_required
|
||||
from notifications_python_client.errors import HTTPError
|
||||
@@ -15,8 +16,6 @@ from app.main import main
|
||||
from app.main.dao import templates_dao
|
||||
from app.main.dao import services_dao
|
||||
|
||||
now = time.strftime('%H:%M')
|
||||
|
||||
|
||||
@main.route("/services/<service_id>/jobs")
|
||||
@login_required
|
||||
@@ -24,7 +23,7 @@ def view_jobs(service_id):
|
||||
try:
|
||||
jobs = job_api_client.get_job(service_id)['data']
|
||||
return render_template(
|
||||
'views/jobs.html',
|
||||
'views/jobs/jobs.html',
|
||||
jobs=jobs,
|
||||
service_id=service_id
|
||||
)
|
||||
@@ -44,16 +43,16 @@ def view_job(service_id, job_id):
|
||||
notifications = notification_api_client.get_notifications_for_service(service_id, job_id)
|
||||
finished = job['status'] == 'finished'
|
||||
return render_template(
|
||||
'views/job.html',
|
||||
'views/jobs/job.html',
|
||||
notifications=notifications['notifications'],
|
||||
counts={
|
||||
'queued': 0 if finished else job['notification_count'],
|
||||
'sent': job['notification_count'] if finished else 0,
|
||||
'failed': 0
|
||||
'failed': 0,
|
||||
'cost': u'£0.00'
|
||||
},
|
||||
uploaded_at=job['created_at'],
|
||||
finished_at=job['updated_at'] if finished else None,
|
||||
cost=u'£0.00',
|
||||
uploaded_file_name=job['original_file_name'],
|
||||
template=Template(
|
||||
templates_dao.get_service_template_or_404(service_id, job['template'])['data'],
|
||||
@@ -70,9 +69,47 @@ def view_job(service_id, job_id):
|
||||
raise e
|
||||
|
||||
|
||||
@main.route("/services/<service_id>/jobs/<job_id>.json")
|
||||
@login_required
|
||||
def view_job_updates(service_id, job_id):
|
||||
service = services_dao.get_service_by_id_or_404(service_id)
|
||||
try:
|
||||
job = job_api_client.get_job(service_id, job_id)['data']
|
||||
notifications = notification_api_client.get_notifications_for_service(service_id, job_id)
|
||||
finished = job['status'] == 'finished'
|
||||
return jsonify(**{
|
||||
'counts': render_template(
|
||||
'partials/jobs/count.html',
|
||||
counts={
|
||||
'queued': 0 if finished else job['notification_count'],
|
||||
'sent': job['notification_count'] if finished else 0,
|
||||
'failed': 0,
|
||||
'cost': u'£0.00'
|
||||
}
|
||||
),
|
||||
'notifications': render_template(
|
||||
'partials/jobs/notifications.html',
|
||||
notifications=notifications['notifications']
|
||||
),
|
||||
'status': render_template(
|
||||
'partials/jobs/status.html',
|
||||
uploaded_at=job['created_at'],
|
||||
finished_at=job['updated_at'] if finished else None
|
||||
),
|
||||
})
|
||||
except HTTPError as e:
|
||||
if e.status_code == 404:
|
||||
abort(404)
|
||||
else:
|
||||
raise e
|
||||
|
||||
|
||||
@main.route("/services/<service_id>/jobs/<job_id>/notification/<string:notification_id>")
|
||||
@login_required
|
||||
def view_notification(service_id, job_id, notification_id):
|
||||
|
||||
now = time.strftime('%H:%M')
|
||||
|
||||
return render_template(
|
||||
'views/notification.html',
|
||||
message=[
|
||||
|
||||
24
app/templates/partials/jobs/count.html
Normal file
24
app/templates/partials/jobs/count.html
Normal file
@@ -0,0 +1,24 @@
|
||||
{% from "components/big-number.html" import big_number %}
|
||||
<ul class="grid-row job-totals">
|
||||
<li class="column-one-quarter">
|
||||
{{ big_number(
|
||||
counts.queued, 'queued'
|
||||
)}}
|
||||
</li>
|
||||
<li class="column-one-quarter">
|
||||
{{ big_number(
|
||||
counts.sent, 'sent'
|
||||
)}}
|
||||
</li>
|
||||
<li class="column-one-quarter">
|
||||
{{ big_number(
|
||||
counts.failed,
|
||||
'failed'
|
||||
)}}
|
||||
</li>
|
||||
<li class="column-one-quarter">
|
||||
{{ big_number(
|
||||
counts.cost, 'total cost'
|
||||
)}}
|
||||
</li>
|
||||
</ul>
|
||||
22
app/templates/partials/jobs/notifications.html
Normal file
22
app/templates/partials/jobs/notifications.html
Normal file
@@ -0,0 +1,22 @@
|
||||
{% from "components/table.html" import list_table, field, right_aligned_field_heading %}
|
||||
|
||||
{% call(item) list_table(
|
||||
notifications,
|
||||
caption=uploaded_file_name,
|
||||
caption_visible=False,
|
||||
empty_message="No messages to show yet",
|
||||
field_headings=[
|
||||
'Recipient',
|
||||
right_aligned_field_heading('Status')
|
||||
]
|
||||
) %}
|
||||
{% call field() %}
|
||||
{{ item.to }}
|
||||
{% endcall %}
|
||||
{% call field(
|
||||
align='right',
|
||||
status='error' if item.status == 'Failed' else 'default'
|
||||
) %}
|
||||
{{ item.status|title }} at {{ item.sent_at|format_time }}
|
||||
{% endcall %}
|
||||
{% endcall %}
|
||||
9
app/templates/partials/jobs/status.html
Normal file
9
app/templates/partials/jobs/status.html
Normal file
@@ -0,0 +1,9 @@
|
||||
{% if finished_at %}
|
||||
<p class='heading-small'>
|
||||
Finished {{ finished_at|format_datetime }}
|
||||
</p>
|
||||
{% else %}
|
||||
<p class='heading-small'>
|
||||
Started {{ uploaded_at|format_datetime }}
|
||||
</p>
|
||||
{% endif %}
|
||||
@@ -1,97 +0,0 @@
|
||||
{% extends "withnav_template.html" %}
|
||||
{% from "components/table.html" import list_table, field, right_aligned_field_heading %}
|
||||
{% from "components/big-number.html" import big_number %}
|
||||
{% from "components/banner.html" import banner %}
|
||||
{% from "components/sms-message.html" import sms_message %}
|
||||
{% from "components/email-message.html" import email_message %}
|
||||
|
||||
{% block page_title %}
|
||||
{{ uploaded_file_name }} – GOV.UK Notify
|
||||
{% endblock %}
|
||||
|
||||
{% block maincolumn_content %}
|
||||
|
||||
<h1 class="heading-large">
|
||||
{{ uploaded_file_name }}
|
||||
</h1>
|
||||
|
||||
{% if 'sms' == template.template_type %}
|
||||
<div class="grid-row">
|
||||
<div class="column-two-thirds">
|
||||
{{ sms_message(
|
||||
template.formatted_as_markup,
|
||||
)}}
|
||||
</div>
|
||||
</div>
|
||||
{% elif 'email' == template.template_type %}
|
||||
{{ email_message(
|
||||
template.subject,
|
||||
template,
|
||||
from_address='{}@notifications.service.gov.uk'.format(service.email_from),
|
||||
from_name=from_name
|
||||
)}}
|
||||
{% endif %}
|
||||
|
||||
{% if finished_at %}
|
||||
<p class='heading-small'>
|
||||
Finished {{ finished_at|format_datetime }}
|
||||
</p>
|
||||
{% else %}
|
||||
<p class='heading-small'>
|
||||
Started {{ uploaded_at|format_datetime }}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<ul class="grid-row job-totals">
|
||||
<li class="column-one-quarter">
|
||||
{{ big_number(
|
||||
counts.queued, 'queued'
|
||||
)}}
|
||||
</li>
|
||||
<li class="column-one-quarter">
|
||||
{{ big_number(
|
||||
counts.sent, 'sent'
|
||||
)}}
|
||||
</li>
|
||||
<li class="column-one-quarter">
|
||||
{{ big_number(
|
||||
counts.failed,
|
||||
'failed'
|
||||
)}}
|
||||
</li>
|
||||
<li class="column-one-quarter">
|
||||
{{ big_number(
|
||||
cost, 'total cost'
|
||||
)}}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
{% if notifications %}
|
||||
{% call(item) list_table(
|
||||
notifications,
|
||||
caption=uploaded_file_name,
|
||||
caption_visible=False,
|
||||
empty_message="Messages go here",
|
||||
field_headings=[
|
||||
'Recipient',
|
||||
right_aligned_field_heading('Status')
|
||||
]
|
||||
) %}
|
||||
{% call field() %}
|
||||
{{ item.to }}
|
||||
{% endcall %}
|
||||
{% call field(
|
||||
align='right',
|
||||
status='error' if item.status == 'Failed' else 'default'
|
||||
) %}
|
||||
{{ item.status|title }} at {{ item.sent_at|format_time }}
|
||||
{% endcall %}
|
||||
{% endcall %}
|
||||
{% else %}
|
||||
<p>
|
||||
<a href="{{ url_for(".view_job", service_id=service_id, job_id=job_id) }}">Refresh</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
69
app/templates/views/jobs/job.html
Normal file
69
app/templates/views/jobs/job.html
Normal file
@@ -0,0 +1,69 @@
|
||||
{% extends "withnav_template.html" %}
|
||||
{% from "components/banner.html" import banner %}
|
||||
{% from "components/sms-message.html" import sms_message %}
|
||||
{% from "components/email-message.html" import email_message %}
|
||||
|
||||
{% block page_title %}
|
||||
{{ uploaded_file_name }} – GOV.UK Notify
|
||||
{% endblock %}
|
||||
|
||||
{% block maincolumn_content %}
|
||||
|
||||
<h1 class="heading-large">
|
||||
{{ uploaded_file_name }}
|
||||
</h1>
|
||||
|
||||
{% if 'sms' == template.template_type %}
|
||||
<div class="grid-row">
|
||||
<div class="column-two-thirds">
|
||||
{{ sms_message(
|
||||
template.formatted_as_markup,
|
||||
)}}
|
||||
</div>
|
||||
</div>
|
||||
{% elif 'email' == template.template_type %}
|
||||
{{ email_message(
|
||||
template.subject,
|
||||
template,
|
||||
from_address='{}@notifications.service.gov.uk'.format(service.email_from),
|
||||
from_name=from_name
|
||||
)}}
|
||||
{% endif %}
|
||||
|
||||
{% if not finished_at %}
|
||||
<div
|
||||
data-module="update-content"
|
||||
data-resource="{{url_for(".view_job_updates", service_id=service_id, job_id=job_id)}}"
|
||||
data-key="status"
|
||||
>
|
||||
{% endif %}
|
||||
{% include 'partials/jobs/status.html' %}
|
||||
{% if not finished_at %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if not finished_at %}
|
||||
<div
|
||||
data-module="update-content"
|
||||
data-resource="{{url_for(".view_job_updates", service_id=service_id, job_id=job_id)}}"
|
||||
data-key="counts"
|
||||
>
|
||||
{% endif %}
|
||||
{% include 'partials/jobs/count.html' %}
|
||||
{% if not finished_at %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if not finished_at %}
|
||||
<div
|
||||
data-module="update-content"
|
||||
data-resource="{{url_for(".view_job_updates", service_id=service_id, job_id=job_id)}}"
|
||||
data-key="notifications"
|
||||
>
|
||||
{% endif %}
|
||||
{% include 'partials/jobs/notifications.html' %}
|
||||
{% if not finished_at %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user