Merge branch 'master' into remove-stats-from-send

This commit is contained in:
Leo Hemsted
2016-08-09 14:41:00 +01:00
55 changed files with 408 additions and 293 deletions

View File

@@ -5,7 +5,7 @@ cache:
directories:
- node_modules
python:
- '3.4.3'
- 3.4.3
env:
secure: jT9BIioqBMkOdLZhU+WJNdnRJ+06G7qUx4QqEVldp96dJwmWpPEvA0XbitdnQt/WXYkpMlDbgSApvvGj2ZNvdpowRRe5HFX8D2Udhi2g9+cXgKrQxH6zv0evJyQLOjCINW6KtgMCJ5wkYR3qQ4BQawlDt6ecpmeboKTmvs2W8jZ09aV4IKKvdd7BwFon10QVPF5ny10G83unLtKnKgRMjSSLnaEiA78pE/LSUkekK4mhmtl+yfQf60cIuQGcN9NCYIt5PrdYYyMkbUaht9ykwL2C11sp5JYPClI9k6lrlpGJCdL9wbJwejGhR/pEqwJ4tKK8Zv+mngmkbzE6fd5ehuRMnIUAifG4t3p6WbhKwY5pJsdVyPgWcRSPXOJA7yEcAeTAvWcC++6mCIFBeMxt/yQNw02jkFHeNKRh2twTRvr4xWZHq9FsVxTEVz89OOuue3IkkyDNmVusGJ9+AVRIn9Oa+U/r3bDnrs7jz+meSwb82GZUBzFpUe2pe8qeBE572Ay7yHB73VHUgp/2A1qkZ4SnTjTpMbnS5RdXTgwtMkOs5MLZgteCVxFL3sHcr9e/B3UIUnzKUSPXXOjHyDxBwrABWo81V9Vp2IPV7P9Ofv8zroudjQxK5MOcbmiPQF+eEB9L4DvkUBNsGxtJ/nmPp6tmN0Xjo0xXVdZCEVj29Og=
before_install:
@@ -26,70 +26,70 @@ notifications:
secure: A6n6Gdz3dsE+KQcOd1nWTvdjOF2YbgItT1E40r25poG6p04WHd8qWtC4T2FuZaxPN/TQdKr/dKa/WCkmiEdxT5O0SOwAnAD3u6Fn2nthoI4M5916UrK1ZrqupvnFPSQc8Ivh51PGkcmB4wrb0ylRhMB94RmLcUZcVuXLDx57GO8bPFyLC3E9bgcVVFWaX45sKs74sBSQWi9EBbzHIuduLdjIpW7wX07dA++HlY14W5WgiurmiYohfP11VdAMmMxJs2WdWk16O/qy0HZXaldNIsSnuDBkhAZOMeSrcvp+62yOiN8jK0nSa1IRr3IoUkITdC9YGys3xFJb8gyIQE9T3hUnTYAKCcgsgpVFS6UzsRN42JUAJ8rFTgK9/J299yTk4lqL8uWzcV1QcKXIPNoG0QfqkmlB9B1fKbXuE/KkPEXPCKAcVQpCzEon09FgTCrlVZqJ6HxQonnLcPlIpVzWHAFokLZVHLAFMKYJnGBcZ6zaRK5pdc1babcOXMIPBC8j028G5bhBaCviDvZlimxOsUK1sJTpjzMU0tBQZa8lI+0O5otvMKiX8jPyaedjVvUmsftF2O5FH5nz2ofJC7BThb76/Tac2pNTCn0pWiVz9wi/YXALOMdIzkYgHnyZdEqAjRlpFwZuOrzR6MuvivBebPxjYaRWzCjOeC1uIwz+48E=
deploy:
- provider: s3
access_key_id: AKIAJ5MKF6G3P2JQP4QQ
access_key_id: AKIAJLWYN4T4D5WU4APA
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=
secure: EUiC93M7BI563BTIZV8tR2PvA1iIVKliKIiiFOJ9F8MJDB4Eya0ZfMLC1hsopJ1gZPWv4CjcW04CWqHf62FQJHDDTFtERMXEhJjVGOTgCC+7fkGEKV5pBJCtVdSGlQwd8HjlcfUZ8M7qlnqwLMj2IcTSiMzCVr8RpuSNHM9DF88n3k0EEiF+h83wB1PSmA+4I+CRevFg6xK1ezjSxCZV9N4A9vd1mrX+BSsH0ikbUA66ccAPMGy0yVR3CtzrI9AObutc9mO8qzLXI0SuCYFvyFEHXdXqqcWw4XJ28hYsayHO4gEX1ndYSNzIcCJKXseJRs6idcE0xRpipigYyx83SqvbPtrBlga47MPHXpP8Xl8xTKtVLo9k+Itbx96t1pllrhFqoysHfV8yRrh+otmED2sziB1VTiecPP0IvlVlWGEzUHMepkQhZKfDmFUtcpa7BrJx0Fxzsu9mL9tKx5H7PVA9uMC67Tc6qI3PNSE5XsNfBUjSOzaSY7UFUdKmvINXRk9PIP+NXOsH0kzILl7EzvUoBIbFf8+/1EDmvMc88J5yg4QCmVnIhw5JqnC1uQyDUy41VOsdqgwVUHy7hJuXtKMnUSYuDOltP2fdkArLby0VFgcQgoT+N87I4xcjVzLjEvNgwlZ1nHuBf9v+aB0aD9e/LB03gIt23Nrucu9ON1M=
local_dir: dpl_cd_upload
skip_cleanup: true
region: eu-west-1
on: &2
repo: alphagov/notifications-admin
bucket: notify.works-notifications-admin-codedeploy
- provider: codedeploy
access_key_id: AKIAJLWYN4T4D5WU4APA
secret_access_key: *1
bucket: notify.works-notifications-admin-codedeploy
key: notifications-admin-$TRAVIS_BRANCH-$TRAVIS_BUILD_NUMBER-$TRAVIS_COMMIT.zip
bundle_type: zip
application: admin
deployment_group: notifications_admin_deployment_group
region: eu-west-1
on: *2
- provider: s3
access_key_id: AKIAJCOSRR7IFXS2WJUA
secret_access_key: &1
secure: LBprDWBFawNvG9M0AVHJkS0txP4IapXG6K8OIzY6T3yDJP8vTnN6M34C++AprkZEqehzug36Vms5PVv9p2gkJOjZkwBKMRFZtjdxGXfRaZnbGQgcBbleMhdXsDWlGJm7RnMS/SX79tiezQ2h6GYyb96vBT0mdfyo/OPr5HJ6IWa/fblUXzZF1a5kCvP2uvR+nXKutsfNnP7lFzrFiH6efyeI1XPD18TU7Nuy6xOZDkDlX2RAvq+Qe5zX0o7VzXFkFdf7NfVT7AswoAEEVZeWNrm84oQJsH88QymDgpdLLzAlwmrWHDTGQtRCOQvXWhK6HvZMgP9n4EcWSIq4WMCjXMcnUqW6mvB+dBjJEe3I+oVgf1TWpdCSAWyffkJ805/xSF/BP92KHg8PAYJKlzrpHWuHaysf05a0QyKsFW09SzzQbpMrs0KLErqOLbG9NKZ5MaUepnsMyuKTr3shysNzlbcC5TghpGQO+D2yj/cJYmKp835Nx9tSU49woDbMDycdgF0rIczfwM8+OrywPw4fY6NAir6uAGx3aYrCx2BNEsGW4tjY7ab8EGCk4pgWpBw1/ERclA6D5sx7ppLI3wnVKsUyPUDvjOFjqdWkBrsONM4xHENRyaJupzQEuzJt/nxtHHGsayyKqYWT24+6ira8p4vJVqnJ+qBM3C7IYVIVXM8=
local_dir: dpl_cd_upload
skip_cleanup: true
region: eu-west-1
on: &2
repo: alphagov/notifications-admin
branch: staging
bucket: staging-notifications-admin-codedeploy
region: eu-west-1
bucket: staging-notify.works-notifications-admin-codedeploy
- provider: codedeploy
access_key_id: AKIAJ5MKF6G3P2JQP4QQ
access_key_id: AKIAJCOSRR7IFXS2WJUA
secret_access_key: *1
bucket: staging-notifications-admin-codedeploy
bucket: staging-notify.works-notifications-admin-codedeploy
key: notifications-admin-$TRAVIS_BRANCH-$TRAVIS_BUILD_NUMBER-$TRAVIS_COMMIT.zip
bundle_type: zip
application: notifications-admin
deployment_group: staging_admin_deployment_group
application: admin
deployment_group: notifications_admin_deployment_group
region: eu-west-1
on: *2
- provider: s3
access_key_id: AKIAJ5MKF6G3P2JQP4QQ
access_key_id: AKIAIFLN7IJIIQT6S37Q
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=
secure: Vt558KgvDIAUDWPLviRk24ZOzvNZ993rovMj4rmaZF9Dh1qEHH/tZ/yCkXUTLEMXUVCf5TKek9HfBZAiIrQn+VAe7RTDNHQUuI1EWmpCuqPCZcF6vP6Jz3KyGeCtsF8Ksr394sGbTc4clGxANin0HqWuBqWUV5QGhFJTgBEfCn+Zx1hLQoJisLUSxYaKbc7GGugYa+S3YDhAS7XPlYr4fTnRFp7VT0U2ofecS/skoGLuzb+HMY2MwXAeuBkCb1tIFFl30wbU1PwjLimSu5bWq9cLrRDiH3cDK3fYwAdbjhEM5Wg9i7YfjTcLasOmYNSsLih0FVo1k/G2xTKGU7nsCiSlEX7FpNgwOO3HGdcgb1Pp1X0iIVMh5gw1hlXJNPn/fSDkjLxwISGzI0XmEAilnIo8ovF/jvoEWj5kXrqtOuoH/moQttDCu+0iritfs40A8qBqv/6H3KSamqrDYC5pyRd1VlLo4aR9cH36pedNTnSH88bxm2We3iO/3m60KyEAfgAmJwINvRSuudyi3SeB1FXU0xf7qJ4wg2vWJCzOccFYhpqdk5wh5ZaeH1RdUbz0nNvs2uxDjWTsH/HFbAhgVgt4RtrwjsJUB5i2TWZKl6VykuIMet/y9BHcMbklwEHryPn74fpIs+7ui5zBOMNpEecEWXL5gwnh2fzUPm1+MlA=
local_dir: dpl_cd_upload
skip_cleanup: true
region: eu-west-1
on: &2
repo: alphagov/notifications-admin
branch: live
bucket: live-notifications-admin-codedeploy
region: eu-west-1
bucket: notifications.service.gov.uk-notifications-admin-codedeploy
- provider: codedeploy
access_key_id: AKIAJ5MKF6G3P2JQP4QQ
access_key_id: AKIAIFLN7IJIIQT6S37Q
secret_access_key: *1
bucket: live-notifications-admin-codedeploy
bucket: notifications.service.gov.uk-notifications-admin-codedeploy
key: notifications-admin-$TRAVIS_BRANCH-$TRAVIS_BUILD_NUMBER-$TRAVIS_COMMIT.zip
bundle_type: zip
application: notifications-admin
deployment_group: live_admin_deployment_group
region: eu-west-1
on: *2
- provider: s3
access_key_id: AKIAJQPPNM6P6V53SWKA
secret_access_key: &1
secure: 7qVw4gmn7zMCaam9PqIfqwC1z82gXxa+rT/VakoMDY7u62BXA8/fWGFn7yU4qyPSrTWttYdDXmdwrgoEDjGIzLXSpufRqhmXc3xBoRktFSRQAFVom49mWo7WDKgX6gZ3GcuVR8HQ1XpojlkLH/a6cKV9jreDiGHy3sHulBkJRXGIGkTNCiVNVbUsFeiOj/YGaIdZ6ZLjDBSOTwLkLsnzZm7S+xcRd0kxT3VJrV0B3x14igK2Rchv9LteT+fafELURO2asASgLPHy5ny3yGF7vVz6tLBrTpy2N7xxpaDkP/LQi38PRGhZkYPHdqgMfq7fFwN2oJF66zxeQ6kzR9lIwVZnqNYwgAapJAx7TrfX2UaR0zoZgs/TBhFijqJ2CK8fIQ7TKW0wEd84rd5FS8ocHmAsiJSLOSXn665m/nUPrWrUi7mK7/9/85rLiZihw862BhwIn83VQcahK4QEDFkK0PC7JqGvLiH3prjlibsA7ONDbFi8NPtvf3bFaofb6a3NO3qjgSvdftIujw86ZmVBAkQXghyZBlgCPiseOphrxZ4Thl54fo86cbiEqAs8c6DCHtPEiAHZ++sK1aitMtepdGORC1SrLICWO4vFAl6oLptzvY+j9+BhOK28JnRUXpQS7DjkrahqcgE/LbCN55lu1UhWWGY0q20QeKgGnsRuOAU=
local_dir: dpl_cd_upload
skip_cleanup: true
on: &2
repo: alphagov/notifications-admin
bucket: notifications-admin-codedeploy
region: eu-west-1
- provider: codedeploy
access_key_id: AKIAJQPPNM6P6V53SWKA
secret_access_key: *1
bucket: notifications-admin-codedeploy
key: notifications-admin-$TRAVIS_BRANCH-$TRAVIS_BUILD_NUMBER-$TRAVIS_COMMIT.zip
bundle_type: zip
application: notifications-admin
application: admin
deployment_group: notifications_admin_deployment_group
region: eu-west-1
on: *2
before_deploy:
- ./scripts/update_version_file.sh
- rm -rf node_modules bower_components app/assets
- zip -r --exclude=*__pycache__* notifications-admin *
- mkdir -p dpl_cd_upload
- mv notifications-admin.zip dpl_cd_upload/notifications-admin-$TRAVIS_BRANCH-$TRAVIS_BUILD_NUMBER-$TRAVIS_COMMIT.zip
- ./scripts/update_version_file.sh
- rm -rf node_modules bower_components app/assets
- zip -r --exclude=*__pycache__* notifications-admin *
- mkdir -p dpl_cd_upload
- mv notifications-admin.zip dpl_cd_upload/notifications-admin-$TRAVIS_BRANCH-$TRAVIS_BUILD_NUMBER-$TRAVIS_COMMIT.zip

