Merge pull request #227 from alphagov/poll-job-page

Make job page poll for updates
This commit is contained in:
Rebecca Law
2016-03-04 11:07:14 +00:00
12 changed files with 260 additions and 117 deletions

View 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);

View File

@@ -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=[

View File

@@ -234,7 +234,6 @@ def check_messages(service_id, upload_id):
else:
raise e
flash('Weve started sending your messages', 'default_with_tick')
return redirect(
url_for('main.view_job', service_id=service_id, job_id=upload_id)
)

View 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>

View 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 %}

View 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 %}

View File

@@ -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 %}

View 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 %}

View File

@@ -54,6 +54,7 @@ gulp.task('javascripts', () => gulp
paths.src + 'javascripts/autofocus.js',
paths.src + 'javascripts/highlightTags.js',
paths.src + 'javascripts/fileUpload.js',
paths.src + 'javascripts/updateContent.js',
paths.src + 'javascripts/main.js'
])
.pipe(plugins.babel({

View File

@@ -1,5 +1,6 @@
from flask import url_for
from bs4 import BeautifulSoup
import json
def test_should_return_list_of_all_jobs(app_,
@@ -21,17 +22,19 @@ def test_should_return_list_of_all_jobs(app_,
assert len(jobs) == 5
def test_should_show_page_for_one_job(app_,
service_one,
api_user_active,
mock_login,
mock_get_user,
mock_get_user_by_email,
mock_get_service,
mock_get_service_template,
job_data,
mock_get_job,
mock_get_notifications):
def test_should_show_page_for_one_job(
app_,
service_one,
api_user_active,
mock_login,
mock_get_user,
mock_get_user_by_email,
mock_get_service,
mock_get_service_template,
job_data,
mock_get_job,
mock_get_notifications
):
service_id = job_data['service']
job_id = job_data['id']
file_name = job_data['original_file_name']
@@ -45,3 +48,35 @@ def test_should_show_page_for_one_job(app_,
content = response.get_data(as_text=True)
assert "Test Service: Your vehicle tax is about to expire" in content
assert file_name in content
def test_should_show_updates_for_one_job_as_json(
app_,
service_one,
api_user_active,
mock_login,
mock_get_user,
mock_get_user_by_email,
mock_get_service,
mock_get_service_template,
job_data,
mock_get_job,
mock_get_notifications
):
service_id = job_data['service']
job_id = job_data['id']
file_name = job_data['original_file_name']
with app_.test_request_context():
with app_.test_client() as client:
client.login(api_user_active)
response = client.get(url_for('main.view_job_updates', service_id=service_id, job_id=job_id))
assert response.status_code == 200
content = json.loads(response.get_data(as_text=True))
assert 'sent' in content['counts']
assert 'queued' in content['counts']
assert 'failed' in content['counts']
assert 'Recipient' in content['notifications']
assert 'Status' in content['notifications']
assert 'Started' in content['status']

View File

@@ -265,7 +265,7 @@ def test_create_job_should_call_api(
response = client.post(url, data=job_data, follow_redirects=True)
assert response.status_code == 200
assert 'Weve started sending your messages' in response.get_data(as_text=True)
assert original_file_name in response.get_data(as_text=True)
mock_create_job.assert_called_with(job_id, service_id, template_id, original_file_name, notification_count)