From 17b99c9bf23f735841f957a9f41efb3d7adedcc5 Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Fri, 19 Feb 2016 15:02:13 +0000 Subject: [PATCH 1/6] Add pages to invite, edit, and delete users MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This takes the original prototype version of this page, and, using the same fake data (ie nothing is wired up): - adds an invite users page - adds an edit (and delete) user page Both these pages allow the user to set another user’s permissions. This commit adds images for the ticks and crosses, so we have control over their appearance. --- app/assets/images/cross.png | Bin 0 -> 971 bytes app/assets/images/tick-white.png | Bin 0 -> 622 bytes app/assets/images/tick.png | Bin 0 -> 834 bytes app/assets/images/tick.psd | Bin 0 -> 60625 bytes app/assets/stylesheets/app.scss | 13 ++ app/assets/stylesheets/components/banner.scss | 16 +- app/assets/stylesheets/components/table.scss | 24 +++ app/assets/stylesheets/components/yes-no.scss | 36 +++++ app/assets/stylesheets/main.scss | 1 + app/main/__init__.py | 2 +- app/main/dao/services_dao.py | 8 +- app/main/forms.py | 8 +- app/main/views/index.py | 60 -------- app/main/views/manage_users.py | 141 ++++++++++++++++++ app/templates/components/table.html | 12 ++ app/templates/components/yes-no.html | 17 +++ app/templates/flash_messages.html | 2 +- app/templates/main_nav.html | 2 +- app/templates/views/api-keys.html | 2 +- app/templates/views/invite-user.html | 48 ++++++ app/templates/views/manage-users.html | 95 ++++++------ gulpfile.babel.js | 1 + tests/app/main/views/test_manage_users.py | 87 +++++++++++ 23 files changed, 443 insertions(+), 132 deletions(-) create mode 100644 app/assets/images/cross.png create mode 100644 app/assets/images/tick-white.png create mode 100644 app/assets/images/tick.png create mode 100644 app/assets/images/tick.psd create mode 100644 app/assets/stylesheets/components/yes-no.scss create mode 100644 app/main/views/manage_users.py create mode 100644 app/templates/components/yes-no.html create mode 100644 app/templates/views/invite-user.html create mode 100644 tests/app/main/views/test_manage_users.py diff --git a/app/assets/images/cross.png b/app/assets/images/cross.png new file mode 100644 index 0000000000000000000000000000000000000000..fab3b895c6253a371bb79c98e4a0d74de6faee07 GIT binary patch literal 971 zcmV;+12p`JP)C0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUyCs0gOMfvOOzdA?1IY+l5G4a&a@zd7v z)YrZ@M7}md#!gzrOOUfKSR&0000DbW%=J|NsC0|NsC0|NsC006pj;h5!Hq zAxT6*RCwC#+sksRKn#T8PTP(z@cz$xT&YwF!)VmfAkK!YvPkiNi`Zr)gZV%I-~^n2 z6L11ffd41o&!iI1>mGpb%k-5J;A^^E#{uvDloGuAlOTZ7&j^0{f&fxKF8J;H0%-lH z;IHorAogQ|Mc)(ju6kYeBZ7~Q#^s4EERW17X4aKGxs76NUwY{q1K#t$^@94J!qNaz9WfAhbK8DgmM16V?a_ z{jRt|Kt#?3`wNKJ$!Ko@5j`93DT{id(#7X80kvz-RRW5a7RSGjX#b&JS^Tr; z`D4283J9OS^w~P9`toeOtS<^s*B7Vzg?&kY%D%K;DD4XZ)b@o9M{(a3pt|qwSjzj3 z0QG%m%X2`#E5H%`?w;w8en)^~`khVJL48wzqx$Bq?XbQfz;S(J+jn5UDZr8a=DzXJ zenWu4{^Kzyz*wJ1Zn})6Rc8M)D8OicRIvR0nLPi71y2KTyQKPI!R2QC4}=8E^M6S2 z(O}tuAlw&`+zK9cH_C~Z{7(-0kiwYb$|O=APJb=_HFu&PeVb#?5=IwZ`}{L z0%kW|jehfv$PqBRXKMA^_k>*mvs<2K-?%I82$1SNs@%#Dh}bpB4WIn0d&BN?(|kzXi-a=0l;c>>6PQX6_1^_-|%}Fz(lac@c002ovPDHLkV1ljd@W=oF literal 0 HcmV?d00001 diff --git a/app/assets/images/tick-white.png b/app/assets/images/tick-white.png new file mode 100644 index 0000000000000000000000000000000000000000..c08e57c4f00bfaacc07e911c0ddd5ccbee50d7ae GIT binary patch literal 622 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&NtU=qlmzFem6RtIr7}3C}&gSx? zoGs}Naj!=CNQkNa#aKjYa{e$Hoe`DxC)>Cfk)+8+N|{=dFYuKTy- zfBmJ${a=2l+y3`BUw`Q}|I4raUw(O8UNKz%e~Pp0=YP&X=KFdu6D0Z`BwGB}5y-r5 z4`#Z8m>>nke?39W{a~gqhzVBvAEfp>SnYq1+VAyOoMrQWyPIAq+&+J+e(sC?kvXe! z-^Hn&Vf9$)+)O$Zv&*sZAzyE4l%kTJ}OXH+?EVH91(cy>^ z3~1B;)4eE+b;<+o@5dHsGtGL={r=gC)l9RVai=#bv^S*vJQS{SQgwmnzL<}vi(dKt zVz?&Kcv5$P<~|+6B~~mZho>uYg-vfz`>{*r`08MXzcsNoLV?>GcKzH{qO+8f@$08j z#iJLeG+6zNvYU5xNyD!nQ8r4~w>0ef5w&aCf|iD=A5*PWbpPB^oyJ!8QLK0V{`;o4 jnW7W8pRn4oNH84PqHX>@evdRTB`|op`njxgN@xNA5eOhG literal 0 HcmV?d00001 diff --git a/app/assets/images/tick.png b/app/assets/images/tick.png new file mode 100644 index 0000000000000000000000000000000000000000..77591a5951c87114f2f1e16e4916aa9621589af5 GIT binary patch literal 834 zcmV-I1HJr-P)C0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUyDo{*RMe6nME}330nO_x*OVQ}uCY4yy z>D)u3ZbYMRE0i6rCr7}gKj#GK)z1mgt)CH~Uq3BC$9__Pp8b>nUHb_E`u6t%bnfp2 z=-uB6(7nGApnrcYfJ1*JfJc8RfJ=WNfKUG|fK&e^fLH%1fLs3|fM5SEfMfqAfM@?I zfNOuA&+zRBz`0NT3~2gHfT~Xf==wZ>vQGnO`z(OEPXg%s9Dt!u0a*H3!08)Z`Z>Vq zI3LiXp8;I@bECl6PXliKHi5OD1U&jJ0&_nFc=hW7dp`kK^lJh_e-D_Bt6wPz82ue! z(=Q1q{VibE&kH#H4It6a2}u1lAlXj~Sp5|s(@zO#{UspV-wSyC1)$L13W)tTpx9pv znEe-^(q9Uw{U@N>e+#(%2cV(<6p;IOKvVxNVE1o;#{N}6?_UAU{dxZRVfQZpL0|mT z`w`Clh_i1$0>QO^`3!tY(AK~H6?{ezS`Sw7GlJIs{hN?42<=DP_z6LK|M}mLD-iTw z?*mMMr2l>+;t53k#XBKOAnPyRia7#de|0Wk2&Db>$;d7c_cv!lt3cl0o{nt-MSpic zU=e8g`x_FyK-Ev&5orawe(IJ?Cs6j2_k?n_h%0(1E%$_0VC{S?K5 zz+L}}Qb8c^zX^Wy_mAI~EwM~TdSAFjGoAS>pwammfB_hQ0r(eS0Bl|s!QRnW-2eap M07*qoM6N<$g4)oE3;+NC literal 0 HcmV?d00001 diff --git a/app/assets/images/tick.psd b/app/assets/images/tick.psd new file mode 100644 index 0000000000000000000000000000000000000000..7f90c3098b56ceaee937fcce9e83b505636d060a GIT binary patch literal 60625 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 literal 0 HcmV?d00001 diff --git a/app/assets/stylesheets/app.scss b/app/assets/stylesheets/app.scss index e4a629217..57b5db45a 100644 --- a/app/assets/stylesheets/app.scss +++ b/app/assets/stylesheets/app.scss @@ -66,3 +66,16 @@ a { font-family: monospace; overflow-x: scroll; } + +.inline { + + .block-label { + + @include media(tablet) { + float: none; + display: inline-block; + } + + } + +} diff --git a/app/assets/stylesheets/components/banner.scss b/app/assets/stylesheets/components/banner.scss index 3a8898c83..7c2fd2b99 100644 --- a/app/assets/stylesheets/components/banner.scss +++ b/app/assets/stylesheets/components/banner.scss @@ -16,19 +16,13 @@ .banner-with-tick, .banner-default-with-tick { - @extend %banner; padding: $gutter-half ($gutter + $gutter-half); - - &:before { - @include core-24; - content: '✔'; - position: absolute; - top: $gutter-half; - left: $gutter-half; - margin-top: -2px; - } - + background-image: file-url('tick-white.png'); + background-size: 19px; + background-repeat: no-repeat; + background-position: $gutter-half $gutter-half; + font-weight: bold; } .banner-dangerous { diff --git a/app/assets/stylesheets/components/table.scss b/app/assets/stylesheets/components/table.scss index 1b40c5fa8..477ddb208 100644 --- a/app/assets/stylesheets/components/table.scss +++ b/app/assets/stylesheets/components/table.scss @@ -7,6 +7,12 @@ margin: 40px 0 5px 0; } +.table-field-headings { + th { + padding: 0 0 5px 0; + } +} + %table-field, .table-field { @@ -36,6 +42,23 @@ } + &-yes, + &-no { + display: block; + text-indent: -999em; + background-size: 19px 19px; + background-repeat: no-repeat; + background-position: 50% 50%; + } + + &-yes { + background-image: file-url('tick.png'); + } + + &-no { + background-image: file-url('cross.png'); + } + &-missing { color: $error-colour; font-weight: bold; @@ -77,4 +100,5 @@ margin-top: -20px; border-bottom: 1px solid $border-colour; padding-bottom: 10px; + text-align: center; } diff --git a/app/assets/stylesheets/components/yes-no.scss b/app/assets/stylesheets/components/yes-no.scss new file mode 100644 index 000000000..9a71113a5 --- /dev/null +++ b/app/assets/stylesheets/components/yes-no.scss @@ -0,0 +1,36 @@ +.yes-no-wrapper { + border-bottom: 1px solid $border-colour; + margin: 0 0 $gutter 0; +} + +.yes-no { + + border-top: 1px solid $border-colour; + padding: 10px 0; + + &-label { + padding-top: 19px; + float: left; + } + + &-fields { + + text-align: right; + + .block-label { + + @include media(tablet) { + + margin-bottom: 0; + + &:last-child { + margin-right: 0; + } + + } + + } + + } + +} diff --git a/app/assets/stylesheets/main.scss b/app/assets/stylesheets/main.scss index eb420be44..e9c36ad7a 100644 --- a/app/assets/stylesheets/main.scss +++ b/app/assets/stylesheets/main.scss @@ -47,6 +47,7 @@ $path: '/static/images/'; @import 'components/browse-list'; @import 'components/email-message'; @import 'components/api-key'; +@import 'components/yes-no'; @import 'views/job'; @import 'views/edit-template'; diff --git a/app/main/__init__.py b/app/main/__init__.py index 7ce30ff8a..fbfd2562a 100644 --- a/app/main/__init__.py +++ b/app/main/__init__.py @@ -5,5 +5,5 @@ main = Blueprint('main', __name__) from app.main.views import ( index, sign_in, sign_out, register, two_factor, verify, sms, add_service, code_not_received, jobs, dashboard, templates, service_settings, forgot_password, - new_password, styleguide, user_profile, choose_service, api_keys + new_password, styleguide, user_profile, choose_service, api_keys, manage_users ) diff --git a/app/main/dao/services_dao.py b/app/main/dao/services_dao.py index 604b2e27d..77f6610dc 100644 --- a/app/main/dao/services_dao.py +++ b/app/main/dao/services_dao.py @@ -1,7 +1,7 @@ -from flask import url_for +from flask import url_for, abort from app import notifications_api_client -from notifications_python_client.errors import HTTPError from app.utils import BrowsableItem +from notifications_python_client.errors import HTTPError def insert_new_service(service_name, user_id): @@ -29,7 +29,9 @@ def get_service_by_id(id_): def get_service_by_id_or_404(id_): try: - return get_service_by_id(id_) + return notifications_api_client.get_service(id_)['data'] + except KeyError: + abort(404) except HTTPError as e: if e.status_code == 404: abort(404) diff --git a/app/main/forms.py b/app/main/forms.py index ddab3fe01..20b007514 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -21,10 +21,10 @@ from app.utils import ( ) -def email_address(): +def email_address(label='Email address'): gov_uk_email \ = "(^[^@^\\s]+@[^@^\\.^\\s]+(\\.[^@^\\.^\\s]*)*.gov.uk)" - return EmailField('Email address', validators=[ + return EmailField(label, validators=[ Length(min=5, max=255), DataRequired(message='Email cannot be empty'), Email(message='Enter a valid email address'), @@ -96,6 +96,10 @@ class RegisterUserForm(Form): password = password() +class InviteUserForm(Form): + email_address = email_address('Their email address') + + class TwoFactorForm(Form): def __init__(self, validate_code_func, *args, **kwargs): ''' diff --git a/app/main/views/index.py b/app/main/views/index.py index 151ee4e80..1200cf65b 100644 --- a/app/main/views/index.py +++ b/app/main/views/index.py @@ -34,63 +34,3 @@ def send_email(service_id): @login_required def check_email(service_id): return render_template('views/check-email.html') - - -@main.route("/services//manage-users") -@login_required -def manage_users(service_id): - users = [ - { - 'name': 'Henry Hadlow', - 'permission_send_messages': True, - 'permission_manage_service': False, - 'permission_manage_api_keys': False - }, - - { - 'name': 'Pete Herlihy', - 'permission_send_messages': False, - 'permission_manage_service': False, - 'permission_manage_api_keys': False, - }, - { - 'name': 'Chris Hill-Scott', - 'permission_send_messages': True, - 'permission_manage_service': True, - 'permission_manage_api_keys': True - }, - { - 'name': 'Martyn Inglis', - 'permission_send_messages': True, - 'permission_manage_service': True, - 'permission_manage_api_keys': True - } - ] - invited_users = [ - { - 'email_localpart': 'caley.smolska', - 'permission_send_messages': True, - 'permission_manage_service': False, - 'permission_manage_api_keys': False - }, - - { - 'email_localpart': 'ash.stephens', - 'permission_send_messages': False, - 'permission_manage_service': False, - 'permission_manage_api_keys': False - }, - { - 'email_localpart': 'nicholas.staples', - 'permission_send_messages': True, - 'permission_manage_service': True, - 'permission_manage_api_keys': True - }, - { - 'email_localpart': 'adam.shimali', - 'permission_send_messages': True, - 'permission_manage_service': True, - 'permission_manage_api_keys': True - } - ] - return render_template('views/manage-users.html', service_id=service_id, users=users, invited_users=invited_users) diff --git a/app/main/views/manage_users.py b/app/main/views/manage_users.py new file mode 100644 index 000000000..54be424d2 --- /dev/null +++ b/app/main/views/manage_users.py @@ -0,0 +1,141 @@ +from flask import ( + request, + render_template, + redirect, + abort, + url_for, + flash +) + +from flask_login import login_required, current_user + +from app.main import main +from app.main.dao import users_dao +from app.main.forms import InviteUserForm +from app.main.dao.services_dao import get_service_by_id_or_404 +from app import user_api_client + + +fake_users = [ + { + 'name': 'Henry Hadlow', + 'email_localpart': 'henry.hadlow', + 'permission_send_messages': True, + 'permission_manage_service': False, + 'permission_manage_api_keys': False, + 'active': True + }, + + { + 'name': 'Pete Herlihy', + 'email_localpart': 'pete.herlihy', + 'permission_send_messages': False, + 'permission_manage_service': False, + 'permission_manage_api_keys': False, + 'active': True + }, + { + 'name': 'Chris Hill-Scott', + 'email_localpart': 'chris.hill-scott', + 'permission_send_messages': True, + 'permission_manage_service': True, + 'permission_manage_api_keys': True, + 'active': True + }, + { + 'name': 'Martyn Inglis', + 'email_localpart': 'martyn.inglis', + 'permission_send_messages': True, + 'permission_manage_service': True, + 'permission_manage_api_keys': True, + 'active': True + }, + { + 'email_localpart': 'caley.smolska', + 'permission_send_messages': True, + 'permission_manage_service': False, + 'permission_manage_api_keys': False, + 'active': False + }, + + { + 'email_localpart': 'ash.stephens', + 'permission_send_messages': False, + 'permission_manage_service': False, + 'permission_manage_api_keys': False, + 'active': False + } +] + + +@main.route("/services//users") +@login_required +def manage_users(service_id): + return render_template( + 'views/manage-users.html', + service_id=service_id, + users=[ + dict(id=user_id, **user) for (user_id, user) in enumerate(fake_users) if user['active'] + ], + invited_users=[ + dict(id=user_id, **user) for (user_id, user) in enumerate(fake_users) if not user['active'] + ] + ) + + +@main.route("/services//users/invite", methods=['GET', 'POST']) +@login_required +def invite_user(service_id): + + form = InviteUserForm() + + if form.validate_on_submit(): + flash('Invite sent to {}'.format(form.email_address.data), 'default_with_tick') + return redirect(url_for('.manage_users', service_id=service_id)) + + return render_template( + 'views/invite-user.html', + user={}, + service=get_service_by_id_or_404(service_id), + service_id=service_id, + form=form + ) + + +@main.route("/services//users/", methods=['GET', 'POST']) +@login_required +def edit_user(service_id, user_id): + + if request.method == 'POST': + return redirect(url_for('.manage_users', service_id=service_id)) + + return render_template( + 'views/invite-user.html', + user=fake_users[int(user_id)], + user_id=user_id, + service=get_service_by_id_or_404(service_id), + service_id=service_id + ) + + +@main.route("/services//users//delete", methods=['GET', 'POST']) +@login_required +def delete_user(service_id, user_id): + + if request.method == 'POST': + return redirect(url_for('.manage_users', service_id=service_id)) + + user = fake_users[int(user_id)] + + flash( + 'Are you sure you want to delete {}’s account?'.format(user.get('name') or user['email_localpart']), + 'delete' + ) + + return render_template( + 'views/invite-user.html', + user=user, + user_id=user_id, + service=get_service_by_id_or_404(service_id), + service_id=service_id + ) diff --git a/app/templates/components/table.html b/app/templates/components/table.html index ed9b35902..9ab70a29f 100644 --- a/app/templates/components/table.html +++ b/app/templates/components/table.html @@ -55,6 +55,18 @@ {%- endmacro %} +{% macro text_field(text) -%} + {% call field() %} + {{ text }} + {% endcall %} +{%- endmacro %} + +{% macro boolean_field(yes) -%} + {% call field(status='yes' if yes else 'no') %} + {{ "Yes" if yes else "No" }} + {% endcall %} +{%- endmacro %} + {% macro right_aligned_field_heading(text) %} {{ text }} {%- endmacro %} diff --git a/app/templates/components/yes-no.html b/app/templates/components/yes-no.html new file mode 100644 index 000000000..4432d6a9f --- /dev/null +++ b/app/templates/components/yes-no.html @@ -0,0 +1,17 @@ +{% macro yes_no(name, label, current_value=None) %} +
+ + {{ label }} + +
+ + +
+
+{% endmacro %} diff --git a/app/templates/flash_messages.html b/app/templates/flash_messages.html index ec2b1a5d0..bf109421e 100644 --- a/app/templates/flash_messages.html +++ b/app/templates/flash_messages.html @@ -5,7 +5,7 @@ {{ banner( message, 'default' if ((category == 'default') or (category == 'default_with_tick')) else 'dangerous', - delete_button="Yes, delete this template" if 'delete' == category else None, + delete_button="Yes, delete" if 'delete' == category else None, with_tick=True if category == 'default_with_tick' else False )}} {% endfor %} diff --git a/app/templates/main_nav.html b/app/templates/main_nav.html index c4e48051a..7e5563890 100644 --- a/app/templates/main_nav.html +++ b/app/templates/main_nav.html @@ -8,7 +8,7 @@
    diff --git a/app/templates/views/api-keys.html b/app/templates/views/api-keys.html index 7ae0c1b90..b7434514c 100644 --- a/app/templates/views/api-keys.html +++ b/app/templates/views/api-keys.html @@ -57,7 +57,7 @@ {% endif %} {% endcall %} -

    +

    diff --git a/app/templates/views/invite-user.html b/app/templates/views/invite-user.html new file mode 100644 index 000000000..2ca19bc2f --- /dev/null +++ b/app/templates/views/invite-user.html @@ -0,0 +1,48 @@ +{% extends "withnav_template.html" %} +{% from "components/yes-no.html" import yes_no %} +{% from "components/textbox.html" import textbox %} +{% from "components/page-footer.html" import page_footer %} + +{% block page_title %} +Manage users – GOV.UK Notify +{% endblock %} + +{% block maincolumn_content %} + +

    + {{ user.name or user.email_localpart or "Add a new team member" }} +

    + +
    +
    + + {% if user %} +

    + {{ user.email_localpart }}@digital.cabinet-office.gov.uk +

    + {% else %} + {{ textbox(form.email_address, hint='Email address must end in .gov.uk', width='1-1') }} + {% endif %} + +
    + + Permissions + + {{ yes_no('send_messages', 'Send messages', user.permission_send_messages) }} + {{ yes_no('manage_service', 'Manage service', user.permission_manage_service) }} + {{ yes_no('manage_api_keys', 'Manage API keys', user.permission_manage_api_keys) }} +
    + + {% if user %} + {{ page_footer( + 'Save', + delete_link=url_for('.delete_user', service_id=service_id, user_id=user_id), + delete_link_text='delete this account' + ) }} + {% else %} + {{ page_footer('Send invitation email') }} + {% endif %} + +
    +
    +{% endblock %} diff --git a/app/templates/views/manage-users.html b/app/templates/views/manage-users.html index ac881a790..4af05c7aa 100644 --- a/app/templates/views/manage-users.html +++ b/app/templates/views/manage-users.html @@ -1,65 +1,56 @@ {% extends "withnav_template.html" %} -{% from "components/table.html" import list_table, row, field %} +{% from "components/table.html" import list_table, row, field, boolean_field, hidden_field_heading %} {% from "components/page-footer.html" import page_footer %} +{% set table_options = { + 'field_headings': [ + 'Name', 'Send messages', 'Manage service', 'Manage API keys', hidden_field_heading('Link to change') + ], + 'field_headings_visible': True, + 'caption_visible': True +} %} + {% block page_title %} Manage users – GOV.UK Notify {% endblock %} {% block maincolumn_content %} -

    Manage users

    +

    + Manage team +

    -

    - Invite users -

    + {% call(item) list_table( + users, caption='Active', **table_options + ) %} + {% call field() %} + {{ item.name }} + {% endcall %} + {{ boolean_field(item.permission_send_messages) }} + {{ boolean_field(item.permission_manage_service) }} + {{ boolean_field(item.permission_manage_api_keys) }} + {% call field(align='right') %} + Change + {% endcall %} + {% endcall %} + -{% call(item) list_table( - users, - caption='Active users', - field_headings=['Name', 'Send messages', 'Manage Service', 'Manage API keys', 'Link to change'], - field_headings_visible=True, - caption_visible=True -) %} - {% call field() %} - {{ item.name }} - {% endcall %} - {% call field() %} - {{ "✔" if item.permission_send_messages else "❌" }} - {% endcall %} - {% call field() %} - {{ "✔" if item.permission_manage_service else "❌" }} - {% endcall %} - {% call field() %} - {{ "✔" if item.permission_manage_api_keys else "❌" }} - {% endcall %} - {% call field(align='right') %} - Change - {% endcall %} -{% endcall %} - -{% call(item) list_table( - invited_users, - caption='Invited users', - field_headings=['Name', 'Send messages', 'Manage Service', 'Manage API keys', 'Link to change'], - field_headings_visible=True, - caption_visible=True -) %} - {% call field() %} - {{ item.email_localpart }} - {% endcall %} - {% call field() %} - {{ "✔" if item.permission_send_messages else "❌" }} - {% endcall %} - {% call field() %} - {{ "✔" if item.permission_manage_service else "❌" }} - {% endcall %} - {% call field() %} - {{ "✔" if item.permission_manage_api_keys else "❌" }} - {% endcall %} - {% call field(align='right') %} - Change - {% endcall %} -{% endcall %} + {% if invited_users %} + {% call(item) list_table( + invited_users, caption='Invited', **table_options + ) %} + {% call field() %} + {{ item.email_localpart }} + {% endcall %} + {{ boolean_field(item.permission_send_messages) }} + {{ boolean_field(item.permission_manage_service) }} + {{ boolean_field(item.permission_manage_api_keys) }} + {% call field(align='right') %} + Change + {% endcall %} + {% endcall %} + {% endif %} {% endblock %} diff --git a/gulpfile.babel.js b/gulpfile.babel.js index 7fbc03610..3938b00a8 100644 --- a/gulpfile.babel.js +++ b/gulpfile.babel.js @@ -82,6 +82,7 @@ gulp.task('watchForChanges', function() { gulp.watch(paths.src + 'javascripts/**/*', ['javascripts']); gulp.watch(paths.src + 'stylesheets/**/*', ['sass']); gulp.watch(paths.src + 'images/**/*', ['images']); + gulp.watch('gulpfile.babel.js', ['default']); }); gulp.task('lint:sass', () => gulp diff --git a/tests/app/main/views/test_manage_users.py b/tests/app/main/views/test_manage_users.py new file mode 100644 index 000000000..000464ecb --- /dev/null +++ b/tests/app/main/views/test_manage_users.py @@ -0,0 +1,87 @@ +import json +from flask import url_for + + +def test_should_show_overview_page( + app_, + api_user_active, + mock_login, + mock_get_service +): + with app_.test_request_context(): + with app_.test_client() as client: + client.login(api_user_active) + response = client.get(url_for('main.manage_users', service_id=55555)) + + assert 'Manage team' in response.get_data(as_text=True) + assert 'Henry Hadlow' in response.get_data(as_text=True) + assert 'caley.smolska' in response.get_data(as_text=True) + assert response.status_code == 200 + + +def test_should_show_page_for_one_user( + app_, + api_user_active, + mock_login, + mock_get_service +): + with app_.test_request_context(): + with app_.test_client() as client: + client.login(api_user_active) + response = client.get(url_for('main.edit_user', service_id=55555, user_id=0)) + + assert 'Henry Hadlow' in response.get_data(as_text=True) + assert response.status_code == 200 + + +def test_redirect_after_saving_user( + app_, + api_user_active, + mock_login, + mock_get_service +): + with app_.test_request_context(): + with app_.test_client() as client: + client.login(api_user_active) + response = client.post(url_for( + 'main.edit_user', service_id=55555, user_id=0 + )) + + assert response.status_code == 302 + assert response.location == url_for( + 'main.manage_users', service_id=55555, _external=True + ) + + +def test_should_show_page_for_inviting_user( + app_, + api_user_active, + mock_login, + mock_get_service +): + with app_.test_request_context(): + with app_.test_client() as client: + client.login(api_user_active) + response = client.get(url_for('main.invite_user', service_id=55555)) + + assert 'Add a new team member' in response.get_data(as_text=True) + assert response.status_code == 200 + + +def test_invite_user( + app_, + api_user_active, + mock_login, + mock_get_service +): + with app_.test_request_context(): + with app_.test_client() as client: + client.login(api_user_active) + response = client.post( + url_for('main.invite_user', service_id=55555), + data={'email_address': 'test@example.gov.uk'}, + follow_redirects=True + ) + + assert response.status_code == 200 + assert 'Invite sent to test@example.gov.uk' in response.get_data(as_text=True) From fd54eeaeb7552f547d3e20a5c8650cab04e6b6b2 Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Sat, 20 Feb 2016 14:11:33 +0000 Subject: [PATCH 2/6] Inline images in CSS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because this commit’s parent added a few new images, we are now serving at least a handful of images, therefore a few additional HTTP requests. It’s better to combine multiple HTTP requests into one for performance reasons (up to a point). This commit adds an extra step to the preprocessing of SASS files which takes any images it finds, base64 encodes them and inlines them into the distributed CSS files. It also modifies the content security policy to allow inline images. --- app/__init__.py | 2 +- gulpfile.babel.js | 1 + package.json | 1 + tests/app/main/views/test_headers.py | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 281cc747e..cf4529634 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -155,7 +155,7 @@ def useful_headers_after_request(response): response.headers.add('X-Content-Type-Options', 'nosniff') response.headers.add('X-XSS-Protection', '1; mode=block') response.headers.add('Content-Security-Policy', - "default-src 'self' 'unsafe-inline'; font-src 'self' data:;") # noqa + "default-src 'self' 'unsafe-inline'; font-src 'self' data:; img-src 'self' data:;") # noqa if 'Cache-Control' in response.headers: del response.headers['Cache-Control'] response.headers.add( diff --git a/gulpfile.babel.js b/gulpfile.babel.js index 3938b00a8..273ad7801 100644 --- a/gulpfile.babel.js +++ b/gulpfile.babel.js @@ -62,6 +62,7 @@ gulp.task('sass', () => gulp paths.npm + 'govuk_frontend_toolkit/stylesheets/' ] })) + .pipe(plugins.base64({baseDir: 'app', debug: true})) .pipe(gulp.dest(paths.dist + '/stylesheets')) ); diff --git a/package.json b/package.json index add7e8f76..fdc1d7edf 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "gulp": "3.9.0", "gulp-add-src": "0.2.0", "gulp-babel": "6.1.1", + "gulp-base64": "0.1.3", "gulp-concat": "2.6.0", "gulp-include": "2.1.0", "gulp-jquery": "1.1.1", diff --git a/tests/app/main/views/test_headers.py b/tests/app/main/views/test_headers.py index 4fd7148fb..bf6303f25 100644 --- a/tests/app/main/views/test_headers.py +++ b/tests/app/main/views/test_headers.py @@ -6,4 +6,4 @@ def test_owasp_useful_headers_set(app_): assert response.headers['X-Frame-Options'] == 'deny' assert response.headers['X-Content-Type-Options'] == 'nosniff' assert response.headers['X-XSS-Protection'] == '1; mode=block' - assert response.headers['Content-Security-Policy'] == "default-src 'self' 'unsafe-inline'; font-src 'self' data:;" # noqa + assert response.headers['Content-Security-Policy'] == "default-src 'self' 'unsafe-inline'; font-src 'self' data:; img-src 'self' data:;" # noqa From f6fe61d10d12277f6fa5c76a0983af6e7224f811 Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Sat, 20 Feb 2016 14:48:23 +0000 Subject: [PATCH 3/6] Compress GOV.UK Template assets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While looking at front end performance I noticed that the GOV.UK Template CSS and Javascript files weren’t being minified, so were being served with (for example) lots of comments still in the code. This changes the Gulp task from simply copying the files to also minfiying them, where appropriate. --- gulpfile.babel.js | 42 +++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/gulpfile.babel.js b/gulpfile.babel.js index 273ad7801..006e1505a 100644 --- a/gulpfile.babel.js +++ b/gulpfile.babel.js @@ -15,7 +15,9 @@ var gulp = require('gulp'), src: 'app/assets/', dist: 'app/static/', templates: 'app/templates/', - npm: 'node_modules/' + npm: 'node_modules/', + template: 'node_modules/govuk_template_jinja/', + toolkit: 'node_modules/govuk_frontend_toolkit/' }; // 3. TASKS @@ -23,18 +25,24 @@ var gulp = require('gulp'), // Move GOV.UK template resources -gulp.task('copy:govuk_template:template', () => gulp.src(paths.npm + '/govuk_template_jinja/views/layouts/govuk_template.html') +gulp.task('copy:govuk_template:template', () => gulp.src(paths.template + 'views/layouts/govuk_template.html') .pipe(gulp.dest(paths.templates)) ); -gulp.task('copy:govuk_template:assets', () => gulp.src(paths.npm + '/govuk_template_jinja/assets/**/*') - .pipe(gulp.dest(paths.dist)) +gulp.task('copy:govuk_template:css', () => gulp.src(paths.template + 'assets/stylesheets/**/*.css') + .pipe(plugins.sass({outputStyle: 'compressed'})) + .pipe(gulp.dest(paths.dist + 'stylesheets/')) +); + +gulp.task('copy:govuk_template:js', () => gulp.src(paths.template + 'assets/javascripts/**/*.js') + .pipe(plugins.uglify()) + .pipe(gulp.dest(paths.dist + 'javascripts/')) ); gulp.task('javascripts', () => gulp .src([ - paths.npm + 'govuk_frontend_toolkit/javascripts/govuk/modules.js', - paths.npm + 'govuk_frontend_toolkit/javascripts/govuk/selection-buttons.js', + paths.toolkit + 'javascripts/govuk/modules.js', + paths.toolkit + 'javascripts/govuk/selection-buttons.js', paths.src + 'javascripts/apiKey.js', paths.src + 'javascripts/autofocus.js', paths.src + 'javascripts/highlightTags.js', @@ -59,11 +67,11 @@ gulp.task('sass', () => gulp outputStyle: 'compressed', includePaths: [ paths.npm + 'govuk-elements-sass/public/sass/', - paths.npm + 'govuk_frontend_toolkit/stylesheets/' + paths.toolkit + 'stylesheets/' ] })) - .pipe(plugins.base64({baseDir: 'app', debug: true})) - .pipe(gulp.dest(paths.dist + '/stylesheets')) + .pipe(plugins.base64({baseDir: 'app'})) + .pipe(gulp.dest(paths.dist + 'stylesheets/')) ); @@ -72,9 +80,10 @@ gulp.task('sass', () => gulp gulp.task('images', () => gulp .src([ paths.src + 'images/**/*', - paths.npm + 'govuk_frontend_toolkit/images/**/*' + paths.toolkit + 'images/**/*', + paths.template + 'assets/images/**/*' ]) - .pipe(gulp.dest(paths.dist + '/images')) + .pipe(gulp.dest(paths.dist + 'images/')) ); @@ -87,7 +96,7 @@ gulp.task('watchForChanges', function() { }); gulp.task('lint:sass', () => gulp - .src(paths.src + '/stylesheets/**/*.scss') + .src(paths.src + 'stylesheets/**/*.scss') .pipe(plugins.sassLint()) .pipe(plugins.sassLint.format(stylish)) .pipe(plugins.sassLint.failOnError()) @@ -106,7 +115,14 @@ gulp.task('lint', // Default: compile everything gulp.task('default', - ['copy:govuk_template:template', 'copy:govuk_template:assets', 'javascripts', 'sass', 'images'] + [ + 'copy:govuk_template:template', + 'copy:govuk_template:css', + 'copy:govuk_template:js', + 'javascripts', + 'sass', + 'images' + ] ); // Optional: recompile on changes From 65dc7578e510cf4d8c9ad094ef11dc42d3e5fe3c Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Sun, 21 Feb 2016 11:15:15 +0000 Subject: [PATCH 4/6] Updates to styleguide Brings in changes to the email message and table patterns. --- app/main/views/styleguide.py | 12 +- app/templates/views/styleguide.html | 187 +++++++++++++++++----------- 2 files changed, 122 insertions(+), 77 deletions(-) diff --git a/app/main/views/styleguide.py b/app/main/views/styleguide.py index e2b540708..eed30b20e 100644 --- a/app/main/views/styleguide.py +++ b/app/main/views/styleguide.py @@ -1,6 +1,7 @@ from flask import render_template, current_app, abort from flask_wtf import Form from wtforms import StringField, PasswordField, TextAreaField, FileField, validators +from utils.template import Template from app.main import main @@ -17,13 +18,16 @@ def styleguide(): message = TextAreaField(u'Message') file_upload = FileField('Upload a CSV file to add your recipients’ details') + sms = "Your vehicle tax for ((registration number)) is due on ((date)). Renew online at www.gov.uk/vehicle-tax" + form = FormExamples() - - form.message.data = "Your vehicle tax for ((registration number)) is due on ((date)). Renew online at www.gov.uk/vehicle-tax" # noqa - + form.message.data = sms form.validate() + template = Template({'content': sms}) + return render_template( 'views/styleguide.html', - form=form + form=form, + template=template ) diff --git a/app/templates/views/styleguide.html b/app/templates/views/styleguide.html index a52144df3..a45f1a30e 100644 --- a/app/templates/views/styleguide.html +++ b/app/templates/views/styleguide.html @@ -5,9 +5,11 @@ {% from "components/browse-list.html" import browse_list %} {% from "components/page-footer.html" import page_footer %} {% from "components/sms-message.html" import sms_message %} -{% from "components/table.html" import mapping_table, list_table, row, field, right_aligned_field_heading %} +{% from "components/email-message.html" import email_message %} +{% from "components/table.html" import mapping_table, list_table, row, field, text_field, boolean_field, right_aligned_field_heading %} {% from "components/textbox.html" import textbox %} {% from "components/file-upload.html" import file_upload %} +{% from "components/yes-no.html" import yes_no %} {% from "components/api-key.html" import api_key %} {% block page_title %} @@ -25,21 +27,26 @@

    Banner

    -

    Used to show the status of a thing or action.

    +
    +
    +

    Used to show the status of a thing or action.

    - {{ banner("You sent 1,234 text messages", with_tick=True) }} + {{ banner("You sent 1,234 text messages", with_tick=True) }} - {{ banner('You’re not allowed to do this', 'dangerous')}} + {{ banner('You’re not allowed to do this', 'dangerous')}} - {{ banner('Are you sure you want to delete?', 'dangerous', delete_button="Yes, delete this thing")}} + {{ banner('Are you sure you want to delete?', 'dangerous', delete_button="Yes, delete this thing")}} - {{ banner( - 'Send your first message'|safe, - subhead='Get started', - type='tip' - )}} + {{ banner( + 'Send your first message'|safe, + subhead='Get started', + type='tip' + )}} + + {{ banner('You could go to jail', 'important')}} +
    +
    - {{ banner('You could go to jail', 'important')}}

    Big number

    @@ -118,7 +125,7 @@

    SMS message

    -

    Used to show, preview or choose an SMS message.

    +

    Used to show, preview or choose an SMS template.

    @@ -127,82 +134,105 @@ name='Two week reminder', ) }} {{ sms_message( - 'Your vehicle tax for ((registration number)) is due on ((date)). Renew online at www.gov.uk/vehicle-tax' + template.formatted_as_markup ) }} {{ sms_message( - 'Your vehicle tax for registration number is due on date. Renew online at www.gov.uk/vehicle-tax', + 'Your vehicle tax for LC12 BFL is due on 1 March 2016. Renew online at www.gov.uk/vehicle-tax', '+44 7700 900 306' ) }} {{ sms_message( - 'Your vehicle tax for ((registration number)) is due on ((date)). Renew online at www.gov.uk/vehicle-tax', + template.formatted_as_markup, name='Two week reminder', edit_link='#' ) }}
    +

    Email message

    + +

    Used to show, preview or choose an email template.

    + +
    +
    + {{ email_message( + subject="Vehicle tax reminder", + body="Dear Alice Smith,\n\nYour vehicle tax for LC12 BFL is due on 1 March 2016.\n\nRenew online at www.gov.uk/vehicle-tax", + from_name="Vehicle tax", + from_address="vehicle.tax@notifications.service.gov.uk", + name="Two week reminder", + ) }} +
    +
    +

    Tables

    - {% call mapping_table( - caption='Account settings', - field_headings=['Label', 'Value', 'Action'], - field_headings_visible=False, - caption_visible=True - ) %} - {% call row() %} - {% call field() %} - Username +
    +
    +

    + Used for comparing rows of data. +

    + {% call mapping_table( + caption='Account settings', + field_headings=['Label', 'True', 'False', 'Action'], + field_headings_visible=False, + caption_visible=True + ) %} + {% call row() %} + {{ text_field('Username' )}} + {{ boolean_field(True) }} + {{ boolean_field(False) }} + {% call field(align='right') %} + Change + {% endcall %} + {% endcall %} {% endcall %} - {% call field() %} - admin - {% endcall %} - {% call field(align='right') %} - Change - {% endcall %} - {% endcall %} - {% endcall %} - {% call(item) list_table( - [ - { - 'file': 'dispatch_20151114.csv', 'status': 'Queued' - }, - { - 'file': 'dispatch_20151117.csv', 'status': 'Delivered' - }, - { - 'file': 'remdinder_monday.csv', 'status': 'Failed' - } - ], - caption='Messages', - field_headings=['File', right_aligned_field_heading('Status')], - field_headings_visible=True, - caption_visible=False - ) %} - {% call field() %} - {{ item.file }} - {% endcall %} - {% call field( - align='right', - status='error' if item.status == 'Failed' else 'default' - ) %} - {{ item.status }} - {% endcall %} - {% endcall %} + {% call(item) list_table( + [ + { + 'file': 'dispatch_20151114.csv', 'status': 'Queued' + }, + { + 'file': 'dispatch_20151117.csv', 'status': 'Delivered' + }, + { + 'file': 'remdinder_monday.csv', 'status': 'Failed' + } + ], + caption='Messages', + field_headings=['File', right_aligned_field_heading('Status')], + field_headings_visible=True, + caption_visible=False + ) %} + {% call field() %} + {{ item.file }} + {% endcall %} + {% call field( + align='right', + status='error' if item.status == 'Failed' else 'default' + ) %} + {{ item.status }} + {% endcall %} + {% endcall %} - {% call(item) list_table( - [], - caption='Jobs', - field_headings=['Job', 'Time'], - caption_visible=True, - empty_message='You haven’t scheduled any jobs yet' - ) %} - {% call field() %} - {{ item.job }} - {% endcall %} - {% call field() %} - {{ item.time }} - {% endcall %} - {% endcall %} + {% call(item) list_table( + [], + caption='Jobs', + field_headings=['Job', 'Time'], + caption_visible=True, + empty_message='You haven’t scheduled any jobs yet' + ) %} + {% call field() %} + {{ item.job }} + {% endcall %} + {% call field() %} + {{ item.time }} + {% endcall %} + {% endcall %} + +
    +

    Textbox

    {{ textbox(form.username) }} @@ -213,6 +243,17 @@

    File upload

    {{ file_upload(form.file_upload) }} +

    Yes/no

    +
    +
    +
    + {{ yes_no('manage_service', 'Manage service', True) }} + {{ yes_no('templates', 'Create templates', True) }} +
    +
    +
    + +

    API key

    {{ api_key('d30512af92e1386d63b90e5973b49a10') }} From bf3f6d7d3eb657c04f5bac7e5b829485502bfa91 Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Mon, 22 Feb 2016 12:07:09 +0000 Subject: [PATCH 5/6] Fix users page for research For the lab sessions, users should just see themselves as the only user, not all the fake users (it might confuse them). --- app/assets/images/cross.png | Bin 971 -> 0 bytes .../stylesheets/components/page-footer.scss | 2 +- app/main/views/manage_users.py | 53 ++---------------- app/templates/views/manage-templates.html | 53 ------------------ app/templates/views/manage-users.html | 9 ++- 5 files changed, 9 insertions(+), 108 deletions(-) delete mode 100644 app/assets/images/cross.png delete mode 100644 app/templates/views/manage-templates.html diff --git a/app/assets/images/cross.png b/app/assets/images/cross.png deleted file mode 100644 index fab3b895c6253a371bb79c98e4a0d74de6faee07..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 971 zcmV;+12p`JP)C0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUyCs0gOMfvOOzdA?1IY+l5G4a&a@zd7v z)YrZ@M7}md#!gzrOOUfKSR&0000DbW%=J|NsC0|NsC0|NsC006pj;h5!Hq zAxT6*RCwC#+sksRKn#T8PTP(z@cz$xT&YwF!)VmfAkK!YvPkiNi`Zr)gZV%I-~^n2 z6L11ffd41o&!iI1>mGpb%k-5J;A^^E#{uvDloGuAlOTZ7&j^0{f&fxKF8J;H0%-lH z;IHorAogQ|Mc)(ju6kYeBZ7~Q#^s4EERW17X4aKGxs76NUwY{q1K#t$^@94J!qNaz9WfAhbK8DgmM16V?a_ z{jRt|Kt#?3`wNKJ$!Ko@5j`93DT{id(#7X80kvz-RRW5a7RSGjX#b&JS^Tr; z`D4283J9OS^w~P9`toeOtS<^s*B7Vzg?&kY%D%K;DD4XZ)b@o9M{(a3pt|qwSjzj3 z0QG%m%X2`#E5H%`?w;w8en)^~`khVJL48wzqx$Bq?XbQfz;S(J+jn5UDZr8a=DzXJ zenWu4{^Kzyz*wJ1Zn})6Rc8M)D8OicRIvR0nLPi71y2KTyQKPI!R2QC4}=8E^M6S2 z(O}tuAlw&`+zK9cH_C~Z{7(-0kiwYb$|O=APJb=_HFu&PeVb#?5=IwZ`}{L z0%kW|jehfv$PqBRXKMA^_k>*mvs<2K-?%I82$1SNs@%#Dh}bpB4WIn0d&BN?(|kzXi-a=0l;c>>6PQX6_1^_-|%}Fz(lac@c002ovPDHLkV1ljd@W=oF diff --git a/app/assets/stylesheets/components/page-footer.scss b/app/assets/stylesheets/components/page-footer.scss index 7dfe8ece0..f38a0e6ea 100644 --- a/app/assets/stylesheets/components/page-footer.scss +++ b/app/assets/stylesheets/components/page-footer.scss @@ -12,7 +12,7 @@ &-delete-link { line-height: 40px; - padding: 0 0 0 5px; + padding: 1px 0 0 15px; a { diff --git a/app/main/views/manage_users.py b/app/main/views/manage_users.py index 54be424d2..df609ee8f 100644 --- a/app/main/views/manage_users.py +++ b/app/main/views/manage_users.py @@ -15,55 +15,13 @@ from app.main.forms import InviteUserForm from app.main.dao.services_dao import get_service_by_id_or_404 from app import user_api_client - fake_users = [ { - 'name': 'Henry Hadlow', - 'email_localpart': 'henry.hadlow', - 'permission_send_messages': True, - 'permission_manage_service': False, - 'permission_manage_api_keys': False, - 'active': True - }, - - { - 'name': 'Pete Herlihy', - 'email_localpart': 'pete.herlihy', - 'permission_send_messages': False, - 'permission_manage_service': False, - 'permission_manage_api_keys': False, - 'active': True - }, - { - 'name': 'Chris Hill-Scott', - 'email_localpart': 'chris.hill-scott', + 'name': '', 'permission_send_messages': True, 'permission_manage_service': True, 'permission_manage_api_keys': True, 'active': True - }, - { - 'name': 'Martyn Inglis', - 'email_localpart': 'martyn.inglis', - 'permission_send_messages': True, - 'permission_manage_service': True, - 'permission_manage_api_keys': True, - 'active': True - }, - { - 'email_localpart': 'caley.smolska', - 'permission_send_messages': True, - 'permission_manage_service': False, - 'permission_manage_api_keys': False, - 'active': False - }, - - { - 'email_localpart': 'ash.stephens', - 'permission_send_messages': False, - 'permission_manage_service': False, - 'permission_manage_api_keys': False, - 'active': False } ] @@ -74,12 +32,9 @@ def manage_users(service_id): return render_template( 'views/manage-users.html', service_id=service_id, - users=[ - dict(id=user_id, **user) for (user_id, user) in enumerate(fake_users) if user['active'] - ], - invited_users=[ - dict(id=user_id, **user) for (user_id, user) in enumerate(fake_users) if not user['active'] - ] + users=fake_users, + current_user=current_user, + invited_users=[] ) diff --git a/app/templates/views/manage-templates.html b/app/templates/views/manage-templates.html deleted file mode 100644 index 346c87739..000000000 --- a/app/templates/views/manage-templates.html +++ /dev/null @@ -1,53 +0,0 @@ -{% extends "withnav_template.html" %} -{% from "components/sms-message.html" import sms_message %} -{% from "components/email-message.html" import email_message %} -{% from "components/browse-list.html" import browse_list %} - -{% block page_title %} -Manage templates – GOV.UK Notify -{% endblock %} - -{% block maincolumn_content %} - -

    Manage templates

    - - - {% if not has_jobs %} - {{ banner( - 'Send yourself a text message'.format( - url_for(".choose_sms_template", service_id=service_id) - )|safe, - subhead='Next step', - type="tip" - )}} - {% endif %} - -
    -
    - - {% for template in templates %} - {% if template.template_type == 'email' %} - {{ email_message( - template.get_field('subject'), - template.get_field('content'), - name=template.get_field('name'), - edit_link=url_for('.edit_service_template', service_id=service_id, template_id=template.id) - ) }} - {% else %} - {{ sms_message( - template.formatted_as_markup, - name=template.name, - id=template.id, - edit_link=url_for('.edit_service_template', service_id=service_id, template_id=template.id) - ) }} - {% endif %} - {% endfor %} - -

    - Add new template -

    - -
    -
    - -{% endblock %} diff --git a/app/templates/views/manage-users.html b/app/templates/views/manage-users.html index 4af05c7aa..c12fb8ddc 100644 --- a/app/templates/views/manage-users.html +++ b/app/templates/views/manage-users.html @@ -20,22 +20,21 @@ Manage users – GOV.UK Notify Manage team + Invite a team member + {% call(item) list_table( users, caption='Active', **table_options ) %} {% call field() %} - {{ item.name }} + {{ current_user.name }} {% endcall %} {{ boolean_field(item.permission_send_messages) }} {{ boolean_field(item.permission_manage_service) }} {{ boolean_field(item.permission_manage_api_keys) }} {% call field(align='right') %} - Change + Change {% endcall %} {% endcall %} - {% if invited_users %} {% call(item) list_table( From 97a3bf922580f61c81f386f9665a3f05724f4ec4 Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Mon, 22 Feb 2016 12:16:19 +0000 Subject: [PATCH 6/6] =?UTF-8?q?Remove=20the=20=E2=80=98manage=20templates?= =?UTF-8?q?=E2=80=99=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ‘manage templates’ page was almost identical to the ‘send text messages’ page. This commit consolidates them into one and makes them all hang together. Part of this means tweaks to the javascript so that files upload as soon as you’ve chosen them. --- app/assets/javascripts/fileUpload.js | 10 +++---- .../stylesheets/components/file-upload.scss | 2 +- app/assets/stylesheets/components/table.scss | 4 --- app/main/views/templates.py | 26 +++++-------------- app/templates/components/file-upload.html | 2 +- app/templates/components/page-footer.html | 10 +++---- app/templates/main_nav.html | 5 ++-- app/templates/views/choose-sms-template.html | 12 ++++----- app/templates/views/edit-template.html | 6 ++--- app/templates/views/invite-user.html | 6 +++-- app/templates/views/send-sms.html | 7 +---- app/templates/views/service_dashboard.html | 16 ++---------- tests/app/main/views/test_manage_users.py | 3 --- tests/app/main/views/test_templates.py | 4 +-- 14 files changed, 37 insertions(+), 76 deletions(-) diff --git a/app/assets/javascripts/fileUpload.js b/app/assets/javascripts/fileUpload.js index 5380ac87f..4df2a1702 100644 --- a/app/assets/javascripts/fileUpload.js +++ b/app/assets/javascripts/fileUpload.js @@ -3,22 +3,20 @@ Modules.FileUpload = function() { - let $field, $button, $filename; + let $field; - this.update = function() { + this.submit = function() { - $filename.text($field.val().split('\\').pop()); + $field.parents('form').trigger('submit'); }; this.start = function(component) { $field = $('.file-upload-field', component); - $button = $('.file-upload-button', component); - $filename = $('.file-upload-filename', component); // Need to put the event on the container, not the input for it to work properly - $(component).on('change', '.file-upload-field', this.update); + $(component).on('change', '.file-upload-field', this.submit); }; diff --git a/app/assets/stylesheets/components/file-upload.scss b/app/assets/stylesheets/components/file-upload.scss index f772d4230..d674944a5 100644 --- a/app/assets/stylesheets/components/file-upload.scss +++ b/app/assets/stylesheets/components/file-upload.scss @@ -23,7 +23,7 @@ } &-button { - @include button($panel-colour); + @include button($button-colour); display: inline-block; } diff --git a/app/assets/stylesheets/components/table.scss b/app/assets/stylesheets/components/table.scss index 477ddb208..36c137bcc 100644 --- a/app/assets/stylesheets/components/table.scss +++ b/app/assets/stylesheets/components/table.scss @@ -55,10 +55,6 @@ background-image: file-url('tick.png'); } - &-no { - background-image: file-url('cross.png'); - } - &-missing { color: $error-colour; font-weight: bold; diff --git a/app/main/views/templates.py b/app/main/views/templates.py index a8453f9e5..be1001113 100644 --- a/app/main/views/templates.py +++ b/app/main/views/templates.py @@ -15,22 +15,10 @@ from app.main.dao import services_dao as sdao @main.route("/services//templates") @login_required def manage_service_templates(service_id): - try: - jobs = job_api_client.get_job(service_id)['data'] - except HTTPError as e: - if e.status_code == 404: - abort(404) - else: - raise e - return render_template( - 'views/manage-templates.html', - service_id=service_id, - has_jobs=bool(jobs), - templates=[ - Template(template) - for template in tdao.get_service_templates(service_id)['data'] - ] - ) + return redirect(url_for( + '.choose_sms_template', + service_id=service_id + )) @main.route("/services//templates/add", methods=['GET', 'POST']) @@ -50,10 +38,10 @@ def add_service_template(service_id): tdao.insert_service_template( form.name.data, form.template_content.data, service_id) return redirect(url_for( - '.manage_service_templates', service_id=service_id)) + '.choose_sms_template', service_id=service_id)) return render_template( 'views/edit-template.html', - h1='Add template', + h1='Add a text message template', form=form, service_id=service_id) @@ -69,7 +57,7 @@ def edit_service_template(service_id, template_id): tdao.update_service_template( template_id, form.name.data, form.template_content.data, service_id) - return redirect(url_for('.manage_service_templates', service_id=service_id)) + return redirect(url_for('.choose_sms_template', service_id=service_id)) return render_template( 'views/edit-template.html', diff --git a/app/templates/components/file-upload.html b/app/templates/components/file-upload.html index 1451b0ee3..437ce699f 100644 --- a/app/templates/components/file-upload.html +++ b/app/templates/components/file-upload.html @@ -1,7 +1,7 @@ {% macro file_upload(field, button_text="Choose file") %}
diff --git a/app/templates/views/choose-sms-template.html b/app/templates/views/choose-sms-template.html index d069c8b13..4fb5595e3 100644 --- a/app/templates/views/choose-sms-template.html +++ b/app/templates/views/choose-sms-template.html @@ -23,18 +23,16 @@ {% endfor %} - {% else %} - {{ banner( - 'Add a text message template to start sending messages'.format( - url_for(".add_service_template", service_id=service_id) - )|safe, - type="tip" - )}} {% endif %} +

+ Add a new template +

+ {% endblock %} diff --git a/app/templates/views/edit-template.html b/app/templates/views/edit-template.html index 2563dd81e..4720a2a61 100644 --- a/app/templates/views/edit-template.html +++ b/app/templates/views/edit-template.html @@ -30,9 +30,9 @@ {{ page_footer( 'Save', delete_link=url_for('.delete_service_template', service_id=service_id, template_id=template_id) if template_id or None, - delete_link_text='delete this template', - secondary_link=url_for('.manage_service_templates', service_id=service_id), - secondary_link_text='Back to templates' + delete_link_text='Delete this template', + back_link=url_for('.choose_sms_template', service_id=service_id), + back_link_text='Cancel' ) }} diff --git a/app/templates/views/invite-user.html b/app/templates/views/invite-user.html index 2ca19bc2f..d1e6e5c7b 100644 --- a/app/templates/views/invite-user.html +++ b/app/templates/views/invite-user.html @@ -18,7 +18,7 @@ Manage users – GOV.UK Notify {% if user %}

- {{ user.email_localpart }}@digital.cabinet-office.gov.uk + {{ current_user.email_address }}

{% else %} {{ textbox(form.email_address, hint='Email address must end in .gov.uk', width='1-1') }} @@ -37,7 +37,9 @@ Manage users – GOV.UK Notify {{ page_footer( 'Save', delete_link=url_for('.delete_user', service_id=service_id, user_id=user_id), - delete_link_text='delete this account' + delete_link_text='Delete this account', + back_link=url_for('.manage_users', service_id=service_id), + back_link_text='Cancel' ) }} {% else %} {{ page_footer('Send invitation email') }} diff --git a/app/templates/views/send-sms.html b/app/templates/views/send-sms.html index 351115c7f..32be81c72 100644 --- a/app/templates/views/send-sms.html +++ b/app/templates/views/send-sms.html @@ -20,16 +20,11 @@ - {{file_upload(form.file, button_text='Choose a CSV file')}} -

Download an example CSV file

- {{ page_footer( - "Continue to preview" - ) }} - + {{file_upload(form.file, button_text='Upload a CSV file')}} {% endblock %} diff --git a/app/templates/views/service_dashboard.html b/app/templates/views/service_dashboard.html index 09443fc40..e0b5527a5 100644 --- a/app/templates/views/service_dashboard.html +++ b/app/templates/views/service_dashboard.html @@ -25,21 +25,9 @@ {% if not jobs %} {{ banner( - """ -
    -
  1. - Add a template -
  2. -
  3. - Send yourself a text message -
  4. -
- """.format( - url_for(".add_service_template", service_id=service_id), - url_for(".choose_sms_template", service_id=service_id) - )|safe, + 'Send yourself a text message', subhead='Get started', - type="tip" + type='tip' )}} {% else %} {% call(item) list_table( diff --git a/tests/app/main/views/test_manage_users.py b/tests/app/main/views/test_manage_users.py index 000464ecb..62ee8d642 100644 --- a/tests/app/main/views/test_manage_users.py +++ b/tests/app/main/views/test_manage_users.py @@ -14,8 +14,6 @@ def test_should_show_overview_page( response = client.get(url_for('main.manage_users', service_id=55555)) assert 'Manage team' in response.get_data(as_text=True) - assert 'Henry Hadlow' in response.get_data(as_text=True) - assert 'caley.smolska' in response.get_data(as_text=True) assert response.status_code == 200 @@ -30,7 +28,6 @@ def test_should_show_page_for_one_user( client.login(api_user_active) response = client.get(url_for('main.edit_user', service_id=55555, user_id=0)) - assert 'Henry Hadlow' in response.get_data(as_text=True) assert response.status_code == 200 diff --git a/tests/app/main/views/test_templates.py b/tests/app/main/views/test_templates.py index 3319f1468..cecc59353 100644 --- a/tests/app/main/views/test_templates.py +++ b/tests/app/main/views/test_templates.py @@ -16,7 +16,7 @@ def test_should_return_list_of_all_templates(app_, client.login(api_user_active) service_id = str(uuid.uuid4()) response = client.get(url_for( - '.manage_service_templates', service_id=service_id)) + '.manage_service_templates', service_id=service_id), follow_redirects=True) assert response.status_code == 200 mock_get_service_templates.assert_called_with(service_id) @@ -72,7 +72,7 @@ def test_should_redirect_when_saving_a_template(app_, assert response.status_code == 302 assert response.location == url_for( - '.manage_service_templates', service_id=service_id, _external=True) + '.choose_sms_template', service_id=service_id, _external=True) mock_update_service_template.assert_called_with( template_id, name, 'sms', content, service_id)