Merge pull request #1297 from dod-ccpo/portfolio-admin-styling__part-3
Portfolio admin styling - manager drop down menu and forms
This commit is contained in:
commit
a4f2881a61
@ -152,7 +152,7 @@
|
|||||||
"hashed_secret": "e4f14805dfd1e6af030359090c535e149e6b4207",
|
"hashed_secret": "e4f14805dfd1e6af030359090c535e149e6b4207",
|
||||||
"is_secret": false,
|
"is_secret": false,
|
||||||
"is_verified": false,
|
"is_verified": false,
|
||||||
"line_number": 665,
|
"line_number": 649,
|
||||||
"type": "Hex High Entropy String"
|
"type": "Hex High Entropy String"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -19,9 +19,6 @@ from atst.domain.exceptions import UnauthorizedError
|
|||||||
|
|
||||||
def filter_perm_sets_data(member):
|
def filter_perm_sets_data(member):
|
||||||
perm_sets_data = {
|
perm_sets_data = {
|
||||||
"perms_portfolio_mgmt": bool(
|
|
||||||
member.has_permission_set(PermissionSets.EDIT_PORTFOLIO_ADMIN)
|
|
||||||
),
|
|
||||||
"perms_app_mgmt": bool(
|
"perms_app_mgmt": bool(
|
||||||
member.has_permission_set(
|
member.has_permission_set(
|
||||||
PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT
|
PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT
|
||||||
@ -33,24 +30,43 @@ def filter_perm_sets_data(member):
|
|||||||
"perms_reporting": bool(
|
"perms_reporting": bool(
|
||||||
member.has_permission_set(PermissionSets.EDIT_PORTFOLIO_REPORTS)
|
member.has_permission_set(PermissionSets.EDIT_PORTFOLIO_REPORTS)
|
||||||
),
|
),
|
||||||
|
"perms_portfolio_mgmt": bool(
|
||||||
|
member.has_permission_set(PermissionSets.EDIT_PORTFOLIO_ADMIN)
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
return perm_sets_data
|
return perm_sets_data
|
||||||
|
|
||||||
|
|
||||||
def filter_members_data(members_list, portfolio):
|
def filter_members_data(members_list):
|
||||||
members_data = []
|
members_data = []
|
||||||
for member in members_list:
|
for member in members_list:
|
||||||
members_data.append(
|
permission_sets = filter_perm_sets_data(member)
|
||||||
{
|
ppoc = (
|
||||||
"role_id": member.id,
|
PermissionSets.get(PermissionSets.PORTFOLIO_POC) in member.permission_sets
|
||||||
"user_name": member.user_name,
|
|
||||||
"permission_sets": filter_perm_sets_data(member),
|
|
||||||
"status": member.display_status,
|
|
||||||
"ppoc": PermissionSets.PORTFOLIO_POC in member.permission_sets,
|
|
||||||
# add in stuff here for forms
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
member_data = {
|
||||||
|
"role_id": member.id,
|
||||||
|
"user_name": member.user_name,
|
||||||
|
"permission_sets": filter_perm_sets_data(member),
|
||||||
|
"status": member.display_status,
|
||||||
|
"ppoc": ppoc,
|
||||||
|
"form": member_forms.PermissionsForm(permission_sets),
|
||||||
|
}
|
||||||
|
|
||||||
|
if not ppoc:
|
||||||
|
member_data["update_invite_form"] = (
|
||||||
|
member_forms.NewForm(user_data=member.latest_invitation)
|
||||||
|
if member.latest_invitation and member.latest_invitation.can_resend
|
||||||
|
else member_forms.NewForm()
|
||||||
|
)
|
||||||
|
member_data["invite_token"] = (
|
||||||
|
member.latest_invitation.token
|
||||||
|
if member.latest_invitation and member.latest_invitation.can_resend
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
|
members_data.append(member_data)
|
||||||
|
|
||||||
return sorted(members_data, key=lambda member: member["user_name"])
|
return sorted(members_data, key=lambda member: member["user_name"])
|
||||||
|
|
||||||
@ -75,7 +91,7 @@ def render_admin_page(portfolio, form=None):
|
|||||||
"portfolios/admin.html",
|
"portfolios/admin.html",
|
||||||
form=form,
|
form=form,
|
||||||
portfolio_form=portfolio_form,
|
portfolio_form=portfolio_form,
|
||||||
members=filter_members_data(member_list, portfolio),
|
members=filter_members_data(member_list),
|
||||||
new_manager_form=member_forms.NewForm(),
|
new_manager_form=member_forms.NewForm(),
|
||||||
assign_ppoc_form=assign_ppoc_form,
|
assign_ppoc_form=assign_ppoc_form,
|
||||||
portfolio=portfolio,
|
portfolio=portfolio,
|
||||||
@ -93,26 +109,27 @@ def admin(portfolio_id):
|
|||||||
return render_admin_page(portfolio)
|
return render_admin_page(portfolio)
|
||||||
|
|
||||||
|
|
||||||
@portfolios_bp.route("/portfolios/<portfolio_id>/update_ppoc", methods=["POST"])
|
# Updating PPoC is a post-MVP feature
|
||||||
@user_can(Permissions.EDIT_PORTFOLIO_POC, message="update portfolio ppoc")
|
# @portfolios_bp.route("/portfolios/<portfolio_id>/update_ppoc", methods=["POST"])
|
||||||
def update_ppoc(portfolio_id):
|
# @user_can(Permissions.EDIT_PORTFOLIO_POC, message="update portfolio ppoc")
|
||||||
role_id = http_request.form.get("role_id")
|
# def update_ppoc(portfolio_id): # pragma: no cover
|
||||||
|
# role_id = http_request.form.get("role_id")
|
||||||
portfolio = Portfolios.get(g.current_user, portfolio_id)
|
#
|
||||||
new_ppoc_role = PortfolioRoles.get_by_id(role_id)
|
# portfolio = Portfolios.get(g.current_user, portfolio_id)
|
||||||
|
# new_ppoc_role = PortfolioRoles.get_by_id(role_id)
|
||||||
PortfolioRoles.make_ppoc(portfolio_role=new_ppoc_role)
|
#
|
||||||
|
# PortfolioRoles.make_ppoc(portfolio_role=new_ppoc_role)
|
||||||
flash("primary_point_of_contact_changed", ppoc_name=new_ppoc_role.full_name)
|
#
|
||||||
|
# flash("primary_point_of_contact_changed", ppoc_name=new_ppoc_role.full_name)
|
||||||
return redirect(
|
#
|
||||||
url_for(
|
# return redirect(
|
||||||
"portfolios.admin",
|
# url_for(
|
||||||
portfolio_id=portfolio.id,
|
# "portfolios.admin",
|
||||||
fragment="primary-point-of-contact",
|
# portfolio_id=portfolio.id,
|
||||||
_anchor="primary-point-of-contact",
|
# fragment="primary-point-of-contact",
|
||||||
)
|
# _anchor="primary-point-of-contact",
|
||||||
)
|
# )
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
@portfolios_bp.route("/portfolios/<portfolio_id>/edit", methods=["POST"])
|
@portfolios_bp.route("/portfolios/<portfolio_id>/edit", methods=["POST"])
|
||||||
@ -166,3 +183,30 @@ def remove_member(portfolio_id, portfolio_role_id):
|
|||||||
fragment="portfolio-members",
|
fragment="portfolio-members",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@portfolios_bp.route(
|
||||||
|
"/portfolios/<portfolio_id>/members/<portfolio_role_id>", methods=["POST"]
|
||||||
|
)
|
||||||
|
@user_can(Permissions.EDIT_PORTFOLIO_USERS, message="update portfolio members")
|
||||||
|
def update_member(portfolio_id, portfolio_role_id):
|
||||||
|
form_data = http_request.form
|
||||||
|
form = member_forms.PermissionsForm(formdata=form_data)
|
||||||
|
portfolio_role = PortfolioRoles.get_by_id(portfolio_role_id)
|
||||||
|
portfolio = Portfolios.get(user=g.current_user, portfolio_id=portfolio_id)
|
||||||
|
|
||||||
|
if form.validate() and portfolio.owner_role != portfolio_role:
|
||||||
|
PortfolioRoles.update(portfolio_role, form.data["permission_sets"])
|
||||||
|
flash("update_portfolio_member", member_name=portfolio_role.full_name)
|
||||||
|
|
||||||
|
return redirect(
|
||||||
|
url_for(
|
||||||
|
"portfolios.admin",
|
||||||
|
portfolio_id=portfolio_id,
|
||||||
|
_anchor="portfolio-members",
|
||||||
|
fragment="portfolio-members",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
flash("update_portfolio_member_error", member_name=portfolio_role.full_name)
|
||||||
|
return (render_admin_page(portfolio), 400)
|
||||||
|
@ -54,13 +54,22 @@ def revoke_invitation(portfolio_id, portfolio_token):
|
|||||||
)
|
)
|
||||||
@user_can(Permissions.EDIT_PORTFOLIO_USERS, message="resend invitation")
|
@user_can(Permissions.EDIT_PORTFOLIO_USERS, message="resend invitation")
|
||||||
def resend_invitation(portfolio_id, portfolio_token):
|
def resend_invitation(portfolio_id, portfolio_token):
|
||||||
invite = PortfolioInvitations.resend(g.current_user, portfolio_token)
|
form = member_forms.NewForm(http_request.form)
|
||||||
send_portfolio_invitation(
|
|
||||||
invitee_email=invite.email,
|
if form.validate():
|
||||||
inviter_name=g.current_user.full_name,
|
invite = PortfolioInvitations.resend(
|
||||||
token=invite.token,
|
g.current_user, portfolio_token, form.data["user_data"]
|
||||||
)
|
)
|
||||||
flash("resend_portfolio_invitation", user_name=invite.user_name)
|
send_portfolio_invitation(
|
||||||
|
invitee_email=invite.email,
|
||||||
|
inviter_name=g.current_user.full_name,
|
||||||
|
token=invite.token,
|
||||||
|
)
|
||||||
|
flash("resend_portfolio_invitation", user_name=invite.user_name)
|
||||||
|
else:
|
||||||
|
user_name = f"{form['user_data']['first_name'].data} {form['user_data']['last_name'].data}"
|
||||||
|
flash("resend_portfolio_invitation_error", user_name=user_name)
|
||||||
|
|
||||||
return redirect(
|
return redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"portfolios.admin",
|
"portfolios.admin",
|
||||||
|
@ -128,6 +128,11 @@ MESSAGES = {
|
|||||||
"message": "flash.portfolio_invite.resent.message",
|
"message": "flash.portfolio_invite.resent.message",
|
||||||
"category": "success",
|
"category": "success",
|
||||||
},
|
},
|
||||||
|
"resend_portfolio_invitation_error": {
|
||||||
|
"title": "flash.portfolio_invite.error.title",
|
||||||
|
"message": "flash.portfolio_invite.error.message",
|
||||||
|
"category": "error",
|
||||||
|
},
|
||||||
"revoked_portfolio_access": {
|
"revoked_portfolio_access": {
|
||||||
"title": "flash.portfolio_member.revoked.title",
|
"title": "flash.portfolio_member.revoked.title",
|
||||||
"message": "flash.portfolio_member.revoked.message",
|
"message": "flash.portfolio_member.revoked.message",
|
||||||
@ -153,6 +158,16 @@ MESSAGES = {
|
|||||||
"message": "flash.task_order.submitted.message",
|
"message": "flash.task_order.submitted.message",
|
||||||
"category": "success",
|
"category": "success",
|
||||||
},
|
},
|
||||||
|
"update_portfolio_member": {
|
||||||
|
"title": "flash.portfolio_member.update.title",
|
||||||
|
"message": "flash.portfolio_member.update.message",
|
||||||
|
"category": "success",
|
||||||
|
},
|
||||||
|
"update_portfolio_member_error": {
|
||||||
|
"title": "flash.portfolio_member.update_error.title",
|
||||||
|
"message": "flash.portfolio_member.update_error.message",
|
||||||
|
"category": "error",
|
||||||
|
},
|
||||||
"updated_application_team_settings": {
|
"updated_application_team_settings": {
|
||||||
"title": "flash.success",
|
"title": "flash.success",
|
||||||
"message": "flash.updated_application_team_settings",
|
"message": "flash.updated_application_team_settings",
|
||||||
|
@ -39,6 +39,7 @@
|
|||||||
@import "components/sticky_cta.scss";
|
@import "components/sticky_cta.scss";
|
||||||
@import "components/error_page.scss";
|
@import "components/error_page.scss";
|
||||||
@import "components/member_form.scss";
|
@import "components/member_form.scss";
|
||||||
|
@import "components/toggle_menu.scss";
|
||||||
|
|
||||||
@import "sections/login";
|
@import "sections/login";
|
||||||
@import "sections/home";
|
@import "sections/home";
|
||||||
|
@ -130,10 +130,6 @@
|
|||||||
&--th {
|
&--th {
|
||||||
width: 50%;
|
width: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
&--td {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
@ -154,55 +150,6 @@
|
|||||||
margin-right: $gap * 6;
|
margin-right: $gap * 6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-member-menu {
|
|
||||||
position: absolute;
|
|
||||||
top: $gap;
|
|
||||||
right: $gap * 2;
|
|
||||||
|
|
||||||
.accordion-table__item__toggler {
|
|
||||||
padding: $gap / 3;
|
|
||||||
border: 1px solid $color-gray-lighter;
|
|
||||||
border-radius: 3px;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&--active {
|
|
||||||
background-color: $color-aqua-lightest;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
margin: $gap / 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__toggle {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 30px;
|
|
||||||
background-color: $color-white;
|
|
||||||
border: 1px solid $color-gray-light;
|
|
||||||
z-index: 1;
|
|
||||||
margin-top: 0;
|
|
||||||
|
|
||||||
a {
|
|
||||||
display: block;
|
|
||||||
padding: $gap;
|
|
||||||
border-bottom: 1px solid $color-gray-lighter;
|
|
||||||
text-decoration: none;
|
|
||||||
color: $color-black;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
border-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: $color-aqua-lightest;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#add-new-env {
|
#add-new-env {
|
||||||
|
58
styles/components/_toggle_menu.scss
Normal file
58
styles/components/_toggle_menu.scss
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
.toggle-menu {
|
||||||
|
position: absolute;
|
||||||
|
top: $gap;
|
||||||
|
right: $gap * 2;
|
||||||
|
|
||||||
|
&__container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-table__item__toggler {
|
||||||
|
padding: $gap / 3;
|
||||||
|
border: 1px solid $color-gray-lighter;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&--active {
|
||||||
|
background-color: $color-aqua-lightest;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
margin: $gap / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__toggle {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 30px;
|
||||||
|
background-color: $color-white;
|
||||||
|
border: 1px solid $color-gray-light;
|
||||||
|
z-index: 1;
|
||||||
|
margin-top: 0;
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: block;
|
||||||
|
padding: $gap;
|
||||||
|
border-bottom: 1px solid $color-gray-lighter;
|
||||||
|
text-decoration: none;
|
||||||
|
color: $color-black;
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $color-aqua-lightest;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
color: $color-gray;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@
|
|||||||
{% from "components/modal.html" import Modal %}
|
{% from "components/modal.html" import Modal %}
|
||||||
{% from "components/multi_step_modal_form.html" import MultiStepModalForm %}
|
{% from "components/multi_step_modal_form.html" import MultiStepModalForm %}
|
||||||
{% from "components/save_button.html" import SaveButton %}
|
{% from "components/save_button.html" import SaveButton %}
|
||||||
|
{% from "components/toggle_menu.html" import ToggleMenu %}
|
||||||
|
|
||||||
{% macro MemberManagementTemplate(
|
{% macro MemberManagementTemplate(
|
||||||
application,
|
application,
|
||||||
@ -38,16 +39,17 @@
|
|||||||
{% call Modal(modal_name, classes="form-content--app-mem") %}
|
{% call Modal(modal_name, classes="form-content--app-mem") %}
|
||||||
<div class="modal__form--header">
|
<div class="modal__form--header">
|
||||||
<h1>{{ Icon('avatar') }} {{ "portfolios.applications.members.form.edit_access_header" | translate({ "user": member.user_name }) }}</h1>
|
<h1>{{ Icon('avatar') }} {{ "portfolios.applications.members.form.edit_access_header" | translate({ "user": member.user_name }) }}</h1>
|
||||||
<hr class="full-width">
|
|
||||||
</div>
|
</div>
|
||||||
<base-form inline-template>
|
<base-form inline-template>
|
||||||
<form id='{{ modal_name }}' method="POST" action="{{ url_for(action_update, application_id=application.id, application_role_id=member.role_id,) }}">
|
<form id='{{ modal_name }}' method="POST" action="{{ url_for(action_update, application_id=application.id, application_role_id=member.role_id,) }}">
|
||||||
{{ member.form.csrf_token }}
|
{{ member.form.csrf_token }}
|
||||||
{{ member_fields.PermsFields(form=member.form, member_role_id=member.role_id) }}
|
{{ member_form.SubmitStep(
|
||||||
<div class="action-group">
|
name=modal_name,
|
||||||
{{ SaveButton(text='Update', element='input', additional_classes='action-group__action') }}
|
form=member_fields.PermsFields(form=member.form, member_role_id=member.role_id),
|
||||||
<a class='action-group__action usa-button usa-button-secondary' v-on:click="closeModal('{{ modal_name }}')">{{ "common.cancel" | translate }}</a>
|
submit_text="Update",
|
||||||
</div>
|
previous=False,
|
||||||
|
modal=modal_name,
|
||||||
|
) }}
|
||||||
</form>
|
</form>
|
||||||
</base-form>
|
</base-form>
|
||||||
{% endcall %}
|
{% endcall %}
|
||||||
@ -57,16 +59,17 @@
|
|||||||
{% call Modal(resend_invite_modal, classes="form-content--app-mem") %}
|
{% call Modal(resend_invite_modal, classes="form-content--app-mem") %}
|
||||||
<div class="modal__form--header">
|
<div class="modal__form--header">
|
||||||
<h1>{{ "portfolios.applications.members.new.verify" | translate }}</h1>
|
<h1>{{ "portfolios.applications.members.new.verify" | translate }}</h1>
|
||||||
<hr class="full-width">
|
|
||||||
</div>
|
</div>
|
||||||
<base-form inline-template :enable-save="true">
|
<base-form inline-template :enable-save="true">
|
||||||
<form id='{{ resend_invite_modal }}' method="POST" action="{{ url_for('applications.resend_invite', application_id=application.id, application_role_id=member.role_id) }}">
|
<form id='{{ resend_invite_modal }}' method="POST" action="{{ url_for('applications.resend_invite', application_id=application.id, application_role_id=member.role_id) }}">
|
||||||
{{ member.update_invite_form.csrf_token }}
|
{{ member.update_invite_form.csrf_token }}
|
||||||
{{ member_fields.InfoFields(member.update_invite_form) }}
|
{{ member_form.SubmitStep(
|
||||||
<div class="action-group">
|
name=resend_invite_modal,
|
||||||
{{ SaveButton(text="Resend Invite")}}
|
form=member_fields.InfoFields(member.update_invite_form),
|
||||||
<a class='action-group__action' v-on:click="closeModal('{{ resend_invite_modal }}')">{{ "common.cancel" | translate }}</a>
|
submit_text="Resend Invite",
|
||||||
</div>
|
previous=False,
|
||||||
|
modal=resend_invite_modal,
|
||||||
|
) }}
|
||||||
</form>
|
</form>
|
||||||
</base-form>
|
</base-form>
|
||||||
{% endcall %}
|
{% endcall %}
|
||||||
@ -119,7 +122,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
<td class="env_role--td">
|
<td class="toggle-menu__container">
|
||||||
{% for env in member.environment_roles %}
|
{% for env in member.environment_roles %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span class="env-role__environment">
|
<span class="env-role__environment">
|
||||||
@ -131,32 +134,21 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if user_can(permissions.EDIT_APPLICATION_MEMBER) -%}
|
{% if user_can(permissions.EDIT_APPLICATION_MEMBER) -%}
|
||||||
<toggle-menu inline-template v-cloak>
|
{% call ToggleMenu() %}
|
||||||
<div class="app-member-menu">
|
<a v-on:click="openModal('{{ perms_modal }}')">
|
||||||
<span v-if="isVisible" class="accordion-table__item__toggler accordion-table__item__toggler--active">
|
{{ "portfolios.applications.members.menu.edit" | translate }}
|
||||||
{{ Icon('ellipsis')}}
|
</a>
|
||||||
</span>
|
{% if invite_pending or invite_expired -%}
|
||||||
<span v-else class="accordion-table__item__toggler">
|
{% set revoke_invite_modal = "revoke_invite_{}".format(member.role_id) %}
|
||||||
{{ Icon('ellipsis')}}
|
{% set resend_invite_modal = "resend_invite-{}".format(member.role_id) %}
|
||||||
</span>
|
<a v-on:click='openModal("{{ resend_invite_modal }}")'>
|
||||||
|
{{ "portfolios.applications.members.menu.resend" | translate }}
|
||||||
<div v-show="isVisible" class="accordion-table__item-toggle-content app-member-menu__toggle">
|
</a>
|
||||||
<a v-on:click="openModal('{{ perms_modal }}')">
|
{% if user_can(permissions.DELETE_APPLICATION_MEMBER) -%}
|
||||||
{{ "portfolios.applications.members.menu.edit" | translate }}
|
<a v-on:click='openModal("{{ revoke_invite_modal }}")'>{{ 'invites.revoke' | translate }}</a>
|
||||||
</a>
|
{%- endif %}
|
||||||
{% if invite_pending or invite_expired -%}
|
{%- endif %}
|
||||||
{% set revoke_invite_modal = "revoke_invite_{}".format(member.role_id) %}
|
{% endcall %}
|
||||||
{% set resend_invite_modal = "resend_invite-{}".format(member.role_id) %}
|
|
||||||
<a v-on:click='openModal("{{ resend_invite_modal }}")'>
|
|
||||||
{{ "portfolios.applications.members.menu.resend" | translate }}
|
|
||||||
</a>
|
|
||||||
{% if user_can(permissions.DELETE_APPLICATION_MEMBER) -%}
|
|
||||||
<a v-on:click='openModal("{{ revoke_invite_modal }}")'>{{ 'invites.revoke' | translate }}</a>
|
|
||||||
{%- endif %}
|
|
||||||
{%- endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</toggle-menu>
|
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
17
templates/components/toggle_menu.html
Normal file
17
templates/components/toggle_menu.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{% from "components/icon.html" import Icon %}
|
||||||
|
|
||||||
|
{% macro ToggleMenu() %}
|
||||||
|
<toggle-menu inline-template v-cloak>
|
||||||
|
<div class="toggle-menu">
|
||||||
|
<span v-if="isVisible" class="accordion-table__item__toggler accordion-table__item__toggler--active">
|
||||||
|
{{ Icon('ellipsis')}}
|
||||||
|
</span>
|
||||||
|
<span v-else class="accordion-table__item__toggler">
|
||||||
|
{{ Icon('ellipsis')}}
|
||||||
|
</span>
|
||||||
|
<div v-show="isVisible" class="accordion-table__item-toggle-content toggle-menu__toggle">
|
||||||
|
{{ caller() }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</toggle-menu>
|
||||||
|
{% endmacro %}
|
@ -59,10 +59,6 @@
|
|||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
{% if user_can(permissions.VIEW_PORTFOLIO_POC) %}
|
|
||||||
{% include "portfolios/fragments/primary_point_of_contact.html" %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if user_can(permissions.VIEW_PORTFOLIO_USERS) %}
|
{% if user_can(permissions.VIEW_PORTFOLIO_USERS) %}
|
||||||
{% include "portfolios/fragments/portfolio_members.html" %}
|
{% include "portfolios/fragments/portfolio_members.html" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -1,80 +0,0 @@
|
|||||||
{% from "components/icon.html" import Icon %}
|
|
||||||
{% from "components/text_input.html" import TextInput %}
|
|
||||||
{% from "components/multi_step_modal_form.html" import MultiStepModalForm %}
|
|
||||||
{% from "components/alert.html" import Alert %}
|
|
||||||
{% from "components/options_input.html" import OptionsInput %}
|
|
||||||
|
|
||||||
{% set step_one %}
|
|
||||||
<hr class="full-width">
|
|
||||||
<h1>{{ "fragments.ppoc.update_ppoc_title" | translate }}</h1>
|
|
||||||
|
|
||||||
{{
|
|
||||||
Alert(
|
|
||||||
level="warning",
|
|
||||||
title=("fragments.ppoc.alert.title" | translate),
|
|
||||||
message=("fragments.ppoc.alert.message" | translate),
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
|
|
||||||
<div class='form-row'>
|
|
||||||
<div class='form-col form-col--half'>
|
|
||||||
{{
|
|
||||||
OptionsInput(
|
|
||||||
assign_ppoc_form.role_id,
|
|
||||||
optional=False
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
<div class='form-col form-col--half'>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class='action-group'>
|
|
||||||
<input
|
|
||||||
type='button'
|
|
||||||
v-on:click="next()"
|
|
||||||
v-bind:disabled="!canSave"
|
|
||||||
class='action-group__action usa-button'
|
|
||||||
value='{{ "fragments.ppoc.assign_user_button_text" | translate }}'>
|
|
||||||
<a class='action-group__action icon-link icon-link--default' v-on:click="closeModal('change-ppoc-form')">
|
|
||||||
{{ "common.cancel" | translate }}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endset %}
|
|
||||||
|
|
||||||
{% set step_two %}
|
|
||||||
<hr class="full-width">
|
|
||||||
<h1>{{ "fragments.ppoc.update_ppoc_confirmation_title" | translate }}</h1>
|
|
||||||
|
|
||||||
{{
|
|
||||||
Alert(
|
|
||||||
level="info",
|
|
||||||
title=("fragments.ppoc.confirm_alert.title" | translate),
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
|
|
||||||
<div class='action-group'>
|
|
||||||
<input
|
|
||||||
type="submit"
|
|
||||||
class='action-group__action usa-button'
|
|
||||||
form="change-ppoc-form"
|
|
||||||
value='{{ "common.confirm" | translate }}'>
|
|
||||||
<a class='action-group__action icon-link icon-link--default' v-on:click="closeModal('change-ppoc-form')">
|
|
||||||
{{ "common.cancel" | translate }}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endset %}
|
|
||||||
|
|
||||||
<div class="flex-reverse-row">
|
|
||||||
{% set disable_ppoc_button = 1 == portfolio.members |length %}
|
|
||||||
<button type="button" class="usa-button usa-button-primary" v-on:click="openModal('change-ppoc-form')" {% if disable_ppoc_button %}disabled{% endif %}>
|
|
||||||
{{ "fragments.ppoc.update_btn" | translate }}
|
|
||||||
</button>
|
|
||||||
{{
|
|
||||||
MultiStepModalForm(
|
|
||||||
'change-ppoc-form',
|
|
||||||
assign_ppoc_form,
|
|
||||||
form_action=url_for("portfolios.update_ppoc", portfolio_id=portfolio.id),
|
|
||||||
steps=[step_one, step_two],
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</div>
|
|
@ -5,6 +5,92 @@
|
|||||||
{% from "components/multi_step_modal_form.html" import MultiStepModalForm %}
|
{% from "components/multi_step_modal_form.html" import MultiStepModalForm %}
|
||||||
{% from 'components/save_button.html' import SaveButton %}
|
{% from 'components/save_button.html' import SaveButton %}
|
||||||
{% import "portfolios/fragments/member_form_fields.html" as member_form_fields %}
|
{% import "portfolios/fragments/member_form_fields.html" as member_form_fields %}
|
||||||
|
{% from "components/toggle_menu.html" import ToggleMenu %}
|
||||||
|
|
||||||
|
{% if user_can(permissions.EDIT_PORTFOLIO_USERS) -%}
|
||||||
|
{% for member in members -%}
|
||||||
|
{% if not member.ppoc -%}
|
||||||
|
{% set invite_pending = member.status == 'invite_pending' %}
|
||||||
|
{% set invite_expired = member.status == 'invite_expired' %}
|
||||||
|
|
||||||
|
{% set modal_name = "edit_member-{}".format(loop.index) %}
|
||||||
|
{% call Modal(modal_name, classes="form-content--app-mem") %}
|
||||||
|
<div class="modal__form--header">
|
||||||
|
<h1>{{ Icon('avatar') }} {{ "portfolios.applications.members.form.edit_access_header" | translate({ "user": member.user_name }) }}</h1>
|
||||||
|
</div>
|
||||||
|
<base-form inline-template>
|
||||||
|
<form id='{{ modal_name }}' method="POST" action="{{ url_for('portfolios.update_member', portfolio_id=portfolio.id, portfolio_role_id=member.role_id) }}">
|
||||||
|
{{ member.form.csrf_token }}
|
||||||
|
{{ member_form.SubmitStep(
|
||||||
|
name=modal_name,
|
||||||
|
form=member_form_fields.PermsFields(member.form, member_role_id=member.role_id),
|
||||||
|
submit_text="Save Changes",
|
||||||
|
previous=False,
|
||||||
|
modal=modal_name,
|
||||||
|
) }}
|
||||||
|
</form>
|
||||||
|
</base-form>
|
||||||
|
{% endcall %}
|
||||||
|
|
||||||
|
{% if invite_pending or invite_expired -%}
|
||||||
|
{% set resend_invite_modal = "resend_invite-{}".format(member.role_id) %}
|
||||||
|
{% call Modal(resend_invite_modal, classes="form-content--app-mem") %}
|
||||||
|
<div class="modal__form--header">
|
||||||
|
<h1>{{ "portfolios.applications.members.new.verify" | translate }}</h1>
|
||||||
|
</div>
|
||||||
|
<base-form inline-template :enable-save="true">
|
||||||
|
<form id='{{ resend_invite_modal }}' method="POST" action="{{ url_for('portfolios.resend_invitation', portfolio_id=portfolio.id, portfolio_token=member.invite_token) }}">
|
||||||
|
{{ member.update_invite_form.csrf_token }}
|
||||||
|
{{ member_form.SubmitStep(
|
||||||
|
name=resend_invite_modal,
|
||||||
|
form=member_form_fields.InfoFields(member.update_invite_form.user_data),
|
||||||
|
submit_text="Resend Invite",
|
||||||
|
previous=False,
|
||||||
|
modal=resend_invite_modal
|
||||||
|
) }}
|
||||||
|
</form>
|
||||||
|
</base-form>
|
||||||
|
{% endcall %}
|
||||||
|
|
||||||
|
{% set revoke_invite_modal = "revoke_invite-{}".format(member.role_id) %}
|
||||||
|
{% call Modal(name=revoke_invite_modal) %}
|
||||||
|
<form method="post" action="{{ url_for('portfolios.revoke_invitation', portfolio_id=portfolio.id, portfolio_token=member.invite_token) }}">
|
||||||
|
{{ member.form.csrf_token }}
|
||||||
|
<h1>{{ "invites.revoke" | translate }}</h1>
|
||||||
|
<hr class="full-width">
|
||||||
|
{{ "invites.revoke_modal_text" | translate({"application": portfolio.name}) }}
|
||||||
|
<div class="action-group">
|
||||||
|
<button class="action-group__action usa-button usa-button-primary" type="submit">{{ "invites.revoke" | translate }}</button>
|
||||||
|
<button class='action-group__action usa-button usa-button-secondary' v-on:click='closeModal("{{revoke_invite_modal}}")' type="button">{{ "common.cancel" | translate }}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endcall %}
|
||||||
|
{% else %}
|
||||||
|
{% set remove_manager_modal = "remove_manager-{}".format(member.role_id) %}
|
||||||
|
{% call Modal(name=remove_manager_modal, dismissable=False) %}
|
||||||
|
<h1>{{ "portfolios.admin.alert_header" | translate }}</h1>
|
||||||
|
<hr class="full-width">
|
||||||
|
{{
|
||||||
|
Alert(
|
||||||
|
title="portfolios.admin.alert_title" | translate,
|
||||||
|
message="portfolios.admin.alert_message" | translate,
|
||||||
|
level="warning"
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
<div class="action-group">
|
||||||
|
<form method="POST" action="{{ url_for('portfolios.remove_member', portfolio_id=portfolio.id, portfolio_role_id=member.role_id)}}">
|
||||||
|
{{ member.form.csrf_token }}
|
||||||
|
<button class="usa-button usa-button-danger">
|
||||||
|
{{ "portfolios.members.archive_button" | translate }}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<a v-on:click="closeModal('{{ modal_id }}')" class="action-group__action icon-link icon-link--default">{{ "common.cancel" | translate }}</a>
|
||||||
|
</div>
|
||||||
|
{% endcall %}
|
||||||
|
{%- endif %}
|
||||||
|
{%- endif %}
|
||||||
|
{%- endfor %}
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
<h3>Portfolio Managers</h3>
|
<h3>Portfolio Managers</h3>
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
@ -19,6 +105,14 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for member in members -%}
|
{% for member in members -%}
|
||||||
|
{% set invite_pending = member.status == 'invite_pending' %}
|
||||||
|
{% set invite_expired = member.status == 'invite_expired' %}
|
||||||
|
{% set current_user = current_member_id == member.role_id %}
|
||||||
|
{% set perms_modal = "edit_member-{}".format(loop.index) %}
|
||||||
|
{% set resend_invite_modal = "resend_invite-{}".format(member.role_id) %}
|
||||||
|
{% set revoke_invite_modal = "revoke_invite-{}".format(member.role_id) %}
|
||||||
|
{% set remove_manager_modal = "remove_manager-{}".format(member.role_id) %}
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<strong>{{ member.user_name }}{% if member.role_id == current_member_id %} (You){% endif %}</strong>
|
<strong>{{ member.user_name }}{% if member.role_id == current_member_id %} (You){% endif %}</strong>
|
||||||
@ -28,14 +122,33 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{{ Label(type=member.status, classes='label--below')}}
|
{{ Label(type=member.status, classes='label--below')}}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="toggle-menu__container">
|
||||||
{% for perm, value in member.permission_sets.items() -%}
|
{% for perm, value in member.permission_sets.items() -%}
|
||||||
<div>
|
{% if value -%}
|
||||||
{% if value -%}
|
<div>
|
||||||
{{ ("portfolios.admin.members.{}.{}".format(perm, value)) | translate }}
|
{{ ("portfolios.admin.members.{}.{}".format(perm, value)) | translate }}
|
||||||
{%- endif %}
|
</div>
|
||||||
</div>
|
{%- endif %}
|
||||||
{%-endfor %}
|
{%-endfor %}
|
||||||
|
{% if user_can(permissions.EDIT_PORTFOLIO_USERS) -%}
|
||||||
|
{% call ToggleMenu() %}
|
||||||
|
<a
|
||||||
|
{% if not member.ppoc %}v-on:click="openModal('{{ perms_modal }}')"{% endif %}
|
||||||
|
class="{% if member.ppoc %}disabled{% endif %}">
|
||||||
|
Edit Permissions
|
||||||
|
</a>
|
||||||
|
{% if invite_pending or invite_expired -%}
|
||||||
|
<a v-on:click="openModal('{{ resend_invite_modal }}')">Resend Invite</a>
|
||||||
|
<a v-on:click="openModal('{{ revoke_invite_modal }}')">Revoke Invite</a>
|
||||||
|
{% else %}
|
||||||
|
<a
|
||||||
|
{% if not current_user %}v-on:click="openModal('{{ remove_manager_modal }}')"{% endif %}
|
||||||
|
class="{% if current_user %}disabled{% endif %}">
|
||||||
|
Remove Manager
|
||||||
|
</a>
|
||||||
|
{%- endif %}
|
||||||
|
{% endcall %}
|
||||||
|
{%- endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
@ -60,13 +173,13 @@
|
|||||||
form=member_form_fields.InfoFields(new_manager_form.user_data),
|
form=member_form_fields.InfoFields(new_manager_form.user_data),
|
||||||
next_button_text="Next: Permissions",
|
next_button_text="Next: Permissions",
|
||||||
previous=False,
|
previous=False,
|
||||||
modal=new_manager_modal_name,
|
modal=new_manager_modal,
|
||||||
),
|
),
|
||||||
member_form.SubmitStep(
|
member_form.SubmitStep(
|
||||||
name=new_manager_modal,
|
name=new_manager_modal,
|
||||||
form=member_form_fields.PermsFields(new_manager_form),
|
form=member_form_fields.PermsFields(new_manager_form),
|
||||||
submit_text="Add Mananger",
|
submit_text="Add Mananger",
|
||||||
modal=new_manager_modal_name,
|
modal=new_manager_modal,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
) }}
|
) }}
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
<section id="primary-point-of-contact" class="panel">
|
|
||||||
<div class="panel__content">
|
|
||||||
{% if g.matchesPath("primary-point-of-contact") %}
|
|
||||||
{% include "fragments/flash.html" %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<h2>{{ "fragments.ppoc.title" | translate }}</h2>
|
|
||||||
<p>{{ "fragments.ppoc.subtitle" | translate }}</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<strong>
|
|
||||||
{{ portfolio.owner.first_name }}
|
|
||||||
{{ portfolio.owner.last_name }}
|
|
||||||
</strong>
|
|
||||||
<br />
|
|
||||||
{{ portfolio.owner.email }}
|
|
||||||
<br />
|
|
||||||
{{ portfolio.owner.phone_number | usPhone }}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{% if user_can(permissions.EDIT_PORTFOLIO_POC) %}
|
|
||||||
{% include "portfolios/fragments/change_ppoc.html" %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
@ -1,3 +1,4 @@
|
|||||||
|
import pytest
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
@ -11,29 +12,6 @@ from atst.utils.localization import translate
|
|||||||
from tests.factories import PortfolioFactory, PortfolioRoleFactory, UserFactory
|
from tests.factories import PortfolioFactory, PortfolioRoleFactory, UserFactory
|
||||||
|
|
||||||
|
|
||||||
def test_member_table_access(client, user_session):
|
|
||||||
admin = UserFactory.create()
|
|
||||||
portfolio = PortfolioFactory.create(owner=admin)
|
|
||||||
rando = UserFactory.create()
|
|
||||||
PortfolioRoleFactory.create(
|
|
||||||
user=rando,
|
|
||||||
portfolio=portfolio,
|
|
||||||
permission_sets=[PermissionSets.get(PermissionSets.VIEW_PORTFOLIO_ADMIN)],
|
|
||||||
)
|
|
||||||
|
|
||||||
url = url_for("portfolios.admin", portfolio_id=portfolio.id)
|
|
||||||
|
|
||||||
# editable
|
|
||||||
user_session(admin)
|
|
||||||
edit_resp = client.get(url)
|
|
||||||
assert "<select" in edit_resp.data.decode()
|
|
||||||
|
|
||||||
# not editable
|
|
||||||
user_session(rando)
|
|
||||||
view_resp = client.get(url)
|
|
||||||
assert "<select" not in view_resp.data.decode()
|
|
||||||
|
|
||||||
|
|
||||||
def test_update_portfolio_name_and_description(client, user_session):
|
def test_update_portfolio_name_and_description(client, user_session):
|
||||||
portfolio = PortfolioFactory.create()
|
portfolio = PortfolioFactory.create()
|
||||||
user_session(portfolio.owner)
|
user_session(portfolio.owner)
|
||||||
@ -47,6 +25,7 @@ def test_update_portfolio_name_and_description(client, user_session):
|
|||||||
assert portfolio.description == "a portfolio for things"
|
assert portfolio.description == "a portfolio for things"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip(reason="Out of scope for MVP")
|
||||||
def updating_ppoc_successfully(client, old_ppoc, new_ppoc, portfolio):
|
def updating_ppoc_successfully(client, old_ppoc, new_ppoc, portfolio):
|
||||||
response = client.post(
|
response = client.post(
|
||||||
url_for("portfolios.update_ppoc", portfolio_id=portfolio.id, _external=True),
|
url_for("portfolios.update_ppoc", portfolio_id=portfolio.id, _external=True),
|
||||||
@ -67,6 +46,7 @@ def updating_ppoc_successfully(client, old_ppoc, new_ppoc, portfolio):
|
|||||||
assert Permissions.EDIT_PORTFOLIO_POC not in old_ppoc.permissions
|
assert Permissions.EDIT_PORTFOLIO_POC not in old_ppoc.permissions
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip(reason="Out of scope for MVP")
|
||||||
def test_update_ppoc_no_user_id_specified(client, user_session):
|
def test_update_ppoc_no_user_id_specified(client, user_session):
|
||||||
portfolio = PortfolioFactory.create()
|
portfolio = PortfolioFactory.create()
|
||||||
|
|
||||||
@ -80,6 +60,7 @@ def test_update_ppoc_no_user_id_specified(client, user_session):
|
|||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip(reason="Out of scope for MVP")
|
||||||
def test_update_ppoc_to_member_not_on_portfolio(client, user_session):
|
def test_update_ppoc_to_member_not_on_portfolio(client, user_session):
|
||||||
portfolio = PortfolioFactory.create()
|
portfolio = PortfolioFactory.create()
|
||||||
original_ppoc = portfolio.owner
|
original_ppoc = portfolio.owner
|
||||||
@ -97,6 +78,7 @@ def test_update_ppoc_to_member_not_on_portfolio(client, user_session):
|
|||||||
assert portfolio.owner.id == original_ppoc.id
|
assert portfolio.owner.id == original_ppoc.id
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip(reason="Out of scope for MVP")
|
||||||
def test_update_ppoc_when_ppoc(client, user_session):
|
def test_update_ppoc_when_ppoc(client, user_session):
|
||||||
portfolio = PortfolioFactory.create()
|
portfolio = PortfolioFactory.create()
|
||||||
original_ppoc = portfolio.owner_role
|
original_ppoc = portfolio.owner_role
|
||||||
@ -113,7 +95,8 @@ def test_update_ppoc_when_ppoc(client, user_session):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_update_ppoc_when_cpo(client, user_session):
|
@pytest.mark.skip(reason="Out of scope for MVP")
|
||||||
|
def test_update_ppoc_when_ccpo(client, user_session):
|
||||||
ccpo = UserFactory.create_ccpo()
|
ccpo = UserFactory.create_ccpo()
|
||||||
portfolio = PortfolioFactory.create()
|
portfolio = PortfolioFactory.create()
|
||||||
original_ppoc = portfolio.owner_role
|
original_ppoc = portfolio.owner_role
|
||||||
@ -130,6 +113,7 @@ def test_update_ppoc_when_cpo(client, user_session):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip(reason="Out of scope for MVP")
|
||||||
def test_update_ppoc_when_not_ppoc(client, user_session):
|
def test_update_ppoc_when_not_ppoc(client, user_session):
|
||||||
portfolio = PortfolioFactory.create()
|
portfolio = PortfolioFactory.create()
|
||||||
new_owner = UserFactory.create()
|
new_owner = UserFactory.create()
|
||||||
@ -145,15 +129,6 @@ def test_update_ppoc_when_not_ppoc(client, user_session):
|
|||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
def test_portfolio_admin_screen_when_ppoc(client, user_session):
|
|
||||||
portfolio = PortfolioFactory.create()
|
|
||||||
user_session(portfolio.owner)
|
|
||||||
response = client.get(url_for("portfolios.admin", portfolio_id=portfolio.id))
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert portfolio.name in response.data.decode()
|
|
||||||
assert translate("fragments.ppoc.update_btn").encode("utf8") in response.data
|
|
||||||
|
|
||||||
|
|
||||||
def test_portfolio_admin_screen_when_not_ppoc(client, user_session):
|
def test_portfolio_admin_screen_when_not_ppoc(client, user_session):
|
||||||
portfolio = PortfolioFactory.create()
|
portfolio = PortfolioFactory.create()
|
||||||
user = UserFactory.create()
|
user = UserFactory.create()
|
||||||
@ -254,3 +229,54 @@ def test_remove_portfolio_member_ppoc(client, user_session):
|
|||||||
PortfolioRoles.get(portfolio_id=portfolio.id, user_id=portfolio.owner.id).status
|
PortfolioRoles.get(portfolio_id=portfolio.id, user_id=portfolio.owner.id).status
|
||||||
== PortfolioRoleStatus.ACTIVE
|
== PortfolioRoleStatus.ACTIVE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_portfolios_update_member(client, user_session):
|
||||||
|
portfolio = PortfolioFactory.create()
|
||||||
|
portfolio_role = PortfolioRoleFactory.create(
|
||||||
|
portfolio=portfolio,
|
||||||
|
permission_sets=[PermissionSets.get(PermissionSets.EDIT_PORTFOLIO_ADMIN)],
|
||||||
|
)
|
||||||
|
|
||||||
|
form_data = {
|
||||||
|
"perms_app_mgmt": "y",
|
||||||
|
}
|
||||||
|
|
||||||
|
user_session(portfolio.owner)
|
||||||
|
response = client.post(
|
||||||
|
url_for(
|
||||||
|
"portfolios.update_member",
|
||||||
|
portfolio_id=portfolio.id,
|
||||||
|
portfolio_role_id=portfolio_role.id,
|
||||||
|
),
|
||||||
|
data=form_data,
|
||||||
|
follow_redirects=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 302
|
||||||
|
assert portfolio_role.has_permission_set(
|
||||||
|
PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT
|
||||||
|
)
|
||||||
|
assert not portfolio_role.has_permission_set(PermissionSets.EDIT_PORTFOLIO_ADMIN)
|
||||||
|
|
||||||
|
|
||||||
|
def test_can_not_update_ppoc_permissions(client, user_session):
|
||||||
|
portfolio = PortfolioFactory.create()
|
||||||
|
owner = portfolio.owner
|
||||||
|
|
||||||
|
form_data = {
|
||||||
|
"perms_app_mgmt": "y",
|
||||||
|
}
|
||||||
|
|
||||||
|
user_session(owner)
|
||||||
|
response = client.post(
|
||||||
|
url_for(
|
||||||
|
"portfolios.update_member",
|
||||||
|
portfolio_id=portfolio.id,
|
||||||
|
portfolio_role_id=portfolio.owner_role.id,
|
||||||
|
),
|
||||||
|
data=form_data,
|
||||||
|
follow_redirects=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 400
|
||||||
|
@ -219,11 +219,11 @@ def test_resend_invitation_sends_email(monkeypatch, client, user_session):
|
|||||||
monkeypatch.setattr("atst.jobs.send_mail.delay", job_mock)
|
monkeypatch.setattr("atst.jobs.send_mail.delay", job_mock)
|
||||||
user = UserFactory.create()
|
user = UserFactory.create()
|
||||||
portfolio = PortfolioFactory.create()
|
portfolio = PortfolioFactory.create()
|
||||||
ws_role = PortfolioRoleFactory.create(
|
portfolio_role = PortfolioRoleFactory.create(
|
||||||
user=user, portfolio=portfolio, status=PortfolioRoleStatus.PENDING
|
user=user, portfolio=portfolio, status=PortfolioRoleStatus.PENDING
|
||||||
)
|
)
|
||||||
invite = PortfolioInvitationFactory.create(
|
invite = PortfolioInvitationFactory.create(
|
||||||
user_id=user.id, role=ws_role, status=InvitationStatus.PENDING
|
user_id=user.id, role=portfolio_role, status=InvitationStatus.PENDING
|
||||||
)
|
)
|
||||||
user_session(portfolio.owner)
|
user_session(portfolio.owner)
|
||||||
client.post(
|
client.post(
|
||||||
@ -231,48 +231,23 @@ def test_resend_invitation_sends_email(monkeypatch, client, user_session):
|
|||||||
"portfolios.resend_invitation",
|
"portfolios.resend_invitation",
|
||||||
portfolio_id=portfolio.id,
|
portfolio_id=portfolio.id,
|
||||||
portfolio_token=invite.token,
|
portfolio_token=invite.token,
|
||||||
)
|
),
|
||||||
|
data={
|
||||||
|
"user_data-dod_id": user.dod_id,
|
||||||
|
"user_data-first_name": user.first_name,
|
||||||
|
"user_data-last_name": user.last_name,
|
||||||
|
"user_data-email": user.email,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert job_mock.called
|
assert job_mock.called
|
||||||
|
|
||||||
|
|
||||||
def test_existing_member_invite_resent_to_email_submitted_in_form(
|
|
||||||
monkeypatch, client, user_session
|
|
||||||
):
|
|
||||||
job_mock = Mock()
|
|
||||||
monkeypatch.setattr("atst.jobs.send_mail.delay", job_mock)
|
|
||||||
portfolio = PortfolioFactory.create()
|
|
||||||
user = UserFactory.create()
|
|
||||||
ws_role = PortfolioRoleFactory.create(
|
|
||||||
user=user, portfolio=portfolio, status=PortfolioRoleStatus.PENDING
|
|
||||||
)
|
|
||||||
invite = PortfolioInvitationFactory.create(
|
|
||||||
user_id=user.id,
|
|
||||||
role=ws_role,
|
|
||||||
status=InvitationStatus.PENDING,
|
|
||||||
email="example@example.com",
|
|
||||||
)
|
|
||||||
user_session(portfolio.owner)
|
|
||||||
client.post(
|
|
||||||
url_for(
|
|
||||||
"portfolios.resend_invitation",
|
|
||||||
portfolio_id=portfolio.id,
|
|
||||||
portfolio_token=invite.token,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
assert user.email != "example@example.com"
|
|
||||||
ordered_args, _unordered_args = job_mock.call_args
|
|
||||||
recipients, _subject, _message = ordered_args
|
|
||||||
assert recipients[0] == "example@example.com"
|
|
||||||
|
|
||||||
|
|
||||||
_DEFAULT_PERMS_FORM_DATA = {
|
_DEFAULT_PERMS_FORM_DATA = {
|
||||||
"permission_sets-perms_app_mgmt": False,
|
"permission_sets-perms_app_mgmt": "n",
|
||||||
"permission_sets-perms_funding": False,
|
"permission_sets-perms_funding": "n",
|
||||||
"permission_sets-perms_reporting": False,
|
"permission_sets-perms_reporting": "n",
|
||||||
"permission_sets-perms_portfolio_mgmt": False,
|
"permission_sets-perms_portfolio_mgmt": "n",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -373,6 +373,24 @@ def test_portfolios_edit_access(post_url_assert_status):
|
|||||||
post_url_assert_status(rando, url, 404)
|
post_url_assert_status(rando, url, 404)
|
||||||
|
|
||||||
|
|
||||||
|
# portfolios.update_member
|
||||||
|
def test_portfolios_update_member_access(post_url_assert_status):
|
||||||
|
ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_ADMIN)
|
||||||
|
owner = user_with()
|
||||||
|
rando = user_with()
|
||||||
|
portfolio = PortfolioFactory.create(owner=owner)
|
||||||
|
portfolio_role = PortfolioRoleFactory.create(portfolio=portfolio)
|
||||||
|
|
||||||
|
url = url_for(
|
||||||
|
"portfolios.update_member",
|
||||||
|
portfolio_id=portfolio.id,
|
||||||
|
portfolio_role_id=portfolio_role.id,
|
||||||
|
)
|
||||||
|
post_url_assert_status(ccpo, url, 302)
|
||||||
|
post_url_assert_status(owner, url, 302)
|
||||||
|
post_url_assert_status(rando, url, 404)
|
||||||
|
|
||||||
|
|
||||||
# applications.new
|
# applications.new
|
||||||
def test_applications_new_access(get_url_assert_status):
|
def test_applications_new_access(get_url_assert_status):
|
||||||
ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT)
|
ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT)
|
||||||
|
@ -169,10 +169,19 @@ flash:
|
|||||||
revoked:
|
revoked:
|
||||||
title: Removed portfolio access
|
title: Removed portfolio access
|
||||||
message: Portfolio access successfully removed from {member_name}.
|
message: Portfolio access successfully removed from {member_name}.
|
||||||
|
update:
|
||||||
|
title: Success!
|
||||||
|
message: You have successfully updated access permissions for {member_name}.
|
||||||
|
update_error:
|
||||||
|
title: Permissions for {member_name} could not be updated
|
||||||
|
message: An unexpected problem occurred with your request, please try again. If the problem persists, contact an administrator.
|
||||||
portfolio_invite:
|
portfolio_invite:
|
||||||
resent:
|
resent:
|
||||||
title: Invitation resent
|
title: Invitation resent
|
||||||
message: Successfully sent a new invitation to {user_name}.
|
message: Successfully sent a new invitation to {user_name}.
|
||||||
|
error:
|
||||||
|
title: Portfolio invitation error
|
||||||
|
message: There was an error processing the invitation for {user_name}.
|
||||||
session_expired:
|
session_expired:
|
||||||
title: Session Expired
|
title: Session Expired
|
||||||
message: Your session expired due to inactivity. Please log in again to continue.
|
message: Your session expired due to inactivity. Please log in again to continue.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user