diff --git a/app/assets/javascripts/fullscreenTable.js b/app/assets/javascripts/fullscreenTable.js index 27ff52636..f4ef998b6 100644 --- a/app/assets/javascripts/fullscreenTable.js +++ b/app/assets/javascripts/fullscreenTable.js @@ -52,7 +52,8 @@ 'height': this.nativeHeight, 'top': this.topOffset }) - ); + ) + .css('position', 'absolute'); this.$scrollableTable = this.$component.find('.fullscreen-scrollable-table'); this.$fixedTable = this.$component.find('.fullscreen-fixed-table'); @@ -100,6 +101,11 @@ this.$scrollableTable.scrollLeft() < (this.$table.width() - this.$scrollableTable.width()) ); + setTimeout( + () => this.$component.find('.fullscreen-right-shadow').addClass('with-transition'), + 3000 + ); + }; }; diff --git a/app/assets/stylesheets/components/fullscreen-table.scss b/app/assets/stylesheets/components/fullscreen-table.scss index b552fcbd8..d5c8f78fc 100644 --- a/app/assets/stylesheets/components/fullscreen-table.scss +++ b/app/assets/stylesheets/components/fullscreen-table.scss @@ -6,8 +6,7 @@ z-index: 10; overflow-y: hidden; box-sizing: border-box; - position: absolute; - margin: 5px 0 $gutter 0; + margin: 0 0 $gutter 0; padding: 0 0 0 0; overflow: hidden; border-bottom: 1px solid $border-colour; @@ -42,8 +41,13 @@ z-index: 200; &.visible { - transition: box-shadow 0.3s ease-in-out; + + &.with-transition { + transition: box-shadow 0.6s ease-out; + } + box-shadow: inset -1px 0 0 0 $border-colour, inset -3px 0 0 0 rgba($border-colour, 0.2); + } } diff --git a/app/assets/stylesheets/components/radio-select.scss b/app/assets/stylesheets/components/radio-select.scss index 19f835310..59586121a 100644 --- a/app/assets/stylesheets/components/radio-select.scss +++ b/app/assets/stylesheets/components/radio-select.scss @@ -1,5 +1,7 @@ .radio-select { + min-height: 39px; + &-column { display: inline-block; diff --git a/app/assets/stylesheets/components/table.scss b/app/assets/stylesheets/components/table.scss index 5bd4c68c0..e7ad5f38a 100644 --- a/app/assets/stylesheets/components/table.scss +++ b/app/assets/stylesheets/components/table.scss @@ -129,8 +129,31 @@ } &-index { + @include bold-16; width: 15px; + + a { + + &:before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + } + + &:focus { + + &:before { + background: $yellow; + z-index: -1; + } + + } + + } } p { diff --git a/app/main/forms.py b/app/main/forms.py index 9901cb89a..90171249f 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -534,15 +534,9 @@ class RequestToGoLiveForm(StripWhitespaceForm): 'Will the number of messages increase and when will that start?', validators=[DataRequired(message='Can’t be empty')] ) - upload_or_api = RadioField( - 'How are you going to send messages?', - choices=[ - ('File upload', 'Upload a spreadsheet of recipients'), - ('API', 'Integrate with the GOV.UK Notify API'), - ('API and file upload', 'Both') - ], - validators=[DataRequired()] - ) + method_one_off = BooleanField('One at a time') + method_upload = BooleanField('Upload a spreadsheet of recipients') + method_api = BooleanField('Integrate with the GOV.UK Notify API') class ProviderForm(StripWhitespaceForm): diff --git a/app/main/views/manage_users.py b/app/main/views/manage_users.py index 440ed6031..707317541 100644 --- a/app/main/views/manage_users.py +++ b/app/main/views/manage_users.py @@ -20,17 +20,10 @@ from app.main.forms import ( PermissionsForm ) from app import (user_api_client, current_service, service_api_client, invite_api_client) +from app.notify_client.models import roles from app.utils import user_has_permissions -roles = { - 'send_messages': ['send_texts', 'send_emails', 'send_letters'], - 'manage_templates': ['manage_templates'], - 'manage_service': ['manage_users', 'manage_settings'], - 'manage_api_keys': ['manage_api_keys'] -} - - @main.route("/services//users") @login_required @user_has_permissions('view_activity', admin_override=True) @@ -92,10 +85,9 @@ def edit_user_permissions(service_id, user_id): user_has_no_mobile_number = user.mobile_number is None form = PermissionsForm( - **{role: user.has_permissions(permissions=permissions) for role, permissions in roles.items()}, + **{role: user.has_permissions(*permissions) for role, permissions in roles.items()}, login_authentication=user.auth_type ) - if form.validate_on_submit(): user_api_client.set_user_permissions( user_id, service_id, @@ -122,7 +114,7 @@ def remove_user_from_service(service_id, user_id): # Need to make the email address read only, or a disabled field? # Do it through the template or the form class? form = PermissionsForm(**{ - role: user.has_permissions(permissions=permissions) for role, permissions in roles.items() + role: user.has_permissions(*permissions) for role, permissions in roles.items() }) if request.method == 'POST': diff --git a/app/main/views/send.py b/app/main/views/send.py index f375c1645..6ca253755 100644 --- a/app/main/views/send.py +++ b/app/main/views/send.py @@ -523,9 +523,12 @@ def _check_messages(service_id, template_type, upload_id, preview_row, letters_a count_of_recipients = len(list(recipients.rows)) - if preview_row < count_of_recipients: - template.values = recipients[preview_row] - elif preview_row > 0: + if preview_row < 2: + abort(404) + + if preview_row < count_of_recipients + 2: + template.values = recipients[preview_row - 2] + elif preview_row > 2: abort(404) session['upload_data']['notification_count'] = count_of_recipients @@ -553,7 +556,8 @@ def _check_messages(service_id, template_type, upload_id, preview_row, letters_a template.template_type == 'letter', not request.args.get('from_test'), )), - required_recipient_columns=OrderedSet(recipients.recipient_column_headers) - optional_address_columns + required_recipient_columns=OrderedSet(recipients.recipient_column_headers) - optional_address_columns, + preview_row=preview_row, ) @@ -561,7 +565,7 @@ def _check_messages(service_id, template_type, upload_id, preview_row, letters_a @main.route("/services///check//row-", methods=['GET']) @login_required @user_has_permissions('send_texts', 'send_emails', 'send_letters') -def check_messages(service_id, template_type, upload_id, row_index=0): +def check_messages(service_id, template_type, upload_id, row_index=2): data = _check_messages(service_id, template_type, upload_id, row_index) @@ -589,7 +593,7 @@ def check_messages(service_id, template_type, upload_id, row_index=0): @main.route("/services///check//row-.", methods=['GET']) @login_required @user_has_permissions('send_texts', 'send_emails', 'send_letters') -def check_messages_preview(service_id, template_type, upload_id, filetype, row_index=0): +def check_messages_preview(service_id, template_type, upload_id, filetype, row_index=2): if filetype not in ('pdf', 'png'): abort(404) diff --git a/app/main/views/service_settings.py b/app/main/views/service_settings.py index e1e11373b..c274c01bb 100644 --- a/app/main/views/service_settings.py +++ b/app/main/views/service_settings.py @@ -153,7 +153,6 @@ def service_request_to_go_live(service_id): form = RequestToGoLiveForm() if form.validate_on_submit(): - data = { 'person_email': current_user.email_address, 'person_name': current_user.name, @@ -161,13 +160,17 @@ def service_request_to_go_live(service_id): 'agent_team_id': current_app.config.get('DESKPRO_ASSIGNED_AGENT_TEAM_ID'), 'subject': 'Request to go live - {}'.format(current_service['name']), 'message': ( - 'On behalf of {} ({})\n\nExpected usage\n---' + 'On behalf of {} ({})\n' + '\n---' + '\nOrganisation type: {}' '\nMOU in place: {}' '\nChannel: {}\nStart date: {}\nStart volume: {}' - '\nPeak volume: {}\nUpload or API: {}' + '\nPeak volume: {}' + '\nFeatures: {}' ).format( current_service['name'], url_for('main.service_dashboard', service_id=current_service['id'], _external=True), + current_service['organisation_type'], form.mou.data, formatted_list(filter(None, ( 'email' if form.channel_email.data else None, @@ -177,7 +180,11 @@ def service_request_to_go_live(service_id): form.start_date.data, form.start_volume.data, form.peak_volume.data, - form.upload_or_api.data + formatted_list(filter(None, ( + 'one off' if form.method_one_off.data else None, + 'file upload' if form.method_upload.data else None, + 'API' if form.method_api.data else None, + )), before_each='', after_each='') ) } diff --git a/app/main/views/templates.py b/app/main/views/templates.py index 90f7d6b82..bd2ba0799 100644 --- a/app/main/views/templates.py +++ b/app/main/views/templates.py @@ -354,7 +354,7 @@ def add_service_template(service_id, template_type): def abort_403_if_not_admin_user(): - if not current_user.has_permissions([], admin_override=True): + if not current_user.has_permissions(admin_override=True): abort(403) diff --git a/app/notify_client/models.py b/app/notify_client/models.py index 1be63849b..3e6a7ae60 100644 --- a/app/notify_client/models.py +++ b/app/notify_client/models.py @@ -1,7 +1,18 @@ +from itertools import chain from flask_login import UserMixin, AnonymousUserMixin from flask import session +roles = { + 'send_messages': ['send_texts', 'send_emails', 'send_letters'], + 'manage_templates': ['manage_templates'], + 'manage_service': ['manage_users', 'manage_settings'], + 'manage_api_keys': ['manage_api_keys'] +} + +all_permissions = set(chain.from_iterable(roles.values())) | {'view_activity'} + + class User(UserMixin): def __init__(self, fields, max_failed_login_count=3): self._id = fields.get('id') @@ -91,7 +102,13 @@ class User(UserMixin): def permissions(self, permissions): raise AttributeError("Read only property") - def has_permissions(self, permissions=[], any_=False, admin_override=False): + def has_permissions(self, *permissions, any_=False, admin_override=False): + + unknown_permissions = set(permissions) - all_permissions + + if unknown_permissions: + raise TypeError('{} are not valid permissions'.format(unknown_permissions)) + # Only available to the platform admin user if admin_override and self.platform_admin: return True @@ -166,7 +183,7 @@ class InvitedUser(object): self.created_at = created_at self.auth_type = auth_type - def has_permissions(self, permissions): + def has_permissions(self, *permissions): return set(self.permissions) > set(permissions) def __eq__(self, other): diff --git a/app/templates/main_nav.html b/app/templates/main_nav.html index 2dc588d0e..c6165b2d7 100644 --- a/app/templates/main_nav.html +++ b/app/templates/main_nav.html @@ -44,17 +44,17 @@