diff --git a/app/assets/javascripts/updateContent.js b/app/assets/javascripts/updateContent.js new file mode 100644 index 000000000..64cfc7325 --- /dev/null +++ b/app/assets/javascripts/updateContent.js @@ -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); diff --git a/app/main/views/jobs.py b/app/main/views/jobs.py index bb0013a63..093c91163 100644 --- a/app/main/views/jobs.py +++ b/app/main/views/jobs.py @@ -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//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//jobs/.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//jobs//notification/") @login_required def view_notification(service_id, job_id, notification_id): + + now = time.strftime('%H:%M') + return render_template( 'views/notification.html', message=[ diff --git a/app/main/views/send.py b/app/main/views/send.py index 7ff1629fb..b80d1d159 100644 --- a/app/main/views/send.py +++ b/app/main/views/send.py @@ -234,7 +234,6 @@ def check_messages(service_id, upload_id): else: raise e - flash('We’ve started sending your messages', 'default_with_tick') return redirect( url_for('main.view_job', service_id=service_id, job_id=upload_id) ) diff --git a/app/templates/partials/jobs/count.html b/app/templates/partials/jobs/count.html new file mode 100644 index 000000000..f88e0aed0 --- /dev/null +++ b/app/templates/partials/jobs/count.html @@ -0,0 +1,24 @@ +{% from "components/big-number.html" import big_number %} +
    +
  • + {{ big_number( + counts.queued, 'queued' + )}} +
  • +
  • + {{ big_number( + counts.sent, 'sent' + )}} +
  • +
  • + {{ big_number( + counts.failed, + 'failed' + )}} +
  • +
  • + {{ big_number( + counts.cost, 'total cost' + )}} +
  • +
diff --git a/app/templates/partials/jobs/notifications.html b/app/templates/partials/jobs/notifications.html new file mode 100644 index 000000000..bc4ecfaaf --- /dev/null +++ b/app/templates/partials/jobs/notifications.html @@ -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 %} diff --git a/app/templates/partials/jobs/status.html b/app/templates/partials/jobs/status.html new file mode 100644 index 000000000..775d0c2d1 --- /dev/null +++ b/app/templates/partials/jobs/status.html @@ -0,0 +1,9 @@ +{% if finished_at %} +

+ Finished {{ finished_at|format_datetime }} +

+{% else %} +

+ Started {{ uploaded_at|format_datetime }} +

+{% endif %} diff --git a/app/templates/views/job.html b/app/templates/views/job.html deleted file mode 100644 index 63346c7ea..000000000 --- a/app/templates/views/job.html +++ /dev/null @@ -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 %} - -

- {{ uploaded_file_name }} -

- - {% if 'sms' == template.template_type %} -
-
- {{ sms_message( - template.formatted_as_markup, - )}} -
-
- {% 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 %} -

- Finished {{ finished_at|format_datetime }} -

- {% else %} -

- Started {{ uploaded_at|format_datetime }} -

- {% endif %} - -
    -
  • - {{ big_number( - counts.queued, 'queued' - )}} -
  • -
  • - {{ big_number( - counts.sent, 'sent' - )}} -
  • -
  • - {{ big_number( - counts.failed, - 'failed' - )}} -
  • -
  • - {{ big_number( - cost, 'total cost' - )}} -
  • -
- - - {% 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 %} -

- Refresh -

- {% endif %} - -{% endblock %} diff --git a/app/templates/views/jobs/job.html b/app/templates/views/jobs/job.html new file mode 100644 index 000000000..3af945de8 --- /dev/null +++ b/app/templates/views/jobs/job.html @@ -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 %} + +

+ {{ uploaded_file_name }} +

+ + {% if 'sms' == template.template_type %} +
+
+ {{ sms_message( + template.formatted_as_markup, + )}} +
+
+ {% 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 %} +
+ {% endif %} + {% include 'partials/jobs/status.html' %} + {% if not finished_at %} +
+ {% endif %} + + {% if not finished_at %} +
+ {% endif %} + {% include 'partials/jobs/count.html' %} + {% if not finished_at %} +
+ {% endif %} + + {% if not finished_at %} +
+ {% endif %} + {% include 'partials/jobs/notifications.html' %} + {% if not finished_at %} +
+ {% endif %} + +{% endblock %} diff --git a/app/templates/views/jobs.html b/app/templates/views/jobs/jobs.html similarity index 100% rename from app/templates/views/jobs.html rename to app/templates/views/jobs/jobs.html diff --git a/gulpfile.babel.js b/gulpfile.babel.js index 030c5eeb7..b5470e712 100644 --- a/gulpfile.babel.js +++ b/gulpfile.babel.js @@ -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({ diff --git a/tests/app/main/views/test_jobs.py b/tests/app/main/views/test_jobs.py index 8a8ff0aff..2b8d4e794 100644 --- a/tests/app/main/views/test_jobs.py +++ b/tests/app/main/views/test_jobs.py @@ -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'] diff --git a/tests/app/main/views/test_send.py b/tests/app/main/views/test_send.py index 4a84ddcdd..e3169f966 100644 --- a/tests/app/main/views/test_send.py +++ b/tests/app/main/views/test_send.py @@ -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 'We’ve 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)