Merge branch 'master' into email-registered-users

This commit is contained in:
Rebecca Law
2016-07-12 11:05:16 +01:00
23 changed files with 405 additions and 141 deletions

View File

@@ -12,7 +12,9 @@ from flask import (
make_response,
current_app,
request,
g)
g,
url_for
)
from flask._compat import string_types
from flask.globals import _lookup_req_object
from flask_login import LoginManager
@@ -112,6 +114,7 @@ def create_app():
application.add_template_filter(format_date_short)
application.add_template_filter(format_notification_status)
application.add_template_filter(format_notification_status_as_field_status)
application.add_template_filter(format_notification_status_as_url)
application.after_request(useful_headers_after_request)
application.after_request(save_service_after_request)
@@ -235,16 +238,16 @@ def format_notification_status(status, template_type):
'email': {
'failed': 'Failed',
'technical-failure': 'Technical failure',
'temporary-failure': 'Temporary failure',
'permanent-failure': 'Email address does not exist',
'temporary-failure': 'Inbox not accepting messages right now',
'permanent-failure': 'Email address doesnt exist',
'delivered': 'Delivered',
'sending': 'Sending'
},
'sms': {
'failed': 'Failed',
'technical-failure': 'Technical failure',
'temporary-failure': 'Temporary failure',
'permanent-failure': 'Phone number does not exist',
'temporary-failure': 'Phone not accepting messages right now',
'permanent-failure': 'Phone number doesnt exist',
'delivered': 'Delivered',
'sending': 'Sending'
}
@@ -262,6 +265,15 @@ def format_notification_status_as_field_status(status):
}.get(status, 'error')
def format_notification_status_as_url(status):
url = partial(url_for, "main.delivery_and_failure")
return {
'technical-failure': url(_anchor='technical-failure'),
'temporary-failure': url(_anchor='not-accepting-messages'),
'permanent-failure': url(_anchor='does-not-exist')
}.get(status)
@login_manager.user_loader
def load_user(user_id):
return user_api_client.get_user(user_id)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,46 @@
%tick-cross {
@include core-16;
display: inline-block;
background-size: 19px 19px;
background-repeat: no-repeat;
background-position: 0 0;
padding: 1px 0 0 25px;
}
.tick-cross {
&-tick {
@extend %tick-cross;
background-image: file-url('tick.png');
}
&-cross {
@extend %tick-cross;
background-image: file-url('cross-grey.png');
color: $secondary-text-colour;
}
&-list {
@extend %grid-row;
margin-top: 5px;
&-permissions {
@include grid-column(3/4);
li {
display: inline-block;
margin-right: 0.5em;
}
}
&-edit-link {
@include grid-column(1/4);
text-align: right;
}
}
}

View File

@@ -54,11 +54,13 @@ $path: '/static/images/';
@import 'components/message';
@import 'components/phone';
@import 'components/research-mode';
@import 'components/tick-cross';
@import 'views/job';
@import 'views/edit-template';
@import 'views/documenation';
@import 'views/dashboard';
@import 'views/users';
// TODO: break this up
@import 'app';

View File

@@ -0,0 +1,16 @@
.user-list {
@include core-16;
&-item {
padding: $gutter-half 0;
border-top: 1px solid $border-colour;
&:last-child {
border-bottom: 1px solid $border-colour;
}
}
}

View File

@@ -28,6 +28,7 @@ from app.utils import (
user_has_permissions,
generate_notifications_csv)
from app.statistics_utils import sum_of_statistics, statistics_by_state, add_rate_to_jobs
from app.utils import get_help_argument
def _parse_filter_args(filter_dict):
@@ -98,7 +99,8 @@ def view_job(service_id, job_id):
job_id=job['id'],
status=request.args.get('status', '')
),
partials=get_job_partials(job)
partials=get_job_partials(job),
help=get_help_argument()
)
@@ -311,7 +313,8 @@ def get_job_partials(job):
notifications=notification_api_client.get_notifications_for_service(
job['service'], job['id'], status=filter_args.get('status')
)['notifications'],
status=request.args.get('status', '')
status=request.args.get('status', ''),
help=get_help_argument()
),
'status': render_template(
'partials/jobs/status.html',

View File

@@ -29,7 +29,7 @@ from app.main.uploader import (
s3download
)
from app import job_api_client, service_api_client, current_service, user_api_client, statistics_api_client
from app.utils import user_has_permissions, get_errors_for_csv, Spreadsheet
from app.utils import user_has_permissions, get_errors_for_csv, Spreadsheet, get_help_argument
def get_page_headings(template_type):
@@ -181,7 +181,8 @@ def send_test(service_id, template_id):
'views/send-test.html',
template=template,
recipient_column=first_column_heading[template.template_type],
example=[get_example_csv_rows(template, use_example_as_example=False)]
example=[get_example_csv_rows(template, use_example_as_example=False)],
help=get_help_argument()
)
@@ -278,7 +279,8 @@ def check_messages(service_id, template_type, upload_id):
upload_id=upload_id,
form=CsvUploadForm(),
statistics=statistics,
back_link=back_link
back_link=back_link,
help=get_help_argument()
)

