From 9e9990edc329a02ad8253cf75f771fe661b3b966 Mon Sep 17 00:00:00 2001 From: Martyn Inglis Date: Thu, 17 Mar 2016 09:15:25 +0000 Subject: [PATCH 01/12] Adding live codedeploy deployment block - on same account hence same keys as staging - differs only in deployment-group name and S3 bucket --- .travis.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.travis.yml b/.travis.yml index 50eecec26..c49980faa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,6 +41,27 @@ deploy: deployment_group: notifications_admin_deployment_group region: eu-west-1 on: *2 +- provider: s3 + access_key_id: AKIAJ5MKF6G3P2JQP4QQ + secret_access_key: &1 + secure: xfjg4kNBvU0B9xhRETr14mB0bCpVonlAKqGnKL2AoqpnF19yihqGNA8sv/pOGUFpeWZO3cW2GA3anyL2gGG1X0K3f81649mneVJkSHaZ2fiG/S1eKtS0Ws5XblSqLmKTPC7H9ndUxT8r+r2wLg62netBE5g8tAxw2QwN/gVz2fK/68owiyeD/jl6gw/iQ47F+mmGdAY/eFe8sUuGR4Oxj2xNAYARaDQOmHpQF/IG3M69FO5uOJtck8fUWnpd0rWxsyWBOVwwIRQHL6cWOyodeIK7YvLmzviCi1GojBPKQwQbjJu89LHfMJJTW1625drj5CNounuENTFte0Pip8zp90bg090VA8OlTXNWcyFjBQD1vNIE59vyQ/hCh90NK1nlTXdnNOwL0VZTMxQ/zYulXoMqwDLfDozhQbmnkXmexJl6BF4/dz+XmwDu7st5A/PI6U5zCK86ST/6g3MklGSseFi5Rkt6kmJrdlRIhiLnoaab8YgI0FPWjzHBC6B98ZtgIUiUk7Ng5ZTM8Sjq1HCC7mUDrDL0c7aerZA5bq2hQiKGhvjBXFU17iHZ0eEDZ8kO+jumeMwmpW6NbjlS0PuDx+lHywMSG7r+YVmkjxq5gwrTVl3evRxhHe8H/lU18y2dOKpIyX5UEZpXRq9kAWuQruCDBwoDe3Y3QP+Rg7HVGBU= + local_dir: dpl_cd_upload + skip_cleanup: true + on: &2 + repo: alphagov/notifications-admin + branch: live + bucket: live_notifications_admin_deployment_group + region: eu-west-1 +- provider: codedeploy + access_key_id: AKIAJ5MKF6G3P2JQP4QQ + secret_access_key: *1 + bucket: staging-notifications-api-codedeploy + key: notifications-admin-$TRAVIS_BRANCH-$TRAVIS_BUILD_NUMBER-$TRAVIS_COMMIT.zip + bundle_type: zip + application: notifications-admin + deployment_group: live_notifications_admin_deployment_group + region: eu-west-1 + on: *2 - provider: s3 access_key_id: AKIAJQPPNM6P6V53SWKA secret_access_key: &1 From 820a9b811e428ff46bba005638f5be1a7e1f613a Mon Sep 17 00:00:00 2001 From: Martyn Inglis Date: Thu, 17 Mar 2016 09:37:10 +0000 Subject: [PATCH 02/12] Typo in bucket name --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c49980faa..030da3e0c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -50,12 +50,12 @@ deploy: on: &2 repo: alphagov/notifications-admin branch: live - bucket: live_notifications_admin_deployment_group + bucket: live-notifications-api-codedeploy region: eu-west-1 - provider: codedeploy access_key_id: AKIAJ5MKF6G3P2JQP4QQ secret_access_key: *1 - bucket: staging-notifications-api-codedeploy + bucket: live-notifications-api-codedeploy key: notifications-admin-$TRAVIS_BRANCH-$TRAVIS_BUILD_NUMBER-$TRAVIS_COMMIT.zip bundle_type: zip application: notifications-admin From d04bde5486fe8892b1bca89044a09af38528f459 Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Thu, 17 Mar 2016 11:44:00 +0000 Subject: [PATCH 03/12] Add API client for notification statistic Adds a client for the endpoints added in https://github.com/alphagov/notifications-api/commit/67c4bd2263364f4f61a11445bf34929442c88da6 --- app/__init__.py | 3 +++ app/notify_client/statistics_api_client.py | 18 ++++++++++++++++++ .../notify_client/test_statistics_client.py | 13 +++++++++++++ tests/app/main/views/test_dashboard.py | 1 + tests/app/main/views/test_sign_out.py | 1 + tests/conftest.py | 9 +++++++++ 6 files changed, 45 insertions(+) create mode 100644 app/notify_client/statistics_api_client.py create mode 100644 tests/app/main/notify_client/test_statistics_client.py diff --git a/app/__init__.py b/app/__init__.py index d0ce1d245..871a5589e 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -19,6 +19,7 @@ from app.notify_client.job_api_client import JobApiClient from app.notify_client.notification_api_client import NotificationApiClient from app.notify_client.status_api_client import StatusApiClient from app.notify_client.invite_api_client import InviteApiClient +from app.notify_client.statistics_api_client import StatisticsApiClient from app.its_dangerous_session import ItsdangerousSessionInterface from app.asset_fingerprinter import AssetFingerprinter from utils.recipients import validate_phone_number, InvalidPhoneError @@ -36,6 +37,7 @@ job_api_client = JobApiClient() notification_api_client = NotificationApiClient() status_api_client = StatusApiClient() invite_api_client = InviteApiClient() +statistics_api_client = StatisticsApiClient() asset_fingerprinter = AssetFingerprinter() @@ -55,6 +57,7 @@ def create_app(config_name, config_overrides=None): notification_api_client.init_app(application) status_api_client.init_app(application) invite_api_client.init_app(application) + statistics_api_client.init_app(application) login_manager.init_app(application) login_manager.login_view = 'main.sign_in' diff --git a/app/notify_client/statistics_api_client.py b/app/notify_client/statistics_api_client.py new file mode 100644 index 000000000..9ef40bbd6 --- /dev/null +++ b/app/notify_client/statistics_api_client.py @@ -0,0 +1,18 @@ +from notifications_python_client.base import BaseAPIClient + + +class StatisticsApiClient(BaseAPIClient): + def __init__(self, base_url=None, client_id=None, secret=None): + super(self.__class__, self).__init__(base_url=base_url or 'base_url', + client_id=client_id or 'client_id', + secret=secret or 'secret') + + def init_app(self, app): + self.base_url = app.config['API_HOST_NAME'] + self.client_id = app.config['ADMIN_CLIENT_USER_NAME'] + self.secret = app.config['ADMIN_CLIENT_SECRET'] + + def get_statistics_for_service(self, service_id): + return self.get( + url='/service/{}/notifications-statistics'.format(service_id), + ) diff --git a/tests/app/main/notify_client/test_statistics_client.py b/tests/app/main/notify_client/test_statistics_client.py new file mode 100644 index 000000000..3dc24c16a --- /dev/null +++ b/tests/app/main/notify_client/test_statistics_client.py @@ -0,0 +1,13 @@ +from app.notify_client.statistics_api_client import StatisticsApiClient + + +def test_client_uses_correct_find_by_email(mocker, api_user_active): + + expected_url = '/service/a1b2c3d4/notifications-statistics' + + client = StatisticsApiClient() + mock_get = mocker.patch('app.notify_client.statistics_api_client.StatisticsApiClient.get') + + client.get_statistics_for_service('a1b2c3d4') + + mock_get.assert_called_once_with(url=expected_url) diff --git a/tests/app/main/views/test_dashboard.py b/tests/app/main/views/test_dashboard.py index 0902f6293..0c88c68d1 100644 --- a/tests/app/main/views/test_dashboard.py +++ b/tests/app/main/views/test_dashboard.py @@ -6,6 +6,7 @@ def test_should_show_recent_jobs_on_dashboard(app_, api_user_active, mock_get_service, mock_get_service_templates, + mock_get_service_statistics, mock_get_user, mock_get_user_by_email, mock_login, diff --git a/tests/app/main/views/test_sign_out.py b/tests/app/main/views/test_sign_out.py index a6b8e5fcb..1d4860a71 100644 --- a/tests/app/main/views/test_sign_out.py +++ b/tests/app/main/views/test_sign_out.py @@ -16,6 +16,7 @@ def test_sign_out_user(app_, mock_get_user, mock_get_user_by_email, mock_get_service_templates, + mock_get_service_statistics, mock_login, mock_get_jobs): with app_.test_request_context(): diff --git a/tests/conftest.py b/tests/conftest.py index 9d5c88f39..b5f01d91f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -154,6 +154,15 @@ def mock_delete_service(mocker, mock_get_service): 'app.notifications_api_client.delete_service', side_effect=_delete) +@pytest.fixture(scope='function') +def mock_get_service_statistics(mocker): + def _create(service_id): + return {'data': []} + + return mocker.patch( + 'app.statistics_api_client.get_statistics_for_service', side_effect=_create) + + @pytest.fixture(scope='function') def mock_get_service_template(mocker): def _create(service_id, template_id): From 2473b09beb5faa6bc41b0d75a29deab35d4ae1fa Mon Sep 17 00:00:00 2001 From: Martyn Inglis Date: Thu, 17 Mar 2016 13:45:59 +0000 Subject: [PATCH 04/12] Start aligning Admin app with config styles used elsewhere - no config overrides - now all set in environment - use different files for staging and live too allow for differently named env variables - updates to run_app and run_tests scripts to set correct environment (test/development) so correct config picked up - use environment file on deployed environments to pick correct config --- app/__init__.py | 24 +++++++----------------- config.py | 18 +++++------------- config_live.py | 15 +++++++++++++++ config_staging.py | 14 ++++++++++++++ scripts/run_app.sh | 1 + scripts/run_tests.sh | 1 + tests/conftest.py | 2 +- wsgi.py | 16 +++++++++++----- 8 files changed, 55 insertions(+), 36 deletions(-) create mode 100644 config_live.py create mode 100644 config_staging.py diff --git a/app/__init__.py b/app/__init__.py index d0ce1d245..b41fdc01b 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -39,12 +39,12 @@ invite_api_client = InviteApiClient() asset_fingerprinter = AssetFingerprinter() -def create_app(config_name, config_overrides=None): +def create_app(): application = Flask(__name__) - application.config['NOTIFY_ADMIN_ENVIRONMENT'] = config_name - application.config.from_object(configs[config_name]) - init_app(application, config_overrides) + application.config.from_object(os.environ['NOTIFY_ADMIN_ENVIRONMENT']) + + init_app(application) logging.init_app(application) init_csrf(application) @@ -101,22 +101,12 @@ def init_csrf(application): abort(400, reason) -def init_app(app, config_overrides): - - if config_overrides: - for key in app.config.keys(): - if key in config_overrides: - app.config[key] = config_overrides[key] - - for key, value in app.config.items(): - if key in os.environ: - app.config[key] = convert_to_boolean(os.environ[key]) - - @app.context_processor +def init_app(application): + @application.context_processor def inject_global_template_variables(): return { 'asset_path': '/static/', - 'header_colour': app.config['HEADER_COLOUR'], + 'header_colour': application.config['HEADER_COLOUR'], 'asset_url': asset_fingerprinter.get_url } diff --git a/config.py b/config.py index bb520a3ae..7521badbb 100644 --- a/config.py +++ b/config.py @@ -72,18 +72,10 @@ class Preview(Config): HEADER_COLOUR = '#F47738' # $orange -class Staging(Preview): - SHOW_STYLEGUIDE = False - - -class Live(Staging): - HEADER_COLOUR = '#B10E1E' # $red - - configs = { - 'development': Development, - 'test': Test, - 'preview': Preview, - 'staging': Staging, - 'live': Live + 'development': 'config.Development', + 'test': 'config.Test', + 'preview': 'config.Preview', + 'staging': 'config_staging.Staging', + 'live': 'config_live.Live' } diff --git a/config_live.py b/config_live.py new file mode 100644 index 000000000..033821bfb --- /dev/null +++ b/config_live.py @@ -0,0 +1,15 @@ +import os +from config import Config + + +class Live(Config): + SHOW_STYLEGUIDE = False + HEADER_COLOUR = '#B10E1E' # $red + HTTP_PROTOCOL = 'https' + API_HOST_NAME = os.getenv('LIVE_API_HOST_NAME') + NOTIFY_API_SECRET = os.getenv('LIVE_NOTIFY_API_SECRET', "dev-secret") + NOTIFY_API_CLIENT = os.getenv('LIVE_NOTIFY_API_CLIENT', "admin") + ADMIN_CLIENT_USER_NAME = os.getenv('LIVE_ADMIN_CLIENT_USER_NAME') + ADMIN_CLIENT_SECRET = os.getenv('LIVE_ADMIN_CLIENT_SECRET') + SECRET_KEY = os.getenv('LIVE_SECRET_KEY') + DANGEROUS_SALT = os.getenv('LIVE_DANGEROUS_SALT') diff --git a/config_staging.py b/config_staging.py new file mode 100644 index 000000000..3be18a7a2 --- /dev/null +++ b/config_staging.py @@ -0,0 +1,14 @@ +import os +from config import Config + + +class Staging(Config): + SHOW_STYLEGUIDE = False + HTTP_PROTOCOL = 'https' + API_HOST_NAME = os.getenv('STAGING_API_HOST_NAME') + NOTIFY_API_SECRET = os.getenv('STAGING_NOTIFY_API_SECRET', "dev-secret") + NOTIFY_API_CLIENT = os.getenv('STAGING_NOTIFY_API_CLIENT', "admin") + ADMIN_CLIENT_USER_NAME = os.getenv('STAGING_ADMIN_CLIENT_USER_NAME') + ADMIN_CLIENT_SECRET = os.getenv('STAGING_ADMIN_CLIENT_SECRET') + SECRET_KEY = os.getenv('STAGING_SECRET_KEY') + DANGEROUS_SALT = os.getenv('STAGING_DANGEROUS_SALT') diff --git a/scripts/run_app.sh b/scripts/run_app.sh index 96947cc19..9236e8642 100755 --- a/scripts/run_app.sh +++ b/scripts/run_app.sh @@ -1,3 +1,4 @@ #!/bin/bash +export NOTIFY_ADMIN_ENVIRONMENT='config.Development' python3 app.py runserver diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh index af5971aa8..bc80a179e 100755 --- a/scripts/run_tests.sh +++ b/scripts/run_tests.sh @@ -32,5 +32,6 @@ display_result $? 2 "Front end code style check" #py.test --cov=app tests/ #display_result $? 3 "Code coverage" +export NOTIFY_ADMIN_ENVIRONMENT='config.Test' py.test -v display_result $? 4 "Unit tests" diff --git a/tests/conftest.py b/tests/conftest.py index 9d5c88f39..38855b2cd 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -24,7 +24,7 @@ from notifications_python_client.errors import HTTPError @pytest.fixture(scope='session') def app_(request): - app = create_app('test') + app = create_app() ctx = app.app_context() ctx.push() diff --git a/wsgi.py b/wsgi.py index fd773b3e0..c875d4269 100644 --- a/wsgi.py +++ b/wsgi.py @@ -2,16 +2,22 @@ from app import create_app from credstash import getAllSecrets import os -config = 'live' default_env_file = '/home/ubuntu/environment' +environment = 'live' if os.path.isfile(default_env_file): - environment = open(default_env_file, 'r') - config = environment.readline().strip() + with open(default_env_file, 'r') as environment_file: + environment = environment_file.readline().strip() -secrets = getAllSecrets(region="eu-west-1") -application = create_app(config, secrets) +# on aws get secrets and export to env +os.environ.update(getAllSecrets(region="eu-west-1")) + +from config import configs + +os.environ['NOTIFY_ADMIN_ENVIRONMENT'] = configs[environment] + +application = create_app() if __name__ == "__main__": application.run() From b38ae08ad6f08e0d6b64fd554d9806d3d4d97fdf Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Thu, 17 Mar 2016 11:45:48 +0000 Subject: [PATCH 05/12] Put some statistics on the dashboard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds two new sections to the dashboard 1. A banner telling you about trial mode, including a count of how many messages you have left today, which is a restriction of trial mode 2. Panels with counts of how many emails and text messages have been sent in a day, plus the failure rates for each It does **not**: - link through to any further information about what trial mode is (coming later) - link through to pages for the failure rates (coming later) - change the ‘recent jobs’ section to ‘recent notifications’ --- app/assets/images/tick-black.png | Bin 0 -> 4057 bytes app/assets/stylesheets/_grids.scss | 6 +- app/assets/stylesheets/components/banner.scss | 49 ++++++++++-- .../stylesheets/components/big-number.scss | 57 ++++++++++++++ app/main/views/dashboard.py | 30 ++++++- app/templates/components/banner.html | 16 +--- app/templates/components/big-number.html | 11 +++ app/templates/views/choose-template.html | 4 +- app/templates/views/dashboard/dashboard.html | 20 +++++ .../views/dashboard/get-started.html | 19 +++++ app/templates/views/dashboard/jobs.html | 28 +++++++ app/templates/views/dashboard/today.html | 25 ++++++ .../views/dashboard/trial-mode-banner.html | 24 ++++++ app/templates/views/service_dashboard.html | 74 ------------------ tests/app/main/views/test_accept_invite.py | 1 + tests/app/main/views/test_dashboard.py | 1 + tests/conftest.py | 2 +- 17 files changed, 268 insertions(+), 99 deletions(-) create mode 100644 app/assets/images/tick-black.png create mode 100644 app/templates/views/dashboard/dashboard.html create mode 100644 app/templates/views/dashboard/get-started.html create mode 100644 app/templates/views/dashboard/jobs.html create mode 100644 app/templates/views/dashboard/today.html create mode 100644 app/templates/views/dashboard/trial-mode-banner.html delete mode 100644 app/templates/views/service_dashboard.html diff --git a/app/assets/images/tick-black.png b/app/assets/images/tick-black.png new file mode 100644 index 0000000000000000000000000000000000000000..dd050c6c78982b1c170c588e549aa38938654ca8 GIT binary patch literal 4057 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 literal 0 HcmV?d00001 diff --git a/app/assets/stylesheets/_grids.scss b/app/assets/stylesheets/_grids.scss index 490f4d01d..ec083e809 100644 --- a/app/assets/stylesheets/_grids.scss +++ b/app/assets/stylesheets/_grids.scss @@ -2,6 +2,10 @@ @include grid-column(3/4); } +.column-one-sixth { + @include grid-column(1/6); +} + .column-one-eighth { @include grid-column(1/8); } @@ -16,7 +20,7 @@ } .bottom-gutter-2-3 { - margin-bottom: $gutter * 2/3; + margin-bottom: $gutter-two-thirds; } .align-with-heading { diff --git a/app/assets/stylesheets/components/banner.scss b/app/assets/stylesheets/components/banner.scss index 884523bdd..36ebad07d 100644 --- a/app/assets/stylesheets/components/banner.scss +++ b/app/assets/stylesheets/components/banner.scss @@ -26,9 +26,8 @@ } -.banner-with-tick, -.banner-default-with-tick { - @extend %banner; +%banner-with-tick, +.banner-with-tick { padding: $gutter-half ($gutter + $gutter-half); background-image: file-url('tick-white.png'); background-size: 19px; @@ -37,6 +36,11 @@ font-weight: bold; } +.banner-default-with-tick { + @extend %banner; + @extend %banner-with-tick; +} + .banner-dangerous { @extend %banner; @@ -54,13 +58,15 @@ } +%banner-tip, .banner-tip { @extend %banner; - background: $yellow; + @include bold-19; + background-color: $yellow; color: $text-colour; text-align: left; - font-weight: bold; + margin-top: 0; a { &:link, @@ -76,6 +82,12 @@ } +.banner-tip-with-tick { + @extend %banner-with-tick; + @extend %banner-tip; + background-image: file-url('tick-black.png'); +} + .banner-info, .banner-important { @extend %banner; @@ -91,3 +103,30 @@ .banner-info { background-image: file-url('icon-information-2x.png'); } + +.banner-mode { + + @extend %banner; + background: $govuk-blue; + color: $white; + margin-top: $gutter; + + .heading-medium { + margin-top: 0; + } + + a { + + &:link, + &:visited { + color: $white; + } + + &:hover, + &:active { + color: $light-blue-25; + } + + } + +} diff --git a/app/assets/stylesheets/components/big-number.scss b/app/assets/stylesheets/components/big-number.scss index 0bce53b25..a14f30d9b 100644 --- a/app/assets/stylesheets/components/big-number.scss +++ b/app/assets/stylesheets/components/big-number.scss @@ -1,3 +1,4 @@ +%big-number, .big-number { @include bold-48($tabular-numbers: true); @@ -5,6 +6,62 @@ &-label { @include core-19; display: block; + padding-bottom: $gutter-half; + } + +} + +.big-number-with-status { + + @extend %big-number; + background: $text-colour; + color: $white; + + .big-number { + padding: 15px; + } + + .big-number-label { + padding-bottom: 0; + } + + .big-number-status { + + display: block; + background: $green; + position: relative; + + &-error-percentage { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: $error-colour; + z-index: 1; + } + + a { + + &:link, + &:visited, + &:active, + &:hover { + color: $white; + } + + } + + .big-number { + @include bold-19; + position: relative; + z-index: 2; + } + + .big-number-label { + display: inline; + } + } } diff --git a/app/main/views/dashboard.py b/app/main/views/dashboard.py index f681faa45..c1f773842 100644 --- a/app/main/views/dashboard.py +++ b/app/main/views/dashboard.py @@ -8,7 +8,7 @@ from flask_login import login_required from app.main import main from app.main.dao.services_dao import get_service_by_id from app.main.dao import templates_dao -from app import job_api_client +from app import job_api_client, statistics_api_client @main.route("/services//dashboard") @@ -27,11 +27,35 @@ def service_dashboard(service_id): message = 'You have successfully accepted your invitation and been added to {}'.format(service_name) flash(message, 'default_with_tick') + statistics = statistics_api_client.get_statistics_for_service(service_id)['data'] + return render_template( - 'views/service_dashboard.html', + 'views/dashboard/dashboard.html', jobs=jobs[:5], more_jobs_to_show=(len(jobs) > 5), free_text_messages_remaining='250,000', spent_this_month='0.00', - template_count=len(templates), + service=service['data'], + statistics=expand_statistics(statistics), + templates=templates, service_id=str(service_id)) + + +def expand_statistics(statistics, danger_zone=25): + + if not statistics or not statistics[0]: + return {} + + today = statistics[0] + + today.update({ + 'emails_failure_rate': int(today['emails_error'] / today['emails_requested'] * 100), + 'sms_failure_rate': int(today['sms_error'] / today['sms_requested'] * 100) + }) + + today.update({ + 'emails_percentage_of_danger_zone': min(today['emails_failure_rate'] / (danger_zone / 100), 100), + 'sms_percentage_of_danger_zone': min(today['sms_failure_rate'] / (danger_zone / 100), 100) + }) + + return today diff --git a/app/templates/components/banner.html b/app/templates/components/banner.html index 0c0ef7abc..d008b5b57 100644 --- a/app/templates/components/banner.html +++ b/app/templates/components/banner.html @@ -1,19 +1,9 @@ {% macro banner(body, type=None, with_tick=False, delete_button=None, subhead=None) %}
- {% if subhead %} -
-
- {{ subhead }} -
-
- {% endif %} - + {% if subhead -%} + {{ subhead }}  + {%- endif -%} {{ body }} - - {% if subhead %} -
-
- {% endif %} {% if delete_button %}
diff --git a/app/templates/components/big-number.html b/app/templates/components/big-number.html index 089466c25..9ee6affb9 100644 --- a/app/templates/components/big-number.html +++ b/app/templates/components/big-number.html @@ -4,3 +4,14 @@ {{ label }}
{% endmacro %} + + +{% macro big_number_with_status(number, label, status_number, status_label, percentage_bad=0) %} +
+ {{ big_number(number, label) }} +
+
+ {{ big_number(status_number, status_label) }} +
+
+{% endmacro %} diff --git a/app/templates/views/choose-template.html b/app/templates/views/choose-template.html index a5d28fd88..e4c58e947 100644 --- a/app/templates/views/choose-template.html +++ b/app/templates/views/choose-template.html @@ -37,9 +37,9 @@ {% if current_user.has_permissions(['send_texts', 'send_emails', 'send_letters'], or_=True) %} {{ banner( """ - Send yourself a test message + Send yourself a test """, - subhead='Next step', + subhead='Next step:', type="tip" )}} {% endif %} diff --git a/app/templates/views/dashboard/dashboard.html b/app/templates/views/dashboard/dashboard.html new file mode 100644 index 000000000..67fb31a53 --- /dev/null +++ b/app/templates/views/dashboard/dashboard.html @@ -0,0 +1,20 @@ +{% extends "withnav_template.html" %} + +{% block page_title %} + {{ session.get('service_name', 'Dashboard') }} – GOV.UK Notify +{% endblock %} + +{% block maincolumn_content %} + + {% if service.restricted %} + {% include 'views/dashboard/trial-mode-banner.html' %} + {% endif %} + + {% if not jobs %} + {% include 'views/dashboard/get-started.html' %} + {% else %} + {% include 'views/dashboard/today.html' %} + {% include 'views/dashboard/jobs.html' %} + {% endif %} + +{% endblock %} diff --git a/app/templates/views/dashboard/get-started.html b/app/templates/views/dashboard/get-started.html new file mode 100644 index 000000000..d1b8adc01 --- /dev/null +++ b/app/templates/views/dashboard/get-started.html @@ -0,0 +1,19 @@ +{% from "components/banner.html" import banner_wrapper %} + +

