From 03b4aabf5f2a88beeb6446d0c84754dfefb42002 Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Fri, 17 Jul 2020 08:07:44 +0100 Subject: [PATCH] Add a link to reject a broadcast MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If a broadcast definitely shouldn’t go out (for example because it has a spelling mistake or is going to the wrong areas) then we should have a way of removing it. Once it’s removed no-one else can approve it, and it isn’t cluttering up the dashboard. This is a link (because it’s a secondary action) and red (because it’s destructive, in that it’s throwing away someone’s work). --- .../stylesheets/components/page-footer.scss | 1 + app/main/views/broadcast.py | 25 +++++ app/models/broadcast_message.py | 3 + app/navigation.py | 4 + .../views/broadcast/view-message.html | 4 +- tests/app/main/views/test_broadcast.py | 99 ++++++++++++++++++- 6 files changed, 134 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/components/page-footer.scss b/app/assets/stylesheets/components/page-footer.scss index 19209a24c..61b04299b 100644 --- a/app/assets/stylesheets/components/page-footer.scss +++ b/app/assets/stylesheets/components/page-footer.scss @@ -7,6 +7,7 @@ line-height: 40px; padding: 1px 0 0 15px; + font-weight: normal; } diff --git a/app/main/views/broadcast.py b/app/main/views/broadcast.py index f45414200..7dda048e8 100644 --- a/app/main/views/broadcast.py +++ b/app/main/views/broadcast.py @@ -195,6 +195,31 @@ def approve_broadcast_message(service_id, broadcast_message_id): )) +@main.route('/services//broadcast//reject') +@user_has_permissions('send_messages') +@service_has_permission('broadcast') +def reject_broadcast_message(service_id, broadcast_message_id): + + broadcast_message = BroadcastMessage.from_id( + broadcast_message_id, + service_id=current_service.id, + ) + + if broadcast_message.status != 'pending-approval': + return redirect(url_for( + '.view_broadcast_message', + service_id=current_service.id, + broadcast_message_id=broadcast_message.id, + )) + + broadcast_message.reject_broadcast() + + return redirect(url_for( + '.broadcast_dashboard', + service_id=current_service.id, + )) + + @main.route('/services//broadcast//cancel') @user_has_permissions('send_messages') @service_has_permission('broadcast') diff --git a/app/models/broadcast_message.py b/app/models/broadcast_message.py index bf6772979..8e7deb224 100644 --- a/app/models/broadcast_message.py +++ b/app/models/broadcast_message.py @@ -144,6 +144,9 @@ class BroadcastMessage(JSONModel): ) self._set_status_to('broadcasting') + def reject_broadcast(self): + self._set_status_to('rejected') + def cancel_broadcast(self): self._set_status_to('cancelled') diff --git a/app/navigation.py b/app/navigation.py index 7d1af6f4b..34d1639ca 100644 --- a/app/navigation.py +++ b/app/navigation.py @@ -362,6 +362,7 @@ class HeaderNavigation(Navigation): 'preview_broadcast_message', 'view_broadcast_message', 'approve_broadcast_message', + 'reject_broadcast_message', 'cancel_broadcast_message', } @@ -421,6 +422,7 @@ class MainNavigation(Navigation): 'preview_broadcast_message', 'view_broadcast_message', 'approve_broadcast_message', + 'reject_broadcast_message', 'cancel_broadcast_message', }, 'uploads': { @@ -1015,6 +1017,7 @@ class CaseworkNavigation(Navigation): 'preview_broadcast_message', 'view_broadcast_message', 'approve_broadcast_message', + 'reject_broadcast_message', 'cancel_broadcast_message', } @@ -1337,5 +1340,6 @@ class OrgNavigation(Navigation): 'preview_broadcast_message', 'view_broadcast_message', 'approve_broadcast_message', + 'reject_broadcast_message', 'cancel_broadcast_message', } diff --git a/app/templates/views/broadcast/view-message.html b/app/templates/views/broadcast/view-message.html index d38584460..9258592ef 100644 --- a/app/templates/views/broadcast/view-message.html +++ b/app/templates/views/broadcast/view-message.html @@ -23,7 +23,9 @@ message until {{ broadcast_message.finishes_at|format_datetime_relative }}.

