Merge pull request #3885 from alphagov/live-broadcast-tour

Add a version of the tour for live services
This commit is contained in:
Chris Hill-Scott
2021-05-24 10:58:45 +01:00
committed by GitHub
17 changed files with 2909 additions and 2623 deletions
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

@@ -137,7 +137,7 @@
&:hover {
color: $white;
background-color: $link-hover-colour;
box-shadow: 0 0 0 10px solid $link-hover-colour;
box-shadow: 0 0 0 10px $link-hover-colour;
}
&:focus,
+11
View File
@@ -42,6 +42,17 @@ def broadcast_tour(service_id, step_index):
)
@main.route('/services/<uuid:service_id>/broadcast-tour/live/<int:step_index>')
@user_has_permissions()
@service_has_permission('broadcast')
def broadcast_tour_live(service_id, step_index):
if step_index not in (1, 2):
abort(404)
return render_template(
f'views/broadcast/tour/live/{step_index}.html'
)
@main.route('/services/<uuid:service_id>/current-alerts')
@user_has_permissions()
@service_has_permission('broadcast')
+8 -3
View File
@@ -37,12 +37,14 @@ def accept_invite(token):
return render_template('views/cancelled-invitation.html',
from_user=invited_user.from_user.name,
service_name=service.name)
if invited_user.status == 'accepted':
session.pop('invited_user_id', None)
service = Service.from_id(invited_user.service)
if service.has_permission('broadcast'):
return redirect(url_for('main.broadcast_tour', service_id=service.id, step_index=1))
if service.live:
return redirect(url_for('main.broadcast_tour_live', service_id=service.id, step_index=1))
else:
return redirect(url_for('main.broadcast_tour', service_id=service.id, step_index=1))
return redirect(url_for('main.service_dashboard', service_id=invited_user.service))
session['invited_user_id'] = invited_user.id
@@ -71,7 +73,10 @@ def accept_invite(token):
invited_by_id=invited_user.from_user.id,
)
if service.has_permission('broadcast'):
return redirect(url_for('main.broadcast_tour', service_id=service.id, step_index=1))
if service.live:
return redirect(url_for('main.broadcast_tour_live', service_id=service.id, step_index=1))
else:
return redirect(url_for('main.broadcast_tour', service_id=service.id, step_index=1))
return redirect(url_for('main.service_dashboard', service_id=service.id))
else:
return redirect(url_for('main.register_from_invite'))
+4 -1
View File
@@ -80,7 +80,10 @@ def activate_user(user_id):
service_id = _add_invited_user_to_service(invited_user)
service = Service.from_id(service_id)
if service.has_permission('broadcast'):
return redirect(url_for('main.broadcast_tour', service_id=service.id, step_index=1))
if service.live:
return redirect(url_for('main.broadcast_tour_live', service_id=service.id, step_index=1))
else:
return redirect(url_for('main.broadcast_tour', service_id=service.id, step_index=1))
return redirect(url_for('main.service_dashboard', service_id=service_id))
invited_org_user = InvitedOrgUser.from_session()
+44
View File
@@ -0,0 +1,44 @@
{% macro navigation_service_name(service) %}
<div class="navigation-service-name govuk-!-font-weight-bold">
{{ service.name }}
{% if not service.active %}
<span class="navigation-service-type navigation-service-type--suspended">Suspended</span>
{% elif service.has_permission('broadcast') %}
{{ broadcast_service_name_tag(
service.trial_mode,
service.broadcast_channel,
service.allowed_broadcast_provider,
) }}
{% endif %}
</div>
{% endmacro %}
{% macro broadcast_service_name_tag(trial_mode, broadcast_channel, allowed_broadcast_provider, left_margin=True) %}
{% set margin_class = "" if left_margin else "govuk-!-margin-left-0" %}
{% if trial_mode %}
<span class="navigation-service-type navigation-service-type--training {{margin_class}}">Training
{% elif broadcast_channel == 'severe' %}
<span class="navigation-service-type navigation-service-type--live {{margin_class}}">Live
{% elif broadcast_channel == 'test' %}
<span class="navigation-service-type navigation-service-type--live {{margin_class}}">{{ broadcast_channel|title }}
{% if allowed_broadcast_provider == "all" %}
(all networks)
{% else %}
({{ allowed_broadcast_provider|format_mobile_network }})
{% endif %}
{% elif broadcast_channel == 'government' %}
<span class="navigation-service-type navigation-service-type--government {{margin_class}}">Government
{% endif %}
</span>
{% endmacro %}
<div class="navigation-service">
{% if current_service.organisation_id %}
{% if current_user.platform_admin or
(current_user.belongs_to_organisation(current_service.organisation_id) and current_service.live) %}
<a href="{{ url_for('.organisation_dashboard', org_id=current_service.organisation_id) }}" class="govuk-link govuk-link--no-visited-state navigation-organisation-link">{{ current_service.organisation_name }}</a>
{% endif %}
{% endif %}
{{ navigation_service_name(current_service) }}
<a href="{{ url_for('main.choose_account') }}" class="govuk-link govuk-link--no-visited-state navigation-service-switch">Switch service</a>
</div>
+2 -3
View File
@@ -1,4 +1,5 @@
{% from "components/banner.html" import banner_wrapper %}
{% from "service_navigation.html" import navigation_service_name %}
{% extends "admin_template.html" %}
@@ -11,9 +12,7 @@
{% block content %}
<div class="navigation-service">
<div class="navigation-service-name govuk-!-font-weight-bold">
{{ current_service.name }} <span class="navigation-service-type navigation-service-type--training">Training</span>
</div>
{{ navigation_service_name(current_service) }}
</div>
<div class="banner-tour banner-tour-no-fixed-height banner-tour-with-service-name">
+1 -5
View File
@@ -10,11 +10,7 @@
{% block content %}
<div class="navigation-service">
<div class="navigation-service-name govuk-!-font-weight-bold">
{{ current_service.name }} <span class="navigation-service-type navigation-service-type--training">Training</span>
</div>
</div>
{% include "service_navigation.html" %}
<div class="banner-tour banner-tour-no-fixed-height banner-tour-with-service-name">
<div class="govuk-grid-row">
@@ -0,0 +1,37 @@
{% from "components/banner.html" import banner_wrapper %}
{% extends "admin_template.html" %}
{% block per_page_title %}
Youve been invited to send live emergency alerts.
{% endblock %}
{% set mainClasses = "govuk-!-padding-top-0 govuk-!-padding-bottom-0" %}
{% block content %}
{% include "service_navigation.html" %}
<div class="banner-tour banner-tour-no-fixed-height banner-tour-with-service-name">
<div class="govuk-grid-row">
<div class="govuk-grid-column-one-half">
<h1 class="heading-medium">
Youve been invited to send live emergency alerts.
</h1>
<p class="govuk-body heading-medium">
Members of the public will receive alerts you send from this service.
</p>
<p class="govuk-body heading-medium">
<a class="govuk-link govuk-link--no-visited-state" href='{{ url_for(".broadcast_tour_live", service_id=current_service.id, step_index=2) }}'>
Continue
</a>
</p>
</div>
<div class="govuk-grid-column-one-half">
<img src="{{ asset_url('images/broadcast-tour/live/1.png') }}" alt="">
<link rel="preload" href="{{ asset_url('images/broadcast-tour/live/2.png') }}" as="image">
</div>
</div>
</div>
{% endblock %}
@@ -0,0 +1,33 @@
{% from "components/banner.html" import banner_wrapper %}
{% extends "admin_template.html" %}
{% block per_page_title %}
You can always go back to training mode by clicking Switch service in the top right.
{% endblock %}
{% set mainClasses = "govuk-!-padding-top-0 govuk-!-padding-bottom-0" %}
{% block content %}
{% include "service_navigation.html" %}
<div class="banner-tour banner-tour-no-fixed-height banner-tour-with-service-name">
<div class="govuk-grid-row">
<div class="govuk-grid-column-one-half">
<h1 class="heading-medium">
You can always go back to training mode by clicking Switch service in the top right.
</h1>
<p class="govuk-body heading-medium">
<a class="govuk-link govuk-link--no-visited-state" href='{{ url_for(".service_dashboard", service_id=current_service.id) }}'>
Continue
</a>
</p>
</div>
<div class="govuk-grid-column-one-half">
<img src="{{ asset_url('images/broadcast-tour/live/2.png') }}" alt="">
</div>
</div>
</div>
{% endblock %}
@@ -1,4 +1,5 @@
{% extends "withnav_template.html" %}
{% from "service_navigation.html" import broadcast_service_name_tag %}
{% from "components/page-header.html" import page_header %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}
@@ -15,30 +16,19 @@
'Confirm emergency alert settings',
back_link=url_for('.service_set_broadcast_channel', service_id=current_service.id)
) }}
<p class="govuk-body">
{{ broadcast_service_name_tag(
form.account_type.service_mode == 'training',
form.account_type.broadcast_channel,
form.account_type.provider_restriction,
left_margin=False,
) }}
</p>
{% if form.account_type.service_mode == 'training' %}
<p class="govuk-body">
<span class="navigation-service-type navigation-service-type--training govuk-!-margin-left-0">Training</span>
</p>
<p class="govuk-body">
No phones will receive alerts sent from this service.
</p>
{% else %}
<p class="govuk-body">
{% if form.account_type.broadcast_channel == 'severe' %}
<span class="navigation-service-type navigation-service-type--live govuk-!-margin-left-0">Live
{% elif form.account_type.broadcast_channel == 'government' %}
<span class="navigation-service-type navigation-service-type--government govuk-!-margin-left-0">Government
{% else %}
<span class="navigation-service-type navigation-service-type--live govuk-!-margin-left-0">{{ form.account_type.broadcast_channel|title }}
{% endif %}
{% if form.account_type.provider_restriction != 'all' %}
({{ form.account_type.provider_restriction|format_mobile_network }})
{% endif %}
{% if form.account_type.broadcast_channel == 'test' and form.account_type.provider_restriction == 'all'%}
(all networks)
{% endif %}
</span>
</p>
<p class="govuk-body">
Members of the public
{% if form.account_type.broadcast_channel == 'test' %}
+1 -31
View File
@@ -8,37 +8,7 @@
{% block main %}
<div class="govuk-width-container">
<div class="navigation-service">
{% if current_service.organisation_id %}
{% if current_user.platform_admin or
(current_user.belongs_to_organisation(current_service.organisation_id) and current_service.live) %}
<a href="{{ url_for('.organisation_dashboard', org_id=current_service.organisation_id) }}" class="govuk-link govuk-link--no-visited-state navigation-organisation-link">{{ current_service.organisation_name }}</a>
{% endif %}
{% endif %}
<div class="navigation-service-name govuk-!-font-weight-bold">
{{ current_service.name }}
{% if not current_service.active %}
<span class="navigation-service-type navigation-service-type--suspended">Suspended</span>
{% elif current_service.has_permission('broadcast') %}
{% if current_service.trial_mode %}
<span class="navigation-service-type navigation-service-type--training">Training
{% elif current_service.broadcast_channel == 'severe' %}
<span class="navigation-service-type navigation-service-type--live">Live
{% elif current_service.broadcast_channel == 'test' %}
<span class="navigation-service-type navigation-service-type--live">{{ current_service.broadcast_channel|title }}
{% if current_service.allowed_broadcast_provider == "all" %}
(all networks)
{% else %}
({{ current_service.allowed_broadcast_provider }})
{% endif %}
{% elif current_service.broadcast_channel == 'government' %}
<span class="navigation-service-type navigation-service-type--government">Government
{% endif %}
</span>
{% endif %}
</div>
<a href="{{ url_for('main.choose_account') }}" class="govuk-link govuk-link--no-visited-state navigation-service-switch">Switch service</a>
</div>
{% include "service_navigation.html" %}
<div class="govuk-grid-row govuk-!-padding-bottom-12">
{% if help %}
<div class="govuk-grid-column-one-third">
@@ -6,6 +6,7 @@ from flask import url_for
from notifications_python_client.errors import HTTPError
import app
from tests import service_json
from tests.conftest import (
SERVICE_ONE_ID,
USER_ONE_ID,
@@ -55,6 +56,44 @@ def test_existing_user_accept_invite_calls_api_and_redirects_to_dashboard(
)
@pytest.mark.parametrize('trial_mode, expected_endpoint', (
(True, '.broadcast_tour'),
(False, '.broadcast_tour_live'),
))
def test_broadcast_service_shows_tour(
client,
service_one,
mock_check_invite_token,
mock_get_unknown_user_by_email,
mock_get_users_by_service,
mock_accept_invite,
mock_add_user_to_service,
mocker,
mock_events,
mock_get_user,
trial_mode,
expected_endpoint,
):
service_one['permissions'] = ['broadcast']
service_one['restricted'] = trial_mode
mocker.patch('app.service_api_client.get_service', return_value={
'data': service_one,
})
response = client.get(url_for(
'main.accept_invite',
token='thisisnotarealtoken'
))
assert response.status_code == 302
assert response.location == url_for(
expected_endpoint,
service_id=SERVICE_ONE_ID,
step_index=1,
_external=True,
)
def test_existing_user_with_no_permissions_or_folder_permissions_accept_invite(
client,
mocker,
@@ -573,6 +612,54 @@ def test_new_invited_user_verifies_and_added_to_service(
assert page.find('h1').text == 'Dashboard'
@pytest.mark.parametrize('service_permissions, trial_mode, expected_endpoint, extra_args', (
([], True, 'main.service_dashboard', {}),
([], False, 'main.service_dashboard', {}),
(['broadcast'], True, 'main.broadcast_tour', {'step_index': 1}),
(['broadcast'], False, 'main.broadcast_tour_live', {'step_index': 1}),
))
def test_new_invited_user_is_redirected_to_correct_place(
mocker,
client,
sample_invite,
mock_check_invite_token,
mock_check_verify_code,
mock_get_user,
mock_dont_get_user_by_email,
mock_add_user_to_service,
mock_get_invited_user_by_id,
mock_events,
mock_get_service,
service_permissions,
trial_mode,
expected_endpoint,
extra_args,
):
mocker.patch('app.service_api_client.get_service', return_value={
'data': service_json(
sample_invite['service'],
restricted=trial_mode,
permissions=service_permissions,
)
})
client.get(url_for('main.accept_invite', token='thisisnotarealtoken'))
with client.session_transaction() as session:
session['user_details'] = {
'email': sample_invite['email_address'],
'id': sample_invite['id'],
}
response = client.post(url_for('main.verify'), data={'sms_code': '12345'})
assert response.status_code == 302
assert response.location == url_for(
expected_endpoint,
service_id=sample_invite['service'],
_external=True,
**extra_args
)
def test_existing_user_accepts_and_sets_email_auth(
client_request,
api_user_active,
+36 -22
View File
@@ -236,24 +236,27 @@ def test_cancel_broadcast_page_403_for_user_without_permission(
)
@pytest.mark.parametrize('step_index, expected_link_text, expected_link_href', (
(1, 'Continue', partial(url_for, '.broadcast_tour', step_index=2)),
(2, 'Continue', partial(url_for, '.broadcast_tour', step_index=3)),
(3, 'Continue', partial(url_for, '.broadcast_tour', step_index=4)),
(4, 'Continue', partial(url_for, '.broadcast_tour', step_index=5)),
(5, 'Continue', partial(url_for, '.service_dashboard')),
(6, 'Continue', partial(url_for, '.service_dashboard')),
@pytest.mark.parametrize('endpoint, step_index, expected_link_text, expected_link_href', (
('.broadcast_tour', 1, 'Continue', partial(url_for, '.broadcast_tour', step_index=2)),
('.broadcast_tour', 2, 'Continue', partial(url_for, '.broadcast_tour', step_index=3)),
('.broadcast_tour', 3, 'Continue', partial(url_for, '.broadcast_tour', step_index=4)),
('.broadcast_tour', 4, 'Continue', partial(url_for, '.broadcast_tour', step_index=5)),
('.broadcast_tour', 5, 'Continue', partial(url_for, '.service_dashboard')),
('.broadcast_tour', 6, 'Continue', partial(url_for, '.service_dashboard')),
('.broadcast_tour_live', 1, 'Continue', partial(url_for, '.broadcast_tour_live', step_index=2)),
('.broadcast_tour_live', 2, 'Continue', partial(url_for, '.service_dashboard')),
))
def test_broadcast_tour_pages_have_continue_link(
client_request,
service_one,
endpoint,
step_index,
expected_link_text,
expected_link_href,
):
service_one['permissions'] += ['broadcast']
page = client_request.get(
'.broadcast_tour',
endpoint,
service_id=SERVICE_ONE_ID,
step_index=step_index,
)
@@ -262,26 +265,31 @@ def test_broadcast_tour_pages_have_continue_link(
assert link['href'] == expected_link_href(service_id=SERVICE_ONE_ID)
@pytest.mark.parametrize('step_index', (
pytest.param(1, marks=pytest.mark.xfail),
pytest.param(2, marks=pytest.mark.xfail),
pytest.param(3, marks=pytest.mark.xfail),
pytest.param(4, marks=pytest.mark.xfail),
5,
6,
@pytest.mark.parametrize('endpoint, step_index', (
pytest.param('.broadcast_tour', 1, marks=pytest.mark.xfail),
pytest.param('.broadcast_tour', 2, marks=pytest.mark.xfail),
pytest.param('.broadcast_tour', 3, marks=pytest.mark.xfail),
pytest.param('.broadcast_tour', 4, marks=pytest.mark.xfail),
('.broadcast_tour', 5),
('.broadcast_tour', 6),
('.broadcast_tour_live', 1),
('.broadcast_tour_live', 2),
))
def test_broadcast_tour_page_4_shows_service_name(
def test_some_broadcast_tour_pages_show_service_name(
client_request,
service_one,
endpoint,
step_index,
):
service_one['permissions'] += ['broadcast']
page = client_request.get(
'.broadcast_tour',
endpoint,
service_id=SERVICE_ONE_ID,
step_index=step_index,
)
assert normalize_spaces(page.select_one('.navigation-service').text) == (
assert normalize_spaces(
page.select_one('.navigation-service').text
).startswith(
'service one Training'
)
@@ -326,8 +334,8 @@ def test_broadcast_tour_page_4_shows_service_name(
'test',
'vodafone',
'.navigation-service-type.navigation-service-type--live',
'service one Test (vodafone) Switch service',
'Test (vodafone)',
'service one Test (Vodafone) Switch service',
'Test (Vodafone)',
),
(
False,
@@ -371,15 +379,21 @@ def test_broadcast_service_shows_live_or_training(
)
@pytest.mark.parametrize('step_index', (0, 7))
@pytest.mark.parametrize('endpoint, step_index', (
('.broadcast_tour', 0),
('.broadcast_tour', 7),
('.broadcast_tour_live', 0),
('.broadcast_tour_live', 3),
))
def test_broadcast_tour_page_404s_out_of_range(
client_request,
service_one,
endpoint,
step_index,
):
service_one['permissions'] += ['broadcast']
client_request.get(
'.broadcast_tour',
endpoint,
service_id=SERVICE_ONE_ID,
step_index=step_index,
_expected_status=404,
+1
View File
@@ -39,6 +39,7 @@ EXCLUDED_ENDPOINTS = tuple(map(Navigation.get_endpoint_with_blueprint, {
'broadcast_dashboard_rejected',
'broadcast_dashboard_updates',
'broadcast_tour',
'broadcast_tour_live',
'callbacks',
'cancel_broadcast_message',
'cancel_invited_org_user',