From 079672c818828942ca42a2285ad7faad1e9179b0 Mon Sep 17 00:00:00 2001 From: George Drummond Date: Mon, 22 Apr 2019 10:37:03 -0400 Subject: [PATCH] Update application environments --- atst/domain/environments.py | 7 ++++ atst/forms/application.py | 6 +++ atst/routes/applications/settings.py | 34 +++++++++++++-- atst/utils/flash.py | 5 +++ js/components/toggler.js | 9 ++++ styles/components/_accordion_table.scss | 5 +++ .../applications/edit_environments.html | 25 ++++++----- .../portfolios/applications/settings.html | 34 ++++++++------- tests/domain/test_environments.py | 7 ++++ tests/routes/applications/test_settings.py | 41 +++++++++++++++---- tests/test_access.py | 19 +++++++++ translations.yaml | 2 + 12 files changed, 159 insertions(+), 35 deletions(-) diff --git a/atst/domain/environments.py b/atst/domain/environments.py index 44eaad9a..9c8e4d52 100644 --- a/atst/domain/environments.py +++ b/atst/domain/environments.py @@ -49,6 +49,13 @@ class Environments(object): .all() ) + @classmethod + def update(cls, environment, name=None): + if name is not None: + environment.name = name + db.session.add(environment) + db.session.commit() + @classmethod def get(cls, environment_id): try: diff --git a/atst/forms/application.py b/atst/forms/application.py index 745bb7f8..4fae3f1c 100644 --- a/atst/forms/application.py +++ b/atst/forms/application.py @@ -5,6 +5,12 @@ from atst.forms.validators import ListItemRequired, ListItemsUnique from atst.utils.localization import translate +class EditEnvironmentForm(BaseForm): + name = StringField( + label=translate("forms.environments.name_label"), validators=[Required()] + ) + + class ApplicationForm(BaseForm): name = StringField( label=translate("forms.application.name_label"), validators=[Required()] diff --git a/atst/routes/applications/settings.py b/atst/routes/applications/settings.py index 169af8c9..a7628ade 100644 --- a/atst/routes/applications/settings.py +++ b/atst/routes/applications/settings.py @@ -4,8 +4,8 @@ from . import applications_bp from atst.domain.environment_roles import EnvironmentRoles from atst.domain.environments import Environments from atst.domain.applications import Applications -from atst.forms.application import ApplicationForm from atst.forms.app_settings import EnvironmentRolesForm +from atst.forms.application import ApplicationForm, EditEnvironmentForm from atst.domain.authz.decorator import user_can_access_decorator as user_can from atst.models.permissions import Permissions from atst.utils.flash import formatted_flash as flash @@ -15,10 +15,14 @@ def get_environments_obj_for_app(application): environments_obj = {} for env in application.environments: - environments_obj[env.name] = [] + environments_obj[env.name] = { + "edit_form": EditEnvironmentForm(obj=env), + "id": env.id, + "members": [], + } for user in env.users: env_role = EnvironmentRoles.get(user.id, env.id) - environments_obj[env.name].append( + environments_obj[env.name]["members"].append( {"name": user.full_name, "role": env_role.displayname} ) @@ -49,6 +53,7 @@ def settings(application_id): application = Applications.get(application_id) form = ApplicationForm(name=application.name, description=application.description) app_envs_data = serialize_env_member_form_data(application) + env_forms = {} for env_data in app_envs_data: env_forms[env_data["env_id"]] = EnvironmentRolesForm(data=env_data) @@ -62,6 +67,29 @@ def settings(application_id): ) +@applications_bp.route("/environments//edit", methods=["POST"]) +@user_can(Permissions.EDIT_ENVIRONMENT, message="edit application environments") +def update_environment(environment_id): + environment = Environments.get(environment_id) + application = environment.application + + form = EditEnvironmentForm(formdata=http_request.form) + + if form.validate(): + Environments.update(environment=environment, name=form.name.data) + + flash("application_environments_updated") + + return redirect( + url_for( + "applications.settings", + application_id=application.id, + fragment="application-environments", + _anchor="application-environments", + ) + ) + + @applications_bp.route("/applications//edit", methods=["POST"]) @user_can(Permissions.EDIT_APPLICATION, message="update application") def update(application_id): diff --git a/atst/utils/flash.py b/atst/utils/flash.py index 7970cc03..da021ecc 100644 --- a/atst/utils/flash.py +++ b/atst/utils/flash.py @@ -2,6 +2,11 @@ from flask import flash, render_template_string from atst.utils.localization import translate MESSAGES = { + "application_environments_updated": { + "title_template": "Application environments updated", + "message_template": "Application environments have been updated", + "category": "success", + }, "primary_point_of_contact_changed": { "title_template": translate("flash.new_ppoc_title"), "message_template": """{{ "flash.new_ppoc_message" | translate({ "ppoc_name": ppoc_name }) }}""", diff --git a/js/components/toggler.js b/js/components/toggler.js index 52f756b0..ff3d55c5 100644 --- a/js/components/toggler.js +++ b/js/components/toggler.js @@ -1,6 +1,15 @@ +import FormMixin from '../mixins/form' +import textinput from './text_input' + export default { name: 'toggler', + mixins: [FormMixin], + + components: { + textinput, + }, + data: function() { return { selectedSection: null, diff --git a/styles/components/_accordion_table.scss b/styles/components/_accordion_table.scss index 5ee20a09..7fa7312a 100644 --- a/styles/components/_accordion_table.scss +++ b/styles/components/_accordion_table.scss @@ -3,6 +3,11 @@ } .accordion-table { + .usa-alert { + margin: $gap*2; + margin-bottom: 0; + } + ul { padding-left: 0; } diff --git a/templates/fragments/applications/edit_environments.html b/templates/fragments/applications/edit_environments.html index 5660fcb6..607d854e 100644 --- a/templates/fragments/applications/edit_environments.html +++ b/templates/fragments/applications/edit_environments.html @@ -1,5 +1,7 @@ {% from "components/icon.html" import Icon %} {% from "components/toggle_list.html" import ToggleButton, ToggleSection %} +{% from "components/text_input.html" import TextInput %} +{% from "components/save_button.html" import SaveButton %}
@@ -20,7 +22,7 @@
    - {% for name, members_list in environments_obj.items() %} + {% for name, environment_info in environments_obj.items() %}
  • @@ -61,7 +63,7 @@ {% call ToggleSection(section_name="members") %}
      - {% for member in members_list %} + {% for member in environment_info['members'] %}
    • {{ member.name }}
      {{ member.role }}
      @@ -73,15 +75,16 @@ {% call ToggleSection(section_name="edit") %}
      • -
        -
        -
        -
        - Row here -
        -
        -
        -
        + {% set edit_form = environment_info['edit_form'] %} +
        + {{ edit_form.csrf_token }} + {{ TextInput(edit_form.name) }} + {{ + SaveButton( + text=("portfolios.applications.update_button_text" | translate) + ) + }} +
      {% endcall %} diff --git a/templates/portfolios/applications/settings.html b/templates/portfolios/applications/settings.html index d6abf274..36717346 100644 --- a/templates/portfolios/applications/settings.html +++ b/templates/portfolios/applications/settings.html @@ -77,23 +77,29 @@
    +
    - {% if user_can(permissions.EDIT_APPLICATION) %} - {% include "fragments/applications/edit_environments.html" %} - - {% elif user_can(permissions.VIEW_ENVIRONMENT) %} - {% include "fragments/applications/read_only_environments.html" %} - {% endif %} + {% if g.matchesPath("application-environments") %} + {% include "fragments/flash.html" %} + {% endif %} + {% if user_can(permissions.EDIT_APPLICATION) %} + {% include "fragments/applications/edit_environments.html" %} + + {% elif user_can(permissions.VIEW_ENVIRONMENT) %} + {% include "fragments/applications/read_only_environments.html" %} + {% endif %}
    +
    + {% if user_can(permissions.DELETE_APPLICATION) %} {% call Modal(name="delete-application") %}

    {{ "portfolios.applications.delete.header" | translate }}

    diff --git a/tests/domain/test_environments.py b/tests/domain/test_environments.py index af8bb2a4..c620c063 100644 --- a/tests/domain/test_environments.py +++ b/tests/domain/test_environments.py @@ -187,3 +187,10 @@ def test_delete_environment(session): assert env_role.deleted # flushed the change assert not session.dirty + + +def test_update_environment(): + environment = EnvironmentFactory.create() + assert environment.name is not "name 2" + Environments.update(environment, name="name 2") + assert environment.name == "name 2" diff --git a/tests/routes/applications/test_settings.py b/tests/routes/applications/test_settings.py index 2b9ec47e..6423e68e 100644 --- a/tests/routes/applications/test_settings.py +++ b/tests/routes/applications/test_settings.py @@ -17,10 +17,36 @@ from atst.domain.permission_sets import PermissionSets from atst.domain.portfolios import Portfolios from atst.models.environment_role import CSPRole from atst.models.portfolio_role import Status as PortfolioRoleStatus +from atst.forms.application import EditEnvironmentForm from tests.utils import captured_templates +def test_updating_application_environments(client, user_session): + portfolio = PortfolioFactory.create() + application = ApplicationFactory.create(portfolio=portfolio) + environment = EnvironmentFactory.create(application=application) + + user_session(portfolio.owner) + + form_data = {"name": "new name a"} + + response = client.post( + url_for("applications.update_environment", environment_id=environment.id), + data=form_data, + ) + + assert response.status_code == 302 + assert response.location == url_for( + "applications.settings", + application_id=application.id, + _external=True, + fragment="application-environments", + _anchor="application-environments", + ) + assert environment.name == "new name a" + + def test_application_settings(client, user_session): portfolio = PortfolioFactory.create() application = Applications.create( @@ -61,13 +87,14 @@ def test_edit_application_environments_obj(app, client, user_session): assert response.status_code == 200 _, context = templates[0] - assert context["environments_obj"] == { - env1.name: [ - {"name": user1.full_name, "role": env_role1.role}, - {"name": user2.full_name, "role": env_role2.role}, - ], - env2.name: [{"name": user1.full_name, "role": env_role3.role}], - } + + env_obj_1 = context["environments_obj"][env1.name] + assert env_obj_1["id"] == env1.id + assert isinstance(env_obj_1["edit_form"], EditEnvironmentForm) + assert env_obj_1["members"] == [ + {"name": user1.full_name, "role": env_role1.role}, + {"name": user2.full_name, "role": env_role2.role}, + ] def test_edit_app_serialize_env_member_form_data(app, client, user_session): diff --git a/tests/test_access.py b/tests/test_access.py index 9839902c..a2270940 100644 --- a/tests/test_access.py +++ b/tests/test_access.py @@ -547,6 +547,25 @@ def test_applications_update_access(post_url_assert_status): post_url_assert_status(rando, url, 404) +# applications.update_environments +def test_applications_update_environments(post_url_assert_status): + ccpo = UserFactory.create_ccpo() + dev = UserFactory.create() + rando = UserFactory.create() + + portfolio = PortfolioFactory.create( + owner=dev, + applications=[{"name": "Mos Eisley", "description": "Where Han shot first"}], + ) + app = portfolio.applications[0] + environment = EnvironmentFactory.create(application=app) + + url = url_for("applications.update_environment", environment_id=environment.id) + post_url_assert_status(dev, url, 302) + post_url_assert_status(ccpo, url, 302) + post_url_assert_status(rando, url, 404) + + # task_orders.view_task_order def test_task_orders_view_task_order_access(get_url_assert_status): ccpo = user_with(PermissionSets.VIEW_PORTFOLIO_FUNDING) diff --git a/translations.yaml b/translations.yaml index c4fd2835..43d406f5 100644 --- a/translations.yaml +++ b/translations.yaml @@ -78,6 +78,8 @@ forms: name_label: Name assign_ppoc: dod_id: 'Select new primary point of contact:' + environments: + name_label: Environment Name ccpo_review: comment_description: Provide instructions or notes for additional information that is necessary to approve the request here. The requestor may then re-submit the updated request or initiate contact outside of AT-AT if further discussion is required. This message will be shared with the person making the JEDI request.. comment_label: Instructions or comments