diff --git a/app/assets/images/tick-black.png b/app/assets/images/tick-black.png new file mode 100644 index 000000000..dd050c6c7 Binary files /dev/null and b/app/assets/images/tick-black.png differ 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 %} - - - - {% 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)