diff --git a/.travis.yml b/.travis.yml index 912637125..e353329e3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/README.md b/README.md index 42be0c2ba..375e7208e 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/app/__init__.py b/app/__init__.py index 71bccc12a..2f39d2bee 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -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 doesn’t 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 doesn’t 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') diff --git a/app/assets/images/email-template/crests/bis_crest_27px_x2.png b/app/assets/images/email-template/crests/bis_crest_27px_x2.png new file mode 100644 index 000000000..f5ef3cabb Binary files /dev/null and b/app/assets/images/email-template/crests/bis_crest_27px_x2.png differ diff --git a/app/assets/images/email-template/crests/coastguard_27px_x2.png b/app/assets/images/email-template/crests/coastguard_27px_x2.png new file mode 100644 index 000000000..b3b146631 Binary files /dev/null and b/app/assets/images/email-template/crests/coastguard_27px_x2.png differ diff --git a/app/assets/images/email-template/crests/hmrc_crest_27px_x2.png b/app/assets/images/email-template/crests/hmrc_crest_27px_x2.png new file mode 100644 index 000000000..cf04d5ed1 Binary files /dev/null and b/app/assets/images/email-template/crests/hmrc_crest_27px_x2.png differ diff --git a/app/assets/images/email-template/crests/ho_crest_27px_x2.png b/app/assets/images/email-template/crests/ho_crest_27px_x2.png new file mode 100644 index 000000000..926ca5fad Binary files /dev/null and b/app/assets/images/email-template/crests/ho_crest_27px_x2.png differ diff --git a/app/assets/images/email-template/crests/mod_crest_27px_x2.png b/app/assets/images/email-template/crests/mod_crest_27px_x2.png new file mode 100644 index 000000000..3d9623ce3 Binary files /dev/null and b/app/assets/images/email-template/crests/mod_crest_27px_x2.png differ diff --git a/app/assets/images/email-template/crests/org_crest_27px_x2.png b/app/assets/images/email-template/crests/org_crest_27px_x2.png new file mode 100644 index 000000000..314000a0e Binary files /dev/null and b/app/assets/images/email-template/crests/org_crest_27px_x2.png differ diff --git a/app/assets/images/email-template/crests/portcullis_27px_x2.png b/app/assets/images/email-template/crests/portcullis_27px_x2.png new file mode 100644 index 000000000..16afaf0f7 Binary files /dev/null and b/app/assets/images/email-template/crests/portcullis_27px_x2.png differ diff --git a/app/assets/images/email-template/crests/so_crest_27px_x2.png b/app/assets/images/email-template/crests/so_crest_27px_x2.png new file mode 100644 index 000000000..41e1bfcd6 Binary files /dev/null and b/app/assets/images/email-template/crests/so_crest_27px_x2.png differ diff --git a/app/assets/images/email-template/crests/ukaea_crest_27px_x2.png b/app/assets/images/email-template/crests/ukaea_crest_27px_x2.png new file mode 100644 index 000000000..0b19917f6 Binary files /dev/null and b/app/assets/images/email-template/crests/ukaea_crest_27px_x2.png differ diff --git a/app/assets/images/email-template/crests/ukho_27px_x2.png b/app/assets/images/email-template/crests/ukho_27px_x2.png new file mode 100644 index 000000000..d6b073b06 Binary files /dev/null and b/app/assets/images/email-template/crests/ukho_27px_x2.png differ diff --git a/app/assets/images/email-template/crests/wales_crest_27px_x2.png b/app/assets/images/email-template/crests/wales_crest_27px_x2.png new file mode 100644 index 000000000..eef1e7312 Binary files /dev/null and b/app/assets/images/email-template/crests/wales_crest_27px_x2.png differ diff --git a/app/assets/images/email-template/spacer.png b/app/assets/images/email-template/spacer.png new file mode 100644 index 000000000..1914264c0 Binary files /dev/null and b/app/assets/images/email-template/spacer.png differ diff --git a/app/assets/stylesheets/_grids.scss b/app/assets/stylesheets/_grids.scss index 5231b67ae..183a85630 100644 --- a/app/assets/stylesheets/_grids.scss +++ b/app/assets/stylesheets/_grids.scss @@ -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; diff --git a/app/main/views/dashboard.py b/app/main/views/dashboard.py index 74072aa2b..67588672a 100644 --- a/app/main/views/dashboard.py +++ b/app/main/views/dashboard.py @@ -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) diff --git a/app/main/views/feedback.py b/app/main/views/feedback.py index 0c21d88bb..73eae1df0 100644 --- a/app/main/views/feedback.py +++ b/app/main/views/feedback.py @@ -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'), diff --git a/app/main/views/jobs.py b/app/main/views/jobs.py index f043ddfca..749fbfa31 100644 --- a/app/main/views/jobs.py +++ b/app/main/views/jobs.py @@ -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//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']) diff --git a/app/main/views/service_settings.py b/app/main/views/service_settings.py index 40e92787b..25a2e4a42 100644 --- a/app/main/views/service_settings.py +++ b/app/main/views/service_settings.py @@ -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 diff --git a/app/notify_client/service_api_client.py b/app/notify_client/service_api_client.py index ae1f5a1fb..d1134406f 100644 --- a/app/notify_client/service_api_client.py +++ b/app/notify_client/service_api_client.py @@ -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 diff --git a/app/notify_client/statistics_api_client.py b/app/notify_client/statistics_api_client.py index ec0a5993a..96cf428a0 100644 --- a/app/notify_client/statistics_api_client.py +++ b/app/notify_client/statistics_api_client.py @@ -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 = { diff --git a/app/templates/components/email-message.html b/app/templates/components/email-message.html index 6099d4341..9766c17d6 100644 --- a/app/templates/components/email-message.html +++ b/app/templates/components/email-message.html @@ -50,7 +50,7 @@ {% if not expanded %}
{% endif %} - {{ body|nl2br }} + {{ body }} {% if not expanded %}
...show full email
diff --git a/app/templates/main_nav.html b/app/templates/main_nav.html index f8fe1ba0e..75c81f8f7 100644 --- a/app/templates/main_nav.html +++ b/app/templates/main_nav.html @@ -2,7 +2,7 @@ {% if help %} {% call banner_wrapper(type='tour') %} -

