Add a version of the tour for live services

At the moment if you’re invited to a live broadcast service you get the
training mode tour. This is misleading, and could make people think they
weren’t in danger of sending a real alert.

This commit adds a short, 2 step tour for users invited to a live
broadcast service.
This commit is contained in:
Chris Hill-Scott
2021-05-13 14:17:28 +01:00
parent af4bdf4620
commit 766df5d1ca
13 changed files with 2836 additions and 2583 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

View File

@@ -137,7 +137,7 @@
&:hover { &:hover {
color: $white; color: $white;
background-color: $link-hover-colour; 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, &:focus,

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') @main.route('/services/<uuid:service_id>/current-alerts')
@user_has_permissions() @user_has_permissions()
@service_has_permission('broadcast') @service_has_permission('broadcast')

View File

@@ -37,12 +37,14 @@ def accept_invite(token):
return render_template('views/cancelled-invitation.html', return render_template('views/cancelled-invitation.html',
from_user=invited_user.from_user.name, from_user=invited_user.from_user.name,
service_name=service.name) service_name=service.name)
if invited_user.status == 'accepted': if invited_user.status == 'accepted':
session.pop('invited_user_id', None) session.pop('invited_user_id', None)
service = Service.from_id(invited_user.service) service = Service.from_id(invited_user.service)
if service.has_permission('broadcast'): 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)) return redirect(url_for('main.service_dashboard', service_id=invited_user.service))
session['invited_user_id'] = invited_user.id session['invited_user_id'] = invited_user.id
@@ -71,7 +73,10 @@ def accept_invite(token):
invited_by_id=invited_user.from_user.id, invited_by_id=invited_user.from_user.id,
) )
if service.has_permission('broadcast'): 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)) return redirect(url_for('main.service_dashboard', service_id=service.id))
else: else:
return redirect(url_for('main.register_from_invite')) return redirect(url_for('main.register_from_invite'))

View File

@@ -0,0 +1,31 @@
<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>

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -8,37 +8,7 @@
{% block main %} {% block main %}
<div class="govuk-width-container"> <div class="govuk-width-container">
<div class="navigation-service"> {% include "service_navigation.html" %}
{% 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>
<div class="govuk-grid-row govuk-!-padding-bottom-12"> <div class="govuk-grid-row govuk-!-padding-bottom-12">
{% if help %} {% if help %}
<div class="govuk-grid-column-one-third"> <div class="govuk-grid-column-one-third">

View File

@@ -55,6 +55,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( def test_existing_user_with_no_permissions_or_folder_permissions_accept_invite(
client, client,
mocker, mocker,

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', ( @pytest.mark.parametrize('endpoint, step_index, expected_link_text, expected_link_href', (
(1, 'Continue', partial(url_for, '.broadcast_tour', step_index=2)), ('.broadcast_tour', 1, 'Continue', partial(url_for, '.broadcast_tour', step_index=2)),
(2, 'Continue', partial(url_for, '.broadcast_tour', step_index=3)), ('.broadcast_tour', 2, 'Continue', partial(url_for, '.broadcast_tour', step_index=3)),
(3, 'Continue', partial(url_for, '.broadcast_tour', step_index=4)), ('.broadcast_tour', 3, 'Continue', partial(url_for, '.broadcast_tour', step_index=4)),
(4, 'Continue', partial(url_for, '.broadcast_tour', step_index=5)), ('.broadcast_tour', 4, 'Continue', partial(url_for, '.broadcast_tour', step_index=5)),
(5, 'Continue', partial(url_for, '.service_dashboard')), ('.broadcast_tour', 5, 'Continue', partial(url_for, '.service_dashboard')),
(6, '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( def test_broadcast_tour_pages_have_continue_link(
client_request, client_request,
service_one, service_one,
endpoint,
step_index, step_index,
expected_link_text, expected_link_text,
expected_link_href, expected_link_href,
): ):
service_one['permissions'] += ['broadcast'] service_one['permissions'] += ['broadcast']
page = client_request.get( page = client_request.get(
'.broadcast_tour', endpoint,
service_id=SERVICE_ONE_ID, service_id=SERVICE_ONE_ID,
step_index=step_index, step_index=step_index,
) )
@@ -286,6 +289,28 @@ def test_broadcast_tour_page_4_shows_service_name(
) )
@pytest.mark.parametrize('step_index', (
1,
2,
))
def test_live_broadcast_tour_shows_service_name_and_switch(
client_request,
service_one,
step_index,
):
service_one['permissions'] += ['broadcast']
service_one['restricted'] = False
service_one['allowed_broadcast_provider'] = 'all'
page = client_request.get(
'.broadcast_tour_live',
service_id=SERVICE_ONE_ID,
step_index=step_index,
)
assert normalize_spaces(page.select_one('.navigation-service').text) == (
'service one Live Switch service'
)
@pytest.mark.parametrize( @pytest.mark.parametrize(
'trial_mode, channel, allowed_broadcast_provider, selector, expected_text, expected_tagged_text', 'trial_mode, channel, allowed_broadcast_provider, selector, expected_text, expected_tagged_text',
( (
@@ -371,15 +396,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( def test_broadcast_tour_page_404s_out_of_range(
client_request, client_request,
service_one, service_one,
endpoint,
step_index, step_index,
): ):
service_one['permissions'] += ['broadcast'] service_one['permissions'] += ['broadcast']
client_request.get( client_request.get(
'.broadcast_tour', endpoint,
service_id=SERVICE_ONE_ID, service_id=SERVICE_ONE_ID,
step_index=step_index, step_index=step_index,
_expected_status=404, _expected_status=404,

View File

@@ -39,6 +39,7 @@ EXCLUDED_ENDPOINTS = tuple(map(Navigation.get_endpoint_with_blueprint, {
'broadcast_dashboard_rejected', 'broadcast_dashboard_rejected',
'broadcast_dashboard_updates', 'broadcast_dashboard_updates',
'broadcast_tour', 'broadcast_tour',
'broadcast_tour_live',
'callbacks', 'callbacks',
'cancel_broadcast_message', 'cancel_broadcast_message',
'cancel_invited_org_user', 'cancel_invited_org_user',