diff --git a/app/main/views/broadcast.py b/app/main/views/broadcast.py index 8cf079752..7074890d9 100644 --- a/app/main/views/broadcast.py +++ b/app/main/views/broadcast.py @@ -1,4 +1,4 @@ -from flask import redirect, render_template, request, url_for +from flask import abort, redirect, render_template, request, url_for from app import current_service from app.main import main @@ -19,7 +19,7 @@ def broadcast_dashboard(service_id): ) -@main.route('/services//broadcast/') +@main.route('/services//new-broadcast/') @user_has_permissions('send_messages') @service_has_permission('broadcast') def broadcast(service_id, template_id): @@ -127,6 +127,22 @@ def preview_broadcast_message(service_id, broadcast_message_id): ) +@main.route('/services//broadcast/') +@user_has_permissions('send_messages') +@service_has_permission('broadcast') +def view_broadcast_message(service_id, broadcast_message_id): + broadcast_message = BroadcastMessage.from_id( + broadcast_message_id, + service_id=current_service.id, + ) + if broadcast_message.status == 'draft': + abort(404) + return render_template( + 'views/broadcast/view-message.html', + broadcast_message=broadcast_message, + ) + + @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 c3efd2ed1..45ab2fb76 100644 --- a/app/models/broadcast_message.py +++ b/app/models/broadcast_message.py @@ -5,6 +5,7 @@ from notifications_utils.template import BroadcastPreviewTemplate from orderedset import OrderedSet from app.models import JSONModel, ModelList +from app.models.user import User from app.notify_client.broadcast_message_api_client import ( broadcast_message_api_client, ) @@ -94,6 +95,18 @@ class BroadcastMessage(JSONModel): return 'completed' return self._dict['status'] + @property + def created_by(self): + return User.from_id(self.created_by_id) + + @property + def approved_by(self): + return User.from_id(self.approved_by_id) + + @property + def cancelled_by(self): + return User.from_id(self.cancelled_by_id) + def add_areas(self, *new_areas): broadcast_message_api_client.update_broadcast_message( broadcast_message_id=self.id, diff --git a/app/navigation.py b/app/navigation.py index a2a5137cd..2eae15689 100644 --- a/app/navigation.py +++ b/app/navigation.py @@ -358,6 +358,7 @@ class HeaderNavigation(Navigation): 'choose_broadcast_area', 'remove_broadcast_area', 'preview_broadcast_message', + 'view_broadcast_message', 'cancel_broadcast_message', } @@ -414,6 +415,7 @@ class MainNavigation(Navigation): 'choose_broadcast_area', 'remove_broadcast_area', 'preview_broadcast_message', + 'view_broadcast_message', 'cancel_broadcast_message', }, 'uploads': { @@ -1003,6 +1005,7 @@ class CaseworkNavigation(Navigation): 'choose_broadcast_area', 'remove_broadcast_area', 'preview_broadcast_message', + 'view_broadcast_message', 'cancel_broadcast_message', } @@ -1321,5 +1324,6 @@ class OrgNavigation(Navigation): 'choose_broadcast_area', 'remove_broadcast_area', 'preview_broadcast_message', + 'view_broadcast_message', 'cancel_broadcast_message', } diff --git a/app/templates/views/broadcast/dashboard.html b/app/templates/views/broadcast/dashboard.html index 24d78a94b..f27680b48 100644 --- a/app/templates/views/broadcast/dashboard.html +++ b/app/templates/views/broadcast/dashboard.html @@ -17,7 +17,7 @@ ) %} {% call row_heading() %}
- {{ item.template_name }} + {{ item.template_name }} To {{ item.initial_area_names|formatted_list(before_each='', after_each='') }} diff --git a/app/templates/views/broadcast/view-message.html b/app/templates/views/broadcast/view-message.html new file mode 100644 index 000000000..625190db9 --- /dev/null +++ b/app/templates/views/broadcast/view-message.html @@ -0,0 +1,54 @@ +{% 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 %} + {{ broadcast_message.template_name }} +{% endblock %} + +{% block maincolumn_content %} + + {{ page_header( + broadcast_message.template_name, + back_link=url_for('.broadcast_dashboard', service_id=current_service.id) + ) }} + +

+ Created by {{ broadcast_message.created_by.name }} and approved by + {{ broadcast_message.approved_by.name }}. +

+ +

+ Started broadcasting + {{ broadcast_message.starts_at|format_datetime_human }}. +

+ +

+ {% if broadcast_message.status == 'broadcasting' %} + Live until {{ broadcast_message.finishes_at|format_datetime_relative }} Stop broadcast early + {% elif broadcast_message.status == 'cancelled' %} + Stopped by {{ broadcast_message.cancelled_by.name }} + {{ broadcast_message.cancelled_at|format_datetime_human }}. + {% else %} + Finished broadcasting {{ broadcast_message.finishes_at|format_datetime_human }}. + {% endif %} +