Try this example

+

Try sending yourself this example

1.

diff --git a/app/templates/partials/jobs/count.html b/app/templates/partials/jobs/count.html index a9f91e093..2755f3259 100644 --- a/app/templates/partials/jobs/count.html +++ b/app/templates/partials/jobs/count.html @@ -1,5 +1,5 @@ {% from "components/pill.html" import pill %}
- {{ pill('Status', counts, status) }} + {{ pill('Status', counts, request.args.get('status', '')) }}
diff --git a/app/templates/partials/jobs/notifications.html b/app/templates/partials/jobs/notifications.html index 27243bfbb..f915e6a11 100644 --- a/app/templates/partials/jobs/notifications.html +++ b/app/templates/partials/jobs/notifications.html @@ -4,12 +4,18 @@
{% endif %} - {% if notifications and not help %} -

- Download this report -   - {{ time_left }} -

+ {% if not help %} + {% if percentage_complete < 100 %} +

+ Report is {{ "{:.0f}%".format(percentage_complete) }} complete… +

+ {% elif notifications %} +

+ Download this report +   + {{ time_left }} +

+ {% endif %} {% endif %} {% call(item, row_number) list_table( @@ -44,6 +50,12 @@ {% endcall %} {% endcall %} + {% if more_than_one_page %} + + {% endif %} + {% if notifications %}
{% endif %} diff --git a/app/templates/partials/templates/guidance-character-count.html b/app/templates/partials/templates/guidance-character-count.html index b11dae62e..9dea399f4 100644 --- a/app/templates/partials/templates/guidance-character-count.html +++ b/app/templates/partials/templates/guidance-character-count.html @@ -1,12 +1,8 @@ -
- Message length -
-

- If your message is long then it will - cost more. -

-

- See pricing for details. -

-
-
+

Message length

+

+ If your message is long then it will + cost more. +

+

+ See pricing for details. +

diff --git a/app/templates/partials/templates/guidance-formatting.html b/app/templates/partials/templates/guidance-formatting.html new file mode 100644 index 000000000..c17fbb452 --- /dev/null +++ b/app/templates/partials/templates/guidance-formatting.html @@ -0,0 +1,27 @@ +

Formatting

+

+ To put a title in your template, use a hash: +

+
+

+ # This is a title +

+
+

+ To make bullet points, use asterisks: +

+
+

+ * point 1
+ * point 2
+ * point 3
+

+
+

+ To add a callout, use a caret: +

+
+

+ ^ You must tell us if your circumstances change +

+
diff --git a/app/templates/partials/templates/guidance-links.html b/app/templates/partials/templates/guidance-links.html index 2d7aab039..34ff7cf82 100644 --- a/app/templates/partials/templates/guidance-links.html +++ b/app/templates/partials/templates/guidance-links.html @@ -1,8 +1,9 @@ -
- Links and URLs -
-

- Always use full URLs, starting with https:// -

-
-
+

Links and URLs

+

+ Always use full URLs, starting with https:// +

+
+

+ Apply now at https://www.gov.uk/example +

+
diff --git a/app/templates/partials/templates/guidance-personalisation.html b/app/templates/partials/templates/guidance-personalisation.html index 3cae73723..bc3c44d91 100644 --- a/app/templates/partials/templates/guidance-personalisation.html +++ b/app/templates/partials/templates/guidance-personalisation.html @@ -1,14 +1,11 @@ -
- Personalisation -
-

- Use double brackets to add personalisation. -

-

- Correct: ((name)) -

-

- Incorrect: ((Helen)) -

-
-
+

+ Personalisation +

+

+ Use double brackets to personalise your message: +

+
+

+ Hello ((first name)), your reference is ((ref number)) +

+
diff --git a/app/templates/views/dashboard/_jobs.html b/app/templates/views/dashboard/_jobs.html index b414e4456..0a2b3a2f7 100644 --- a/app/templates/views/dashboard/_jobs.html +++ b/app/templates/views/dashboard/_jobs.html @@ -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 %} diff --git a/app/templates/views/dashboard/dashboard.html b/app/templates/views/dashboard/dashboard.html index 536476cbb..b504b2a26 100644 --- a/app/templates/views/dashboard/dashboard.html +++ b/app/templates/views/dashboard/dashboard.html @@ -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 %} diff --git a/app/templates/views/edit-email-template.html b/app/templates/views/edit-email-template.html index 1e3db961c..669b0e278 100644 --- a/app/templates/views/edit-email-template.html +++ b/app/templates/views/edit-email-template.html @@ -14,11 +14,11 @@
-
+
{{ textbox(form.name, width='1-1', hint='Your recipients won’t see this', rows=10) }} {{ textbox(form.subject, width='1-1', highlight_tags=True, rows=2) }}
-
+
{{ 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' ) }}
-
-
{% if notifications %} -

- Download as a CSV file +

+ Download this report   - Delivery information is available for 7 days + Data available for 7 days

{% endif %} diff --git a/app/templates/views/weekly.html b/app/templates/views/weekly.html index 10a516290..5a5a87493 100644 --- a/app/templates/views/weekly.html +++ b/app/templates/views/weekly.html @@ -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 %} diff --git a/app/version.py b/app/version.py index 1defd614d..fc30d1fca 100644 --- a/app/version.py +++ b/app/version.py @@ -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__ = "" diff --git a/appspec.yml b/appspec.yml index cb81bb36f..c663536ab 100644 --- a/appspec.yml +++ b/appspec.yml @@ -1,7 +1,7 @@ --- files: - - destination: /home/ubuntu/notifications-admin + destination: /home/notify-app/notifications-admin source: / hooks: AfterInstall: diff --git a/config.py b/config.py index 37df9d749..56586c883 100644 --- a/config.py +++ b/config.py @@ -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 } diff --git a/config_live.py b/config_live.py deleted file mode 100644 index e01535e00..000000000 --- a/config_live.py +++ /dev/null @@ -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 diff --git a/config_staging.py b/config_staging.py deleted file mode 100644 index 775a2d566..000000000 --- a/config_staging.py +++ /dev/null @@ -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 diff --git a/docs/index.md b/docs/index.md index 23a603249..4eb07af8f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -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.

