From 16ebdfeb8b36fa0966bb1b418c6143fc127c07c0 Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Mon, 2 Dec 2019 14:29:34 +0000 Subject: [PATCH 1/7] Add page showing priority of two SMS providers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s not very useful to know the priority of one provider without knowing the other. And these pages were never really designed, so they weren’t super easy to understand anyway. This commit adds a page that takes the first two text message providers and shows their relative priority against each other. It follows the design of the events page, as a pattern for showing a log of historical events. --- app/assets/stylesheets/views/history.scss | 57 ++++++++++++ app/main/views/providers.py | 88 ++++++++++++++++++- app/navigation.py | 4 + .../providers/edit-sms-provider-ratio.html | 61 +++++++++++++ 4 files changed, 208 insertions(+), 2 deletions(-) create mode 100644 app/templates/views/providers/edit-sms-provider-ratio.html diff --git a/app/assets/stylesheets/views/history.scss b/app/assets/stylesheets/views/history.scss index b971a1726..2b31ef45f 100644 --- a/app/assets/stylesheets/views/history.scss +++ b/app/assets/stylesheets/views/history.scss @@ -26,4 +26,61 @@ $item-top-padding: $gutter-half; color: $secondary-text-colour; } + &-percentage { + + $axis-thickness: 2px; + border-bottom: $axis-thickness solid $black; + position: relative; + margin-bottom: 30px; + height: 35px; + + &:before, + &:after { + content: ""; + position: absolute; + bottom: -6px; + left: 0; + height: 10px; + width: $axis-thickness; + background: $black; + } + + &:after { + left: auto; + right: 0; + } + + &-marker { + $size: 20px; + $border-thickness: 7px; + $text-width: 100px; + display: block; + width: $size; + height: $size; + position: absolute; + top: 35px - ($size / 2) - ($border-thickness - ($axis-thickness / 2)); + margin-left: 0 - ($size / 2) - $border-thickness; + background: $black; + border-radius: $size; + border: $border-thickness solid $white; + } + + &-left-label, + &-right-label { + width: 100%; + position: absolute; + top: 0; + } + + &-left-label { + left: 0; + } + + &-right-label { + right: 0; + text-align: right; + } + + } + } diff --git a/app/main/views/providers.py b/app/main/views/providers.py index 003f054ff..eecbf2176 100644 --- a/app/main/views/providers.py +++ b/app/main/views/providers.py @@ -1,11 +1,17 @@ -from flask import render_template, url_for +from collections import defaultdict +from datetime import datetime +from operator import itemgetter + +from flask import abort, render_template, url_for from werkzeug.utils import redirect -from app import provider_client +from app import format_date_numeric, provider_client from app.main import main from app.main.forms import ProviderForm from app.utils import user_is_platform_admin +PROVIDER_PRIORITY_MEANING_SWITCHOVER = datetime(2019, 11, 29, 11, 0) + @main.route("/providers") @user_is_platform_admin @@ -51,6 +57,84 @@ def edit_provider(provider_id): return render_template('views/providers/edit-provider.html', form=form, provider=provider) +@main.route("/provider/edit-sms-provider-ratio", methods=['GET', 'POST']) +@user_is_platform_admin +def edit_sms_provider_ratio(): + + providers = sorted([ + provider + for provider in provider_client.get_all_providers()['provider_details'] + if provider['notification_type'] == 'sms' + ], key=itemgetter('identifier'), reverse=True) + + if len(providers) < 2: + abort(400) + + return render_template( + 'views/providers/edit-sms-provider-ratio.html', + versions=_chunk_versions_by_day(_get_versions(providers[0], providers[1])), + ) + + +def _get_versions(provider0, provider1): + + versions = sorted(( + provider_client.get_provider_versions(provider0['id'])['data'] + + provider_client.get_provider_versions(provider1['id'])['data'] + ), key=lambda version: version['updated_at'] or '', reverse=True) + + for index, version in enumerate(versions): + + previous_version = get_previous_version( + versions, + index, + version['identifier'], + ) + + if ( + (version['updated_at'] or '0') < str(PROVIDER_PRIORITY_MEANING_SWITCHOVER) + ): + version['priority'], previous_version['priority'] = ( + int(version['priority'] > previous_version['priority']), + int(version['priority'] <= previous_version['priority']) + ) + + if version['identifier'] == provider0['identifier']: + fresh_version = version.copy() + fresh_version['other_provider'] = previous_version.copy() + yield fresh_version + elif previous_version['identifier'] in {provider0['identifier'], None}: + fresh_version = previous_version.copy() + fresh_version['other_provider'] = version.copy() + yield fresh_version + + +def get_previous_version(versions, start_index, current_provider_identifier): + for index, version in enumerate(versions): + if index < start_index: + continue + if current_provider_identifier == version['identifier']: + continue + return version + return { + 'identifier': None, + 'priority': 999, + 'updated_at': None, + } + + +def _chunk_versions_by_day(versions): + + days = defaultdict(list) + + for version in sorted(versions, key=lambda version: version['updated_at'] or '', reverse=True): + days[ + format_date_numeric(version['updated_at']) if version['updated_at'] else '' + ].append(version) + + return sorted(days.items(), reverse=True) + + @main.route("/provider/") @user_is_platform_admin def view_provider(provider_id): diff --git a/app/navigation.py b/app/navigation.py index 018c903e4..4e9cdef8c 100644 --- a/app/navigation.py +++ b/app/navigation.py @@ -89,6 +89,7 @@ class HeaderNavigation(Navigation): 'clear_cache', 'create_email_branding', 'create_letter_branding', + 'edit_sms_provider_ratio', 'email_branding', 'find_services_by_name', 'find_users_by_email', @@ -497,6 +498,7 @@ class MainNavigation(Navigation): 'edit_organisation_name', 'edit_organisation_type', 'edit_provider', + 'edit_sms_provider_ratio', 'edit_user_org_permissions', 'email_branding', 'email_not_received', @@ -717,6 +719,7 @@ class CaseworkNavigation(Navigation): 'edit_organisation_name', 'edit_organisation_type', 'edit_provider', + 'edit_sms_provider_ratio', 'edit_service_template', 'edit_template_postage', 'edit_user_email', @@ -1002,6 +1005,7 @@ class OrgNavigation(Navigation): 'edit_data_retention', 'edit_provider', 'edit_service_template', + 'edit_sms_provider_ratio', 'edit_template_postage', 'edit_user_email', 'edit_user_mobile_number', diff --git a/app/templates/views/providers/edit-sms-provider-ratio.html b/app/templates/views/providers/edit-sms-provider-ratio.html new file mode 100644 index 000000000..2527ba733 --- /dev/null +++ b/app/templates/views/providers/edit-sms-provider-ratio.html @@ -0,0 +1,61 @@ +{% extends "views/platform-admin/_base_template.html" %} +{% from "components/textbox.html" import textbox %} +{% from "components/page-header.html" import page_header %} +{% from "components/page-footer.html" import page_footer %} +{% from "components/form.html" import form_wrapper %} + +{% block per_page_title %} + Text message providers +{% endblock %} + +{% block platform_admin_content %} + + {{ page_header('Text message providers') }} + + {% call form_wrapper() %} + {% endcall %} + + + {% for day, versions in versions %} +

