Merge pull request #1060 from dod-ccpo/app-settings-redesign
App settings redesign
This commit is contained in:
commit
dcb70ad925
@ -5,7 +5,6 @@ applications_bp = Blueprint("applications", __name__)
|
||||
from . import index
|
||||
from . import new
|
||||
from . import settings
|
||||
from . import team
|
||||
from . import invitations
|
||||
from atst.domain.environment_roles import EnvironmentRoles
|
||||
from atst.domain.exceptions import UnauthorizedError
|
||||
|
@ -1,17 +1,24 @@
|
||||
from flask import redirect, render_template, request as http_request, url_for
|
||||
from flask import redirect, render_template, request as http_request, url_for, g
|
||||
|
||||
from . import applications_bp
|
||||
from atst.domain.exceptions import AlreadyExistsError
|
||||
from atst.domain.environments import Environments
|
||||
from atst.domain.applications import Applications
|
||||
from atst.domain.application_roles import ApplicationRoles
|
||||
from atst.domain.audit_log import AuditLog
|
||||
from atst.domain.common import Paginator
|
||||
from atst.domain.environment_roles import EnvironmentRoles
|
||||
from atst.forms.app_settings import AppEnvRolesForm
|
||||
from atst.forms.application import ApplicationForm, EditEnvironmentForm
|
||||
from atst.forms.application_member import NewForm as NewMemberForm
|
||||
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.models.permissions import Permissions
|
||||
from atst.domain.permission_sets import PermissionSets
|
||||
from atst.utils.flash import formatted_flash as flash
|
||||
from atst.utils.localization import translate
|
||||
from atst.jobs import send_mail
|
||||
|
||||
|
||||
def get_environments_obj_for_app(application):
|
||||
@ -79,12 +86,65 @@ def data_for_app_env_roles_form(application):
|
||||
return {"envs": nested_data}
|
||||
|
||||
|
||||
def get_form_permission_value(member, edit_perm_set):
|
||||
if member.has_permission_set(edit_perm_set):
|
||||
return edit_perm_set
|
||||
else:
|
||||
return PermissionSets.VIEW_APPLICATION
|
||||
|
||||
|
||||
def get_members_data(application):
|
||||
members_data = []
|
||||
for member in application.members:
|
||||
permission_sets = {
|
||||
"perms_team_mgmt": get_form_permission_value(
|
||||
member, PermissionSets.EDIT_APPLICATION_TEAM
|
||||
),
|
||||
"perms_env_mgmt": get_form_permission_value(
|
||||
member, PermissionSets.EDIT_APPLICATION_ENVIRONMENTS
|
||||
),
|
||||
"perms_del_env": get_form_permission_value(
|
||||
member, PermissionSets.DELETE_APPLICATION_ENVIRONMENTS
|
||||
),
|
||||
}
|
||||
roles = EnvironmentRoles.get_for_application_member(member.id)
|
||||
environment_roles = [
|
||||
{
|
||||
"environment_id": str(role.environment.id),
|
||||
"environment_name": role.environment.name,
|
||||
"role": role.role,
|
||||
}
|
||||
for role in roles
|
||||
]
|
||||
members_data.append(
|
||||
{
|
||||
"role_id": member.id,
|
||||
"user_name": member.user_name,
|
||||
"permission_sets": permission_sets,
|
||||
"environment_roles": environment_roles,
|
||||
}
|
||||
)
|
||||
|
||||
return members_data
|
||||
|
||||
|
||||
def get_new_member_form(application):
|
||||
env_roles = [
|
||||
{"environment_id": e.id, "environment_name": e.name}
|
||||
for e in application.environments
|
||||
]
|
||||
|
||||
return NewMemberForm(data={"environment_roles": env_roles})
|
||||
|
||||
|
||||
def render_settings_page(application, **kwargs):
|
||||
environments_obj = get_environments_obj_for_app(application=application)
|
||||
members_form = AppEnvRolesForm(data=data_for_app_env_roles_form(application))
|
||||
new_env_form = EditEnvironmentForm()
|
||||
pagination_opts = Paginator.get_pagination_opts(http_request)
|
||||
audit_events = AuditLog.get_application_events(application, pagination_opts)
|
||||
new_member_form = get_new_member_form(application)
|
||||
members = get_members_data(application)
|
||||
|
||||
if "application_form" not in kwargs:
|
||||
kwargs["application_form"] = ApplicationForm(
|
||||
@ -98,10 +158,23 @@ def render_settings_page(application, **kwargs):
|
||||
members_form=members_form,
|
||||
new_env_form=new_env_form,
|
||||
audit_events=audit_events,
|
||||
new_member_form=new_member_form,
|
||||
members=members,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
def send_application_invitation(invitee_email, inviter_name, token):
|
||||
body = render_template(
|
||||
"emails/application/invitation.txt", owner=inviter_name, token=token
|
||||
)
|
||||
send_mail.delay(
|
||||
[invitee_email],
|
||||
translate("email.application_invite", {"inviter_name": inviter_name}),
|
||||
body,
|
||||
)
|
||||
|
||||
|
||||
@applications_bp.route("/applications/<application_id>/settings")
|
||||
@user_can(Permissions.VIEW_APPLICATION, message="view application edit form")
|
||||
def settings(application_id):
|
||||
@ -264,3 +337,72 @@ def delete_environment(environment_id):
|
||||
fragment="application-environments",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@applications_bp.route("/application/<application_id>/members/new", methods=["POST"])
|
||||
@user_can(
|
||||
Permissions.CREATE_APPLICATION_MEMBER, message="create new application member"
|
||||
)
|
||||
def create_member(application_id):
|
||||
application = Applications.get(application_id)
|
||||
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.permission_sets.data,
|
||||
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)
|
||||
|
||||
except AlreadyExistsError:
|
||||
return render_template(
|
||||
"error.html", message="There was an error processing your request."
|
||||
)
|
||||
else:
|
||||
pass
|
||||
# TODO: flash error message
|
||||
|
||||
return redirect(
|
||||
url_for(
|
||||
"applications.settings",
|
||||
application_id=application_id,
|
||||
fragment="application-members",
|
||||
_anchor="application-members",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@applications_bp.route(
|
||||
"/applications/<application_id>/members/<application_role_id>/delete",
|
||||
methods=["POST"],
|
||||
)
|
||||
@user_can(Permissions.DELETE_APPLICATION_MEMBER, message="remove application member")
|
||||
def remove_member(application_id, application_role_id):
|
||||
application_role = ApplicationRoles.get_by_id(application_role_id)
|
||||
Applications.remove_member(application_role)
|
||||
|
||||
flash(
|
||||
"application_member_removed",
|
||||
user_name=application_role.user_name,
|
||||
application_name=g.application.name,
|
||||
)
|
||||
|
||||
return redirect(
|
||||
url_for(
|
||||
"applications.settings",
|
||||
_anchor="application-members",
|
||||
application_id=g.application.id,
|
||||
fragment="application-members",
|
||||
)
|
||||
)
|
||||
|
@ -1,205 +0,0 @@
|
||||
from flask import render_template, request as http_request, g, url_for, redirect
|
||||
|
||||
|
||||
from . import applications_bp
|
||||
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.forms.application_member import NewForm as NewMemberForm
|
||||
from atst.forms.team import TeamForm
|
||||
from atst.models import Permissions
|
||||
from atst.utils.flash import formatted_flash as flash
|
||||
from atst.utils.localization import translate
|
||||
from atst.jobs import send_mail
|
||||
|
||||
|
||||
def get_form_permission_value(member, edit_perm_set):
|
||||
if member.has_permission_set(edit_perm_set):
|
||||
return edit_perm_set
|
||||
else:
|
||||
return PermissionSets.VIEW_APPLICATION
|
||||
|
||||
|
||||
def get_team_form(application):
|
||||
team_data = []
|
||||
for member in application.members:
|
||||
permission_sets = {
|
||||
"perms_team_mgmt": get_form_permission_value(
|
||||
member, PermissionSets.EDIT_APPLICATION_TEAM
|
||||
),
|
||||
"perms_env_mgmt": get_form_permission_value(
|
||||
member, PermissionSets.EDIT_APPLICATION_ENVIRONMENTS
|
||||
),
|
||||
"perms_del_env": get_form_permission_value(
|
||||
member, PermissionSets.DELETE_APPLICATION_ENVIRONMENTS
|
||||
),
|
||||
}
|
||||
roles = EnvironmentRoles.get_for_application_member(member.id)
|
||||
environment_roles = [
|
||||
{
|
||||
"environment_id": str(role.environment.id),
|
||||
"environment_name": role.environment.name,
|
||||
"role": role.role,
|
||||
}
|
||||
for role in roles
|
||||
]
|
||||
team_data.append(
|
||||
{
|
||||
"role_id": member.id,
|
||||
"user_name": member.user_name,
|
||||
"permission_sets": permission_sets,
|
||||
"environment_roles": environment_roles,
|
||||
}
|
||||
)
|
||||
|
||||
return TeamForm(data={"members": team_data})
|
||||
|
||||
|
||||
def get_new_member_form(application):
|
||||
env_roles = [
|
||||
{"environment_id": e.id, "environment_name": e.name}
|
||||
for e in application.environments
|
||||
]
|
||||
|
||||
return NewMemberForm(data={"environment_roles": env_roles})
|
||||
|
||||
|
||||
def render_team_page(application):
|
||||
team_form = get_team_form(application)
|
||||
new_member_form = get_new_member_form(application)
|
||||
|
||||
return render_template(
|
||||
"portfolios/applications/team.html",
|
||||
application=application,
|
||||
team_form=team_form,
|
||||
new_member_form=new_member_form,
|
||||
)
|
||||
|
||||
|
||||
@applications_bp.route("/applications/<application_id>/team")
|
||||
@user_can(Permissions.VIEW_APPLICATION, message="view portfolio applications")
|
||||
def team(application_id):
|
||||
application = Applications.get(resource_id=application_id)
|
||||
return render_team_page(application)
|
||||
|
||||
|
||||
@applications_bp.route("/application/<application_id>/team", methods=["POST"])
|
||||
@user_can(Permissions.EDIT_APPLICATION_MEMBER, message="update application member")
|
||||
def update_team(application_id):
|
||||
application = Applications.get(application_id)
|
||||
form = TeamForm(http_request.form)
|
||||
|
||||
if form.validate():
|
||||
for member_form in form.members:
|
||||
app_role = ApplicationRoles.get_by_id(member_form.role_id.data)
|
||||
new_perms = [
|
||||
perm
|
||||
for perm in member_form.data["permission_sets"]
|
||||
if perm != PermissionSets.VIEW_APPLICATION
|
||||
]
|
||||
ApplicationRoles.update_permission_sets(app_role, new_perms)
|
||||
|
||||
for environment_role_form in member_form.environment_roles:
|
||||
environment = Environments.get(
|
||||
environment_role_form.environment_id.data
|
||||
)
|
||||
Environments.update_env_role(
|
||||
environment, app_role, environment_role_form.data.get("role")
|
||||
)
|
||||
|
||||
flash("updated_application_team_settings", application_name=application.name)
|
||||
|
||||
return redirect(
|
||||
url_for(
|
||||
"applications.team",
|
||||
application_id=application_id,
|
||||
fragment="application-members",
|
||||
_anchor="application-members",
|
||||
)
|
||||
)
|
||||
else:
|
||||
return (render_team_page(application), 400)
|
||||
|
||||
|
||||
def send_application_invitation(invitee_email, inviter_name, token):
|
||||
body = render_template(
|
||||
"emails/application/invitation.txt", owner=inviter_name, token=token
|
||||
)
|
||||
send_mail.delay(
|
||||
[invitee_email],
|
||||
translate("email.application_invite", {"inviter_name": inviter_name}),
|
||||
body,
|
||||
)
|
||||
|
||||
|
||||
@applications_bp.route("/application/<application_id>/members/new", methods=["POST"])
|
||||
@user_can(
|
||||
Permissions.CREATE_APPLICATION_MEMBER, message="create new application member"
|
||||
)
|
||||
def create_member(application_id):
|
||||
application = Applications.get(application_id)
|
||||
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.permission_sets.data,
|
||||
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)
|
||||
|
||||
except AlreadyExistsError:
|
||||
return render_template(
|
||||
"error.html", message="There was an error processing your request."
|
||||
)
|
||||
else:
|
||||
pass
|
||||
# TODO: flash error message
|
||||
|
||||
return redirect(
|
||||
url_for(
|
||||
"applications.team",
|
||||
application_id=application_id,
|
||||
fragment="application-members",
|
||||
_anchor="application-members",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@applications_bp.route(
|
||||
"/applications/<application_id>/members/<application_role_id>/delete",
|
||||
methods=["POST"],
|
||||
)
|
||||
@user_can(Permissions.DELETE_APPLICATION_MEMBER, message="remove application member")
|
||||
def remove_member(application_id, application_role_id):
|
||||
application_role = ApplicationRoles.get_by_id(application_role_id)
|
||||
Applications.remove_member(application_role)
|
||||
|
||||
flash(
|
||||
"application_member_removed",
|
||||
user_name=application_role.user_name,
|
||||
application_name=g.application.name,
|
||||
)
|
||||
|
||||
return redirect(
|
||||
url_for(
|
||||
"applications.team",
|
||||
_anchor="application-members",
|
||||
application_id=g.application.id,
|
||||
fragment="application-members",
|
||||
)
|
||||
)
|
@ -139,6 +139,9 @@
|
||||
}
|
||||
|
||||
table {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
|
||||
thead {
|
||||
th:first-child {
|
||||
padding-left: 3 * $gap;
|
||||
@ -282,6 +285,13 @@
|
||||
.application-content {
|
||||
.subheading {
|
||||
@include subheading;
|
||||
position: relative;
|
||||
|
||||
.icon-link__add {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.panel {
|
||||
@ -312,6 +322,34 @@
|
||||
input#delete-application {
|
||||
margin-top: $gap * 3;
|
||||
}
|
||||
|
||||
.accordion-table__item-content.form-row {
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
li.accordion-table__item__expanded {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.environment-list__item {
|
||||
position: relative;
|
||||
height: 7rem;
|
||||
}
|
||||
|
||||
span.accordion-table__item__toggler.icon-link {
|
||||
font-size: $small-font-size;
|
||||
font-weight: $font-normal;
|
||||
position: absolute;
|
||||
left: -$gap * 1.25;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
a.application-list-item__environment__csp_link.icon-link {
|
||||
font-size: $small-font-size;
|
||||
font-weight: $font-normal;
|
||||
}
|
||||
}
|
||||
|
||||
.activity-log {
|
||||
|
@ -4,7 +4,6 @@
|
||||
z-index: 10;
|
||||
|
||||
@include media($medium-screen) {
|
||||
margin-left: -$gap * 5;
|
||||
margin-right: -$gap * 5;
|
||||
}
|
||||
|
||||
@ -53,4 +52,10 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-return-link {
|
||||
padding-top: 1.6rem;
|
||||
font-size: $small-font-size;
|
||||
font-weight: $font-bold;
|
||||
}
|
||||
}
|
||||
|
@ -58,6 +58,7 @@
|
||||
|
||||
.usa-input {
|
||||
margin: ($gap * 4) ($gap * 2) ($gap * 4) 0;
|
||||
max-width: 75rem;
|
||||
|
||||
@include media($medium-screen) {
|
||||
margin: ($gap * 4) 0;
|
||||
|
@ -60,11 +60,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.app-team-settings-link {
|
||||
font-size: $small-font-size;
|
||||
font-weight: $font-normal;
|
||||
}
|
||||
|
||||
.environment-roles {
|
||||
padding: 0 ($gap * 3) ($gap * 3);
|
||||
|
||||
|
@ -1,12 +1,23 @@
|
||||
{% macro StickyCTA(text) -%}
|
||||
{% from 'components/icon.html' import Icon %}
|
||||
|
||||
{% macro StickyCTA(text, return_link_url=None, return_link_text=None) -%}
|
||||
<div class="sticky-cta" v-sticky='{ "stickyBitStickyOffset": 76 }'>
|
||||
<div class="sticky-cta-container">
|
||||
<div class="sticky-cta-text">
|
||||
{% if return_link_url and return_link_text %}
|
||||
<div class="sticky-cta-return-link">
|
||||
<a href="{{ return_link_url }}">
|
||||
{{ Icon('caret_left', classes="icon--tiny icon--blue") }} {{ return_link_text}}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
<h3>{{ text }}</h3>
|
||||
</div>
|
||||
{% if caller %}
|
||||
<div class="sticky-cta-buttons">
|
||||
{{ caller() }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{%- endmacro %}
|
||||
|
@ -18,25 +18,17 @@
|
||||
</div>
|
||||
<div class="panel__footer">
|
||||
<div class="action-group">
|
||||
<div class='action-group-cancel'>
|
||||
<a class='action-group-cancel__action icon-link icon-link--default' v-on:click="toggle">
|
||||
{{ SaveButton(text=('common.save' | translate), element="input", form="add-new-env") }}
|
||||
<a class='action-group__action icon-link icon-link--default' v-on:click="toggle">
|
||||
{{ "common.cancel" | translate }}
|
||||
</a>
|
||||
{{ SaveButton(text=('common.save' | translate), element="input", form="add-new-env") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div v-else class="panel__footer">
|
||||
<div class="action-group">
|
||||
<a class='icon-link' v-on:click="toggle">
|
||||
{{ "portfolios.applications.add_environment" | translate }}
|
||||
<a class='icon-link icon-link__add' v-on:click="toggle">
|
||||
{{ Icon('plus') }}
|
||||
{{ "portfolios.applications.add_environment" | translate }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</new-environment>
|
||||
|
||||
|
@ -1,97 +0,0 @@
|
||||
{% from "components/icon.html" import Icon %}
|
||||
{% from "components/save_button.html" import SaveButton %}
|
||||
|
||||
|
||||
{% for env_form in members_form.envs %}
|
||||
{% if env_form.env_id.data == env['id'] %}
|
||||
<div class='app-team-settings-link'>
|
||||
{{ 'fragments.edit_environment_team_form.add_new_member_text' | translate }}
|
||||
<a href='{{ url_for("applications.team", application_id=application.id) }}'>
|
||||
{{ 'fragments.edit_environment_team_form.add_new_member_link' | translate }}
|
||||
</a>
|
||||
</div>
|
||||
<form
|
||||
action="{{ url_for('applications.update_env_roles', environment_id=env['id']) }}"
|
||||
method="post">
|
||||
{{ members_form.csrf_token }}
|
||||
{{ env_form.env_id() }}
|
||||
<edit-environment-role
|
||||
inline-template
|
||||
v-bind:initial-role-categories='{{ env_form.team_roles.data | tojson }}'>
|
||||
<div>
|
||||
<div v-for='(roleCategory, roleindex) in roleCategories' class='environment-role'>
|
||||
<h4 v-if='checkNoAccess(roleCategory.role)'>
|
||||
{{ 'fragments.edit_environment_team_form.unassigned_title' | translate }}
|
||||
</h4>
|
||||
<h4 v-else v-html='roleCategory.role'></h4>
|
||||
<ul class='environment-role__users'>
|
||||
<div
|
||||
v-if="roleCategory.members && !roleCategory.members.length"
|
||||
class='environment-role__no-user'>
|
||||
{{ 'fragments.edit_environment_team_form.no_members' | translate }}
|
||||
</div>
|
||||
<li
|
||||
v-for='(member, memberindex) in roleCategory.members'
|
||||
class="environment-role__user"
|
||||
v-bind:class="{'unassigned': checkNoAccess(member.role_name)}">
|
||||
<span v-html='member.user_name'>
|
||||
</span>
|
||||
<span v-on:click="toggleSection(member.application_role_id)" class="icon-link right">
|
||||
{{ Icon('edit', classes="icon--medium") }}
|
||||
</span>
|
||||
<div
|
||||
v-show="selectedSection === member.application_role_id"
|
||||
class='environment-role__user-field'>
|
||||
<div class="usa-input">
|
||||
<fieldset
|
||||
data-ally-disabled="true"
|
||||
class="usa-input__choices"
|
||||
v-on:change="onInput">
|
||||
<ul
|
||||
v-for='(roleCategory, roleinputindex) in roleCategories'
|
||||
v-bind:id="'envs-{{ loop.index0 }}-team_roles-' + roleindex + '-members-' + memberindex + '-role_name'">
|
||||
<li>
|
||||
<input
|
||||
v-bind:checked="member.role_name === roleCategory.role"
|
||||
v-bind:name="'envs-{{ loop.index0 }}-team_roles-' + roleindex + '-members-' + memberindex + '-role_name'"
|
||||
v-bind:id="'envs-{{ loop.index0 }}-team_roles-' + roleindex + '-members-' + memberindex + '-role_name-' + roleinputindex"
|
||||
type="radio"
|
||||
v-bind:user-id='member.application_role_id'
|
||||
v-bind:value='roleCategory.role'>
|
||||
<label
|
||||
v-bind:for="'envs-{{ loop.index0 }}-team_roles-' + roleindex + '-members-' + memberindex + '-role_name-' + roleinputindex">
|
||||
<span v-if='checkNoAccess(roleCategory.role)'>
|
||||
{{ 'fragments.edit_environment_team_form.no_access' | translate }}
|
||||
</span>
|
||||
<span v-else v-html='roleCategory.role'></span>
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
v-bind:id="'envs-{{ loop.index0 }}-team_roles-' + roleindex + '-members-' + memberindex + '-application_role_id'"
|
||||
v-bind:name="'envs-{{ loop.index0 }}-team_roles-' + roleindex + '-members-' + memberindex + '-application_role_id'"
|
||||
type="hidden"
|
||||
v-bind:value='member.application_role_id'>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class='action-group'>
|
||||
{{
|
||||
SaveButton(
|
||||
text=("common.save" | translate)
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</edit-environment-role>
|
||||
<div class='action-group-cancel'>
|
||||
<a class='action-group-cancel__action icon-link icon-link--default' v-on:click="toggleSection('members')">
|
||||
{{ "common.cancel" | translate }}
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endfor %}
|
@ -1,128 +0,0 @@
|
||||
{% from "components/delete_confirmation.html" import DeleteConfirmation %}
|
||||
{% from "components/icon.html" import Icon %}
|
||||
{% from "components/modal.html" import Modal %}
|
||||
{% from "components/options_input.html" import OptionsInput %}
|
||||
{% from "components/save_button.html" import SaveButton %}
|
||||
{% from "components/text_input.html" import TextInput %}
|
||||
{% from "components/toggle_list.html" import ToggleButton, ToggleSection %}
|
||||
|
||||
|
||||
<div class="application-list-item application-list">
|
||||
<header>
|
||||
<div class="responsive-table-wrapper__header">
|
||||
<div class='responsive-table-wrapper__title'>
|
||||
<div class='h3'>{{ 'portfolios.applications.environments_heading' | translate }}</div>
|
||||
</div>
|
||||
<a class='icon-link'>
|
||||
{{ Icon('info') }}
|
||||
{{ "portfolios.admin.settings_info" | translate }}
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="accordion-table accordion-table-list">
|
||||
<div class="accordion-table__head row">
|
||||
<div class="col col--grow">{{ "portfolios.applications.environments.name" | translate }}</div>
|
||||
<div class="col col--grow">{{ "portfolios.applications.environments.edit_name" | translate }}</div>
|
||||
<div class="col col--grow">{{ "common.delete" | translate }}</div>
|
||||
<div class="col col--grow">{{ "common.members" | translate }}</div>
|
||||
</div>
|
||||
|
||||
<ul class="accordion-table__items">
|
||||
{% for env in environments_obj %}
|
||||
{% set delete_environment_modal_id = "delete_modal_environment{}".format(env['id']) %}
|
||||
{% set edit_form = env['edit_form'] %}
|
||||
|
||||
<toggler inline-template {% if active_toggler == (env['id'] | safe) %}initial-selected-section="{{ active_toggler_section }}"{% endif %}>
|
||||
<li class="accordion-table__item">
|
||||
<div class="accordion-table__item-content row">
|
||||
<div class="col col--grow">
|
||||
{{ env['name'] }}
|
||||
</div>
|
||||
<div class="col col--grow">
|
||||
<span class="icon-link">
|
||||
{% set edit_environment_button %}
|
||||
{{ Icon('edit') }}
|
||||
{% endset %}
|
||||
|
||||
{{
|
||||
ToggleButton(
|
||||
open_html=edit_environment_button,
|
||||
close_html=edit_environment_button,
|
||||
section_name="edit"
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="col col--grow">
|
||||
<span class="icon-link icon-link--danger" alt="Delete environment" v-on:click="openModal('{{ delete_environment_modal_id }}')">
|
||||
{{ Icon('trash') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="col col--grow icon-link icon-link--large accordion-table__item__toggler">
|
||||
{% set open_members_button %}
|
||||
{{ "common.members" | translate }} ({{ env['member_count'] }}) {{ Icon('caret_down') }}
|
||||
{% endset %}
|
||||
|
||||
{% set close_members_button %}
|
||||
{{ "common.members" | translate }} ({{ env['member_count'] }}) {{ Icon('caret_up') }}
|
||||
{% endset %}
|
||||
|
||||
{{
|
||||
ToggleButton(
|
||||
open_html=open_members_button,
|
||||
close_html=close_members_button,
|
||||
section_name="members"
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% call ToggleSection(section_name="members", classes="environment-roles") %}
|
||||
{% include 'fragments/applications/edit_environment_team_form.html' %}
|
||||
{% endcall %}
|
||||
|
||||
{% call ToggleSection(section_name="edit") %}
|
||||
<ul>
|
||||
<li class="accordion-table__item__expanded">
|
||||
<form action="{{ url_for('applications.update_environment', environment_id=env['id']) }}" method="post" v-on:submit="handleSubmit">
|
||||
{{ edit_form.csrf_token }}
|
||||
{{ TextInput(edit_form.name, validation='requiredField') }}
|
||||
{{
|
||||
SaveButton(
|
||||
text=("common.save" | translate)
|
||||
)
|
||||
}}
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
{% endcall %}
|
||||
</li>
|
||||
</toggler>
|
||||
|
||||
{% call Modal(name=delete_environment_modal_id) %}
|
||||
<h1>
|
||||
{{ 'fragments.edit_environment_team_form.delete_environment_title' | translate }}
|
||||
</h1>
|
||||
|
||||
{{
|
||||
Alert(
|
||||
level="warning",
|
||||
title=('components.modal.destructive_title' | translate),
|
||||
message=('components.modal.destructive_message' | translate({"resource": "environment"})),
|
||||
)
|
||||
}}
|
||||
|
||||
{{
|
||||
DeleteConfirmation(
|
||||
modal_id=delete_environment_modal_id,
|
||||
delete_text=('portfolios.applications.environments.delete.button' | translate),
|
||||
delete_action= url_for('applications.delete_environment', environment_id=env['id']),
|
||||
form=edit_form
|
||||
)
|
||||
}}
|
||||
{% endcall %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
@ -1,88 +0,0 @@
|
||||
{% from "components/options_input.html" import OptionsInput %}
|
||||
{% from "components/toggle_list.html" import ToggleButton, ToggleSection %}
|
||||
|
||||
{{ team_form.csrf_token }}
|
||||
|
||||
{% for member_form in team_form.members %}
|
||||
{% set delete_modal_id = "delete-user-{}".format(member_form.id) %}
|
||||
{% set environment_roles_form = member_form.environment_roles %}
|
||||
{% set permissions_form = member_form.permission_sets %}
|
||||
|
||||
<toggler inline-template>
|
||||
<li class="accordion-table__item">
|
||||
<div class="accordion-table__item-content row">
|
||||
<div class="col col--grow">
|
||||
<div class="member-list__name">
|
||||
{{ member_form.user_name.data }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col col--grow">{{ OptionsInput(permissions_form.perms_team_mgmt, label=False, watch=True) }}</div>
|
||||
<div class="col col--grow">{{ OptionsInput(permissions_form.perms_env_mgmt, label=False, watch=True) }}</div>
|
||||
<div class="col col--grow">{{ OptionsInput(permissions_form.perms_del_env, label=False, watch=True) }}</div>
|
||||
<div class="col col--grow icon-link icon-link--large accordion-table__item__toggler">
|
||||
{% set open_html %}
|
||||
{{ "portfolios.applications.team_settings.environments" | translate }} ({{ environment_roles_form | length }}) {{ Icon('caret_down') }}
|
||||
{% endset %}
|
||||
|
||||
{% set close_html %}
|
||||
{{ "portfolios.applications.team_settings.environments" | translate }} ({{ environment_roles_form | length }}) {{ Icon('caret_up') }}
|
||||
{% endset %}
|
||||
|
||||
{{
|
||||
ToggleButton(
|
||||
open_html=open_html,
|
||||
close_html=close_html,
|
||||
section_name="environments"
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
{% call ToggleSection(section_name="environments") %}
|
||||
<ul>
|
||||
{% for environment_form in environment_roles_form %}
|
||||
<li class="accordion-table__item__expanded">
|
||||
<environment-role inline-template v-bind:initial-role="'{{ environment_form.role.data }}'">
|
||||
<div>
|
||||
<div class="row">
|
||||
<div class="col col--grow">
|
||||
{{ environment_form.environment_name.data }}
|
||||
</div>
|
||||
<div class="accordion-table__item__expanded-role col col--grow">
|
||||
<div class="right">
|
||||
<span v-html="role">
|
||||
</span>
|
||||
<div class="icon-link" v-on:click="toggle">
|
||||
{{ Icon("edit") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="member-list__role-select" v-show="expanded">
|
||||
{{ environment_form.role.label }}
|
||||
{{ environment_form.role(**{"v-on:change": "radioChange", "class": "member-list____role-select__radio"}) }}
|
||||
<button
|
||||
class="usa-button"
|
||||
type="button"
|
||||
v-on:click="toggle"
|
||||
>
|
||||
{{ "common.close" | translate }}
|
||||
</button>
|
||||
{{ environment_form.environment_id() }}
|
||||
</div>
|
||||
</div>
|
||||
</environment-role>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<div class="accordion-table__item__action-group">
|
||||
{% if user_can(permissions.DELETE_APPLICATION_MEMBER) %}
|
||||
<a class="usa-button button-danger" v-on:click="openModal('{{ delete_modal_id }}')">
|
||||
{{ "portfolios.applications.remove_member.button" | translate }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endcall %}
|
||||
{{ member_form.role_id() }}
|
||||
</li>
|
||||
</toggler>
|
||||
{% endfor %}
|
@ -1,76 +0,0 @@
|
||||
{% from "components/icon.html" import Icon %}
|
||||
{% from "components/toggle_list.html" import ToggleButton, ToggleSection %}
|
||||
|
||||
<div class="application-list-item">
|
||||
<header>
|
||||
<div class="responsive-table-wrapper__header">
|
||||
<div class='responsive-table-wrapper__title'>
|
||||
<div class='h3'>{{ 'portfolios.applications.environments_heading' | translate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="accordion-table accordion-table-list">
|
||||
<div class="accordion-table__head">
|
||||
<span>{{ "portfolios.applications.environments.name" | translate }}</span>
|
||||
</div>
|
||||
|
||||
<ul class="accordion-table__items">
|
||||
{% for env in environments_obj %}
|
||||
<toggler inline-template>
|
||||
<li class="accordion-table__item">
|
||||
<div class="accordion-table__item-content">
|
||||
<span>
|
||||
{{ env['name'] }}
|
||||
</span>
|
||||
|
||||
<span class="icon-link icon-link--large accordion-table__item__toggler">
|
||||
{% set open_members_button %}
|
||||
{{ "common.members" | translate }} ({{ env['member_count'] }}) {{ Icon('caret_down') }}
|
||||
{% endset %}
|
||||
|
||||
{% set close_members_button %}
|
||||
{{ "common.members" | translate }} ({{ env['member_count'] }}) {{ Icon('caret_up') }}
|
||||
{% endset %}
|
||||
|
||||
{{
|
||||
ToggleButton(
|
||||
open_html=open_members_button,
|
||||
close_html=close_members_button,
|
||||
section_name="members"
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{% call ToggleSection(section_name="members") %}
|
||||
<ul>
|
||||
{% for member in env['members'] %}
|
||||
<li class="accordion-table__item__expanded">
|
||||
<div class="accordion-table__item__expanded_first">{{ member }}</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endcall %}
|
||||
|
||||
{% call ToggleSection(section_name="edit") %}
|
||||
<ul>
|
||||
<li class="accordion-table__item__expanded">
|
||||
<div>
|
||||
<form>
|
||||
<div class="form-row">
|
||||
<div class="form-col form-col--half">
|
||||
Row here
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
{% endcall %}
|
||||
</li>
|
||||
</toggler>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
@ -1,47 +0,0 @@
|
||||
{% from "components/toggle_list.html" import ToggleButton, ToggleSection %}
|
||||
|
||||
{% for member in team_form.members %}
|
||||
{% set user_permissions = [member.permission_sets.perms_team_mgmt, member.permission_sets.perms_env_mgmt, member.permission_sets.perms_del_env] %}
|
||||
|
||||
{% macro PermissionField(value) %}
|
||||
<div class="col col--grow user-permission{% if "Edit" in value or "Yes" in value %} green{% endif %}">{{ value }}</div>
|
||||
{% endmacro %}
|
||||
|
||||
<toggler inline-template>
|
||||
<li class="accordion-table__item">
|
||||
<div class="accordion-table__item-content row">
|
||||
<div class="col col--grow">{{ member.user_name.data }}</div>
|
||||
{% for permission in user_permissions %}
|
||||
{% set perm = dict(permission.choices).get(permission.data) %}
|
||||
{{ PermissionField(perm) }}
|
||||
{% endfor %}
|
||||
<div class="col col--grow icon-link icon-link--large accordion-table__item__toggler">
|
||||
{% set open_html %}
|
||||
{{ "portfolios.applications.team_settings.environments" | translate }} ({{ member.environment_roles | length }}) {{ Icon('caret_down') }}
|
||||
{% endset %}
|
||||
|
||||
{% set close_html %}
|
||||
{{ "portfolios.applications.team_settings.environments" | translate }} ({{ member.environment_roles | length }}) {{ Icon('caret_up') }}
|
||||
{% endset %}
|
||||
|
||||
{{
|
||||
ToggleButton(
|
||||
open_html=open_html,
|
||||
close_html=close_html,
|
||||
section_name="environments"
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
{% call ToggleSection(section_name="environments") %}
|
||||
<ul>
|
||||
{% for environment in member.environment_roles %}
|
||||
<li class="accordion-table__item__expanded">
|
||||
{{ environment.environment_name.data }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endcall %}
|
||||
</li>
|
||||
</toggler>
|
||||
{% endfor %}
|
@ -1,11 +1,11 @@
|
||||
{% extends "portfolios/base.html" %}
|
||||
|
||||
{% from "components/sticky_cta.html" import StickyCTA %}
|
||||
|
||||
{% block portfolio_header %}
|
||||
<div class='portfolio-header'>
|
||||
<div class='portfolio-header__name'>
|
||||
{{ secondary_breadcrumb }}
|
||||
</div>
|
||||
</div>
|
||||
{% if application %}
|
||||
{{ StickyCTA(text=application.name, return_link_url=url_for('applications.portfolio_applications', portfolio_id=application.portfolio_id), return_link_text="BACK TO APPLICATIONS") }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block portfolio_content %}
|
||||
|
@ -51,12 +51,6 @@
|
||||
<span>{{ "portfolios.applications.app_settings_text" | translate }}</span>
|
||||
</a>
|
||||
<div class='separator'></div>
|
||||
<a
|
||||
href="{{ url_for('applications.team', application_id=application.id) }}"
|
||||
class='icon-link'>
|
||||
<span>{{ "portfolios.applications.team_text" | translate }} ({{ application.members | length }})</span>
|
||||
</a>
|
||||
<div class='separator'></div>
|
||||
{% set has_environments = 0 < (application.environments|length) %}
|
||||
<a class='icon-link triangle-box' v-on:click="toggleSection('{{ section_name }}')" disabled="{{ not has_environments }}">
|
||||
<span>Environments ({{ application.environments|length }})</span>
|
||||
|
@ -3,16 +3,19 @@
|
||||
{% from "components/alert.html" import Alert %}
|
||||
{% from "components/delete_confirmation.html" import DeleteConfirmation %}
|
||||
{% from "components/icon.html" import Icon %}
|
||||
{% import "fragments/applications/new_member_modal_content.html" as member_steps %}
|
||||
{% from "components/modal.html" import Modal %}
|
||||
{% from "components/multi_step_modal_form.html" import MultiStepModalForm %}
|
||||
{% from "components/pagination.html" import Pagination %}
|
||||
{% from "components/save_button.html" import SaveButton %}
|
||||
{% from "components/text_input.html" import TextInput %}
|
||||
{% from "components/toggle_list.html" import ToggleButton, ToggleSection %}
|
||||
|
||||
{% set secondary_breadcrumb = 'portfolios.applications.existing_application_title' | translate({ "application_name": application.name }) %}
|
||||
|
||||
{% block application_content %}
|
||||
|
||||
<div class='subheading'>{{ 'portfolios.applications.settings_heading' | translate }}</div>
|
||||
<div class='subheading'>{{ 'portfolios.applications.settings.name_description' | translate }}</div>
|
||||
|
||||
{% if user_can(permissions.EDIT_APPLICATION) %}
|
||||
<base-form inline-template>
|
||||
@ -20,32 +23,16 @@
|
||||
<div class="panel">
|
||||
<div class="panel__content">
|
||||
{{ application_form.csrf_token }}
|
||||
<p>
|
||||
{{ "fragments.edit_application_form.explain" | translate }}
|
||||
</p>
|
||||
<div class="form-row">
|
||||
<div class="form-col form-col--two-thirds">
|
||||
{{ TextInput(application_form.name, optional=False) }}
|
||||
{{ TextInput(application_form.description, paragraph=True, optional=False) }}
|
||||
</div>
|
||||
<div class="form-col form-col--third">
|
||||
{% if user_can(permissions.DELETE_APPLICATION) %}
|
||||
<div class="usa-input">
|
||||
<input
|
||||
id="delete-application"
|
||||
type="button"
|
||||
v-on:click="openModal('delete-application')"
|
||||
class='usa-button button-danger-outline'
|
||||
value="{{ 'portfolios.applications.delete.button' | translate }}"
|
||||
>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel__footer">
|
||||
<div class="action-group">
|
||||
{{ SaveButton('common.save'|translate) }}
|
||||
{{ SaveButton('common.save_changes'|translate) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -81,26 +68,221 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div id="application-environments">
|
||||
<div class="accordion-table responsive-table-wrapper panel">
|
||||
{% if g.matchesPath("application-environments") %}
|
||||
{% include "fragments/flash.html" %}
|
||||
{% if not application.members %}
|
||||
{% set user_can_invite = user_can(permissions.CREATE_APPLICATION_MEMBER) %}
|
||||
|
||||
<div class='empty-state'>
|
||||
<p class='empty-state__message'>{{ ("portfolios.applications.team_settings.blank_slate.title" | translate) }}</p>
|
||||
|
||||
{{ 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(permissions.EDIT_ENVIRONMENT) %}
|
||||
{% include "fragments/applications/edit_environments.html" %}
|
||||
{% 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 %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Member</th>
|
||||
<th>Project Permissions</th>
|
||||
<th>Environment Access</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for member in members %}
|
||||
<tr>
|
||||
<td>{{ member.user_name }}</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>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% if user_can(permissions.CREATE_APPLICATION_MEMBER) %}
|
||||
{% import "fragments/applications/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'>
|
||||
{{ 'common.resource_names.environments' | translate }}
|
||||
|
||||
{% if user_can(permissions.CREATE_ENVIRONMENT) %}
|
||||
{% include "fragments/applications/add_new_environment.html" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% elif user_can(permissions.VIEW_ENVIRONMENT) %}
|
||||
{% include "fragments/applications/read_only_environments.html" %}
|
||||
<div class="panel">
|
||||
{% if g.matchesPath("application-environments") %}
|
||||
{% include "fragments/flash.html" %}
|
||||
{% endif %}
|
||||
<div class="panel__content">
|
||||
<div class="accordion-table accordion-table-list">
|
||||
<ul class="accordion-table__items">
|
||||
{% for env in environments_obj %}
|
||||
{% set edit_form = env['edit_form'] %}
|
||||
<toggler inline-template>
|
||||
<li class="accordion-table__item">
|
||||
<div class="accordion-table__item-content form-row">
|
||||
<div class="form-col form-col--two-thirds">
|
||||
<div class="environment-list__item">
|
||||
<span>
|
||||
{{ env['name'] }}
|
||||
</span>
|
||||
<span class="icon-link">
|
||||
{% set edit_environment_button %}
|
||||
{{ Icon('edit') }}
|
||||
{% endset %}
|
||||
|
||||
{{
|
||||
ToggleButton(
|
||||
open_html=edit_environment_button,
|
||||
close_html=edit_environment_button,
|
||||
section_name="edit"
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
<span class="accordion-table__item__toggler icon-link">
|
||||
{% set members_button = "portfolios.applications.member_count" | translate({'count': env['member_count']}) %}
|
||||
{{
|
||||
ToggleButton(
|
||||
open_html=members_button,
|
||||
close_html=members_button,
|
||||
section_name="members"
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-col form-col--third">
|
||||
<a href='{{ url_for("applications.access_environment", environment_id=env.id)}}' target='_blank' rel='noopener noreferrer' class='application-list-item__environment__csp_link icon-link'>
|
||||
<span>{{ "portfolios.applications.csp_link" | translate }} {{ Icon('link', classes="icon--tiny") }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% call ToggleSection(section_name="members") %}
|
||||
<ul>
|
||||
{% for member in env['members'] %}
|
||||
<li class="accordion-table__item__expanded">
|
||||
{{ member }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endcall %}
|
||||
|
||||
{% call ToggleSection(section_name="edit") %}
|
||||
<ul>
|
||||
<li class="accordion-table__item__expanded">
|
||||
<form action="{{ url_for('applications.update_environment', environment_id=env['id']) }}" method="post" v-on:submit="handleSubmit">
|
||||
{{ edit_form.csrf_token }}
|
||||
{{ TextInput(edit_form.name, validation='requiredField') }}
|
||||
{{
|
||||
SaveButton(
|
||||
text=("common.save" | translate)
|
||||
)
|
||||
}}
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
{% endcall %}
|
||||
</li>
|
||||
</toggler>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
{% if user_can(permissions.DELETE_APPLICATION) %}
|
||||
{% set env_count = application.environments | length %}
|
||||
{% if env_count == 1 %}
|
||||
{% set pluralized_env = "environment" %}
|
||||
{% else %}
|
||||
{% set pluralized_env = "environments" %}
|
||||
{% endif %}
|
||||
|
||||
<div class='subheading'>
|
||||
{{ "portfolios.applications.delete.subheading" | translate }}
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="panel__content">
|
||||
<div class="form-row">
|
||||
<div class="form-col form-col--two-thirds">
|
||||
{{ "portfolios.applications.delete.panel_text" | translate({"name": application.name, "env_count": env_count , "pluralized_env": pluralized_env}) | safe }}
|
||||
</div>
|
||||
<div class="form-col form-col--third">
|
||||
<div class="usa-input">
|
||||
<input
|
||||
id="delete-application"
|
||||
type="button"
|
||||
v-on:click="openModal('delete-application')"
|
||||
class='usa-button button-danger-outline'
|
||||
value="{{ 'portfolios.applications.delete.button' | translate }}"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% call Modal(name="delete-application") %}
|
||||
<h1>{{ "portfolios.applications.delete.header" | translate }}</h1>
|
||||
|
||||
|
@ -1,154 +0,0 @@
|
||||
{% extends "portfolios/applications/base.html" %}
|
||||
|
||||
{% from "components/icon.html" import Icon %}
|
||||
{% from "components/multi_step_modal_form.html" import MultiStepModalForm %}
|
||||
{% from 'components/save_button.html' import SaveButton %}
|
||||
{% import "fragments/applications/new_member_modal_content.html" as member_steps %}
|
||||
{% from "components/alert.html" import Alert %}
|
||||
{% from "components/delete_confirmation.html" import DeleteConfirmation %}
|
||||
{% from "components/modal.html" import Modal %}
|
||||
|
||||
{% set secondary_breadcrumb = 'portfolios.applications.team_settings.title' | translate({ "application_name": application.name }) %}
|
||||
|
||||
{% block application_content %}
|
||||
{% if not application.members %}
|
||||
{% set user_can_invite = user_can(permissions.CREATE_APPLICATION_MEMBER) %}
|
||||
|
||||
<div class='empty-state'>
|
||||
<p class='empty-state__message'>{{ ("portfolios.applications.team_settings.blank_slate.title" | translate) }}</p>
|
||||
|
||||
{{ 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.team_settings.subheading' | translate }}
|
||||
</div>
|
||||
|
||||
<section class="member-list application-list" id="application-members">
|
||||
<base-form inline-template>
|
||||
<form method='POST' id="team" action='{{ url_for("applications.update_team", application_id=application.id) }}' autocomplete="off" enctype="multipart/form-data">
|
||||
<div class='responsive-table-wrapper panel'>
|
||||
{% if g.matchesPath("application-members") %}
|
||||
{% include "fragments/flash.html" %}
|
||||
{% endif %}
|
||||
<header>
|
||||
<div class="responsive-table-wrapper__header">
|
||||
<div class="responsive-table-wrapper__title row">
|
||||
<div class="h3">
|
||||
{{ "portfolios.applications.team_settings.section.title" | translate({ "application_name": application.name }) }}
|
||||
<p class="member-list__subhead">Members ({{ team_form.members | length }})</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="accordion-table accordion-table-list">
|
||||
<div class="accordion-table__head row">
|
||||
<div class="col col--grow">
|
||||
{{ "common.name" | translate }}
|
||||
</div>
|
||||
<div class="col col--grow">
|
||||
{{ "portfolios.applications.team_settings.section.table.team_management" | translate }}
|
||||
</div>
|
||||
<div class="col col--grow">
|
||||
{{ "portfolios.applications.team_settings.section.table.environment_management" | translate }}
|
||||
</div>
|
||||
<div class="col col--grow">
|
||||
{{ "portfolios.applications.team_settings.section.table.delete_access" | translate }}
|
||||
</div>
|
||||
<div class="col col--grow">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<ul class="accordion-table__items">
|
||||
{% 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 %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="panel__footer">
|
||||
<div class="action-group save">
|
||||
{% if user_can(permissions.EDIT_APPLICATION_MEMBER) %}
|
||||
{{ SaveButton(text=('common.save' | translate), element="input", form="team") }}
|
||||
{% endif %}
|
||||
|
||||
{% set new_member_modal_name = "add-app-mem" %}
|
||||
{% if user_can(permissions.CREATE_APPLICATION_MEMBER) %}
|
||||
<a class="icon-link modal-link" v-on:click="openModal('{{ new_member_modal_name }}')">
|
||||
{{ "portfolios.admin.add_new_member" | translate }}
|
||||
{{ Icon("plus") }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</base-form>
|
||||
|
||||
{% if user_can(permissions.DELETE_APPLICATION_MEMBER) %}
|
||||
{% for member_form in team_form.members %}
|
||||
{% set delete_modal_id = "delete-user-{}".format(member_form.id) %}
|
||||
{% call Modal(name=delete_modal_id) %}
|
||||
<h1>
|
||||
{{ "portfolios.applications.remove_member.header" | translate }}
|
||||
</h1>
|
||||
|
||||
{{
|
||||
Alert(
|
||||
title=("components.modal.destructive_title" | translate),
|
||||
message=("portfolios.applications.remove_member.alert.message" | translate({"user_name": member_form.user_name.data})),
|
||||
level="warning"
|
||||
)
|
||||
}}
|
||||
|
||||
{{
|
||||
DeleteConfirmation(
|
||||
modal_id=delete_modal_id,
|
||||
delete_text=('portfolios.applications.remove_member.button' | translate),
|
||||
delete_action=url_for('applications.remove_member', application_id=application.id, application_role_id=member_form.data.role_id),
|
||||
form=member_form
|
||||
)
|
||||
}}
|
||||
{% endcall %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if user_can(permissions.CREATE_APPLICATION_MEMBER) %}
|
||||
{% import "fragments/applications/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 %}
|
||||
{% endblock %}
|
@ -1,5 +1,7 @@
|
||||
import pytest
|
||||
import uuid
|
||||
from flask import url_for, get_flashed_messages
|
||||
from unittest.mock import Mock
|
||||
|
||||
from tests.factories import *
|
||||
|
||||
@ -424,3 +426,122 @@ def test_delete_environment(client, user_session):
|
||||
assert environment.name in message["message"]
|
||||
# deletes environment
|
||||
assert len(application.environments) == 0
|
||||
|
||||
|
||||
def test_create_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 = application.environments[0]
|
||||
env_1 = application.environments[1]
|
||||
|
||||
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,
|
||||
"permission_sets-perms_env_mgmt": True,
|
||||
"permission_sets-perms_team_mgmt": True,
|
||||
"permission_sets-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
|
||||
|
||||
|
||||
def test_remove_member_success(client, user_session):
|
||||
user = UserFactory.create()
|
||||
application = ApplicationFactory.create()
|
||||
application_role = ApplicationRoleFactory.create(application=application, user=user)
|
||||
|
||||
user_session(application.portfolio.owner)
|
||||
|
||||
response = client.post(
|
||||
url_for(
|
||||
"applications.remove_member",
|
||||
application_id=application.id,
|
||||
application_role_id=application_role.id,
|
||||
)
|
||||
)
|
||||
|
||||
assert response.status_code == 302
|
||||
assert response.location == url_for(
|
||||
"applications.settings",
|
||||
_anchor="application-members",
|
||||
_external=True,
|
||||
application_id=application.id,
|
||||
fragment="application-members",
|
||||
)
|
||||
|
||||
|
||||
def test_remove_new_member_success(client, user_session):
|
||||
application = ApplicationFactory.create()
|
||||
application_role = ApplicationRoleFactory.create(application=application, user=None)
|
||||
|
||||
user_session(application.portfolio.owner)
|
||||
|
||||
response = client.post(
|
||||
url_for(
|
||||
"applications.remove_member",
|
||||
application_id=application.id,
|
||||
application_role_id=application_role.id,
|
||||
)
|
||||
)
|
||||
|
||||
assert response.status_code == 302
|
||||
assert response.location == url_for(
|
||||
"applications.settings",
|
||||
_anchor="application-members",
|
||||
_external=True,
|
||||
application_id=application.id,
|
||||
fragment="application-members",
|
||||
)
|
||||
|
||||
|
||||
def test_remove_member_failure(client, user_session):
|
||||
user = UserFactory.create()
|
||||
application = ApplicationFactory.create()
|
||||
|
||||
user_session(application.portfolio.owner)
|
||||
|
||||
response = client.post(
|
||||
url_for(
|
||||
"applications.remove_member",
|
||||
application_id=application.id,
|
||||
application_role_id=uuid.uuid4(),
|
||||
)
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
|
@ -1,269 +0,0 @@
|
||||
import uuid
|
||||
from unittest.mock import Mock
|
||||
|
||||
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 *
|
||||
|
||||
|
||||
def test_application_team(client, user_session):
|
||||
portfolio = PortfolioFactory.create()
|
||||
application = ApplicationFactory.create(portfolio=portfolio)
|
||||
|
||||
user_session(portfolio.owner)
|
||||
|
||||
response = client.get(url_for("applications.team", application_id=application.id))
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_update_team_permissions(client, user_session):
|
||||
application = ApplicationFactory.create()
|
||||
owner = application.portfolio.owner
|
||||
app_role = ApplicationRoleFactory.create(
|
||||
application=application, permission_sets=[]
|
||||
)
|
||||
user_session(owner)
|
||||
response = client.post(
|
||||
url_for("applications.update_team", application_id=application.id),
|
||||
data={
|
||||
"members-0-role_id": app_role.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,
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 302
|
||||
actual_perms_names = [perm.name for perm in app_role.permission_sets]
|
||||
expected_perms_names = [
|
||||
PermissionSets.VIEW_APPLICATION,
|
||||
PermissionSets.EDIT_APPLICATION_TEAM,
|
||||
PermissionSets.EDIT_APPLICATION_ENVIRONMENTS,
|
||||
PermissionSets.DELETE_APPLICATION_ENVIRONMENTS,
|
||||
]
|
||||
assert expected_perms_names == actual_perms_names
|
||||
|
||||
|
||||
def test_update_team_with_bad_permission_sets(client, user_session):
|
||||
application = ApplicationFactory.create()
|
||||
owner = application.portfolio.owner
|
||||
app_role = ApplicationRoleFactory.create(
|
||||
application=application, permission_sets=[]
|
||||
)
|
||||
permission_sets = app_role.permission_sets
|
||||
|
||||
user_session(owner)
|
||||
response = client.post(
|
||||
url_for("applications.update_team", application_id=application.id),
|
||||
data={
|
||||
"members-0-role_id": app_role.id,
|
||||
"members-0-permission_sets-perms_team_mgmt": PermissionSets.EDIT_APPLICATION_TEAM,
|
||||
"members-0-permission_sets-perms_env_mgmt": "some random string",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 400
|
||||
assert app_role.permission_sets == permission_sets
|
||||
|
||||
|
||||
def test_update_team_with_non_app_user(client, user_session):
|
||||
application = ApplicationFactory.create()
|
||||
owner = application.portfolio.owner
|
||||
|
||||
user_session(owner)
|
||||
response = client.post(
|
||||
url_for("applications.update_team", application_id=application.id),
|
||||
data={
|
||||
"members-0-role_id": str(uuid.uuid4()),
|
||||
"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,
|
||||
},
|
||||
)
|
||||
|
||||
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=[]
|
||||
)
|
||||
environment = EnvironmentFactory.create(application=application)
|
||||
env_role = EnvironmentRoleFactory.create(
|
||||
application_role=app_role,
|
||||
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-role_id": app_role.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
|
||||
|
||||
|
||||
def test_update_team_revoke_environment_access(client, user_session, db, session):
|
||||
application = ApplicationFactory.create()
|
||||
owner = application.portfolio.owner
|
||||
user = UserFactory.create()
|
||||
app_role = ApplicationRoleFactory.create(
|
||||
application=application, user=user, permission_sets=[]
|
||||
)
|
||||
environment = EnvironmentFactory.create(application=application)
|
||||
env_role = EnvironmentRoleFactory.create(
|
||||
application_role=app_role,
|
||||
environment=environment,
|
||||
role=CSPRole.BASIC_ACCESS.value,
|
||||
)
|
||||
assert user in environment.users
|
||||
|
||||
user_session(owner)
|
||||
response = client.post(
|
||||
url_for("applications.update_team", application_id=application.id),
|
||||
data={
|
||||
"members-0-role_id": app_role.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": NO_ACCESS,
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 302
|
||||
env_role_exists = db.exists().where(EnvironmentRole.id == env_role.id)
|
||||
assert not session.query(env_role_exists).scalar()
|
||||
assert user not in environment.users
|
||||
|
||||
|
||||
def test_create_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 = application.environments[0]
|
||||
env_1 = application.environments[1]
|
||||
|
||||
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,
|
||||
"permission_sets-perms_env_mgmt": True,
|
||||
"permission_sets-perms_team_mgmt": True,
|
||||
"permission_sets-perms_del_env": True,
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 302
|
||||
expected_url = url_for(
|
||||
"applications.team",
|
||||
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
|
||||
|
||||
|
||||
def test_remove_member_success(client, user_session):
|
||||
user = UserFactory.create()
|
||||
application = ApplicationFactory.create()
|
||||
application_role = ApplicationRoleFactory.create(application=application, user=user)
|
||||
|
||||
user_session(application.portfolio.owner)
|
||||
|
||||
response = client.post(
|
||||
url_for(
|
||||
"applications.remove_member",
|
||||
application_id=application.id,
|
||||
application_role_id=application_role.id,
|
||||
)
|
||||
)
|
||||
|
||||
assert response.status_code == 302
|
||||
assert response.location == url_for(
|
||||
"applications.team",
|
||||
_anchor="application-members",
|
||||
_external=True,
|
||||
application_id=application.id,
|
||||
fragment="application-members",
|
||||
)
|
||||
|
||||
|
||||
def test_remove_new_member_success(client, user_session):
|
||||
application = ApplicationFactory.create()
|
||||
application_role = ApplicationRoleFactory.create(application=application, user=None)
|
||||
|
||||
user_session(application.portfolio.owner)
|
||||
|
||||
response = client.post(
|
||||
url_for(
|
||||
"applications.remove_member",
|
||||
application_id=application.id,
|
||||
application_role_id=application_role.id,
|
||||
)
|
||||
)
|
||||
|
||||
assert response.status_code == 302
|
||||
assert response.location == url_for(
|
||||
"applications.team",
|
||||
_anchor="application-members",
|
||||
_external=True,
|
||||
application_id=application.id,
|
||||
fragment="application-members",
|
||||
)
|
||||
|
||||
|
||||
def test_remove_member_failure(client, user_session):
|
||||
user = UserFactory.create()
|
||||
application = ApplicationFactory.create()
|
||||
|
||||
user_session(application.portfolio.owner)
|
||||
|
||||
response = client.post(
|
||||
url_for(
|
||||
"applications.remove_member",
|
||||
application_id=application.id,
|
||||
application_role_id=uuid.uuid4(),
|
||||
)
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
@ -585,20 +585,6 @@ def test_task_orders_new_post_routes(post_url_assert_status):
|
||||
post_url_assert_status(rando, url, 404, data=data)
|
||||
|
||||
|
||||
def test_applications_application_team_access(get_url_assert_status):
|
||||
ccpo = UserFactory.create_ccpo()
|
||||
rando = UserFactory.create()
|
||||
|
||||
portfolio = PortfolioFactory.create()
|
||||
application = ApplicationFactory.create(portfolio=portfolio)
|
||||
|
||||
url = url_for("applications.team", application_id=application.id)
|
||||
|
||||
get_url_assert_status(ccpo, url, 200)
|
||||
get_url_assert_status(portfolio.owner, url, 200)
|
||||
get_url_assert_status(rando, url, 404)
|
||||
|
||||
|
||||
def test_portfolio_delete_access(post_url_assert_status):
|
||||
rando = UserFactory.create()
|
||||
owner = UserFactory.create()
|
||||
|
@ -59,6 +59,7 @@ common:
|
||||
'no': 'No'
|
||||
response_label: Response required
|
||||
save: Save
|
||||
save_changes: Save Changes
|
||||
undo: Undo
|
||||
view: View
|
||||
resource_names:
|
||||
@ -283,13 +284,15 @@ portfolios:
|
||||
portfolio_name: Portfolio name
|
||||
applications:
|
||||
add_application_text: Add a new application
|
||||
add_environment: Add new environment
|
||||
add_environment: Create an Environment
|
||||
add_member: Add a New Team Member
|
||||
add_another_environment: Add another environment
|
||||
app_settings_text: App settings
|
||||
create_button_text: Create
|
||||
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.
|
||||
csp_console_text: CSP console
|
||||
csp_link: Cloud Service Provider Link
|
||||
remove_member:
|
||||
alert:
|
||||
message: '{user_name} will no longer be able to access this application or any of its environments'
|
||||
@ -300,6 +303,8 @@ portfolios:
|
||||
message: You will lose access to this application and all environments will be removed from the CSP. Your reporting and activity will still be accessible.
|
||||
button: Delete application
|
||||
header: Are you sure you want to delete this application?
|
||||
panel_text: 'Deleting {name} will delete this application along with all {env_count} {pluralized_env}. <strong>This cannot be undone.</strong>'
|
||||
subheading: Delete Application
|
||||
enter_env_name: "Enter environment name:"
|
||||
environments:
|
||||
name: Name
|
||||
@ -309,8 +314,12 @@ portfolios:
|
||||
environments_description: Each environment created within an application is logically separated from one another for easier management and security.
|
||||
environments_heading: Application environments
|
||||
existing_application_title: '{application_name} Application Settings'
|
||||
member_count: '{count} members'
|
||||
new_application_title: New Application
|
||||
settings_heading: Application Settings
|
||||
settings:
|
||||
name_description: Name and Description
|
||||
team_members: Team Members
|
||||
team_settings:
|
||||
blank_slate:
|
||||
action_label: Invite a new team member
|
||||
@ -323,7 +332,6 @@ portfolios:
|
||||
environment_management: Environment Management
|
||||
team_management: Team Management
|
||||
title: '{application_name} Team'
|
||||
subheading: Team Settings
|
||||
title: '{application_name} Team Settings'
|
||||
add_to_environment: Add to existing environment
|
||||
team_text: Team
|
||||
@ -335,6 +343,15 @@ portfolios:
|
||||
manage_envs: 'Allow member to <strong>add</strong> and <strong>rename environments</strong> within the application.'
|
||||
delete_envs: 'Allow member to <strong>delete environments</strong> within the application.'
|
||||
manage_team: 'Allow member to <strong>add, update,</strong> and <strong>remove members</strong> from the application team.'
|
||||
perms_team_mgmt:
|
||||
view_application: View Team
|
||||
edit_application_team: Edit Team
|
||||
perms_env_mgmt:
|
||||
view_application: View Environments
|
||||
edit_application_environments: Edit Environments
|
||||
perms_del_env:
|
||||
view_application: ""
|
||||
delete_application_environments: Delete Application
|
||||
index:
|
||||
empty:
|
||||
start_button: Start a new JEDI portfolio
|
||||
|
Loading…
x
Reference in New Issue
Block a user