{{ page_footer( - "Start broadcasting now" + "Start broadcasting now", + delete_link=url_for('main.reject_broadcast_message', service_id=current_service.id, broadcast_message_id=broadcast_message.id), + delete_link_text='Reject this broadcast' ) }} {% endcall %} {% else %} diff --git a/tests/app/main/views/test_broadcast.py b/tests/app/main/views/test_broadcast.py index ec8826be9..edad43dd8 100644 --- a/tests/app/main/views/test_broadcast.py +++ b/tests/app/main/views/test_broadcast.py @@ -431,7 +431,7 @@ def test_view_pending_broadcast( normalize_spaces(page.select_one('.banner').text) ) == ( 'Test User wants to broadcast this message until tomorrow at 11:23pm. ' - 'Start broadcasting now' + 'Start broadcasting now Reject this broadcast' ) form = page.select_one('form.banner') @@ -439,6 +439,14 @@ def test_view_pending_broadcast( assert 'action' not in form assert form.select_one('button[type=submit]') + link = form.select_one('a.govuk-link.govuk-link--destructive') + assert link.text == 'Reject this broadcast' + assert link['href'] == url_for( + '.reject_broadcast_message', + service_id=SERVICE_ONE_ID, + broadcast_message_id=fake_uuid, + ) + @pytest.mark.parametrize('initial_status, expected_approval', ( ('draft', False,), @@ -502,6 +510,95 @@ def test_approve_broadcast( assert mock_update_broadcast_message_status.called is False +@freeze_time('2020-02-22T22:22:22.000000') +def test_reject_broadcast( + mocker, + client_request, + service_one, + mock_get_broadcast_template, + fake_uuid, + mock_update_broadcast_message, + mock_update_broadcast_message_status, +): + mocker.patch( + 'app.broadcast_message_api_client.get_broadcast_message', + return_value=broadcast_message_json( + id_=fake_uuid, + service_id=SERVICE_ONE_ID, + template_id=fake_uuid, + created_by_id=fake_uuid, + finishes_at='2020-02-23T23:23:23.000000', + status='pending-approval', + ), + ) + service_one['permissions'] += ['broadcast'] + + client_request.get( + '.reject_broadcast_message', + service_id=SERVICE_ONE_ID, + broadcast_message_id=fake_uuid, + _expected_redirect=url_for( + '.broadcast_dashboard', + service_id=SERVICE_ONE_ID, + _external=True, + ) + ) + + assert mock_update_broadcast_message.called is False + + mock_update_broadcast_message_status.assert_called_once_with( + 'rejected', + service_id=SERVICE_ONE_ID, + broadcast_message_id=fake_uuid, + ) + + +@pytest.mark.parametrize('initial_status', ( + 'draft', + 'rejected', + 'broadcasting', + 'cancelled', +)) +@freeze_time('2020-02-22T22:22:22.000000') +def test_cant_reject_broadcast_in_wrong_state( + mocker, + client_request, + service_one, + mock_get_broadcast_template, + fake_uuid, + mock_update_broadcast_message, + mock_update_broadcast_message_status, + initial_status, +): + mocker.patch( + 'app.broadcast_message_api_client.get_broadcast_message', + return_value=broadcast_message_json( + id_=fake_uuid, + service_id=SERVICE_ONE_ID, + template_id=fake_uuid, + created_by_id=fake_uuid, + finishes_at='2020-02-23T23:23:23.000000', + status=initial_status, + ), + ) + service_one['permissions'] += ['broadcast'] + + client_request.get( + '.reject_broadcast_message', + service_id=SERVICE_ONE_ID, + broadcast_message_id=fake_uuid, + _expected_redirect=url_for( + '.view_broadcast_message', + service_id=SERVICE_ONE_ID, + broadcast_message_id=fake_uuid, + _external=True, + ) + ) + + assert mock_update_broadcast_message.called is False + assert mock_update_broadcast_message_status.called is False + + def test_no_view_page_for_draft( client_request, service_one,