mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-14 02:48:45 -04:00
Merge pull request #3518 from alphagov/view-broadcast-page
Add a page to view a single broadcast
This commit is contained in:
@@ -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/<uuid:service_id>/broadcast/<uuid:template_id>')
|
||||
@main.route('/services/<uuid:service_id>/new-broadcast/<uuid:template_id>')
|
||||
@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/<uuid:service_id>/broadcast/<uuid:broadcast_message_id>')
|
||||
@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/<uuid:service_id>/broadcast/<uuid:broadcast_message_id>/cancel')
|
||||
@user_has_permissions('send_messages')
|
||||
@service_has_permission('broadcast')
|
||||
@@ -136,6 +152,7 @@ def cancel_broadcast_message(service_id, broadcast_message_id):
|
||||
service_id=current_service.id,
|
||||
).cancel_broadcast()
|
||||
return redirect(url_for(
|
||||
'.broadcast_dashboard',
|
||||
'.view_broadcast_message',
|
||||
service_id=current_service.id,
|
||||
broadcast_message_id=broadcast_message_id,
|
||||
))
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -359,6 +359,7 @@ class HeaderNavigation(Navigation):
|
||||
'choose_broadcast_area',
|
||||
'remove_broadcast_area',
|
||||
'preview_broadcast_message',
|
||||
'view_broadcast_message',
|
||||
'cancel_broadcast_message',
|
||||
}
|
||||
|
||||
@@ -415,6 +416,7 @@ class MainNavigation(Navigation):
|
||||
'choose_broadcast_area',
|
||||
'remove_broadcast_area',
|
||||
'preview_broadcast_message',
|
||||
'view_broadcast_message',
|
||||
'cancel_broadcast_message',
|
||||
},
|
||||
'uploads': {
|
||||
@@ -1006,6 +1008,7 @@ class CaseworkNavigation(Navigation):
|
||||
'choose_broadcast_area',
|
||||
'remove_broadcast_area',
|
||||
'preview_broadcast_message',
|
||||
'view_broadcast_message',
|
||||
'cancel_broadcast_message',
|
||||
}
|
||||
|
||||
@@ -1325,5 +1328,6 @@ class OrgNavigation(Navigation):
|
||||
'choose_broadcast_area',
|
||||
'remove_broadcast_area',
|
||||
'preview_broadcast_message',
|
||||
'view_broadcast_message',
|
||||
'cancel_broadcast_message',
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
) %}
|
||||
{% call row_heading() %}
|
||||
<div class="file-list">
|
||||
<a class="file-list-filename-large govuk-link govuk-link--no-visited-state" href="#">{{ item.template_name }}</a>
|
||||
<a class="file-list-filename-large govuk-link govuk-link--no-visited-state" href="{{ url_for('.view_broadcast_message', service_id=current_service.id, broadcast_message_id=item.id) }}">{{ item.template_name }}</a>
|
||||
<span class="file-list-hint-large">
|
||||
To {{ item.initial_area_names|formatted_list(before_each='', after_each='') }}
|
||||
</span>
|
||||
@@ -25,9 +25,8 @@
|
||||
{% endcall %}
|
||||
{% call field(align='right') %}
|
||||
{% if item.status == 'broadcasting' %}
|
||||
<p class="govuk-body letter-recipient-summary">
|
||||
<p class="govuk-body govuk-!-margin-top-6 govuk-!-margin-bottom-0">
|
||||
Live until {{ item.finishes_at|format_datetime_relative }}
|
||||
<a href="{{ url_for('.cancel_broadcast_message', service_id=current_service.id, broadcast_message_id=item.id) }}" class="destructive-link destructive-link--no-visited-state">Stop broadcasting</a>
|
||||
</p>
|
||||
{% elif item.status == 'cancelled' %}
|
||||
<p class="govuk-body govuk-!-margin-top-6 govuk-!-margin-bottom-0 govuk-hint">
|
||||
|
||||
54
app/templates/views/broadcast/view-message.html
Normal file
54
app/templates/views/broadcast/view-message.html
Normal file
@@ -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)
|
||||
) }}
|
||||
|
||||
<p class="govuk-body govuk-!-margin-bottom-3">
|
||||
Created by {{ broadcast_message.created_by.name }} and approved by
|
||||
{{ broadcast_message.approved_by.name }}.
|
||||
</p>
|
||||
|
||||
<p class="govuk-body govuk-!-margin-bottom-3">
|
||||
Started broadcasting
|
||||
{{ broadcast_message.starts_at|format_datetime_human }}.
|
||||
</p>
|
||||
|
||||
<p class="govuk-body">
|
||||
{% if broadcast_message.status == 'broadcasting' %}
|
||||
Live until {{ broadcast_message.finishes_at|format_datetime_relative }} <a href="{{ url_for('.cancel_broadcast_message', service_id=current_service.id, broadcast_message_id=broadcast_message.id) }}" class="destructive-link destructive-link--no-visited-state">Stop broadcast early</a>
|
||||
{% 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 %}
|
||||
</p>
|
||||
|
||||
{% for area in broadcast_message.areas %}
|
||||
{% if loop.first %}
|
||||
<ul class="area-list">
|
||||
{% endif %}
|
||||
<li class="area-list-item area-list-item--unremoveable">
|
||||
{{ area.name }}
|
||||
</li>
|
||||
{% if loop.last %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{{ broadcast_message.template|string }}
|
||||
|
||||
{% endblock %}
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
@@ -78,7 +78,7 @@ def test_broadcast_dashboard(
|
||||
assert [
|
||||
normalize_spaces(row.text) for row in page.select('table')[0].select('tbody tr')
|
||||
] == [
|
||||
'Example template To England and Scotland Live until tomorrow at 2:20am Stop broadcasting',
|
||||
'Example template To England and Scotland Live until tomorrow at 2:20am',
|
||||
]
|
||||
assert [
|
||||
normalize_spaces(row.text) for row in page.select('table')[1].select('tbody tr')
|
||||
@@ -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,
|
||||
@@ -290,8 +382,9 @@ def test_cancel_broadcast(
|
||||
service_id=SERVICE_ONE_ID,
|
||||
broadcast_message_id=fake_uuid,
|
||||
_expected_redirect=url_for(
|
||||
'.broadcast_dashboard',
|
||||
'.view_broadcast_message',
|
||||
service_id=SERVICE_ONE_ID,
|
||||
broadcast_message_id=fake_uuid,
|
||||
_external=True,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -4192,6 +4192,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,
|
||||
|
||||
Reference in New Issue
Block a user