View File

@@ -0,0 +1,15 @@
{% macro tick_cross(yes, label) %}
<li>
{% if yes %}
<span class="tick-cross-tick">
<span class="visually-hidden">Can</span>
{{ label}}
</span>
{% else %}
<span class="tick-cross-cross">
<span class="visually-hidden">Cant</span>
{{ label}}
</span>
{% endif %}
</li>
{% endmacro %}

View File

@@ -1,9 +1,9 @@
{% from "components/banner.html" import banner_wrapper %}
{% if request.args['help'] and request.args['help'] != '0' %}
{% if help %}
{% call banner_wrapper(type='tour') %}
<p class="heading-medium">Try this example</p>
<div class="grid-row bottom-gutter" {% if request.args['help'] != '1' %}style="opacity: 0.6"{% endif %}>
<div class="grid-row bottom-gutter" {% if help != '1' %}style="opacity: 0.6"{% endif %}>
<div class="column-one-sixth">
<p class="heading-large" style="float: left;">1.</p>
</div>
@@ -13,7 +13,7 @@
</p>
</div>
</div>
<div class="grid-row bottom-gutter" {% if request.args['help'] != '2' %}style="opacity: 0.6"{% endif %}>
<div class="grid-row bottom-gutter" {% if help != '2' %}style="opacity: 0.6"{% endif %}>
<div class="column-one-sixth">
<p class="heading-large">2.</p>
</div>
@@ -23,7 +23,7 @@
</p>
</div>
</div>
<div class="grid-row bottom-gutter" {% if request.args['help'] != '3' %}style="opacity: 0.6"{% endif %}>
<div class="grid-row bottom-gutter" {% if help != '3' %}style="opacity: 0.6"{% endif %}>
<div class="column-one-sixth">
<p class="heading-large">3.</p>
</div>
@@ -31,7 +31,7 @@
<p>
Notify delivers the message
</p>
{% if request.args['help'] == '3' %}
{% if help == '3' %}
<a href='{{ url_for(".go_to_dashboard_after_tour", service_id=current_service.id, example_template_id=template.id) }}'>
Now go to your dashboard
</a>

View File

@@ -4,7 +4,7 @@
<div class="dashboard-table">
{% endif %}
{% if notifications %}
{% if notifications and not help %}
<p class="bottom-gutter">
<a href="{{ url_for('.view_job_csv', service_id=current_service.id, job_id=job.id, status=status) }}" download="download" class="heading-small">Download as a CSV file</a>
&emsp;
@@ -34,7 +34,13 @@
align='right',
status=item.status|format_notification_status_as_field_status
) %}
{% if item.status|format_notification_status_as_url %}
<a href="{{ item.status|format_notification_status_as_url }}">
{% endif %}
{{ item.status|format_notification_status(item.template.template_type) }}
{% if item.status|format_notification_status_as_url %}
</a>
{% endif %}
{% endcall %}
{% endcall %}

View File

@@ -174,7 +174,7 @@
{% else %}
<form method="post" enctype="multipart/form-data" action="{{url_for('main.start_job', service_id=current_service.id, upload_id=upload_id)}}" class='page-footer'>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<input type="hidden" name="help" value="{{ '3' if request.args['help'] == '2' else 0 }}" />
<input type="hidden" name="help" value="{{ '3' if help else 0 }}" />
<input type="submit" class="button" value="Send {{ count_of_recipients }} {{ message_count_label(count_of_recipients, template.template_type, suffix='') }}" />
<a href="{{ back_link }}" class="page-footer-back-link">Back</a>
</form>

View File

