Merge pull request #1098 from dod-ccpo/add-members-to-app-provisioning-flow

Add members to app provisioning flow
This commit is contained in:
graham-dds 2019-10-01 16:39:07 -04:00 committed by GitHub
commit 3aa5895c93
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 377 additions and 201 deletions

View File

@ -3,7 +3,7 @@
"files": "^.secrets.baseline$", "files": "^.secrets.baseline$",
"lines": null "lines": null
}, },
"generated_at": "2019-09-26T13:53:31Z", "generated_at": "2019-09-30T13:51:34Z",
"plugins_used": [ "plugins_used": [
{ {
"base64_limit": 4.5, "base64_limit": 4.5,
@ -199,5 +199,5 @@
} }
] ]
}, },
"version": "0.12.6" "version": "0.12.5"
} }

View File

@ -7,6 +7,11 @@ from atst.forms.application import NameAndDescriptionForm, EnvironmentsForm
from atst.domain.authz.decorator import user_can_access_decorator as user_can from atst.domain.authz.decorator import user_can_access_decorator as user_can
from atst.models.permissions import Permissions from atst.models.permissions import Permissions
from atst.utils.flash import formatted_flash as flash from atst.utils.flash import formatted_flash as flash
from atst.routes.applications.settings import (
get_members_data,
get_new_member_form,
handle_create_member,
)
def get_new_application_form(form_data, form_class, application_id=None): def get_new_application_form(form_data, form_class, application_id=None):
@ -24,6 +29,7 @@ def render_new_application_form(
if application_id: if application_id:
application = Applications.get(application_id) application = Applications.get(application_id)
render_args["form"] = form or form_class(obj=application) render_args["form"] = form or form_class(obj=application)
render_args["application"] = application
else: else:
render_args["form"] = form or form_class() render_args["form"] = form or form_class()
@ -93,12 +99,19 @@ def create_or_update_new_application_step_1(portfolio_id, application_id=None):
) )
@user_can(Permissions.CREATE_APPLICATION, message="view create new application form") @user_can(Permissions.CREATE_APPLICATION, message="view create new application form")
def view_new_application_step_2(portfolio_id, application_id): def view_new_application_step_2(portfolio_id, application_id):
return render_new_application_form( application = Applications.get(application_id)
"applications/new/step_2.html", render_args = {
EnvironmentsForm, "form": EnvironmentsForm(
portfolio_id=portfolio_id, data={
application_id=application_id, "environment_names": [
) environment.name for environment in application.environments
]
}
),
"application": application,
}
return render_template("applications/new/step_2.html", **render_args)
@applications_bp.route( @applications_bp.route(
@ -112,11 +125,11 @@ def update_new_application_step_2(portfolio_id, application_id):
if form.validate(): if form.validate():
application = Applications.get(application_id) application = Applications.get(application_id)
application = Applications.update(application, form.data) application = Applications.update(application, form.data)
flash("application_created", application_name=application.name) flash("application_environments_updated")
return redirect( return redirect(
url_for( url_for(
"applications.portfolio_applications", "applications.update_new_application_step_3",
portfolio_id=application.portfolio_id, application_id=application_id,
) )
) )
else: else:
@ -130,3 +143,32 @@ def update_new_application_step_2(portfolio_id, application_id):
), ),
400, 400,
) )
@applications_bp.route("/applications/<application_id>/step_3")
@user_can(Permissions.CREATE_APPLICATION, message="view create new application form")
def view_new_application_step_3(application_id):
application = Applications.get(application_id)
members = get_members_data(application)
new_member_form = get_new_member_form(application)
return render_template(
"applications/new/step_3.html",
application_id=application_id,
application=application,
members=members,
new_member_form=new_member_form,
)
@applications_bp.route("/applications/<application_id>/step_3", methods=["POST"])
@user_can(Permissions.CREATE_APPLICATION, message="view create new application form")
def update_new_application_step_3(application_id):
handle_create_member(application_id, http_request.form)
return redirect(
url_for(
"applications.view_new_application_step_3", application_id=application_id
)
)

View File