Get started

+
    + {% if current_user.has_permissions(['manage_templates']) %} +
  1. + {% call banner_wrapper(type="tip", subhead='1.' if not templates else None, with_tick=templates|length) %} + Add a template + {% endcall %} +
  2. + {% endif %} + {% if current_user.has_permissions(['send_texts', 'send_emails', 'send_letters']) %} +
  3. + {% call banner_wrapper(type="tip", subhead='2.') %} + Send yourself a message + {% endcall %} +
  4. + {% endif %} +
diff --git a/app/templates/views/dashboard/jobs.html b/app/templates/views/dashboard/jobs.html new file mode 100644 index 000000000..941bb51cc --- /dev/null +++ b/app/templates/views/dashboard/jobs.html @@ -0,0 +1,28 @@ +{% from "components/table.html" import list_table, field, right_aligned_field_heading, hidden_field_heading %} + +{% call(item) list_table( + jobs, + caption="Recent batch jobs", + empty_message='You haven’t sent any text messages yet', + field_headings=['File', 'Started', right_aligned_field_heading('Rows'), right_aligned_field_heading('Sent')] +) %} + {% call field() %} + {{ item.original_file_name }} + {% endcall %} + {% call field() %} + {{ item.created_at|format_datetime }} + {% endcall %} + {% call field(align='right') %} + {{ item.notification_count }} + {% endcall %} + {% call field(align='right') %} + {{ item.notifications_sent }} + {% endcall %} +{% endcall %} +{% if more_jobs_to_show %} + {% if current_user.has_permissions(['send_texts', 'send_emails', 'send_letters']) %} + + {% endif %} +{% endif %} diff --git a/app/templates/views/dashboard/today.html b/app/templates/views/dashboard/today.html new file mode 100644 index 000000000..bc1d13abf --- /dev/null +++ b/app/templates/views/dashboard/today.html @@ -0,0 +1,25 @@ +{% from "components/big-number.html" import big_number_with_status %} + +

