Merge branch 'master' into reinstate-new-rate-api

Conflicts:
	app/main/views/dashboard.py
This commit is contained in:
Martyn Inglis
2017-06-07 14:44:27 +01:00
44 changed files with 1079 additions and 186 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -26,6 +26,19 @@
@include grid-column(7/8);
}
%top-gutter,
.top-gutter {
@extend %contain-floats;
display: block;
margin-top: $gutter;
clear: both;
}
.top-gutter-4-3 {
@extend %top-gutter;
margin-top: $gutter * 4 / 3;
}
%bottom-gutter,
.bottom-gutter {
@extend %contain-floats;

View File

@@ -129,3 +129,26 @@
}
}
.big-number-meta-wrapper {
position: relative;
margin: $gutter-half 0 $gutter 0;
background: $govuk-blue;
.big-number-meta {
padding: ($gutter / 3) $gutter-half;
color: $white;
pointer-events: none;
@include media(desktop) {
position: absolute;
bottom: 7px;
right: 5px;
text-align: right;
}
}
}

View File

@@ -59,3 +59,21 @@
}
}
.align-button-with-textbox {
.button {
@include media(desktop) {
position: relative;
top: 32px;
left: -30px;
width: 100%;
margin-right: -30px;
padding-top: 8px;
box-sizing: content-box;
}
}
}

View File

@@ -1,6 +1,8 @@
%sms-message-wrapper,
$tail-angle: 20deg;
.sms-message-wrapper {
position: relative;
width: 100%;
max-width: 464px;
box-sizing: border-box;
@@ -13,21 +15,20 @@
clear: both;
word-wrap: break-word;
p {
margin: 0;
line-height: 1.6;
&:after {
content: "";
display: block;
position: absolute;
bottom: -4px;
right: -20px;
border: 10px solid transparent;
border-left-width: 13px;
border-right-width: 13px;
border-bottom-color: $panel-colour;
border-left-color: $panel-colour;
transform: rotate($tail-angle);
}
p + p {
margin-top: 20px;
}
}
.sms-message-wrapper-with-radio {
@extend %sms-message-wrapper;
padding-left: 45px;
cursor: pointer;
}
.sms-message-recipient {
@@ -35,22 +36,3 @@
color: $secondary-text-colour;
margin: 10px 0 0 0;
}
.sms-message-name {
@include bold-24;
margin: 20px 0 5px 0;
}
.sms-message-picker {
display: block;
margin: 7px 0 0 0;
position: absolute;
left: 15px;
top: 50%;
z-index: 50;
}
.sms-message-from {
@include bold-19;
display: block;
}

View File

@@ -74,6 +74,12 @@
}
&-invisible-error {
border-left: 5px solid transparent;
padding-left: 7px;
display: block;
}
&-status {
&-default {
@@ -123,6 +129,7 @@
}
&-index {
@include bold-16;
width: 15px;
}
@@ -132,6 +139,10 @@
}
.table-font-xsmall td.table-field-index { // overrides GOV.UK Elements
@include bold-16;
}
.table-field-headings,
.table-field-headings-visible {
@@ -199,3 +210,11 @@ a.table-show-more-link {
border-bottom: 1px solid $border-colour;
padding: 0.75em 0 0.5625em 0;
}
.wide-left-hand-column {
display: block;
max-width: 560px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

View File

@@ -94,3 +94,7 @@
}
}
.align-with-message-body {
margin-top: $gutter * 5 / 6;
}

View File

