Merge pull request #922 from alphagov/cancelled-jobs

Let users cancel a job
This commit is contained in:
Chris Hill-Scott
2016-09-06 17:12:19 +01:00
committed by GitHub
7 changed files with 109 additions and 4 deletions

View File

@@ -120,7 +120,7 @@ def get_dashboard_partials(service_id):
job for job in jobs if job['job_status'] == 'scheduled'
], key=lambda job: job['scheduled_for'])
immediate_jobs = [
job for job in jobs if job['job_status'] != 'scheduled'
job for job in jobs if job['job_status'] not in ['scheduled', 'cancelled']
]
service = service_api_client.get_detailed_service(service_id)

View File

@@ -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
@@ -67,7 +68,7 @@ def view_jobs(service_id):
'views/jobs/jobs.html',
jobs=add_rate_to_jobs([
job for job in job_api_client.get_job(service_id)['data']
if job['job_status'] != 'scheduled'
if job['job_status'] not in ['scheduled', 'cancelled']
])
)
@@ -77,6 +78,10 @@ def view_jobs(service_id):
@user_has_permissions('view_activity', admin_override=True)
def view_job(service_id, job_id):
job = job_api_client.get_job(service_id, job_id)['data']
if job['job_status'] == 'cancelled':
abort(404)
filter_args = _parse_filter_args(request.args)
filter_args['status'] = _set_status_filters(filter_args)
@@ -142,6 +147,14 @@ def view_job_csv(service_id, job_id):
)
@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))
@main.route("/services/<service_id>/jobs/<job_id>.json")
@login_required
@user_has_permissions('view_activity', admin_override=True)

View File

@@ -78,3 +78,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

View File

@@ -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 %}
<div class="ajax-block-container">
{% if job.job_status == 'scheduled' %}
@@ -6,6 +7,14 @@
<p>
Sending will start at {{ job.scheduled_for|format_time }}
</p>
<div class="page-footer">
<form method="post">
{{ page_footer(
button_text="Cancel sending",
destructive=True
) }}
</form>
</div>
{% else %}

View File

@@ -147,6 +147,47 @@ 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(
app_,
service_one,
active_user_with_permissions,
mock_get_cancelled_job,
mocker,
fake_uuid
):
with app_.test_request_context(), app_.test_client() as client:
client.login(active_user_with_permissions, mocker, service_one)
response = client.get(url_for(
'main.view_job',
service_id=service_one['id'],
job_id=fake_uuid
))
assert response.status_code == 404
def test_should_show_not_show_csv_download_in_tour(

View File

@@ -322,3 +322,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={}
)

View File

@@ -872,6 +872,20 @@ def mock_get_scheduled_job(mocker, api_user_active):
return mocker.patch('app.job_api_client.get_job', side_effect=_get_job)
@pytest.fixture(scope='function')
def mock_get_cancelled_job(mocker, api_user_active):
def _get_job(service_id, job_id):
return {"data": job_json(
service_id,
api_user_active,
job_id=job_id,
job_status='cancelled',
scheduled_for='2016-01-01T00:00:00.061258'
)}
return mocker.patch('app.job_api_client.get_job', side_effect=_get_job)
@pytest.fixture(scope='function')
def mock_get_job_in_progress(mocker, api_user_active):
def _get_job(service_id, job_id):
@@ -903,7 +917,8 @@ def mock_get_jobs(mocker, api_user_active):
("applicants.ods", '', ''),
("thisisatest.csv", '', ''),
("send_me_later.csv", '2016-01-01 11:09:00.061258', 'scheduled'),
("even_later.csv", '2016-01-01 23:09:00.061258', 'scheduled')
("even_later.csv", '2016-01-01 23:09:00.061258', 'scheduled'),
("full_of_regret.csv", '2016-01-01 23:09:00.061258', 'cancelled')
)
]}