mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-06-21 21:53:42 -04:00
Merge pull request #3881 from alphagov/confirm-live-broadcast
Add a confirmation checkbox for live broadcasts
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import math
|
||||
import weakref
|
||||
from datetime import datetime, timedelta
|
||||
from itertools import chain
|
||||
@@ -1288,6 +1289,52 @@ class NewBroadcastForm(StripWhitespaceForm):
|
||||
return self.content.data == 'template'
|
||||
|
||||
|
||||
class ConfirmBroadcastForm(StripWhitespaceForm):
|
||||
|
||||
def __init__(self, *args, service_is_live, channel, max_phones, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.confirm.label.text = self.generate_label(channel, max_phones)
|
||||
|
||||
if service_is_live:
|
||||
self.confirm.validators += (
|
||||
DataRequired('You need to confirm that you understand'),
|
||||
)
|
||||
|
||||
confirm = GovukCheckboxField("Confirm")
|
||||
|
||||
@staticmethod
|
||||
def generate_label(channel, max_phones):
|
||||
if channel == 'test':
|
||||
return (
|
||||
'I understand this will alert anyone who has switched '
|
||||
'on the test channel'
|
||||
)
|
||||
if channel == 'severe':
|
||||
return (
|
||||
f'I understand this will alert {ConfirmBroadcastForm.format_number_generic(max_phones)} '
|
||||
'of people'
|
||||
)
|
||||
if channel == 'government':
|
||||
return (
|
||||
f'I understand this will alert {ConfirmBroadcastForm.format_number_generic(max_phones)} '
|
||||
'of people, even if they’ve opted out'
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def format_number_generic(count):
|
||||
for threshold, message in (
|
||||
(1_000_000, 'millions'),
|
||||
(100_000, 'hundreds of thousands'),
|
||||
(10_000, 'tens of thousands'),
|
||||
(1_000, 'thousands'),
|
||||
(100, 'hundreds'),
|
||||
(-math.inf, 'an unknown number')
|
||||
):
|
||||
if count >= threshold:
|
||||
return message
|
||||
|
||||
|
||||
class BaseTemplateForm(StripWhitespaceForm):
|
||||
name = GovukTextInputField(
|
||||
"Template name",
|
||||
|
||||
@@ -14,6 +14,7 @@ from app.main.forms import (
|
||||
BroadcastAreaForm,
|
||||
BroadcastAreaFormWithSelectAll,
|
||||
BroadcastTemplateForm,
|
||||
ConfirmBroadcastForm,
|
||||
NewBroadcastForm,
|
||||
SearchByNameForm,
|
||||
)
|
||||
@@ -21,6 +22,15 @@ from app.models.broadcast_message import BroadcastMessage, BroadcastMessages
|
||||
from app.utils import service_has_permission, user_has_permissions
|
||||
|
||||
|
||||
def _get_back_link_from_view_broadcast_endpoint():
|
||||
return {
|
||||
'main.view_current_broadcast': '.broadcast_dashboard',
|
||||
'main.view_previous_broadcast': '.broadcast_dashboard_previous',
|
||||
'main.view_rejected_broadcast': '.broadcast_dashboard_rejected',
|
||||
'main.approve_broadcast_message': '.broadcast_dashboard',
|
||||
}[request.endpoint]
|
||||
|
||||
|
||||
@main.route('/services/<uuid:service_id>/broadcast-tour/<int:step_index>')
|
||||
@user_has_permissions()
|
||||
@service_has_permission('broadcast')
|
||||
@@ -388,19 +398,18 @@ def view_broadcast(service_id, broadcast_message_id):
|
||||
broadcast_message_id=broadcast_message.id,
|
||||
))
|
||||
|
||||
back_link_endpoint = {
|
||||
'main.view_current_broadcast': '.broadcast_dashboard',
|
||||
'main.view_previous_broadcast': '.broadcast_dashboard_previous',
|
||||
'main.view_rejected_broadcast': '.broadcast_dashboard_rejected',
|
||||
}[request.endpoint]
|
||||
|
||||
return render_template(
|
||||
'views/broadcast/view-message.html',
|
||||
broadcast_message=broadcast_message,
|
||||
back_link=url_for(
|
||||
back_link_endpoint,
|
||||
_get_back_link_from_view_broadcast_endpoint(),
|
||||
service_id=current_service.id,
|
||||
),
|
||||
form=ConfirmBroadcastForm(
|
||||
service_is_live=current_service.live,
|
||||
channel=current_service.broadcast_channel,
|
||||
max_phones=broadcast_message.count_of_phones_likely,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -414,6 +423,12 @@ def approve_broadcast_message(service_id, broadcast_message_id):
|
||||
service_id=current_service.id,
|
||||
)
|
||||
|
||||
form = ConfirmBroadcastForm(
|
||||
service_is_live=current_service.live,
|
||||
channel=current_service.broadcast_channel,
|
||||
max_phones=broadcast_message.count_of_phones_likely,
|
||||
)
|
||||
|
||||
if broadcast_message.status != 'pending-approval':
|
||||
return redirect(url_for(
|
||||
'.view_current_broadcast',
|
||||
@@ -421,14 +436,25 @@ def approve_broadcast_message(service_id, broadcast_message_id):
|
||||
broadcast_message_id=broadcast_message.id,
|
||||
))
|
||||
|
||||
broadcast_message.approve_broadcast()
|
||||
|
||||
if current_service.trial_mode:
|
||||
broadcast_message.approve_broadcast()
|
||||
return redirect(url_for(
|
||||
'.broadcast_tour',
|
||||
service_id=current_service.id,
|
||||
step_index=6,
|
||||
))
|
||||
elif form.validate_on_submit():
|
||||
broadcast_message.approve_broadcast()
|
||||
else:
|
||||
return render_template(
|
||||
'views/broadcast/view-message.html',
|
||||
broadcast_message=broadcast_message,
|
||||
back_link=url_for(
|
||||
_get_back_link_from_view_broadcast_endpoint(),
|
||||
service_id=current_service.id,
|
||||
),
|
||||
form=form,
|
||||
)
|
||||
|
||||
return redirect(url_for(
|
||||
'.view_current_broadcast',
|
||||
|
||||
@@ -92,6 +92,17 @@
|
||||
wants to broadcast
|
||||
{{ broadcast_message.template.name }}
|
||||
</h1>
|
||||
{% if current_service.live %}
|
||||
{{ form.confirm(param_extensions={
|
||||
'formGroup': {
|
||||
'classes': 'govuk-!-margin-bottom-4'
|
||||
}
|
||||
}) }}
|
||||
{% else %}
|
||||
<p class="govuk-body govuk-!-margin-bottom-3">
|
||||
No phones will get this alert.
|
||||
</p>
|
||||
{% endif %}
|
||||
{{ page_footer(
|
||||
"Start broadcasting now",
|
||||
delete_link=url_for('main.reject_broadcast_message', service_id=current_service.id, broadcast_message_id=broadcast_message.id),
|
||||
|
||||
@@ -1710,8 +1710,10 @@ def test_view_pending_broadcast(
|
||||
normalize_spaces(page.select_one('.banner').text)
|
||||
) == (
|
||||
'Test User wants to broadcast Example template '
|
||||
'No phones will get this alert. '
|
||||
'Start broadcasting now Reject this alert'
|
||||
)
|
||||
assert not page.select('.banner input[type=checkbox]')
|
||||
|
||||
form = page.select_one('form.banner')
|
||||
assert form['method'] == 'post'
|
||||
@@ -1764,6 +1766,7 @@ def test_view_pending_broadcast_without_template(
|
||||
normalize_spaces(page.select_one('.banner').text)
|
||||
) == (
|
||||
'Test User wants to broadcast No template test '
|
||||
'No phones will get this alert. '
|
||||
'Start broadcasting now Reject this alert'
|
||||
)
|
||||
assert (
|
||||
@@ -1807,6 +1810,7 @@ def test_view_pending_broadcast_from_api_call(
|
||||
normalize_spaces(page.select_one('.banner').text)
|
||||
) == (
|
||||
'An API call wants to broadcast abc123 '
|
||||
'No phones will get this alert. '
|
||||
'Start broadcasting now Reject this alert'
|
||||
)
|
||||
assert (
|
||||
@@ -1817,6 +1821,101 @@ def test_view_pending_broadcast_from_api_call(
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('channel, expected_label_text', (
|
||||
('test', (
|
||||
'I understand this will alert anyone who has switched on the test channel'
|
||||
)),
|
||||
('severe', (
|
||||
'I understand this will alert millions of people'
|
||||
)),
|
||||
('government', (
|
||||
'I understand this will alert millions of people, even if they’ve opted out'
|
||||
)),
|
||||
))
|
||||
@freeze_time('2020-02-22T22:22:22.000000')
|
||||
def test_checkbox_to_confirm_non_training_broadcasts(
|
||||
mocker,
|
||||
client_request,
|
||||
service_one,
|
||||
active_user_with_permissions,
|
||||
fake_uuid,
|
||||
channel,
|
||||
expected_label_text,
|
||||
):
|
||||
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=None,
|
||||
created_by_id=None,
|
||||
status='pending-approval',
|
||||
),
|
||||
)
|
||||
service_one['permissions'] += ['broadcast']
|
||||
service_one['restricted'] = False
|
||||
service_one['allowed_broadcast_provider'] = 'all'
|
||||
service_one['broadcast_channel'] = channel
|
||||
|
||||
page = client_request.get(
|
||||
'.view_current_broadcast',
|
||||
service_id=SERVICE_ONE_ID,
|
||||
broadcast_message_id=fake_uuid,
|
||||
)
|
||||
|
||||
label = page.select_one('form.banner label')
|
||||
|
||||
assert label['for'] == 'confirm'
|
||||
assert (
|
||||
normalize_spaces(page.select_one('form.banner label').text)
|
||||
) == expected_label_text
|
||||
assert page.select_one('form.banner input[type=checkbox]')['name'] == 'confirm'
|
||||
assert page.select_one('form.banner input[type=checkbox]')['value'] == 'y'
|
||||
|
||||
|
||||
@freeze_time('2020-02-22T22:22:22.000000')
|
||||
def test_confirm_approve_non_training_broadcasts_errors_if_not_ticked(
|
||||
mocker,
|
||||
client_request,
|
||||
service_one,
|
||||
active_user_with_permissions,
|
||||
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=None,
|
||||
created_by_id=None,
|
||||
status='pending-approval',
|
||||
),
|
||||
)
|
||||
service_one['permissions'] += ['broadcast']
|
||||
service_one['restricted'] = False
|
||||
service_one['allowed_broadcast_provider'] = 'all'
|
||||
service_one['broadcast_channel'] = 'severe'
|
||||
|
||||
page = client_request.post(
|
||||
'.view_current_broadcast',
|
||||
service_id=SERVICE_ONE_ID,
|
||||
broadcast_message_id=fake_uuid,
|
||||
_data={},
|
||||
_expected_status=200,
|
||||
)
|
||||
|
||||
error_message = page.select_one('form.banner .govuk-error-message')
|
||||
assert error_message['id'] == 'confirm-error'
|
||||
assert normalize_spaces(error_message.text) == (
|
||||
'Error: You need to confirm that you understand'
|
||||
)
|
||||
|
||||
assert mock_update_broadcast_message.called is False
|
||||
assert mock_update_broadcast_message_status.called is False
|
||||
|
||||
|
||||
@freeze_time('2020-02-22T22:22:22.000000')
|
||||
def test_cant_approve_own_broadcast(
|
||||
mocker,
|
||||
@@ -1997,38 +2096,41 @@ def test_view_only_user_cant_approve_broadcast(
|
||||
assert not page.select_one('.banner a')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('trial_mode, initial_status, expected_approval, expected_redirect', (
|
||||
(True, 'draft', False, partial(
|
||||
url_for,
|
||||
'.view_current_broadcast',
|
||||
broadcast_message_id=sample_uuid,
|
||||
)),
|
||||
(True, 'pending-approval', True, partial(
|
||||
url_for,
|
||||
'.broadcast_tour',
|
||||
step_index=6,
|
||||
)),
|
||||
(False, 'pending-approval', True, partial(
|
||||
url_for,
|
||||
'.view_current_broadcast',
|
||||
broadcast_message_id=sample_uuid,
|
||||
)),
|
||||
(True, 'rejected', False, partial(
|
||||
url_for,
|
||||
'.view_current_broadcast',
|
||||
broadcast_message_id=sample_uuid,
|
||||
)),
|
||||
(True, 'broadcasting', False, partial(
|
||||
url_for,
|
||||
'.view_current_broadcast',
|
||||
broadcast_message_id=sample_uuid,
|
||||
)),
|
||||
(True, 'cancelled', False, partial(
|
||||
url_for,
|
||||
'.view_current_broadcast',
|
||||
broadcast_message_id=sample_uuid,
|
||||
)),
|
||||
))
|
||||
@pytest.mark.parametrize(
|
||||
'trial_mode, initial_status, post_data, expected_approval, expected_redirect',
|
||||
(
|
||||
(True, 'draft', {}, False, partial(
|
||||
url_for,
|
||||
'.view_current_broadcast',
|
||||
broadcast_message_id=sample_uuid,
|
||||
)),
|
||||
(True, 'pending-approval', {}, True, partial(
|
||||
url_for,
|
||||
'.broadcast_tour',
|
||||
step_index=6,
|
||||
)),
|
||||
(False, 'pending-approval', {'confirm': 'y'}, True, partial(
|
||||
url_for,
|
||||
'.view_current_broadcast',
|
||||
broadcast_message_id=sample_uuid,
|
||||
)),
|
||||
(True, 'rejected', {}, False, partial(
|
||||
url_for,
|
||||
'.view_current_broadcast',
|
||||
broadcast_message_id=sample_uuid,
|
||||
)),
|
||||
(True, 'broadcasting', {}, False, partial(
|
||||
url_for,
|
||||
'.view_current_broadcast',
|
||||
broadcast_message_id=sample_uuid,
|
||||
)),
|
||||
(True, 'cancelled', {}, False, partial(
|
||||
url_for,
|
||||
'.view_current_broadcast',
|
||||
broadcast_message_id=sample_uuid,
|
||||
)),
|
||||
)
|
||||
)
|
||||
@freeze_time('2020-02-22T22:22:22.000000')
|
||||
def test_request_approval(
|
||||
mocker,
|
||||
@@ -2039,6 +2141,7 @@ def test_request_approval(
|
||||
mock_update_broadcast_message,
|
||||
mock_update_broadcast_message_status,
|
||||
initial_status,
|
||||
post_data,
|
||||
expected_approval,
|
||||
trial_mode,
|
||||
expected_redirect,
|
||||
@@ -2064,7 +2167,8 @@ def test_request_approval(
|
||||
_expected_redirect=expected_redirect(
|
||||
service_id=SERVICE_ONE_ID,
|
||||
_external=True,
|
||||
)
|
||||
),
|
||||
_data=post_data,
|
||||
)
|
||||
|
||||
if expected_approval:
|
||||
|
||||
Reference in New Issue
Block a user