From eb11615a320df8d67865b274b36422452fe86b28 Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Thu, 1 Sep 2016 15:40:49 +0100 Subject: [PATCH] Add a cancel job button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If you schedule a job you might change your mind or circumstances might change. So you need to be able to cancel it. This commit adds a button on the job page which hits the `…/cancel` API endpoint for a job. --- app/main/views/jobs.py | 11 +++++++++- app/notify_client/job_api_client.py | 15 +++++++++++++ .../partials/jobs/notifications.html | 9 ++++++++ tests/app/main/views/test_jobs.py | 22 +++++++++++++++++++ tests/app/notify_client/test_job_client.py | 12 ++++++++++ 5 files changed, 68 insertions(+), 1 deletion(-) diff --git a/app/main/views/jobs.py b/app/main/views/jobs.py index dba2cd7fa..265d5149c 100644 --- a/app/main/views/jobs.py +++ b/app/main/views/jobs.py @@ -12,7 +12,8 @@ from flask import ( jsonify, request, url_for, - current_app + current_app, + redirect ) from flask_login import login_required from werkzeug.datastructures import MultiDict @@ -147,6 +148,14 @@ def view_job_csv(service_id, job_id): ) +@main.route("/services//jobs/", 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)) + + @main.route("/services//jobs/.json") @login_required @user_has_permissions('view_activity', admin_override=True) diff --git a/app/notify_client/job_api_client.py b/app/notify_client/job_api_client.py index 6bb862058..33638c294 100644 --- a/app/notify_client/job_api_client.py +++ b/app/notify_client/job_api_client.py @@ -77,3 +77,18 @@ class JobApiClient(BaseAPIClient): job['data']['notifications_requested'] = stats['requested'] return job + + def cancel_job(self, service_id, job_id): + + job = self.post( + url='/service/{}/job/{}/cancel'.format(service_id, job_id), + data={} + ) + + stats = self.__convert_statistics(job['data']) + job['data']['notifications_sent'] = stats['delivered'] + stats['failed'] + job['data']['notifications_delivered'] = stats['delivered'] + job['data']['notifications_failed'] = stats['failed'] + job['data']['notifications_requested'] = stats['requested'] + + return job diff --git a/app/templates/partials/jobs/notifications.html b/app/templates/partials/jobs/notifications.html index 12d0d47e0..266ad75ff 100644 --- a/app/templates/partials/jobs/notifications.html +++ b/app/templates/partials/jobs/notifications.html @@ -1,4 +1,5 @@ {% from "components/table.html" import list_table, field, right_aligned_field_heading, date_field, row_heading %} +{% from "components/page-footer.html" import page_footer %}
{% if job.job_status == 'scheduled' %} @@ -6,6 +7,14 @@

Sending will start at {{ job.scheduled_for|format_time }}

+ {% else %} diff --git a/tests/app/main/views/test_jobs.py b/tests/app/main/views/test_jobs.py index baeba675b..547046c4a 100644 --- a/tests/app/main/views/test_jobs.py +++ b/tests/app/main/views/test_jobs.py @@ -147,6 +147,28 @@ def test_should_show_scheduled_job( assert response.status_code == 200 page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser') assert page.find('main').find_all('p')[2].text.strip() == 'Sending will start at midnight' + assert page.find('input', {'type': 'submit', 'value': 'Cancel sending'}) + + +def test_should_cancel_job( + app_, + service_one, + active_user_with_permissions, + fake_uuid, + mocker +): + with app_.test_request_context(), app_.test_client() as client: + client.login(active_user_with_permissions, mocker, service_one) + mock_cancel = mocker.patch('app.main.jobs.job_api_client.cancel_job') + response = client.post(url_for( + 'main.cancel_job', + service_id=service_one['id'], + job_id=fake_uuid + )) + + mock_cancel.assert_called_once_with(service_one['id'], fake_uuid) + assert response.status_code == 302 + assert response.location == url_for('main.service_dashboard', service_id=service_one['id'], _external=True) def test_should_not_show_cancelled_job( diff --git a/tests/app/notify_client/test_job_client.py b/tests/app/notify_client/test_job_client.py index dd8306dd7..8c8f1380b 100644 --- a/tests/app/notify_client/test_job_client.py +++ b/tests/app/notify_client/test_job_client.py @@ -361,3 +361,15 @@ def test_client_parses_empty_job_stats_for_service(mocker): assert result['data'][1]['notifications_sent'] == 0 assert result['data'][1]['notification_count'] == 40 assert result['data'][1]['notifications_failed'] == 0 + + +def test_cancel_job(mocker): + + mock_post = mocker.patch('app.notify_client.job_api_client.JobApiClient.post') + + JobApiClient().cancel_job('service_id', 'job_id') + + mock_post.assert_called_once_with( + url='/service/{}/job/{}/cancel'.format('service_id', 'job_id'), + data={} + )