Merge pull request #1098 from dod-ccpo/add-members-to-app-provisioning-flow
Add members to app provisioning flow
This commit is contained in:
commit
3aa5895c93
@ -3,7 +3,7 @@
|
||||
"files": "^.secrets.baseline$",
|
||||
"lines": null
|
||||
},
|
||||
"generated_at": "2019-09-26T13:53:31Z",
|
||||
"generated_at": "2019-09-30T13:51:34Z",
|
||||
"plugins_used": [
|
||||
{
|
||||
"base64_limit": 4.5,
|
||||
@ -199,5 +199,5 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": "0.12.6"
|
||||
"version": "0.12.5"
|
||||
}
|
||||
|
@ -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.models.permissions import Permissions
|
||||
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):
|
||||
@ -24,6 +29,7 @@ def render_new_application_form(
|
||||
if application_id:
|
||||
application = Applications.get(application_id)
|
||||
render_args["form"] = form or form_class(obj=application)
|
||||
render_args["application"] = application
|
||||
else:
|
||||
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")
|
||||
def view_new_application_step_2(portfolio_id, application_id):
|
||||
return render_new_application_form(
|
||||
"applications/new/step_2.html",
|
||||
EnvironmentsForm,
|
||||
portfolio_id=portfolio_id,
|
||||
application_id=application_id,
|
||||
)
|
||||
application = Applications.get(application_id)
|
||||
render_args = {
|
||||
"form": EnvironmentsForm(
|
||||
data={
|
||||
"environment_names": [
|
||||
environment.name for environment in application.environments
|
||||
]
|
||||
}
|
||||
),
|
||||
"application": application,
|
||||
}
|
||||
|
||||
return render_template("applications/new/step_2.html", **render_args)
|
||||
|
||||
|
||||
@applications_bp.route(
|
||||
@ -112,11 +125,11 @@ def update_new_application_step_2(portfolio_id, application_id):
|
||||
if form.validate():
|
||||
application = Applications.get(application_id)
|
||||
application = Applications.update(application, form.data)
|
||||
flash("application_created", application_name=application.name)
|
||||
flash("application_environments_updated")
|
||||
return redirect(
|
||||
url_for(
|
||||
"applications.portfolio_applications",
|
||||
portfolio_id=application.portfolio_id,
|
||||
"applications.update_new_application_step_3",
|
||||
application_id=application_id,
|
||||
)
|
||||
)
|
||||
else:
|
||||
@ -130,3 +143,32 @@ def update_new_application_step_2(portfolio_id, application_id):
|
||||
),
|
||||
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
|
||||
)
|
||||
)
|
||||
|
@ -73,8 +73,9 @@ def filter_env_roles_form_data(member, environments):
|
||||
"environment_name": env.name,
|
||||
"role": NO_ACCESS,
|
||||
}
|
||||
env_role = EnvironmentRoles.get_by_user_and_environment(member.user_id, env.id)
|
||||
if env_role:
|
||||
env_roles_set = set(env.roles).intersection(set(member.environment_roles))
|
||||
if len(env_roles_set) == 1:
|
||||
(env_role,) = env_roles_set
|
||||
env_data["role"] = env_role.role
|
||||
|
||||
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")
|
||||
@user_can(Permissions.VIEW_APPLICATION, message="view application edit form")
|
||||
def settings(application_id):
|
||||
@ -283,39 +319,7 @@ def delete_environment(environment_id):
|
||||
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.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
|
||||
|
||||
handle_create_member(application_id, http_request.form)
|
||||
return redirect(
|
||||
url_for(
|
||||
"applications.settings",
|
||||
|
@ -12,13 +12,15 @@
|
||||
{% set action = url_for('applications.create_new_application_step_1', portfolio_id=portfolio.id, application_id=application_id) %}
|
||||
{% endif %}
|
||||
|
||||
{% block portfolio_header %}
|
||||
{% include "portfolios/header.html" %}
|
||||
{{ StickyCTA(text="Name and Describe New Application") }}
|
||||
{% endblock %}
|
||||
|
||||
{% block application_content %}
|
||||
|
||||
{% 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 }}'>
|
||||
<form method="POST" action="{{ action }}" v-on:submit="handleSubmit">
|
||||
<div class="panel">
|
||||
@ -42,7 +44,7 @@
|
||||
|
||||
<span class="action-group">
|
||||
{% block next_button %}
|
||||
{{ SaveButton(text=('portfolios.applications.next_button_text' | translate)) }}
|
||||
{{ SaveButton(text=('portfolios.applications.new.step_1_button_text' | translate)) }}
|
||||
{% endblock %}
|
||||
</span>
|
||||
</form>
|
||||
|
@ -7,14 +7,17 @@
|
||||
|
||||
{% set secondary_breadcrumb = 'portfolios.applications.new_application_title' | translate %}
|
||||
|
||||
{% block portfolio_header %}
|
||||
{{ StickyCTA(text=application.name) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block application_content %}
|
||||
|
||||
{% set modalName = "newApplicationConfirmation" %}
|
||||
{% include "fragments/flash.html" %}
|
||||
|
||||
<div class='subheading'>{{ 'portfolios.applications.settings_heading' | translate }}</div>
|
||||
<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__content">
|
||||
{{ form.csrf_token }}
|
||||
@ -63,7 +66,7 @@
|
||||
|
||||
<span class="action-group">
|
||||
{% block next_button %}
|
||||
{{ SaveButton(text=('portfolios.applications.create_button_text' | translate)) }}
|
||||
{{ SaveButton(text=('portfolios.applications.new.step_2_button_text' | translate)) }}
|
||||
{% endblock %}
|
||||
</span>
|
||||
</form>
|
||||
|
26
templates/applications/new/step_3.html
Normal file
26
templates/applications/new/step_3.html
Normal 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 %}
|
||||
|
@ -5,6 +5,7 @@
|
||||
{% 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 "fragments/members.html" import MemberManagementTemplate %}
|
||||
{% from "components/modal.html" import Modal %}
|
||||
{% from "components/multi_step_modal_form.html" import MultiStepModalForm %}
|
||||
{% from "components/pagination.html" import Pagination %}
|
||||
@ -73,150 +74,12 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% 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.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 %}
|
||||
{{ MemberManagementTemplate(
|
||||
application,
|
||||
members,
|
||||
new_member_form,
|
||||
"applications.settings",
|
||||
user_can(permissions.CREATE_APPLICATION_MEMBER)) }}
|
||||
|
||||
<div class='subheading'>
|
||||
{{ 'common.resource_names.environments' | translate }}
|
||||
|
143
templates/fragments/members.html
Normal file
143
templates/fragments/members.html
Normal 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 %}
|
@ -1,7 +1,9 @@
|
||||
from flask import url_for
|
||||
|
||||
from tests.factories import PortfolioFactory, ApplicationFactory
|
||||
from atst.domain.applications import Applications
|
||||
from tests.factories import PortfolioFactory, ApplicationFactory, UserFactory
|
||||
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):
|
||||
@ -94,3 +96,66 @@ def test_post_environments(client, session, user_session):
|
||||
assert response.status_code == 302
|
||||
session.refresh(application)
|
||||
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
|
||||
|
@ -21,6 +21,7 @@ from atst.models.portfolio_role import Status as PortfolioRoleStatus
|
||||
from atst.forms.application import EditEnvironmentForm
|
||||
from atst.forms.application_member import UpdateMemberForm
|
||||
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
|
||||
|
||||
@ -559,3 +560,28 @@ def test_revoke_invite(client, user_session):
|
||||
|
||||
assert invite.is_revoked
|
||||
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"
|
||||
|
@ -302,8 +302,10 @@ portfolios:
|
||||
add_member: Add a New Team Member
|
||||
add_another_environment: Add another environment
|
||||
app_settings_text: App settings
|
||||
create_button_text: Create
|
||||
next_button_text: "Next: Environments"
|
||||
new:
|
||||
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_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
|
||||
|
Loading…
x
Reference in New Issue
Block a user