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

@@ -71,7 +71,7 @@
a { a {
&:link, &:link,
&:visited { &:visited {
color: $text-colour; padding: $gutter-half;
text-decoration: underline; text-decoration: underline;
} }
} }
@@ -110,13 +110,54 @@
background: $govuk-blue; background: $govuk-blue;
color: $white; color: $white;
margin-top: $gutter; margin-top: $gutter;
padding: $gutter-half;
.heading-medium { p {
margin: 0 0 10px 0; 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 { 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, &:link,
&:visited { &:visited {
color: $white; color: $white;
@@ -124,19 +165,25 @@
&:hover, &:hover,
&:active { &: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 { img {
max-width: 100%;
margin-top: 10px; display: block;
}
&-label {
padding-bottom: 0;
}
&-image-flush-bottom {
margin: 40px 0 -60px 0;
} }
} }

View File

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

View File

@@ -44,7 +44,7 @@ def add_service():
user_id=session['user_id'], user_id=session['user_id'],
email_from=email_from) 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: else:
return render_template( return render_template(
'views/add-service.html', '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

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

View File

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

View File

@@ -2,24 +2,12 @@
{% from "components/big-number.html" import big_number %} {% from "components/big-number.html" import big_number %}
{% call banner_wrapper(type="mode") %} {% call banner_wrapper(type="mode") %}
<h2 class="heading-medium">Trial mode</h2>
<div class="grid-row"> <div class="grid-row">
<div class="column-one-half"> <div class="column-one-half">
<p> Your service is in trial mode
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>
</div> </div>
<div class="column-one-sixth"> <div class="column-one-half">
&nbsp; <a href="{{ url_for(".help", _anchor="trial-mode") }}" class="banner-mode-action">Find out more</a>
</div>
<div class="column-one-third">
{{ big_number(
service.limit - statistics.get('emails_requested', 0) - statistics.get('sms_requested', 0),
'messages left today'
) }}
</div> </div>
</div> </div>
{% endcall %} {% 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'), url_for('main.add_service'),
data={'name': 'testing the post'}) data={'name': 'testing the post'})
assert response.status_code == 302 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 assert mock_get_services.called
mock_create_service.asset_called_once_with(service_name='testing the post', mock_create_service.asset_called_once_with(service_name='testing the post',
active=False, 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)