mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-04-16 23:31:10 -04:00
Merge with new master.
This commit is contained in:
21
.travis.yml
21
.travis.yml
@@ -41,6 +41,27 @@ deploy:
|
||||
deployment_group: notifications_admin_deployment_group
|
||||
region: eu-west-1
|
||||
on: *2
|
||||
- provider: s3
|
||||
access_key_id: AKIAJ5MKF6G3P2JQP4QQ
|
||||
secret_access_key: &1
|
||||
secure: xfjg4kNBvU0B9xhRETr14mB0bCpVonlAKqGnKL2AoqpnF19yihqGNA8sv/pOGUFpeWZO3cW2GA3anyL2gGG1X0K3f81649mneVJkSHaZ2fiG/S1eKtS0Ws5XblSqLmKTPC7H9ndUxT8r+r2wLg62netBE5g8tAxw2QwN/gVz2fK/68owiyeD/jl6gw/iQ47F+mmGdAY/eFe8sUuGR4Oxj2xNAYARaDQOmHpQF/IG3M69FO5uOJtck8fUWnpd0rWxsyWBOVwwIRQHL6cWOyodeIK7YvLmzviCi1GojBPKQwQbjJu89LHfMJJTW1625drj5CNounuENTFte0Pip8zp90bg090VA8OlTXNWcyFjBQD1vNIE59vyQ/hCh90NK1nlTXdnNOwL0VZTMxQ/zYulXoMqwDLfDozhQbmnkXmexJl6BF4/dz+XmwDu7st5A/PI6U5zCK86ST/6g3MklGSseFi5Rkt6kmJrdlRIhiLnoaab8YgI0FPWjzHBC6B98ZtgIUiUk7Ng5ZTM8Sjq1HCC7mUDrDL0c7aerZA5bq2hQiKGhvjBXFU17iHZ0eEDZ8kO+jumeMwmpW6NbjlS0PuDx+lHywMSG7r+YVmkjxq5gwrTVl3evRxhHe8H/lU18y2dOKpIyX5UEZpXRq9kAWuQruCDBwoDe3Y3QP+Rg7HVGBU=
|
||||
local_dir: dpl_cd_upload
|
||||
skip_cleanup: true
|
||||
on: &2
|
||||
repo: alphagov/notifications-admin
|
||||
branch: live
|
||||
bucket: live-notifications-api-codedeploy
|
||||
region: eu-west-1
|
||||
- provider: codedeploy
|
||||
access_key_id: AKIAJ5MKF6G3P2JQP4QQ
|
||||
secret_access_key: *1
|
||||
bucket: live-notifications-api-codedeploy
|
||||
key: notifications-admin-$TRAVIS_BRANCH-$TRAVIS_BUILD_NUMBER-$TRAVIS_COMMIT.zip
|
||||
bundle_type: zip
|
||||
application: notifications-admin
|
||||
deployment_group: live_notifications_admin_deployment_group
|
||||
region: eu-west-1
|
||||
on: *2
|
||||
- provider: s3
|
||||
access_key_id: AKIAJQPPNM6P6V53SWKA
|
||||
secret_access_key: &1
|
||||
|
||||
2
app.py
2
app.py
@@ -3,7 +3,7 @@ from flask.ext.script import Manager, Server
|
||||
from app import create_app
|
||||
|
||||
|
||||
application = create_app(os.getenv('NOTIFICATIONS_ADMIN_ENVIRONMENT') or 'development')
|
||||
application = create_app()
|
||||
manager = Manager(application)
|
||||
port = int(os.environ.get('PORT', 6012))
|
||||
manager.add_command("runserver", Server(host='0.0.0.0', port=port))
|
||||
|
||||
@@ -20,11 +20,11 @@ from app.notify_client.job_api_client import JobApiClient
|
||||
from app.notify_client.notification_api_client import NotificationApiClient
|
||||
from app.notify_client.status_api_client import StatusApiClient
|
||||
from app.notify_client.invite_api_client import InviteApiClient
|
||||
from app.notify_client.statistics_api_client import StatisticsApiClient
|
||||
from app.its_dangerous_session import ItsdangerousSessionInterface
|
||||
from app.asset_fingerprinter import AssetFingerprinter
|
||||
from utils.recipients import validate_phone_number, InvalidPhoneError
|
||||
import app.proxy_fix
|
||||
from config import configs
|
||||
from utils import logging
|
||||
|
||||
login_manager = LoginManager()
|
||||
@@ -37,15 +37,16 @@ job_api_client = JobApiClient()
|
||||
notification_api_client = NotificationApiClient()
|
||||
status_api_client = StatusApiClient()
|
||||
invite_api_client = InviteApiClient()
|
||||
statistics_api_client = StatisticsApiClient()
|
||||
asset_fingerprinter = AssetFingerprinter()
|
||||
|
||||
|
||||
def create_app(config_name, config_overrides=None):
|
||||
def create_app():
|
||||
application = Flask(__name__)
|
||||
|
||||
application.config['NOTIFY_ADMIN_ENVIRONMENT'] = config_name
|
||||
application.config.from_object(configs[config_name])
|
||||
init_app(application, config_overrides)
|
||||
application.config.from_object(os.environ['NOTIFY_ADMIN_ENVIRONMENT'])
|
||||
|
||||
init_app(application)
|
||||
logging.init_app(application)
|
||||
init_csrf(application)
|
||||
|
||||
@@ -56,6 +57,7 @@ def create_app(config_name, config_overrides=None):
|
||||
notification_api_client.init_app(application)
|
||||
status_api_client.init_app(application)
|
||||
invite_api_client.init_app(application)
|
||||
statistics_api_client.init_app(application)
|
||||
|
||||
login_manager.init_app(application)
|
||||
login_manager.login_view = 'main.sign_in'
|
||||
@@ -103,22 +105,12 @@ def init_csrf(application):
|
||||
abort(400, reason)
|
||||
|
||||
|
||||
def init_app(app, config_overrides):
|
||||
|
||||
if config_overrides:
|
||||
for key in app.config.keys():
|
||||
if key in config_overrides:
|
||||
app.config[key] = config_overrides[key]
|
||||
|
||||
for key, value in app.config.items():
|
||||
if key in os.environ:
|
||||
app.config[key] = convert_to_boolean(os.environ[key])
|
||||
|
||||
@app.context_processor
|
||||
def init_app(application):
|
||||
@application.context_processor
|
||||
def inject_global_template_variables():
|
||||
return {
|
||||
'asset_path': '/static/',
|
||||
'header_colour': app.config['HEADER_COLOUR'],
|
||||
'header_colour': application.config['HEADER_COLOUR'],
|
||||
'asset_url': asset_fingerprinter.get_url
|
||||
}
|
||||
|
||||
|
||||
BIN
app/assets/images/tick-black.png
Normal file
BIN
app/assets/images/tick-black.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
@@ -2,6 +2,10 @@
|
||||
@include grid-column(3/4);
|
||||
}
|
||||
|
||||
.column-one-sixth {
|
||||
@include grid-column(1/6);
|
||||
}
|
||||
|
||||
.column-one-eighth {
|
||||
@include grid-column(1/8);
|
||||
}
|
||||
@@ -16,7 +20,7 @@
|
||||
}
|
||||
|
||||
.bottom-gutter-2-3 {
|
||||
margin-bottom: $gutter * 2/3;
|
||||
margin-bottom: $gutter-two-thirds;
|
||||
}
|
||||
|
||||
.align-with-heading {
|
||||
@@ -26,3 +30,19 @@
|
||||
padding-left: 2px;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
.global-cookie-message {
|
||||
p {
|
||||
@extend %site-width-container;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-nav {
|
||||
@include copy-16;
|
||||
margin-bottom: $gutter-two-thirds;
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
margin-right: $gutter-half;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,3 +80,11 @@ a {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.heading-xlarge {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
@@ -26,9 +26,8 @@
|
||||
|
||||
}
|
||||
|
||||
.banner-with-tick,
|
||||
.banner-default-with-tick {
|
||||
@extend %banner;
|
||||
%banner-with-tick,
|
||||
.banner-with-tick {
|
||||
padding: $gutter-half ($gutter + $gutter-half);
|
||||
background-image: file-url('tick-white.png');
|
||||
background-size: 19px;
|
||||
@@ -37,6 +36,11 @@
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.banner-default-with-tick {
|
||||
@extend %banner;
|
||||
@extend %banner-with-tick;
|
||||
}
|
||||
|
||||
.banner-dangerous {
|
||||
|
||||
@extend %banner;
|
||||
@@ -54,13 +58,15 @@
|
||||
|
||||
}
|
||||
|
||||
%banner-tip,
|
||||
.banner-tip {
|
||||
|
||||
@extend %banner;
|
||||
background: $yellow;
|
||||
@include bold-19;
|
||||
background-color: $yellow;
|
||||
color: $text-colour;
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
margin-top: 0;
|
||||
|
||||
a {
|
||||
&:link,
|
||||
@@ -76,6 +82,12 @@
|
||||
|
||||
}
|
||||
|
||||
.banner-tip-with-tick {
|
||||
@extend %banner-with-tick;
|
||||
@extend %banner-tip;
|
||||
background-image: file-url('tick-black.png');
|
||||
}
|
||||
|
||||
.banner-info,
|
||||
.banner-important {
|
||||
@extend %banner;
|
||||
@@ -91,3 +103,40 @@
|
||||
.banner-info {
|
||||
background-image: file-url('icon-information-2x.png');
|
||||
}
|
||||
|
||||
.banner-mode {
|
||||
|
||||
@extend %banner;
|
||||
background: $govuk-blue;
|
||||
color: $white;
|
||||
margin-top: $gutter;
|
||||
|
||||
.heading-medium {
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
a {
|
||||
|
||||
&:link,
|
||||
&:visited {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
color: $light-blue-25;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.big-number {
|
||||
|
||||
margin-top: 10px;
|
||||
|
||||
&-label {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
%big-number,
|
||||
.big-number {
|
||||
|
||||
@include bold-48($tabular-numbers: true);
|
||||
@@ -5,6 +6,62 @@
|
||||
&-label {
|
||||
@include core-19;
|
||||
display: block;
|
||||
padding-bottom: $gutter-half;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.big-number-with-status {
|
||||
|
||||
@extend %big-number;
|
||||
background: $text-colour;
|
||||
color: $white;
|
||||
|
||||
.big-number {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.big-number-label {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.big-number-status {
|
||||
|
||||
display: block;
|
||||
background: $green;
|
||||
position: relative;
|
||||
|
||||
&-error-percentage {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: $error-colour;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
a {
|
||||
|
||||
&:link,
|
||||
&:visited,
|
||||
&:active,
|
||||
&:hover {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.big-number {
|
||||
@include bold-19;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.big-number-label {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ from flask_login import login_required
|
||||
from app.main import main
|
||||
from app.main.dao.services_dao import get_service_by_id
|
||||
from app.main.dao import templates_dao
|
||||
from app import job_api_client
|
||||
from app import job_api_client, statistics_api_client
|
||||
|
||||
|
||||
@main.route("/services/<service_id>/dashboard")
|
||||
@@ -27,11 +27,37 @@ def service_dashboard(service_id):
|
||||
message = 'You have successfully accepted your invitation and been added to {}'.format(service_name)
|
||||
flash(message, 'default_with_tick')
|
||||
|
||||
statistics = statistics_api_client.get_statistics_for_service(service_id)['data']
|
||||
|
||||
return render_template(
|
||||
'views/service_dashboard.html',
|
||||
'views/dashboard/dashboard.html',
|
||||
jobs=jobs[:5],
|
||||
more_jobs_to_show=(len(jobs) > 5),
|
||||
free_text_messages_remaining='250,000',
|
||||
spent_this_month='0.00',
|
||||
template_count=len(templates),
|
||||
service=service['data'],
|
||||
statistics=expand_statistics(statistics),
|
||||
templates=templates,
|
||||
service_id=str(service_id))
|
||||
|
||||
|
||||
def expand_statistics(statistics, danger_zone=25):
|
||||
|
||||
if not statistics or not statistics[0]:
|
||||
return {}
|
||||
|
||||
today = statistics[0]
|
||||
|
||||
today.update({
|
||||
'emails_failure_rate':
|
||||
int(today['emails_error'] / today['emails_requested'] * 100) if today['emails_requested'] else 0,
|
||||
'sms_failure_rate':
|
||||
int(today['sms_error'] / today['sms_requested'] * 100) if today['sms_requested'] else 0
|
||||
})
|
||||
|
||||
today.update({
|
||||
'emails_percentage_of_danger_zone': min(today['emails_failure_rate'] / (danger_zone / 100), 100),
|
||||
'sms_percentage_of_danger_zone': min(today['sms_failure_rate'] / (danger_zone / 100), 100)
|
||||
})
|
||||
|
||||
return today
|
||||
|
||||
@@ -16,3 +16,13 @@ def index():
|
||||
@login_required
|
||||
def verify_mobile():
|
||||
return render_template('views/verify-mobile.html')
|
||||
|
||||
|
||||
@main.route('/cookies')
|
||||
def cookies():
|
||||
return render_template('views/cookies.html')
|
||||
|
||||
|
||||
@main.route('/help')
|
||||
def help():
|
||||
return render_template('views/help.html')
|
||||
|
||||
18
app/notify_client/statistics_api_client.py
Normal file
18
app/notify_client/statistics_api_client.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from notifications_python_client.base import BaseAPIClient
|
||||
|
||||
|
||||
class StatisticsApiClient(BaseAPIClient):
|
||||
def __init__(self, base_url=None, client_id=None, secret=None):
|
||||
super(self.__class__, self).__init__(base_url=base_url or 'base_url',
|
||||
client_id=client_id or 'client_id',
|
||||
secret=secret or 'secret')
|
||||
|
||||
def init_app(self, app):
|
||||
self.base_url = app.config['API_HOST_NAME']
|
||||
self.client_id = app.config['ADMIN_CLIENT_USER_NAME']
|
||||
self.secret = app.config['ADMIN_CLIENT_SECRET']
|
||||
|
||||
def get_statistics_for_service(self, service_id):
|
||||
return self.get(
|
||||
url='/service/{}/notifications-statistics'.format(service_id),
|
||||
)
|
||||
@@ -24,6 +24,10 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block cookie_message %}
|
||||
<p>
|
||||
GOV.UK Notify uses cookies to make the site simpler.
|
||||
<a href="{{ url_for("main.cookies") }}">Find out more about cookies</a>
|
||||
</p>
|
||||
{% endblock %}
|
||||
|
||||
{% block inside_header %}
|
||||
@@ -68,6 +72,15 @@
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block footer_support_links %}
|
||||
<nav class="footer-nav">
|
||||
<a href="{{ url_for("main.help") }}">Help</a>
|
||||
<a href="https://docs.google.com/forms/d/1AL8U-xJX_HAFEiQiJszGQw0PcEaEUnYATSntEghNDGo/viewform">Support and feedback</a>
|
||||
<a href="{{ url_for("main.cookies") }}">Cookies</a>
|
||||
Built by the <a href="https://www.gov.uk/government/organisations/government-digital-service">Government Digital Service</a>
|
||||
</nav>
|
||||
{% endblock %}
|
||||
|
||||
{% block body_end %}
|
||||
<script type="text/javascript" src="{{ asset_url('javascripts/all.js') }}" /></script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
{% macro banner(body, type=None, with_tick=False, delete_button=None, subhead=None) %}
|
||||
<div class='banner{% if type %}-{{ type }}{% endif %}{% if with_tick %}-with-tick{% endif %}'>
|
||||
{% if subhead %}
|
||||
<div class="grid-row">
|
||||
<div class="column-one-third">
|
||||
{{ subhead }}
|
||||
</div>
|
||||
<div class="column-two-thirds">
|
||||
{% endif %}
|
||||
|
||||
{% if subhead -%}
|
||||
{{ subhead }} 
|
||||
{%- endif -%}
|
||||
{{ body }}
|
||||
|
||||
{% if subhead %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if delete_button %}
|
||||
<form method='post'>
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
|
||||
@@ -4,3 +4,14 @@
|
||||
<span class="big-number-label">{{ label }}</span>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro big_number_with_status(number, label, status_number, status_label, percentage_bad=0) %}
|
||||
<div class="big-number-with-status">
|
||||
{{ big_number(number, label) }}
|
||||
<div class="big-number-status">
|
||||
<div class="big-number-status-error-percentage" style="opacity: {{ percentage_bad / 100 }}"></div>
|
||||
{{ big_number(status_number, status_label) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
@@ -37,9 +37,9 @@
|
||||
{% if current_user.has_permissions(['send_texts', 'send_emails', 'send_letters'], or_=True) %}
|
||||
{{ banner(
|
||||
"""
|
||||
Send yourself a test message
|
||||
Send yourself a test
|
||||
""",
|
||||
subhead='Next step',
|
||||
subhead='Next step:',
|
||||
type="tip"
|
||||
)}}
|
||||
{% endif %}
|
||||
|
||||
81
app/templates/views/cookies.html
Normal file
81
app/templates/views/cookies.html
Normal file
@@ -0,0 +1,81 @@
|
||||
{% extends "withoutnav_template.html" %}
|
||||
|
||||
{% block page_title %}
|
||||
Cookies – GOV.UK Notify
|
||||
{% endblock %}
|
||||
|
||||
{% block maincolumn_content %}
|
||||
|
||||
<div class="grid-row">
|
||||
<div class="column-two-thirds">
|
||||
<h1 class="heading-xlarge">Cookies</h1>
|
||||
<p class="summary">
|
||||
GOV.UK Notify puts small files (known as ‘cookies’)
|
||||
on to your computer.
|
||||
</p>
|
||||
<p>These cookies are used to remember you once you’ve logged in</p>
|
||||
<p>
|
||||
Find out <a href="http://www.aboutcookies.org/">how to manage cookies</a>.
|
||||
</p>
|
||||
|
||||
<h2 class="heading-medium">Session cookies</h2>
|
||||
<p>
|
||||
We store session cookies on your computer to help keep your information
|
||||
secure while you use the service.
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Purpose</th>
|
||||
<th>Expires</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
notify_admin_session
|
||||
</td>
|
||||
<td width="50%">
|
||||
Used to keep you logged in
|
||||
</td>
|
||||
<td>
|
||||
1 hour
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 class="heading-medium">Introductory message cookie</h2>
|
||||
<p>
|
||||
When you first use the service, you may see a pop-up ‘welcome’ message.
|
||||
Once you’ve seen the message, we store a cookie on your computer so it
|
||||
knows not to show it again.
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Purpose</th>
|
||||
<th>Expires</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
seen_cookie_message
|
||||
</td>
|
||||
<td width="50%">
|
||||
Saves a message to let us know that you have seen our cookie
|
||||
message
|
||||
</td>
|
||||
<td>
|
||||
1 month
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
20
app/templates/views/dashboard/dashboard.html
Normal file
20
app/templates/views/dashboard/dashboard.html
Normal file
@@ -0,0 +1,20 @@
|
||||
{% extends "withnav_template.html" %}
|
||||
|
||||
{% block page_title %}
|
||||
{{ session.get('service_name', 'Dashboard') }} – GOV.UK Notify
|
||||
{% endblock %}
|
||||
|
||||
{% block maincolumn_content %}
|
||||
|
||||
{% if service.restricted %}
|
||||
{% include 'views/dashboard/trial-mode-banner.html' %}
|
||||
{% endif %}
|
||||
|
||||
{% if not jobs %}
|
||||
{% include 'views/dashboard/get-started.html' %}
|
||||
{% else %}
|
||||
{% include 'views/dashboard/today.html' %}
|
||||
{% include 'views/dashboard/jobs.html' %}
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
19
app/templates/views/dashboard/get-started.html
Normal file
19
app/templates/views/dashboard/get-started.html
Normal file
@@ -0,0 +1,19 @@
|
||||
{% from "components/banner.html" import banner_wrapper %}
|
||||
|
||||
<h2 class="heading-medium">Get started</h2>
|
||||
<ol class="grid-row">
|
||||
{% if current_user.has_permissions(['manage_templates']) %}
|
||||
<li class="column-half">
|
||||
{% call banner_wrapper(type="tip", subhead='1.' if not templates else None, with_tick=templates|length) %}
|
||||
<a href='{{ url_for(".add_service_template", service_id=service_id, template_type="sms") }}'>Add a template</a>
|
||||
{% endcall %}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if current_user.has_permissions(['send_texts', 'send_emails', 'send_letters']) %}
|
||||
<li class="column-half">
|
||||
{% call banner_wrapper(type="tip", subhead='2.') %}
|
||||
<a href='{{ url_for(".choose_template", service_id=service_id, template_type="sms") }}'>Send yourself a message</a>
|
||||
{% endcall %}
|
||||
</li>
|
||||
{% endif %}
|
||||
</ol>
|
||||
28
app/templates/views/dashboard/jobs.html
Normal file
28
app/templates/views/dashboard/jobs.html
Normal file
@@ -0,0 +1,28 @@
|
||||
{% from "components/table.html" import list_table, field, right_aligned_field_heading, hidden_field_heading %}
|
||||
|
||||
{% call(item) list_table(
|
||||
jobs,
|
||||
caption="Recent batch jobs",
|
||||
empty_message='You haven’t sent any text messages yet',
|
||||
field_headings=['File', 'Started', right_aligned_field_heading('Rows'), right_aligned_field_heading('Sent')]
|
||||
) %}
|
||||
{% call field() %}
|
||||
<a href="{{ url_for('.view_job', service_id=service_id, job_id=item.id) }}">{{ item.original_file_name }}</a>
|
||||
{% endcall %}
|
||||
{% call field() %}
|
||||
{{ item.created_at|format_datetime }}
|
||||
{% endcall %}
|
||||
{% call field(align='right') %}
|
||||
{{ item.notification_count }}
|
||||
{% endcall %}
|
||||
{% call field(align='right') %}
|
||||
{{ item.notifications_sent }}
|
||||
{% endcall %}
|
||||
{% endcall %}
|
||||
{% if more_jobs_to_show %}
|
||||
{% if current_user.has_permissions(['send_texts', 'send_emails', 'send_letters']) %}
|
||||
<p class="table-show-more-link">
|
||||
<a href="{{ url_for('.view_jobs', service_id=service_id) }}">See all sent text messages</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
25
app/templates/views/dashboard/today.html
Normal file
25
app/templates/views/dashboard/today.html
Normal file
@@ -0,0 +1,25 @@
|
||||
{% from "components/big-number.html" import big_number_with_status %}
|
||||
|
||||
<h2 class="heading-medium">
|
||||
Sent today
|
||||
</h2>
|
||||
<div class="grid-row">
|
||||
<div class="column-half">
|
||||
{{ big_number_with_status(
|
||||
statistics.get('emails_requested', 0),
|
||||
'email' if statistics.get('emails_requested') == 1 else 'emails',
|
||||
'{}%'.format(statistics.get('emails_failure_rate', 0)),
|
||||
'failed',
|
||||
statistics.get('emails_percentage_of_danger_zone', 0)
|
||||
) }}
|
||||
</div>
|
||||
<div class="column-half">
|
||||
{{ big_number_with_status(
|
||||
statistics.get('sms_requested', 0),
|
||||
'text message' if statistics.get('sms_requested') == 1 else 'text messages',
|
||||
'{}%'.format(statistics.get('sms_failure_rate', 0)),
|
||||
'failed',
|
||||
statistics.get('sms_percentage_of_danger_zone', 0)
|
||||
) }}
|
||||
</div>
|
||||
</div>
|
||||
25
app/templates/views/dashboard/trial-mode-banner.html
Normal file
25
app/templates/views/dashboard/trial-mode-banner.html
Normal file
@@ -0,0 +1,25 @@
|
||||
{% from "components/banner.html" import banner_wrapper %}
|
||||
{% 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>
|
||||
We’ll 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 class="column-one-sixth">
|
||||
|
||||
</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>
|
||||
{% endcall %}
|
||||
79
app/templates/views/help.html
Normal file
79
app/templates/views/help.html
Normal file
@@ -0,0 +1,79 @@
|
||||
{% extends "withoutnav_template.html" %}
|
||||
|
||||
{% block page_title %}
|
||||
Cookies – GOV.UK Notify
|
||||
{% endblock %}
|
||||
|
||||
{% block maincolumn_content %}
|
||||
|
||||
<div class="grid-row">
|
||||
<div class="column-two-thirds">
|
||||
<h1 class="heading-xlarge">Help</h1>
|
||||
|
||||
<h2 class="heading-large" id="trial-mode">Trial mode</h2>
|
||||
|
||||
<p>
|
||||
When you create a service on GOV.UK Notify, you start off in trial
|
||||
mode. This means:
|
||||
</p>
|
||||
<ul class="list list-bullet">
|
||||
<li>
|
||||
you can only send messages to yourself and your team members
|
||||
</li>
|
||||
<li>
|
||||
you can only send 50 messages per day
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Anyone working in central government can create an account on
|
||||
GOV.UK Notify and try it out. So it’s important that you start off
|
||||
with some restrictions in place.
|
||||
</p>
|
||||
|
||||
<h3 class="heading-medium">To remove these restrictions</h3>
|
||||
|
||||
<p>
|
||||
You need to request to go live:
|
||||
</p>
|
||||
<ul class="list list-bullet">
|
||||
<li>
|
||||
You’ll need to agree to our terms of use
|
||||
</li>
|
||||
<li>
|
||||
If you plan to send more than 250,000 text messages per year, you’ll
|
||||
need to agree to pay for what you use – take a look at our pricing
|
||||
</li>
|
||||
<li>
|
||||
We’ll check your templates to make sure they’re consistent with our
|
||||
design patterns, style guide and information security principles
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3 class="heading-medium">
|
||||
All other aspects of GOV.UK Notify are exactly the same
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
You can still:
|
||||
</p>
|
||||
<ul class="list list-bullet">
|
||||
<li>
|
||||
upload a batch of recipients
|
||||
</li>
|
||||
<li>
|
||||
do an API integration
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
We’ll send the messages you have permission to send. The messages you
|
||||
don’t have permission to send will still be listed on your
|
||||
GOV.UK Notify dashboard but won’t actually get sent. This means you
|
||||
can check that everything is fully working without accidentally sending
|
||||
hundreds of text messages or emails.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
20
config.py
20
config.py
@@ -28,8 +28,6 @@ class Config(object):
|
||||
REMEMBER_COOKIE_SECURE = True
|
||||
|
||||
API_HOST_NAME = os.getenv('API_HOST_NAME')
|
||||
NOTIFY_API_SECRET = os.getenv('NOTIFY_API_SECRET', "dev-secret")
|
||||
NOTIFY_API_CLIENT = os.getenv('NOTIFY_API_CLIENT', "admin")
|
||||
|
||||
ADMIN_CLIENT_USER_NAME = os.getenv('ADMIN_CLIENT_USER_NAME')
|
||||
ADMIN_CLIENT_SECRET = os.getenv('ADMIN_CLIENT_SECRET')
|
||||
@@ -73,18 +71,10 @@ class Preview(Config):
|
||||
HEADER_COLOUR = '#F47738' # $orange
|
||||
|
||||
|
||||
class Staging(Preview):
|
||||
SHOW_STYLEGUIDE = False
|
||||
|
||||
|
||||
class Live(Staging):
|
||||
HEADER_COLOUR = '#B10E1E' # $red
|
||||
|
||||
|
||||
configs = {
|
||||
'development': Development,
|
||||
'test': Test,
|
||||
'preview': Preview,
|
||||
'staging': Staging,
|
||||
'live': Live
|
||||
'development': 'config.Development',
|
||||
'test': 'config.Test',
|
||||
'preview': 'config.Preview',
|
||||
'staging': 'config_staging.Staging',
|
||||
'live': 'config_live.Live'
|
||||
}
|
||||
|
||||
12
config_live.py
Normal file
12
config_live.py
Normal file
@@ -0,0 +1,12 @@
|
||||
import os
|
||||
from config import Config
|
||||
|
||||
|
||||
class Live(Config):
|
||||
SHOW_STYLEGUIDE = False
|
||||
HEADER_COLOUR = '#B10E1E' # $red
|
||||
HTTP_PROTOCOL = 'https'
|
||||
API_HOST_NAME = os.getenv('LIVE_API_HOST_NAME')
|
||||
ADMIN_CLIENT_SECRET = os.getenv('LIVE_ADMIN_CLIENT_SECRET')
|
||||
SECRET_KEY = os.getenv('LIVE_SECRET_KEY')
|
||||
DANGEROUS_SALT = os.getenv('LIVE_DANGEROUS_SALT')
|
||||
11
config_staging.py
Normal file
11
config_staging.py
Normal file
@@ -0,0 +1,11 @@
|
||||
import os
|
||||
from config import Config
|
||||
|
||||
|
||||
class Staging(Config):
|
||||
SHOW_STYLEGUIDE = False
|
||||
HTTP_PROTOCOL = 'https'
|
||||
API_HOST_NAME = os.getenv('STAGING_API_HOST_NAME')
|
||||
ADMIN_CLIENT_SECRET = os.getenv('STAGING_ADMIN_CLIENT_SECRET')
|
||||
SECRET_KEY = os.getenv('STAGING_SECRET_KEY')
|
||||
DANGEROUS_SALT = os.getenv('STAGING_DANGEROUS_SALT')
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
export NOTIFY_ADMIN_ENVIRONMENT='config.Development'
|
||||
python3 app.py runserver
|
||||
|
||||
@@ -32,5 +32,6 @@ display_result $? 2 "Front end code style check"
|
||||
#py.test --cov=app tests/
|
||||
#display_result $? 3 "Code coverage"
|
||||
|
||||
export NOTIFY_ADMIN_ENVIRONMENT='config.Test'
|
||||
py.test -v
|
||||
display_result $? 4 "Unit tests"
|
||||
|
||||
13
tests/app/main/notify_client/test_statistics_client.py
Normal file
13
tests/app/main/notify_client/test_statistics_client.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from app.notify_client.statistics_api_client import StatisticsApiClient
|
||||
|
||||
|
||||
def test_client_uses_correct_find_by_email(mocker, api_user_active):
|
||||
|
||||
expected_url = '/service/a1b2c3d4/notifications-statistics'
|
||||
|
||||
client = StatisticsApiClient()
|
||||
mock_get = mocker.patch('app.notify_client.statistics_api_client.StatisticsApiClient.get')
|
||||
|
||||
client.get_statistics_for_service('a1b2c3d4')
|
||||
|
||||
mock_get.assert_called_once_with(url=expected_url)
|
||||
@@ -175,7 +175,7 @@ def test_new_user_accept_invite_calls_api_and_views_registration_page(app_,
|
||||
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
||||
assert page.h1.string.strip() == 'Create an account'
|
||||
|
||||
email_in_page = page.find('p')
|
||||
email_in_page = page.find('main').find('p')
|
||||
assert email_in_page.text.strip() == 'Your account will be created with this email: invited_user@test.gov.uk' # noqa
|
||||
|
||||
form = page.find('form')
|
||||
@@ -275,6 +275,7 @@ def test_new_invited_user_verifies_and_added_to_service(app_,
|
||||
mock_accept_invite,
|
||||
mock_get_service,
|
||||
mock_get_service_templates,
|
||||
mock_get_service_statistics,
|
||||
mock_get_jobs):
|
||||
|
||||
with app_.test_request_context():
|
||||
|
||||
@@ -6,6 +6,7 @@ def test_should_show_recent_jobs_on_dashboard(app_,
|
||||
api_user_active,
|
||||
mock_get_service,
|
||||
mock_get_service_templates,
|
||||
mock_get_service_statistics,
|
||||
mock_get_user,
|
||||
mock_get_user_by_email,
|
||||
mock_login,
|
||||
@@ -34,6 +35,7 @@ def _test_dashboard_menu(mocker, app_, usr, service, permissions):
|
||||
mocker.patch('app.user_api_client.get_user', return_value=usr)
|
||||
mocker.patch('app.user_api_client.get_user_by_email', return_value=usr)
|
||||
mocker.patch('app.notifications_api_client.get_service', return_value={'data': service})
|
||||
mocker.patch('app.statistics_api_client.get_statistics_for_service', return_value={'data': [{}]})
|
||||
client.login(usr)
|
||||
return client.get(url_for('main.service_dashboard', service_id=service['id']))
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ def test_sign_out_user(app_,
|
||||
mock_get_user,
|
||||
mock_get_user_by_email,
|
||||
mock_get_service_templates,
|
||||
mock_get_service_statistics,
|
||||
mock_login,
|
||||
mock_get_jobs):
|
||||
with app_.test_request_context():
|
||||
|
||||
@@ -24,7 +24,7 @@ from notifications_python_client.errors import HTTPError
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def app_(request):
|
||||
app = create_app('test')
|
||||
app = create_app()
|
||||
|
||||
ctx = app.app_context()
|
||||
ctx.push()
|
||||
@@ -154,6 +154,15 @@ def mock_delete_service(mocker, mock_get_service):
|
||||
'app.notifications_api_client.delete_service', side_effect=_delete)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def mock_get_service_statistics(mocker):
|
||||
def _create(service_id):
|
||||
return {'data': [{}]}
|
||||
|
||||
return mocker.patch(
|
||||
'app.statistics_api_client.get_statistics_for_service', side_effect=_create)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def mock_get_service_template(mocker):
|
||||
def _create(service_id, template_id):
|
||||
|
||||
19
wsgi.py
19
wsgi.py
@@ -1,17 +1,24 @@
|
||||
from app import create_app
|
||||
from credstash import getAllSecrets
|
||||
import os
|
||||
|
||||
config = 'live'
|
||||
default_env_file = '/home/ubuntu/environment'
|
||||
environment = 'live'
|
||||
|
||||
if os.path.isfile(default_env_file):
|
||||
environment = open(default_env_file, 'r')
|
||||
config = environment.readline().strip()
|
||||
with open(default_env_file, 'r') as environment_file:
|
||||
environment = environment_file.readline().strip()
|
||||
|
||||
secrets = getAllSecrets(region="eu-west-1")
|
||||
|
||||
application = create_app(config, secrets)
|
||||
# on aws get secrets and export to env
|
||||
os.environ.update(getAllSecrets(region="eu-west-1"))
|
||||
|
||||
from config import configs
|
||||
|
||||
os.environ['NOTIFY_ADMIN_ENVIRONMENT'] = configs[environment]
|
||||
|
||||
from app import create_app
|
||||
|
||||
application = create_app()
|
||||
|
||||
if __name__ == "__main__":
|
||||
application.run()
|
||||
|
||||
Reference in New Issue
Block a user