Merge pull request #1294 from dod-ccpo/portfolio-admin-styling__part-2
Portfolio admin styling - Managers table
This commit is contained in:
commit
7de2f440c6
@ -75,10 +75,10 @@ class Portfolios(object):
|
||||
permission_sets = PortfolioRoles._permission_sets_for_names(
|
||||
member_data.get("permission_sets", [])
|
||||
)
|
||||
role = PortfolioRole(portfolio_id=portfolio.id, permission_sets=permission_sets)
|
||||
role = PortfolioRole(portfolio=portfolio, permission_sets=permission_sets)
|
||||
|
||||
invitation = PortfolioInvitations.create(
|
||||
inviter=inviter, role=role, member_data=member_data
|
||||
inviter=inviter, role=role, member_data=member_data["user_data"]
|
||||
)
|
||||
|
||||
PortfoliosQuery.add_and_commit(role)
|
||||
|
@ -1,76 +1,59 @@
|
||||
from wtforms.validators import Required
|
||||
from wtforms.fields import StringField, FormField, FieldList, HiddenField
|
||||
from wtforms.fields import BooleanField, FormField
|
||||
|
||||
from atst.domain.permission_sets import PermissionSets
|
||||
from .forms import BaseForm
|
||||
from .member import NewForm as BaseNewMemberForm
|
||||
from atst.domain.permission_sets import PermissionSets
|
||||
from atst.forms.fields import SelectField
|
||||
from atst.utils.localization import translate
|
||||
|
||||
|
||||
class PermissionsForm(BaseForm):
|
||||
member_name = StringField()
|
||||
member_id = HiddenField()
|
||||
perms_app_mgmt = SelectField(
|
||||
translate("forms.new_member.app_mgmt"),
|
||||
choices=[
|
||||
(
|
||||
PermissionSets.VIEW_PORTFOLIO_APPLICATION_MANAGEMENT,
|
||||
translate("common.view"),
|
||||
),
|
||||
(
|
||||
PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT,
|
||||
translate("common.edit"),
|
||||
),
|
||||
],
|
||||
perms_app_mgmt = BooleanField(
|
||||
translate("forms.new_member.app_mgmt.label"),
|
||||
default=False,
|
||||
description=translate("forms.new_member.app_mgmt.description"),
|
||||
)
|
||||
perms_funding = SelectField(
|
||||
translate("forms.new_member.funding"),
|
||||
choices=[
|
||||
(PermissionSets.VIEW_PORTFOLIO_FUNDING, translate("common.view")),
|
||||
(PermissionSets.EDIT_PORTFOLIO_FUNDING, translate("common.edit")),
|
||||
],
|
||||
perms_funding = BooleanField(
|
||||
translate("forms.new_member.funding.label"),
|
||||
default=False,
|
||||
description=translate("forms.new_member.funding.description"),
|
||||
)
|
||||
perms_reporting = SelectField(
|
||||
translate("forms.new_member.reporting"),
|
||||
choices=[
|
||||
(PermissionSets.VIEW_PORTFOLIO_REPORTS, translate("common.view")),
|
||||
(PermissionSets.EDIT_PORTFOLIO_REPORTS, translate("common.edit")),
|
||||
],
|
||||
perms_reporting = BooleanField(
|
||||
translate("forms.new_member.reporting.label"),
|
||||
default=False,
|
||||
description=translate("forms.new_member.reporting.description"),
|
||||
)
|
||||
perms_portfolio_mgmt = SelectField(
|
||||
translate("forms.new_member.portfolio_mgmt"),
|
||||
choices=[
|
||||
(PermissionSets.VIEW_PORTFOLIO_ADMIN, translate("common.view")),
|
||||
(PermissionSets.EDIT_PORTFOLIO_ADMIN, translate("common.edit")),
|
||||
],
|
||||
perms_portfolio_mgmt = BooleanField(
|
||||
translate("forms.new_member.portfolio_mgmt.label"),
|
||||
default=False,
|
||||
description=translate("forms.new_member.portfolio_mgmt.description"),
|
||||
)
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
_data = super().data
|
||||
_data["permission_sets"] = []
|
||||
for field in _data:
|
||||
if "perms" in field:
|
||||
_data["permission_sets"].append(_data[field])
|
||||
_data.pop("csrf_token", None)
|
||||
perm_sets = []
|
||||
|
||||
if _data["perms_app_mgmt"]:
|
||||
perm_sets.append(PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT)
|
||||
|
||||
if _data["perms_funding"]:
|
||||
perm_sets.append(PermissionSets.EDIT_PORTFOLIO_FUNDING)
|
||||
|
||||
if _data["perms_reporting"]:
|
||||
perm_sets.append(PermissionSets.EDIT_PORTFOLIO_REPORTS)
|
||||
|
||||
if _data["perms_portfolio_mgmt"]:
|
||||
perm_sets.append(PermissionSets.EDIT_PORTFOLIO_ADMIN)
|
||||
|
||||
_data["permission_sets"] = perm_sets
|
||||
return _data
|
||||
|
||||
|
||||
class MembersPermissionsForm(BaseForm):
|
||||
members_permissions = FieldList(FormField(PermissionsForm))
|
||||
|
||||
|
||||
class NewForm(BaseForm):
|
||||
class NewForm(PermissionsForm):
|
||||
user_data = FormField(BaseNewMemberForm)
|
||||
permission_sets = FormField(PermissionsForm)
|
||||
|
||||
@property
|
||||
def update_data(self):
|
||||
return {
|
||||
"permission_sets": self.data.get("permission_sets").get("permission_sets"),
|
||||
**self.data.get("user_data"),
|
||||
}
|
||||
|
||||
|
||||
class AssignPPOCForm(PermissionsForm):
|
||||
|
@ -12,17 +12,6 @@ from atst.utils import first_or_none
|
||||
from atst.models.mixins.auditable import record_permission_sets_updates
|
||||
|
||||
|
||||
MEMBER_STATUSES = {
|
||||
"active": "Active",
|
||||
"revoked": "Invite revoked",
|
||||
"expired": "Invite expired",
|
||||
"error": "Error on invite",
|
||||
"pending": "Pending",
|
||||
"unknown": "Unknown errors",
|
||||
"disabled": "Disabled",
|
||||
}
|
||||
|
||||
|
||||
class Status(Enum):
|
||||
ACTIVE = "active"
|
||||
DISABLED = "disabled"
|
||||
@ -90,23 +79,23 @@ class PortfolioRole(
|
||||
@property
|
||||
def display_status(self):
|
||||
if self.status == Status.ACTIVE:
|
||||
return MEMBER_STATUSES["active"]
|
||||
return "active"
|
||||
elif self.status == Status.DISABLED:
|
||||
return MEMBER_STATUSES["disabled"]
|
||||
return "disabled"
|
||||
elif self.latest_invitation:
|
||||
if self.latest_invitation.is_revoked:
|
||||
return MEMBER_STATUSES["revoked"]
|
||||
return "invite_revoked"
|
||||
elif self.latest_invitation.is_rejected_wrong_user:
|
||||
return MEMBER_STATUSES["error"]
|
||||
return "invite_error"
|
||||
elif (
|
||||
self.latest_invitation.is_rejected_expired
|
||||
or self.latest_invitation.is_expired
|
||||
):
|
||||
return MEMBER_STATUSES["expired"]
|
||||
return "invite_expired"
|
||||
else:
|
||||
return MEMBER_STATUSES["pending"]
|
||||
return "invite_pending"
|
||||
else:
|
||||
return MEMBER_STATUSES["unknown"]
|
||||
return "unknown"
|
||||
|
||||
def has_permission_set(self, perm_set_name):
|
||||
return first_or_none(
|
||||
|
@ -17,63 +17,51 @@ from atst.utils.flash import formatted_flash as flash
|
||||
from atst.domain.exceptions import UnauthorizedError
|
||||
|
||||
|
||||
def permission_str(member, edit_perm_set, view_perm_set):
|
||||
if member.has_permission_set(edit_perm_set):
|
||||
return edit_perm_set
|
||||
else:
|
||||
return view_perm_set
|
||||
|
||||
|
||||
def serialize_member_form_data(member):
|
||||
return {
|
||||
"member_name": member.full_name,
|
||||
"member_id": member.id,
|
||||
"perms_app_mgmt": permission_str(
|
||||
member,
|
||||
PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT,
|
||||
PermissionSets.VIEW_PORTFOLIO_APPLICATION_MANAGEMENT,
|
||||
def filter_perm_sets_data(member):
|
||||
perm_sets_data = {
|
||||
"perms_portfolio_mgmt": bool(
|
||||
member.has_permission_set(PermissionSets.EDIT_PORTFOLIO_ADMIN)
|
||||
),
|
||||
"perms_funding": permission_str(
|
||||
member,
|
||||
PermissionSets.EDIT_PORTFOLIO_FUNDING,
|
||||
PermissionSets.VIEW_PORTFOLIO_FUNDING,
|
||||
"perms_app_mgmt": bool(
|
||||
member.has_permission_set(
|
||||
PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT
|
||||
)
|
||||
),
|
||||
"perms_reporting": permission_str(
|
||||
member,
|
||||
PermissionSets.EDIT_PORTFOLIO_REPORTS,
|
||||
PermissionSets.VIEW_PORTFOLIO_REPORTS,
|
||||
"perms_funding": bool(
|
||||
member.has_permission_set(PermissionSets.EDIT_PORTFOLIO_FUNDING)
|
||||
),
|
||||
"perms_portfolio_mgmt": permission_str(
|
||||
member,
|
||||
PermissionSets.EDIT_PORTFOLIO_ADMIN,
|
||||
PermissionSets.VIEW_PORTFOLIO_ADMIN,
|
||||
"perms_reporting": bool(
|
||||
member.has_permission_set(PermissionSets.EDIT_PORTFOLIO_REPORTS)
|
||||
),
|
||||
}
|
||||
|
||||
return perm_sets_data
|
||||
|
||||
def get_members_data(portfolio):
|
||||
members = sorted(
|
||||
[serialize_member_form_data(member) for member in portfolio.members],
|
||||
key=lambda member: member["member_name"],
|
||||
|
||||
def filter_members_data(members_list, portfolio):
|
||||
members_data = []
|
||||
for member in members_list:
|
||||
members_data.append(
|
||||
{
|
||||
"role_id": member.id,
|
||||
"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
|
||||
}
|
||||
)
|
||||
for member in members:
|
||||
if member["member_id"] == portfolio.owner_role.id:
|
||||
ppoc = member
|
||||
members.remove(member)
|
||||
members.insert(0, ppoc)
|
||||
return members
|
||||
|
||||
return sorted(members_data, key=lambda member: member["user_name"])
|
||||
|
||||
|
||||
def render_admin_page(portfolio, form=None):
|
||||
pagination_opts = Paginator.get_pagination_opts(http_request)
|
||||
audit_events = AuditLog.get_portfolio_events(portfolio, pagination_opts)
|
||||
members_data = get_members_data(portfolio)
|
||||
portfolio_form = PortfolioForm(obj=portfolio)
|
||||
member_perms_form = member_forms.MembersPermissionsForm(
|
||||
data={"members_permissions": members_data}
|
||||
)
|
||||
|
||||
member_list = portfolio.members
|
||||
assign_ppoc_form = member_forms.AssignPPOCForm()
|
||||
|
||||
for pf_role in portfolio.roles:
|
||||
if pf_role.user != portfolio.owner and pf_role.is_active:
|
||||
assign_ppoc_form.role_id.choices += [(pf_role.id, pf_role.full_name)]
|
||||
@ -87,13 +75,12 @@ def render_admin_page(portfolio, form=None):
|
||||
"portfolios/admin.html",
|
||||
form=form,
|
||||
portfolio_form=portfolio_form,
|
||||
member_perms_form=member_perms_form,
|
||||
member_form=member_forms.NewForm(),
|
||||
members=filter_members_data(member_list, portfolio),
|
||||
new_manager_form=member_forms.NewForm(),
|
||||
assign_ppoc_form=assign_ppoc_form,
|
||||
portfolio=portfolio,
|
||||
audit_events=audit_events,
|
||||
user=g.current_user,
|
||||
ppoc_id=members_data[0].get("member_id"),
|
||||
current_member_id=current_member_id,
|
||||
applications_count=len(portfolio.applications),
|
||||
)
|
||||
@ -106,34 +93,6 @@ def admin(portfolio_id):
|
||||
return render_admin_page(portfolio)
|
||||
|
||||
|
||||
@portfolios_bp.route("/portfolios/<portfolio_id>/admin", methods=["POST"])
|
||||
@user_can(Permissions.EDIT_PORTFOLIO_USERS, message="view portfolio admin page")
|
||||
def edit_members(portfolio_id):
|
||||
portfolio = Portfolios.get_for_update(portfolio_id)
|
||||
member_perms_form = member_forms.MembersPermissionsForm(http_request.form)
|
||||
|
||||
if member_perms_form.validate():
|
||||
for subform in member_perms_form.members_permissions:
|
||||
member_id = subform.member_id.data
|
||||
member = PortfolioRoles.get_by_id(member_id)
|
||||
if member is not portfolio.owner_role:
|
||||
new_perm_set = subform.data["permission_sets"]
|
||||
PortfolioRoles.update(member, new_perm_set)
|
||||
|
||||
flash("update_portfolio_members", portfolio=portfolio)
|
||||
|
||||
return redirect(
|
||||
url_for(
|
||||
"portfolios.admin",
|
||||
portfolio_id=portfolio_id,
|
||||
fragment="portfolio-members",
|
||||
_anchor="portfolio-members",
|
||||
)
|
||||
)
|
||||
else:
|
||||
return render_admin_page(portfolio)
|
||||
|
||||
|
||||
@portfolios_bp.route("/portfolios/<portfolio_id>/update_ppoc", methods=["POST"])
|
||||
@user_can(Permissions.EDIT_PORTFOLIO_POC, message="update portfolio ppoc")
|
||||
def update_ppoc(portfolio_id):
|
||||
|
@ -79,7 +79,7 @@ def invite_member(portfolio_id):
|
||||
|
||||
if form.validate():
|
||||
try:
|
||||
invite = Portfolios.invite(portfolio, g.current_user, form.update_data)
|
||||
invite = Portfolios.invite(portfolio, g.current_user, form.data)
|
||||
send_portfolio_invitation(
|
||||
invitee_email=invite.email,
|
||||
inviter_name=g.current_user.full_name,
|
||||
|
@ -159,7 +159,7 @@ def get_users():
|
||||
|
||||
def add_members_to_portfolio(portfolio):
|
||||
for user_data in PORTFOLIO_USERS:
|
||||
invite = Portfolios.invite(portfolio, portfolio.owner, user_data)
|
||||
invite = Portfolios.invite(portfolio, portfolio.owner, {"user_data": user_data})
|
||||
profile = {
|
||||
k: user_data[k] for k in user_data if k not in ["dod_id", "permission_sets"]
|
||||
}
|
||||
|
@ -38,6 +38,7 @@
|
||||
@import "components/dod_login_notice.scss";
|
||||
@import "components/sticky_cta.scss";
|
||||
@import "components/error_page.scss";
|
||||
@import "components/member_form.scss";
|
||||
|
||||
@import "sections/login";
|
||||
@import "sections/home";
|
||||
|
61
styles/components/_member_form.scss
Normal file
61
styles/components/_member_form.scss
Normal file
@ -0,0 +1,61 @@
|
||||
.member-form {
|
||||
text-align: left;
|
||||
|
||||
input[type="checkbox"] + label::before {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.input__inline-fields {
|
||||
text-align: left;
|
||||
|
||||
.usa-input__choices label {
|
||||
font-weight: $font-bold;
|
||||
}
|
||||
}
|
||||
|
||||
.input__inline-fields {
|
||||
padding: $gap * 2;
|
||||
border: 1px solid $color-gray-lighter;
|
||||
|
||||
&.checked {
|
||||
border: 1px solid $color-blue;
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: $font-bold;
|
||||
}
|
||||
|
||||
p.usa-input__help {
|
||||
margin-bottom: 0;
|
||||
padding-left: 3rem;
|
||||
}
|
||||
}
|
||||
|
||||
.user-info {
|
||||
.usa-input {
|
||||
width: 45rem;
|
||||
|
||||
input,
|
||||
label,
|
||||
.usa-input__message {
|
||||
max-width: unset;
|
||||
}
|
||||
|
||||
label .icon-validation {
|
||||
left: unset;
|
||||
right: -$gap * 4;
|
||||
}
|
||||
|
||||
&--validation--phoneExt {
|
||||
width: 18rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#modal--add-app-mem,
|
||||
#modal--add-portfolio-manager {
|
||||
.modal__body {
|
||||
min-width: 75rem;
|
||||
}
|
||||
}
|
@ -5,13 +5,6 @@
|
||||
}
|
||||
|
||||
margin-left: 2 * $gap;
|
||||
|
||||
.line {
|
||||
box-sizing: border-box;
|
||||
height: 2px;
|
||||
width: 100%;
|
||||
border: 1px solid $color-gray-lightest;
|
||||
}
|
||||
}
|
||||
|
||||
.portfolio-header {
|
||||
@ -40,36 +33,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__budget {
|
||||
font-size: $small-font-size;
|
||||
align-items: center;
|
||||
|
||||
.icon-tooltip {
|
||||
margin-left: -$gap / 2;
|
||||
}
|
||||
|
||||
button {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&--dollars {
|
||||
font-size: $h2-font-size;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&--amount {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&--cents {
|
||||
font-size: 2rem;
|
||||
margin-top: 0.75rem;
|
||||
margin-left: -0.7rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.links {
|
||||
justify-content: center;
|
||||
font-size: $small-font-size;
|
||||
@ -109,22 +72,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.column-left {
|
||||
width: 12.5rem;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.column-right {
|
||||
margin-left: -0.4rem;
|
||||
}
|
||||
|
||||
.unfunded {
|
||||
color: $color-red;
|
||||
.icon {
|
||||
@include icon-color($color-red);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin subheading {
|
||||
@ -138,6 +85,10 @@
|
||||
.portfolio-content {
|
||||
margin: (4 * $gap) $gap 0 $gap;
|
||||
|
||||
.panel {
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
|
||||
a.add-new-button {
|
||||
display: inherit;
|
||||
margin-left: auto;
|
||||
@ -157,44 +108,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
input.usa-button.usa-button-primary {
|
||||
width: 9rem;
|
||||
height: 4rem;
|
||||
}
|
||||
|
||||
select {
|
||||
padding-left: 1.2rem;
|
||||
}
|
||||
|
||||
.members-table-ppoc {
|
||||
select::-ms-expand {
|
||||
display: none;
|
||||
color: $color-gray;
|
||||
}
|
||||
|
||||
select {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
display: block;
|
||||
width: 100%;
|
||||
float: right;
|
||||
margin: 5px 0px;
|
||||
padding: 0px 24px;
|
||||
background-image: none;
|
||||
-ms-word-break: normal;
|
||||
word-break: normal;
|
||||
padding-right: 3rem;
|
||||
padding-left: 1.2rem;
|
||||
color: $color-gray;
|
||||
}
|
||||
|
||||
select:hover {
|
||||
box-shadow: none;
|
||||
color: $color-gray;
|
||||
}
|
||||
}
|
||||
|
||||
a.modal-link.icon-link {
|
||||
float: right;
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
.usa-button,
|
||||
a {
|
||||
margin: 0 0 0 $gap;
|
||||
cursor: pointer;
|
||||
|
||||
@include media($medium-screen) {
|
||||
margin: 0 0 0 ($gap * 2);
|
||||
|
@ -21,24 +21,8 @@ table.atat-table {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&--align-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&--shrink {
|
||||
width: 1%;
|
||||
}
|
||||
|
||||
&--expand {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&--hide-small {
|
||||
display: none;
|
||||
|
||||
@include media($medium-screen) {
|
||||
display: table-cell;
|
||||
}
|
||||
&--third {
|
||||
width: 33%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -83,28 +67,6 @@ table.atat-table {
|
||||
|
||||
@include panel-margin;
|
||||
|
||||
&__header {
|
||||
@include panel-base;
|
||||
@include panel-theme-default;
|
||||
|
||||
border-top: none;
|
||||
border-bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: $gap * 2;
|
||||
|
||||
&__title {
|
||||
@include h4;
|
||||
|
||||
font-size: $lead-font-size;
|
||||
justify-content: space-between;
|
||||
flex: 2;
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
@ -23,66 +23,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
#modal--add-app-mem,
|
||||
.form-content--app-mem {
|
||||
text-align: left;
|
||||
|
||||
.modal__body {
|
||||
min-width: 75rem;
|
||||
}
|
||||
|
||||
input[type="checkbox"] + label::before {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.input__inline-fields {
|
||||
text-align: left;
|
||||
|
||||
.usa-input__choices label {
|
||||
font-weight: $font-bold;
|
||||
}
|
||||
}
|
||||
|
||||
.input__inline-fields {
|
||||
padding: $gap * 2;
|
||||
border: 1px solid $color-gray-lighter;
|
||||
|
||||
&.checked {
|
||||
border: 1px solid $color-blue;
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: $font-bold;
|
||||
}
|
||||
|
||||
p.usa-input__help {
|
||||
margin-bottom: 0;
|
||||
padding-left: 3rem;
|
||||
}
|
||||
}
|
||||
|
||||
.application-member__user-info {
|
||||
.usa-input {
|
||||
width: 45rem;
|
||||
|
||||
input,
|
||||
label,
|
||||
.usa-input__message {
|
||||
max-width: unset;
|
||||
}
|
||||
|
||||
label .icon-validation {
|
||||
left: unset;
|
||||
right: -$gap * 4;
|
||||
}
|
||||
|
||||
&--validation--phoneExt {
|
||||
width: 18rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.environment-roles {
|
||||
padding: 0 ($gap * 3) ($gap * 3);
|
||||
|
||||
|
@ -118,7 +118,7 @@
|
||||
{% endmacro %}
|
||||
|
||||
{% macro InfoFields(member_form) %}
|
||||
<div class="application-member__user-info">
|
||||
<div class="user-info">
|
||||
{{ TextInput(member_form.first_name, validation='requiredField', optional=False) }}
|
||||
{{ TextInput(member_form.last_name, validation='requiredField', optional=False) }}
|
||||
{{ TextInput(member_form.email, validation='email', optional=False) }}
|
||||
|
@ -1,12 +1,10 @@
|
||||
{% from "components/alert.html" import Alert %}
|
||||
{% from "components/icon.html" import Icon %}
|
||||
{% from "components/label.html" import Label %}
|
||||
{% import "applications/fragments/new_member_modal_content.html" as member_steps %}
|
||||
{% import "components/member_form.html" as member_form %}
|
||||
{% import "applications/fragments/member_form_fields.html" as member_fields %}
|
||||
{% from "components/modal.html" import Modal %}
|
||||
{% from "components/multi_step_modal_form.html" import MultiStepModalForm %}
|
||||
{% from "components/save_button.html" import SaveButton %}
|
||||
{% from "components/toggle_list.html" import ToggleButton, ToggleSection %}
|
||||
|
||||
{% macro MemberManagementTemplate(
|
||||
application,
|
||||
@ -179,8 +177,19 @@
|
||||
form=new_member_form,
|
||||
form_action=url_for(action_new, application_id=application.id),
|
||||
steps=[
|
||||
member_steps.MemberStepOne(new_member_form),
|
||||
member_steps.MemberStepTwo(new_member_form, application)
|
||||
member_form.BasicStep(
|
||||
title="portfolios.applications.members.form.add_member"|translate,
|
||||
form=member_fields.InfoFields(new_member_form.user_data),
|
||||
next_button_text="portfolios.applications.members.form.next_button"|translate,
|
||||
previous=False,
|
||||
modal=new_member_modal_name,
|
||||
),
|
||||
member_form.SubmitStep(
|
||||
name=new_member_modal_name,
|
||||
form=member_fields.PermsFields(form=new_member_form, new=True),
|
||||
submit_text="portfolios.applications.members.form.add_member"|translate,
|
||||
modal=new_member_modal_name,
|
||||
)
|
||||
],
|
||||
) }}
|
||||
{% endif %}
|
||||
|
@ -1,50 +0,0 @@
|
||||
{% from "components/icon.html" import Icon %}
|
||||
{% import "applications/fragments/member_form_fields.html" as member_fields %}
|
||||
|
||||
{% macro MemberFormTemplate(title=None, next_button=None, previous=True) %}
|
||||
<hr class="full-width">
|
||||
{% if title %} <h1>{{ title }}</h1> {% endif %}
|
||||
|
||||
{{ caller() }}
|
||||
|
||||
<div class='action-group'>
|
||||
{{ next_button }}
|
||||
{% if previous %}
|
||||
<input
|
||||
type='button'
|
||||
v-on:click="previous()"
|
||||
class='action-group__action usa-button usa-button-secondary'
|
||||
value='{{ "common.previous" | translate }}'>
|
||||
{% endif %}
|
||||
<a class='action-group__action' v-on:click="closeModal('{{ new_port_mem }}')">{{ "common.cancel" | translate }}</a>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro MemberStepOne(member_form) %}
|
||||
{% set next_button %}
|
||||
<input
|
||||
type='button'
|
||||
v-on:click="next()"
|
||||
v-bind:disabled="!canSave"
|
||||
class='action-group__action usa-button'
|
||||
value='{{ "portfolios.applications.members.form.next_button" | translate }}'>
|
||||
{% endset %}
|
||||
|
||||
{% call MemberFormTemplate(title="portfolios.applications.members.form.add_member"|translate, next_button=next_button, previous=False) %}
|
||||
{{ member_fields.InfoFields(member_form.user_data) }}
|
||||
{% endcall %}
|
||||
{% endmacro %}
|
||||
{% macro MemberStepTwo(member_form, application) %}
|
||||
{% set next_button %}
|
||||
<input
|
||||
type="submit"
|
||||
class='action-group__action usa-button'
|
||||
form="add-app-mem"
|
||||
v-bind:disabled="!canSave"
|
||||
value='{{ "portfolios.applications.members.form.add_member" | translate}}'>
|
||||
{% endset %}
|
||||
|
||||
{% call MemberFormTemplate(next_button=next_button) %}
|
||||
{{ member_fields.PermsFields(form=member_form, new=True) }}
|
||||
{% endcall %}
|
||||
{% endmacro %}
|
@ -9,11 +9,15 @@
|
||||
"text": "changes pending",
|
||||
"color": "default",
|
||||
},
|
||||
"ppoc": {"text": "primary point of contact"}
|
||||
} %}
|
||||
|
||||
{% if type -%}
|
||||
{% if type in label_info.keys() -%}
|
||||
<span class='label label--{{ label_info[type]["color"] }} {{ classes }}'>
|
||||
{{ Icon(label_info[type]["icon"]) }} {{ label_info[type]["text"] }}
|
||||
{% if label_info[type]["icon"] %}
|
||||
{{ Icon(label_info[type]["icon"]) }}
|
||||
{% endif %}
|
||||
{{ label_info[type]["text"] }}
|
||||
</span>
|
||||
{%- endif %}
|
||||
{%- endmacro %}
|
||||
|
65
templates/components/member_form.html
Normal file
65
templates/components/member_form.html
Normal file
@ -0,0 +1,65 @@
|
||||
<!-- Layout macro -->
|
||||
{% macro MemberForm(title=None, next_button=None, previous=True, modal=modal) %}
|
||||
<div class="member-form">
|
||||
<hr class="full-width">
|
||||
{% if title %} <h2>{{ title }}</h2> {% endif %}
|
||||
|
||||
{{ caller() }}
|
||||
</div>
|
||||
<div class='action-group'>
|
||||
{{ next_button }}
|
||||
{% if previous %}
|
||||
<input
|
||||
type='button'
|
||||
v-on:click="previous()"
|
||||
class='action-group__action usa-button usa-button-secondary'
|
||||
value='{{ "common.previous" | translate }}'>
|
||||
{% endif %}
|
||||
<a class='action-group__action' v-on:click="closeModal('{{ modal }}')">{{ "common.cancel" | translate }}</a>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
<!-- Step macros to use with MultiStepModalForm -->
|
||||
{% macro BasicStep(
|
||||
title=None,
|
||||
form=form,
|
||||
next_button_text=next_button_text,
|
||||
previous=True,
|
||||
modal=modal
|
||||
) %}
|
||||
{% set next_button %}
|
||||
<input
|
||||
type='button'
|
||||
v-on:click="next()"
|
||||
v-bind:disabled="!canSave"
|
||||
class='action-group__action usa-button'
|
||||
value='{{ next_button_text }}'>
|
||||
{% endset %}
|
||||
|
||||
{% call MemberForm(title=title, next_button=next_button, previous=previous, modal=modal) %}
|
||||
{{ form }}
|
||||
{% endcall %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro SubmitStep(
|
||||
name=name,
|
||||
title=None,
|
||||
form=form,
|
||||
submit_text=submit_text,
|
||||
previous=True,
|
||||
modal=modal
|
||||
) %}
|
||||
{% set next_button %}
|
||||
<input
|
||||
type="submit"
|
||||
class='action-group__action usa-button'
|
||||
form="{{ name }}"
|
||||
v-bind:disabled="!canSave"
|
||||
value='{{ submit_text }}'>
|
||||
{% endset %}
|
||||
|
||||
{% call MemberForm(title=title, next_button=next_button, previous=previous, modal=modal) %}
|
||||
{{ form }}
|
||||
{% endcall %}
|
||||
{% endmacro %}
|
@ -1,5 +1,6 @@
|
||||
{% extends "portfolios/base.html" %}
|
||||
|
||||
{% from "components/label.html" import Label %}
|
||||
{% from "components/pagination.html" import Pagination %}
|
||||
{% from 'components/save_button.html' import SaveButton %}
|
||||
{% from 'components/sticky_cta.html' import StickyCTA %}
|
||||
@ -9,8 +10,8 @@
|
||||
|
||||
{{ StickyCTA(text="Settings") }}
|
||||
|
||||
<div v-cloak class="portfolio-admin portfolio-content">
|
||||
|
||||
<div v-cloak class="portfolio-admin">
|
||||
{% include "fragments/flash.html" %}
|
||||
<!-- max width of this section is 460px -->
|
||||
<section class="form-container__half">
|
||||
<h3>Portfolio name and component</h3>
|
||||
|
@ -1,79 +0,0 @@
|
||||
{% from "components/icon.html" import Icon %}
|
||||
{% from "components/text_input.html" import TextInput %}
|
||||
{% from "components/multi_step_modal_form.html" import MultiStepModalForm %}
|
||||
|
||||
{% macro SimpleOptionsInput(field) %}
|
||||
<div class="usa-input">
|
||||
<fieldset data-ally-disabled="true" class="usa-input__choices">
|
||||
<legend>
|
||||
<div class="usa-input__title-inline">
|
||||
{{ field.label | striptags}}
|
||||
</div>
|
||||
</legend>
|
||||
{{ field() }}
|
||||
</fieldset>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% set step_one %}
|
||||
<hr class="full-width">
|
||||
<h1>Invite new portfolio member</h1>
|
||||
<div class='form-row'>
|
||||
<div class='form-col form-col--half'>
|
||||
{{ TextInput(member_form.user_data.first_name, validation='requiredField', optional=False) }}
|
||||
</div>
|
||||
<div class='form-col form-col--half'>
|
||||
{{ TextInput(member_form.user_data.last_name, validation='requiredField', optional=False) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class='form-row'>
|
||||
<div class='form-col form-col--half'>
|
||||
{{ TextInput(member_form.user_data.email, validation='email', optional=False) }}
|
||||
</div>
|
||||
<div class='form-col form-col--half'>
|
||||
{{ TextInput(member_form.user_data.phone_number, validation='usPhone') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class='form-row'>
|
||||
<div class='form-col form-col--half'>
|
||||
{{ TextInput(member_form.user_data.dod_id, validation='dodId', 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 usa-button-primary'
|
||||
value='Next'>
|
||||
<a class='action-group__action' v-on:click="closeModal('{{ new_port_mem }}')">Cancel</a>
|
||||
</div>
|
||||
{% endset %}
|
||||
{% set step_two %}
|
||||
<hr class="full-width">
|
||||
<h1>Assign member permissions</h1>
|
||||
<a class='icon-link'>
|
||||
{{ Icon('info') }}
|
||||
{{ "portfolios.admin.permissions_info" | translate }}
|
||||
</a>
|
||||
{{ SimpleOptionsInput(member_form.permission_sets.perms_app_mgmt) }}
|
||||
{{ SimpleOptionsInput(member_form.permission_sets.perms_funding) }}
|
||||
{{ SimpleOptionsInput(member_form.permission_sets.perms_reporting) }}
|
||||
{{ SimpleOptionsInput(member_form.permission_sets.perms_portfolio_mgmt) }}
|
||||
<div class='action-group'>
|
||||
<input
|
||||
type="submit"
|
||||
class='action-group__action usa-button usa-button-primary'
|
||||
form="add-port-mem"
|
||||
value='Invite member'>
|
||||
<a class='action-group__action' v-on:click="closeModal('{{ new_port_mem }}')">Cancel</a>
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ MultiStepModalForm(
|
||||
'add-port-mem',
|
||||
member_form,
|
||||
url_for("portfolios.invite_member", portfolio_id=portfolio.id),
|
||||
[step_one, step_two],
|
||||
) }}
|
37
templates/portfolios/fragments/member_form_fields.html
Normal file
37
templates/portfolios/fragments/member_form_fields.html
Normal file
@ -0,0 +1,37 @@
|
||||
{% from "components/checkbox_input.html" import CheckboxInput %}
|
||||
{% from "components/icon.html" import Icon %}
|
||||
{% from "components/phone_input.html" import PhoneInput %}
|
||||
{% from "components/text_input.html" import TextInput %}
|
||||
|
||||
{% macro PermsFields(form, member_role_id=None) %}
|
||||
<h2>Set Portfolio Permissions</h2>
|
||||
<div class="portfolio-perms">
|
||||
{% if new %}
|
||||
{% set app_mgmt = form.perms_app_mgmt.name %}
|
||||
{% set funding = form.perms_funding.name %}
|
||||
{% set reporting = form.perms_reporting.name %}
|
||||
{% set portfolio_mgmt = form.perms_portfolio_mgmt.name %}
|
||||
{% else %}
|
||||
{% set app_mgmt = "perms_app_mgmt-{}".format(member_role_id) %}
|
||||
{% set funding = "perms_funding-{}".format(member_role_id) %}
|
||||
{% set reporting = "perms_reporting-{}".format(member_role_id) %}
|
||||
{% set portfolio_mgmt = "perms_portfolio_mgmt-{}".format(member_role_id) %}
|
||||
{% endif %}
|
||||
|
||||
{{ CheckboxInput(form.perms_app_mgmt, classes="input__inline-fields", key=app_mgmt, id=app_mgmt, optional=True) }}
|
||||
{{ CheckboxInput(form.perms_funding, classes="input__inline-fields", key=funding, id=funding, optional=True) }}
|
||||
{{ CheckboxInput(form.perms_reporting, classes="input__inline-fields", key=reporting, id=reporting, optional=True) }}
|
||||
{{ CheckboxInput(form.perms_portfolio_mgmt, classes="input__inline-fields", key=portfolio_mgmt, id=portfolio_mgmt, optional=True) }}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro InfoFields(member_form) %}
|
||||
<div class="user-info">
|
||||
{{ TextInput(member_form.first_name, validation='requiredField', optional=False) }}
|
||||
{{ TextInput(member_form.last_name, validation='requiredField', optional=False) }}
|
||||
{{ TextInput(member_form.email, validation='email', optional=False) }}
|
||||
{{ PhoneInput(member_form.phone_number, member_form.phone_ext)}}
|
||||
{{ TextInput(member_form.dod_id, validation='dodId', optional=False) }}
|
||||
<a href="#">How do I find the DoD ID?</a>
|
||||
</div>
|
||||
{% endmacro %}
|
@ -1,38 +0,0 @@
|
||||
{% from "components/alert.html" import Alert %}
|
||||
{% from "components/modal.html" import Modal %}
|
||||
{% from "components/options_input.html" import OptionsInput %}
|
||||
|
||||
{% for subform in member_perms_form.members_permissions %}
|
||||
{% set modal_id = "portfolio_id_{}_user_id_{}".format(portfolio.id, subform.member_id.data) %}
|
||||
{% set ppoc = subform.member_id.data == ppoc_id %}
|
||||
{% set archive_button_class = 'button-danger-outline' %}
|
||||
|
||||
<tr {% if ppoc %}class="members-table-ppoc"{% endif %}>
|
||||
<td class='name'>{{ subform.member_name.data }}
|
||||
<div>
|
||||
{% if ppoc %}
|
||||
{% set archive_button_class = 'usa-button-disabled' %}
|
||||
<span class='you'>PPoC</span>
|
||||
{% endif %}
|
||||
{% if subform.member_id.data == current_member_id %}
|
||||
{% set archive_button_class = 'usa-button-disabled' %}
|
||||
<span class='you'>(<span class='green'>you</span>)</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td>{{ OptionsInput(subform.perms_app_mgmt, label=False, disabled=ppoc) }}</td>
|
||||
<td>{{ OptionsInput(subform.perms_funding, label=False, disabled=ppoc) }}</td>
|
||||
<td>{{ OptionsInput(subform.perms_reporting, label=False, disabled=ppoc) }}</td>
|
||||
<td>{{ OptionsInput(subform.perms_portfolio_mgmt, label=False, disabled=ppoc) }}</td>
|
||||
|
||||
<td>
|
||||
<a v-on:click="openModal('{{ modal_id }}')" class='usa-button {{ archive_button_class }}'>
|
||||
{{ "portfolios.members.archive_button" | translate }}
|
||||
</a>
|
||||
{% if not ppoc %}
|
||||
{{ subform.member_id() }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
@ -1,26 +0,0 @@
|
||||
{% for subform in member_perms_form.members_permissions %}
|
||||
{% set ppoc = subform.member_id.data == ppoc_id %}
|
||||
{% set heading_perms = [subform.perms_app_mgmt, subform.perms_funding, subform.perms_reporting, subform.perms_portfolio_mgmt] %}
|
||||
|
||||
<tr>
|
||||
<td class='name'>{{ subform.member_name.data }}
|
||||
<div>
|
||||
{% if ppoc %}
|
||||
<span class='you'>PPoC</span>
|
||||
{% endif %}
|
||||
{% if subform.member_id.data == current_member_id %}
|
||||
<span class='you'>(<span class='green'>you</span>)</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{% for access in heading_perms %}
|
||||
{% if dict(access.choices).get(access.data) == ('portfolios.members.permissions.edit_access' | translate) %}
|
||||
<td class='green'>{{ 'portfolios.members.permissions.edit_access' | translate }}</td>
|
||||
{% else %}
|
||||
<td>{{ 'common.view' | translate }}</td>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
</tr>
|
||||
{% endfor %}
|
@ -1,105 +1,74 @@
|
||||
{% from "components/icon.html" import Icon %}
|
||||
{% from 'components/save_button.html' import SaveButton %}
|
||||
{% from "components/modal.html" import Modal %}
|
||||
{% from "components/alert.html" import Alert %}
|
||||
{% from "components/icon.html" import Icon %}
|
||||
{% import "components/member_form.html" as member_form %}
|
||||
{% from "components/modal.html" import Modal %}
|
||||
{% from "components/multi_step_modal_form.html" import MultiStepModalForm %}
|
||||
{% from 'components/save_button.html' import SaveButton %}
|
||||
{% import "portfolios/fragments/member_form_fields.html" as member_form_fields %}
|
||||
|
||||
<section class="member-list" id="portfolio-members">
|
||||
<div class='responsive-table-wrapper panel accordion-table'>
|
||||
{% if g.matchesPath("portfolio-members") %}
|
||||
{% include "fragments/flash.html" %}
|
||||
{% endif %}
|
||||
<base-form inline-template>
|
||||
<form method='POST' id="member-perms" action='{{ url_for("portfolios.edit_members", portfolio_id=portfolio.id) }}' autocomplete="off" enctype="multipart/form-data">
|
||||
{{ member_perms_form.csrf_token }}
|
||||
|
||||
<div class='application-list-item'>
|
||||
<header>
|
||||
<div class='responsive-table-wrapper__header'>
|
||||
<div class='responsive-table-wrapper__title'>
|
||||
<div class='h3'>{{ "portfolios.admin.portfolio_members_title" | translate }}</div>
|
||||
<div class='subheading'>
|
||||
{{ "portfolios.admin.portfolio_members_subheading" | translate }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a class='icon-link'>
|
||||
{{ Icon('info') }}
|
||||
{{ "portfolios.admin.settings_info" | translate }}
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{% if not portfolio.members %}
|
||||
<p>{{ "portfolios.admin.no_members" | translate }}</p>
|
||||
{% else %}
|
||||
<h3>Portfolio Managers</h3>
|
||||
<div class="panel">
|
||||
<section class="member-list">
|
||||
<div class="responsive-table-wrapper">
|
||||
<table class="atat-table">
|
||||
|
||||
<thead>
|
||||
<tr>
|
||||
<td>{{ "portfolios.members.permissions.name" | translate }}</td>
|
||||
<td>{{ "portfolios.members.permissions.app_mgmt" | translate }}</td>
|
||||
<td>{{ "portfolios.members.permissions.funding" | translate }}</td>
|
||||
<td>{{ "portfolios.members.permissions.reporting" | translate }}</td>
|
||||
<td>{{ "portfolios.members.permissions.portfolio_mgmt" | translate }}</td>
|
||||
<td></td>
|
||||
<th class="table-cell--third">Name</th>
|
||||
<th>Portfolio Permissions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% if user_can(permissions.EDIT_PORTFOLIO_USERS) %}
|
||||
{% include "portfolios/fragments/members_edit.html" %}
|
||||
{% elif user_can(permissions.VIEW_PORTFOLIO_USERS) %}
|
||||
{% include "portfolios/fragments/members_view.html" %}
|
||||
{% for member in members -%}
|
||||
<tr>
|
||||
<td>
|
||||
<strong>{{ member.user_name }}{% if member.role_id == current_member_id %} (You){% endif %}</strong>
|
||||
<br>
|
||||
{% if member.ppoc %}
|
||||
{{ Label(type="ppoc", classes='label--below label--purple')}}
|
||||
{% endif %}
|
||||
{{ Label(type=member.status, classes='label--below')}}
|
||||
</td>
|
||||
<td>
|
||||
{% for perm, value in member.permission_sets.items() -%}
|
||||
<div>
|
||||
{% if value -%}
|
||||
{{ ("portfolios.admin.members.{}.{}".format(perm, value)) | translate }}
|
||||
{%- endif %}
|
||||
</div>
|
||||
{%-endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{%- endfor %}
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="panel__footer">
|
||||
<div class="action-group save">
|
||||
{% if user_can(permissions.EDIT_PORTFOLIO_USERS) %}
|
||||
{{ SaveButton(text=('common.save' | translate), element="input", form="member-perms") }}
|
||||
{% endif %}
|
||||
</section>
|
||||
|
||||
{% if user_can(permissions.CREATE_PORTFOLIO_USERS) %}
|
||||
<a class="icon-link modal-link" v-on:click="openModal('add-port-mem')">
|
||||
{{ "portfolios.admin.add_new_member" | translate }}
|
||||
{{ Icon("plus") }}
|
||||
{% set new_manager_modal = "add-portfolio-manager" %}
|
||||
<a class="usa-button usa-button-secondary add-new-button" v-on:click="openModal('{{ new_manager_modal }}')">
|
||||
Add Portfolio Manager
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</base-form>
|
||||
{% if user_can(permissions.EDIT_PORTFOLIO_USERS) %}
|
||||
{% for subform in member_perms_form.members_permissions %}
|
||||
{% set modal_id = "portfolio_id_{}_user_id_{}".format(portfolio.id, subform.member_id.data) %}
|
||||
{% call Modal(name=modal_id, dismissable=False) %}
|
||||
<h1>{{ "portfolios.admin.alert_header" | translate }}</h1>
|
||||
<hr>
|
||||
{{
|
||||
Alert(
|
||||
title="portfolios.admin.alert_title" | translate,
|
||||
message="portfolios.admin.alert_message" | translate,
|
||||
level="warning"
|
||||
|
||||
{{ MultiStepModalForm(
|
||||
name=new_manager_modal,
|
||||
form=new_manager_form,
|
||||
form_action=url_for("portfolios.invite_member", portfolio_id=portfolio.id),
|
||||
steps=[
|
||||
member_form.BasicStep(
|
||||
title="Add Manager",
|
||||
form=member_form_fields.InfoFields(new_manager_form.user_data),
|
||||
next_button_text="Next: Permissions",
|
||||
previous=False,
|
||||
modal=new_manager_modal_name,
|
||||
),
|
||||
member_form.SubmitStep(
|
||||
name=new_manager_modal,
|
||||
form=member_form_fields.PermsFields(new_manager_form),
|
||||
submit_text="Add Mananger",
|
||||
modal=new_manager_modal_name,
|
||||
)
|
||||
}}
|
||||
<div class="action-group">
|
||||
<form method="POST" action="{{ url_for('portfolios.remove_member', portfolio_id=portfolio.id, portfolio_role_id=subform.member_id.data)}}">
|
||||
{{ member_perms_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 %}
|
||||
{% endfor %}
|
||||
],
|
||||
) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if user_can(permissions.CREATE_PORTFOLIO_USERS) %}
|
||||
{% include "portfolios/fragments/add_new_portfolio_member.html" %}
|
||||
{% endif %}
|
||||
</section>
|
||||
</div>
|
||||
|
@ -205,7 +205,7 @@ def test_invite():
|
||||
inviter = UserFactory.create()
|
||||
member_data = UserFactory.dictionary()
|
||||
|
||||
invitation = Portfolios.invite(portfolio, inviter, member_data)
|
||||
invitation = Portfolios.invite(portfolio, inviter, {"user_data": member_data})
|
||||
|
||||
assert invitation.role
|
||||
assert invitation.role.portfolio == portfolio
|
||||
|
@ -151,12 +151,12 @@ def test_event_details():
|
||||
|
||||
def test_status_when_member_is_active():
|
||||
portfolio_role = PortfolioRoleFactory.create(status=PortfolioRoleStatus.ACTIVE)
|
||||
assert portfolio_role.display_status == "Active"
|
||||
assert portfolio_role.display_status == "active"
|
||||
|
||||
|
||||
def test_status_when_member_is_disabled():
|
||||
portfolio_role = PortfolioRoleFactory.create(status=PortfolioRoleStatus.DISABLED)
|
||||
assert portfolio_role.display_status == "Disabled"
|
||||
assert portfolio_role.display_status == "disabled"
|
||||
|
||||
|
||||
def test_status_when_invitation_has_been_rejected_for_expirations():
|
||||
@ -168,7 +168,7 @@ def test_status_when_invitation_has_been_rejected_for_expirations():
|
||||
PortfolioInvitationFactory.create(
|
||||
role=portfolio_role, status=InvitationStatus.REJECTED_EXPIRED
|
||||
)
|
||||
assert portfolio_role.display_status == "Invite expired"
|
||||
assert portfolio_role.display_status == "invite_expired"
|
||||
|
||||
|
||||
def test_status_when_invitation_has_been_rejected_for_wrong_user():
|
||||
@ -180,7 +180,7 @@ def test_status_when_invitation_has_been_rejected_for_wrong_user():
|
||||
PortfolioInvitationFactory.create(
|
||||
role=portfolio_role, status=InvitationStatus.REJECTED_WRONG_USER
|
||||
)
|
||||
assert portfolio_role.display_status == "Error on invite"
|
||||
assert portfolio_role.display_status == "invite_error"
|
||||
|
||||
|
||||
def test_status_when_invitation_has_been_revoked():
|
||||
@ -192,7 +192,7 @@ def test_status_when_invitation_has_been_revoked():
|
||||
PortfolioInvitationFactory.create(
|
||||
role=portfolio_role, status=InvitationStatus.REVOKED
|
||||
)
|
||||
assert portfolio_role.display_status == "Invite revoked"
|
||||
assert portfolio_role.display_status == "invite_revoked"
|
||||
|
||||
|
||||
def test_status_when_invitation_is_expired():
|
||||
@ -206,7 +206,7 @@ def test_status_when_invitation_is_expired():
|
||||
status=InvitationStatus.PENDING,
|
||||
expiration_time=datetime.datetime.now() - datetime.timedelta(seconds=1),
|
||||
)
|
||||
assert portfolio_role.display_status == "Invite expired"
|
||||
assert portfolio_role.display_status == "invite_expired"
|
||||
|
||||
|
||||
def test_can_not_resend_invitation_if_active():
|
||||
|
@ -34,138 +34,6 @@ def test_member_table_access(client, user_session):
|
||||
assert "<select" not in view_resp.data.decode()
|
||||
|
||||
|
||||
def test_update_member_permissions(client, user_session):
|
||||
portfolio = PortfolioFactory.create()
|
||||
rando = UserFactory.create()
|
||||
rando_pf_role = PortfolioRoleFactory.create(
|
||||
user=rando,
|
||||
portfolio=portfolio,
|
||||
permission_sets=[PermissionSets.get(PermissionSets.VIEW_PORTFOLIO_ADMIN)],
|
||||
)
|
||||
|
||||
user = UserFactory.create()
|
||||
PortfolioRoleFactory.create(
|
||||
user=user,
|
||||
portfolio=portfolio,
|
||||
permission_sets=PermissionSets.get_many(
|
||||
[PermissionSets.EDIT_PORTFOLIO_ADMIN, PermissionSets.VIEW_PORTFOLIO_ADMIN]
|
||||
),
|
||||
)
|
||||
user_session(user)
|
||||
|
||||
form_data = {
|
||||
"members_permissions-0-member_id": rando_pf_role.id,
|
||||
"members_permissions-0-perms_app_mgmt": "edit_portfolio_application_management",
|
||||
"members_permissions-0-perms_funding": "view_portfolio_funding",
|
||||
"members_permissions-0-perms_reporting": "view_portfolio_reports",
|
||||
"members_permissions-0-perms_portfolio_mgmt": "view_portfolio_admin",
|
||||
}
|
||||
|
||||
response = client.post(
|
||||
url_for("portfolios.edit_members", portfolio_id=portfolio.id),
|
||||
data=form_data,
|
||||
follow_redirects=True,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert rando_pf_role.has_permission_set(
|
||||
PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT
|
||||
)
|
||||
|
||||
|
||||
def test_no_update_member_permissions_without_edit_access(client, user_session):
|
||||
portfolio = PortfolioFactory.create()
|
||||
rando = UserFactory.create()
|
||||
rando_pf_role = PortfolioRoleFactory.create(
|
||||
user=rando,
|
||||
portfolio=portfolio,
|
||||
permission_sets=[PermissionSets.get(PermissionSets.VIEW_PORTFOLIO_ADMIN)],
|
||||
)
|
||||
|
||||
user = UserFactory.create()
|
||||
PortfolioRoleFactory.create(
|
||||
user=user,
|
||||
portfolio=portfolio,
|
||||
permission_sets=[PermissionSets.get(PermissionSets.VIEW_PORTFOLIO_ADMIN)],
|
||||
)
|
||||
user_session(user)
|
||||
|
||||
form_data = {
|
||||
"members_permissions-0-member_id": rando_pf_role.id,
|
||||
"members_permissions-0-perms_app_mgmt": "edit_portfolio_application_management",
|
||||
"members_permissions-0-perms_funding": "view_portfolio_funding",
|
||||
"members_permissions-0-perms_reporting": "view_portfolio_reports",
|
||||
"members_permissions-0-perms_portfolio_mgmt": "view_portfolio_admin",
|
||||
}
|
||||
|
||||
response = client.post(
|
||||
url_for("portfolios.edit_members", portfolio_id=portfolio.id),
|
||||
data=form_data,
|
||||
follow_redirects=True,
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
assert not rando_pf_role.has_permission_set(
|
||||
PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT
|
||||
)
|
||||
|
||||
|
||||
def test_rerender_admin_page_if_member_perms_form_does_not_validate(
|
||||
client, user_session, monkeypatch
|
||||
):
|
||||
portfolio = PortfolioFactory.create()
|
||||
user = UserFactory.create()
|
||||
role = PortfolioRoleFactory.create(
|
||||
user=user,
|
||||
portfolio=portfolio,
|
||||
permission_sets=[PermissionSets.get(PermissionSets.EDIT_PORTFOLIO_ADMIN)],
|
||||
)
|
||||
user_session(user)
|
||||
form_data = {
|
||||
"members_permissions-0-member_id": role.id,
|
||||
"members_permissions-0-perms_app_mgmt": "bad input",
|
||||
"members_permissions-0-perms_funding": "view_portfolio_funding",
|
||||
"members_permissions-0-perms_reporting": "view_portfolio_reports",
|
||||
"members_permissions-0-perms_portfolio_mgmt": "view_portfolio_admin",
|
||||
}
|
||||
|
||||
mock_route = MagicMock(return_value=("", 200, {}))
|
||||
monkeypatch.setattr("atst.routes.portfolios.admin.render_admin_page", mock_route)
|
||||
client.post(
|
||||
url_for("portfolios.edit_members", portfolio_id=portfolio.id), data=form_data
|
||||
)
|
||||
mock_route.assert_called()
|
||||
|
||||
|
||||
def test_cannot_update_portfolio_ppoc_perms(client, user_session):
|
||||
portfolio = PortfolioFactory.create()
|
||||
ppoc = portfolio.owner
|
||||
ppoc_pf_role = PortfolioRoles.get(portfolio_id=portfolio.id, user_id=ppoc.id)
|
||||
user = UserFactory.create()
|
||||
PortfolioRoleFactory.create(portfolio=portfolio, user=user)
|
||||
|
||||
user_session(user)
|
||||
|
||||
assert ppoc_pf_role.has_permission_set(PermissionSets.PORTFOLIO_POC)
|
||||
|
||||
member_perms_data = {
|
||||
"members_permissions-0-member_id": ppoc_pf_role.id,
|
||||
"members_permissions-0-perms_app_mgmt": "view_portfolio_application_management",
|
||||
"members_permissions-0-perms_funding": "view_portfolio_funding",
|
||||
"members_permissions-0-perms_reporting": "view_portfolio_reports",
|
||||
"members_permissions-0-perms_portfolio_mgmt": "view_portfolio_admin",
|
||||
}
|
||||
|
||||
response = client.post(
|
||||
url_for("portfolios.edit_members", portfolio_id=portfolio.id),
|
||||
data=member_perms_data,
|
||||
follow_redirects=True,
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
assert ppoc_pf_role.has_permission_set(PermissionSets.PORTFOLIO_POC)
|
||||
|
||||
|
||||
def test_update_portfolio_name_and_description(client, user_session):
|
||||
portfolio = PortfolioFactory.create()
|
||||
user_session(portfolio.owner)
|
||||
|
@ -269,10 +269,10 @@ def test_existing_member_invite_resent_to_email_submitted_in_form(
|
||||
|
||||
|
||||
_DEFAULT_PERMS_FORM_DATA = {
|
||||
"permission_sets-perms_app_mgmt": PermissionSets.VIEW_PORTFOLIO_APPLICATION_MANAGEMENT,
|
||||
"permission_sets-perms_funding": PermissionSets.VIEW_PORTFOLIO_FUNDING,
|
||||
"permission_sets-perms_reporting": PermissionSets.VIEW_PORTFOLIO_REPORTS,
|
||||
"permission_sets-perms_portfolio_mgmt": PermissionSets.VIEW_PORTFOLIO_ADMIN,
|
||||
"permission_sets-perms_app_mgmt": False,
|
||||
"permission_sets-perms_funding": False,
|
||||
"permission_sets-perms_reporting": False,
|
||||
"permission_sets-perms_portfolio_mgmt": False,
|
||||
}
|
||||
|
||||
|
||||
|
@ -161,15 +161,23 @@ forms:
|
||||
phone_number_label: Phone number
|
||||
service_branch_label: Service branch or agency
|
||||
new_member:
|
||||
app_mgmt: App management
|
||||
app_mgmt:
|
||||
label: Edit Applications
|
||||
description: Add, remove and edit applications in this Portfolio.
|
||||
dod_id_label: DoD ID
|
||||
email_label: Email address
|
||||
first_name_label: First name
|
||||
funding: Funding
|
||||
funding:
|
||||
label: Edit Funding
|
||||
description: Add and Modify Task Orders to fund this Portfolio.
|
||||
last_name_label: Last name
|
||||
phone_number_label: Phone number
|
||||
portfolio_mgmt: Portfolio management
|
||||
reporting: Reporting
|
||||
portfolio_mgmt:
|
||||
label: Edit Portfolio
|
||||
description: "Edit this Portfolio's settings."
|
||||
reporting:
|
||||
label: Edit Reporting
|
||||
description: "View and export reports about this Portfolio's funding."
|
||||
portfolio:
|
||||
name:
|
||||
label: Portfolio Name
|
||||
@ -317,6 +325,19 @@ portfolios:
|
||||
portfolio_members_title: Portfolio members
|
||||
settings_info: Learn more about these settings
|
||||
portfolio_name: Portfolio name
|
||||
members:
|
||||
perms_portfolio_mgmt:
|
||||
'False': View Portfolio
|
||||
'True': Edit Portfolio
|
||||
perms_app_mgmt:
|
||||
'False': View Applications
|
||||
'True': Edit Applications
|
||||
perms_funding:
|
||||
'False': View Funding
|
||||
'True': Edit Funding
|
||||
perms_reporting:
|
||||
'False': View Reporting
|
||||
'True': Edit Reporting
|
||||
applications:
|
||||
add_application_text: Add a new application
|
||||
add_environment: Create an Environment
|
||||
|
Loading…
x
Reference in New Issue
Block a user