@@ -57,9 +57,7 @@ class Config(object):
CSV_UPLOAD_BUCKET_NAME = 'local-notifications-csv-upload'
DESKPRO_PERSON_EMAIL = 'donotreply@notifications.service.gov.uk'
ACTIVITY_STATS_LIMIT_DAYS = 7
TEST_MESSAGE_FILENAME = 'Test message'
SMS_FREE_TIER_AMOUNT = 250000
TEST_MESSAGE_FILENAME = 'Report'
STATSD_ENABLED = False
STATSD_HOST = "statsd.hostedgraphite.com"
@@ -83,6 +81,8 @@ class Config(object):
r"hmcts\.net",
r"scotent\.co\.uk",
r"assembly\.wales",
r"cjsm\.net",
r"cqc\.org\.uk",
]
@@ -100,6 +100,7 @@ class Test(Development):
WTF_CSRF_ENABLED = False
CSV_UPLOAD_BUCKET_NAME = 'test-notifications-csv-upload'
NOTIFY_ENVIRONMENT = 'test'
TEMPLATE_PREVIEW_API_HOST = 'http://localhost:9999'
class Preview(Config):

View File

@@ -7,6 +7,7 @@ from notifications_utils.recipients import (
validate_phone_number,
InvalidPhoneError
)
from notifications_utils.columns import Columns
from wtforms import (
validators,
StringField,
@@ -102,11 +103,26 @@ class UKMobileNumber(TelField):
raise ValidationError(str(e))
def mobile_number():
return UKMobileNumber('Mobile number',
class InternationalPhoneNumber(TelField):
def pre_validate(self, form):
try:
validate_phone_number(self.data, international=True)
except InvalidPhoneError as e:
raise ValidationError(str(e))
def mobile_number(label='Mobile number'):
return UKMobileNumber(label,
validators=[DataRequired(message='Cant be empty')])
def international_phone_number(label='Mobile number'):
return InternationalPhoneNumber(
label,
validators=[DataRequired(message='Cant be empty')]
)
def password(label='Password'):
return PasswordField(label,
validators=[DataRequired(message='Cant be empty'),
@@ -502,7 +518,11 @@ class ServiceSmsSender(Form):
class ServiceLetterContactBlock(Form):
letter_contact_block = TextAreaField()
letter_contact_block = TextAreaField(
validators=[
NoCommasInPlaceHolders()
]
)
def validate_letter_contact_block(form, field):
line_count = field.data.strip().count('\n')
@@ -621,6 +641,11 @@ class SearchTemplatesForm(Form):
search = SearchField('Search by name')
class SearchNotificationsForm(Form):
to = SearchField('Search by phone number or email address')
class PlaceholderForm(Form):
pass
@@ -629,15 +654,25 @@ class PlaceholderForm(Form):
def get_placeholder_form_instance(
placeholder_name,
dict_to_populate_from,
optional_placeholder=False
optional_placeholder=False,
allow_international_phone_numbers=False,
):
PlaceholderForm.placeholder_value = StringField(
placeholder_name,
validators=[
if Columns.make_key(placeholder_name) == 'emailaddress':
field = email_address(label=placeholder_name, gov_user=False)
elif Columns.make_key(placeholder_name) == 'phonenumber':
if allow_international_phone_numbers:
field = international_phone_number(label=placeholder_name)
else:
field = mobile_number(label=placeholder_name)
elif optional_placeholder:
field = StringField(placeholder_name)
else:
field = StringField(placeholder_name, validators=[
DataRequired(message='Cant be empty')
] if not optional_placeholder else []
)
])
PlaceholderForm.placeholder_value = field
return PlaceholderForm(
placeholder_value=dict_to_populate_from.get(placeholder_name, '')

View File

@@ -2,7 +2,6 @@ from datetime import datetime
from functools import partial
from flask import (
render_template,
current_app,
url_for,
session,
jsonify,
@@ -13,6 +12,7 @@ from flask_login import login_required
from app.main import main
from app import (
current_service,
job_api_client,
service_api_client,
template_statistics_client
@@ -136,6 +136,20 @@ def monthly(service_id):
)
@main.route("/services/<service_id>/inbox")
@login_required
@user_has_permissions('manage_settings', admin_override=True)
def inbox(service_id):
if 'inbound_sms' not in current_service['permissions']:
abort(403)
return render_template(
'views/dashboard/inbox.html',
messages=service_api_client.get_inbound_sms(service_id),
)
def aggregate_usage(template_statistics, sort_key='count'):
return sorted(
template_statistics,
@@ -167,6 +181,13 @@ def get_dashboard_partials(service_id):
'views/dashboard/_upcoming.html',
scheduled_jobs=scheduled_jobs
),
'inbox': render_template(
'views/dashboard/_inbox.html',
inbound_sms_summary=(
service_api_client.get_inbound_sms_summary(service_id)
if 'inbound_sms' in current_service['permissions'] else None
),
),
'totals': render_template(
'views/dashboard/_totals.html',
service_id=service_id,
@@ -213,7 +234,8 @@ def calculate_free_tier_usage(usage, service):
def calculate_usage(usage):
sms_free_allowance = current_app.config['SMS_FREE_TIER_AMOUNT']
# TODO: Don't hardcode these - get em from the API
sms_free_allowance = 250000
sms_rate = 0 if len(usage) == 0 else usage[0].get("rate", 0)
sms_sent = get_sum_billing_units(breakdown for breakdown in usage if breakdown['notification_type'] == 'sms')

View File

@@ -8,6 +8,11 @@ from app.main.forms import SupportType, Feedback, Problem, Triage
from datetime import datetime
@main.route('/feedback', methods=['GET'])
def old_feedback():
return redirect(url_for('.support'))
@main.route('/support', methods=['GET', 'POST'])
def support():
form = SupportType()

View File

@@ -26,6 +26,7 @@ from app import (
current_service,
format_datetime_short)
from app.main import main
from app.main.forms import SearchNotificationsForm
from app.utils import (
get_page_from_request,
generate_next_dict,
@@ -197,8 +198,10 @@ def view_notifications(service_id, message_type):
'views/notifications.html',
partials=get_notifications(service_id, message_type),
message_type=message_type,
status=request.args.get('status'),
page=request.args.get('page', 1)
status=request.args.get('status') or 'sending,delivered,failed',
page=request.args.get('page', 1),
to=request.args.get('to'),
search_form=SearchNotificationsForm(to=request.args.get('to')),
)
@@ -241,7 +244,9 @@ def get_notifications(service_id, message_type, status_override=None):
page=page,
template_type=[message_type],
status=filter_args.get('status'),
limit_days=current_app.config['ACTIVITY_STATS_LIMIT_DAYS'])
limit_days=current_app.config['ACTIVITY_STATS_LIMIT_DAYS'],
to=request.args.get('to'),
)
url_args = {
'message_type': message_type,
@@ -249,11 +254,11 @@ def get_notifications(service_id, message_type, status_override=None):
}
prev_page = None
if notifications['links'].get('prev', None):
if 'links' in notifications and notifications['links'].get('prev', None):
prev_page = generate_previous_dict('main.view_notifications', service_id, page, url_args=url_args)
next_page = None
if notifications['links'].get('next', None):
if 'links' in notifications and notifications['links'].get('next', None):
next_page = generate_next_dict('main.view_notifications', service_id, page, url_args)
return {

View File

@@ -157,14 +157,18 @@ def get_example_csv(service_id, template_id):
}
@main.route("/services/<service_id>/send/<template_id>/test")
@main.route("/services/<service_id>/send/<template_id>/test", endpoint='send_test')
@main.route("/services/<service_id>/send/<template_id>/one-off", endpoint='send_one_off')
@login_required
@user_has_permissions('send_texts', 'send_emails', 'send_letters')
def send_test(service_id, template_id):
session['send_test_values'] = dict()
session['send_test_letter_page_count'] = None
return redirect(url_for(
'.send_test_step',
{
'main.send_test': '.send_test_step',
'main.send_one_off': '.send_one_off_step',
}[request.endpoint],
service_id=service_id,
template_id=template_id,
step_index=0,
@@ -172,14 +176,28 @@ def send_test(service_id, template_id):
))
@main.route("/services/<service_id>/send/<template_id>/test/step-<int:step_index>", methods=['GET', 'POST'])
@main.route(
"/services/<service_id>/send/<template_id>/test/step-<int:step_index>",
methods=['GET', 'POST'],
endpoint='send_test_step',
)
@main.route(
"/services/<service_id>/send/<template_id>/one-off/step-<int:step_index>",
methods=['GET', 'POST'],
endpoint='send_one_off_step',
)
@login_required
@user_has_permissions('send_texts', 'send_emails', 'send_letters')
def send_test_step(service_id, template_id, step_index):
if 'send_test_values' not in session:
return redirect(url_for(
'.send_test', service_id=service_id, template_id=template_id
{
'main.send_test_step': '.send_test',
'main.send_one_off_step': '.send_one_off',
}[request.endpoint],
service_id=service_id,
template_id=template_id,
))
template = service_api_client.get_service_template(service_id, template_id)['data']
@@ -201,7 +219,10 @@ def send_test_step(service_id, template_id, step_index):
page_count=session['send_test_letter_page_count']
)
placeholders = fields_to_fill_in(template)
placeholders = fields_to_fill_in(
template,
prefill_current_user=(request.endpoint == 'main.send_test_step'),
)
if len(placeholders) == 0:
return make_and_upload_csv_file(service_id, template)
@@ -209,28 +230,33 @@ def send_test_step(service_id, template_id, step_index):
try:
current_placeholder = placeholders[step_index]
except IndexError:
if all_placeholders_in_session(placeholders):
return make_and_upload_csv_file(service_id, template)
return redirect(url_for(
'.send_test', service_id=service_id, template_id=template_id
{
'main.send_test_step': '.send_test',
'main.send_one_off_step': '.send_one_off',
}[request.endpoint],
service_id=service_id,
template_id=template_id,
))
optional_placeholder = (current_placeholder in optional_address_columns)
form = get_placeholder_form_instance(
current_placeholder,
dict_to_populate_from=get_normalised_send_test_values_from_session(),
optional_placeholder=optional_placeholder,
allow_international_phone_numbers=current_service['can_send_international_sms'],
)
if form.validate_on_submit():
session['send_test_values'][current_placeholder] = form.placeholder_value.data
if all(
get_normalised_send_test_values_from_session().get(placeholder, False) not in (False, None)
for placeholder in placeholders
):
if all_placeholders_in_session(placeholders):
return make_and_upload_csv_file(service_id, template)
return redirect(url_for(
'.send_test_step',
request.endpoint,
service_id=service_id,
template_id=template_id,
step_index=step_index + 1,
@@ -247,7 +273,7 @@ def send_test_step(service_id, template_id, step_index):
)
else:
back_link = url_for(
'.send_test_step',
request.endpoint,
service_id=service_id,
template_id=template_id,
step_index=step_index - 1,
@@ -256,13 +282,27 @@ def send_test_step(service_id, template_id, step_index):
template.values = get_normalised_send_test_values_from_session()
template.values[current_placeholder] = None
if (
request.endpoint == 'main.send_one_off_step' and
step_index == 0 and
template.template_type != 'letter'
):
skip_link = (
'Use my {}'.format(first_column_headings[template.template_type][0]),
url_for('.send_test', service_id=service_id, template_id=template.id),
)
else:
skip_link = None
return render_template(
'views/send-test.html',
page_title=get_send_test_page_title(template.template_type, get_help_argument()),
template=template,
form=form,
skip_link=skip_link,
optional_placeholder=optional_placeholder,
help=get_help_argument(),
back_link=back_link,
help=get_help_argument(),
)
@@ -470,11 +510,11 @@ def get_check_messages_back_url(service_id, template_type):
return url_for('main.choose_template', service_id=service_id)
def fields_to_fill_in(template):
def fields_to_fill_in(template, prefill_current_user=False):
recipient_columns = first_column_headings[template.template_type]
if 'letter' == template.template_type:
if 'letter' == template.template_type or not prefill_current_user:
return recipient_columns + list(template.placeholders)
session['send_test_values'][recipient_columns[0]] = {
@@ -513,3 +553,18 @@ def make_and_upload_csv_file(service_id, template):
from_test=True,
help=2 if get_help_argument() else 0
))
def all_placeholders_in_session(placeholders):
return all(
get_normalised_send_test_values_from_session().get(placeholder, False) not in (False, None)
for placeholder in placeholders
)
def get_send_test_page_title(template_type, help_argument):
if help_argument:
return 'Example text message'
if template_type == 'letter':
return 'Print a test letter'
return 'Send to one recipient'

View File

@@ -14,6 +14,8 @@ from flask_login import (
login_required,
current_user
)
from notifications_utils.field import Field
from notifications_python_client.errors import HTTPError
from app import service_api_client
@@ -46,7 +48,9 @@ def service_settings(service_id):
organisation=organisation,
letter_branding=letter_branding_organisations.get(
current_service.get('dvla_organisation', '001')
)
),
can_receive_inbound=('inbound_sms' in current_service['permissions']),
letter_contact_block=Field(current_service['letter_contact_block'], html='escape')
)
@@ -264,14 +268,27 @@ def service_set_reply_to_email(service_id):
@user_has_permissions('manage_settings', admin_override=True)
def service_set_sms_sender(service_id):
form = ServiceSmsSender()
if form.validate_on_submit():
set_inbound_sms = request.args.get('set_inbound_sms', False)
if set_inbound_sms == 'True':
permissions = current_service['permissions']
if 'inbound_sms' in permissions:
permissions.remove('inbound_sms')
else:
permissions.append('inbound_sms')
service_api_client.update_service_with_properties(
current_service['id'],
{'permissions': permissions,
'sms_sender': form.sms_sender.data or None}
)
else:
service_api_client.update_service(
current_service['id'],
sms_sender=form.sms_sender.data or None
)
return redirect(url_for('.service_settings', service_id=service_id))
if request.method == 'GET':
form.sms_sender.data = current_service.get('sms_sender')
if form.validate_on_submit():
service_api_client.update_service(
current_service['id'],
sms_sender=form.sms_sender.data or None
)
return redirect(url_for('.service_settings', service_id=service_id))
return render_template(
'views/service-settings/set-sms-sender.html',
form=form)

View File

@@ -21,7 +21,8 @@ class NotificationApiClient(NotifyAdminAPIClient):
limit_days=None,
include_jobs=None,
include_from_test_key=None,
format_for_csv=None
format_for_csv=None,
to=None,
):
params = {}
if page is not None:
@@ -38,6 +39,8 @@ class NotificationApiClient(NotifyAdminAPIClient):
params['include_from_test_key'] = include_from_test_key
if format_for_csv is not None:
params['format_for_csv'] = format_for_csv
if to is not None:
params['to'] = to
if job_id:
return self.get(
url='/service/{}/job/{}/notifications'.format(service_id, job_id),

View File

@@ -1,4 +1,5 @@
from __future__ import unicode_literals
from flask import url_for
from app.utils import BrowsableItem
from app.notify_client import _attach_current_user, NotifyAdminAPIClient
@@ -94,6 +95,7 @@ class ServiceAPIClient(NotifyAdminAPIClient):
'organisation',
'letter_contact_block',
'dvla_organisation',
'permissions'
}
if disallowed_attributes:
raise TypeError('Not allowed to update service attributes: {}'.format(
@@ -242,6 +244,16 @@ class ServiceAPIClient(NotifyAdminAPIClient):
params=dict(year=year)
)
def get_inbound_sms(self, service_id):
return self.get(
'/service/{}/inbound-sms'.format(service_id)
)['data']
def get_inbound_sms_summary(self, service_id):
return self.get(
'/service/{}/inbound-sms/summary'.format(service_id)
)
class ServicesBrowsableItem(BrowsableItem):
@property

View File

@@ -31,22 +31,25 @@
failure_percentage,
danger_zone=False,
failure_link=None,
link=None
link=None,
show_failures=True
) %}
<div class="big-number-with-status">
{{ big_number(number, label, link=link) }}
<div class="big-number-status{% if danger_zone %}-failing{% endif %}">
{% if failures %}
{% if failure_link %}
<a href="{{ failure_link }}">
{% if show_failures %}
<div class="big-number-status{% if danger_zone %}-failing{% endif %}">
{% if failures %}
{% if failure_link %}
<a href="{{ failure_link }}">
{{ "{:,}".format(failures) }} failed {{ failure_percentage }}%
</a>
{% else %}
{{ "{:,}".format(failures) }} failed {{ failure_percentage }}%
</a>
{% endif %}
{% else %}
{{ "{:,}".format(failures) }} failed {{ failure_percentage }}%
No failures
{% endif %}
{% else %}
No failures
{% endif %}
</div>
</div>
{% endif %}
</div>
{% endmacro %}

View File

@@ -73,9 +73,9 @@
</th>
{%- endmacro %}
{% macro index_field(text) -%}
{% macro index_field(text=None) -%}
<td class="table-field-index">
<span>{{ text }}</span>
{{ text if text != None else caller() }}
</td>
{%- endmacro %}

View File

@@ -21,7 +21,10 @@
{{ item.to }}
</p>
<p class="hint">
{% if item.job %}
{% if item.job and item.job.original_file_name == 'Report' %}
<a href="{{ url_for('.view_template_version', service_id=current_service.id, template_id=item.template.id, version=item.template_version) }}">{{ item.template.name }}</a>
sent to one recipient
{% elif item.job %}
From <a href="{{ url_for(".view_job", service_id=current_service.id, job_id=item.job.id) }}">{{ item.job.original_file_name }}</a>
{% else %}
<a href="{{ url_for('.view_template_version', service_id=current_service.id, template_id=item.template.id, version=item.template_version) }}">{{ item.template.name }}</a>

View File

@@ -150,7 +150,9 @@
{% endif %}
{{ template|string }}
{% if not errors %}
{{ template|string }}
{% endif %}
<div class="bottom-gutter-3-2">
{% if errors %}
@@ -188,14 +190,18 @@
caption=original_file_name,
caption_visible=False,
field_headings=[
'<span class="visually-hidden">Row in file</span><span aria-hidden="true">1</span>'|safe
'<span class="visually-hidden">Row in file</span><span aria-hidden="true" class="{}">1</span>'.format("table-field-invisible-error" if errors else "")|safe
] + recipients.column_headers
) %}
{{ index_field(item.index + 2) }}
{% call index_field() %}
<span class="{% if item.index in recipients.rows_with_errors %}table-field-error{% endif %}">
{{ item.index + 2 }}
</span>
{% endcall %}
{% for column in recipients.column_headers %}
{% if item['columns'][column].error and not recipients.missing_column_headers %}
{% call field() %}
<span class="table-field-error">
<span>
<span class="table-field-error-label">{{ item['columns'][column].error }}</span>
{{ item['columns'][column].data if item['columns'][column].data != None }}
</span>
@@ -234,4 +240,9 @@
</p>
{% endif %}
{% if errors %}
<h2 class="heading-medium">Preview of {{ template.name }}</h2>
{{ template|string }}
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,21 @@
{% from "components/big-number.html" import big_number, big_number_with_status %}
<div class="ajax-block">
{% if inbound_sms_summary != None %}
<div class="big-number-meta-wrapper">
{{
big_number_with_status(
inbound_sms_summary.count,
'text messages received',
link=url_for('.inbox', service_id=current_service.id),
show_failures=False
)
}}
<div class="big-number-meta">
{% if inbound_sms_summary.most_recent %}
latest message {{ inbound_sms_summary.most_recent | format_delta }}
{% endif %}
</div>
</div>
{% endif %}
</div>