@@ -16,18 +16,17 @@ Delivery and failure GOV.UK Notify
<ul class="list list-bullet">
<li><a href="#sending">Sending</a></li>
<li><a href="#delivered">Delivered</a></li>
<li><a href="#permanent-failure">Permanently failed</a></li>
<li><a href="#temporary-failure">Temporarily failed</a></li>
<li><a href="#does-not-exist">Phone number or email address does not exist</a></li>
<li><a href="#inbox-not-accepting-messages">Inbox not accepting messages right now</a></li>
<li><a href="#phone-not-accepting-messages">Phone not accepting messages right now</a></li>
<li><a href="#technical-failure">Technical failure</a></li>
</ul>
<p>Our delivery states are the same for both email and text message.</p>
<h2 id="sending" class="heading-medium">Sending</h2>
<p>All new messages start with the state Sending.</p>
<p>All messages start in the Sending state.</p>
<p>This means that we have accepted the message, the message is waiting in a queue to be sent to our email or text message delivery partners.</p>
<p>This means that we have accepted the message. Its waiting in a queue to be sent to our email or text message delivery partners.</p>
<h2 id="delivered" class="heading-medium">Delivered</h2>
@@ -35,23 +34,27 @@ Delivery and failure GOV.UK Notify
<p>We cant tell you if theyve read it to do so would require invasive and unreliable tracking techniques.</p>
<h2 id="permanent-failure" class="heading-medium">Permanently failed</h2>
<h2 id="does-not-exist" class="heading-medium">Phone number or email address does not exist</h2>
<p>This means the email address or mobile number doesnt exist or is blacklisted also known as a hard bounce.</p>
<p>Youre still billed for text messages to non-existant phone numbers.</p>
<p>Youre still billed for these text messages.</p>
<p><strong>You need to remove these email addresses or phone numbers from your database.</strong></p>
<p><strong>You need to remove this email address or mobile number from your database.</strong></p>
<a id="not-accepting-messages"></a>
<h2 id="temporary-failure" class="heading-medium">Temporarily failed</h2>
<h2 id="inbox-not-accepting-messages" class="heading-medium">Inbox not accepting messages right now</h2>
<p>This means the email address or mobile number was full, or the mobile phone was switched off  also known as a soft bounce.</p>
<p>This can happen for a number of reasons, eg the users inbox was full.</p>
<p>We mark messages as Temporarily failed.</p>
<p><strong>You can choose to retry these messages later or not.</strong></p>
<h2 id="phone-not-accepting-messages" class="heading-medium">Phone not accepting messages right now</h2>
<p>This means the users phone was full or hasnt been switched on in the last 72 hours.</p>
<p>Youre still billed for these messages.</p>
<p><strong>You can choose to retry this message later or not.</strong></p>
<p><strong>You can choose to retry these messages later or not.</strong></p>
<h2 id="technical-failure" class="heading-medium">Technical failure</h2>
@@ -61,7 +64,7 @@ Delivery and failure GOV.UK Notify
<p>You wont be billed for these messages.</p>
<p><strong>You need to retry this message yourself later.</strong></p>
<p><strong>You need to retry these messages yourself later.</strong></p>
</div>
</div>

View File

