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: directories:
- node_modules - node_modules
python: python:
- '3.4.3' - 3.4.3
env: env:
secure: jT9BIioqBMkOdLZhU+WJNdnRJ+06G7qUx4QqEVldp96dJwmWpPEvA0XbitdnQt/WXYkpMlDbgSApvvGj2ZNvdpowRRe5HFX8D2Udhi2g9+cXgKrQxH6zv0evJyQLOjCINW6KtgMCJ5wkYR3qQ4BQawlDt6ecpmeboKTmvs2W8jZ09aV4IKKvdd7BwFon10QVPF5ny10G83unLtKnKgRMjSSLnaEiA78pE/LSUkekK4mhmtl+yfQf60cIuQGcN9NCYIt5PrdYYyMkbUaht9ykwL2C11sp5JYPClI9k6lrlpGJCdL9wbJwejGhR/pEqwJ4tKK8Zv+mngmkbzE6fd5ehuRMnIUAifG4t3p6WbhKwY5pJsdVyPgWcRSPXOJA7yEcAeTAvWcC++6mCIFBeMxt/yQNw02jkFHeNKRh2twTRvr4xWZHq9FsVxTEVz89OOuue3IkkyDNmVusGJ9+AVRIn9Oa+U/r3bDnrs7jz+meSwb82GZUBzFpUe2pe8qeBE572Ay7yHB73VHUgp/2A1qkZ4SnTjTpMbnS5RdXTgwtMkOs5MLZgteCVxFL3sHcr9e/B3UIUnzKUSPXXOjHyDxBwrABWo81V9Vp2IPV7P9Ofv8zroudjQxK5MOcbmiPQF+eEB9L4DvkUBNsGxtJ/nmPp6tmN0Xjo0xXVdZCEVj29Og= secure: jT9BIioqBMkOdLZhU+WJNdnRJ+06G7qUx4QqEVldp96dJwmWpPEvA0XbitdnQt/WXYkpMlDbgSApvvGj2ZNvdpowRRe5HFX8D2Udhi2g9+cXgKrQxH6zv0evJyQLOjCINW6KtgMCJ5wkYR3qQ4BQawlDt6ecpmeboKTmvs2W8jZ09aV4IKKvdd7BwFon10QVPF5ny10G83unLtKnKgRMjSSLnaEiA78pE/LSUkekK4mhmtl+yfQf60cIuQGcN9NCYIt5PrdYYyMkbUaht9ykwL2C11sp5JYPClI9k6lrlpGJCdL9wbJwejGhR/pEqwJ4tKK8Zv+mngmkbzE6fd5ehuRMnIUAifG4t3p6WbhKwY5pJsdVyPgWcRSPXOJA7yEcAeTAvWcC++6mCIFBeMxt/yQNw02jkFHeNKRh2twTRvr4xWZHq9FsVxTEVz89OOuue3IkkyDNmVusGJ9+AVRIn9Oa+U/r3bDnrs7jz+meSwb82GZUBzFpUe2pe8qeBE572Ay7yHB73VHUgp/2A1qkZ4SnTjTpMbnS5RdXTgwtMkOs5MLZgteCVxFL3sHcr9e/B3UIUnzKUSPXXOjHyDxBwrABWo81V9Vp2IPV7P9Ofv8zroudjQxK5MOcbmiPQF+eEB9L4DvkUBNsGxtJ/nmPp6tmN0Xjo0xXVdZCEVj29Og=
before_install: before_install:
@@ -26,70 +26,70 @@ notifications:
secure: A6n6Gdz3dsE+KQcOd1nWTvdjOF2YbgItT1E40r25poG6p04WHd8qWtC4T2FuZaxPN/TQdKr/dKa/WCkmiEdxT5O0SOwAnAD3u6Fn2nthoI4M5916UrK1ZrqupvnFPSQc8Ivh51PGkcmB4wrb0ylRhMB94RmLcUZcVuXLDx57GO8bPFyLC3E9bgcVVFWaX45sKs74sBSQWi9EBbzHIuduLdjIpW7wX07dA++HlY14W5WgiurmiYohfP11VdAMmMxJs2WdWk16O/qy0HZXaldNIsSnuDBkhAZOMeSrcvp+62yOiN8jK0nSa1IRr3IoUkITdC9YGys3xFJb8gyIQE9T3hUnTYAKCcgsgpVFS6UzsRN42JUAJ8rFTgK9/J299yTk4lqL8uWzcV1QcKXIPNoG0QfqkmlB9B1fKbXuE/KkPEXPCKAcVQpCzEon09FgTCrlVZqJ6HxQonnLcPlIpVzWHAFokLZVHLAFMKYJnGBcZ6zaRK5pdc1babcOXMIPBC8j028G5bhBaCviDvZlimxOsUK1sJTpjzMU0tBQZa8lI+0O5otvMKiX8jPyaedjVvUmsftF2O5FH5nz2ofJC7BThb76/Tac2pNTCn0pWiVz9wi/YXALOMdIzkYgHnyZdEqAjRlpFwZuOrzR6MuvivBebPxjYaRWzCjOeC1uIwz+48E= secure: A6n6Gdz3dsE+KQcOd1nWTvdjOF2YbgItT1E40r25poG6p04WHd8qWtC4T2FuZaxPN/TQdKr/dKa/WCkmiEdxT5O0SOwAnAD3u6Fn2nthoI4M5916UrK1ZrqupvnFPSQc8Ivh51PGkcmB4wrb0ylRhMB94RmLcUZcVuXLDx57GO8bPFyLC3E9bgcVVFWaX45sKs74sBSQWi9EBbzHIuduLdjIpW7wX07dA++HlY14W5WgiurmiYohfP11VdAMmMxJs2WdWk16O/qy0HZXaldNIsSnuDBkhAZOMeSrcvp+62yOiN8jK0nSa1IRr3IoUkITdC9YGys3xFJb8gyIQE9T3hUnTYAKCcgsgpVFS6UzsRN42JUAJ8rFTgK9/J299yTk4lqL8uWzcV1QcKXIPNoG0QfqkmlB9B1fKbXuE/KkPEXPCKAcVQpCzEon09FgTCrlVZqJ6HxQonnLcPlIpVzWHAFokLZVHLAFMKYJnGBcZ6zaRK5pdc1babcOXMIPBC8j028G5bhBaCviDvZlimxOsUK1sJTpjzMU0tBQZa8lI+0O5otvMKiX8jPyaedjVvUmsftF2O5FH5nz2ofJC7BThb76/Tac2pNTCn0pWiVz9wi/YXALOMdIzkYgHnyZdEqAjRlpFwZuOrzR6MuvivBebPxjYaRWzCjOeC1uIwz+48E=
deploy: deploy:
- provider: s3 - provider: s3
access_key_id: AKIAJ5MKF6G3P2JQP4QQ access_key_id: AKIAJLWYN4T4D5WU4APA
secret_access_key: &1 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 local_dir: dpl_cd_upload
skip_cleanup: true 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 on: &2
repo: alphagov/notifications-admin repo: alphagov/notifications-admin
branch: staging branch: staging
bucket: staging-notifications-admin-codedeploy bucket: staging-notify.works-notifications-admin-codedeploy
region: eu-west-1
- provider: codedeploy - provider: codedeploy
access_key_id: AKIAJ5MKF6G3P2JQP4QQ access_key_id: AKIAJCOSRR7IFXS2WJUA
secret_access_key: *1 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 key: notifications-admin-$TRAVIS_BRANCH-$TRAVIS_BUILD_NUMBER-$TRAVIS_COMMIT.zip
bundle_type: zip bundle_type: zip
application: notifications-admin application: admin
deployment_group: staging_admin_deployment_group deployment_group: notifications_admin_deployment_group
region: eu-west-1 region: eu-west-1
on: *2 on: *2
- provider: s3 - provider: s3
access_key_id: AKIAJ5MKF6G3P2JQP4QQ access_key_id: AKIAIFLN7IJIIQT6S37Q
secret_access_key: &1 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 local_dir: dpl_cd_upload
skip_cleanup: true skip_cleanup: true
region: eu-west-1
on: &2 on: &2
repo: alphagov/notifications-admin repo: alphagov/notifications-admin
branch: live branch: live
bucket: live-notifications-admin-codedeploy bucket: notifications.service.gov.uk-notifications-admin-codedeploy
region: eu-west-1
- provider: codedeploy - provider: codedeploy
access_key_id: AKIAJ5MKF6G3P2JQP4QQ access_key_id: AKIAIFLN7IJIIQT6S37Q
secret_access_key: *1 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 key: notifications-admin-$TRAVIS_BRANCH-$TRAVIS_BUILD_NUMBER-$TRAVIS_COMMIT.zip
bundle_type: zip bundle_type: zip
application: notifications-admin application: 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
deployment_group: notifications_admin_deployment_group deployment_group: notifications_admin_deployment_group
region: eu-west-1 region: eu-west-1
on: *2 on: *2
before_deploy: before_deploy:
- ./scripts/update_version_file.sh - ./scripts/update_version_file.sh
- rm -rf node_modules bower_components app/assets - rm -rf node_modules bower_components app/assets
- zip -r --exclude=*__pycache__* notifications-admin * - zip -r --exclude=*__pycache__* notifications-admin *
- mkdir -p dpl_cd_upload - mkdir -p dpl_cd_upload
- mv notifications-admin.zip dpl_cd_upload/notifications-admin-$TRAVIS_BRANCH-$TRAVIS_BUILD_NUMBER-$TRAVIS_COMMIT.zip - 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 " echo "
export NOTIFY_ADMIN_ENVIRONMENT='config.Development' export NOTIFY_ENVIRONMENT='development'
export ADMIN_CLIENT_SECRET='dev-notify-secret-key' export ADMIN_CLIENT_SECRET='dev-notify-secret-key'
export ADMIN_CLIENT_USER_NAME='dev-notify-admin' export ADMIN_CLIENT_USER_NAME='dev-notify-admin'
export API_HOST_NAME='http://localhost:6011' 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 SECRET_KEY='dev-notify-secret-key'
export DESKPRO_API_HOST="" export DESKPRO_API_HOST=""
export DESKPRO_API_KEY="" export DESKPRO_API_KEY=""
export DESKPRO_PERSON_EMAIL=""
export DESKPRO_DEPT_ID="" export DESKPRO_DEPT_ID=""
export DESKPRO_ASSIGNED_AGENT_TEAM_ID="" export DESKPRO_ASSIGNED_AGENT_TEAM_ID=""
"> environment.sh "> environment.sh

