commit
f877b0a1d8
@ -3,7 +3,7 @@
|
|||||||
"files": "^.secrets.baseline$",
|
"files": "^.secrets.baseline$",
|
||||||
"lines": null
|
"lines": null
|
||||||
},
|
},
|
||||||
"generated_at": "2019-10-02T14:53:58Z",
|
"generated_at": "2019-10-02T23:24:50Z",
|
||||||
"plugins_used": [
|
"plugins_used": [
|
||||||
{
|
{
|
||||||
"base64_limit": 4.5,
|
"base64_limit": 4.5,
|
||||||
@ -194,7 +194,7 @@
|
|||||||
"hashed_secret": "e4f14805dfd1e6af030359090c535e149e6b4207",
|
"hashed_secret": "e4f14805dfd1e6af030359090c535e149e6b4207",
|
||||||
"is_secret": false,
|
"is_secret": false,
|
||||||
"is_verified": false,
|
"is_verified": false,
|
||||||
"line_number": 638,
|
"line_number": 651,
|
||||||
"type": "Hex High Entropy String"
|
"type": "Hex High Entropy String"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -117,22 +117,21 @@ class BaseInvitations(object):
|
|||||||
return cls._update_status(invite, InvitationStatus.REVOKED)
|
return cls._update_status(invite, InvitationStatus.REVOKED)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def resend(cls, inviter, token):
|
def resend(cls, inviter, token, user_info=None):
|
||||||
previous_invitation = cls._get(token)
|
previous_invitation = cls._get(token)
|
||||||
cls._update_status(previous_invitation, InvitationStatus.REVOKED)
|
cls._update_status(previous_invitation, InvitationStatus.REVOKED)
|
||||||
|
|
||||||
return cls.create(
|
if not user_info:
|
||||||
inviter,
|
user_info = {
|
||||||
previous_invitation.role,
|
|
||||||
{
|
|
||||||
"email": previous_invitation.email,
|
"email": previous_invitation.email,
|
||||||
"dod_id": previous_invitation.dod_id,
|
"dod_id": previous_invitation.dod_id,
|
||||||
"first_name": previous_invitation.first_name,
|
"first_name": previous_invitation.first_name,
|
||||||
"last_name": previous_invitation.last_name,
|
"last_name": previous_invitation.last_name,
|
||||||
"phone_number": previous_invitation.last_name,
|
"phone_number": previous_invitation.phone_number,
|
||||||
},
|
"phone_ext": previous_invitation.phone_ext,
|
||||||
commit=True,
|
}
|
||||||
)
|
|
||||||
|
return cls.create(inviter, previous_invitation.role, user_info, commit=True)
|
||||||
|
|
||||||
|
|
||||||
class PortfolioInvitations(BaseInvitations):
|
class PortfolioInvitations(BaseInvitations):
|
||||||
|
@ -12,6 +12,7 @@ from atst.domain.invitations import ApplicationInvitations
|
|||||||
from atst.forms.application_member import NewForm as NewMemberForm, UpdateMemberForm
|
from atst.forms.application_member import NewForm as NewMemberForm, UpdateMemberForm
|
||||||
from atst.forms.application import NameAndDescriptionForm, EditEnvironmentForm
|
from atst.forms.application import NameAndDescriptionForm, EditEnvironmentForm
|
||||||
from atst.forms.data import ENV_ROLE_NO_ACCESS as NO_ACCESS
|
from atst.forms.data import ENV_ROLE_NO_ACCESS as NO_ACCESS
|
||||||
|
from atst.forms.member import NewForm as MemberForm
|
||||||
from atst.domain.authz.decorator import user_can_access_decorator as user_can
|
from atst.domain.authz.decorator import user_can_access_decorator as user_can
|
||||||
from atst.models.permissions import Permissions
|
from atst.models.permissions import Permissions
|
||||||
from atst.domain.permission_sets import PermissionSets
|
from atst.domain.permission_sets import PermissionSets
|
||||||
@ -95,6 +96,13 @@ def get_members_data(application):
|
|||||||
form = UpdateMemberForm(
|
form = UpdateMemberForm(
|
||||||
environment_roles=env_roles_form_data, **permission_sets
|
environment_roles=env_roles_form_data, **permission_sets
|
||||||
)
|
)
|
||||||
|
update_invite_form = None
|
||||||
|
|
||||||
|
if member.latest_invitation and member.latest_invitation.can_resend:
|
||||||
|
update_invite_form = MemberForm(obj=member.latest_invitation)
|
||||||
|
else:
|
||||||
|
update_invite_form = MemberForm()
|
||||||
|
|
||||||
members_data.append(
|
members_data.append(
|
||||||
{
|
{
|
||||||
"role_id": member.id,
|
"role_id": member.id,
|
||||||
@ -103,6 +111,7 @@ def get_members_data(application):
|
|||||||
"environment_roles": environment_roles,
|
"environment_roles": environment_roles,
|
||||||
"role_status": member.status.value,
|
"role_status": member.status.value,
|
||||||
"form": form,
|
"form": form,
|
||||||
|
"update_invite_form": update_invite_form,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -419,3 +428,46 @@ def revoke_invite(application_id, application_role_id):
|
|||||||
_anchor="application-members",
|
_anchor="application-members",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@applications_bp.route(
|
||||||
|
"/applications/<application_id>/members/<application_role_id>/resend_invite",
|
||||||
|
methods=["POST"],
|
||||||
|
)
|
||||||
|
@user_can(Permissions.EDIT_APPLICATION_MEMBER, message="resend application invitation")
|
||||||
|
def resend_invite(application_id, application_role_id):
|
||||||
|
app_role = ApplicationRoles.get_by_id(application_role_id)
|
||||||
|
invite = app_role.latest_invitation
|
||||||
|
form = MemberForm(http_request.form)
|
||||||
|
|
||||||
|
if form.validate():
|
||||||
|
new_invite = ApplicationInvitations.resend(
|
||||||
|
g.current_user, invite.token, form.data
|
||||||
|
)
|
||||||
|
|
||||||
|
send_application_invitation(
|
||||||
|
invitee_email=new_invite.email,
|
||||||
|
inviter_name=g.current_user.full_name,
|
||||||
|
token=new_invite.token,
|
||||||
|
)
|
||||||
|
|
||||||
|
flash(
|
||||||
|
"application_invite_resent",
|
||||||
|
user_name=new_invite.user_name,
|
||||||
|
application_name=app_role.application.name,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
flash(
|
||||||
|
"application_invite_error",
|
||||||
|
user_name=app_role.user_name,
|
||||||
|
application_name=g.application.name,
|
||||||
|
)
|
||||||
|
|
||||||
|
return redirect(
|
||||||
|
url_for(
|
||||||
|
"applications.settings",
|
||||||
|
application_id=application_id,
|
||||||
|
fragment="application-members",
|
||||||
|
_anchor="application-members",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
@ -39,6 +39,11 @@ MESSAGES = {
|
|||||||
"message_template": "There was an error processing the invitation for {{ user_name }} from {{ application_name }}",
|
"message_template": "There was an error processing the invitation for {{ user_name }} from {{ application_name }}",
|
||||||
"category": "error",
|
"category": "error",
|
||||||
},
|
},
|
||||||
|
"application_invite_resent": {
|
||||||
|
"title_template": "Application invitation revoked",
|
||||||
|
"message_template": "You have successfully resent the invite for {{ user_name }} from {{ application_name }}",
|
||||||
|
"category": "success",
|
||||||
|
},
|
||||||
"application_invite_revoked": {
|
"application_invite_revoked": {
|
||||||
"title_template": "Application invitation revoked",
|
"title_template": "Application invitation revoked",
|
||||||
"message_template": "You have successfully revoked the invite for {{ user_name }} from {{ application_name }}",
|
"message_template": "You have successfully revoked the invite for {{ user_name }} from {{ application_name }}",
|
||||||
|
71
templates/applications/fragments/member_form_fields.html
Normal file
71
templates/applications/fragments/member_form_fields.html
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
{% from "components/checkbox_input.html" import CheckboxInput %}
|
||||||
|
{% from "components/text_input.html" import TextInput %}
|
||||||
|
{% from "components/phone_input.html" import PhoneInput %}
|
||||||
|
|
||||||
|
{% macro PermsFields(form, new=False, member_role_id=None) %}
|
||||||
|
<h4>{{ "portfolios.applications.members.form.project_perms" | translate }}</h4>
|
||||||
|
<div class="application-perms">
|
||||||
|
{% if new %}
|
||||||
|
{% set team_mgmt = form.perms_team_mgmt.name %}
|
||||||
|
{% set env_mgmt = form.perms_env_mgmt.name %}
|
||||||
|
{% set del_env = form.perms_del_env.name %}
|
||||||
|
{% else %}
|
||||||
|
{% set team_mgmt = "perms_team_mgmt-{}".format(member_role_id) %}
|
||||||
|
{% set env_mgmt = "perms_env_mgmt-{}".format(member_role_id) %}
|
||||||
|
{% set del_env = "perms_del_env-{}".format(member_role_id) %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{{ CheckboxInput(form.perms_team_mgmt, classes="input__inline-fields", key=team_mgmt, id=team_mgmt, optional=True) }}
|
||||||
|
{{ CheckboxInput(form.perms_env_mgmt, classes="input__inline-fields", key=env_mgmt, id=env_mgmt, optional=True) }}
|
||||||
|
{{ CheckboxInput(form.perms_del_env, classes="input__inline-fields", key=del_env, id=del_env, optional=True) }}
|
||||||
|
</div>
|
||||||
|
<div class="environment_roles environment-roles-new">
|
||||||
|
<h4>{{ "portfolios.applications.members.form.env_access" | translate }}</h4>
|
||||||
|
<hr>
|
||||||
|
{% for environment_data in form.environment_roles %}
|
||||||
|
<optionsinput inline-template
|
||||||
|
v-bind:initial-value="'{{ environment_data.role.data | string }}'"
|
||||||
|
v-bind:name="'{{ environment_data.name | string }}'"
|
||||||
|
v-bind:optional="true"
|
||||||
|
v-bind:watch="true">
|
||||||
|
<div class="usa-input">
|
||||||
|
<fieldset data-ally-disabled="true" v-on:change="onInput" class="usa-input__choices">
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-col form-col--two-thirds">
|
||||||
|
<legend>
|
||||||
|
<div v-bind:class='["usa-input__title-inline", {"environment-name--gray": value === "None" }]'>
|
||||||
|
{{ environment_data.environment_name.data }}
|
||||||
|
</div>
|
||||||
|
</legend>
|
||||||
|
</div>
|
||||||
|
<div class="form-col form-col--third">
|
||||||
|
{{ environment_data.role(**{"v-model": "value"}) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</optionsinput>
|
||||||
|
{{ environment_data.environment_id() }}
|
||||||
|
<hr>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro InfoFields(member_form) %}
|
||||||
|
<div class='form-row'>
|
||||||
|
{{ TextInput(member_form.first_name, validation='requiredField', optional=False) }}
|
||||||
|
</div>
|
||||||
|
<div class='form-row'>
|
||||||
|
{{ TextInput(member_form.last_name, validation='requiredField', optional=False) }}
|
||||||
|
</div>
|
||||||
|
<div class='form-row'>
|
||||||
|
{{ TextInput(member_form.email, validation='email', optional=False) }}
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
{{ PhoneInput(member_form.phone_number, member_form.phone_ext)}}
|
||||||
|
</div>
|
||||||
|
<div class='form-row'>
|
||||||
|
{{ TextInput(member_form.dod_id, validation='dodId', optional=False) }}
|
||||||
|
</div>
|
||||||
|
<a href="#">How do I find the DoD ID?</a>
|
||||||
|
{% endmacro %}
|
@ -1,52 +0,0 @@
|
|||||||
{% from "components/checkbox_input.html" import CheckboxInput %}
|
|
||||||
|
|
||||||
{% macro MemberPermsFields(form, new=False, member_role_id=None) %}
|
|
||||||
<div class="form-content--app-mem">
|
|
||||||
<h4>{{ "portfolios.applications.members.form.project_perms" | translate }}</h4>
|
|
||||||
<div class="application-perms">
|
|
||||||
{% if new %}
|
|
||||||
{% set team_mgmt = form.perms_team_mgmt.name %}
|
|
||||||
{% set env_mgmt = form.perms_env_mgmt.name %}
|
|
||||||
{% set del_env = form.perms_del_env.name %}
|
|
||||||
{% else %}
|
|
||||||
{% set team_mgmt = "perms_team_mgmt-{}".format(member_role_id) %}
|
|
||||||
{% set env_mgmt = "perms_env_mgmt-{}".format(member_role_id) %}
|
|
||||||
{% set del_env = "perms_del_env-{}".format(member_role_id) %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{{ CheckboxInput(form.perms_team_mgmt, classes="input__inline-fields", key=team_mgmt, id=team_mgmt, optional=True) }}
|
|
||||||
{{ CheckboxInput(form.perms_env_mgmt, classes="input__inline-fields", key=env_mgmt, id=env_mgmt, optional=True) }}
|
|
||||||
{{ CheckboxInput(form.perms_del_env, classes="input__inline-fields", key=del_env, id=del_env, optional=True) }}
|
|
||||||
</div>
|
|
||||||
<div class="environment_roles environment-roles-new">
|
|
||||||
<h4>{{ "portfolios.applications.members.form.env_access" | translate }}</h4>
|
|
||||||
<hr>
|
|
||||||
{% for environment_data in form.environment_roles %}
|
|
||||||
<optionsinput inline-template
|
|
||||||
v-bind:initial-value="'{{ environment_data.role.data | string }}'"
|
|
||||||
v-bind:name="'{{ environment_data.name | string }}'"
|
|
||||||
v-bind:optional="true"
|
|
||||||
v-bind:watch="true">
|
|
||||||
<div class="usa-input">
|
|
||||||
<fieldset data-ally-disabled="true" v-on:change="onInput" class="usa-input__choices">
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="form-col form-col--two-thirds">
|
|
||||||
<legend>
|
|
||||||
<div v-bind:class='["usa-input__title-inline", {"environment-name--gray": value === "None" }]'>
|
|
||||||
{{ environment_data.environment_name.data }}
|
|
||||||
</div>
|
|
||||||
</legend>
|
|
||||||
</div>
|
|
||||||
<div class="form-col form-col--third">
|
|
||||||
{{ environment_data.role(**{"v-model": "value"}) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
</optionsinput>
|
|
||||||
{{ environment_data.environment_id() }}
|
|
||||||
<hr>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endmacro %}
|
|
@ -1,8 +1,5 @@
|
|||||||
{% from "components/icon.html" import Icon %}
|
{% from "components/icon.html" import Icon %}
|
||||||
{% from "components/text_input.html" import TextInput %}
|
{% import "applications/fragments/member_form_fields.html" as member_fields %}
|
||||||
{% from "components/checkbox_input.html" import CheckboxInput %}
|
|
||||||
{% from "components/phone_input.html" import PhoneInput %}
|
|
||||||
{% from "applications/fragments/member_perms_form_fields.html" import MemberPermsFields %}
|
|
||||||
|
|
||||||
{% macro MemberFormTemplate(title, next_button, previous=True) %}
|
{% macro MemberFormTemplate(title, next_button, previous=True) %}
|
||||||
<div class="modal__form--header">
|
<div class="modal__form--header">
|
||||||
@ -36,22 +33,7 @@
|
|||||||
{% endset %}
|
{% endset %}
|
||||||
|
|
||||||
{% call MemberFormTemplate(title="portfolios.applications.members.form.add_member"|translate, next_button=next_button, previous=False) %}
|
{% call MemberFormTemplate(title="portfolios.applications.members.form.add_member"|translate, next_button=next_button, previous=False) %}
|
||||||
<div class='form-row'>
|
{{ member_fields.InfoFields(member_form.user_data) }}
|
||||||
{{ TextInput(member_form.user_data.first_name, validation='requiredField', optional=False) }}
|
|
||||||
</div>
|
|
||||||
<div class='form-row'>
|
|
||||||
{{ TextInput(member_form.user_data.last_name, validation='requiredField', optional=False) }}
|
|
||||||
</div>
|
|
||||||
<div class='form-row'>
|
|
||||||
{{ TextInput(member_form.user_data.email, validation='email', optional=False) }}
|
|
||||||
</div>
|
|
||||||
<div class="form-row">
|
|
||||||
{{ PhoneInput(member_form.user_data.phone_number, member_form.user_data.phone_ext)}}
|
|
||||||
</div>
|
|
||||||
<div class='form-row'>
|
|
||||||
{{ TextInput(member_form.user_data.dod_id, validation='dodId', optional=False) }}
|
|
||||||
</div>
|
|
||||||
<a href="#">How do I find the DoD ID?</a>
|
|
||||||
{% endcall %}
|
{% endcall %}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
{% macro MemberStepTwo(member_form, application) %}
|
{% macro MemberStepTwo(member_form, application) %}
|
||||||
@ -64,6 +46,6 @@
|
|||||||
{% endset %}
|
{% endset %}
|
||||||
|
|
||||||
{% call MemberFormTemplate(title="portfolios.applications.members.form.step_2_title"|translate, next_button=next_button) %}
|
{% call MemberFormTemplate(title="portfolios.applications.members.form.step_2_title"|translate, next_button=next_button) %}
|
||||||
{{ MemberPermsFields(form=member_form) }}
|
{{ member_fields.PermsFields(form=member_form) }}
|
||||||
{% endcall %}
|
{% endcall %}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
{% from "components/delete_confirmation.html" import DeleteConfirmation %}
|
{% from "components/delete_confirmation.html" import DeleteConfirmation %}
|
||||||
{% from "components/icon.html" import Icon %}
|
{% from "components/icon.html" import Icon %}
|
||||||
{% import "applications/fragments/new_member_modal_content.html" as member_steps %}
|
{% import "applications/fragments/new_member_modal_content.html" as member_steps %}
|
||||||
{% from "applications/fragments/member_perms_form_fields.html" import MemberPermsFields %}
|
|
||||||
{% from "fragments/members.html" import MemberManagementTemplate %}
|
{% from "fragments/members.html" import MemberManagementTemplate %}
|
||||||
{% from "components/modal.html" import Modal %}
|
{% from "components/modal.html" import Modal %}
|
||||||
{% from "components/multi_step_modal_form.html" import MultiStepModalForm %}
|
{% from "components/multi_step_modal_form.html" import MultiStepModalForm %}
|
||||||
@ -79,7 +78,10 @@
|
|||||||
members,
|
members,
|
||||||
new_member_form,
|
new_member_form,
|
||||||
"applications.create_member",
|
"applications.create_member",
|
||||||
user_can(permissions.CREATE_APPLICATION_MEMBER)) }}
|
user_can_create_app_member=user_can(permissions.CREATE_APPLICATION_MEMBER),
|
||||||
|
user_can_edit_app_member=user_can(permissions.EDIT_APPLICATION_MEMBER),
|
||||||
|
user_can_delete_app_member=user_can(permissions.DELETE_APPLICATION_MEMBER),
|
||||||
|
) }}
|
||||||
|
|
||||||
<div class="subheading">
|
<div class="subheading">
|
||||||
{{ 'common.resource_names.environments' | translate }}
|
{{ 'common.resource_names.environments' | translate }}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{% from "components/alert.html" import Alert %}
|
{% from "components/alert.html" import Alert %}
|
||||||
{% from "components/icon.html" import Icon %}
|
{% from "components/icon.html" import Icon %}
|
||||||
{% import "applications/fragments/new_member_modal_content.html" as member_steps %}
|
{% import "applications/fragments/new_member_modal_content.html" as member_steps %}
|
||||||
{% from "applications/fragments/member_perms_form_fields.html" import MemberPermsFields %}
|
{% import "applications/fragments/member_form_fields.html" as member_fields %}
|
||||||
{% 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 %}
|
||||||
@ -11,7 +11,9 @@
|
|||||||
members,
|
members,
|
||||||
new_member_form,
|
new_member_form,
|
||||||
action,
|
action,
|
||||||
user_can_create_app_member=False
|
user_can_create_app_member=False,
|
||||||
|
user_can_edit_app_member=False,
|
||||||
|
user_can_delete_app_member=False
|
||||||
) %}
|
) %}
|
||||||
|
|
||||||
|
|
||||||
@ -54,6 +56,7 @@
|
|||||||
{% set new_member_modal_name = "add-app-mem" %}
|
{% set new_member_modal_name = "add-app-mem" %}
|
||||||
|
|
||||||
{% for member in members %}
|
{% for member in members %}
|
||||||
|
{%- if user_can_edit_app_member %}
|
||||||
{% set modal_name = "edit_member-{}".format(loop.index) %}
|
{% set modal_name = "edit_member-{}".format(loop.index) %}
|
||||||
{% call Modal(modal_name) %}
|
{% call Modal(modal_name) %}
|
||||||
<div class="modal__form--header">
|
<div class="modal__form--header">
|
||||||
@ -63,7 +66,7 @@
|
|||||||
<base-form inline-template>
|
<base-form inline-template>
|
||||||
<form id='{{ modal_name }}' method="POST" action="{{ url_for('applications.update_member', application_id=application.id, application_role_id=member.role_id) }}">
|
<form id='{{ modal_name }}' method="POST" action="{{ url_for('applications.update_member', application_id=application.id, application_role_id=member.role_id) }}">
|
||||||
{{ member.form.csrf_token }}
|
{{ member.form.csrf_token }}
|
||||||
{{ MemberPermsFields(form=member.form, member_role_id=member.role_id) }}
|
{{ member_fields.PermsFields(form=member.form, member_role_id=member.role_id) }}
|
||||||
<div class="action-group">
|
<div class="action-group">
|
||||||
{{ SaveButton(text='Update', element='input', additional_classes='action-group__action') }}
|
{{ SaveButton(text='Update', element='input', additional_classes='action-group__action') }}
|
||||||
<a class='action-group__action usa-button usa-button-secondary' v-on:click="closeModal('{{ modal_name }}')">{{ "common.cancel" | translate }}</a>
|
<a class='action-group__action usa-button usa-button-secondary' v-on:click="closeModal('{{ modal_name }}')">{{ "common.cancel" | translate }}</a>
|
||||||
@ -71,8 +74,44 @@
|
|||||||
</form>
|
</form>
|
||||||
</base-form>
|
</base-form>
|
||||||
{% endcall %}
|
{% endcall %}
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
|
{%- if member.role_status == 'pending' %}
|
||||||
|
{% 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>Verify Member Information</h1>
|
||||||
|
<hr>
|
||||||
|
</div>
|
||||||
|
<base-form inline-template>
|
||||||
|
<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_fields.InfoFields(member.update_invite_form) }}
|
||||||
|
<div class="action-group">
|
||||||
|
{{ SaveButton(text='Resend Invite', element='input', additional_classes='action-group__action') }}
|
||||||
|
<a class='action-group__action usa-button usa-button-secondary' v-on:click="closeModal('{{ resend_invite_modal }}')">{{ "common.cancel" | translate }}</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</base-form>
|
||||||
|
{% endcall %}
|
||||||
|
{% endif -%}
|
||||||
|
{% endif -%}
|
||||||
|
|
||||||
|
{% if user_can_delete_app_member and member.role_status == 'pending' -%}
|
||||||
|
{% set revoke_invite_modal = "revoke_invite_{}".format(member.role_id) %}
|
||||||
|
{% call Modal(name=revoke_invite_modal, dismissable=True) %}
|
||||||
|
<div class="task-order__modal-cancel">
|
||||||
|
<form method="post" action="{{ url_for('applications.revoke_invite', application_id=application.id, application_role_id=member.role_id) }}">
|
||||||
|
{{ member.form.csrf_token }}
|
||||||
|
<h1>{{ "invites.revoke.modal_heading" | translate({'user_name': member.user_name}) }}</h1>
|
||||||
|
<div class="task-order__modal-cancel_buttons">
|
||||||
|
<button class="usa-button usa-button-primary" type="submit">{{ "invites.revoke.submit" | translate }}</button>
|
||||||
|
<button type='button' v-on:click='closeModal("{{revoke_invite_modal}}")' class="usa-button usa-button-primary">{{ "invites.revoke.cancel" | translate }}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endcall %}
|
||||||
|
{%- endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
<section class="member-list application-list" id="application-members">
|
<section class="member-list application-list" id="application-members">
|
||||||
<div class='responsive-table-wrapper'>
|
<div class='responsive-table-wrapper'>
|
||||||
@ -112,10 +151,14 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if member.role_status == 'pending' %}
|
{% if member.role_status == 'pending' -%}
|
||||||
<a href="#">Resend Invite</a><br>
|
{% set revoke_invite_modal = "revoke_invite_{}".format(member.role_id) %}
|
||||||
<a href="#">Revoke Invite</a>
|
{% set resend_invite_modal = "resend_invite-{}".format(member.role_id) %}
|
||||||
{% endif %}
|
<a v-on:click='openModal("{{ resend_invite_modal }}")'>Resend Invite</a><br>
|
||||||
|
{% if user_can_delete_app_member -%}
|
||||||
|
<a v-on:click='openModal("{{ revoke_invite_modal }}")'>{{ 'invites.revoke.button' | translate }}</a>
|
||||||
|
{%- endif %}
|
||||||
|
{%- endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -129,7 +172,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if user_can_create_app_member %}
|
{% if user_can_create_app_member %}
|
||||||
{% import "applications/fragments/new_member_modal_content.html" as member_steps %}
|
|
||||||
{{ MultiStepModalForm(
|
{{ MultiStepModalForm(
|
||||||
name=new_member_modal_name,
|
name=new_member_modal_name,
|
||||||
form=new_member_form,
|
form=new_member_form,
|
||||||
|
@ -2,6 +2,7 @@ import pytest
|
|||||||
import uuid
|
import uuid
|
||||||
from flask import url_for, get_flashed_messages
|
from flask import url_for, get_flashed_messages
|
||||||
from unittest.mock import Mock
|
from unittest.mock import Mock
|
||||||
|
import datetime
|
||||||
|
|
||||||
from tests.factories import *
|
from tests.factories import *
|
||||||
|
|
||||||
@ -591,3 +592,35 @@ def test_filter_environment_roles():
|
|||||||
|
|
||||||
environment_data = filter_env_roles_form_data(application_role3, [environment])
|
environment_data = filter_env_roles_form_data(application_role3, [environment])
|
||||||
assert environment_data[0]["role"] == "No Access"
|
assert environment_data[0]["role"] == "No Access"
|
||||||
|
|
||||||
|
def test_resend_invite(client, user_session, session):
|
||||||
|
user = UserFactory.create()
|
||||||
|
# need to set the time created to yesterday, otherwise the original invite and resent
|
||||||
|
# invite have the same time_created and then we can't rely on time to order the invites
|
||||||
|
yesterday = datetime.date.today() - datetime.timedelta(days=1)
|
||||||
|
invite = ApplicationInvitationFactory.create(
|
||||||
|
user=user, time_created=yesterday, email="original@example.com"
|
||||||
|
)
|
||||||
|
app_role = invite.role
|
||||||
|
application = app_role.application
|
||||||
|
|
||||||
|
user_session(application.portfolio.owner)
|
||||||
|
response = client.post(
|
||||||
|
url_for(
|
||||||
|
"applications.resend_invite",
|
||||||
|
application_id=application.id,
|
||||||
|
application_role_id=app_role.id,
|
||||||
|
),
|
||||||
|
data={
|
||||||
|
"first_name": user.first_name,
|
||||||
|
"last_name": user.last_name,
|
||||||
|
"dod_id": user.dod_id,
|
||||||
|
"email": "an_email@example.com",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
session.refresh(app_role)
|
||||||
|
assert response.status_code == 302
|
||||||
|
assert invite.is_revoked
|
||||||
|
assert app_role.status == ApplicationRoleStatus.PENDING
|
||||||
|
assert app_role.latest_invitation.email == "an_email@example.com"
|
||||||
|
@ -595,6 +595,24 @@ def test_applications_revoke_invite(post_url_assert_status):
|
|||||||
post_url_assert_status(user, url, status)
|
post_url_assert_status(user, url, status)
|
||||||
|
|
||||||
|
|
||||||
|
# applications.resend_invite
|
||||||
|
def test_applications_resend_invite(post_url_assert_status):
|
||||||
|
ccpo = UserFactory.create_ccpo()
|
||||||
|
rando = UserFactory.create()
|
||||||
|
application = ApplicationFactory.create()
|
||||||
|
|
||||||
|
for user, status in [(ccpo, 302), (application.portfolio.owner, 302), (rando, 404)]:
|
||||||
|
app_role = ApplicationRoleFactory.create()
|
||||||
|
invite = ApplicationInvitationFactory.create(role=app_role)
|
||||||
|
|
||||||
|
url = url_for(
|
||||||
|
"applications.resend_invite",
|
||||||
|
application_id=application.id,
|
||||||
|
application_role_id=app_role.id,
|
||||||
|
)
|
||||||
|
post_url_assert_status(user, url, status)
|
||||||
|
|
||||||
|
|
||||||
# task_orders.download_task_order_pdf
|
# task_orders.download_task_order_pdf
|
||||||
def test_task_orders_download_task_order_pdf_access(get_url_assert_status, monkeypatch):
|
def test_task_orders_download_task_order_pdf_access(get_url_assert_status, monkeypatch):
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user