JSON Web Tokens: claims

diff --git a/environment_test.sh b/environment_test.sh index 549aae5f5..1ce9096df 100644 --- a/environment_test.sh +++ b/environment_test.sh @@ -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="" diff --git a/requirements.txt b/requirements.txt index a1ce67cba..b04cc336c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 diff --git a/scripts/aws_change_ownership.sh b/scripts/aws_change_ownership.sh index a0622593f..e800956d3 100755 --- a/scripts/aws_change_ownership.sh +++ b/scripts/aws_change_ownership.sh @@ -1,5 +1,5 @@ #!/bin/bash echo "Chown application to be owned by ubuntu" -cd /home/ubuntu/; -chown -R ubuntu:ubuntu notifications-admin \ No newline at end of file +cd /home/notify-app/; +chown -R notify-app:govuk-notify-applications notifications-admin \ No newline at end of file diff --git a/scripts/aws_install_dependencies.sh b/scripts/aws_install_dependencies.sh index 474fee8a1..e5988d7b2 100755 --- a/scripts/aws_install_dependencies.sh +++ b/scripts/aws_install_dependencies.sh @@ -1,5 +1,5 @@ #!/bin/bash echo "Install dependencies" -cd /home/ubuntu/notifications-admin; -pip3 install -r /home/ubuntu/notifications-admin/requirements.txt \ No newline at end of file +cd /home/notify-app/notifications-admin; +pip3 install -r /home/notify-app/notifications-admin/requirements.txt \ No newline at end of file diff --git a/scripts/aws_start_app.sh b/scripts/aws_start_app.sh index 1c3743f2c..b4082672a 100755 --- a/scripts/aws_start_app.sh +++ b/scripts/aws_start_app.sh @@ -1,5 +1,4 @@ #!/bin/bash echo "Starting application" -cd ~/notifications-admin/; sudo service notifications-admin start \ No newline at end of file diff --git a/scripts/common_functions.sh b/scripts/common_functions.sh index 469d22e6c..09cdbe4e0 100644 --- a/scripts/common_functions.sh +++ b/scripts/common_functions.sh @@ -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 } diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh index 9ef53a3fc..b1d6333f3 100755 --- a/scripts/run_tests.sh +++ b/scripts/run_tests.sh @@ -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" diff --git a/tests/app/main/views/test_dashboard.py b/tests/app/main/views/test_dashboard.py index dc5a35d80..b1cc51ccb 100644 --- a/tests/app/main/views/test_dashboard.py +++ b/tests/app/main/views/test_dashboard.py @@ -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} diff --git a/tests/app/main/views/test_feedback.py b/tests/app/main/views/test_feedback.py index 40ee5a656..fdf758be7 100644 --- a/tests/app/main/views/test_feedback.py +++ b/tests/app/main/views/test_feedback.py @@ -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())) diff --git a/tests/app/main/views/test_jobs.py b/tests/app/main/views/test_jobs.py index 7e3378e63..7bfbcefb2 100644 --- a/tests/app/main/views/test_jobs.py +++ b/tests/app/main/views/test_jobs.py @@ -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' ] diff --git a/tests/app/main/views/test_service_settings.py b/tests/app/main/views/test_service_settings.py index 358aab9e3..1793211dd 100644 --- a/tests/app/main/views/test_service_settings.py +++ b/tests/app/main/views/test_service_settings.py @@ -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 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 ) diff --git a/tests/conftest.py b/tests/conftest.py index 79883f9b7..0bcef48af 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -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( diff --git a/wsgi.py b/wsgi.py index 15ff05b38..db71cf075 100644 --- a/wsgi.py +++ b/wsgi.py @@ -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()