View File

@@ -13,8 +13,8 @@ from flask import (
current_app, current_app,
request, request,
g, g,
url_for url_for)
)
from flask._compat import string_types from flask._compat import string_types
from flask.globals import _lookup_req_object from flask.globals import _lookup_req_object
from flask_login import LoginManager from flask_login import LoginManager
@@ -67,9 +67,11 @@ current_service = LocalProxy(partial(_lookup_req_object, 'service'))
def create_app(): def create_app():
from config import configs
application = Flask(__name__) application = Flask(__name__)
application.config.from_object(os.environ['NOTIFY_ADMIN_ENVIRONMENT']) application.config.from_object(configs[os.environ['NOTIFY_ENVIRONMENT']])
init_app(application) init_app(application)
logging.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', 'temporary-failure': 'Inbox not accepting messages right now',
'permanent-failure': 'Email address doesnt exist', 'permanent-failure': 'Email address doesnt exist',
'delivered': 'Delivered', 'delivered': 'Delivered',
'sending': 'Sending' 'sending': 'Sending',
'created': 'Sending'
}, },
'sms': { 'sms': {
'failed': 'Failed', 'failed': 'Failed',
@@ -249,7 +252,8 @@ def format_notification_status(status, template_type):
'temporary-failure': 'Phone not accepting messages right now', 'temporary-failure': 'Phone not accepting messages right now',
'permanent-failure': 'Phone number doesnt exist', 'permanent-failure': 'Phone number doesnt exist',
'delivered': 'Delivered', 'delivered': 'Delivered',
'sending': 'Sending' 'sending': 'Sending',
'created': 'Sending'
} }
}.get(template_type).get(status, status) }.get(template_type).get(status, status)
@@ -261,7 +265,8 @@ def format_notification_status_as_field_status(status):
'temporary-failure': 'error', 'temporary-failure': 'error',
'permanent-failure': 'error', 'permanent-failure': 'error',
'delivered': None, 'delivered': None,
'sending': 'default' 'sending': 'default',
'created': 'default'
}.get(status, 'error') }.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; margin-bottom: $gutter-two-thirds;
} }
.bottom-gutter-1-2 {
margin-bottom: $gutter-half;
}
.align-with-heading { .align-with-heading {
display: block; display: block;
text-align: center; 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 collections import namedtuple
from itertools import groupby from itertools import groupby
import dateutil
from flask import ( from flask import (
render_template, render_template,
url_for, url_for,
@@ -9,13 +10,11 @@ from flask import (
jsonify, jsonify,
current_app current_app
) )
from flask_login import login_required from flask_login import login_required
from app.main import main from app.main import main
from app import ( from app import (
job_api_client, job_api_client,
statistics_api_client,
service_api_client, service_api_client,
template_statistics_client template_statistics_client
) )
@@ -90,20 +89,10 @@ def usage(service_id):
@login_required @login_required
@user_has_permissions('manage_settings', admin_override=True) @user_has_permissions('manage_settings', admin_override=True)
def weekly(service_id): def weekly(service_id):
stats = service_api_client.get_weekly_notification_stats(service_id)['data']
earliest_date = date(2016, 4, 1) # start of tax year
while earliest_date.weekday() != 0: # 0 for monday
earliest_date -= timedelta(days=1)
return render_template( return render_template(
'views/weekly.html', 'views/weekly.html',
days=( days=format_weekly_stats_to_list(stats),
add_rates_to(day) for day in
statistics_api_client.get_7_day_aggregate_for_service(
service_id,
date_from=earliest_date
)['data']
),
now=datetime.utcnow() now=datetime.utcnow()
) )
@@ -199,3 +188,21 @@ def calculate_usage(usage):
'sms_chargeable': max(0, sms_sent - sms_free_allowance), 'sms_chargeable': max(0, sms_sent - sms_free_allowance),
'sms_rate': sms_rate '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(): def feedback():
form = Feedback() form = Feedback()
if form.validate_on_submit(): 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 = { 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'), 'department_id': current_app.config.get('DESKPRO_DEPT_ID'),
'agent_team_id': current_app.config.get('DESKPRO_ASSIGNED_AGENT_TEAM_ID'), 'agent_team_id': current_app.config.get('DESKPRO_ASSIGNED_AGENT_TEAM_ID'),
'subject': 'Notify feedback', 'subject': 'Notify feedback',
'message': 'Environment: {}\n\n{}\n{}\n{}'.format( 'message': feedback_msg
url_for('main.index', _external=True),
form.name.data,
form.email_address.data,
form.feedback.data
)
} }
headers = { headers = {
"X-DeskPRO-API-Key": current_app.config.get('DESKPRO_API_KEY'), "X-DeskPRO-API-Key": current_app.config.get('DESKPRO_API_KEY'),

View File

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

View File

@@ -204,6 +204,9 @@ class ServiceAPIClient(NotificationsAPIClient):
def get_service_usage(self, service_id): def get_service_usage(self, service_id):
return self.get('/service/{0}/fragment/aggregate_statistics'.format(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): class ServicesBrowsableItem(BrowsableItem):
@property @property

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
{% if help %} {% if help %}
{% call banner_wrapper(type='tour') %} {% 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="grid-row bottom-gutter" {% if help != '1' %}style="opacity: 0.6"{% endif %}>
<div class="column-one-sixth"> <div class="column-one-sixth">
<p class="heading-large" style="float: left;">1.</p> <p class="heading-large" style="float: left;">1.</p>

View File

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

View File

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

View File

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

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

View File

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

View File

@@ -23,7 +23,7 @@
{% endcall %} {% endcall %}
{% call field() %} {% call field() %}
{{ big_number( {{ 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 smallest=True
) }} ) }}
{% endcall %} {% endcall %}

View File

@@ -19,7 +19,7 @@
{% if not templates %} {% if not templates %}
{% include 'views/dashboard/write-first-messages.html' %} {% include 'views/dashboard/write-first-messages.html' %}
{% endif %} {% 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' %} {% include 'views/dashboard/no-permissions-banner.html' %}
{% endif %} {% endif %}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,6 @@ from datetime import timedelta
class Config(object): class Config(object):
DEBUG = False DEBUG = False
ADMIN_CLIENT_SECRET = os.environ['ADMIN_CLIENT_SECRET'] ADMIN_CLIENT_SECRET = os.environ['ADMIN_CLIENT_SECRET']
ADMIN_CLIENT_USER_NAME = os.environ['ADMIN_CLIENT_USER_NAME'] 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' CSV_UPLOAD_BUCKET_NAME = 'local-notifications-csv-upload'
DESKPRO_API_HOST = os.environ['DESKPRO_API_HOST'] DESKPRO_API_HOST = os.environ['DESKPRO_API_HOST']
DESKPRO_API_KEY = os.environ['DESKPRO_API_KEY'] 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_DEPT_ID = os.environ['DESKPRO_DEPT_ID']
DESKPRO_ASSIGNED_AGENT_TEAM_ID = os.environ['DESKPRO_ASSIGNED_AGENT_TEAM_ID'] DESKPRO_ASSIGNED_AGENT_TEAM_ID = os.environ['DESKPRO_ASSIGNED_AGENT_TEAM_ID']
ACTIVITY_STATS_LIMIT_DAYS = 7 ACTIVITY_STATS_LIMIT_DAYS = 7
@@ -78,10 +77,24 @@ class Preview(Config):
CSV_UPLOAD_BUCKET_NAME = 'preview-notifications-csv-upload' 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 = { configs = {
'development': 'config.Development', 'development': Development,
'test': 'config.Test', 'test': Test,
'preview': 'config.Preview', 'preview': Preview,
'staging': 'config_staging.Staging', 'staging': Staging,
'live': 'config_live.Live' '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 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 * 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> <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_SECRET='dev-notify-secret-key'
export ADMIN_CLIENT_USER_NAME='dev-notify-admin' export ADMIN_CLIENT_USER_NAME='dev-notify-admin'
export API_HOST_NAME='' export API_HOST_NAME=''
@@ -6,6 +6,5 @@ export DANGEROUS_SALT='dev-notify-salt'
export SECRET_KEY='dev-notify-secret-key' export SECRET_KEY='dev-notify-secret-key'
export DESKPRO_API_HOST="" export DESKPRO_API_HOST=""
export DESKPRO_API_KEY="" export DESKPRO_API_KEY=""
export DESKPRO_PERSON_EMAIL=""
export DESKPRO_DEPT_ID="" export DESKPRO_DEPT_ID=""
export DESKPRO_ASSIGNED_AGENT_TEAM_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-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 #!/bin/bash
echo "Chown application to be owned by ubuntu" echo "Chown application to be owned by ubuntu"
cd /home/ubuntu/; cd /home/notify-app/;
chown -R ubuntu:ubuntu notifications-admin chown -R notify-app:govuk-notify-applications notifications-admin

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,4 @@
from datetime import datetime
import copy import copy
from flask import url_for from flask import url_for
@@ -5,7 +6,7 @@ import pytest
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from freezegun import freeze_time 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 import validate_route_permission
from tests.conftest import SERVICE_ONE_ID from tests.conftest import SERVICE_ONE_ID
@@ -65,7 +66,6 @@ def test_get_started(
api_user_active, api_user_active,
mock_get_service, mock_get_service,
mock_get_service_templates_when_no_templates_exist, mock_get_service_templates_when_no_templates_exist,
mock_get_aggregate_service_statistics,
mock_get_user, mock_get_user,
mock_get_user_by_email, mock_get_user_by_email,
mock_login, mock_login,
@@ -94,7 +94,6 @@ def test_get_started_is_hidden_once_templates_exist(
api_user_active, api_user_active,
mock_get_service, mock_get_service,
mock_get_service_templates, mock_get_service_templates,
mock_get_aggregate_service_statistics,
mock_get_user, mock_get_user,
mock_get_user_by_email, mock_get_user_by_email,
mock_login, mock_login,
@@ -119,7 +118,6 @@ def test_should_show_recent_templates_on_dashboard(app_,
api_user_active, api_user_active,
mock_get_service, mock_get_service,
mock_get_service_templates, mock_get_service_templates,
mock_get_aggregate_service_statistics,
mock_get_user, mock_get_user,
mock_get_user_by_email, mock_get_user_by_email,
mock_login, mock_login,
@@ -204,7 +202,6 @@ def test_should_show_recent_jobs_on_dashboard(
api_user_active, api_user_active,
mock_get_service, mock_get_service,
mock_get_service_templates, mock_get_service_templates,
mock_get_aggregate_service_statistics,
mock_get_user, mock_get_user,
mock_get_user_by_email, mock_get_user_by_email,
mock_login, mock_login,
@@ -482,3 +479,46 @@ def test_get_dashboard_totals_adds_warning(failures, expected):
} }
} }
assert get_dashboard_totals(stats)['sms']['show_warning'] == 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 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( mock_post = mocker.patch(
'app.main.views.feedback.requests.post', 'app.main.views.feedback.requests.post',
return_value=Mock(status_code=201)) return_value=Mock(status_code=201))
with app_.test_request_context(): with app_.test_request_context():
with app_.test_client() as client: 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 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( mock_post = mocker.patch(
'app.main.views.feedback.requests.post', 'app.main.views.feedback.requests.post',
return_value=Mock(status_code=201)) return_value=Mock(status_code=201))
@@ -50,8 +60,9 @@ def test_post_feedback_with_no_name_email(app_, mocker):
'department_id': ANY, 'department_id': ANY,
'agent_team_id': ANY, 'agent_team_id': ANY,
'subject': 'Notify feedback', 'subject': 'Notify feedback',
'message': 'Environment: http://localhost/\n\n\n\nblah', 'message': 'Environment: http://localhost/\n (no email address supplied)\nblah',
'person_email': ANY}, 'person_email': app_.config['DESKPRO_PERSON_EMAIL'],
'person_name': None},
headers=ANY) headers=ANY)
@@ -71,8 +82,9 @@ def test_post_feedback_with_name_email(app_, mocker):
'subject': 'Notify feedback', 'subject': 'Notify feedback',
'department_id': ANY, 'department_id': ANY,
'agent_team_id': ANY, 'agent_team_id': ANY,
'message': 'Environment: http://localhost/\n\nSteve Irwin\nrip@gmail.com\nblah', 'message': 'Environment: http://localhost/\n\nblah',
'person_email': ANY}, 'person_name': 'Steve Irwin',
'person_email': 'rip@gmail.com'},
headers=ANY) headers=ANY)
@@ -91,14 +103,6 @@ def test_log_error_on_post(app_, mocker):
resp = client.post( resp = client.post(
url_for('main.feedback'), url_for('main.feedback'),
data={'feedback': "blah", 'name': "Steve Irwin", 'email_address': 'rip@gmail.com'}) data={'feedback': "blah", 'name': "Steve Irwin", 'email_address': 'rip@gmail.com'})
mock_post.assert_called_with( assert mock_post.called
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)
mock_logger.assert_called_with( mock_logger.assert_called_with(
"Deskpro create ticket request failed with {} '{}'".format(mock_post().status_code, mock_post().json())) "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", [ "status_argument, expected_api_call", [
( (
'', '',
['sending', 'delivered', 'failed', 'temporary-failure', 'permanent-failure', 'technical-failure'] ['created', 'sending', 'delivered', 'failed', 'temporary-failure', 'permanent-failure', 'technical-failure']
),
(
'processed',
['sending', 'delivered', 'failed', 'temporary-failure', 'permanent-failure', 'technical-failure']
), ),
( (
'sending', 'sending',
['sending'] ['sending', 'created']
), ),
( (
'delivered', 'delivered',
@@ -99,6 +95,7 @@ def test_should_show_page_for_one_job(
) )
assert csv_link.text == 'Download this report' assert csv_link.text == 'Download this report'
assert page.find('span', {'id': 'time-left'}).text == 'Data available for 7 days' 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( mock_get_notifications.assert_called_with(
service_one['id'], service_one['id'],
fake_uuid, 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( def test_should_show_not_show_csv_download_in_tour(
app_, app_,
service_one, service_one,
@@ -179,12 +199,12 @@ def test_should_show_updates_for_one_job_as_json(
@pytest.mark.parametrize( @pytest.mark.parametrize(
"status_argument, expected_api_call", [ "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'] ['sending', 'created']
), ),
( (
'delivered', 'delivered',
@@ -275,10 +295,21 @@ def test_should_show_notifications_for_a_service_with_next_previous(
)) ))
assert response.status_code == 200 assert response.status_code == 200
content = response.get_data(as_text=True) 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 page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
assert url_for('main.view_notifications', service_id=service_one['id'], message_type='sms', page=1) in content next_page_link = page.find('a', {'rel': 'next'})
assert 'Previous page' in content prev_page_link = page.find('a', {'rel': 'previous'})
assert 'Next page' in content 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") @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) ret = get_status_filters({'id': 'foo'}, 'sms', STATISTICS)
assert {label: count for label, _option, _link, count in ret} == { assert {label: count for label, _option, _link, count in ret} == {
'processed': 6, 'total': 6,
'sending': 3, 'sending': 3,
'failed': 2, 'failed': 2,
'delivered': 1 'delivered': 1
@@ -347,7 +378,7 @@ def test_get_status_filters_in_right_order(app_):
ret = get_status_filters({'id': 'foo'}, 'sms', STATISTICS) ret = get_status_filters({'id': 'foo'}, 'sms', STATISTICS)
assert [label for label, _option, _link, _count in ret] == [ 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', 'subject': 'Request to go live',
'department_id': ANY, 'department_id': ANY,
'agent_team_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 'message': 'On behalf of Test Service (http://localhost/services/6ce466d0-fd6a-11e5-82f5-e0accb9d11a6/dashboard)\n\nUsage estimate\n---\n\nOne million messages', # noqa
# noqa 'person_name': api_user_active.name,
# noqa 'person_email': api_user_active.email_address
'person_email': ANY
}, },
headers=ANY headers=ANY
) )

View File

@@ -217,12 +217,23 @@ def mock_delete_service(mocker, mock_get_service):
@pytest.fixture(scope='function') @pytest.fixture(scope='function')
def mock_get_aggregate_service_statistics(mocker): def mock_get_service_statistics_for_day(mocker):
def _create(service_id, limit_days=None):
return {'data': [{}]} 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( 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') @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) 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') @pytest.fixture(scope='function')
def mock_get_jobs(mocker, api_user_active): def mock_get_jobs(mocker, api_user_active):
def _get_jobs(service_id, limit_days=None): 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}, template={'template_type': set_template_type, 'name': 'name', 'id': 'id', 'version': 1},
rows=rows, rows=rows,
status=set_status, status=set_status,
job=job job=job,
with_links=True
) )
else: else:
return notification_json( return notification_json(
service_id, service_id,
rows=rows, rows=rows,
status=set_status, status=set_status,
job=job job=job,
with_links=True
) )
return mocker.patch( return mocker.patch(

12
wsgi.py
View File

@@ -1,21 +1,9 @@
from credstash import getAllSecrets from credstash import getAllSecrets
import os 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 # on aws get secrets and export to env
os.environ.update(getAllSecrets(region="eu-west-1")) 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 from app import create_app # noqa
application = create_app() application = create_app()