@@ -1,6 +1,7 @@
{% extends "withnav_template.html" %}
{% from "components/table.html" import list_table, row, field, boolean_field, hidden_field_heading %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/tick-cross.html" import tick_cross %}
{% set table_options = {
'field_headings': [
@@ -29,49 +30,84 @@ Manage users GOV.UK Notify
{% endif %}
</div>
{% call(item, row_number) list_table(
users, caption='Active', **table_options
) %}
{% call field() %}
{{ item.name }}
{%- if item.email_address == current_user.email_address -%}
&ensp;<span class="hint">(you)</span>
{% endif %}
{% endcall %}
{{ boolean_field(item.has_permissions(permissions=['send_texts', 'send_emails', 'send_letters'])) }}
{{ boolean_field(item.has_permissions(permissions=['manage_users', 'manage_templates', 'manage_settings'])) }}
{{ boolean_field(item.has_permissions(permissions=['manage_api_keys'])) }}
{% call field(align='right') %}
{% if current_user.has_permissions(['manage_users']) %}
{% if current_user.id != item.id %}
<a href="{{ url_for('.edit_user_permissions', service_id=current_service.id, user_id=item.id)}}">Edit</a>
{% endif %}
{% endif %}
{% endcall %}
{% endcall %}
<h2 class="heading-medium">
Active
</h2>
<div class="user-list">
{% for user in users %}
<div class="user-list-item">
<h3>
{{ user.name }}&ensp;<span class="hint">
{%- if user.email_address == current_user.email_address -%}
(you)
{% else %}
{{ user.email_address }}
{% endif %}
</span>
</h3>
<ul class="tick-cross-list">
<div class="tick-cross-list-permissions">
{{ tick_cross(
user.has_permissions(permissions=['send_texts', 'send_emails', 'send_letters']),
'Send messages'
) }}
{{ tick_cross(
user.has_permissions(permissions=['manage_users', 'manage_templates', 'manage_settings']),
'Manage service'
) }}
{{ tick_cross(
user.has_permissions(permissions=['manage_api_keys']),
'Access API keys'
) }}
</div>
{% if current_user.has_permissions(['manage_users']) %}
{% if current_user.id != user.id %}
<li class="tick-cross-list-edit-link">
<a href="{{ url_for('.edit_user_permissions', service_id=current_service.id, user_id=user.id)}}">Edit permissions</a>
</li>
{% endif %}
{% endif %}
</ul>
</div>
{% endfor %}
</div>
{% if invited_users %}
{% call(item, row_number) list_table(
invited_users, caption='Invited', **table_options
) %}
{% call field() %}
{{ item.email_address }}
{% endcall %}
{{ boolean_field(item.has_permissions(permissions=['send_texts', 'send_emails', 'send_letters'])) }}
{{ boolean_field(item.has_permissions(permissions=['manage_users', 'manage_templates', 'manage_settings'])) }}
{{ boolean_field(item.has_permissions(permissions=['manage_api_keys'])) }}
{% if item.status == 'pending' %}
{% call field(align='right') %}
{% if current_user.has_permissions(['manage_users']) %}
<a href="{{ url_for('.cancel_invited_user', service_id=current_service.id, invited_user_id=item.id)}}">Cancel invitation</a>
{% endif %}
{% endcall %}
{% else %}
{% call field() %}
{{ item.status }}
{% endcall %}
{% endif %}
{% endcall %}
<h2 class="heading-medium">
Invited
</h2>
<div class="user-list">
{% for user in invited_users %}
<div class="user-list-item">
<h3>
{{ user.email_address }}
</h3>
<ul class="tick-cross-list">
<div class="tick-cross-list-permissions">
{{ tick_cross(
user.has_permissions(permissions=['send_texts', 'send_emails', 'send_letters']),
'Send messages'
) }}
{{ tick_cross(
user.has_permissions(permissions=['manage_users', 'manage_templates', 'manage_settings']),
'Manage service'
) }}
{{ tick_cross(
user.has_permissions(permissions=['manage_api_keys']),
'Access API keys'
) }}
</div>
<li class="tick-cross-list-edit-link">
{% if user.status == 'pending' and current_user.has_permissions(['manage_users']) %}
<a href="{{ url_for('.cancel_invited_user', service_id=current_service.id, invited_user_id=user.id)}}">Cancel invitation</a>
{% else %}
{{ user.status|title }}
{% endif %}
</li>
</ul>
</div>
{% endfor %}
</div>
{% endif %}
{% endblock %}

View File

@@ -73,7 +73,13 @@
) }}
{% call field(status=item.status|format_notification_status_as_field_status, align='right') %}
{% if item.status|format_notification_status_as_url %}
<a href="{{ item.status|format_notification_status_as_url }}">
{% endif %}
{{ item.status|format_notification_status(item.template.template_type) }}
{% if item.status|format_notification_status_as_url %}
</a>
{% endif %}
{% endcall %}
{% endcall %}

View File

@@ -55,7 +55,7 @@
{% endcall %}
{{ page_footer("Preview", back_link=(
url_for('.choose_template', service_id=current_service.id, template_type=template.template_type)) if not request.args['help'] else None
url_for('.send_messages', service_id=current_service.id, template_id=template.id)) if not help else None
) }}
</form>

View File

@@ -89,7 +89,7 @@ def get_errors_for_csv(recipients, template_type):
def generate_notifications_csv(json_list):
from app import format_datetime
from app import format_datetime, format_notification_status
content = StringIO()
retval = None
with content as csvfile:
@@ -102,7 +102,7 @@ def generate_notifications_csv(json_list):
x['template']['name'],
x['template']['template_type'],
x['job']['original_file_name'] if x['job'] else '',
x['status'],
format_notification_status(x['status'], x['template']['template_type']),
format_datetime(x['created_at'])])
retval = content.getvalue()
return retval
@@ -186,3 +186,7 @@ class Spreadsheet():
file_type=extension,
file_content=file_content.getvalue()
).to_array(), filename)
def get_help_argument():
return request.args.get('help') if request.args.get('help') in ('1', '2', '3') else None