+ Sent today +

+
+
+ {{ big_number_with_status( + statistics.get('emails_requested', 0), + 'email' if statistics.get('emails_requested') == 1 else 'emails', + '{}%'.format(statistics.get('emails_failure_rate', 0)), + 'failed', + statistics.get('emails_percentage_of_danger_zone', 0) + ) }} +
+
+ {{ big_number_with_status( + statistics.get('sms_requested', 0), + 'text message' if statistics.get('sms_requested') == 1 else 'text messages', + '{}%'.format(statistics.get('sms_failure_rate', 0)), + 'failed', + statistics.get('sms_percentage_of_danger_zone', 0) + ) }} +
+
diff --git a/app/templates/views/dashboard/trial-mode-banner.html b/app/templates/views/dashboard/trial-mode-banner.html new file mode 100644 index 000000000..1ad62066e --- /dev/null +++ b/app/templates/views/dashboard/trial-mode-banner.html @@ -0,0 +1,24 @@ +{% from "components/banner.html" import banner_wrapper %} +{% from "components/big-number.html" import big_number %} + +{% call banner_wrapper(type="mode") %} + +
+
+

Trial mode

+

+ We’ll only deliver messages to you and members of your team + +

+
+
+   +
+
+ {{ big_number( + service.limit - statistics.get('emails_requested', 0) - statistics.get('sms_requested', 0), + 'messages left today' + ) }} +
+
+{% endcall %} diff --git a/app/templates/views/service_dashboard.html b/app/templates/views/service_dashboard.html deleted file mode 100644 index f7a6dc7fa..000000000 --- a/app/templates/views/service_dashboard.html +++ /dev/null @@ -1,74 +0,0 @@ -{% extends "withnav_template.html" %} -{% from "components/banner.html" import banner_wrapper %} -{% from "components/table.html" import list_table, field, right_aligned_field_heading %} -{% from "components/big-number.html" import big_number %} - -{% block page_title %} - {{ session.get('service_name', 'Dashboard') }} – GOV.UK Notify -{% endblock %} - -{% block maincolumn_content %} - -
    -
  • - {{ big_number( - free_text_messages_remaining, - 'free text messages remaining' - )}} -
  • -
  • - {{ big_number( - '£' + spent_this_month, - 'spent this month' - )}} -
  • -
