From ff41a20ad80c5cfd1e41f5bd3d847127c1dbdf1e Mon Sep 17 00:00:00 2001 From: dandds Date: Thu, 9 May 2019 15:38:44 -0400 Subject: [PATCH 1/6] Adds additional buttons and functionality to app team settings. "Add to existing environment" table, "delete member", and environment roles and env role edit buttons added for every user in the team settings list. Accompanying CSS changes. --- styles/components/_accordion_table.scss | 39 ++++++++ styles/components/_portfolio_layout.scss | 38 ++++---- .../fragments/applications/edit_team.html | 57 +++++++---- templates/portfolios/applications/team.html | 94 +++++++++---------- translations.yaml | 1 + 5 files changed, 141 insertions(+), 88 deletions(-) diff --git a/styles/components/_accordion_table.scss b/styles/components/_accordion_table.scss index 1bcf3331..c5d93b09 100644 --- a/styles/components/_accordion_table.scss +++ b/styles/components/_accordion_table.scss @@ -91,6 +91,30 @@ border-bottom: 1px solid $color-gray-lighter; } } + + .accordion-table__item__action-group { + padding: 1rem 3.2rem; + background-color: $color-gray-lightest; + + button, + a { + margin: 0; + font-size: $small-font-size; + } + + .icon-link { + padding-top: 0.5rem; + float: none; + } + + > *:first-child { + padding-left: 0; + } + + > *:last-child { + float: right; + } + } } .accordion-table__item__toggler { @@ -100,6 +124,10 @@ color: $color-blue; padding: $gap; + > span { + margin-left: auto; + } + .icon { @include icon-size(12); @@ -147,6 +175,17 @@ .accordion-table__item__expanded_first { float: left; } + + .accordion-table__item__expanded-role { + .icon-link { + padding: 0; + vertical-align: text-top; + + .icon { + margin: 0 0 0 0.25rem; + } + } + } } } } diff --git a/styles/components/_portfolio_layout.scss b/styles/components/_portfolio_layout.scss index 60fd04c6..7303c016 100644 --- a/styles/components/_portfolio_layout.scss +++ b/styles/components/_portfolio_layout.scss @@ -1,7 +1,6 @@ .portfolio-panel-container { @include media($large-screen) { @include grid-row; - min-height: 500px; } @@ -38,7 +37,6 @@ &.icon-link--disabled { color: $color-gray-dark; opacity: 1; - .icon { @include icon-color($color-gray-dark); } @@ -53,7 +51,6 @@ .icon-link { color: $color-gray-medium; pointer-events: none; - &.icon-link--disabled { opacity: 1; } @@ -63,7 +60,6 @@ .portfolio-header { flex-direction: column; - @include media($small-screen) { flex-direction: row; } @@ -103,10 +99,8 @@ .icon-link { padding: 0.8rem 1.2rem; - &.active { color: $color-gray; - .icon { @include icon-color($color-gray); } @@ -150,7 +144,6 @@ .unfunded { color: $color-red; - .icon { @include icon-color($color-red); } @@ -166,7 +159,7 @@ } .portfolio-content { - margin: (6 * $gap) $gap 0 $gap; + margin: 6 * $gap $gap 0 $gap; .panel { @include shadow-panel; @@ -175,7 +168,6 @@ .member-list { .panel { @include shadow-panel; - padding-bottom: 0; } @@ -191,7 +183,7 @@ } tr:first-child { - padding: 0 (2 * $gap) 0 (5 * $gap); + padding: 0 2 * $gap 0 5 * $gap; } td { @@ -203,14 +195,14 @@ th { background-color: $color-gray-lightest; - padding: $gap (2 * $gap); + padding: $gap 2 * $gap; border-top: none; border-bottom: none; color: $color-gray; } td:first-child { - padding: (2 * $gap) (2 * $gap) (2 * $gap) (5 * $gap); + padding: 2 * $gap 2 * $gap 2 * $gap 5 * $gap; } tbody { @@ -218,7 +210,7 @@ border-bottom: 1px solid $color-gray-lightest; font-size: 1.6rem; border-top: 0; - padding: (3 * $gap) (2 * $gap); + padding: 3 * $gap 2 * $gap; .usa-button-disabled { color: $color-gray-medium; @@ -318,6 +310,11 @@ .alert { margin: 4rem; } + + .member-list__subhead { + font-weight: $font-normal; + font-size: $base-font-size; + } } .application-content { @@ -333,7 +330,6 @@ .block-list__footer { border-bottom: none; } - .application-edit__env-list-item { label { color: $color-black; @@ -373,7 +369,6 @@ .portfolio-applications__header--actions { color: $color-blue; font-size: $small-font-size; - .icon { @include icon-color($color-blue); @include icon-size(14); @@ -384,7 +379,6 @@ .application-list { .toggle-link { background-color: $color-blue-light; - .icon { margin: $gap / 2; } @@ -402,7 +396,6 @@ .application-list-item__environment__csp_link { font-size: $small-font-size; font-weight: normal; - &:hover { background-color: $color-aqua-light; } @@ -420,7 +413,6 @@ .subheading { @include subheading; - margin-top: 6 * $gap; margin-bottom: 2 * $gap; } @@ -431,6 +423,7 @@ .pending-task-order { background-color: $color-gold-lightest; + align-items: center; margin: 0; margin-bottom: 2 * $gap; @@ -462,7 +455,6 @@ .icon--tiny { @include icon-size(10); - margin-left: 1rem; } } @@ -487,7 +479,7 @@ th { background-color: $color-gray-lightest; - padding: $gap (2 * $gap); + padding: $gap 2 * $gap; border-top: none; border-bottom: none; color: $color-gray; @@ -566,7 +558,6 @@ .panel { @include shadow-panel; - margin-bottom: 4 * $gap; } } @@ -586,7 +577,6 @@ input { max-width: 30em; } - .icon-validation { left: 30em; } @@ -615,3 +605,7 @@ } } } + +.member-list__name { + margin-top: 1rem; +} diff --git a/templates/fragments/applications/edit_team.html b/templates/fragments/applications/edit_team.html index 1914a62a..8b17426f 100644 --- a/templates/fragments/applications/edit_team.html +++ b/templates/fragments/applications/edit_team.html @@ -11,7 +11,11 @@
  • -
    {{ member_form.user_name.data }}
    +
    +
    + {{ member_form.user_name.data }} +
    +
    {{ OptionsInput(permissions_form.perms_team_mgmt, label=False, watch=True) }}
    {{ OptionsInput(permissions_form.perms_env_mgmt, label=False, watch=True) }}
    {{ OptionsInput(permissions_form.perms_del_env, label=False, watch=True) }}
    @@ -26,30 +30,47 @@ {{ ToggleButton( - open_html=open_html, - close_html=close_html, - section_name="environments" - ) + open_html=open_html, + close_html=close_html, + section_name="environments" + ) }}
    - - {% call ToggleSection(section_name="environments") %} + {% call ToggleSection(section_name="environments") %}
      {% for environment_form in environment_roles_form %} -
    • +
    • +
      {{ environment_form.environment_name.data }} -
    • + +
      +
      + + {{ environment_form.role.data }} + + +
      +
      + {% endfor %} - {% if user_can(permissions.DELETE_APPLICATION_MEMBER) %} -
    • - - {{ "portfolios.applications.remove_member.button" | translate }} - -
    • - {% endif %}
    - {% endcall %} - {{ member_form.user_id() }} + + {% endcall %} + {{ member_form.user_id() }}
  • {% endfor %} diff --git a/templates/portfolios/applications/team.html b/templates/portfolios/applications/team.html index d33992cd..a1ef3d57 100644 --- a/templates/portfolios/applications/team.html +++ b/templates/portfolios/applications/team.html @@ -52,62 +52,60 @@ {% if g.matchesPath("application-members") %} {% include "fragments/flash.html" %} {% endif %} -
    -
    -
    -
    - {{ "portfolios.applications.team_settings.section.title" | translate({ "application_name": application.name }) }} +
    +
    +
    +
    + {{ "portfolios.applications.team_settings.section.title" | translate({ "application_name": application.name }) }} +

    Members ({{ team_form.members | length }})

    +
    - - {{ Icon('info') }} - {{ "portfolios.admin.settings_info" | translate }} -
    -
    -
    -
    - {{ "common.name" | translate }} +
    +
    +
    + {{ "common.name" | translate }} +
    +
    + {{ "portfolios.applications.team_settings.section.table.team_management" | translate }} +
    +
    + {{ "portfolios.applications.team_settings.section.table.environment_management" | translate }} +
    +
    + {{ "portfolios.applications.team_settings.section.table.delete_access" | translate }} +
    +
    +   +
    -
    - {{ "portfolios.applications.team_settings.section.table.team_management" | translate }} -
    -
    - {{ "portfolios.applications.team_settings.section.table.environment_management" | translate }} -
    -
    - {{ "portfolios.applications.team_settings.section.table.delete_access" | translate }} -
    -
    -   +
      + {% if user_can(permissions.EDIT_APPLICATION_MEMBER) %} + {% include "fragments/applications/edit_team.html" %} + {% elif user_can(permissions.VIEW_APPLICATION_MEMBER) %} + {% include "fragments/applications/read_only_team.html" %} + {% endif %} +
    +
    + + -
      - {% if user_can(permissions.EDIT_APPLICATION_MEMBER) %} - {% include "fragments/applications/edit_team.html" %} - {% elif user_can(permissions.VIEW_APPLICATION_MEMBER) %} - {% include "fragments/applications/read_only_team.html" %} - {% endif %} -
    - - -
    diff --git a/translations.yaml b/translations.yaml index 920c7ffc..9342cb56 100644 --- a/translations.yaml +++ b/translations.yaml @@ -468,6 +468,7 @@ portfolios: subheading: Team Settings title: '{application_name} Team Settings' user: User + add_to_environment: Add to existing environment team_text: Team update_button_text: Save members: From d38c4df87852eef429587d30b6aa41965c215309 Mon Sep 17 00:00:00 2001 From: dandds Date: Fri, 10 May 2019 09:51:52 -0400 Subject: [PATCH 2/6] Form components for changing user environment role --- atst/forms/team.py | 14 +++- js/components/toggler.js | 1 + styles/components/_portfolio_layout.scss | 37 +++++++++ styles/core/_variables.scss | 2 +- templates/components/modal.html | 2 +- .../fragments/applications/edit_team.html | 81 +++++++++++-------- translations.yaml | 2 +- 7 files changed, 102 insertions(+), 37 deletions(-) diff --git a/atst/forms/team.py b/atst/forms/team.py index 6e4ce3ee..96c6a5ea 100644 --- a/atst/forms/team.py +++ b/atst/forms/team.py @@ -1,14 +1,24 @@ from flask_wtf import FlaskForm -from wtforms.fields import FormField, FieldList, HiddenField, StringField +from wtforms.fields import FormField, FieldList, HiddenField, RadioField, StringField from wtforms.validators import Required -from .application_member import EnvironmentForm +from .application_member import EnvironmentForm as BaseEnvironmentForm +from .data import ENV_ROLES from .forms import BaseForm from atst.forms.fields import SelectField from atst.domain.permission_sets import PermissionSets from atst.utils.localization import translate +class EnvironmentForm(BaseEnvironmentForm): + role = RadioField( + "Role", + choices=ENV_ROLES, + default=None, + filters=[lambda x: None if x == "None" else x], + ) + + class PermissionsForm(FlaskForm): perms_team_mgmt = SelectField( translate("portfolios.applications.members.new.manage_team"), diff --git a/js/components/toggler.js b/js/components/toggler.js index e433b294..44739b61 100644 --- a/js/components/toggler.js +++ b/js/components/toggler.js @@ -20,6 +20,7 @@ export default { optionsinput, textinput, optionsinput, + toggler: this, }, data: function() { diff --git a/styles/components/_portfolio_layout.scss b/styles/components/_portfolio_layout.scss index 7303c016..c555ed13 100644 --- a/styles/components/_portfolio_layout.scss +++ b/styles/components/_portfolio_layout.scss @@ -609,3 +609,40 @@ .member-list__name { margin-top: 1rem; } + +.member-list__role-select { + overflow: auto; + margin: 1.6rem -3.2rem -1.6rem -3.2rem; + padding: 2rem 3.2rem 2rem 5rem; + background: $color-gray-cool-light; + border-top: 1px solid $color-gray-lighter; + + > label { + font-weight: $font-bold; + margin: 0; + } + + > label:first-child + ul.member-list____role-select__radio { + display: flex; + background: $color-gray-cool-light; + + li { + border-bottom: none; + + label { + margin-top: 1rem; + margin-left: 2rem; + } + } + + li:first-child > label { + margin-left: 0; + } + } + + button { + font-size: $small-font-size; + float: right; + margin-right: 0; + } +} diff --git a/styles/core/_variables.scss b/styles/core/_variables.scss index 65153584..4463f1bd 100644 --- a/styles/core/_variables.scss +++ b/styles/core/_variables.scss @@ -73,7 +73,7 @@ $color-gray-lightest: #f1f1f1; $color-gray-warm-dark: #494440; $color-gray-warm-light: #e4e2e0; -$color-gray-cool-light: #dce4ef; +$color-gray-cool-light: #eff2f7; $color-gold-dark: #cd841b; $color-gold: #fdb81e; diff --git a/templates/components/modal.html b/templates/components/modal.html index 957aee3a..25772f53 100644 --- a/templates/components/modal.html +++ b/templates/components/modal.html @@ -12,7 +12,7 @@ {% endif %} diff --git a/templates/fragments/applications/edit_team.html b/templates/fragments/applications/edit_team.html index 8b17426f..a6a18ddd 100644 --- a/templates/fragments/applications/edit_team.html +++ b/templates/fragments/applications/edit_team.html @@ -37,40 +37,57 @@ }}
    {% call ToggleSection(section_name="environments") %} -
      - {% for environment_form in environment_roles_form %} -
    • -
      - {{ environment_form.environment_name.data }} -
      -
      -
      - - {{ environment_form.role.data }} - - -
      -
      -
    • - {% endfor %} -
    -
    - - {{ "portfolios.applications.team_settings.add_to_environment" | translate }} - {{ Icon("plus") }} - - +
    +
    + + + {% endfor %} + + + {{ "portfolios.members.archive_button" | translate }} + +
    {% endcall %} {{ member_form.user_id() }} - + {% endfor %} diff --git a/translations.yaml b/translations.yaml index 9342cb56..53e25cdf 100644 --- a/translations.yaml +++ b/translations.yaml @@ -20,6 +20,7 @@ base_public: common: back: Back cancel: Cancel + close: Close confirm: Confirm continue: Continue delete: Delete @@ -46,7 +47,6 @@ common: name: Name components: modal: - close: Close destructive_message: You will no longer be able to access this {resource} destructive_title: Warning! This action is permanent usa_header: From 39cc200bf21beba09f0d3f5a6fc571ba193819bd Mon Sep 17 00:00:00 2001 From: dandds Date: Fri, 10 May 2019 12:11:39 -0400 Subject: [PATCH 3/6] Vue component for editing application member environment role --- js/components/environment_role.js | 36 +++++++++++++++++++ js/components/toggler.js | 2 ++ js/index.js | 2 ++ .../fragments/applications/edit_team.html | 15 ++++---- 4 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 js/components/environment_role.js diff --git a/js/components/environment_role.js b/js/components/environment_role.js new file mode 100644 index 00000000..23133c25 --- /dev/null +++ b/js/components/environment_role.js @@ -0,0 +1,36 @@ +import optionsinput from './options_input' +import { emitEvent } from '../lib/emitters' + +export default { + name: 'environment-role', + + components: { + optionsinput, + }, + + props: { + initialRole: String, + }, + + data: function() { + return { + role: this.initialRole, + expanded: false, + } + }, + + methods: { + toggle: function() { + this.expanded = !this.expanded + }, + radioChange: function(e) { + this.role = e.target.value + emitEvent('field-change', this, { + value: e.target.value, + valid: true, + name: this.name, + watch: true, + }) + }, + }, +} diff --git a/js/components/toggler.js b/js/components/toggler.js index 44739b61..480af36e 100644 --- a/js/components/toggler.js +++ b/js/components/toggler.js @@ -2,6 +2,7 @@ import editEnvironmentRole from './forms/edit_environment_role' import FormMixin from '../mixins/form' import optionsinput from './options_input' import textinput from './text_input' +import EnvironmentRole from './environment_role' export default { name: 'toggler', @@ -20,6 +21,7 @@ export default { optionsinput, textinput, optionsinput, + EnvironmentRole, toggler: this, }, diff --git a/js/index.js b/js/index.js index 02a9b404..68eceffb 100644 --- a/js/index.js +++ b/js/index.js @@ -39,6 +39,7 @@ import KoReview from './components/forms/ko_review' import BaseForm from './components/forms/base_form' import DeleteConfirmation from './components/delete_confirmation' import NewEnvironment from './components/forms/new_environment' +import EnvironmentRole from './components/environment_role' Vue.config.productionTip = false @@ -80,6 +81,7 @@ const app = new Vue({ DeleteConfirmation, nestedcheckboxinput, NewEnvironment, + EnvironmentRole, }, mounted: function() { diff --git a/templates/fragments/applications/edit_team.html b/templates/fragments/applications/edit_team.html index a6a18ddd..dedee4a7 100644 --- a/templates/fragments/applications/edit_team.html +++ b/templates/fragments/applications/edit_team.html @@ -40,7 +40,7 @@
      {% for environment_form in environment_roles_form %}
    • - +
      @@ -48,28 +48,27 @@
      - - {{ environment_form.role.data }} + -
      -
      +
      {{ environment_form.role.label }} - {{ environment_form.role(class="member-list____role-select__radio") }} + {{ environment_form.role(**{"v-on:change": "radioChange", "class": "member-list____role-select__radio"}) }}
      - +
    • {% endfor %}
    From 0dc0397702f503e27e4f5d424dc400acfae7623f Mon Sep 17 00:00:00 2001 From: dandds Date: Tue, 14 May 2019 11:35:12 -0400 Subject: [PATCH 4/6] Update user's environment role on the team page. - Includes adjustments to the applications.update_team route - Adds hidden environment ID to the HTML form --- atst/routes/applications/team.py | 19 +++- atst/utils/flash.py | 4 +- .../fragments/applications/edit_team.html | 98 ++++++++++--------- tests/routes/applications/test_team.py | 60 +++++++++++- translations.yaml | 2 +- 5 files changed, 127 insertions(+), 56 deletions(-) diff --git a/atst/routes/applications/team.py b/atst/routes/applications/team.py index 97783f89..d4fa0a22 100644 --- a/atst/routes/applications/team.py +++ b/atst/routes/applications/team.py @@ -6,6 +6,7 @@ from atst.domain.applications import Applications from atst.domain.application_roles import ApplicationRoles from atst.domain.authz.decorator import user_can_access_decorator as user_can from atst.domain.environment_roles import EnvironmentRoles +from atst.domain.environments import Environments from atst.domain.exceptions import AlreadyExistsError from atst.domain.permission_sets import PermissionSets from atst.domain.users import Users @@ -97,15 +98,25 @@ def update_team(application_id): form = TeamForm(http_request.form) if form.validate(): - for member in form.members: - app_role = ApplicationRoles.get(member.data["user_id"], application.id) + for member_form in form.members: + app_role = ApplicationRoles.get(member_form.user_id.data, application.id) new_perms = [ perm - for perm in member.data["permission_sets"] + for perm in member_form.data["permission_sets"] if perm != PermissionSets.VIEW_APPLICATION ] ApplicationRoles.update_permission_sets(app_role, new_perms) - flash("updated_application_members_permissions") + + for environment_role_form in member_form.environment_roles: + user = Users.get(member_form.user_id.data) + environment = Environments.get( + environment_role_form.environment_id.data + ) + Environments.update_env_role( + environment, user, environment_role_form.role.data + ) + + flash("updated_application_team_settings", application_name=application.name) return redirect( url_for( diff --git a/atst/utils/flash.py b/atst/utils/flash.py index 1557a90e..0a96366a 100644 --- a/atst/utils/flash.py +++ b/atst/utils/flash.py @@ -186,10 +186,10 @@ MESSAGES = { """, "category": "success", }, - "updated_application_members_permissions": { + "updated_application_team_settings": { "title_template": translate("flash.success"), "message_template": """ -

    {{ "flash.updated_application_members_permissions" | translate }}

    +

    {{ "flash.updated_application_team_settings" | translate({"application_name": application_name}) }}

    """, "category": "success", }, diff --git a/templates/fragments/applications/edit_team.html b/templates/fragments/applications/edit_team.html index dedee4a7..74dba163 100644 --- a/templates/fragments/applications/edit_team.html +++ b/templates/fragments/applications/edit_team.html @@ -36,57 +36,59 @@ ) }}
    - {% call ToggleSection(section_name="environments") %} -
      - {% for environment_form in environment_roles_form %} -
    • - -
      -
      -
      - {{ environment_form.environment_name.data }} -
      -
      -
      - - - +
      + {% call ToggleSection(section_name="environments") %} +
        + {% for environment_form in environment_roles_form %} +
      • + +
        +
        +
        + {{ environment_form.environment_name.data }} +
        +
        +
        + + +
        -
        - {{ environment_form.role.label }} - {{ environment_form.role(**{"v-on:change": "radioChange", "class": "member-list____role-select__radio"}) }} - -
        - -
      • - {% endfor %} -
      - - {% endcall %} - {{ member_form.user_id() }} -
    • +
      + {{ environment_form.role.label }} + {{ environment_form.role(**{"v-on:change": "radioChange", "class": "member-list____role-select__radio"}) }} + + {{ environment_form.environment_id() }} +
      +
    + + + {% endfor %} + + + {% endcall %} + {{ member_form.user_id() }} + {% endfor %} diff --git a/tests/routes/applications/test_team.py b/tests/routes/applications/test_team.py index 39aaf533..b2d5a0e1 100644 --- a/tests/routes/applications/test_team.py +++ b/tests/routes/applications/test_team.py @@ -3,6 +3,7 @@ import uuid from flask import url_for from atst.domain.permission_sets import PermissionSets +from atst.models import CSPRole from tests.factories import * @@ -17,7 +18,7 @@ def test_application_team(client, user_session): assert response.status_code == 200 -def test_update_team(client, user_session): +def test_update_team_permissions(client, user_session): application = ApplicationFactory.create() owner = application.portfolio.owner app_role = ApplicationRoleFactory.create( @@ -91,6 +92,63 @@ def test_update_team_with_non_app_user(client, user_session): assert response.status_code == 404 +def test_update_team_environment_roles(client, user_session): + application = ApplicationFactory.create() + owner = application.portfolio.owner + app_role = ApplicationRoleFactory.create( + application=application, permission_sets=[] + ) + app_user = app_role.user + environment = EnvironmentFactory.create(application=application) + env_role = EnvironmentRoleFactory.create( + user=app_user, environment=environment, role=CSPRole.NETWORK_ADMIN.value + ) + user_session(owner) + response = client.post( + url_for("applications.update_team", application_id=application.id), + data={ + "members-0-user_id": app_user.id, + "members-0-permission_sets-perms_team_mgmt": PermissionSets.EDIT_APPLICATION_TEAM, + "members-0-permission_sets-perms_env_mgmt": PermissionSets.EDIT_APPLICATION_ENVIRONMENTS, + "members-0-permission_sets-perms_del_env": PermissionSets.DELETE_APPLICATION_ENVIRONMENTS, + "members-0-environment_roles-0-environment_id": environment.id, + "members-0-environment_roles-0-role": CSPRole.TECHNICAL_READ.value, + }, + ) + + assert response.status_code == 302 + assert env_role.role == CSPRole.TECHNICAL_READ.value + + +@pytest.mark.skip(reason="Need to rebase against master") +def test_update_team_revoke_environment_access(client, user_session): + application = ApplicationFactory.create() + owner = application.portfolio.owner + app_role = ApplicationRoleFactory.create( + application=application, permission_sets=[] + ) + app_user = app_role.user + environment = EnvironmentFactory.create(application=application) + env_role = EnvironmentRoleFactory.create( + user=app_user, environment=environment, role=CSPRole.BASIC_ACCESS.value + ) + user_session(owner) + response = client.post( + url_for("applications.update_team", application_id=application.id), + data={ + "members-0-user_id": app_user.id, + "members-0-permission_sets-perms_team_mgmt": PermissionSets.EDIT_APPLICATION_TEAM, + "members-0-permission_sets-perms_env_mgmt": PermissionSets.EDIT_APPLICATION_ENVIRONMENTS, + "members-0-permission_sets-perms_del_env": PermissionSets.DELETE_APPLICATION_ENVIRONMENTS, + "members-0-environment_roles-0-environment_id": environment.id, + "members-0-environment_roles-0-role": "", + }, + ) + + assert response.status_code == 302 + assert env_role.role == CSPRole.TECHNICAL_READ.value + + def test_create_member(client, user_session): user = UserFactory.create() application = ApplicationFactory.create( diff --git a/translations.yaml b/translations.yaml index 53e25cdf..1bbd8dac 100644 --- a/translations.yaml +++ b/translations.yaml @@ -80,7 +80,7 @@ flash: portfolio_home: Go to my portfolio home page success: Success! new_application_member: 'You have successfully invited {user_name} to the team.' - updated_application_members_permissions: 'You have successfully updated member permissions.' + updated_application_team_settings: 'You have updated the {application_name} team settings.' footer: about_link_text: Joint Enterprise Defense Infrastructure browser_support: JEDI Cloud supported on these web browsers From 815632ed00f3c4acef1a8538f1e925c80a6b07ad Mon Sep 17 00:00:00 2001 From: dandds Date: Wed, 15 May 2019 09:27:07 -0400 Subject: [PATCH 5/6] Handle setting no access for user environment roles: - use constant for no access string - update no access constant - update language and permissions for rendering remove app member button --- atst/forms/app_settings.py | 6 +++--- atst/forms/data.py | 3 ++- atst/forms/team.py | 9 ++++++++- atst/routes/applications/settings.py | 5 +++-- atst/routes/applications/team.py | 2 +- .../__tests__/edit_environment_role.test.js | 8 ++++---- js/components/forms/edit_application_roles.js | 2 +- js/components/forms/edit_environment_role.js | 8 +++++--- js/components/toggler.js | 4 ++-- js/index.js | 2 +- templates/fragments/applications/edit_team.html | 16 +++++++++------- tests/routes/applications/test_settings.py | 6 +++--- tests/routes/applications/test_team.py | 9 +++++---- 13 files changed, 47 insertions(+), 33 deletions(-) diff --git a/atst/forms/app_settings.py b/atst/forms/app_settings.py index 20e1aa25..19801a10 100644 --- a/atst/forms/app_settings.py +++ b/atst/forms/app_settings.py @@ -2,18 +2,18 @@ from flask_wtf import FlaskForm from wtforms.fields import FieldList, FormField, HiddenField, RadioField, StringField from .forms import BaseForm -from .data import ENV_ROLES +from .data import ENV_ROLES, ENV_ROLE_NO_ACCESS as NO_ACCESS class MemberForm(FlaskForm): user_id = HiddenField() user_name = StringField() - role_name = RadioField(choices=ENV_ROLES, default="no_access") + role_name = RadioField(choices=ENV_ROLES, default=NO_ACCESS) @property def data(self): _data = super().data - if "role_name" in _data and _data["role_name"] == "no_access": + if "role_name" in _data and _data["role_name"] == NO_ACCESS: _data["role_name"] = None return _data diff --git a/atst/forms/data.py b/atst/forms/data.py index 2ddc3124..e0d4cf7f 100644 --- a/atst/forms/data.py +++ b/atst/forms/data.py @@ -217,6 +217,7 @@ REQUIRED_DISTRIBUTIONS = [ ("other", "Other as necessary"), ] +ENV_ROLE_NO_ACCESS = "No Access" ENV_ROLES = [(role.value, role.value) for role in CSPRole] + [ - ("no_access", "No access") + (ENV_ROLE_NO_ACCESS, "No access") ] diff --git a/atst/forms/team.py b/atst/forms/team.py index 96c6a5ea..29e46874 100644 --- a/atst/forms/team.py +++ b/atst/forms/team.py @@ -3,7 +3,7 @@ from wtforms.fields import FormField, FieldList, HiddenField, RadioField, String from wtforms.validators import Required from .application_member import EnvironmentForm as BaseEnvironmentForm -from .data import ENV_ROLES +from .data import ENV_ROLES, ENV_ROLE_NO_ACCESS as NO_ACCESS from .forms import BaseForm from atst.forms.fields import SelectField from atst.domain.permission_sets import PermissionSets @@ -18,6 +18,13 @@ class EnvironmentForm(BaseEnvironmentForm): filters=[lambda x: None if x == "None" else x], ) + @property + def data(self): + _data = super().data + if "role" in _data and _data["role"] == NO_ACCESS: + _data["role"] = None + return _data + class PermissionsForm(FlaskForm): perms_team_mgmt = SelectField( diff --git a/atst/routes/applications/settings.py b/atst/routes/applications/settings.py index b3077b77..935aaaf4 100644 --- a/atst/routes/applications/settings.py +++ b/atst/routes/applications/settings.py @@ -5,6 +5,7 @@ from atst.domain.environments import Environments from atst.domain.applications import Applications from atst.forms.app_settings import AppEnvRolesForm from atst.forms.application import ApplicationForm, EditEnvironmentForm +from atst.forms.data import ENV_ROLE_NO_ACCESS as NO_ACCESS from atst.domain.authz.decorator import user_can_access_decorator as user_can from atst.models.environment_role import CSPRole from atst.domain.exceptions import NotFoundError @@ -46,10 +47,10 @@ def sort_env_users_by_role(env): users_list = [] no_access_users = env.application.users - env.users no_access_list = [ - {"user_id": str(user.id), "user_name": user.full_name, "role_name": "no_access"} + {"user_id": str(user.id), "user_name": user.full_name, "role_name": NO_ACCESS} for user in no_access_users ] - users_list.append({"role": "no_access", "members": no_access_list}) + users_list.append({"role": NO_ACCESS, "members": no_access_list}) for role in CSPRole: users_list.append( diff --git a/atst/routes/applications/team.py b/atst/routes/applications/team.py index d4fa0a22..3aa988e4 100644 --- a/atst/routes/applications/team.py +++ b/atst/routes/applications/team.py @@ -113,7 +113,7 @@ def update_team(application_id): environment_role_form.environment_id.data ) Environments.update_env_role( - environment, user, environment_role_form.role.data + environment, user, environment_role_form.data.get("role") ) flash("updated_application_team_settings", application_name=application.name) diff --git a/js/components/__tests__/edit_environment_role.test.js b/js/components/__tests__/edit_environment_role.test.js index 4421d58c..8242acb4 100644 --- a/js/components/__tests__/edit_environment_role.test.js +++ b/js/components/__tests__/edit_environment_role.test.js @@ -1,5 +1,5 @@ import { shallowMount } from '@vue/test-utils' -import EditEnvironmentRole from '../forms/edit_environment_role' +import { NO_ACCESS, EditEnvironmentRole } from '../forms/edit_environment_role' describe('EditEnvironmentRole', () => { var initialRoleCategories, wrapper @@ -7,7 +7,7 @@ describe('EditEnvironmentRole', () => { beforeEach(() => { initialRoleCategories = [ { - role: 'no_access', + role: NO_ACCESS, members: [ { role_name: null, user_id: '123' }, { role_name: null, user_id: '456' }, @@ -41,10 +41,10 @@ describe('EditEnvironmentRole', () => { it('removes null roles to no_access', () => { let roles = wrapper.vm.sanitizeValues([ - { role: 'no_access', members: [{ role_name: null }] }, + { role: NO_ACCESS, members: [{ role_name: null }] }, ]) expect(roles).toEqual([ - { role: 'no_access', members: [{ role_name: 'no_access' }] }, + { role: NO_ACCESS, members: [{ role_name: NO_ACCESS }] }, ]) }) diff --git a/js/components/forms/edit_application_roles.js b/js/components/forms/edit_application_roles.js index c4f6d490..ef5de676 100644 --- a/js/components/forms/edit_application_roles.js +++ b/js/components/forms/edit_application_roles.js @@ -1,7 +1,7 @@ import FormMixin from '../../mixins/form' import Modal from '../../mixins/modal' import toggler from '../toggler' -import EditEnvironmentRole from './edit_environment_role' +import { EditEnvironmentRole } from './edit_environment_role' export default { name: 'edit-application-roles', diff --git a/js/components/forms/edit_environment_role.js b/js/components/forms/edit_environment_role.js index cbb09457..e9081c34 100644 --- a/js/components/forms/edit_environment_role.js +++ b/js/components/forms/edit_environment_role.js @@ -5,7 +5,9 @@ import Modal from '../../mixins/modal' // https://github.com/dod-ccpo/atst/pull/799/files#r282240663 // May also want to reconsider the data structure by storing the roles and members separately -export default { +export const NO_ACCESS = 'No Access' + +export const EditEnvironmentRole = { name: 'edit-environment-role', mixins: [FormMixin], @@ -26,7 +28,7 @@ export default { roles.forEach(role => { role.members.forEach(member => { if (member.role_name === null) { - member.role_name = 'no_access' + member.role_name = NO_ACCESS } }) }) @@ -34,7 +36,7 @@ export default { }, checkNoAccess: function(role) { - return role === 'no_access' + return role === NO_ACCESS }, toggleSection: function(sectionName) { diff --git a/js/components/toggler.js b/js/components/toggler.js index 480af36e..225e144b 100644 --- a/js/components/toggler.js +++ b/js/components/toggler.js @@ -1,4 +1,4 @@ -import editEnvironmentRole from './forms/edit_environment_role' +import { EditEnvironmentRole } from './forms/edit_environment_role' import FormMixin from '../mixins/form' import optionsinput from './options_input' import textinput from './text_input' @@ -17,7 +17,7 @@ export default { }, components: { - editEnvironmentRole, + EditEnvironmentRole, optionsinput, textinput, optionsinput, diff --git a/js/index.js b/js/index.js index 68eceffb..520dea03 100644 --- a/js/index.js +++ b/js/index.js @@ -18,7 +18,7 @@ import poc from './components/forms/poc' import oversight from './components/forms/oversight' import toggler from './components/toggler' import NewApplication from './components/forms/new_application' -import EditEnvironmentRole from './components/forms/edit_environment_role' +import { EditEnvironmentRole } from './components/forms/edit_environment_role' import EditApplicationRoles from './components/forms/edit_application_roles' import MultiStepModalForm from './components/forms/multi_step_modal_form' import funding from './components/forms/funding' diff --git a/templates/fragments/applications/edit_team.html b/templates/fragments/applications/edit_team.html index 74dba163..1609b0cb 100644 --- a/templates/fragments/applications/edit_team.html +++ b/templates/fragments/applications/edit_team.html @@ -79,13 +79,15 @@ {{ "portfolios.applications.team_settings.add_to_environment" | translate }} {{ Icon("plus") }} - + {% if user_can(permissions.DELETE_APPLICATION_MEMBER) %} + + {% endif %} {% endcall %} {{ member_form.user_id() }} diff --git a/tests/routes/applications/test_settings.py b/tests/routes/applications/test_settings.py index a658107a..40f758ed 100644 --- a/tests/routes/applications/test_settings.py +++ b/tests/routes/applications/test_settings.py @@ -18,11 +18,11 @@ from atst.domain.environments import Environments from atst.domain.permission_sets import PermissionSets from atst.domain.portfolios import Portfolios from atst.domain.exceptions import NotFoundError - from atst.models.environment_role import CSPRole from atst.models.portfolio_role import Status as PortfolioRoleStatus from atst.forms.application import EditEnvironmentForm from atst.forms.app_settings import AppEnvRolesForm +from atst.forms.data import ENV_ROLE_NO_ACCESS as NO_ACCESS from tests.utils import captured_templates @@ -166,7 +166,7 @@ def test_data_for_app_env_roles_form(app, client, user_session): "env_id": env.id, "team_roles": [ { - "role": "no_access", + "role": NO_ACCESS, "members": [ { "user_id": str(app_role.user_id), @@ -309,7 +309,7 @@ def test_update_team_env_roles(client, user_session): "envs-0-team_roles-1-members-1-user_id": env_role_2.user.id, "envs-0-team_roles-1-members-1-role_name": CSPRole.BASIC_ACCESS.value, "envs-0-team_roles-1-members-2-user_id": env_role_3.user.id, - "envs-0-team_roles-1-members-2-role_name": "no_access", + "envs-0-team_roles-1-members-2-role_name": NO_ACCESS, } user_session(application.portfolio.owner) diff --git a/tests/routes/applications/test_team.py b/tests/routes/applications/test_team.py index b2d5a0e1..99c7da78 100644 --- a/tests/routes/applications/test_team.py +++ b/tests/routes/applications/test_team.py @@ -4,6 +4,7 @@ from flask import url_for from atst.domain.permission_sets import PermissionSets from atst.models import CSPRole +from atst.forms.data import ENV_ROLE_NO_ACCESS as NO_ACCESS from tests.factories import * @@ -120,8 +121,7 @@ def test_update_team_environment_roles(client, user_session): assert env_role.role == CSPRole.TECHNICAL_READ.value -@pytest.mark.skip(reason="Need to rebase against master") -def test_update_team_revoke_environment_access(client, user_session): +def test_update_team_revoke_environment_access(client, user_session, db, session): application = ApplicationFactory.create() owner = application.portfolio.owner app_role = ApplicationRoleFactory.create( @@ -141,12 +141,13 @@ def test_update_team_revoke_environment_access(client, user_session): "members-0-permission_sets-perms_env_mgmt": PermissionSets.EDIT_APPLICATION_ENVIRONMENTS, "members-0-permission_sets-perms_del_env": PermissionSets.DELETE_APPLICATION_ENVIRONMENTS, "members-0-environment_roles-0-environment_id": environment.id, - "members-0-environment_roles-0-role": "", + "members-0-environment_roles-0-role": NO_ACCESS, }, ) assert response.status_code == 302 - assert env_role.role == CSPRole.TECHNICAL_READ.value + env_role_exists = db.exists().where(EnvironmentRole.id == env_role.id) + assert not session.query(env_role_exists).scalar() def test_create_member(client, user_session): From 8bb1e5bebad294a331a5653ee2f724568beb59af Mon Sep 17 00:00:00 2001 From: dandds Date: Wed, 15 May 2019 16:49:21 -0400 Subject: [PATCH 6/6] Fix up templates for application team table: - add permission check for link to add member to an environment - import missing macros in read-only version of member table --- templates/fragments/applications/edit_team.html | 10 ++++++---- templates/fragments/applications/read_only_team.html | 2 ++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/templates/fragments/applications/edit_team.html b/templates/fragments/applications/edit_team.html index 1609b0cb..844e22dc 100644 --- a/templates/fragments/applications/edit_team.html +++ b/templates/fragments/applications/edit_team.html @@ -75,10 +75,12 @@ {% endfor %}