From 72aac68a37a3c2e337256f0580065c29f2f7074b Mon Sep 17 00:00:00 2001 From: Montana Date: Wed, 20 Mar 2019 09:54:08 -0400 Subject: [PATCH 1/8] Add Archive User button --- styles/components/_portfolio_layout.scss | 29 +++++++++++++++++++ .../fragments/admin/portfolio_members.html | 6 ++++ translations.yaml | 1 + 3 files changed, 36 insertions(+) diff --git a/styles/components/_portfolio_layout.scss b/styles/components/_portfolio_layout.scss index 87c080c2..14a39615 100644 --- a/styles/components/_portfolio_layout.scss +++ b/styles/components/_portfolio_layout.scss @@ -233,6 +233,26 @@ font-size: 1.6rem; border-top: 0; padding: 3 * $gap 2 * $gap; + + .usa-button-secondary { + color: $color-red; + background-color: $color-red-lightest; + box-shadow: inset 0 0 0 1px $color-red; + } + + .usa-button-disabled { + color: $color-gray-medium; + background-color: $color-gray-lightest; + box-shadow: inset 0 0 0 1px $color-gray-medium; + } + + button { + padding: 0; + margin: 0; + font-size: 1.5rem; + width: 11rem; + height: 3rem; + } } .green { @@ -248,6 +268,15 @@ } } + .usa-button-disabled { + color: $color-gray; + } + + .usa-button-secondary { + color: $color-red; + background-color: $color-red-light; + } + .add-member-link { text-align: right; } diff --git a/templates/fragments/admin/portfolio_members.html b/templates/fragments/admin/portfolio_members.html index b9301368..f08f2ae6 100644 --- a/templates/fragments/admin/portfolio_members.html +++ b/templates/fragments/admin/portfolio_members.html @@ -40,6 +40,9 @@ {{ member_data.member.user_name }} {% if member_data.member.user == user %} (you) + {% set archive_button_class = 'usa-button-disabled' %} + {% else %} + {% set archive_button_class = 'usa-button-secondary' %} {% endif %} {% set heading_perms = [member_data.app_mgmt, member_data.funding, member_data.reporting, member_data.portfolio_mgmt] %} @@ -52,6 +55,9 @@ {% endif %} {% endfor %} + + + {% endfor %} diff --git a/translations.yaml b/translations.yaml index 37b28d51..cb3e9102 100644 --- a/translations.yaml +++ b/translations.yaml @@ -568,6 +568,7 @@ portfolios: settings_info: Learn more about these settings activity_log_title: Activity Log members: + archive_button: Archive User permissions: name: Name app_mgmt: App Mgmt From a6c60fc5886c0257060985d62ad9961222997c56 Mon Sep 17 00:00:00 2001 From: Montana Date: Wed, 20 Mar 2019 11:38:56 -0400 Subject: [PATCH 2/8] Unstyled Save Button --- styles/components/_portfolio_layout.scss | 14 +++++--------- templates/fragments/admin/portfolio_members.html | 6 +++++- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/styles/components/_portfolio_layout.scss b/styles/components/_portfolio_layout.scss index 14a39615..6d09bfa2 100644 --- a/styles/components/_portfolio_layout.scss +++ b/styles/components/_portfolio_layout.scss @@ -268,18 +268,14 @@ } } - .usa-button-disabled { - color: $color-gray; - } - - .usa-button-secondary { - color: $color-red; - background-color: $color-red-light; - } - .add-member-link { text-align: right; } + + .usa-button-primary .usa-button { + padding: 2 * $gap; + float: right; + } } } diff --git a/templates/fragments/admin/portfolio_members.html b/templates/fragments/admin/portfolio_members.html index f08f2ae6..29e87503 100644 --- a/templates/fragments/admin/portfolio_members.html +++ b/templates/fragments/admin/portfolio_members.html @@ -2,6 +2,8 @@
+
+
{{ "portfolios.admin.portfolio_members_title" | translate }}
@@ -61,8 +63,10 @@ {% endfor %} - + + +
{% endif %} From dd4dc9de865dc0584613c7dda8ba157801b01856 Mon Sep 17 00:00:00 2001 From: Montana Date: Thu, 21 Mar 2019 11:35:13 -0400 Subject: [PATCH 3/8] Render dropdowns for permissions --- atst/forms/portfolio_member.py | 7 ++- atst/routes/portfolios/index.py | 52 ++++++++++++++----- styles/components/_portfolio_layout.scss | 10 +++- templates/components/options_input.html | 26 +++++----- .../fragments/admin/portfolio_members.html | 29 +++++------ templates/portfolios/admin.html | 13 ++--- translations.yaml | 3 ++ 7 files changed, 90 insertions(+), 50 deletions(-) diff --git a/atst/forms/portfolio_member.py b/atst/forms/portfolio_member.py index 36e0d3cd..492777a7 100644 --- a/atst/forms/portfolio_member.py +++ b/atst/forms/portfolio_member.py @@ -1,4 +1,4 @@ -from wtforms.fields import StringField +from wtforms.fields import StringField, FormField, FieldList from wtforms.fields.html5 import EmailField from wtforms.validators import Required, Email, Length @@ -10,6 +10,7 @@ from atst.utils.localization import translate class PermissionsForm(BaseForm): + member = StringField() perms_app_mgmt = SelectField( None, choices=[ @@ -50,6 +51,10 @@ class PermissionsForm(BaseForm): return _data +class MembersPermissionsForm(BaseForm): + members_permissions = FieldList(FormField(PermissionsForm)) + + class EditForm(PermissionsForm): # This form also accepts a field for each environment in each application # that the user is a member of diff --git a/atst/routes/portfolios/index.py b/atst/routes/portfolios/index.py index 6aadb012..592792b0 100644 --- a/atst/routes/portfolios/index.py +++ b/atst/routes/portfolios/index.py @@ -8,9 +8,10 @@ from atst.domain.portfolios import Portfolios from atst.domain.audit_log import AuditLog from atst.domain.common import Paginator from atst.forms.portfolio import PortfolioForm +from atst.forms.portfolio_member import MembersPermissionsForm +from atst.models.permissions import Permissions from atst.domain.permission_sets import PermissionSets from atst.domain.authz.decorator import user_can_access_decorator as user_can -from atst.models.permissions import Permissions @portfolios_bp.route("/portfolios") @@ -23,27 +24,53 @@ def portfolios(): return render_template("portfolios/blank_slate.html") -def serialize_member(member): +def permission_str(member, edit_perm_set, view_perm_set): + if member.has_permission_set(edit_perm_set): + return edit_perm_set + else: + return view_perm_set + + +def serialize_member_form_data(member): return { - "member": member, - "app_mgmt": member.has_permission_set( - PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT + "member": member.user.full_name, + "perms_app_mgmt": permission_str( + member, + PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT, + PermissionSets.VIEW_PORTFOLIO_APPLICATION_MANAGEMENT, ), - "funding": member.has_permission_set(PermissionSets.EDIT_PORTFOLIO_FUNDING), - "reporting": member.has_permission_set(PermissionSets.EDIT_PORTFOLIO_REPORTS), - "portfolio_mgmt": member.has_permission_set( - PermissionSets.EDIT_PORTFOLIO_ADMIN + "perms_funding": permission_str( + member, + PermissionSets.EDIT_PORTFOLIO_FUNDING, + PermissionSets.VIEW_PORTFOLIO_FUNDING, + ), + "perms_reporting": permission_str( + member, + PermissionSets.EDIT_PORTFOLIO_REPORTS, + PermissionSets.VIEW_PORTFOLIO_REPORTS, + ), + "perms_portfolio_mgmt": permission_str( + member, + PermissionSets.EDIT_PORTFOLIO_ADMIN, + PermissionSets.VIEW_PORTFOLIO_ADMIN, ), } -def render_admin_page(portfolio, form): +def render_admin_page(portfolio, form=None): pagination_opts = Paginator.get_pagination_opts(http_request) audit_events = AuditLog.get_portfolio_events(portfolio, pagination_opts) - members_data = [serialize_member(member) for member in portfolio.members] + members_data = [serialize_member_form_data(member) for member in portfolio.members] + + portfolio_form = PortfolioForm(data={"name": portfolio.name}) + permissions_form = MembersPermissionsForm( + data={"members_permissions": members_data} + ) return render_template( "portfolios/admin.html", form=form, + portfolio_form=portfolio_form, + permissions_form=permissions_form, portfolio=portfolio, audit_events=audit_events, user=g.current_user, @@ -55,8 +82,7 @@ def render_admin_page(portfolio, form): @user_can(Permissions.VIEW_PORTFOLIO_ADMIN, message="view portfolio admin page") def portfolio_admin(portfolio_id): portfolio = Portfolios.get_for_update(portfolio_id) - form = PortfolioForm(data={"name": portfolio.name}) - return render_admin_page(portfolio, form) + return render_admin_page(portfolio) @portfolios_bp.route("/portfolios//edit", methods=["POST"]) diff --git a/styles/components/_portfolio_layout.scss b/styles/components/_portfolio_layout.scss index 6d09bfa2..bd6e6429 100644 --- a/styles/components/_portfolio_layout.scss +++ b/styles/components/_portfolio_layout.scss @@ -197,8 +197,6 @@ } table { - box-shadow: 0 6px 18px 0 rgba(144,164,183,0.3); - thead { th:first-child { padding-left: 3 * $gap; @@ -266,6 +264,14 @@ font-size: 1.2rem; } } + + .usa-input.usa-input--success { + margin: 0; + } + + select { + border: none; + } } .add-member-link { diff --git a/templates/components/options_input.html b/templates/components/options_input.html index d2b52e07..bda1805b 100644 --- a/templates/components/options_input.html +++ b/templates/components/options_input.html @@ -1,7 +1,7 @@ {% from "components/icon.html" import Icon %} {% from "components/tooltip.html" import Tooltip %} -{% macro OptionsInput(field, tooltip, inline=False) -%} +{% macro OptionsInput(field, tooltip, inline=False, label=True) -%}
- -
- {{ field.label | striptags}} - {% if tooltip %}{{ Tooltip(tooltip) }}{% endif %} -
+ {% if label %} + +
+ {{ field.label | striptags}} + {% if tooltip %}{{ Tooltip(tooltip) }}{% endif %} +
- {% if field.description %} - {{ field.description | safe }} - {% endif %} + {% if field.description %} + {{ field.description | safe }} + {% endif %} - {{ Icon('alert',classes="icon-validation") }} - {{ Icon('ok',classes="icon-validation") }} -
+ {{ Icon('alert',classes="icon-validation") }} + {{ Icon('ok',classes="icon-validation") }} +
+ {% endif %} {{ field() }} diff --git a/templates/fragments/admin/portfolio_members.html b/templates/fragments/admin/portfolio_members.html index 29e87503..e7babb0f 100644 --- a/templates/fragments/admin/portfolio_members.html +++ b/templates/fragments/admin/portfolio_members.html @@ -1,4 +1,5 @@ {% from "components/icon.html" import Icon %} +{% from "components/options_input.html" import OptionsInput %}
@@ -37,31 +38,27 @@ - {% for member_data in members_data %} - - {{ member_data.member.user_name }} - {% if member_data.member.user == user %} + {% for subform in permissions_form.members_permissions %} + + {{ subform.member.data }} + {% if subform.member.data == user.full_name %} (you) {% set archive_button_class = 'usa-button-disabled' %} {% else %} {% set archive_button_class = 'usa-button-secondary' %} {% endif %} - {% set heading_perms = [member_data.app_mgmt, member_data.funding, member_data.reporting, member_data.portfolio_mgmt] %} - {% for has_perm in heading_perms %} - {% if has_perm %} - Edit Access - {% else %} - View Only - {% endif %} - {% endfor %} - - - + {{ OptionsInput(subform.perms_app_mgmt, label=False) }} + {{ OptionsInput(subform.perms_funding, label=False) }} + {{ OptionsInput(subform.perms_reporting, label=False) }} + {{ OptionsInput(subform.perms_portfolio_mgmt, label=False) }} + + {% endfor %} + @@ -69,6 +66,6 @@
-{% endif %} + {% endif %}
diff --git a/templates/portfolios/admin.html b/templates/portfolios/admin.html index 25c3f445..2f0614a8 100644 --- a/templates/portfolios/admin.html +++ b/templates/portfolios/admin.html @@ -14,13 +14,14 @@
+ {% if user_can(permissions.VIEW_PORTFOLIO_NAME) %} -
- {{ form.csrf_token }} -
-
- {{ TextInput(form.name, validation="portfolioName") }} -
+ + {{ portfolio_form.csrf_token }} +
+
+ {{ TextInput(portfolio_form.name, validation="portfolioName") }} +
diff --git a/translations.yaml b/translations.yaml index cb3e9102..67f1335d 100644 --- a/translations.yaml +++ b/translations.yaml @@ -138,6 +138,7 @@ forms: last_name_label: Last Name portfolio_role_description: 'The portfolio role controls whether a member is permitted to organize a portfolio into applications and environments, add members to this portfolio, and view billing information.' portfolio_role_label: Portfolio Role + access: Access Level new_request: am_poc_label: I am the Portfolio Owner average_daily_traffic_description: What is the average daily traffic you expect the systems under this cloud contract to use? @@ -575,6 +576,8 @@ portfolios: funding: Funding reporting: Reporting portfolio_mgmt: Portfolio Mgmt + view_only: View Only + edit_access: Edit Access testing: example_string: Hello World example_with_variables: 'Hello, {name}!' From 5312a11e1646e6ef9b45bca01f76a1a003b3edb2 Mon Sep 17 00:00:00 2001 From: Montana Date: Mon, 25 Mar 2019 15:50:27 -0400 Subject: [PATCH 4/8] Add new member link --- templates/fragments/admin/portfolio_members.html | 4 ++++ translations.yaml | 1 + 2 files changed, 5 insertions(+) diff --git a/templates/fragments/admin/portfolio_members.html b/templates/fragments/admin/portfolio_members.html index e7babb0f..b05b6b8f 100644 --- a/templates/fragments/admin/portfolio_members.html +++ b/templates/fragments/admin/portfolio_members.html @@ -61,6 +61,10 @@ + + {{ "portfolios.admin.add_member" | translate }} + {{ Icon('plus') }} + diff --git a/translations.yaml b/translations.yaml index 67f1335d..1577f78c 100644 --- a/translations.yaml +++ b/translations.yaml @@ -567,6 +567,7 @@ portfolios: portfolio_members_title: Portfolio Members portfolio_members_subheading: These members have different levels of access to the portfolio. settings_info: Learn more about these settings + add_member: Add a New Member activity_log_title: Activity Log members: archive_button: Archive User From 6f25d27fb4cb5d7304b19616d7cec750916ff0cb Mon Sep 17 00:00:00 2001 From: Montana Date: Tue, 26 Mar 2019 10:18:29 -0400 Subject: [PATCH 5/8] Style save button and add new member link --- static/icons/plus-circle-solid.svg | 1 + styles/components/_portfolio_layout.scss | 11 +++++++++++ templates/fragments/admin/portfolio_members.html | 12 +++++++----- 3 files changed, 19 insertions(+), 5 deletions(-) create mode 100644 static/icons/plus-circle-solid.svg diff --git a/static/icons/plus-circle-solid.svg b/static/icons/plus-circle-solid.svg new file mode 100644 index 00000000..67441e7f --- /dev/null +++ b/static/icons/plus-circle-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/styles/components/_portfolio_layout.scss b/styles/components/_portfolio_layout.scss index bd6e6429..2ae5f61e 100644 --- a/styles/components/_portfolio_layout.scss +++ b/styles/components/_portfolio_layout.scss @@ -283,6 +283,17 @@ float: right; } } + + input.usa-button.usa-button-primary { + margin: 0; + width: 9rem; + height: 4rem; + } + + .members-table-footer { + float: right; + padding: 3 * $gap; + } } .application-content { diff --git a/templates/fragments/admin/portfolio_members.html b/templates/fragments/admin/portfolio_members.html index b05b6b8f..2adda63a 100644 --- a/templates/fragments/admin/portfolio_members.html +++ b/templates/fragments/admin/portfolio_members.html @@ -61,11 +61,13 @@ - - {{ "portfolios.admin.add_member" | translate }} - {{ Icon('plus') }} - - +
From 957630750a99cadabd632f81960af7e8cdc8af6a Mon Sep 17 00:00:00 2001 From: Montana Date: Tue, 26 Mar 2019 14:41:15 -0400 Subject: [PATCH 6/8] Handle View Only case --- templates/fragments/admin/members_edit.html | 20 ++++++++ templates/fragments/admin/members_view.html | 20 ++++++++ .../fragments/admin/portfolio_members.html | 48 +++++++------------ 3 files changed, 58 insertions(+), 30 deletions(-) create mode 100644 templates/fragments/admin/members_edit.html create mode 100644 templates/fragments/admin/members_view.html diff --git a/templates/fragments/admin/members_edit.html b/templates/fragments/admin/members_edit.html new file mode 100644 index 00000000..97799b41 --- /dev/null +++ b/templates/fragments/admin/members_edit.html @@ -0,0 +1,20 @@ +{% for subform in permissions_form.members_permissions %} + + {{ subform.member.data }} + {% if subform.member.data == user.full_name %} + (you) + {% set archive_button_class = 'usa-button-disabled' %} + {% else %} + {% set archive_button_class = 'usa-button-secondary' %} + {% endif %} + + + {{ OptionsInput(subform.perms_app_mgmt, label=False) }} + {{ OptionsInput(subform.perms_funding, label=False) }} + {{ OptionsInput(subform.perms_reporting, label=False) }} + {{ OptionsInput(subform.perms_portfolio_mgmt, label=False) }} + + + + +{% endfor %} diff --git a/templates/fragments/admin/members_view.html b/templates/fragments/admin/members_view.html new file mode 100644 index 00000000..639d3fd4 --- /dev/null +++ b/templates/fragments/admin/members_view.html @@ -0,0 +1,20 @@ +{% for subform in permissions_form.members_permissions %} + + + {{ subform.member.data }} + {% if subform.member.data == user.full_name %} + (you) + {% endif %} + + {% set heading_perms = [subform.perms_app_mgmt, subform.perms_funding, subform.perms_reporting, subform.perms_portfolio_mgmt] %} + + {% for access in heading_perms %} + {% if dict(access.choices).get(access.data) == 'Edit Access' %} + Edit Access + {% else %} + View Only + {% endif %} + {% endfor %} + + +{% endfor %} diff --git a/templates/fragments/admin/portfolio_members.html b/templates/fragments/admin/portfolio_members.html index 2adda63a..9b50df74 100644 --- a/templates/fragments/admin/portfolio_members.html +++ b/templates/fragments/admin/portfolio_members.html @@ -38,38 +38,26 @@ - {% for subform in permissions_form.members_permissions %} - - {{ subform.member.data }} - {% if subform.member.data == user.full_name %} - (you) - {% set archive_button_class = 'usa-button-disabled' %} - {% else %} - {% set archive_button_class = 'usa-button-secondary' %} - {% endif %} - - - {{ OptionsInput(subform.perms_app_mgmt, label=False) }} - {{ OptionsInput(subform.perms_funding, label=False) }} - {{ OptionsInput(subform.perms_reporting, label=False) }} - {{ OptionsInput(subform.perms_portfolio_mgmt, label=False) }} - - - - - {% endfor %} - + {% if user_can(permissions.EDIT_PORTFOLIO_USERS) %} + {% include "fragments/admin/members_edit.html" %} + {% else %} + {% include "fragments/admin/members_view.html" %} + {% endif %} - - - + + + {% if user_can(permissions.EDIT_PORTFOLIO_USERS) %} + + {% endif %} + +
{% endif %} From 34d4463665ae291d2253ab221c6e4cb037349116 Mon Sep 17 00:00:00 2001 From: Montana Date: Tue, 26 Mar 2019 14:49:54 -0400 Subject: [PATCH 7/8] Change form name in route and template --- atst/routes/portfolios/index.py | 4 ++-- templates/fragments/admin/members_edit.html | 2 +- templates/fragments/admin/members_view.html | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/atst/routes/portfolios/index.py b/atst/routes/portfolios/index.py index 592792b0..b87763d4 100644 --- a/atst/routes/portfolios/index.py +++ b/atst/routes/portfolios/index.py @@ -63,14 +63,14 @@ def render_admin_page(portfolio, form=None): members_data = [serialize_member_form_data(member) for member in portfolio.members] portfolio_form = PortfolioForm(data={"name": portfolio.name}) - permissions_form = MembersPermissionsForm( + member_perms_form = MembersPermissionsForm( data={"members_permissions": members_data} ) return render_template( "portfolios/admin.html", form=form, portfolio_form=portfolio_form, - permissions_form=permissions_form, + member_perms_form=member_perms_form, portfolio=portfolio, audit_events=audit_events, user=g.current_user, diff --git a/templates/fragments/admin/members_edit.html b/templates/fragments/admin/members_edit.html index 97799b41..c64dafcb 100644 --- a/templates/fragments/admin/members_edit.html +++ b/templates/fragments/admin/members_edit.html @@ -1,4 +1,4 @@ -{% for subform in permissions_form.members_permissions %} +{% for subform in member_perms_form.members_permissions %} {{ subform.member.data }} {% if subform.member.data == user.full_name %} diff --git a/templates/fragments/admin/members_view.html b/templates/fragments/admin/members_view.html index 639d3fd4..33e66478 100644 --- a/templates/fragments/admin/members_view.html +++ b/templates/fragments/admin/members_view.html @@ -1,4 +1,4 @@ -{% for subform in permissions_form.members_permissions %} +{% for subform in member_perms_form.members_permissions %} {{ subform.member.data }} From fd64db8688d1dec47ce70830f862d0ddf97b2c63 Mon Sep 17 00:00:00 2001 From: Montana Date: Tue, 26 Mar 2019 15:13:38 -0400 Subject: [PATCH 8/8] add a test --- .../fragments/admin/portfolio_members.html | 2 +- tests/routes/portfolios/test_admin.py | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 tests/routes/portfolios/test_admin.py diff --git a/templates/fragments/admin/portfolio_members.html b/templates/fragments/admin/portfolio_members.html index 9b50df74..5c7032a7 100644 --- a/templates/fragments/admin/portfolio_members.html +++ b/templates/fragments/admin/portfolio_members.html @@ -40,7 +40,7 @@ {% if user_can(permissions.EDIT_PORTFOLIO_USERS) %} {% include "fragments/admin/members_edit.html" %} - {% else %} + {% elif user_can(permissions.VIEW_PORTFOLIO_USERS) %} {% include "fragments/admin/members_view.html" %} {% endif %} diff --git a/tests/routes/portfolios/test_admin.py b/tests/routes/portfolios/test_admin.py new file mode 100644 index 00000000..7f141fe8 --- /dev/null +++ b/tests/routes/portfolios/test_admin.py @@ -0,0 +1,28 @@ +from flask import url_for + +from atst.domain.permission_sets import PermissionSets + +from tests.factories import PortfolioFactory, PortfolioRoleFactory, UserFactory + + +def test_member_table_access(client, user_session): + admin = UserFactory.create() + portfolio = PortfolioFactory.create(owner=admin) + rando = UserFactory.create() + PortfolioRoleFactory.create( + user=rando, + portfolio=portfolio, + permission_sets=[PermissionSets.get(PermissionSets.VIEW_PORTFOLIO_ADMIN)], + ) + + url = url_for("portfolios.portfolio_admin", portfolio_id=portfolio.id) + + # editable + user_session(admin) + edit_resp = client.get(url) + assert "