- - {% if not template_count and not jobs %} - {% call banner_wrapper(subhead='Get started', type="tip") %} -
    - {% if current_user.has_permissions(['manage_templates']) %} -
  1. - Add a template -
  2. - {% endif %} - {% if current_user.has_permissions(['send_texts', 'send_emails', 'send_letters']) %} -
  3. - Send yourself a text message -
  4. - {% endif %} -
- {% endcall %} - {% elif not jobs %} - {% call banner_wrapper(subhead='Next step', type="tip") %} - {% if current_user.has_permissions(['send_texts', 'send_emails', 'send_letters']) %} - Send yourself a text message - {% endif %} - {% endcall %} - {% else %} - {% call(item) list_table( - jobs, - caption="Recent text messages", - empty_message='You haven’t sent any text messages yet', - field_headings=['Job', 'Created', right_aligned_field_heading('completion')] - ) %} - {% call field() %} - {{ item.original_file_name }} - {% endcall %} - {% call field() %} - {{ item.created_at|format_datetime }} - {% endcall %} - {% call field(align='right') %} - {{ (item.notifications_sent / item.notification_count * 100)|round|int }}% - {% endcall %} - {% endcall %} - {% if more_jobs_to_show %} - {% if current_user.has_permissions(['send_texts', 'send_emails', 'send_letters']) %} - - {% endif %} - {% endif %} - {% endif %} - -{% endblock %} diff --git a/tests/app/main/views/test_accept_invite.py b/tests/app/main/views/test_accept_invite.py index e38f150ec..2cf6f3198 100644 --- a/tests/app/main/views/test_accept_invite.py +++ b/tests/app/main/views/test_accept_invite.py @@ -275,6 +275,7 @@ def test_new_invited_user_verifies_and_added_to_service(app_, mock_accept_invite, mock_get_service, mock_get_service_templates, + mock_get_service_statistics, mock_get_jobs): with app_.test_request_context(): diff --git a/tests/app/main/views/test_dashboard.py b/tests/app/main/views/test_dashboard.py index 0c88c68d1..9594f0072 100644 --- a/tests/app/main/views/test_dashboard.py +++ b/tests/app/main/views/test_dashboard.py @@ -35,6 +35,7 @@ def _test_dashboard_menu(mocker, app_, usr, service, permissions): mocker.patch('app.user_api_client.get_user', return_value=usr) mocker.patch('app.user_api_client.get_user_by_email', return_value=usr) mocker.patch('app.notifications_api_client.get_service', return_value={'data': service}) + mocker.patch('app.statistics_api_client.get_statistics_for_service', return_value={'data': [{}]}) client.login(usr) return client.get(url_for('main.service_dashboard', service_id=service['id'])) diff --git a/tests/conftest.py b/tests/conftest.py index b5f01d91f..ffa173d77 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -157,7 +157,7 @@ def mock_delete_service(mocker, mock_get_service): @pytest.fixture(scope='function') def mock_get_service_statistics(mocker): def _create(service_id): - return {'data': []} + return {'data': [{}]} return mocker.patch( 'app.statistics_api_client.get_statistics_for_service', side_effect=_create) From 643d0477053aa3ae426dadac81723d7cbba2a2a9 Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Thu, 17 Mar 2016 14:40:08 +0000 Subject: [PATCH 06/12] Fix division by zero --- app/__init__.py | 4 +++- app/main/views/dashboard.py | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 871a5589e..fe5ea530e 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -2,7 +2,7 @@ import os import re import dateutil -from flask import (Flask, session, Markup, escape, render_template, make_response) +from flask import (Flask, session, Markup, escape, render_template, make_response, current_app) from flask._compat import string_types from flask_login import LoginManager from flask_wtf import CsrfProtect @@ -206,4 +206,6 @@ def register_errorhandlers(application): @application.errorhandler(Exception) def handle_bad_request(error): + if current_app.config.get('DEBUG', None): + raise error return _error_response(500) diff --git a/app/main/views/dashboard.py b/app/main/views/dashboard.py index c1f773842..6fc17c49c 100644 --- a/app/main/views/dashboard.py +++ b/app/main/views/dashboard.py @@ -49,8 +49,10 @@ def expand_statistics(statistics, danger_zone=25): today = statistics[0] today.update({ - 'emails_failure_rate': int(today['emails_error'] / today['emails_requested'] * 100), - 'sms_failure_rate': int(today['sms_error'] / today['sms_requested'] * 100) + 'emails_failure_rate': + int(today['emails_error'] / today['emails_requested'] * 100) if today['emails_requested'] else 0, + 'sms_failure_rate': + int(today['sms_error'] / today['sms_requested'] * 100) if today['sms_requested'] else 0 }) today.update({ From d1becbe1e32e777544de534052b6d458651dbc6b Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Wed, 16 Mar 2016 11:01:19 +0000 Subject: [PATCH 07/12] Add cookie banner text, page, and footer links MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit > Let’s start the footer links with the cookie page. > Banner to say: "GOV.UK Notify uses cookies to make the site simpler. Find out > more about cookies" > Standard style one... see > https://www.registertovote.service.gov.uk/register-to-vote/cookies or > https://www.digitalmarketplace.service.gov.uk/cookies > > Let's link to the feedback form too... > https://docs.google.com/forms/d/1AL8U-xJX_HAFEiQiJszGQw0PcEaEUnYATSntEghNDGo/viewform > Call it Support and feedback https://www.pivotaltracker.com/story/show/115483375 --- app/assets/stylesheets/_grids.scss | 16 +++++ app/assets/stylesheets/app.scss | 8 +++ app/main/views/index.py | 5 ++ app/templates/admin_template.html | 12 ++++ app/templates/views/cookies.html | 81 ++++++++++++++++++++++ tests/app/main/views/test_accept_invite.py | 2 +- 6 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 app/templates/views/cookies.html diff --git a/app/assets/stylesheets/_grids.scss b/app/assets/stylesheets/_grids.scss index ec083e809..f0008d51e 100644 --- a/app/assets/stylesheets/_grids.scss +++ b/app/assets/stylesheets/_grids.scss @@ -30,3 +30,19 @@ padding-left: 2px; padding-right: 2px; } + +.global-cookie-message { + p { + @extend %site-width-container; + } +} + +.footer-nav { + @include copy-16; + margin-bottom: $gutter-two-thirds; + + a { + display: inline-block; + margin-right: $gutter-half; + } +} diff --git a/app/assets/stylesheets/app.scss b/app/assets/stylesheets/app.scss index d7eeacb62..d2d7fb838 100644 --- a/app/assets/stylesheets/app.scss +++ b/app/assets/stylesheets/app.scss @@ -80,3 +80,11 @@ a { } } + +td { + vertical-align: top; +} + +.heading-xlarge { + margin-bottom: 20px; +} diff --git a/app/main/views/index.py b/app/main/views/index.py index 5f7d700df..a37968740 100644 --- a/app/main/views/index.py +++ b/app/main/views/index.py @@ -16,3 +16,8 @@ def index(): @login_required def verify_mobile(): return render_template('views/verify-mobile.html') + + +@main.route('/cookies') +def cookies(): + return render_template('views/cookies.html') diff --git a/app/templates/admin_template.html b/app/templates/admin_template.html index bd1eba398..846ee2a91 100644 --- a/app/templates/admin_template.html +++ b/app/templates/admin_template.html @@ -24,6 +24,10 @@ {% endblock %} {% block cookie_message %} +

+ GOV.UK Notify uses cookies to make the site simpler. + Find out more about cookies +

{% endblock %} {% block inside_header %} @@ -68,6 +72,14 @@ {% endblock %} +{% block footer_support_links %} + +{% endblock %} + {% block body_end %} {% endblock %} diff --git a/app/templates/views/cookies.html b/app/templates/views/cookies.html new file mode 100644 index 000000000..6337c1f77 --- /dev/null +++ b/app/templates/views/cookies.html @@ -0,0 +1,81 @@ +{% extends "withoutnav_template.html" %} + +{% block page_title %} + Cookies – GOV.UK Notify +{% endblock %} + +{% block maincolumn_content %} + +
+
+

Cookies

+

+ GOV.UK Notify puts small files (known as ‘cookies’) + on to your computer. +

+

These cookies are used to remember you once you’ve logged in

+

+ Find out how to manage cookies. +

+ +

Session cookies

+

+ We store session cookies on your computer to help keep your information + secure while you use the service. +

+ + + + + + + + + + + + + + + +
NamePurposeExpires
+ notify_admin_session + + Used to keep you logged in + + 1 hour +
+ +

Introductory message cookie

+

+ When you first use the service, you may see a pop-up ‘welcome’ message. + Once you’ve seen the message, we store a cookie on your computer so it + knows not to show it again. +

+ + + + + + + + + + + + + + + +
NamePurposeExpires
+ seen_cookie_message + + Saves a message to let us know that you have seen our cookie + message + + 1 month +
+
+
+ +{% endblock %} diff --git a/tests/app/main/views/test_accept_invite.py b/tests/app/main/views/test_accept_invite.py index 2cf6f3198..cb2133f38 100644 --- a/tests/app/main/views/test_accept_invite.py +++ b/tests/app/main/views/test_accept_invite.py @@ -175,7 +175,7 @@ def test_new_user_accept_invite_calls_api_and_views_registration_page(app_, page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser') assert page.h1.string.strip() == 'Create an account' - email_in_page = page.find('p') + email_in_page = page.find('main').find('p') assert email_in_page.text.strip() == 'Your account will be created with this email: invited_user@test.gov.uk' # noqa form = page.find('form') From 774ac17ee97eca8e8d7242855b4db974b562494d Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Thu, 17 Mar 2016 13:44:33 +0000 Subject: [PATCH 08/12] Add a help page that explains trial mode > We start in trial mode and there are a bunch of things that we need to know, so > let's explain this with a page, accessed from the footer. Not requiring log in. > Should explain: > 50 messages per day > Can only send to yourself or team members > How to go live > We can then link to this from the dashboard (and any other place) where we > tell you that you're in trial mode. https://www.pivotaltracker.com/story/show/115775751 --- app/main/views/index.py | 5 ++ app/templates/admin_template.html | 1 + app/templates/views/help.html | 79 +++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 app/templates/views/help.html diff --git a/app/main/views/index.py b/app/main/views/index.py index a37968740..47780a1e7 100644 --- a/app/main/views/index.py +++ b/app/main/views/index.py @@ -21,3 +21,8 @@ def verify_mobile(): @main.route('/cookies') def cookies(): return render_template('views/cookies.html') + + +@main.route('/help') +def help(): + return render_template('views/help.html') diff --git a/app/templates/admin_template.html b/app/templates/admin_template.html index 846ee2a91..666add0bb 100644 --- a/app/templates/admin_template.html +++ b/app/templates/admin_template.html @@ -74,6 +74,7 @@ {% block footer_support_links %}