@ -73,8 +73,9 @@ def filter_env_roles_form_data(member, environments):
"environment_name": env.name, "environment_name": env.name,
"role": NO_ACCESS, "role": NO_ACCESS,
} }
env_role = EnvironmentRoles.get_by_user_and_environment(member.user_id, env.id) env_roles_set = set(env.roles).intersection(set(member.environment_roles))
if env_role: if len(env_roles_set) == 1:
(env_role,) = env_roles_set
env_data["role"] = env_role.role env_data["role"] = env_role.role
env_roles_form_data.append(env_data) env_roles_form_data.append(env_data)
@ -153,6 +154,41 @@ def send_application_invitation(invitee_email, inviter_name, token):
) )
def handle_create_member(application_id, form_data):
application = Applications.get(application_id)
form = NewMemberForm(form_data)
if form.validate():
try:
invite = Applications.invite(
application=application,
inviter=g.current_user,
user_data=form.user_data.data,
permission_sets_names=form.data["permission_sets"],
environment_roles_data=form.environment_roles.data,
)
send_application_invitation(
invitee_email=invite.email,
inviter_name=g.current_user.full_name,
token=invite.token,
)
flash(
"new_application_member",
user_name=invite.user_name,
application_name=application.name,
)
except AlreadyExistsError:
return render_template(
"error.html", message="There was an error processing your request."
)
else:
pass
# TODO: flash error message
@applications_bp.route("/applications/<application_id>/settings") @applications_bp.route("/applications/<application_id>/settings")
@user_can(Permissions.VIEW_APPLICATION, message="view application edit form") @user_can(Permissions.VIEW_APPLICATION, message="view application edit form")
def settings(application_id): def settings(application_id):
@ -283,39 +319,7 @@ def delete_environment(environment_id):
Permissions.CREATE_APPLICATION_MEMBER, message="create new application member" Permissions.CREATE_APPLICATION_MEMBER, message="create new application member"
) )
def create_member(application_id): def create_member(application_id):
application = Applications.get(application_id) handle_create_member(application_id, http_request.form)
form = NewMemberForm(http_request.form)
if form.validate():
try:
invite = Applications.invite(
application=application,
inviter=g.current_user,
user_data=form.user_data.data,
permission_sets_names=form.data["permission_sets"],
environment_roles_data=form.environment_roles.data,
)
send_application_invitation(
invitee_email=invite.email,
inviter_name=g.current_user.full_name,
token=invite.token,
)
flash(
"new_application_member",
user_name=invite.user_name,
application_name=application.name,
)
except AlreadyExistsError:
return render_template(
"error.html", message="There was an error processing your request."
)
else:
pass
# TODO: flash error message
return redirect( return redirect(
url_for( url_for(
"applications.settings", "applications.settings",

View File

@ -12,13 +12,15 @@
{% set action = url_for('applications.create_new_application_step_1', portfolio_id=portfolio.id, application_id=application_id) %} {% set action = url_for('applications.create_new_application_step_1', portfolio_id=portfolio.id, application_id=application_id) %}
{% endif %} {% endif %}
{% block portfolio_header %}
{% include "portfolios/header.html" %}
{{ StickyCTA(text="Name and Describe New Application") }}
{% endblock %}
{% block application_content %} {% block application_content %}
{% include "fragments/flash.html" %} {% include "fragments/flash.html" %}
<div class='subheading'>{{ 'portfolios.applications.settings_heading' | translate }}</div>
<application-name-and-description inline-template v-bind:initial-data='{{ form.data|tojson }}'> <application-name-and-description inline-template v-bind:initial-data='{{ form.data|tojson }}'>
<form method="POST" action="{{ action }}" v-on:submit="handleSubmit"> <form method="POST" action="{{ action }}" v-on:submit="handleSubmit">
<div class="panel"> <div class="panel">
@ -42,7 +44,7 @@
<span class="action-group"> <span class="action-group">
{% block next_button %} {% block next_button %}
{{ SaveButton(text=('portfolios.applications.next_button_text' | translate)) }} {{ SaveButton(text=('portfolios.applications.new.step_1_button_text' | translate)) }}
{% endblock %} {% endblock %}
</span> </span>
</form> </form>

View File

@ -7,14 +7,17 @@
{% set secondary_breadcrumb = 'portfolios.applications.new_application_title' | translate %} {% set secondary_breadcrumb = 'portfolios.applications.new_application_title' | translate %}
{% block portfolio_header %}
{{ StickyCTA(text=application.name) }}
{% endblock %}
{% block application_content %} {% block application_content %}
{% set modalName = "newApplicationConfirmation" %} {% set modalName = "newApplicationConfirmation" %}
{% include "fragments/flash.html" %} {% include "fragments/flash.html" %}
<div class='subheading'>{{ 'portfolios.applications.settings_heading' | translate }}</div>
<application-environments inline-template v-bind:initial-data='{{ form.data|tojson }}'> <application-environments inline-template v-bind:initial-data='{{ form.data|tojson }}'>
<form method="POST" action="{{ url_for('applications.view_new_application_step_2', portfolio_id=portfolio.id, application_id=application_id) }}" v-on:submit="handleSubmit"> <form method="POST" action="{{ url_for('applications.update_new_application_step_2', portfolio_id=portfolio.id, application_id=application.id) }}" v-on:submit="handleSubmit">
<div class="panel"> <div class="panel">
<div class="panel__content"> <div class="panel__content">
{{ form.csrf_token }} {{ form.csrf_token }}
@ -63,7 +66,7 @@
<span class="action-group"> <span class="action-group">
{% block next_button %} {% block next_button %}
{{ SaveButton(text=('portfolios.applications.create_button_text' | translate)) }} {{ SaveButton(text=('portfolios.applications.new.step_2_button_text' | translate)) }}
{% endblock %} {% endblock %}
</span> </span>
</form> </form>

View File

@ -0,0 +1,26 @@
{% extends "applications/base.html" %}
{% from "fragments/members.html" import MemberManagementTemplate %}
{% set secondary_breadcrumb = 'portfolios.applications.new_application_title' | translate %}
{% block portfolio_header %}
{% include "portfolios/header.html" %}
{{ StickyCTA(text=application.name) }}
{% endblock %}
{% block application_content %}
{{ MemberManagementTemplate(
application,
members,
new_member_form,
"applications.update_new_application_step_3",
user_can(permissions.CREATE_APPLICATION_MEMBER)) }}
<span class="action-group">
<a class="usa-button" href="{{ url_for('applications.settings', application_id=application_id) }}">
Return to Application Settings
</a>
</span>
{% endblock %}

View File

@ -5,6 +5,7 @@
{% from "components/icon.html" import Icon %} {% from "components/icon.html" import Icon %}
{% import "applications/fragments/new_member_modal_content.html" as member_steps %} {% import "applications/fragments/new_member_modal_content.html" as member_steps %}
{% from "applications/fragments/member_perms_form_fields.html" import MemberPermsFields %} {% from "applications/fragments/member_perms_form_fields.html" import MemberPermsFields %}
{% from "fragments/members.html" import MemberManagementTemplate %}
{% from "components/modal.html" import Modal %} {% from "components/modal.html" import Modal %}
{% from "components/multi_step_modal_form.html" import MultiStepModalForm %} {% from "components/multi_step_modal_form.html" import MultiStepModalForm %}
{% from "components/pagination.html" import Pagination %} {% from "components/pagination.html" import Pagination %}
@ -73,150 +74,12 @@
</div> </div>
{% endif %} {% endif %}
{% if not application.members %} {{ MemberManagementTemplate(
{% set user_can_invite = user_can(permissions.CREATE_APPLICATION_MEMBER) %} application,
members,
<div class='empty-state'> new_member_form,
<p class='empty-state__message'>{{ ("portfolios.applications.team_settings.blank_slate.title" | translate) }}</p> "applications.settings",
user_can(permissions.CREATE_APPLICATION_MEMBER)) }}
{{ Icon('avatar') }}
{% if not user_can_invite %}
<p class='empty-state__sub-message'>{{ ("portfolios.applications.team_settings.blank_slate.sub_message" | translate) }}</p>
{% endif %}
{% if user_can_invite %}
{% set new_member_modal_name = "add-app-mem" %}
<a class="usa-button usa-button-big" v-on:click="openModal('{{ new_member_modal_name }}')">
{{ "portfolios.applications.team_settings.blank_slate.action_label" | translate }}
</a>
{{ MultiStepModalForm(
name=new_member_modal_name,
form=new_member_form,
form_action=url_for("applications.create_member", application_id=application.id),
steps=[
member_steps.MemberStepOne(new_member_form),
member_steps.MemberStepTwo(new_member_form, application)
],
) }}
{% endif %}
</div>
{% else %}
<div class='subheading'>
{{ 'portfolios.applications.settings.team_members' | translate }}
{% set new_member_modal_name = "add-app-mem" %}
{% if user_can(permissions.CREATE_APPLICATION_MEMBER) %}
<a class="icon-link modal-link icon-link__add" v-on:click="openModal('{{ new_member_modal_name }}')">
{{ Icon("plus") }}
{{ "portfolios.applications.add_member" | translate }}
</a>
{% endif %}
</div>
<section class="member-list application-list" id="application-members">
<div class='responsive-table-wrapper panel'>
{% if g.matchesPath("application-members") %}
{% include "fragments/flash.html" %}
{% endif %}
{% for member in members %}
{% set modal_name = "edit_member-{}".format(loop.index) %}
{% call Modal(modal_name) %}
<div class="modal__form--header">
<h1>{{ Icon('avatar') }} {{ member.user_name }}</h1>
<hr>
</div>
<base-form inline-template>
<form id='{{ modal_name }}' method="POST" action="{{ url_for('applications.update_member', application_id=application.id, application_role_id=member.role_id) }}">
{{ member.form.csrf_token }}
{{ MemberPermsFields(form=member.form, member_role_id=member.role_id) }}
<div class="action-group">
{{ SaveButton(text='Update', element='input', additional_classes='action-group__action') }}
<a class='action-group__action usa-button usa-button-secondary' v-on:click="closeModal('{{ modal_name }}')">{{ "common.cancel" | translate }}</a>
</div>
</form>
</base-form>
{% endcall %}
{% if user_can(permissions.DELETE_APPLICATION_MEMBER) and member.role_status == 'pending' %}
{% set revoke_invite_modal = "revoke_invite_{}".format(member.role_id) %}
{% call Modal(name=revoke_invite_modal, dismissable=True) %}
<div class="task-order__modal-cancel">
<form method="post" action="{{ url_for('applications.revoke_invite', application_id=application.id, application_role_id=member.role_id) }}">
{{ member.form.csrf_token }}
<h1>{{ "invites.revoke.modal_heading" | translate({'user_name': member.user_name}) }}</h1>
<div class="task-order__modal-cancel_buttons">
<button class="usa-button usa-button-primary" type="submit">{{ "invites.revoke.submit" | translate }}</button>
<button type='button' v-on:click='closeModal("{{revoke_invite_modal}}")' class="usa-button usa-button-primary">{{ "invites.revoke.cancel" | translate }}</button>
</div>
</form>
</div>
{% endcall %}
{% endif %}
{% endfor %}
<table>
<thead>
<tr>
<th>Member</th>
<th>Project Permissions</th>
<th>Environment Access</th>
<th></th>
</tr>
</thead>
<tbody>
{% for member in members %}
{% set modal_name = "edit_member-{}".format(loop.index) %}
<tr>
<td>
{{ member.user_name }}
<a class="icon-link" v-on:click="openModal('{{ modal_name }}')">
{{ Icon('edit') }}
</a>
<br>
{% if member.role_status == 'pending' %}
<span class='label label--purple'>INVITE PENDING</span>
{% endif %}
</td>
<td>
{% for perm, value in member.permission_sets.items() %}
{{ ("portfolios.applications.members.{}.{}".format(perm, value)) | translate }}<br>
{% endfor %}
</td>
<td>
{% for env in member.environment_roles %}
{{ env.environment_name }}{% if not env == member.environment_roles[-1]%},{% endif %}
{% endfor %}
</td>
<td>
{% if user_can(permissions.DELETE_APPLICATION_MEMBER) and member.role_status == 'pending' %}
{% set revoke_invite_modal = "revoke_invite_{}".format(member.role_id) %}
<a href="#">Resend Invite</a><br>
<a v-on:click='openModal("{{ revoke_invite_modal }}")'>{{ 'invites.revoke.button' | translate }}</a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% if user_can(permissions.CREATE_APPLICATION_MEMBER) %}
{% import "applications/fragments/new_member_modal_content.html" as member_steps %}
{{ MultiStepModalForm(
name=new_member_modal_name,
form=new_member_form,
form_action=url_for("applications.create_member", application_id=application.id),
steps=[
member_steps.MemberStepOne(new_member_form),
member_steps.MemberStepTwo(new_member_form, application)
],
) }}
{% endif %}
</section>
{% endif %}
<div class='subheading'> <div class='subheading'>
{{ 'common.resource_names.environments' | translate }} {{ 'common.resource_names.environments' | translate }}

View File

@ -0,0 +1,143 @@
{% from "components/alert.html" import Alert %}
{% from "components/icon.html" import Icon %}
{% import "applications/fragments/new_member_modal_content.html" as member_steps %}
{% from "applications/fragments/member_perms_form_fields.html" import MemberPermsFields %}
{% from "components/modal.html" import Modal %}
{% from "components/multi_step_modal_form.html" import MultiStepModalForm %}
{% from "components/save_button.html" import SaveButton %}
{% macro MemberManagementTemplate(
application,
members,
new_member_form,
action,
user_can_create_app_member=False
) %}
{% include "fragments/flash.html" %}
{% if not application.members %}
<div class='empty-state'>
<p class='empty-state__message'>{{ ("portfolios.applications.team_settings.blank_slate.title" | translate) }}</p>
{{ Icon('avatar') }}
{% if not user_can_create_app_member %}
<p class='empty-state__sub-message'>{{ ("portfolios.applications.team_settings.blank_slate.sub_message" | translate) }}</p>
{% endif %}
{% if user_can_create_app_member %}
{% set new_member_modal_name = "add-app-mem" %}
<a class="usa-button usa-button-big" v-on:click="openModal('{{ new_member_modal_name }}')">
{{ "portfolios.applications.team_settings.blank_slate.action_label" | translate }}
</a>
{{ MultiStepModalForm(
name=new_member_modal_name,
form=new_member_form,
form_action=url_for(action, application_id=application.id),
steps=[
member_steps.MemberStepOne(new_member_form),
member_steps.MemberStepTwo(new_member_form, application)
],
) }}
{% endif %}
</div>
{% else %}
<div class='subheading'>
{{ 'portfolios.applications.settings.team_members' | translate }}
{% set new_member_modal_name = "add-app-mem" %}
{% if user_can_create_app_member %}
<a class="icon-link modal-link icon-link__add" v-on:click="openModal('{{ new_member_modal_name }}')">
{{ Icon("plus") }}
{{ "portfolios.applications.add_member" | translate }}
</a>
{% endif %}
</div>
<section class="member-list application-list" id="application-members">
<div class='responsive-table-wrapper panel'>
{% for member in members %}
{% set modal_name = "edit_member-{}".format(loop.index) %}
{% call Modal(modal_name) %}
<div class="modal__form--header">
<h1>{{ Icon('avatar') }} {{ member.user_name }}</h1>
<hr>
</div>
<base-form inline-template>
<form id='{{ modal_name }}' method="POST" action="{{ url_for('applications.update_member', application_id=application.id, application_role_id=member.role_id) }}">
{{ member.form.csrf_token }}
{{ MemberPermsFields(form=member.form, member_role_id=member.role_id) }}
<div class="action-group">
{{ SaveButton(text='Update', element='input', additional_classes='action-group__action') }}
<a class='action-group__action usa-button usa-button-secondary' v-on:click="closeModal('{{ modal_name }}')">{{ "common.cancel" | translate }}</a>
</div>
</form>
</base-form>
{% endcall %}
{% endfor %}
<table>
<thead>
<tr>
<th>Member</th>
<th>Project Permissions</th>
<th>Environment Access</th>
<th></th>
</tr>
</thead>
<tbody>
{% for member in members %}
{% set modal_name = "edit_member-{}".format(loop.index) %}
<tr>
<td>
{{ member.user_name }}
<a class="icon-link" v-on:click="openModal('{{ modal_name }}')">
{{ Icon('edit') }}
</a>
<br>
{% if member.role_status == 'pending' %}
<span class='label label--purple'>INVITE PENDING</span>
{% endif %}
</td>
<td>
{% for perm, value in member.permission_sets.items() %}
{{ ("portfolios.applications.members.{}.{}".format(perm, value)) | translate }}<br>
{% endfor %}
</td>
<td>
{% for env in member.environment_roles %}
{{ env.environment_name }}{% if not env == member.environment_roles[-1]%},{% endif %}
{% endfor %}
</td>
<td>
{% if member.role_status == 'pending' %}
<a href="#">Resend Invite</a><br>
<a href="#">Revoke Invite</a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% if user_can_create_app_member %}
{% import "applications/fragments/new_member_modal_content.html" as member_steps %}
{{ MultiStepModalForm(
name=new_member_modal_name,
form=new_member_form,
form_action=url_for(action, application_id=application.id),
steps=[
member_steps.MemberStepOne(new_member_form),
member_steps.MemberStepTwo(new_member_form, application)
],
) }}
{% endif %}
</section>
{% endif %}
{% endmacro %}

View File

@ -1,7 +1,9 @@
from flask import url_for from flask import url_for
from tests.factories import PortfolioFactory, ApplicationFactory from tests.factories import PortfolioFactory, ApplicationFactory, UserFactory
from atst.domain.applications import Applications from unittest.mock import Mock
from atst.forms.data import ENV_ROLE_NO_ACCESS as NO_ACCESS
from atst.models.application_invitation import ApplicationInvitation
def test_get_name_and_description_form(client, user_session): def test_get_name_and_description_form(client, user_session):
@ -94,3 +96,66 @@ def test_post_environments(client, session, user_session):
assert response.status_code == 302 assert response.status_code == 302
session.refresh(application) session.refresh(application)
assert len(application.environments) == 3 assert len(application.environments) == 3
def test_get_members(client, session, user_session):
application = ApplicationFactory.create()
user_session(application.portfolio.owner)
response = client.get(
url_for(
"applications.view_new_application_step_3", application_id=application.id
)
)
assert response.status_code == 200
def test_post_member(monkeypatch, client, user_session, session):
job_mock = Mock()
monkeypatch.setattr("atst.jobs.send_mail.delay", job_mock)
user = UserFactory.create()
application = ApplicationFactory.create(
environments=[{"name": "Naboo"}, {"name": "Endor"}]
)
(env, env_1) = application.environments
user_session(application.portfolio.owner)
response = client.post(
url_for("applications.create_member", application_id=application.id),
data={
"user_data-first_name": user.first_name,
"user_data-last_name": user.last_name,
"user_data-dod_id": user.dod_id,
"user_data-email": user.email,
"environment_roles-0-environment_id": env.id,
"environment_roles-0-role": "Basic Access",
"environment_roles-0-environment_name": env.name,
"environment_roles-1-environment_id": env_1.id,
"environment_roles-1-role": NO_ACCESS,
"environment_roles-1-environment_name": env_1.name,
"perms_env_mgmt": True,
"perms_team_mgmt": True,
"perms_del_env": True,
},
)
assert response.status_code == 302
expected_url = url_for(
"applications.settings",
application_id=application.id,
fragment="application-members",
_anchor="application-members",
_external=True,
)
assert response.location == expected_url
assert len(application.roles) == 1
environment_roles = application.roles[0].environment_roles
assert len(environment_roles) == 1
assert environment_roles[0].environment == env
invitation = (
session.query(ApplicationInvitation).filter_by(dod_id=user.dod_id).one()
)
assert invitation.role.application == application
assert job_mock.called

View File

@ -21,6 +21,7 @@ from atst.models.portfolio_role import Status as PortfolioRoleStatus
from atst.forms.application import EditEnvironmentForm from atst.forms.application import EditEnvironmentForm
from atst.forms.application_member import UpdateMemberForm from atst.forms.application_member import UpdateMemberForm
from atst.forms.data import ENV_ROLE_NO_ACCESS as NO_ACCESS from atst.forms.data import ENV_ROLE_NO_ACCESS as NO_ACCESS
from atst.routes.applications.settings import filter_env_roles_form_data
from tests.utils import captured_templates from tests.utils import captured_templates
@ -559,3 +560,28 @@ def test_revoke_invite(client, user_session):
assert invite.is_revoked assert invite.is_revoked
assert app_role.status == ApplicationRoleStatus.DISABLED assert app_role.status == ApplicationRoleStatus.DISABLED
def test_filter_environment_roles():
application_role = ApplicationRoleFactory.create(user=None)
application_role2 = ApplicationRoleFactory.create(
user=None, application=application_role.application
)
application_role3 = ApplicationRoleFactory.create(
user=None, application=application_role.application
)
environment = EnvironmentFactory.create(application=application_role.application)
EnvironmentRoleFactory.create(
environment=environment, application_role=application_role
)
EnvironmentRoleFactory.create(
environment=environment, application_role=application_role2
)
environment_data = filter_env_roles_form_data(application_role, [environment])
assert environment_data[0]["role"] != "No Access"
environment_data = filter_env_roles_form_data(application_role3, [environment])
assert environment_data[0]["role"] == "No Access"

View File

@ -302,8 +302,10 @@ portfolios:
add_member: Add a New Team Member add_member: Add a New Team Member
add_another_environment: Add another environment add_another_environment: Add another environment
app_settings_text: App settings app_settings_text: App settings
create_button_text: Create new:
next_button_text: "Next: Environments" step_1_button_text: "Save and Add Environments"
step_2_button_text: "Save and Add Members"
step_3_button_text: Save Application
create_new_env: Create a new environment. create_new_env: Create a new environment.
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. 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_console_text: CSP console csp_console_text: CSP console