+ + {% for area in broadcast_message.areas %} + {% if loop.first %} +
    + {% endif %} +
  • + {{ area.name }} +
  • + {% if loop.last %} +
+ {% endif %} + {% endfor %} + + {{ broadcast_message.template|string }} + +{% endblock %} diff --git a/tests/__init__.py b/tests/__init__.py index a40715e9e..8aef7db2e 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -647,6 +647,8 @@ def broadcast_message_json( starts_at=None, finishes_at=None, cancelled_at=None, + approved_by_id=None, + cancelled_by_id=None, ): return { 'id': id_, @@ -673,6 +675,6 @@ def broadcast_message_json( 'updated_at': None, 'created_by_id': created_by_id, - 'approved_by_id': None, - 'cancelled_by_id': None, + 'approved_by_id': approved_by_id, + 'cancelled_by_id': cancelled_by_id, } diff --git a/tests/app/main/views/test_broadcast.py b/tests/app/main/views/test_broadcast.py index 4b24d74b2..c2d58d0d6 100644 --- a/tests/app/main/views/test_broadcast.py +++ b/tests/app/main/views/test_broadcast.py @@ -2,7 +2,7 @@ import pytest from flask import url_for from freezegun import freeze_time -from tests import sample_uuid +from tests import broadcast_message_json, sample_uuid, user_json from tests.conftest import SERVICE_ONE_ID, normalize_spaces sample_uuid = sample_uuid() @@ -277,6 +277,98 @@ def test_start_broadcasting( ) +@pytest.mark.parametrize('extra_fields, expected_paragraphs', ( + ({ + 'status': 'broadcasting', + 'finishes_at': '2020-02-23T23:23:23.000000', + }, [ + 'Created by Alice and approved by Bob.', + 'Started broadcasting on 20 February at 8:20pm.', + 'Live until tomorrow at 11:23pm Stop broadcast early', + ]), + ({ + 'status': 'broadcasting', + 'finishes_at': '2020-02-22T22:20:20.000000', # 2 mins before now() + }, [ + 'Created by Alice and approved by Bob.', + 'Started broadcasting on 20 February at 8:20pm.', + 'Finished broadcasting today at 10:20pm.', + ]), + ({ + 'status': 'finished', + 'finishes_at': '2020-02-21T21:21:21.000000', + }, [ + 'Created by Alice and approved by Bob.', + 'Started broadcasting on 20 February at 8:20pm.', + 'Finished broadcasting yesterday at 9:21pm.', + ]), + ({ + 'status': 'cancelled', + 'cancelled_by_id': sample_uuid, + 'cancelled_at': '2020-02-21T21:21:21.000000', + }, [ + 'Created by Alice and approved by Bob.', + 'Started broadcasting on 20 February at 8:20pm.', + 'Stopped by Carol yesterday at 9:21pm.', + ]), +)) +@freeze_time('2020-02-22T22:22:22.000000') +def test_view_broadcast_message_page( + mocker, + client_request, + service_one, + active_user_with_permissions, + mock_get_broadcast_template, + fake_uuid, + extra_fields, + expected_paragraphs, +): + 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, + approved_by_id=fake_uuid, + starts_at='2020-02-20T20:20:20.000000', + **extra_fields + ), + ) + mocker.patch('app.user_api_client.get_user', side_effect=[ + active_user_with_permissions, + user_json(name='Alice'), + user_json(name='Bob'), + user_json(name='Carol'), + ]) + service_one['permissions'] += ['broadcast'] + + page = client_request.get( + '.view_broadcast_message', + service_id=SERVICE_ONE_ID, + broadcast_message_id=fake_uuid, + ) + + assert [ + normalize_spaces(p.text) for p in page.select('main p.govuk-body') + ] == expected_paragraphs + + +def test_no_view_page_for_draft( + client_request, + service_one, + mock_get_draft_broadcast_message, + fake_uuid, +): + service_one['permissions'] += ['broadcast'] + client_request.get( + '.view_broadcast_message', + service_id=SERVICE_ONE_ID, + broadcast_message_id=fake_uuid, + _expected_status=404, + ) + + def test_cancel_broadcast( client_request, service_one, diff --git a/tests/conftest.py b/tests/conftest.py index 6cee5a58b..76d1fb130 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4191,6 +4191,34 @@ def mock_get_draft_broadcast_message( ) +@pytest.fixture(scope='function') +def mock_get_live_broadcast_message( + mocker, + fake_uuid, +): + def _get( + *, service_id, broadcast_message_id + ): + return broadcast_message_json( + id_=broadcast_message_id, + service_id=service_id, + template_id=fake_uuid, + status='broadcasting', + created_by_id=fake_uuid, + starts_at=( + datetime.utcnow() + ).isoformat(), + finishes_at=( + datetime.utcnow() + timedelta(hours=24) + ).isoformat(), + ) + + return mocker.patch( + 'app.broadcast_message_api_client.get_broadcast_message', + side_effect=_get, + ) + + @pytest.fixture(scope='function') def mock_get_no_broadcast_messages( mocker,