View File

@@ -6,7 +6,7 @@
<div id="total-email" class="column-half">
{{ big_number_with_status(
statistics['email']['requested'],
message_count_label(statistics['email']['requested'], 'email', suffix=''),
message_count_label(statistics['email']['requested'], 'email', suffix='sent'),
statistics['email']['failed'],
statistics['email']['failed_percentage'],
statistics['email']['show_warning'],
@@ -17,7 +17,7 @@
<div id="total-sms" class="column-half">
{{ big_number_with_status(
statistics['sms']['requested'],
message_count_label(statistics['sms']['requested'], 'sms', suffix=''),
message_count_label(statistics['sms']['requested'], 'sms', suffix='sent'),
statistics['sms']['failed'],
statistics['sms']['failed_percentage'],
statistics['sms']['show_warning'],

View File

@@ -29,6 +29,8 @@
In the last 7 days
</h2>
{{ ajax_block(partials, updates_url, 'inbox') }}
{{ ajax_block(partials, updates_url, 'totals') }}
{{ show_more(
url_for('.monthly', service_id=current_service.id),

View File

@@ -0,0 +1,43 @@
{% from "components/table.html" import list_table, field, hidden_field_heading, right_aligned_field_heading, row_heading %}
{% from "components/message-count-label.html" import message_count_label %}
{% extends "withnav_template.html" %}
{% block service_page_title %}
Inbox
{% endblock %}
{% block maincolumn_content %}
<h1 class="heading-large">
Received text messages
</h1>
<div>
{% call(item, row_number) list_table(
messages,
caption="Inbox",
caption_visible=False,
empty_message='When users text your services phone number ({}) youll see the messages here'.format(current_service.sms_sender),
field_headings=[
'From',
'First two lines of message'
],
field_headings_visible=False
) %}
{% call field() %}
<span class="file-list-filename" href="#">{{ item.user_number }}</span>
<span class="wide-left-hand-column">{{ item.content }}</span>
{% endcall %}
{% call field(align='right') %}
<span class="file-list-hint align-with-message-body">
{{ item.created_at | format_delta }}
</span>
{% endcall %}
{% endcall %}
{% if messages %}
<p class="table-show-more-link">
Showing all {{ messages | length }} messages
</p>
{% endif %}
</div>
{% endblock %}

View File

@@ -1,6 +1,8 @@
{% extends "withnav_template.html" %}
{% from "components/ajax-block.html" import ajax_block %}
{% from "components/message-count-label.html" import message_count_label, recipient_count_label %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/textbox.html" import textbox %}
{% block service_page_title %}
{{ message_count_label(99, message_type, suffix='') | capitalize }}
@@ -18,9 +20,27 @@
'counts'
) }}
<form
method="get"
action="{{ url_for('.view_notifications', service_id=current_service.id, message_type=message_type) }}"
class="grid-row"
>
<div class="column-three-quarters">
<input type="hidden" name="status" value="{{ status }}">
{{ textbox(
search_form.to,
width='1-1',
label='Search by {}'.format('email address' if message_type == 'email' else 'phone number')
) }}
</div>
<div class="column-one-quarter align-button-with-textbox">
<input type="submit" class="button" value="Search">
</div>
</form>
{{ ajax_block(
partials,
url_for('.get_notifications_as_json', service_id=current_service.id, message_type=message_type, status=status, page=page),
url_for('.get_notifications_as_json', service_id=current_service.id, message_type=message_type, status=status, page=page, to=to),
'notifications'
) }}

View File

@@ -5,32 +5,30 @@
{% from "components/table.html" import list_table, field, text_field, index_field, index_field_heading %}
{% block service_page_title %}
{% if request.args['help'] %}
Example text message
{% else %}
Send yourself a test
{% endif %}
{{ page_title }}
{% endblock %}
{% block maincolumn_content %}
<h1 class="heading-large">
{% if request.args['help'] %}
Example text message
{% else %}
{% if template.template_type == 'letter' %}
Print a test letter
{% else %}
Send yourself a test
{% endif %}
{% endif %}
{{ page_title }}
</h1>
<form method="post" class="js-stick-at-top-when-scrolling" data-module="autofocus">
{{ textbox(
form.placeholder_value,
hint='Optional' if optional_placeholder else None
) }}
<div class="grid-row">
<div class="column-two-thirds">
{{ textbox(
form.placeholder_value,
hint='Optional' if optional_placeholder else None,
width='1-1',
) }}
</div>
{% if skip_link %}
<div class="column-one-third">
<a href="{{ skip_link[1] }}" class="top-gutter-4-3">{{ skip_link[0] }}</a>
</div>
{% endif %}
</div>
{{ page_footer('Next', back_link=back_link) }}
</form>

View File

@@ -36,8 +36,13 @@
{% call row() %}
{{ text_field('Text message sender') }}
{{ text_field(current_service.sms_sender or 'GOVUK') }}
{{ edit_field('Change', url_for('.service_set_sms_sender', service_id=current_service.id)) }}
{{ text_field(current_service.sms_sender) }}
{% if current_user.has_permissions([], admin_override=True) or not can_receive_inbound %}
{{ edit_field('Change', url_for('.service_set_sms_sender', service_id=current_service.id, set_inbound_sms=False)) }}
{% else %}
{{ text_field('') }}
{% endif %}
{% endcall %}
{% call row() %}
@@ -56,7 +61,7 @@
{% call row() %}
{{ text_field('Letter contact details') }}
{% call field(status='' if current_service.letter_contact_block else 'default') %}
{{ current_service.letter_contact_block | escape | nl2br | safe }}
{{ letter_contact_block | string | nl2br | safe if current_service.letter_contact_block else 'None'}}
{% endcall %}
{{ edit_field('Change', url_for('.service_set_letter_contact_block', service_id=current_service.id)) }}
{% endcall %}
@@ -162,6 +167,11 @@
</a>
</li>
{% endif %}
<li class="bottom-gutter">
<a href="{{ url_for('.service_set_sms_sender', service_id=current_service.id, set_inbound_sms=True) }}" class="button">
{{ 'Stop inbound sms' if can_receive_inbound else 'Allow inbound sms' }}
</a>
</li>
</ul>
{% endif %}

View File

@@ -18,7 +18,8 @@
label='How should users contact your service?<br>This applies to all the letters you send.'|safe,
hint='10 lines maximum',
width='1-1',
rows=10
rows=10,
highlight_tags=True
) }}
{{ page_footer(
'Save',

View File

@@ -117,7 +117,7 @@
<div class="grid-row bottom-gutter">
<div class="column-half">
<h3 class="visually-hidden">Services</h3>
<div class="product-page-big-number">51</div>
<div class="product-page-big-number">54</div>
services
</div>
<div class="column-half">

View File

@@ -13,8 +13,8 @@
</a>
</div>
<div class="{{ 'column-half' if template.template_type == 'letter' else 'column-third' }}">
<a href="{{ url_for(".send_test", service_id=current_service.id, template_id=template.id) }}" class="pill-separate-item">
{{ 'Print a test letter' if template.template_type == 'letter' else 'Send yourself a test' }}
<a href="{{ url_for(".send_one_off", service_id=current_service.id, template_id=template.id) }}" class="pill-separate-item">
{{ 'Print a test letter' if template.template_type == 'letter' else 'Send to one recipient' }}
</a>
</div>
{% endif %}