View File

@@ -61,7 +61,7 @@ in a separate terminal from the app
```
echo "
export NOTIFY_ADMIN_ENVIRONMENT='config.Development'
export NOTIFY_ENVIRONMENT='development'
export ADMIN_CLIENT_SECRET='dev-notify-secret-key'
export ADMIN_CLIENT_USER_NAME='dev-notify-admin'
export API_HOST_NAME='http://localhost:6011'
@@ -69,7 +69,6 @@ export DANGEROUS_SALT='dev-notify-salt'
export SECRET_KEY='dev-notify-secret-key'
export DESKPRO_API_HOST=""
export DESKPRO_API_KEY=""
export DESKPRO_PERSON_EMAIL=""
export DESKPRO_DEPT_ID=""
export DESKPRO_ASSIGNED_AGENT_TEAM_ID=""
"> environment.sh

View File

@@ -13,8 +13,8 @@ from flask import (
current_app,
request,
g,
url_for
)
url_for)
from flask._compat import string_types
from flask.globals import _lookup_req_object
from flask_login import LoginManager
@@ -67,9 +67,11 @@ current_service = LocalProxy(partial(_lookup_req_object, 'service'))
def create_app():
from config import configs
application = Flask(__name__)
application.config.from_object(os.environ['NOTIFY_ADMIN_ENVIRONMENT'])
application.config.from_object(configs[os.environ['NOTIFY_ENVIRONMENT']])
init_app(application)
logging.init_app(application)
@@ -241,7 +243,8 @@ def format_notification_status(status, template_type):
'temporary-failure': 'Inbox not accepting messages right now',
'permanent-failure': 'Email address doesnt exist',
'delivered': 'Delivered',
'sending': 'Sending'
'sending': 'Sending',
'created': 'Sending'
},
'sms': {
'failed': 'Failed',
@@ -249,7 +252,8 @@ def format_notification_status(status, template_type):
'temporary-failure': 'Phone not accepting messages right now',
'permanent-failure': 'Phone number doesnt exist',
'delivered': 'Delivered',
'sending': 'Sending'
'sending': 'Sending',
'created': 'Sending'
}
}.get(template_type).get(status, status)
@@ -261,7 +265,8 @@ def format_notification_status_as_field_status(status):
'temporary-failure': 'error',
'permanent-failure': 'error',
'delivered': None,
'sending': 'default'
'sending': 'default',
'created': 'default'
}.get(status, 'error')

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 B

View File

@@ -31,6 +31,10 @@
margin-bottom: $gutter-two-thirds;
}
.bottom-gutter-1-2 {
margin-bottom: $gutter-half;
}
.align-with-heading {
display: block;
text-align: center;

View File

@@ -1,7 +1,8 @@
from datetime import datetime, date, timedelta
from datetime import datetime, timedelta
from collections import namedtuple
from itertools import groupby
import dateutil
from flask import (
render_template,
url_for,
@@ -9,13 +10,11 @@ from flask import (
jsonify,
current_app
)
from flask_login import login_required
from app.main import main
from app import (
job_api_client,
statistics_api_client,
service_api_client,
template_statistics_client
)
@@ -90,20 +89,10 @@ def usage(service_id):
@login_required
@user_has_permissions('manage_settings', admin_override=True)
def weekly(service_id):
earliest_date = date(2016, 4, 1) # start of tax year
while earliest_date.weekday() != 0: # 0 for monday
earliest_date -= timedelta(days=1)
stats = service_api_client.get_weekly_notification_stats(service_id)['data']
return render_template(
'views/weekly.html',
days=(
add_rates_to(day) for day in
statistics_api_client.get_7_day_aggregate_for_service(
service_id,
date_from=earliest_date
)['data']
),
days=format_weekly_stats_to_list(stats),
now=datetime.utcnow()
)
@@ -199,3 +188,21 @@ def calculate_usage(usage):
'sms_chargeable': max(0, sms_sent - sms_free_allowance),
'sms_rate': sms_rate
}
def format_weekly_stats_to_list(historical_stats):
out = []
for week, weekly_stats in historical_stats.items():
for stats in weekly_stats.values():
stats['failure_rate'] = get_formatted_percentage(stats['failed'], stats['requested'])
week_start = dateutil.parser.parse(week)
week_end = week_start + timedelta(days=6)
weekly_stats.update({
'week_start': week,
'week_end': week_end.date().isoformat(),
'week_end_datetime': week_end,
})
out.append(weekly_stats)
return sorted(out, key=lambda x: x['week_start'], reverse=True)

View File

@@ -8,17 +8,19 @@ from app.main.forms import Feedback
def feedback():
form = Feedback()
if form.validate_on_submit():
user_supplied_email = form.email_address.data != ''
feedback_msg = 'Environment: {}\n{}\n{}'.format(
url_for('main.index', _external=True),
'' if user_supplied_email else '{} (no email address supplied)'.format(form.name.data),
form.feedback.data
)
data = {
'person_email': current_app.config.get('DESKPRO_PERSON_EMAIL'),
'person_email': form.email_address.data or current_app.config.get('DESKPRO_PERSON_EMAIL'),
'person_name': form.name.data or None,
'department_id': current_app.config.get('DESKPRO_DEPT_ID'),
'agent_team_id': current_app.config.get('DESKPRO_ASSIGNED_AGENT_TEAM_ID'),
'subject': 'Notify feedback',
'message': 'Environment: {}\n\n{}\n{}\n{}'.format(
url_for('main.index', _external=True),
form.name.data,
form.email_address.data,
form.feedback.data
)
'message': feedback_msg
}
headers = {
"X-DeskPRO-API-Key": current_app.config.get('DESKPRO_API_KEY'),

View File

@@ -1,8 +1,10 @@
# -*- coding: utf-8 -*-
import ago
import time
import dateutil
from orderedset import OrderedSet
from datetime import datetime, timedelta, timezone
import ago
from itertools import chain
from flask import (
render_template,
@@ -47,15 +49,14 @@ def _parse_filter_args(filter_dict):
def _set_status_filters(filter_args):
status_filters = filter_args.get('status', [])
all_failure_statuses = ['failed', 'temporary-failure', 'permanent-failure', 'technical-failure']
all_statuses = ['sending', 'delivered'] + all_failure_statuses
if filter_args.get('status'):
if 'processed' in filter_args.get('status') or not filter_args.get('status'):
filter_args['status'] = all_statuses
elif 'failed' in filter_args.get('status'):
filter_args['status'].extend(all_failure_statuses[1:])
else:
filter_args['status'] = all_statuses
all_sending_statuses = ['created', 'sending']
return list(OrderedSet(chain(
(status_filters or all_sending_statuses + ['delivered'] + all_failure_statuses),
all_sending_statuses if 'sending' in status_filters else [],
all_failure_statuses if 'failed' in status_filters else []
)))
@main.route("/services/<service_id>/jobs")
@@ -75,7 +76,7 @@ def view_job(service_id, job_id):
job = job_api_client.get_job(service_id, job_id)['data']
filter_args = _parse_filter_args(request.args)
_set_status_filters(filter_args)
filter_args['status'] = _set_status_filters(filter_args)
return render_template(
'views/jobs/job.html',
@@ -117,7 +118,7 @@ def view_job_csv(service_id, job_id):
version=job['template_version']
)['data']
filter_args = _parse_filter_args(request.args)
_set_status_filters(filter_args)
filter_args['status'] = _set_status_filters(filter_args)
return (
generate_notifications_csv(
@@ -161,7 +162,7 @@ def view_notifications(service_id, message_type):
abort(404)
filter_args = _parse_filter_args(request.args)
_set_status_filters(filter_args)
filter_args['status'] = _set_status_filters(filter_args)
notifications = notification_api_client.get_notifications_for_service(
service_id=service_id,
@@ -210,7 +211,7 @@ def view_notifications(service_id, message_type):
page=page,
prev_page=prev_page,
next_page=next_page,
request_args=request.args,
status=request.args.get('status'),
message_type=message_type,
download_link=url_for(
'.view_notifications_csv',
@@ -250,7 +251,7 @@ def get_status_filters(service, message_type, statistics):
filters = [
# key, label, option
('requested', 'processed', 'sending,delivered,failed'),
('requested', 'total', 'sending,delivered,failed'),
('sending', 'sending', 'sending'),
('delivered', 'delivered', 'delivered'),
('failed', 'failed', 'failed'),
@@ -287,24 +288,22 @@ def _get_job_counts(job, help_argument):
count
) for label, query_param, count in [
[
'Processed', '',
job.get('notifications_sent', 0)
'total', '',
job.get('notification_count', 0)
],
[
'Sending', 'sending',
(
job.get('notifications_sent', 0) -
job.get('notifications_delivered', 0) -
job.get('notifications_failed', 0)
)
'sending', 'sending',
job.get('notification_count', 0) -
job.get('notifications_delivered', 0) -
job.get('notifications_failed', 0)
],
[
'Delivered', 'delivered',
'delivered', 'delivered',
job.get('notifications_delivered', 0)
],
[
'Failed', 'failed',
job.get('notifications_failed')
'failed', 'failed',
job.get('notifications_failed', 0)
]
]
]
@@ -312,24 +311,27 @@ def _get_job_counts(job, help_argument):
def get_job_partials(job):
filter_args = _parse_filter_args(request.args)
_set_status_filters(filter_args)
filter_args['status'] = _set_status_filters(filter_args)
notifications = notification_api_client.get_notifications_for_service(
job['service'], job['id'], status=filter_args['status']
)
return {
'counts': render_template(
'partials/jobs/count.html',
job=job,
counts=_get_job_counts(job, request.args.get('help', 0)),
status=request.args.get('status', '')
status=filter_args['status']
),
'notifications': render_template(
'partials/jobs/notifications.html',
notifications=notification_api_client.get_notifications_for_service(
job['service'], job['id'], status=filter_args.get('status')
)['notifications'],
notifications=notifications['notifications'],
more_than_one_page=bool(notifications.get('links', {}).get('next')),
percentage_complete=(job['notifications_sent'] / job['notification_count'] * 100),
download_link=url_for(
'.view_job_csv',
service_id=current_service['id'],
job_id=job['id'],
status=request.args.get('status', '')
status=request.args.get('status')
),
help=get_help_argument(),
time_left=get_time_left(job['created_at'])

View File

@@ -103,13 +103,12 @@ def service_request_to_go_live(service_id):
if form.validate_on_submit():
data = {
'person_email': current_app.config.get('DESKPRO_PERSON_EMAIL'),
'person_email': current_user.email_address,
'person_name': current_user.name,
'department_id': current_app.config.get('DESKPRO_DEPT_ID'),
'agent_team_id': current_app.config.get('DESKPRO_ASSIGNED_AGENT_TEAM_ID'),
'subject': 'Request to go live',
'message': "From {} <{}> on behalf of {} ({})\n\nUsage estimate\n---\n\n{}".format(
current_user.name,
current_user.email_address,
'message': "On behalf of {} ({})\n\nUsage estimate\n---\n\n{}".format(
current_service['name'],
url_for('main.service_dashboard', service_id=current_service['id'], _external=True),
form.usage.data

View File

@@ -204,6 +204,9 @@ class ServiceAPIClient(NotificationsAPIClient):
def get_service_usage(self, service_id):
return self.get('/service/{0}/fragment/aggregate_statistics'.format(service_id))
def get_weekly_notification_stats(self, service_id):
return self.get(url='/service/{}/notifications/weekly'.format(service_id))
class ServicesBrowsableItem(BrowsableItem):
@property

View File

@@ -12,16 +12,15 @@ class StatisticsApiClient(BaseAPIClient):
self.client_id = app.config['ADMIN_CLIENT_USER_NAME']
self.secret = app.config['ADMIN_CLIENT_SECRET']
def get_7_day_aggregate_for_service(self, service_id, date_from=None, week_count=None):
params = {}
if date_from is not None:
params['date_from'] = date_from
if week_count is not None:
params['week_count'] = week_count
return self.get(
url='/service/{}/notifications-statistics/seven_day_aggregate'.format(service_id),
params=params
)
def get_statistics_for_service_for_day(self, service_id, day):
url = '/service/{}/notifications-statistics/day/{}'.format(service_id, day)
try:
return self.get(url=url)['data']
except HTTPError as e:
if e.status_code == 404:
return None
else:
raise e
def get_statistics_for_all_services_for_day(self, day):
params = {

View File

@@ -50,7 +50,7 @@
{% if not expanded %}
<div class="email-message-body-wrapper">
{% endif %}
{{ body|nl2br }}
{{ body }}
{% if not expanded %}
</div>
<div class='toggle' tabindex='0'>...<span class='visually-hidden'>show full email</span></div>

View File

@@ -2,7 +2,7 @@
{% if help %}
{% call banner_wrapper(type='tour') %}
<p class="heading-medium">Try this example</p>
<p class="heading-medium">Try sending yourself this example</p>
<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>

View File

@@ -1,5 +1,5 @@
{% from "components/pill.html" import pill %}
<div class="bottom-gutter">
{{ pill('Status', counts, status) }}
{{ pill('Status', counts, request.args.get('status', '')) }}
</div>

View File

@@ -4,12 +4,18 @@
<div class="dashboard-table">
{% endif %}
{% if notifications and not help %}
<p class="bottom-gutter">
<a href="{{ download_link }}" download="download" class="heading-small">Download this report</a>
&emsp;
<span id="time-left">{{ time_left }}</span>
</p>
{% if not help %}
{% if percentage_complete < 100 %}
<p class="bottom-gutter-1-2 hint">
Report is {{ "{:.0f}%".format(percentage_complete) }} complete…
</p>
{% elif notifications %}
<p class="bottom-gutter-1-2">
<a href="{{ download_link }}" download="download" class="heading-small">Download this report</a>
&emsp;
<span id="time-left">{{ time_left }}</span>
</p>
{% endif %}
{% endif %}
{% call(item, row_number) list_table(
@@ -44,6 +50,12 @@
{% endcall %}
{% endcall %}
{% if more_than_one_page %}
<p class="table-show-more-link">
Only showing the first 50 rows
</p>
{% endif %}
{% if notifications %}
</div>
{% endif %}

View File

@@ -1,12 +1,8 @@
<details>
<summary class="button tertiary-button">Message length</summary>
<div id="guidance-personalisation">
<p>
If your message is long then it will
cost more.
</p>
<p>
See <a href="{{ url_for('.pricing') }}">pricing</a> for details.
</p>
</div>
</details>
<h2 class="heading-medium">Message length</h2>
<p>
If your message is long then it will
cost more.
</p>
<p>
See <a href="{{ url_for('.pricing') }}">pricing</a> for details.
</p>

View File

@@ -0,0 +1,27 @@
<h2 class="heading-medium">Formatting</h2>
<p>
To put a title in your template, use a hash:
</p>
<div class="panel panel-border-wide">
<p>
# This is a title
</p>
</div>
<p>
To make bullet points, use asterisks:
</p>
<div class="panel panel-border-wide">
<p>
* point 1<br/>
* point 2<br/>
* point 3<br/>
</p>
</div>
<p>
To add a callout, use a caret:
</p>
<div class="panel panel-border-wide">
<p>
^ You must tell us if your circumstances change
</p>
</div>

View File

@@ -1,8 +1,9 @@
<details>
<summary class="button tertiary-button">Links and URLs</summary>
<div id="guidance-personalisation">
<p>
Always use full URLs, starting with https://
</p>
</div>
</details>
<h2 class="heading-medium">Links and URLs</h2>
<p>
Always use full URLs, starting with https://
</p>
<div class="panel panel-border-wide">
<p>
Apply now at https://www.gov.uk/example
</p>
</div>

View File

@@ -1,14 +1,11 @@
<details>
<summary class="button tertiary-button">Personalisation</summary>
<div id="guidance-personalisation">
<p>
Use double brackets to add personalisation.
</p>
<p>
Correct: ((name))
</p>
<p>
Incorrect: ((Helen))
</p>
</div>
</details>
<h2 class="heading-medium">
Personalisation
</h2>
<p>
Use double brackets to personalise your message:
</p>
<div class="panel panel-border-wide">
<p>
Hello ((first name)), your reference is ((ref number))
</p>
</div>

View File

@@ -23,7 +23,7 @@
{% endcall %}
{% call field() %}
{{ big_number(
item.get('notifications_sent', 0) - item.get('notifications_delivered', 0) - item.get('notifications_failed', 0),
item.get('notification_count', 0) - item.get('notifications_delivered', 0) - item.get('notifications_failed', 0),
smallest=True
) }}
{% endcall %}

View File

@@ -19,7 +19,7 @@
{% if not templates %}
{% include 'views/dashboard/write-first-messages.html' %}
{% endif %}
{% elif not current_user.has_permissions(['send_texts', 'send_emails', 'send_letters'], any_=True) %}
{% elif not current_user.has_permissions(['send_texts', 'send_emails', 'send_letters', 'manage_api_keys'], any_=True) %}
{% include 'views/dashboard/no-permissions-banner.html' %}
{% endif %}

View File

@@ -14,11 +14,11 @@
<form method="post">
<div class="grid-row">
<div class="column-two-thirds">
<div class="column-three-quarters">
{{ textbox(form.name, width='1-1', hint='Your recipients wont see this', rows=10) }}
{{ textbox(form.subject, width='1-1', highlight_tags=True, rows=2) }}
</div>
<div class="column-two-thirds">
<div class="column-three-quarters">
{{ textbox(form.template_content, highlight_tags=True, width='1-1', rows=8) }}
{{ page_footer(
'Save',
@@ -26,8 +26,8 @@
delete_link_text='Delete this template'
) }}
</div>
<aside class="column-one-third">
<h2 style="margin: 0 0 5px 0;">Help</h2>
<aside class="column-whole">
{% include "partials/templates/guidance-formatting.html" %}
{% include "partials/templates/guidance-personalisation.html" %}
{% include "partials/templates/guidance-links.html" %}
</aside>

View File

@@ -25,8 +25,7 @@
delete_link_text='Delete this template'
) }}
</div>
<aside class="column-one-third">
<h2 style="margin: 0 0 5px 0;">Help</h2>
<aside class="column-whole">
{% include "partials/templates/guidance-personalisation.html" %}
{% include "partials/templates/guidance-links.html" %}
{% include "partials/templates/guidance-character-count.html" %}

View File

@@ -14,9 +14,9 @@
<h1 class="heading-large">
<span class="visually-hidden">
{%- if request_args.get('status') != 'delivered,failed' -%}
{%- if status != 'delivered,failed' -%}
{%- for label, option, _, _ in status_filters -%}
{%- if request_args.get('status', 'delivered,failed') == option -%}{{label}} {% endif -%}
{%- if status == option -%}{{label}} {% endif -%}
{%- endfor -%}
{%- endif -%}
</span>
@@ -29,15 +29,15 @@
{{ pill(
'Status',
status_filters,
request_args.get('status', '')
status
) }}
</div>
{% if notifications %}
<p class="bottom-gutter">
<a href="{{ download_link }}" download="download" class="heading-small">Download as a CSV file</a>
<p class="bottom-gutter-1-2">
<a href="{{ download_link }}" download="download" class="heading-small">Download this report</a>
&emsp;
Delivery information is available for 7 days
Data available for 7 days
</p>
{% endif %}

View File

@@ -3,7 +3,7 @@
{% extends "withnav_template.html" %}
{% block page_title %}
Daily GOV.UK Notify
Previous weeks GOV.UK Notify
{% endblock %}
{% block maincolumn_content %}
@@ -33,16 +33,16 @@
{% endif %}
{% endcall %}
{% call field(align='right') %}
{{ item.emails_requested }}
{{ item.email.requested }}
{% endcall %}
{% call field(align='right') %}
{{ item.emails_failure_rate }}%
{{ item.email.failure_rate }}%
{% endcall %}
{% call field(align='right') %}
{{ item.sms_requested }}
{{ item.sms.requested }}
{% endcall %}
{% call field(align='right') %}
{{ item.sms_failure_rate }}%
{{ item.sms.failure_rate }}%
{% endcall %}
{% endcall %}

View File

@@ -1,3 +1,3 @@
__travis_commit__ = "dev"
__time__ = "dev"
__travis_job_number__ = "dev"
__travis_commit__ = ""
__time__ = "2016-07-05:14:44:52"
__travis_job_number__ = ""

View File

@@ -1,7 +1,7 @@
---
files:
-
destination: /home/ubuntu/notifications-admin
destination: /home/notify-app/notifications-admin
source: /
hooks:
AfterInstall:

View File

@@ -3,7 +3,6 @@ from datetime import timedelta
class Config(object):
DEBUG = False
ADMIN_CLIENT_SECRET = os.environ['ADMIN_CLIENT_SECRET']
ADMIN_CLIENT_USER_NAME = os.environ['ADMIN_CLIENT_USER_NAME']
@@ -36,7 +35,7 @@ class Config(object):
CSV_UPLOAD_BUCKET_NAME = 'local-notifications-csv-upload'
DESKPRO_API_HOST = os.environ['DESKPRO_API_HOST']
DESKPRO_API_KEY = os.environ['DESKPRO_API_KEY']
DESKPRO_PERSON_EMAIL = os.environ['DESKPRO_PERSON_EMAIL']
DESKPRO_PERSON_EMAIL = 'donotreply@notifications.service.gov.uk'
DESKPRO_DEPT_ID = os.environ['DESKPRO_DEPT_ID']
DESKPRO_ASSIGNED_AGENT_TEAM_ID = os.environ['DESKPRO_ASSIGNED_AGENT_TEAM_ID']
ACTIVITY_STATS_LIMIT_DAYS = 7
@@ -78,10 +77,24 @@ class Preview(Config):
CSV_UPLOAD_BUCKET_NAME = 'preview-notifications-csv-upload'
class Staging(Config):
SHOW_STYLEGUIDE = False
HTTP_PROTOCOL = 'https'
HEADER_COLOUR = '#F47738' # $orange
CSV_UPLOAD_BUCKET_NAME = 'staging-notify-csv-upload'
class Live(Config):
SHOW_STYLEGUIDE = False
HEADER_COLOUR = '#B10E1E' # $red
HTTP_PROTOCOL = 'https'
CSV_UPLOAD_BUCKET_NAME = 'live-notifications-csv-upload'
configs = {
'development': 'config.Development',
'test': 'config.Test',
'preview': 'config.Preview',
'staging': 'config_staging.Staging',
'live': 'config_live.Live'
'development': Development,
'test': Test,
'preview': Preview,
'staging': Staging,
'live': Live
}

View File

@@ -1,17 +0,0 @@
import os
from config import Config
class Live(Config):
SHOW_STYLEGUIDE = False
HEADER_COLOUR = '#B10E1E' # $red
HTTP_PROTOCOL = 'https'
API_HOST_NAME = os.environ['LIVE_API_HOST_NAME']
ADMIN_CLIENT_SECRET = os.environ['LIVE_ADMIN_CLIENT_SECRET']
SECRET_KEY = os.environ['LIVE_SECRET_KEY']
DANGEROUS_SALT = os.environ['LIVE_DANGEROUS_SALT']
CSV_UPLOAD_BUCKET_NAME = 'live-notifications-csv-upload'
DESKPRO_API_KEY = os.environ['LIVE_DESKPRO_API_KEY']
DESKPRO_DEPT_ID = os.environ['LIVE_DESKPRO_DEPT_ID']
DESKPRO_ASSIGNED_AGENT_TEAM_ID = os.environ['LIVE_DESKPRO_ASSIGNED_AGENT_TEAM_ID']
HEADER_COLOUR = '#B10E1E' # $red

View File

@@ -1,16 +0,0 @@
import os
from config import Config
class Staging(Config):
SHOW_STYLEGUIDE = False
HTTP_PROTOCOL = 'https'
API_HOST_NAME = os.environ['STAGING_API_HOST_NAME']
ADMIN_CLIENT_SECRET = os.environ['STAGING_ADMIN_CLIENT_SECRET']
SECRET_KEY = os.environ['STAGING_SECRET_KEY']
DANGEROUS_SALT = os.environ['STAGING_DANGEROUS_SALT']
CSV_UPLOAD_BUCKET_NAME = 'staging-notifications-csv-upload'
DESKPRO_API_KEY = os.environ['STAGING_DESKPRO_API_KEY']
DESKPRO_DEPT_ID = os.environ['STAGING_DESKPRO_DEPT_ID']
DESKPRO_ASSIGNED_AGENT_TEAM_ID = os.environ['STAGING_DESKPRO_ASSIGNED_AGENT_TEAM_ID']
HEADER_COLOUR = '#F47738' # $orange

View File

@@ -42,7 +42,7 @@ To create JSON Web Tokens you need:
* your Service ID identifies your service
* your API key (in JSON Web Token terms this is called the client ID) used to sign tokens during requests for API resources
To find your Service ID and create or revoke API keys, click on **API keys** in the [GOV.UK Notify](https://www.notifications.service.gov.uk/) web application.
To find your Service ID and create or revoke API keys, click on **API integration** in the [GOV.UK Notify](https://www.notifications.service.gov.uk/) web application.
<h3 id="JWT_claims">JSON Web Tokens: claims</h3>

View File

@@ -1,4 +1,4 @@
export NOTIFY_ADMIN_ENVIRONMENT='config.Test'
export NOTIFY_ENVIRONMENT='test'
export ADMIN_CLIENT_SECRET='dev-notify-secret-key'
export ADMIN_CLIENT_USER_NAME='dev-notify-admin'
export API_HOST_NAME=''
@@ -6,6 +6,5 @@ export DANGEROUS_SALT='dev-notify-salt'
export SECRET_KEY='dev-notify-secret-key'
export DESKPRO_API_HOST=""
export DESKPRO_API_KEY=""
export DESKPRO_PERSON_EMAIL=""
export DESKPRO_DEPT_ID=""
export DESKPRO_ASSIGNED_AGENT_TEAM_ID=""

View File

@@ -19,4 +19,4 @@ pytz==2016.4
git+https://github.com/alphagov/notifications-python-client.git@1.0.0#egg=notifications-python-client==1.0.0
git+https://github.com/alphagov/notifications-utils.git@8.7.1#egg=notifications-utils==8.7.1
git+https://github.com/alphagov/notifications-utils.git@8.7.2#egg=notifications-utils==8.7.2

View File

@@ -1,5 +1,5 @@
#!/bin/bash
echo "Chown application to be owned by ubuntu"
cd /home/ubuntu/;
chown -R ubuntu:ubuntu notifications-admin
cd /home/notify-app/;
chown -R notify-app:govuk-notify-applications notifications-admin

View File

@@ -1,5 +1,5 @@
#!/bin/bash
echo "Install dependencies"
cd /home/ubuntu/notifications-admin;
pip3 install -r /home/ubuntu/notifications-admin/requirements.txt
cd /home/notify-app/notifications-admin;
pip3 install -r /home/notify-app/notifications-admin/requirements.txt

View File

@@ -1,5 +1,4 @@
#!/bin/bash
echo "Starting application"
cd ~/notifications-admin/;
sudo service notifications-admin start

View File

@@ -101,7 +101,7 @@ reset_waiter_timeout() {
fi
# Base register/deregister action may take up to about 30 seconds
timeout=$((timeout + 30))
timeout=$((timeout + 60))
WAITER_ATTEMPTS=$((timeout / WAITER_INTERVAL))
}
@@ -183,7 +183,7 @@ get_instance_health_elb() {
;;
*)
msg "Instance '$instance_id' not part of ELB '$elb_name'"
return 1
return 0
esac
fi
}

View File

@@ -30,8 +30,6 @@ display_result $? 1 "Code style check"
npm test
display_result $? 2 "Front end code style check"
export NOTIFY_ADMIN_ENVIRONMENT='config.Test'
## Code coverage
py.test -n2 --cov=app --cov-report=term-missing tests/
display_result $? 3 "Code coverage"

View File

@@ -1,3 +1,4 @@
from datetime import datetime
import copy
from flask import url_for
@@ -5,7 +6,7 @@ import pytest
from bs4 import BeautifulSoup
from freezegun import freeze_time
from app.main.views.dashboard import get_dashboard_totals
from app.main.views.dashboard import get_dashboard_totals, format_weekly_stats_to_list
from tests import validate_route_permission
from tests.conftest import SERVICE_ONE_ID
@@ -65,7 +66,6 @@ def test_get_started(
api_user_active,
mock_get_service,
mock_get_service_templates_when_no_templates_exist,
mock_get_aggregate_service_statistics,
mock_get_user,
mock_get_user_by_email,
mock_login,
@@ -94,7 +94,6 @@ def test_get_started_is_hidden_once_templates_exist(
api_user_active,
mock_get_service,
mock_get_service_templates,
mock_get_aggregate_service_statistics,
mock_get_user,
mock_get_user_by_email,
mock_login,
@@ -119,7 +118,6 @@ def test_should_show_recent_templates_on_dashboard(app_,
api_user_active,
mock_get_service,
mock_get_service_templates,
mock_get_aggregate_service_statistics,
mock_get_user,
mock_get_user_by_email,
mock_login,
@@ -204,7 +202,6 @@ def test_should_show_recent_jobs_on_dashboard(
api_user_active,
mock_get_service,
mock_get_service_templates,
mock_get_aggregate_service_statistics,
mock_get_user,
mock_get_user_by_email,
mock_login,
@@ -482,3 +479,46 @@ def test_get_dashboard_totals_adds_warning(failures, expected):
}
}
assert get_dashboard_totals(stats)['sms']['show_warning'] == expected
def test_format_weekly_stats_to_list_empty_case():
assert format_weekly_stats_to_list({}) == []
def test_format_weekly_stats_to_list_sorts_by_week():
stats = {
'2016-07-04': {},
'2016-07-11': {},
'2016-07-18': {},
'2016-07-25': {}
}
resp = format_weekly_stats_to_list(stats)
assert resp[0]['week_start'] == '2016-07-25'
assert resp[1]['week_start'] == '2016-07-18'
assert resp[2]['week_start'] == '2016-07-11'
assert resp[3]['week_start'] == '2016-07-04'
def test_format_weekly_stats_to_list_includes_datetime_for_comparison():
stats = {
'2016-07-25': {}
}
resp = format_weekly_stats_to_list(stats)
assert resp == [{
'week_start': '2016-07-25',
'week_end': '2016-07-31',
'week_end_datetime': datetime(2016, 7, 31, 0, 0, 0)
}]
def test_format_weekly_stats_to_list_has_stats_with_failure_rate():
stats = {
'2016-07-25': {'sms': _stats(3, 1, 2)}
}
resp = format_weekly_stats_to_list(stats)
assert resp[0]['sms']['failure_rate'] == '66.7'
assert resp[0]['sms']['requested'] == 3
def _stats(requested, delivered, failed):
return {'requested': requested, 'delivered': delivered, 'failed': failed}

View File

@@ -26,17 +26,27 @@ def test_get_feedback_page(app_):
assert resp.status_code == 200
def test_post_feedback_with_no_name_email(app_, mocker):
def test_post_feedback_with_name_but_no_email(app_, mocker):
mock_post = mocker.patch(
'app.main.views.feedback.requests.post',
return_value=Mock(status_code=201))
with app_.test_request_context():
with app_.test_client() as client:
resp = client.post(url_for('main.feedback'), data={'feedback': "blah"})
resp = client.post(url_for('main.feedback'), data={'feedback': "blah", 'name': 'Fred'})
assert resp.status_code == 302
mock_post.assert_called_with(
ANY,
data={
'department_id': ANY,
'agent_team_id': ANY,
'subject': 'Notify feedback',
'message': 'Environment: http://localhost/\nFred (no email address supplied)\nblah',
'person_email': app_.config['DESKPRO_PERSON_EMAIL'],
'person_name': 'Fred'},
headers=ANY)
def test_post_feedback_with_no_name_email(app_, mocker):
def test_post_feedback_with_no_name_or_email(app_, mocker):
mock_post = mocker.patch(
'app.main.views.feedback.requests.post',
return_value=Mock(status_code=201))
@@ -50,8 +60,9 @@ def test_post_feedback_with_no_name_email(app_, mocker):
'department_id': ANY,
'agent_team_id': ANY,
'subject': 'Notify feedback',
'message': 'Environment: http://localhost/\n\n\n\nblah',
'person_email': ANY},
'message': 'Environment: http://localhost/\n (no email address supplied)\nblah',
'person_email': app_.config['DESKPRO_PERSON_EMAIL'],
'person_name': None},
headers=ANY)
@@ -71,8 +82,9 @@ def test_post_feedback_with_name_email(app_, mocker):
'subject': 'Notify feedback',
'department_id': ANY,
'agent_team_id': ANY,
'message': 'Environment: http://localhost/\n\nSteve Irwin\nrip@gmail.com\nblah',
'person_email': ANY},
'message': 'Environment: http://localhost/\n\nblah',
'person_name': 'Steve Irwin',
'person_email': 'rip@gmail.com'},
headers=ANY)
@@ -91,14 +103,6 @@ def test_log_error_on_post(app_, mocker):
resp = client.post(
url_for('main.feedback'),
data={'feedback': "blah", 'name': "Steve Irwin", 'email_address': 'rip@gmail.com'})
mock_post.assert_called_with(
ANY,
data={
'subject': 'Notify feedback',
'department_id': ANY,
'agent_team_id': ANY,
'message': 'Environment: http://localhost/\n\nSteve Irwin\nrip@gmail.com\nblah',
'person_email': ANY},
headers=ANY)
assert mock_post.called
mock_logger.assert_called_with(
"Deskpro create ticket request failed with {} '{}'".format(mock_post().status_code, mock_post().json()))

View File

@@ -32,15 +32,11 @@ def test_should_return_list_of_all_jobs(app_,
"status_argument, expected_api_call", [
(
'',
['sending', 'delivered', 'failed', 'temporary-failure', 'permanent-failure', 'technical-failure']
),
(
'processed',
['sending', 'delivered', 'failed', 'temporary-failure', 'permanent-failure', 'technical-failure']
['created', 'sending', 'delivered', 'failed', 'temporary-failure', 'permanent-failure', 'technical-failure']
),
(
'sending',
['sending']
['sending', 'created']
),
(
'delivered',
@@ -99,6 +95,7 @@ def test_should_show_page_for_one_job(
)
assert csv_link.text == 'Download this report'
assert page.find('span', {'id': 'time-left'}).text == 'Data available for 7 days'
assert page.find('p', {'class': 'table-show-more-link'}).text.strip() == 'Only showing the first 50 rows'
mock_get_notifications.assert_called_with(
service_one['id'],
fake_uuid,
@@ -106,6 +103,29 @@ def test_should_show_page_for_one_job(
)
def test_should_show_job_in_progress(
app_,
service_one,
active_user_with_permissions,
mock_get_service_template,
mock_get_job_in_progress,
mocker,
mock_get_notifications,
fake_uuid
):
with app_.test_request_context(), app_.test_client() as client:
client.login(active_user_with_permissions, mocker, service_one)
response = client.get(url_for(
'main.view_job',
service_id=service_one['id'],
job_id=fake_uuid
))
assert response.status_code == 200
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
assert page.find('p', {'class': 'hint'}).text.strip() == 'Report is 50% complete…'
def test_should_show_not_show_csv_download_in_tour(
app_,
service_one,
@@ -179,12 +199,12 @@ def test_should_show_updates_for_one_job_as_json(
@pytest.mark.parametrize(
"status_argument, expected_api_call", [
(
'processed',
['sending', 'delivered', 'failed', 'temporary-failure', 'permanent-failure', 'technical-failure']
'',
['created', 'sending', 'delivered', 'failed', 'temporary-failure', 'permanent-failure', 'technical-failure']
),
(
'sending',
['sending']
['sending', 'created']
),
(
'delivered',
@@ -275,10 +295,21 @@ def test_should_show_notifications_for_a_service_with_next_previous(
))
assert response.status_code == 200
content = response.get_data(as_text=True)
assert url_for('main.view_notifications', service_id=service_one['id'], message_type='sms', page=3) in content
assert url_for('main.view_notifications', service_id=service_one['id'], message_type='sms', page=1) in content
assert 'Previous page' in content
assert 'Next page' in content
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
next_page_link = page.find('a', {'rel': 'next'})
prev_page_link = page.find('a', {'rel': 'previous'})
assert (
url_for('main.view_notifications', service_id=service_one['id'], message_type='sms', page=3) in
next_page_link['href']
)
assert 'Next page' in next_page_link.text.strip()
assert 'page 3' in next_page_link.text.strip()
assert (
url_for('main.view_notifications', service_id=service_one['id'], message_type='sms', page=1) in
prev_page_link['href']
)
assert 'Previous page' in prev_page_link.text.strip()
assert 'page 1' in prev_page_link.text.strip()
@freeze_time("2016-01-01 11:09:00.061258")
@@ -335,7 +366,7 @@ def test_get_status_filters_calculates_stats(app_):
ret = get_status_filters({'id': 'foo'}, 'sms', STATISTICS)
assert {label: count for label, _option, _link, count in ret} == {
'processed': 6,
'total': 6,
'sending': 3,
'failed': 2,
'delivered': 1
@@ -347,7 +378,7 @@ def test_get_status_filters_in_right_order(app_):
ret = get_status_filters({'id': 'foo'}, 'sms', STATISTICS)
assert [label for label, _option, _link, _count in ret] == [
'processed', 'sending', 'delivered', 'failed'
'total', 'sending', 'delivered', 'failed'
]

View File

@@ -241,10 +241,9 @@ def test_should_redirect_after_request_to_go_live(
'subject': 'Request to go live',
'department_id': ANY,
'agent_team_id': ANY,
'message': 'From Test User <test@user.gov.uk> on behalf of Test Service (http://localhost/services/6ce466d0-fd6a-11e5-82f5-e0accb9d11a6/dashboard)\n\nUsage estimate\n---\n\nOne million messages', # noqa
# noqa
# noqa
'person_email': ANY
'message': 'On behalf of Test Service (http://localhost/services/6ce466d0-fd6a-11e5-82f5-e0accb9d11a6/dashboard)\n\nUsage estimate\n---\n\nOne million messages', # noqa
'person_name': api_user_active.name,
'person_email': api_user_active.email_address
},
headers=ANY
)

View File

@@ -217,12 +217,23 @@ def mock_delete_service(mocker, mock_get_service):
@pytest.fixture(scope='function')
def mock_get_aggregate_service_statistics(mocker):
def _create(service_id, limit_days=None):
return {'data': [{}]}
def mock_get_service_statistics_for_day(mocker):
stats = {'day': datetime.today().date().strftime('%Y-%m-%d'),
'emails_delivered': 0,
'sms_requested': 0,
'sms_delivered': 0,
'sms_failed': 0,
'emails_requested': 0,
'emails_failed': 0,
'service': fake_uuid,
'id': fake_uuid}
def _stats(service_id, day):
return {'data': stats}
return mocker.patch(
'app.statistics_api_client.get_7_day_aggregate_for_service', side_effect=_create)
'app.statistics_api_client.get_statistics_for_service_for_day', side_effect=_stats)
@pytest.fixture(scope='function')
@@ -854,6 +865,18 @@ def mock_get_job(mocker, api_user_active):
return mocker.patch('app.job_api_client.get_job', side_effect=_get_job)
@pytest.fixture(scope='function')
def mock_get_job_in_progress(mocker, api_user_active):
def _get_job(service_id, job_id):
return {"data": job_json(
service_id, api_user_active, job_id=job_id,
notification_count=10,
notifications_sent=5
)}
return mocker.patch('app.job_api_client.get_job', side_effect=_get_job)
@pytest.fixture(scope='function')
def mock_get_jobs(mocker, api_user_active):
def _get_jobs(service_id, limit_days=None):
@@ -894,14 +917,16 @@ def mock_get_notifications(mocker, api_user_active):
template={'template_type': set_template_type, 'name': 'name', 'id': 'id', 'version': 1},
rows=rows,
status=set_status,
job=job
job=job,
with_links=True
)
else:
return notification_json(
service_id,
rows=rows,
status=set_status,
job=job
job=job,
with_links=True
)
return mocker.patch(

12
wsgi.py
View File

@@ -1,21 +1,9 @@
from credstash import getAllSecrets
import os
default_env_file = '/home/ubuntu/environment'
environment = 'live'
if os.path.isfile(default_env_file):
with open(default_env_file, 'r') as environment_file:
environment = environment_file.readline().strip()
# on aws get secrets and export to env
os.environ.update(getAllSecrets(region="eu-west-1"))
from config import configs # noqa
os.environ['NOTIFY_ADMIN_ENVIRONMENT'] = configs[environment]
from app import create_app # noqa
application = create_app()