mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-18 21:49:37 -04:00
Merge pull request #3512 from alphagov/make-broadcast-flow-talk-to-api
Make the flow to create a broadcast talk to the API
This commit is contained in:
@@ -59,6 +59,9 @@ from app.navigation import (
|
||||
from app.notify_client import InviteTokenError
|
||||
from app.notify_client.api_key_api_client import api_key_api_client
|
||||
from app.notify_client.billing_api_client import billing_api_client
|
||||
from app.notify_client.broadcast_message_api_client import (
|
||||
broadcast_message_api_client,
|
||||
)
|
||||
from app.notify_client.complaint_api_client import complaint_api_client
|
||||
from app.notify_client.contact_list_api_client import contact_list_api_client
|
||||
from app.notify_client.email_branding_client import email_branding_client
|
||||
@@ -143,6 +146,7 @@ def create_app(application):
|
||||
# API clients
|
||||
api_key_api_client,
|
||||
billing_api_client,
|
||||
broadcast_message_api_client,
|
||||
contact_list_api_client,
|
||||
complaint_api_client,
|
||||
email_branding_client,
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
from flask import redirect, render_template, request, session, url_for
|
||||
from notifications_utils.broadcast_areas import broadcast_area_libraries
|
||||
from notifications_utils.template import BroadcastPreviewTemplate
|
||||
from orderedset import OrderedSet
|
||||
from flask import redirect, render_template, request, url_for
|
||||
|
||||
from app import current_service
|
||||
from app.main import main
|
||||
from app.main.forms import BroadcastAreaForm, SearchByNameForm
|
||||
from app.models.broadcast_message import BroadcastMessage
|
||||
from app.utils import service_has_permission, user_has_permissions
|
||||
|
||||
|
||||
@@ -18,63 +16,63 @@ def broadcast_dashboard(service_id):
|
||||
)
|
||||
|
||||
|
||||
@main.route('/services/<uuid:service_id>/broadcast')
|
||||
@main.route('/services/<uuid:service_id>/broadcast/<uuid:template_id>')
|
||||
@user_has_permissions('send_messages')
|
||||
@service_has_permission('broadcast')
|
||||
def broadcast(service_id):
|
||||
if 'broadcast_areas' in session:
|
||||
session.pop('broadcast_areas')
|
||||
def broadcast(service_id, template_id):
|
||||
return redirect(url_for(
|
||||
'.preview_broadcast_areas',
|
||||
service_id=current_service.id,
|
||||
broadcast_message_id=BroadcastMessage.create(
|
||||
service_id=service_id,
|
||||
template_id=template_id,
|
||||
).id,
|
||||
))
|
||||
|
||||
|
||||
@main.route('/services/<uuid:service_id>/broadcast/areas')
|
||||
@main.route('/services/<uuid:service_id>/broadcast/<uuid:broadcast_message_id>/areas')
|
||||
@user_has_permissions('send_messages')
|
||||
@service_has_permission('broadcast')
|
||||
def preview_broadcast_areas(service_id):
|
||||
selected_areas_ids = session.get('broadcast_areas', [])
|
||||
def preview_broadcast_areas(service_id, broadcast_message_id):
|
||||
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/<uuid:service_id>/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', [])
|
||||
broadcast_message=BroadcastMessage.from_id(
|
||||
broadcast_message_id,
|
||||
service_id=current_service.id,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@main.route('/services/<uuid:service_id>/broadcast/libraries/<library_slug>', methods=['GET', 'POST'])
|
||||
@main.route('/services/<uuid:service_id>/broadcast/<uuid:broadcast_message_id>/libraries')
|
||||
@user_has_permissions('send_messages')
|
||||
@service_has_permission('broadcast')
|
||||
def choose_broadcast_area(service_id, library_slug):
|
||||
library = broadcast_area_libraries.get(library_slug)
|
||||
def choose_broadcast_library(service_id, broadcast_message_id):
|
||||
return render_template(
|
||||
'views/broadcast/libraries.html',
|
||||
libraries=BroadcastMessage.libraries,
|
||||
broadcast_message_id=broadcast_message_id,
|
||||
)
|
||||
|
||||
|
||||
@main.route(
|
||||
'/services/<uuid:service_id>/broadcast/<uuid:broadcast_message_id>/libraries/<library_slug>',
|
||||
methods=['GET', 'POST'],
|
||||
)
|
||||
@user_has_permissions('send_messages')
|
||||
@service_has_permission('broadcast')
|
||||
def choose_broadcast_area(service_id, broadcast_message_id, library_slug):
|
||||
broadcast_message = BroadcastMessage.from_id(
|
||||
broadcast_message_id,
|
||||
service_id=current_service.id,
|
||||
)
|
||||
library = BroadcastMessage.libraries.get(library_slug)
|
||||
form = BroadcastAreaForm.from_library(library)
|
||||
if form.validate_on_submit():
|
||||
if not session.get('broadcast_areas'):
|
||||
session['broadcast_areas'] = []
|
||||
session['broadcast_areas'] = session['broadcast_areas'] + form.areas.data
|
||||
session['broadcast_areas'] = list(OrderedSet(
|
||||
session['broadcast_areas']
|
||||
))
|
||||
broadcast_message.add_areas(*form.areas.data)
|
||||
return redirect(url_for(
|
||||
'.preview_broadcast_areas',
|
||||
service_id=current_service.id,
|
||||
broadcast_message_id=broadcast_message.id,
|
||||
))
|
||||
return render_template(
|
||||
'views/broadcast/areas.html',
|
||||
@@ -85,35 +83,41 @@ def choose_broadcast_area(service_id, library_slug):
|
||||
)
|
||||
|
||||
|
||||
@main.route('/services/<uuid:service_id>/broadcast/remove/<area_slug>')
|
||||
@main.route('/services/<uuid:service_id>/broadcast/<uuid:broadcast_message_id>/remove/<area_slug>')
|
||||
@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', []),
|
||||
))
|
||||
|
||||
def remove_broadcast_area(service_id, broadcast_message_id, area_slug):
|
||||
BroadcastMessage.from_id(
|
||||
broadcast_message_id,
|
||||
service_id=current_service.id,
|
||||
).remove_area(
|
||||
area_slug
|
||||
)
|
||||
return redirect(url_for(
|
||||
'.preview_broadcast_areas',
|
||||
service_id=current_service.id,
|
||||
broadcast_message_id=broadcast_message_id,
|
||||
))
|
||||
|
||||
|
||||
@main.route('/services/<uuid:service_id>/broadcast/preview', methods=['GET', 'POST'])
|
||||
@main.route(
|
||||
'/services/<uuid:service_id>/broadcast/<uuid:broadcast_message_id>/preview',
|
||||
methods=['GET', 'POST'],
|
||||
)
|
||||
@user_has_permissions('send_messages')
|
||||
@service_has_permission('broadcast')
|
||||
def preview_broadcast_message(service_id):
|
||||
def preview_broadcast_message(service_id, broadcast_message_id):
|
||||
broadcast_message = BroadcastMessage.from_id(
|
||||
broadcast_message_id,
|
||||
service_id=current_service.id,
|
||||
)
|
||||
if request.method == 'POST':
|
||||
return 'OK'
|
||||
selected_areas = session.get('broadcast_areas', [])
|
||||
broadcast_message.start_broadcast()
|
||||
return redirect(url_for(
|
||||
'.broadcast_dashboard',
|
||||
service_id=current_service.id,
|
||||
))
|
||||
return render_template(
|
||||
'views/broadcast/preview-message.html',
|
||||
selected=list(broadcast_area_libraries.get_areas(
|
||||
*selected_areas
|
||||
)),
|
||||
template=BroadcastPreviewTemplate({
|
||||
'content': 'Message here',
|
||||
'template_type': 'broadcast',
|
||||
})
|
||||
broadcast_message=broadcast_message,
|
||||
)
|
||||
|
||||
111
app/models/broadcast_message.py
Normal file
111
app/models/broadcast_message.py
Normal file
@@ -0,0 +1,111 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from notifications_utils.broadcast_areas import broadcast_area_libraries
|
||||
from notifications_utils.template import BroadcastPreviewTemplate
|
||||
from orderedset import OrderedSet
|
||||
|
||||
from app.models import JSONModel
|
||||
from app.notify_client.broadcast_message_api_client import (
|
||||
broadcast_message_api_client,
|
||||
)
|
||||
from app.notify_client.service_api_client import service_api_client
|
||||
|
||||
|
||||
class BroadcastMessage(JSONModel):
|
||||
|
||||
ALLOWED_PROPERTIES = {
|
||||
'id',
|
||||
'service_id',
|
||||
'template_id',
|
||||
'template_name',
|
||||
'template_version',
|
||||
'service_id',
|
||||
'created_by',
|
||||
'personalisation',
|
||||
'starts_at',
|
||||
'finishes_at',
|
||||
'status',
|
||||
'created_at',
|
||||
'approved_at',
|
||||
'cancelled_at',
|
||||
'updated_at',
|
||||
'created_by_id',
|
||||
'approved_by_id',
|
||||
'cancelled_by_id',
|
||||
}
|
||||
DEFAULT_TTL = timedelta(hours=72)
|
||||
|
||||
libraries = broadcast_area_libraries
|
||||
|
||||
@classmethod
|
||||
def create(cls, *, service_id, template_id):
|
||||
return cls(broadcast_message_api_client.create_broadcast_message(
|
||||
service_id=service_id,
|
||||
template_id=template_id,
|
||||
))
|
||||
|
||||
@classmethod
|
||||
def from_id(cls, broadcast_message_id, *, service_id):
|
||||
return cls(broadcast_message_api_client.get_broadcast_message(
|
||||
service_id=service_id,
|
||||
broadcast_message_id=broadcast_message_id,
|
||||
))
|
||||
|
||||
@property
|
||||
def areas(self):
|
||||
return broadcast_area_libraries.get_areas(
|
||||
*self._dict['areas']
|
||||
)
|
||||
|
||||
@property
|
||||
def polygons(self):
|
||||
return broadcast_area_libraries.get_polygons_for_areas_lat_long(
|
||||
*self._dict['areas']
|
||||
)
|
||||
|
||||
@property
|
||||
def template(self):
|
||||
response = service_api_client.get_service_template(
|
||||
self.service_id,
|
||||
self.template_id,
|
||||
version=self.template_version,
|
||||
)
|
||||
return BroadcastPreviewTemplate(response['data'])
|
||||
|
||||
def add_areas(self, *new_areas):
|
||||
broadcast_message_api_client.update_broadcast_message(
|
||||
broadcast_message_id=self.id,
|
||||
service_id=self.service_id,
|
||||
data={
|
||||
'areas': list(OrderedSet(
|
||||
self._dict['areas'] + list(new_areas)
|
||||
))
|
||||
},
|
||||
)
|
||||
|
||||
def remove_area(self, area_to_remove):
|
||||
broadcast_message_api_client.update_broadcast_message(
|
||||
broadcast_message_id=self.id,
|
||||
service_id=self.service_id,
|
||||
data={
|
||||
'areas': [
|
||||
area for area in self._dict['areas']
|
||||
if area != area_to_remove
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
def start_broadcast(self):
|
||||
broadcast_message_api_client.update_broadcast_message(
|
||||
broadcast_message_id=self.id,
|
||||
service_id=self.service_id,
|
||||
data={
|
||||
'starts_at': datetime.utcnow().isoformat(),
|
||||
'finishes_at': (datetime.utcnow() + self.DEFAULT_TTL).isoformat(),
|
||||
},
|
||||
)
|
||||
broadcast_message_api_client.update_broadcast_message_status(
|
||||
'broadcasting',
|
||||
broadcast_message_id=self.id,
|
||||
service_id=self.service_id,
|
||||
)
|
||||
46
app/notify_client/broadcast_message_api_client.py
Normal file
46
app/notify_client/broadcast_message_api_client.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from app.notify_client import NotifyAdminAPIClient, _attach_current_user
|
||||
|
||||
|
||||
class BroadcastMessageAPIClient(NotifyAdminAPIClient):
|
||||
|
||||
def create_broadcast_message(
|
||||
self,
|
||||
*,
|
||||
service_id,
|
||||
template_id,
|
||||
):
|
||||
data = {
|
||||
"service_id": service_id,
|
||||
"template_id": template_id,
|
||||
"personalisation": {},
|
||||
}
|
||||
|
||||
data = _attach_current_user(data)
|
||||
|
||||
broadcast_message = self.post(
|
||||
f'/service/{service_id}/broadcast-message',
|
||||
data=data,
|
||||
)
|
||||
|
||||
return broadcast_message
|
||||
|
||||
def get_broadcast_message(self, *, service_id, broadcast_message_id):
|
||||
return self.get(f'/service/{service_id}/broadcast-message/{broadcast_message_id}')
|
||||
|
||||
def update_broadcast_message(self, *, service_id, broadcast_message_id, data):
|
||||
self.post(
|
||||
f'/service/{service_id}/broadcast-message/{broadcast_message_id}',
|
||||
data=data,
|
||||
)
|
||||
|
||||
def update_broadcast_message_status(self, status, *, service_id, broadcast_message_id):
|
||||
data = _attach_current_user({
|
||||
'status': status,
|
||||
})
|
||||
self.post(
|
||||
f'/service/{service_id}/broadcast-message/{broadcast_message_id}/status',
|
||||
data=data,
|
||||
)
|
||||
|
||||
|
||||
broadcast_message_api_client = BroadcastMessageAPIClient()
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
{{ page_header(
|
||||
page_title,
|
||||
back_link=url_for('.choose_broadcast_library', service_id=current_service.id),
|
||||
back_link=url_for('.choose_broadcast_library', service_id=current_service.id, broadcast_message_id=broadcast_message_id),
|
||||
)}}
|
||||
|
||||
{{ live_search(target_selector='.multiple-choice', show=show_search_form, form=search_form, label='Search by name') }}
|
||||
|
||||
@@ -10,11 +10,11 @@
|
||||
|
||||
{{ page_header(
|
||||
"Choose where to broadcast to",
|
||||
back_link=url_for(".preview_broadcast_areas", service_id=current_service.id)
|
||||
back_link=url_for(".preview_broadcast_areas", service_id=current_service.id, broadcast_message_id=broadcast_message_id)
|
||||
) }}
|
||||
|
||||
{% for library in libraries|sort %}
|
||||
<a class="file-list-filename-large govuk-link govuk-link--no-visited-state" href="{{ url_for('.choose_broadcast_area', service_id=current_service.id, library_slug=library.id) }}">{{ library.name }}</a>
|
||||
<a class="file-list-filename-large govuk-link govuk-link--no-visited-state" href="{{ url_for('.choose_broadcast_area', service_id=current_service.id, broadcast_message_id=broadcast_message_id, library_slug=library.id) }}">{{ library.name }}</a>
|
||||
<p class="file-list-hint-large">{{ library.get_examples() }}</p>
|
||||
{% endfor %}
|
||||
|
||||
|
||||
@@ -36,9 +36,9 @@
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
}).addTo(mymap);
|
||||
|
||||
{% for area in area_polygons %}
|
||||
{% for polygon in broadcast_message.polygons %}
|
||||
polygons.push(
|
||||
L.polygon({{area}}, {
|
||||
L.polygon({{polygon}}, {
|
||||
color: '#0b0b0c', // $black
|
||||
fillColor: '#2B8CC4', // $light-blue
|
||||
fillOpacity: 0.2,
|
||||
@@ -58,20 +58,23 @@
|
||||
|
||||
{% block maincolumn_content %}
|
||||
|
||||
{{ page_header("Choose where to broadcast to", back_link=url_for('.choose_template', service_id=current_service.id)) }}
|
||||
{{ page_header(
|
||||
"Choose where to broadcast to",
|
||||
back_link=url_for('.view_template', service_id=current_service.id, template_id=broadcast_message.template_id)
|
||||
) }}
|
||||
|
||||
{% for area in selected %}
|
||||
{% for area in broadcast_message.areas %}
|
||||
{% if loop.first %}
|
||||
<ul class="area-list">
|
||||
{% endif %}
|
||||
<li class="area-list-item">
|
||||
{{ area.name }} <a class="area-list-item-remove" href="{{ url_for('.remove_broadcast_area', service_id=current_service.id, area_slug=area.id) }}">remove</a>
|
||||
{{ area.name }} <a class="area-list-item-remove" href="{{ url_for('.remove_broadcast_area', service_id=current_service.id, broadcast_message_id=broadcast_message_id, area_slug=area.id) }}">remove</a>
|
||||
</li>
|
||||
{% if loop.last %}
|
||||
{{ govukButton({
|
||||
"element": "a",
|
||||
"text": "Add another area",
|
||||
"href": url_for('.choose_broadcast_library', service_id=current_service.id),
|
||||
"href": url_for('.choose_broadcast_library', service_id=current_service.id, broadcast_message_id=broadcast_message.id),
|
||||
"classes": "govuk-button--secondary govuk-!-margin-bottom-5"
|
||||
}) }}
|
||||
</ul>
|
||||
@@ -81,15 +84,15 @@
|
||||
{{ govukButton({
|
||||
"element": "a",
|
||||
"text": "Add broadcast areas",
|
||||
"href": url_for('.choose_broadcast_library', service_id=current_service.id),
|
||||
"href": url_for('.choose_broadcast_library', service_id=current_service.id, broadcast_message_id=broadcast_message.id),
|
||||
"classes": "govuk-button--secondary"
|
||||
}) }}
|
||||
</p>
|
||||
{% endfor %}
|
||||
|
||||
{% if selected %}
|
||||
{% if broadcast_message.areas %}
|
||||
<div id="map"></div>
|
||||
<form action="{{ url_for('.preview_broadcast_message', service_id=current_service.id) }}" method="get">
|
||||
<form action="{{ url_for('.preview_broadcast_message', service_id=current_service.id, broadcast_message_id=broadcast_message.id) }}" method="get">
|
||||
{{ sticky_page_footer('Continue to preview') }}
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
|
||||
{% block maincolumn_content %}
|
||||
|
||||
{{ page_header("Preview", back_link=url_for('.preview_broadcast_areas', service_id=current_service.id)) }}
|
||||
{{ page_header("Preview", back_link=url_for('.preview_broadcast_areas', service_id=current_service.id, broadcast_message_id=broadcast_message.id)) }}
|
||||
|
||||
{% for area in selected %}
|
||||
{% for area in broadcast_message.areas %}
|
||||
{% if loop.first %}
|
||||
<ul class="area-list">
|
||||
{% endif %}
|
||||
@@ -25,17 +25,10 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if selected %}
|
||||
|
||||
<p class="govuk-body">
|
||||
{{ template|string }}
|
||||
</p>
|
||||
|
||||
{% call form_wrapper() %}
|
||||
{{ sticky_page_footer('Start broadcast') }}
|
||||
{% endcall %}
|
||||
|
||||
{% endif %}
|
||||
{{ broadcast_message.template|string }}
|
||||
|
||||
{% call form_wrapper() %}
|
||||
{{ sticky_page_footer('Start broadcast') }}
|
||||
{% endcall %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
{% elif template.template_type == 'broadcast' %}
|
||||
{% if current_user.has_permissions('send_messages') %}
|
||||
<div class="govuk-grid-column-one-half">
|
||||
<a href="{{ url_for(".broadcast", service_id=current_service.id) }}" class="govuk-link govuk-link--no-visited-state pill-separate-item">
|
||||
<a href="{{ url_for(".broadcast", service_id=current_service.id, template_id=template.id) }}" class="govuk-link govuk-link--no-visited-state pill-separate-item">
|
||||
Prepare broadcast
|
||||
</a>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user