Show a tour when users first create a service

This commit adds a 3 screen tour, similar to those used on GOV.UK Verify
and Passports.

We guerilla tested this on Friday, and it really helped users to build a
mental model of how Notify works, so that when they’re playing around
with it they have a greater sense of what they’re aiming to do. This
makes concepts like templates and placeholders click more quickly.

https://www.pivotaltracker.com/story/show/116710119
This commit is contained in:
Chris Hill-Scott
2016-04-01 10:54:55 +01:00
parent 5f9605d605
commit 5d873bdc45
18 changed files with 246 additions and 69 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 983 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

View File

@@ -127,4 +127,4 @@ a[rel="external"] {
@include media(tablet) {
@include external-link-19;
}
}
}

View File

@@ -71,7 +71,7 @@
a {
&:link,
&:visited {
color: $text-colour;
padding: $gutter-half;
text-decoration: underline;
}
}
@@ -110,13 +110,54 @@
background: $govuk-blue;
color: $white;
margin-top: $gutter;
padding: $gutter-half;
.heading-medium {
p {
margin: 0 0 10px 0;
}
&-action {
text-align: right;
}
}
.banner-tour {
@extend %banner;
background: $govuk-blue;
color: $white;
margin-top: $gutter;
padding: $gutter * 2;
.heading-large {
margin: 0 0 $gutter 0;
}
p {
margin-bottom: $gutter;
&:last-child {
margin-bottom: 0
}
& + p {
margin-top: -$gutter-half;
}
}
a {
@include bold-24;
display: inline-block;
background-image: file-url('tour-next.png');
background-size: auto 24px;
padding: 0 23px 0 0;
background-position: right 3px;
background-repeat: no-repeat;
&:link,
&:visited {
color: $white;
@@ -124,19 +165,25 @@
&:hover,
&:active {
color: $light-blue-25;
background-color: $link-hover-colour;
outline: 10px solid $link-hover-colour;
}
&:active,
&:focus {
background-color: $yellow;
outline: 10px solid $yellow;
}
}
.big-number {
margin-top: 10px;
&-label {
padding-bottom: 0;
}
img {
max-width: 100%;
display: block;
}
&-image-flush-bottom {
margin: 40px 0 -60px 0;
}
}

View File

@@ -23,5 +23,6 @@ from app.main.views import (
api_keys,
manage_users,
invites,
all_services
all_services,
tour
)

View File

@@ -44,7 +44,7 @@ def add_service():
user_id=session['user_id'],
email_from=email_from)
return redirect(url_for('main.service_dashboard', service_id=service_id))
return redirect(url_for('main.tour', service_id=service_id, page=1))
else:
return render_template(
'views/add-service.html',

15
app/main/views/tour.py Normal file
View File

@@ -0,0 +1,15 @@
from flask import render_template
from flask_login import login_required
from app.main import main
@main.route("/services/<service_id>/tour/<int:page>")
@login_required
def tour(service_id, page):
return render_template(
'views/tour/{}.html'.format(page),
service_id=service_id, # TODO: fix when Nicks PR is merged
current_page=page,
next_page=(page + 1)
)

View File

@@ -5,26 +5,26 @@
{% endblock %}
{% block maincolumn_content %}
{% if not templates and current_user.has_permissions(['send_texts', 'send_emails', 'send_letters'], any_=True) %}
{% include 'views/dashboard/get-started.html' %}
{% elif service.restricted %}
<div class="dashboard">
{% include 'views/dashboard/trial-mode-banner.html' %}
</div>
{% endif %}
{% if service.restricted %}
{% include 'views/dashboard/trial-mode-banner.html' %}
{% endif %}
{% if not templates and current_user.has_permissions(['send_texts', 'send_emails', 'send_letters'], any_=True) %}
{% include 'views/dashboard/get-started.html' %}
{% endif %}
{% if templates %}
<div
data-module="update-content"
data-resource="{{url_for(".service_dashboard_updates", service_id=service_id)}}"
data-key="today"
data-interval-seconds="10"
>
{% include 'views/dashboard/today.html' %}
</div>
<div
data-module="update-content"
data-resource="{{url_for(".service_dashboard_updates", service_id=service_id)}}"
data-key="today"
data-interval-seconds="10"
>
{% include 'views/dashboard/today.html' %}
</div>
{% include 'views/dashboard/jobs.html' %}
{% endif %}
{% include 'views/dashboard/jobs.html' %}
{% endblock %}

View File

@@ -1,30 +1,23 @@
{% from "components/banner.html" import banner_wrapper %}
<h2 class="heading-medium">Get started</h2>
{% if current_user.has_permissions(['manage_templates']) %}
<p>
You need to set up a template before you can send messages
</p>
<ol class="grid-row">
<li class="column-half">
{% call banner_wrapper(type="tip") %}
<a href='{{ url_for(".add_service_template", service_id=service_id, template_type="sms") }}' style="white-space: nowrap;">
Set up a text message template
</a>
{% endcall %}
</li>
<li class="column-half">
{% call banner_wrapper(type="tip") %}
<a href='{{ url_for(".add_service_template", service_id=service_id, template_type="email") }}'>
Set up an email template
</a>
{% endcall %}
</li>
</ol>
{% call banner_wrapper(type='tour') %}
<h2 class="heading-large">Youre ready to get started</h2>
<p>
<a href='{{ url_for(".add_service_template", service_id=service_id, template_type="sms") }}'>
Set up a text message template
</a>
</p>
<p>
<a href='{{ url_for(".add_service_template", service_id=service_id, template_type="email") }}'>
Set up an email template
</a>
</p>
{% endcall %}
{% else %}
<p>
{% call banner_wrapper(type='mode') %}
<p>
You need to ask your service manager to set up some templates before you can send messages
</p>
</p>
{% endcall %}
{% endif %}

View File

@@ -2,24 +2,12 @@
{% from "components/big-number.html" import big_number %}
{% call banner_wrapper(type="mode") %}
<h2 class="heading-medium">Trial mode</h2>
<div class="grid-row">
<div class="column-one-half">
<p>
Well only deliver messages to you and members of your team
<br>
<a href="{{ url_for(".help", _anchor="trial-mode") }}">Find out more</a>
</p>
Your service is in trial mode
</div>
<div class="column-one-sixth">
&nbsp;
</div>
<div class="column-one-third">
{{ big_number(
service.limit - statistics.get('emails_requested', 0) - statistics.get('sms_requested', 0),
'messages left today'
) }}
<div class="column-one-half">
<a href="{{ url_for(".help", _anchor="trial-mode") }}" class="banner-mode-action">Find out more</a>
</div>
</div>
{% endcall %}

View File

@@ -0,0 +1,25 @@
{% extends "withoutnav_template.html" %}
{% from "components/textbox.html" import textbox %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/banner.html" import banner_wrapper %}
{% block page_title %}
{{heading}} GOV.UK Notify
{% endblock %}
{% block maincolumn_content %}
{% call banner_wrapper(type='tour') %}
<h2 class="heading-large">Trial mode</h2>
<p>
To start off with, you can only send messages to yourself.
</p>
<p>
We can remove these restrictions when youre ready.
</p>
<a href='{{ url_for('.tour', service_id=service_id, page=next_page) }}'>
Next
</a>
{% endcall %}
{% endblock %}

View File

@@ -0,0 +1,29 @@
{% extends "withoutnav_template.html" %}
{% from "components/textbox.html" import textbox %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/banner.html" import banner_wrapper %}
{% block page_title %}
{{heading}} GOV.UK Notify
{% endblock %}
{% block maincolumn_content %}
{% call banner_wrapper(type='tour') %}
<h2 class="heading-large">Start with templates</h2>
<p>
Set up a template like this:
</p>
<p>
<img
src="/static/images/tour/{{ current_page }}.png"
width="554" height="97"
alt="A template for a text message with placeholders for the recipients name, document and date"
>
</p>
<a href='{{ url_for('.tour', service_id=service_id, page=next_page) }}'>
Next
</a>
{% endcall %}
{% endblock %}

View File

@@ -0,0 +1,32 @@
{% extends "withoutnav_template.html" %}
{% from "components/textbox.html" import textbox %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/banner.html" import banner_wrapper %}
{% block page_title %}
{{heading}} GOV.UK Notify
{% endblock %}
{% block maincolumn_content %}
{% call banner_wrapper(type='tour') %}
<h2 class="heading-large">Add recipients</h2>
<p>
Add recipients by uploading a .csv spreadsheet:
</p>
<p>
<img
src="/static/images/tour/{{ current_page }}.png"
width="554" height="118"
alt="A screenshot of a spreadsheet containing data about three people"
>
</p>
<p>
Developers, you can add data automatically using an API
</p>
<a href='{{ url_for('.tour', service_id=service_id, page=next_page) }}'>
Next
</a>
{% endcall %}
{% endblock %}

View File

@@ -0,0 +1,28 @@
{% extends "withoutnav_template.html" %}
{% from "components/textbox.html" import textbox %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/banner.html" import banner_wrapper %}
{% block page_title %}
{{heading}} GOV.UK Notify
{% endblock %}
{% block maincolumn_content %}
{% call banner_wrapper(type='tour') %}
<h2 class="heading-large">Send your messages</h2>
<p>
Notify merges your data with the template and sends the messages
</p>
<a href="{{ url_for('.service_dashboard', service_id=service_id) }}">
Next
</a>
<img
src="/static/images/tour/{{ current_page }}.png"
class="banner-tour-image-flush-bottom"
width="840" height="290"
alt="Three mobiles phones, each showing a text message personalised with data about the recipient"
>
{% endcall %}
{% endblock %}

View File

@@ -26,7 +26,7 @@ def test_should_add_service_and_redirect_to_next_page(app_,
url_for('main.add_service'),
data={'name': 'testing the post'})
assert response.status_code == 302
assert response.location == url_for('main.service_dashboard', service_id=101, _external=True)
assert response.location == url_for('main.tour', service_id=101, page=1, _external=True)
assert mock_get_services.called
mock_create_service.asset_called_once_with(service_name='testing the post',
active=False,

View File

@@ -0,0 +1,19 @@
import pytest
from flask import url_for
import app
@pytest.mark.parametrize("page", range(1, 5))
def test_should_render_tour_pages(
app_,
api_user_active,
mocker,
page
):
with app_.test_request_context():
with app_.test_client() as client:
client.login(api_user_active, mocker)
response = client.get(url_for('main.tour', service_id=101, page=page))
assert response.status_code == 200
assert 'Next' in response.get_data(as_text=True)