+ {% if day %} + {{ day|format_date_human|title }} + {% else %} + Start + {% endif %} +

+ + {% endfor %} + +{% endblock %} From 5233ee4bd979a27b49d62ed7afa0bbaacbff7e3d Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Mon, 2 Dec 2019 18:15:00 +0000 Subject: [PATCH 2/7] Add a form to set priority of top 2 providers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Their priority should always add up to 100%. Currently we have to ensure this by hand. Adding this form means there’s no way to not set their combined priorities to 100%. And it’s a bit more of an intuitive UI than two textboxes on separate pages. --- app/assets/javascripts/radioSlider.js | 26 +++++++++ .../stylesheets/components/page-footer.scss | 9 +++ app/assets/stylesheets/components/radios.scss | 57 +++++++++++++++++++ app/assets/stylesheets/views/history.scss | 11 +++- app/main/forms.py | 16 ++++++ app/main/views/providers.py | 9 ++- app/templates/components/page-footer.html | 5 +- .../providers/edit-sms-provider-ratio.html | 36 +++++++++++- gulpfile.js | 1 + 9 files changed, 164 insertions(+), 6 deletions(-) create mode 100644 app/assets/javascripts/radioSlider.js diff --git a/app/assets/javascripts/radioSlider.js b/app/assets/javascripts/radioSlider.js new file mode 100644 index 000000000..878a5188e --- /dev/null +++ b/app/assets/javascripts/radioSlider.js @@ -0,0 +1,26 @@ +(function(global) { + + "use strict"; + + global.GOVUK.Modules.RadioSlider = function() { + + this.start = function(component) { + + $(component) + .on('click', function() { + + leftRight = $(this).find(':checked').next('label').text().split('/'); + + if (leftRight.length === 2) { + $(this).find('.radio-slider-left-value').html(leftRight[0]); + $(this).find('.radio-slider-right-value').html(leftRight[1]); + } + + }) + .trigger('click'); + + }; + + }; + +})(window); diff --git a/app/assets/stylesheets/components/page-footer.scss b/app/assets/stylesheets/components/page-footer.scss index 8bc4ad4be..d39ee4579 100644 --- a/app/assets/stylesheets/components/page-footer.scss +++ b/app/assets/stylesheets/components/page-footer.scss @@ -65,6 +65,15 @@ margin: 0; } + &-centered-button { + + .button { + display: block; + margin: 0 auto; + } + + } + } .align-button-with-textbox { diff --git a/app/assets/stylesheets/components/radios.scss b/app/assets/stylesheets/components/radios.scss index 0b755e10f..2f80de451 100644 --- a/app/assets/stylesheets/components/radios.scss +++ b/app/assets/stylesheets/components/radios.scss @@ -117,3 +117,60 @@ margin-right: 15px; } } + +.inline-stacked-label { + +} + +.radio-slider { + + border-bottom: 2px solid $black; + height: 18px; + margin-bottom: 18px + 30px; + margin-right: -18px; + margin-left: -18px; + white-space: nowrap; + + legend { + display: none; + } + + .multiple-choice { + + display: inline-block; + margin-right: 0; + padding: 0; + width: 38px; + margin-right: calc((100% - (38px * 11)) / 10); + + &:last-child { + margin-right: 0; + } + + label { + + font-size: 0; + padding: 0; + + &:before { + background: $white; + } + + } + + } + + &-left-value { + position: absolute; + bottom: 30px; + left: 0; + } + + &-right-value { + position: absolute; + bottom: 30px; + right: 0; + text-align: right; + } + +} diff --git a/app/assets/stylesheets/views/history.scss b/app/assets/stylesheets/views/history.scss index 2b31ef45f..b625c82e1 100644 --- a/app/assets/stylesheets/views/history.scss +++ b/app/assets/stylesheets/views/history.scss @@ -15,6 +15,10 @@ $item-top-padding: $gutter-half; border-bottom: 1px solid $border-colour; } + .page-footer { + margin-bottom: 5px; + } + } &-user { @@ -31,7 +35,7 @@ $item-top-padding: $gutter-half; $axis-thickness: 2px; border-bottom: $axis-thickness solid $black; position: relative; - margin-bottom: 30px; + margin-bottom: 35px; height: 35px; &:before, @@ -50,6 +54,11 @@ $item-top-padding: $gutter-half; right: 0; } + &-without-border { + position: relative; + padding-top: 35px; + } + &-marker { $size: 20px; $border-thickness: 7px; diff --git a/app/main/forms.py b/app/main/forms.py index 6c831a8fc..940f730e9 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -952,6 +952,22 @@ class ProviderForm(StripWhitespaceForm): priority = IntegerField('Priority', [validators.NumberRange(min=1, max=100, message="Must be between 1 and 100")]) +class ProviderRatioForm(StripWhitespaceForm): + + ratio = RadioField(choices=[ + (str(value), '{}% / {}%'.format(value, 100 - value)) + for value in range(100, -10, -10) + ]) + + @property + def percentage_left(self): + return int(self.ratio.data) + + @property + def percentage_right(self): + return 100 - self.percentage_left + + class ServiceContactDetailsForm(StripWhitespaceForm): contact_details_type = RadioField( 'Type of contact details', diff --git a/app/main/views/providers.py b/app/main/views/providers.py index eecbf2176..d440daebd 100644 --- a/app/main/views/providers.py +++ b/app/main/views/providers.py @@ -7,7 +7,7 @@ from werkzeug.utils import redirect from app import format_date_numeric, provider_client from app.main import main -from app.main.forms import ProviderForm +from app.main.forms import ProviderForm, ProviderRatioForm from app.utils import user_is_platform_admin PROVIDER_PRIORITY_MEANING_SWITCHOVER = datetime(2019, 11, 29, 11, 0) @@ -67,12 +67,19 @@ def edit_sms_provider_ratio(): if provider['notification_type'] == 'sms' ], key=itemgetter('identifier'), reverse=True) + form = ProviderRatioForm(ratio=providers[0]['priority']) + + if form.validate_on_submit(): + provider_client.update_provider(providers[0]['id'], form.percentage_left) + provider_client.update_provider(providers[1]['id'], form.percentage_right) + if len(providers) < 2: abort(400) return render_template( 'views/providers/edit-sms-provider-ratio.html', versions=_chunk_versions_by_day(_get_versions(providers[0], providers[1])), + form=form, ) diff --git a/app/templates/components/page-footer.html b/app/templates/components/page-footer.html index 6bf8d32c1..b9bb4a0a0 100644 --- a/app/templates/components/page-footer.html +++ b/app/templates/components/page-footer.html @@ -6,9 +6,10 @@ secondary_link=False, secondary_link_text=None, delete_link=False, - delete_link_text="delete" + delete_link_text="delete", + centered_button=False ) %} -