diff --git a/app/main/__init__.py b/app/main/__init__.py index 97aa9f228..29208e5cd 100644 --- a/app/main/__init__.py +++ b/app/main/__init__.py @@ -7,6 +7,7 @@ from app.main.views import ( # noqa isort:skip add_service, agreement, api_keys, + broadcast, choose_account, code_not_received, conversation, diff --git a/app/main/views/broadcast.py b/app/main/views/broadcast.py new file mode 100644 index 000000000..f5e90aeeb --- /dev/null +++ b/app/main/views/broadcast.py @@ -0,0 +1,106 @@ +from flask import redirect, render_template, request, session, url_for +from notifications_utils.broadcast_areas import broadcast_area_libraries +from orderedset import OrderedSet + +from app import current_service +from app.main import main +from app.utils import service_has_permission, user_has_permissions + + +@main.route('/services//broadcast') +@user_has_permissions('send_messages') +@service_has_permission('broadcast') +def broadcast(service_id): + if 'broadcast_areas' in session: + session.pop('broadcast_areas') + return redirect(url_for( + '.preview_broadcast_areas', + service_id=current_service.id, + )) + + +@main.route('/services//broadcast/areas') +@user_has_permissions('send_messages') +@service_has_permission('broadcast') +def preview_broadcast_areas(service_id): + selected_areas_ids = session.get('broadcast_areas', []) + return render_template( + 'views/broadcast/preview-areas.html', + selected=list(broadcast_area_libraries.get_areas( + *selected_areas_ids + )), + area_polygons=broadcast_area_libraries.get_polygons_for_areas_lat_long( + *selected_areas_ids + ) + ) + + +@main.route('/services//broadcast/libraries') +@user_has_permissions('send_messages') +@service_has_permission('broadcast') +def choose_broadcast_library(service_id): + return render_template( + 'views/broadcast/libraries.html', + libraries=broadcast_area_libraries, + selected=broadcast_area_libraries.get_areas( + *session.get('broadcast_areas', []) + ), + ) + + +@main.route('/services//broadcast/libraries/') +@user_has_permissions('send_messages') +@service_has_permission('broadcast') +def choose_broadcast_area(service_id, library_slug): + return render_template( + 'views/broadcast/areas.html', + areas=broadcast_area_libraries.get(library_slug), + ) + + +@main.route('/services//broadcast/add/') +@user_has_permissions('send_messages') +@service_has_permission('broadcast') +def add_broadcast_area(service_id, area_slug): + if not session.get('broadcast_areas'): + session['broadcast_areas'] = [] + + session['broadcast_areas'].append(area_slug) + + session['broadcast_areas'] = list(OrderedSet( + session['broadcast_areas'] + )) + return redirect(url_for( + '.preview_broadcast_areas', + service_id=current_service.id, + )) + + +@main.route('/services//broadcast/remove/') +@user_has_permissions('send_messages') +@service_has_permission('broadcast') +def remove_broadcast_area(service_id, area_slug): + session['broadcast_areas'] = list(filter( + lambda saved_area_id: saved_area_id != area_slug, + session.get('broadcast_areas', []), + )) + + return redirect(url_for( + '.preview_broadcast_areas', + service_id=current_service.id, + )) + + +@main.route('/services//broadcast/preview', methods=['GET', 'POST']) +@user_has_permissions('send_messages') +@service_has_permission('broadcast') +def preview_broadcast_message(service_id): + if request.method == 'POST': + return 'OK' + selected_areas = session.get('broadcast_areas', []) + return render_template( + 'views/broadcast/preview-message.html', + selected=list(broadcast_area_libraries.get_areas( + *selected_areas + )), + ) diff --git a/app/navigation.py b/app/navigation.py index 1aa043ef2..51717ef52 100644 --- a/app/navigation.py +++ b/app/navigation.py @@ -351,6 +351,13 @@ class HeaderNavigation(Navigation): 'old_guest_list', 'who_can_use_notify', 'who_its_for', + 'broadcast', + 'preview_broadcast_areas', + 'choose_broadcast_library', + 'choose_broadcast_area', + 'add_broadcast_area', + 'remove_broadcast_area', + 'preview_broadcast_message', } # header HTML now comes from GOVUK Frontend so requires a boolean, not an attribute @@ -399,6 +406,13 @@ class MainNavigation(Navigation): 'view_template', 'view_template_version', 'view_template_versions', + 'broadcast', + 'preview_broadcast_areas', + 'choose_broadcast_library', + 'choose_broadcast_area', + 'add_broadcast_area', + 'remove_broadcast_area', + 'preview_broadcast_message', }, 'uploads': { 'upload_contact_list', @@ -978,6 +992,13 @@ class CaseworkNavigation(Navigation): 'old_guest_list', 'who_can_use_notify', 'who_its_for', + 'broadcast', + 'preview_broadcast_areas', + 'choose_broadcast_library', + 'choose_broadcast_area', + 'add_broadcast_area', + 'remove_broadcast_area', + 'preview_broadcast_message', } @@ -1288,4 +1309,11 @@ class OrgNavigation(Navigation): 'old_guest_list', 'who_can_use_notify', 'who_its_for', + 'broadcast', + 'preview_broadcast_areas', + 'choose_broadcast_library', + 'choose_broadcast_area', + 'add_broadcast_area', + 'remove_broadcast_area', + 'preview_broadcast_message', } diff --git a/app/templates/views/broadcast/areas.html b/app/templates/views/broadcast/areas.html new file mode 100644 index 000000000..55db5a0e2 --- /dev/null +++ b/app/templates/views/broadcast/areas.html @@ -0,0 +1,20 @@ +{% from "components/page-header.html" import page_header %} + +{% extends "withnav_template.html" %} + +{% block service_page_title %} + {{ page_title }} +{% endblock %} + +{% block maincolumn_content %} + + {{ page_header( + regions.name, + back_link=url_for('.choose_broadcast_library', service_id=current_service.id), + )}} + + {% for area in areas %} +

{{ region.name }}

+ {% endfor %} + +{% endblock %} diff --git a/app/templates/views/broadcast/libraries.html b/app/templates/views/broadcast/libraries.html new file mode 100644 index 000000000..e029febd8 --- /dev/null +++ b/app/templates/views/broadcast/libraries.html @@ -0,0 +1,21 @@ +{% from "components/page-header.html" import page_header %} + +{% extends "withnav_template.html" %} + +{% block service_page_title %} + Choose where to broadcast to +{% endblock %} + +{% block maincolumn_content %} + + {{ page_header( + "Choose where to broadcast to", + back_link=url_for(".preview_broadcast_areas", service_id=current_service.id) + ) }} + + {% for library in libraries|sort %} + {{ library.name }} +

{{ library.examples }}

+ {% endfor %} + +{% endblock %} diff --git a/app/templates/views/broadcast/preview-areas.html b/app/templates/views/broadcast/preview-areas.html new file mode 100644 index 000000000..6b86fc3ba --- /dev/null +++ b/app/templates/views/broadcast/preview-areas.html @@ -0,0 +1,47 @@ +{% from "components/button/macro.njk" import govukButton %} +{% from "components/page-header.html" import page_header %} +{% from "components/page-footer.html" import sticky_page_footer %} + +{% extends "withnav_template.html" %} + +{% block service_page_title %} + Choose where to broadcast to +{% endblock %} + +{% block maincolumn_content %} + + {{ page_header("Choose where to broadcast to", back_link="#") }} + + {% for area in selected %} + {{ area.name }} (remove)  + {% if loop.last %} +

+ {{ govukButton({ + "element": "a", + "text": "Add another area", + "href": url_for('.choose_broadcast_library', service_id=current_service.id), + "classes": "govuk-button--secondary govuk-!-margin-top-2" + }) }} +

+ {% endif %} + {% else %} +

+ {{ govukButton({ + "element": "a", + "text": "Add broadcast areas", + "href": url_for('.choose_broadcast_library', service_id=current_service.id), + "classes": "govuk-button--secondary" + }) }} +

+ {% endfor %} + + {% if selected %} + {% for area in area_polygons %} + {{ area }}

+ {% endfor %} +
+ {{ sticky_page_footer('Continue to preview') }} +
+ {% endif %} + +{% endblock %} diff --git a/app/templates/views/broadcast/preview-message.html b/app/templates/views/broadcast/preview-message.html new file mode 100644 index 000000000..7a7ecf719 --- /dev/null +++ b/app/templates/views/broadcast/preview-message.html @@ -0,0 +1,41 @@ +{% from "components/button/macro.njk" import govukButton %} +{% from "components/form.html" import form_wrapper %} +{% from "components/page-header.html" import page_header %} +{% from "components/page-footer.html" import sticky_page_footer %} + +{% extends "withnav_template.html" %} + +{% block service_page_title %} + Preview message +{% endblock %} + +{% block maincolumn_content %} + + {{ page_header("Preview message", back_link=url_for('.preview_broadcast_areas', service_id=current_service.id)) }} + + {% for region in selected %} + {% if loop.first %} +
    + {% endif %} +
  • + {{ region.name }} +
  • + {% if loop.last %} +
+ {% endif %} + {% endfor %} + + {% if selected %} + +

+ {{ "" }} +

+ + {% call form_wrapper() %} + {{ sticky_page_footer('Start broadcast') }} + {% endcall %} + + {% endif %} + + +{% endblock %} diff --git a/app/templates/views/templates/_template.html b/app/templates/views/templates/_template.html index e5913d5d6..8f3bbf321 100644 --- a/app/templates/views/templates/_template.html +++ b/app/templates/views/templates/_template.html @@ -29,8 +29,15 @@ {% endif %} {% elif template.template_type == 'broadcast' %} + {% if current_user.has_permissions('send_messages') %} + + {% endif %} {% if current_user.has_permissions('manage_templates') %} -
+
Edit diff --git a/requirements-app.txt b/requirements-app.txt index 5a2afd14c..b6514ae12 100644 --- a/requirements-app.txt +++ b/requirements-app.txt @@ -23,7 +23,7 @@ notifications-python-client==5.6.0 awscli-cwlogs>=1.4,<1.5 itsdangerous==1.1.0 -git+https://github.com/alphagov/notifications-utils.git@40.2.1#egg=notifications-utils==40.2.1 +git+https://github.com/alphagov/notifications-utils.git@40.3.1#egg=notifications-utils==40.3.1 git+https://github.com/alphagov/govuk-frontend-jinja.git@v0.5.1-alpha#egg=govuk-frontend-jinja==0.5.1-alpha # gds-metrics requires prometheseus 0.2.0, override that requirement as later versions bring significant performance gains diff --git a/requirements.txt b/requirements.txt index 037366b83..29cf99318 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,7 +25,7 @@ notifications-python-client==5.6.0 awscli-cwlogs>=1.4,<1.5 itsdangerous==1.1.0 -git+https://github.com/alphagov/notifications-utils.git@40.2.1#egg=notifications-utils==40.2.1 +git+https://github.com/alphagov/notifications-utils.git@40.3.1#egg=notifications-utils==40.3.1 git+https://github.com/alphagov/govuk-frontend-jinja.git@v0.5.1-alpha#egg=govuk-frontend-jinja==0.5.1-alpha # gds-metrics requires prometheseus 0.2.0, override that requirement as later versions bring significant performance gains @@ -33,10 +33,10 @@ prometheus-client==0.8.0 gds-metrics==0.2.0 ## The following requirements were added by pip freeze: -awscli==1.18.93 +awscli==1.18.95 bleach==3.1.4 boto3==1.10.38 -botocore==1.17.16 +botocore==1.17.18 cachetools==4.1.0 certifi==2020.6.20 chardet==3.0.4 @@ -48,6 +48,7 @@ docutils==0.15.2 et-xmlfile==1.0.1 flask-redis==0.4.0 future==0.18.2 +geojson==2.5.0 greenlet==0.4.16 idna==2.10 jdcal==1.4.1 diff --git a/tests/app/main/views/test_broadcast.py b/tests/app/main/views/test_broadcast.py new file mode 100644 index 000000000..378054a51 --- /dev/null +++ b/tests/app/main/views/test_broadcast.py @@ -0,0 +1,121 @@ +import pytest +from flask import url_for + +from tests.conftest import SERVICE_ONE_ID + + +@pytest.mark.parametrize('endpoint, extra_args', ( + ('.broadcast', {}), + ('.preview_broadcast_areas', {}), + ('.choose_broadcast_library', {}), + ('.choose_broadcast_area', {'library_slug': 'countries'}), + ('.add_broadcast_area', {'area_slug': 'england'}), + ('.remove_broadcast_area', {'area_slug': 'england'}), + ('.preview_broadcast_message', {}), +)) +def test_broadcast_pages_403_without_permission( + client_request, + endpoint, + extra_args, +): + client_request.get( + endpoint, + service_id=SERVICE_ONE_ID, + _expected_status=403, + **extra_args + ) + + +def test_broadcast_page( + client_request, + service_one, +): + service_one['permissions'] += ['broadcast'] + client_request.get( + '.broadcast', + service_id=SERVICE_ONE_ID, + _expected_redirect=url_for( + '.preview_broadcast_areas', + service_id=SERVICE_ONE_ID, + _external=True, + ), + ), + + +def test_preview_broadcast_areas_page( + client_request, + service_one, +): + service_one['permissions'] += ['broadcast'] + client_request.get( + '.preview_broadcast_areas', + service_id=SERVICE_ONE_ID, + ) + + +def test_choose_broadcast_library_page( + client_request, + service_one, +): + service_one['permissions'] += ['broadcast'] + client_request.get( + '.choose_broadcast_library', + service_id=SERVICE_ONE_ID, + ) + + +def test_choose_broadcast_area_page( + client_request, + service_one, +): + service_one['permissions'] += ['broadcast'] + client_request.get( + '.choose_broadcast_area', + service_id=SERVICE_ONE_ID, + library_slug='countries', + ) + + +def test_add_broadcast_area_page( + client_request, + service_one, +): + service_one['permissions'] += ['broadcast'] + client_request.get( + '.add_broadcast_area', + service_id=SERVICE_ONE_ID, + area_slug='england', + _expected_redirect=url_for( + '.preview_broadcast_areas', + service_id=SERVICE_ONE_ID, + _external=True, + ), + ), + + +def test_remove_broadcast_area_page( + client_request, + service_one, +): + service_one['permissions'] += ['broadcast'] + client_request.get( + '.remove_broadcast_area', + service_id=SERVICE_ONE_ID, + area_slug='england', + _expected_redirect=url_for( + '.preview_broadcast_areas', + service_id=SERVICE_ONE_ID, + _external=True, + ), + ), + + +def test_preview_broadcast_message_page( + client_request, + service_one, +): + service_one['permissions'] += ['broadcast'] + client_request.get( + '.preview_broadcast_message', + service_id=SERVICE_ONE_ID, + ), diff --git a/tests/app/main/views/test_templates.py b/tests/app/main/views/test_templates.py index bc07a0c49..d44a2794f 100644 --- a/tests/app/main/views/test_templates.py +++ b/tests/app/main/views/test_templates.py @@ -693,6 +693,10 @@ def test_view_broadcast_template( (link.text.strip(), link['href']) for link in page.select('.pill-separate-item') ] == [ + ('Prepare broadcast', url_for( + '.broadcast', + service_id=SERVICE_ONE_ID, + )), ('Edit', url_for( '.edit_service_template', service_id=SERVICE_ONE_ID,