diff --git a/app/main/views/broadcast.py b/app/main/views/broadcast.py
index 6a795d4b8..0ae193919 100644
--- a/app/main/views/broadcast.py
+++ b/app/main/views/broadcast.py
@@ -3,7 +3,7 @@ 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.models.broadcast_message import BroadcastMessage, BroadcastMessages
from app.utils import service_has_permission, user_has_permissions
@@ -11,8 +11,11 @@ from app.utils import service_has_permission, user_has_permissions
@user_has_permissions()
@service_has_permission('broadcast')
def broadcast_dashboard(service_id):
+ broadcast_messages = BroadcastMessages(service_id)
return render_template(
'views/broadcast/dashboard.html',
+ live_broadcasts=broadcast_messages.with_status('broadcasting'),
+ previous_broadcasts=broadcast_messages.with_status('cancelled', 'completed'),
)
diff --git a/app/models/broadcast_message.py b/app/models/broadcast_message.py
index 041d3831a..bdc4c37bf 100644
--- a/app/models/broadcast_message.py
+++ b/app/models/broadcast_message.py
@@ -4,7 +4,7 @@ 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.models import JSONModel, ModelList
from app.notify_client.broadcast_message_api_client import (
broadcast_message_api_client,
)
@@ -24,7 +24,6 @@ class BroadcastMessage(JSONModel):
'personalisation',
'starts_at',
'finishes_at',
- 'status',
'created_at',
'approved_at',
'cancelled_at',
@@ -57,6 +56,12 @@ class BroadcastMessage(JSONModel):
*self._dict['areas']
)
+ @property
+ def initial_area_names(self):
+ return [
+ area.name for area in self.areas
+ ][:10]
+
@property
def polygons(self):
return broadcast_area_libraries.get_polygons_for_areas_lat_long(
@@ -72,6 +77,16 @@ class BroadcastMessage(JSONModel):
)
return BroadcastPreviewTemplate(response['data'])
+ @property
+ def status(self):
+ if (
+ self._dict['status']
+ and self._dict['status'] == 'broadcasting'
+ and self.finishes_at < datetime.utcnow().isoformat()
+ ):
+ return 'completed'
+ return self._dict['status']
+
def add_areas(self, *new_areas):
broadcast_message_api_client.update_broadcast_message(
broadcast_message_id=self.id,
@@ -109,3 +124,14 @@ class BroadcastMessage(JSONModel):
broadcast_message_id=self.id,
service_id=self.service_id,
)
+
+
+class BroadcastMessages(ModelList):
+
+ model = BroadcastMessage
+ client_method = broadcast_message_api_client.get_broadcast_messages
+
+ def with_status(self, *statuses):
+ return [
+ broadcast for broadcast in self if broadcast.status in statuses
+ ]
diff --git a/app/notify_client/broadcast_message_api_client.py b/app/notify_client/broadcast_message_api_client.py
index 57439798d..2f04a76f5 100644
--- a/app/notify_client/broadcast_message_api_client.py
+++ b/app/notify_client/broadcast_message_api_client.py
@@ -24,6 +24,9 @@ class BroadcastMessageAPIClient(NotifyAdminAPIClient):
return broadcast_message
+ def get_broadcast_messages(self, service_id):
+ return self.get(f'/service/{service_id}/broadcast-message')['broadcast_messages']
+
def get_broadcast_message(self, *, service_id, broadcast_message_id):
return self.get(f'/service/{service_id}/broadcast-message/{broadcast_message_id}')
diff --git a/app/templates/views/broadcast/dashboard.html b/app/templates/views/broadcast/dashboard.html
index c92ce3992..64672943e 100644
--- a/app/templates/views/broadcast/dashboard.html
+++ b/app/templates/views/broadcast/dashboard.html
@@ -1,5 +1,43 @@
+{% from "components/table.html" import list_table, field, right_aligned_field_heading, row_heading %}
+
{% extends "withnav_template.html" %}
+{% macro broadcast_table(broadcasts, empty_message) %}
+
+ {% call(item, row_number) list_table(
+ broadcasts,
+ caption="Live broadcasts",
+ caption_visible=False,
+ empty_message=empty_message,
+ field_headings=[
+ 'Template name',
+ 'Status'
+ ],
+ field_headings_visible=False
+ ) %}
+ {% call row_heading() %}
+
+ {% endcall %}
+ {% call field(align='right') %}
+
+ {% if item.status == 'broadcasting' %}
+ Live until {{ item.finishes_at|format_datetime_relative }}
+ {% elif item.status == 'cancelled' %}
+ Stopped {{ item.cancelled_at|format_datetime_relative }}
+ {% else %}
+ Finished {{ item.finishes_at|format_datetime_relative }}
+ {% endif %}
+
+ {% endcall %}
+ {% endcall %}
+
+{% endmacro %}
+
{% block service_page_title %}
Dashboard
{% endblock %}
@@ -8,8 +46,12 @@
Dashboard
-
- Broadcasts here
-
+ Live broadcasts
+
+ {{ broadcast_table(live_broadcasts, 'You do not have any live broadcasts at the moment') }}
+
+ Previous broadcasts
+
+ {{ broadcast_table(previous_broadcasts, 'You do not have any previous broadcasts') }}
{% endblock %}
diff --git a/tests/__init__.py b/tests/__init__.py
index 14ebdd8ac..a40715e9e 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -635,3 +635,44 @@ def assert_url_expected(actual, expected):
def find_element_by_tag_and_partial_text(page, tag, string):
return [e for e in page.find_all(tag) if string in e.text][0]
+
+
+def broadcast_message_json(
+ *,
+ id_,
+ service_id,
+ template_id,
+ status,
+ created_by_id,
+ starts_at=None,
+ finishes_at=None,
+ cancelled_at=None,
+):
+ return {
+ 'id': id_,
+
+ 'service_id': service_id,
+
+ 'template_id': template_id,
+ 'template_version': 123,
+ 'template_name': 'Example template',
+
+ 'personalisation': {},
+ 'areas': [
+ 'england', 'scotland',
+ ],
+
+ 'status': status,
+
+ 'starts_at': starts_at,
+ 'finishes_at': finishes_at,
+
+ 'created_at': None,
+ 'approved_at': None,
+ 'cancelled_at': cancelled_at,
+ 'updated_at': None,
+
+ 'created_by_id': created_by_id,
+ 'approved_by_id': None,
+ 'cancelled_by_id': None,
+ }
diff --git a/tests/app/main/views/test_broadcast.py b/tests/app/main/views/test_broadcast.py
index 984a3fd6e..13a933f2e 100644
--- a/tests/app/main/views/test_broadcast.py
+++ b/tests/app/main/views/test_broadcast.py
@@ -46,15 +46,46 @@ def test_dashboard_redirects_to_broadcast_dashboard(
),
+def test_empty_broadcast_dashboard(
+ client_request,
+ service_one,
+ mock_get_no_broadcast_messages,
+):
+ service_one['permissions'] += ['broadcast']
+ page = client_request.get(
+ '.broadcast_dashboard',
+ service_id=SERVICE_ONE_ID,
+ )
+ assert [
+ normalize_spaces(row.text) for row in page.select('tbody tr .table-empty-message')
+ ] == [
+ 'You do not have any live broadcasts at the moment',
+ 'You do not have any previous broadcasts',
+ ]
+
+
+@freeze_time('2020-02-20 02:20')
def test_broadcast_dashboard(
client_request,
service_one,
+ mock_get_broadcast_messages,
):
service_one['permissions'] += ['broadcast']
- client_request.get(
+ page = client_request.get(
'.broadcast_dashboard',
service_id=SERVICE_ONE_ID,
- ),
+ )
+ 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',
+ ]
+ assert [
+ normalize_spaces(row.text) for row in page.select('table')[1].select('tbody tr')
+ ] == [
+ 'Example template To England and Scotland Finished yesterday at 8:20pm',
+ 'Example template To England and Scotland Stopped 10 February at 2:20am',
+ ]
def test_broadcast_page(
diff --git a/tests/conftest.py b/tests/conftest.py
index 81f54d481..6cee5a58b 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -2,6 +2,7 @@ import json
import os
from contextlib import contextmanager
from datetime import date, datetime, timedelta
+from functools import partial
from unittest.mock import Mock, PropertyMock
from uuid import UUID, uuid4
@@ -17,6 +18,7 @@ from . import (
TestClient,
api_key_json,
assert_url_expected,
+ broadcast_message_json,
generate_uuid,
invite_json,
job_json,
@@ -4175,34 +4177,13 @@ def mock_get_draft_broadcast_message(
def _get(
*, service_id, broadcast_message_id
):
- return {
- 'id': broadcast_message_id,
-
- 'service_id': service_id,
-
- 'template_id': fake_uuid,
- 'template_version': 123,
- 'template_name': 'Example template',
-
- 'personalisation': {},
- 'areas': [
- 'england', 'scotland',
- ],
-
- 'status': 'draft',
-
- 'starts_at': None,
- 'finishes_at': None,
-
- 'created_at': None,
- 'approved_at': None,
- 'cancelled_at': None,
- 'updated_at': None,
-
- 'created_by_id': fake_uuid,
- 'approved_by_id': None,
- 'cancelled_by_id': None,
- }
+ return broadcast_message_json(
+ id_=broadcast_message_id,
+ service_id=service_id,
+ template_id=fake_uuid,
+ status='draft',
+ created_by_id=fake_uuid,
+ )
return mocker.patch(
'app.broadcast_message_api_client.get_broadcast_message',
@@ -4210,6 +4191,80 @@ def mock_get_draft_broadcast_message(
)
+@pytest.fixture(scope='function')
+def mock_get_no_broadcast_messages(
+ mocker,
+ fake_uuid,
+):
+ return mocker.patch(
+ 'app.models.broadcast_message.BroadcastMessages.client_method',
+ return_value=[
+ broadcast_message_json(
+ id_=fake_uuid,
+ service_id=SERVICE_ONE_ID,
+ template_id=fake_uuid,
+ status='draft', # draft broadcasts aren’t shown on the dashboard
+ created_by_id=fake_uuid,
+ ),
+ ],
+ )
+
+
+@pytest.fixture(scope='function')
+def mock_get_broadcast_messages(
+ mocker,
+ fake_uuid,
+):
+ def _get(service_id):
+ partial_json = partial(
+ broadcast_message_json,
+ id_=fake_uuid,
+ service_id=service_id,
+ template_id=fake_uuid,
+ created_by_id=fake_uuid,
+ )
+ return [
+ partial_json(
+ status='draft',
+ ),
+ partial_json(
+ status='broadcasting',
+ starts_at=(
+ datetime.utcnow()
+ ).isoformat(),
+ finishes_at=(
+ datetime.utcnow() + timedelta(hours=24)
+ ).isoformat(),
+ ),
+ partial_json(
+ status='completed',
+ starts_at=(
+ datetime.utcnow() - timedelta(hours=12)
+ ).isoformat(),
+ finishes_at=(
+ datetime.utcnow() - timedelta(hours=6)
+ ).isoformat(),
+ ),
+ partial_json(
+ status='cancelled',
+ starts_at=(
+ datetime.utcnow() - timedelta(days=1)
+ ).isoformat(),
+ finishes_at=(
+ datetime.utcnow() - timedelta(days=100)
+ ).isoformat(),
+ cancelled_at=(
+ datetime.utcnow() - timedelta(days=10)
+ ).isoformat(),
+ ),
+ ]
+
+ return mocker.patch(
+ 'app.models.broadcast_message.BroadcastMessages.client_method',
+ side_effect=_get,
+ )
+
+
@pytest.fixture(scope='function')
def mock_update_broadcast_message(
mocker,