From 31b7e2f589362f79e77d6fc4e3748e9d6a371a54 Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Thu, 23 Jan 2020 11:00:09 -0500 Subject: [PATCH 1/6] Create route and domain method for creating a subscription --- atst/domain/csp/cloud.py | 11 +++++++ atst/routes/applications/settings.py | 31 +++++++++++++++++- atst/utils/flash.py | 5 +++ tests/routes/applications/test_settings.py | 37 ++++++++++++++++++++++ translations.yaml | 3 ++ 5 files changed, 86 insertions(+), 1 deletion(-) diff --git a/atst/domain/csp/cloud.py b/atst/domain/csp/cloud.py index 9f764da0..b04ed91f 100644 --- a/atst/domain/csp/cloud.py +++ b/atst/domain/csp/cloud.py @@ -341,6 +341,12 @@ class CloudProviderInterface: """ raise NotImplementedError() + def create_subscription(self, environment): + """Returns True if a new subscription has been created or raises an + exception if an error occurs while creating a subscription. + """ + raise NotImplementedError() + class MockCloudProvider(CloudProviderInterface): @@ -508,6 +514,11 @@ class MockCloudProvider(CloudProviderInterface): return self._maybe(12) + def create_subscription(self, environment): + self._maybe_raise(self.UNAUTHORIZED_RATE, GeneralCSPException) + + return True + def get_calculator_url(self): return "https://www.rackspace.com/en-us/calculator" diff --git a/atst/routes/applications/settings.py b/atst/routes/applications/settings.py index da04bc84..137af0d7 100644 --- a/atst/routes/applications/settings.py +++ b/atst/routes/applications/settings.py @@ -1,9 +1,10 @@ from flask import ( + current_app as app, + g, redirect, render_template, request as http_request, url_for, - g, ) from .blueprint import applications_bp @@ -522,3 +523,31 @@ def resend_invite(application_id, application_role_id): _anchor="application-members", ) ) + + +@applications_bp.route( + "/environments//add_subscription", methods=["POST"] +) +# TODO: decide what perms are needed to create a subscription +@user_can(Permissions.EDIT_ENVIRONMENT, message="create new environment subscription") +def create_subscription(environment_id): + environment = Environments.get(environment_id) + + try: + app.csp.cloud.create_subscription(environment) + + except GeneralCSPException: + flash("environment_subscription_failure") + return ( + render_settings_page(application=environment.application, show_flash=True), + 400, + ) + + return redirect( + url_for( + "applications.settings", + application_id=environment.application.id, + fragment="application-environments", + _anchor="application-environments", + ) + ) diff --git a/atst/utils/flash.py b/atst/utils/flash.py index 02a74cb2..8caf7ed8 100644 --- a/atst/utils/flash.py +++ b/atst/utils/flash.py @@ -83,6 +83,11 @@ MESSAGES = { "message": "flash.environment.deleted.message", "category": "success", }, + "environment_subscription_failure": { + "title": "flash.environment.subscription_failure.title", + "message": "flash.environment.subscription_failure.message", + "category": "error", + }, "form_errors": { "title": "flash.form.errors.title", "message": "flash.form.errors.message", diff --git a/tests/routes/applications/test_settings.py b/tests/routes/applications/test_settings.py index fe9823ab..37c71878 100644 --- a/tests/routes/applications/test_settings.py +++ b/tests/routes/applications/test_settings.py @@ -777,3 +777,40 @@ def test_handle_update_member_with_error(set_g, monkeypatch, mock_logger): handle_update_member(application.id, app_role.id, form_data) assert mock_logger.messages[-1] == exception + + +def test_create_subscription_success(client, user_session): + environment = EnvironmentFactory.create() + + user_session(environment.portfolio.owner) + response = client.post( + url_for("applications.create_subscription", environment_id=environment.id), + ) + + assert response.status_code == 302 + assert response.location == url_for( + "applications.settings", + application_id=environment.application.id, + _external=True, + fragment="application-environments", + _anchor="application-environments", + ) + + +def test_create_subscription_failure(client, user_session, monkeypatch): + environment = EnvironmentFactory.create() + + def _raise_csp_exception(*args, **kwargs): + raise GeneralCSPException("An error occurred.") + + monkeypatch.setattr( + "atst.domain.csp.cloud.MockCloudProvider.create_subscription", + _raise_csp_exception, + ) + + user_session(environment.portfolio.owner) + response = client.post( + url_for("applications.create_subscription", environment_id=environment.id), + ) + + assert response.status_code == 400 diff --git a/translations.yaml b/translations.yaml index 22165fb9..c6d3dbbc 100644 --- a/translations.yaml +++ b/translations.yaml @@ -127,6 +127,9 @@ flash: deleted: title: "{environment_name} deleted" message: The environment "{environment_name}" has been deleted + subscription_failure: + title: Environment subscription error + message: An unexpected problem occurred with your request, please try again. If the problem persists, contact an administrator. form: errors: title: There were some errors From ec56d8e38a4bf435a9a76ce5ceaa8789b3a2426a Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Thu, 23 Jan 2020 11:35:57 -0500 Subject: [PATCH 2/6] Properly display environment role on application index page --- atst/routes/applications/index.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atst/routes/applications/index.py b/atst/routes/applications/index.py index 92b65398..8b249faa 100644 --- a/atst/routes/applications/index.py +++ b/atst/routes/applications/index.py @@ -26,7 +26,7 @@ def has_portfolio_applications(_user, portfolio=None, **_kwargs): def portfolio_applications(portfolio_id): user_env_roles = EnvironmentRoles.for_user(g.current_user.id, portfolio_id) environment_access = { - env_role.environment_id: env_role.role for env_role in user_env_roles + env_role.environment_id: env_role.role.value for env_role in user_env_roles } return render_template( From 105445704834356deb6fa12bc40c6276b2872838 Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Thu, 23 Jan 2020 13:16:24 -0500 Subject: [PATCH 3/6] Button for adding a subscription --- .../applications/fragments/environments.html | 15 +++++++++------ templates/components/save_button.html | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/templates/applications/fragments/environments.html b/templates/applications/fragments/environments.html index 8676bc6d..5f92e6aa 100644 --- a/templates/applications/fragments/environments.html +++ b/templates/applications/fragments/environments.html @@ -79,14 +79,17 @@
  • -
    + {{ edit_form.csrf_token }} {{ TextInput(edit_form.name, validation='defaultStringField', optional=False) }} - {{ - SaveButton( - text=("common.save_changes" | translate) - ) - }} +
    + {{ + SaveButton( + text=("common.save_changes" | translate) + ) + }} +
    +
  • diff --git a/templates/components/save_button.html b/templates/components/save_button.html index 5426219f..ebf96e5a 100644 --- a/templates/components/save_button.html +++ b/templates/components/save_button.html @@ -2,7 +2,7 @@ {% set class = "usa-button usa-button-primary " + additional_classes %} {% if element == "button" %} - {% elif element == 'input' %} From 0fcd5a6471bb2bfe08cbc260eb807adc5d01de55 Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Thu, 23 Jan 2020 14:34:43 -0500 Subject: [PATCH 4/6] Move text into translations file and fix formatting to make file more readable --- .../applications/fragments/environments.html | 30 ++++++++++++++----- translations.yaml | 7 ++++- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/templates/applications/fragments/environments.html b/templates/applications/fragments/environments.html index 5f92e6aa..51cad0fc 100644 --- a/templates/applications/fragments/environments.html +++ b/templates/applications/fragments/environments.html @@ -17,7 +17,7 @@ {% if 0 == environments_obj | length -%}

    - This Application has no environments + {{ 'portfolios.applications.environments.blank_slate' | translate }}

    {% else %} @@ -43,7 +43,7 @@ ) }} {% if user_can(permissions.EDIT_ENVIRONMENT) -%} - {% set edit_environment_button = "Edit" %} + {% set edit_environment_button = "common.edit"|translate %} {{ ToggleButton( open_html=edit_environment_button, @@ -56,8 +56,15 @@ {% if env['pending'] -%} {{ Label(type="changes_pending", classes='label--below')}} {% else %} - - {{ "portfolios.applications.csp_link" | translate }} {{ Icon('link', classes="icon--tiny") }} + + + {{ "portfolios.applications.csp_link" | translate }} + {{ Icon('link', classes="icon--tiny") }} + {%- endif %} @@ -66,7 +73,7 @@ {% call ToggleSection(section_name="members") %}
      {% for member in env['members'] %} - {% set status = ": Access Suspended" if member['status'] == 'disabled' %} + {% set status = "portfolios.applications.environments.disabled"|translate if member['status'] == 'disabled' %}
    • {{ member['user_name'] }}{{ status }}
    • @@ -79,7 +86,11 @@
      • -
        + {{ edit_form.csrf_token }} {{ TextInput(edit_form.name, validation='defaultStringField', optional=False) }}
        @@ -89,7 +100,12 @@ ) }}
        - +
      • diff --git a/translations.yaml b/translations.yaml index c6d3dbbc..27e5805d 100644 --- a/translations.yaml +++ b/translations.yaml @@ -46,6 +46,7 @@ common: delete_confirm: "Please type the word {word} to confirm:" dod_id: DoD ID disable: Disable + edit: Edit email: Email lorem: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. name: Name @@ -409,9 +410,13 @@ portfolios: create_new_env_info: Creating an environment gives you access to the Cloud Service Provider. This environment will function within the constraints of the task order, and any costs will be billed against the portfolio. csp_link: Cloud Service Provider Link enter_env_name: "Enter environment name:" + environments: + add_subscription: Add new subscription + blank_slate: This Application has no environments + disabled: ": Access Suspended" environments_heading: Application Environments existing_application_title: "{application_name} Application Settings" - member_count: "{count} members" + member_count: "{count} Members" new_application_title: New Application settings: name_description: Application name and description From 59327d4ceafd40d1d52dc361ab21cf662d8e8e44 Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Thu, 23 Jan 2020 15:10:24 -0500 Subject: [PATCH 5/6] Styling for environment edit form --- styles/components/_accordion_table.scss | 2 +- styles/components/_portfolio_layout.scss | 9 ++++- .../applications/fragments/environments.html | 40 ++++++++----------- 3 files changed, 26 insertions(+), 25 deletions(-) diff --git a/styles/components/_accordion_table.scss b/styles/components/_accordion_table.scss index 9a32c009..67e6fb0a 100644 --- a/styles/components/_accordion_table.scss +++ b/styles/components/_accordion_table.scss @@ -106,7 +106,7 @@ &__expanded { font-size: $small-font-size; font-weight: $font-normal; - background-color: $color-gray-lightest; + background-color: $color-offwhite; padding: $gap; &:last-child { diff --git a/styles/components/_portfolio_layout.scss b/styles/components/_portfolio_layout.scss index 81a15b69..d01c1427 100644 --- a/styles/components/_portfolio_layout.scss +++ b/styles/components/_portfolio_layout.scss @@ -222,14 +222,21 @@ span.accordion-table__item__toggler { font-weight: $font-normal; + text-decoration: underline; + font-size: $small-font-size; &.environment-list__item__members { float: unset; - font-size: $small-font-size; + margin-left: -$gap; } } } + li.environment-list__edit { + border: 1px solid $color-gray-lighter; + padding: 0 $gap * 3 $gap * 2; + } + .activity-log { border-top: 3px solid $color-blue; diff --git a/templates/applications/fragments/environments.html b/templates/applications/fragments/environments.html index 51cad0fc..fa4e5959 100644 --- a/templates/applications/fragments/environments.html +++ b/templates/applications/fragments/environments.html @@ -31,8 +31,23 @@
        - {{ env['name'] }} + + {{ env['name'] }} {{ Icon('link', classes='icon--medium icon--primary') }} + + {% if user_can(permissions.EDIT_ENVIRONMENT) -%} + {{ + ToggleButton( + open_html="common.edit"|translate, + close_html="common.close"|translate, + section_name="edit" + ) + }} + {%- endif %} +
        {% set members_button = "portfolios.applications.member_count" | translate({'count': env['member_count']}) %} {{ ToggleButton( @@ -42,30 +57,9 @@ classes="environment-list__item__members" ) }} - {% if user_can(permissions.EDIT_ENVIRONMENT) -%} - {% set edit_environment_button = "common.edit"|translate %} - {{ - ToggleButton( - open_html=edit_environment_button, - close_html=edit_environment_button, - section_name="edit" - ) - }} - {%- endif %}
        {% if env['pending'] -%} {{ Label(type="changes_pending", classes='label--below')}} - {% else %} - - - {{ "portfolios.applications.csp_link" | translate }} - {{ Icon('link', classes="icon--tiny") }} - - {%- endif %}
        @@ -84,7 +78,7 @@ {% if user_can(permissions.EDIT_ENVIRONMENT) -%} {% call ToggleSection(section_name="edit") %}
          -
        • +
        • Date: Thu, 23 Jan 2020 15:18:36 -0500 Subject: [PATCH 6/6] Add success flash message --- atst/routes/applications/settings.py | 2 +- atst/utils/flash.py | 5 +++++ translations.yaml | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/atst/routes/applications/settings.py b/atst/routes/applications/settings.py index 137af0d7..b4e75fc1 100644 --- a/atst/routes/applications/settings.py +++ b/atst/routes/applications/settings.py @@ -528,13 +528,13 @@ def resend_invite(application_id, application_role_id): @applications_bp.route( "/environments//add_subscription", methods=["POST"] ) -# TODO: decide what perms are needed to create a subscription @user_can(Permissions.EDIT_ENVIRONMENT, message="create new environment subscription") def create_subscription(environment_id): environment = Environments.get(environment_id) try: app.csp.cloud.create_subscription(environment) + flash("environment_subscription_success", name=environment.displayname) except GeneralCSPException: flash("environment_subscription_failure") diff --git a/atst/utils/flash.py b/atst/utils/flash.py index 8caf7ed8..ea85f1ef 100644 --- a/atst/utils/flash.py +++ b/atst/utils/flash.py @@ -88,6 +88,11 @@ MESSAGES = { "message": "flash.environment.subscription_failure.message", "category": "error", }, + "environment_subscription_success": { + "title": "flash.environment.subscription_success.title", + "message": "flash.environment.subscription_success.message", + "category": "success", + }, "form_errors": { "title": "flash.form.errors.title", "message": "flash.form.errors.message", diff --git a/translations.yaml b/translations.yaml index 27e5805d..d9615c4d 100644 --- a/translations.yaml +++ b/translations.yaml @@ -131,6 +131,9 @@ flash: subscription_failure: title: Environment subscription error message: An unexpected problem occurred with your request, please try again. If the problem persists, contact an administrator. + subscription_success: + title: Success! + message: "A subscription has been added to {name} environment" form: errors: title: There were some errors