From 024c695f662be6fa4ee5fac01b44e99a936e44f7 Mon Sep 17 00:00:00 2001 From: richard-dds Date: Mon, 5 Nov 2018 14:44:01 -0500 Subject: [PATCH 01/10] Simplify Invitations.create signature --- atst/domain/invitations.py | 4 ++-- atst/routes/workspaces.py | 5 +++-- tests/domain/test_invitations.py | 6 +++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/atst/domain/invitations.py b/atst/domain/invitations.py index bb653693..99b30b27 100644 --- a/atst/domain/invitations.py +++ b/atst/domain/invitations.py @@ -52,11 +52,11 @@ class Invitations(object): return invite @classmethod - def create(cls, workspace_role, inviter, user): + def create(cls, inviter, workspace_role): invite = Invitation( workspace_role=workspace_role, inviter=inviter, - user=user, + user=workspace_role.user, status=InvitationStatus.PENDING, expiration_time=Invitations.current_expiration_time(), ) diff --git a/atst/routes/workspaces.py b/atst/routes/workspaces.py index bbe47803..a8f6493d 100644 --- a/atst/routes/workspaces.py +++ b/atst/routes/workspaces.py @@ -255,11 +255,12 @@ def send_invite_email(owner_name, token, new_member_email): def create_member(workspace_id): workspace = Workspaces.get(g.current_user, workspace_id) form = NewMemberForm(http_request.form) + user = g.current_user if form.validate(): try: - new_member = Workspaces.create_member(g.current_user, workspace, form.data) - invite = Invitations.create(new_member, g.current_user, new_member.user) + new_member = Workspaces.create_member(user, workspace, form.data) + invite = Invitations.create(user, new_member) send_invite_email( g.current_user.full_name, invite.token, new_member.user.email ) diff --git a/tests/domain/test_invitations.py b/tests/domain/test_invitations.py index 6f5a0093..0b34695b 100644 --- a/tests/domain/test_invitations.py +++ b/tests/domain/test_invitations.py @@ -22,7 +22,7 @@ def test_create_invitation(): workspace = WorkspaceFactory.create() user = UserFactory.create() ws_role = WorkspaceRoleFactory.create(user=user, workspace=workspace) - invite = Invitations.create(ws_role, workspace.owner, user) + invite = Invitations.create(workspace.owner, ws_role) assert invite.user == user assert invite.workspace_role == ws_role assert invite.inviter == workspace.owner @@ -34,7 +34,7 @@ def test_accept_invitation(): workspace = WorkspaceFactory.create() user = UserFactory.create() ws_role = WorkspaceRoleFactory.create(user=user, workspace=workspace) - invite = Invitations.create(ws_role, workspace.owner, user) + invite = Invitations.create(workspace.owner, ws_role) assert invite.is_pending accepted_invite = Invitations.accept(user, invite.token) assert accepted_invite.is_accepted @@ -89,7 +89,7 @@ def test_accept_invitation_twice(): workspace = WorkspaceFactory.create() user = UserFactory.create() ws_role = WorkspaceRoleFactory.create(user=user, workspace=workspace) - invite = Invitations.create(ws_role, workspace.owner, user) + invite = Invitations.create(workspace.owner, ws_role) Invitations.accept(user, invite.token) with pytest.raises(InvitationError): Invitations.accept(user, invite.token) From c3e1a7eb27d026d39621c662998b223c58ac9a20 Mon Sep 17 00:00:00 2001 From: richard-dds Date: Mon, 5 Nov 2018 15:36:44 -0500 Subject: [PATCH 02/10] Complete user profile for dev users --- atst/routes/dev.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/atst/routes/dev.py b/atst/routes/dev.py index 9aa7f5f2..b1f14a56 100644 --- a/atst/routes/dev.py +++ b/atst/routes/dev.py @@ -7,6 +7,7 @@ from flask import ( url_for, current_app as app, ) +import pendulum from . import redirect_after_login_url from atst.domain.users import Users @@ -21,6 +22,11 @@ _DEV_USERS = { "last_name": "Stevenson", "atat_role_name": "ccpo", "email": "sam@example.com", + "service_branch": "Fake Service Branch", + "phone_number": "1234567890", + "citizenship": "United States", + "designation": "Military", + "date_latest_training": pendulum.date(2018, 1, 1), }, "amanda": { "dod_id": "2345678901", @@ -28,6 +34,11 @@ _DEV_USERS = { "last_name": "Adamson", "atat_role_name": "default", "email": "amanda@example.com", + "service_branch": "Fake Service Branch", + "phone_number": "1234567890", + "citizenship": "United States", + "designation": "Military", + "date_latest_training": pendulum.date(2018, 1, 1), }, "brandon": { "dod_id": "3456789012", @@ -35,6 +46,11 @@ _DEV_USERS = { "last_name": "Buchannan", "atat_role_name": "default", "email": "brandon@example.com", + "service_branch": "Fake Service Branch", + "phone_number": "1234567890", + "citizenship": "United States", + "designation": "Military", + "date_latest_training": pendulum.date(2018, 1, 1), }, "christina": { "dod_id": "4567890123", @@ -42,6 +58,11 @@ _DEV_USERS = { "last_name": "Collins", "atat_role_name": "default", "email": "christina@example.com", + "service_branch": "Fake Service Branch", + "phone_number": "1234567890", + "citizenship": "United States", + "designation": "Military", + "date_latest_training": pendulum.date(2018, 1, 1), }, "dominick": { "dod_id": "5678901234", @@ -49,6 +70,11 @@ _DEV_USERS = { "last_name": "Domingo", "atat_role_name": "default", "email": "dominick@example.com", + "service_branch": "Fake Service Branch", + "phone_number": "1234567890", + "citizenship": "United States", + "designation": "Military", + "date_latest_training": pendulum.date(2018, 1, 1), }, "erica": { "dod_id": "6789012345", @@ -56,6 +82,11 @@ _DEV_USERS = { "last_name": "Eichner", "atat_role_name": "default", "email": "erica@example.com", + "service_branch": "Fake Service Branch", + "phone_number": "1234567890", + "citizenship": "United States", + "designation": "Military", + "date_latest_training": pendulum.date(2018, 1, 1), }, } From b1468f1376336b1a83daead07f01d382c9724786 Mon Sep 17 00:00:00 2001 From: richard-dds Date: Mon, 5 Nov 2018 15:41:39 -0500 Subject: [PATCH 03/10] Add some invited users to the seed script --- script/seed_sample.py | 45 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/script/seed_sample.py b/script/seed_sample.py index 9a3b1ff5..2c718791 100644 --- a/script/seed_sample.py +++ b/script/seed_sample.py @@ -12,8 +12,9 @@ from atst.domain.requests import Requests from atst.domain.workspaces import Workspaces from atst.domain.projects import Projects from atst.domain.workspace_roles import WorkspaceRoles +from atst.models.invitation import Status as InvitationStatus from atst.domain.exceptions import AlreadyExistsError -from tests.factories import RequestFactory, TaskOrderFactory +from tests.factories import RequestFactory, TaskOrderFactory, InvitationFactory from atst.routes.dev import _DEV_USERS as DEV_USERS WORKSPACE_USERS = [ @@ -40,6 +41,41 @@ WORKSPACE_USERS = [ }, ] +WORKSPACE_INVITED_USERS = [ + { + "first_name": "Frederick", + "last_name": "Fitzgerald", + "email": "frederick@mil.gov", + "workspace_role": "developer", + "dod_id": "0000000004", + "status": InvitationStatus.REJECTED_WRONG_USER + }, + { + "first_name": "Gina", + "last_name": "Guzman", + "email": "gina@mil.gov", + "workspace_role": "developer", + "dod_id": "0000000005", + "status": InvitationStatus.REJECTED_EXPIRED + }, + { + "first_name": "Hector", + "last_name": "Harper", + "email": "hector@mil.gov", + "workspace_role": "developer", + "dod_id": "0000000006", + "status": InvitationStatus.REVOKED + }, + { + "first_name": "Isabella", + "last_name": "Ingram", + "email": "isabella@mil.gov", + "workspace_role": "developer", + "dod_id": "0000000007", + "status": InvitationStatus.PENDING + }, +] + def seed_db(): users = [] @@ -78,6 +114,13 @@ def seed_db(): ws_role = Workspaces.create_member(user, workspace, workspace_role) WorkspaceRoles.enable(ws_role) + for workspace_role in WORKSPACE_INVITED_USERS: + ws_role = Workspaces.create_member(user, workspace, workspace_role) + invitation = InvitationFactory.build(workspace_role=ws_role, status=workspace_role["status"]) + db.session.add(invitation) + + db.session.commit() + Projects.create( user, workspace=workspace, From 280f0162edf8dcbc26ec0b7eb8c65048d69a89c2 Mon Sep 17 00:00:00 2001 From: richard-dds Date: Thu, 8 Nov 2018 14:58:52 -0500 Subject: [PATCH 04/10] Fix new test --- tests/domain/test_invitations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/domain/test_invitations.py b/tests/domain/test_invitations.py index 0b34695b..cb2adfe5 100644 --- a/tests/domain/test_invitations.py +++ b/tests/domain/test_invitations.py @@ -99,7 +99,7 @@ def test_revoke_invitation(): workspace = WorkspaceFactory.create() user = UserFactory.create() ws_role = WorkspaceRoleFactory.create(user=user, workspace=workspace) - invite = Invitations.create(ws_role, workspace.owner, user) + invite = Invitations.create(workspace.owner, ws_role) assert invite.is_pending Invitations.revoke(invite.token) assert invite.is_revoked From a7253105359a68a9146b1688dc0c254bbe46ec80 Mon Sep 17 00:00:00 2001 From: richard-dds Date: Thu, 8 Nov 2018 17:08:43 -0500 Subject: [PATCH 05/10] Resend a workspace member's invite --- atst/domain/invitations.py | 17 +++++++++++++++++ atst/models/workspace_role.py | 6 ++++++ atst/routes/workspaces.py | 6 ++++++ templates/workspaces/members/edit.html | 10 ++++++++++ tests/domain/test_invitations.py | 10 ++++++++++ tests/models/test_workspace_role.py | 14 ++++++++++++++ 6 files changed, 63 insertions(+) diff --git a/atst/domain/invitations.py b/atst/domain/invitations.py index 99b30b27..738525ab 100644 --- a/atst/domain/invitations.py +++ b/atst/domain/invitations.py @@ -4,6 +4,8 @@ from sqlalchemy.orm.exc import NoResultFound from atst.database import db from atst.models.invitation import Invitation, Status as InvitationStatus from atst.domain.workspace_roles import WorkspaceRoles +from atst.domain.authz import Authorization, Permissions +from atst.domain.workspaces import Workspaces from .exceptions import NotFoundError @@ -104,3 +106,18 @@ class Invitations(object): def revoke(cls, token): invite = Invitations._get(token) return Invitations._update_status(invite, InvitationStatus.REVOKED) + + @classmethod + def resend(cls, user, workspace_id, token): + workspace = Workspaces.get(user, workspace_id) + Authorization.check_workspace_permission( + user, + workspace, + Permissions.ASSIGN_AND_UNASSIGN_ATAT_ROLE, + "resend a workspace invitation", + ) + + previous_invitation = Invitations._get(token) + Invitations._update_status(previous_invitation, InvitationStatus.REVOKED) + + return Invitations.create(user, previous_invitation.workspace_role) diff --git a/atst/models/workspace_role.py b/atst/models/workspace_role.py index ad223c5f..c006b87a 100644 --- a/atst/models/workspace_role.py +++ b/atst/models/workspace_role.py @@ -109,6 +109,12 @@ class WorkspaceRole(Base, mixins.TimestampsMixin, mixins.AuditableMixin): def has_environment_roles(self): return self.num_environment_roles > 0 + @property + def can_resend_invitation(self): + return self.latest_invitation and ( + self.latest_invitation.is_rejected or self.latest_invitation.is_expired + ) + Index( "workspace_role_user_workspace", diff --git a/atst/routes/workspaces.py b/atst/routes/workspaces.py index a8f6493d..e1d6c321 100644 --- a/atst/routes/workspaces.py +++ b/atst/routes/workspaces.py @@ -374,3 +374,9 @@ def revoke_invitation(workspace_id, token): Invitations.revoke(token) return redirect(url_for("workspaces.workspace_members", workspace_id=workspace.id)) + + +@bp.route("/workspaces//invitations//resend", methods=["POST"]) +def resend_invitation(workspace_id, token): + Invitations.resend(g.current_user, workspace_id, token) + return redirect(url_for("workspaces.workspace_members", workspace_id=workspace_id)) diff --git a/templates/workspaces/members/edit.html b/templates/workspaces/members/edit.html index e840ffef..fcaf9553 100644 --- a/templates/workspaces/members/edit.html +++ b/templates/workspaces/members/edit.html @@ -39,6 +39,7 @@ {% if editable %} edit account details {% endif %} +
{% if member.latest_invitation.is_pending %} {{ ConfirmationButton( "Revoke Invitation", @@ -46,6 +47,15 @@ form.csrf_token ) }} {% endif %} + {% if member.can_resend_invitation %} + {{ ConfirmationButton ( + "Resend Invitation", + url_for("workspaces.resend_invitation", workspace_id=workspace.id, token=member.latest_invitation.token), + form.csrf_token, + confirm_msg="Are you sure? This will invalidate the previously sent invitation." + )}} + {% endif %} +
diff --git a/tests/domain/test_invitations.py b/tests/domain/test_invitations.py index cb2adfe5..e63258e8 100644 --- a/tests/domain/test_invitations.py +++ b/tests/domain/test_invitations.py @@ -103,3 +103,13 @@ def test_revoke_invitation(): assert invite.is_pending Invitations.revoke(invite.token) assert invite.is_revoked + + +def test_resend_invitation(): + workspace = WorkspaceFactory.create() + user = UserFactory.create() + ws_role = WorkspaceRoleFactory.create(user=user, workspace=workspace) + invite = Invitations.create(workspace.owner, ws_role) + Invitations.resend(workspace.owner, workspace.id, invite.token) + assert ws_role.invitations[0].is_revoked + assert ws_role.invitations[1].is_pending diff --git a/tests/models/test_workspace_role.py b/tests/models/test_workspace_role.py index 573b67ef..3622e88a 100644 --- a/tests/models/test_workspace_role.py +++ b/tests/models/test_workspace_role.py @@ -96,3 +96,17 @@ def test_status_when_invitation_is_expired(): ] ) assert workspace_role.display_status == "Invite expired" + + +def test_can_not_resend_invitation_if_active(): + workspace_role = WorkspaceRoleFactory.create( + invitations=[InvitationFactory.create(status=InvitationStatus.ACCEPTED)] + ) + assert not workspace_role.can_resend_invitation + + +def test_can_resend_invitation_if_expired(): + workspace_role = WorkspaceRoleFactory.create( + invitations=[InvitationFactory.create(status=InvitationStatus.REJECTED_EXPIRED)] + ) + assert workspace_role.can_resend_invitation From 2f1970e93b5838119fa21550ff78cf1a156273d3 Mon Sep 17 00:00:00 2001 From: richard-dds Date: Mon, 12 Nov 2018 13:39:47 -0500 Subject: [PATCH 06/10] Give dev users a random service branch --- atst/routes/dev.py | 32 ++++++++++++++++++++++---------- tests/factories.py | 1 - 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/atst/routes/dev.py b/atst/routes/dev.py index b1f14a56..ee43a38d 100644 --- a/atst/routes/dev.py +++ b/atst/routes/dev.py @@ -12,6 +12,8 @@ import pendulum from . import redirect_after_login_url from atst.domain.users import Users from atst.queue import queue +from tests.factories import random_service_branch +from atst.utils import pick bp = Blueprint("dev", __name__) @@ -22,7 +24,7 @@ _DEV_USERS = { "last_name": "Stevenson", "atat_role_name": "ccpo", "email": "sam@example.com", - "service_branch": "Fake Service Branch", + "service_branch": random_service_branch(), "phone_number": "1234567890", "citizenship": "United States", "designation": "Military", @@ -34,7 +36,7 @@ _DEV_USERS = { "last_name": "Adamson", "atat_role_name": "default", "email": "amanda@example.com", - "service_branch": "Fake Service Branch", + "service_branch": random_service_branch(), "phone_number": "1234567890", "citizenship": "United States", "designation": "Military", @@ -46,7 +48,7 @@ _DEV_USERS = { "last_name": "Buchannan", "atat_role_name": "default", "email": "brandon@example.com", - "service_branch": "Fake Service Branch", + "service_branch": random_service_branch(), "phone_number": "1234567890", "citizenship": "United States", "designation": "Military", @@ -58,7 +60,7 @@ _DEV_USERS = { "last_name": "Collins", "atat_role_name": "default", "email": "christina@example.com", - "service_branch": "Fake Service Branch", + "service_branch": random_service_branch(), "phone_number": "1234567890", "citizenship": "United States", "designation": "Military", @@ -70,7 +72,7 @@ _DEV_USERS = { "last_name": "Domingo", "atat_role_name": "default", "email": "dominick@example.com", - "service_branch": "Fake Service Branch", + "service_branch": random_service_branch(), "phone_number": "1234567890", "citizenship": "United States", "designation": "Military", @@ -82,7 +84,7 @@ _DEV_USERS = { "last_name": "Eichner", "atat_role_name": "default", "email": "erica@example.com", - "service_branch": "Fake Service Branch", + "service_branch": random_service_branch(), "phone_number": "1234567890", "citizenship": "United States", "designation": "Military", @@ -97,10 +99,20 @@ def login_dev(): user_data = _DEV_USERS[role] user = Users.get_or_create_by_dod_id( user_data["dod_id"], - atat_role_name=user_data["atat_role_name"], - first_name=user_data["first_name"], - last_name=user_data["last_name"], - email=user_data["email"], + **pick( + [ + "atat_role_name", + "first_name", + "last_name", + "email", + "service_branch", + "phone_number", + "citizenship", + "designation", + "date_latest_training", + ], + user_data, + ), ) session["user_id"] = user.id diff --git a/tests/factories.py b/tests/factories.py index 179fc02d..8d62a22d 100644 --- a/tests/factories.py +++ b/tests/factories.py @@ -21,7 +21,6 @@ from atst.domain.roles import Roles from atst.models.workspace_role import WorkspaceRole, Status as WorkspaceRoleStatus from atst.models.environment_role import EnvironmentRole from atst.models.invitation import Invitation, Status as InvitationStatus -from atst.domain.workspaces import Workspaces from atst.domain.invitations import Invitations From 016116c9ccbadb751adbe06269689c896c557b59 Mon Sep 17 00:00:00 2001 From: richard-dds Date: Mon, 12 Nov 2018 14:05:20 -0500 Subject: [PATCH 07/10] Send an email --- atst/models/invitation.py | 4 ++++ atst/routes/workspaces.py | 5 ++++- tests/routes/test_workspaces.py | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/atst/models/invitation.py b/atst/models/invitation.py index ca3eba9c..f65ff4da 100644 --- a/atst/models/invitation.py +++ b/atst/models/invitation.py @@ -81,3 +81,7 @@ class Invitation(Base, TimestampsMixin, AuditableMixin): def workspace(self): if self.workspace_role: return self.workspace_role.workspace + + @property + def user_email(self): + return self.workspace_role.user.email diff --git a/atst/routes/workspaces.py b/atst/routes/workspaces.py index e1d6c321..51d04546 100644 --- a/atst/routes/workspaces.py +++ b/atst/routes/workspaces.py @@ -378,5 +378,8 @@ def revoke_invitation(workspace_id, token): @bp.route("/workspaces//invitations//resend", methods=["POST"]) def resend_invitation(workspace_id, token): - Invitations.resend(g.current_user, workspace_id, token) + invite = Invitations.resend(g.current_user, workspace_id, token) + send_invite_email( + g.current_user.full_name, invite.token, invite.user_email + ) return redirect(url_for("workspaces.workspace_members", workspace_id=workspace_id)) diff --git a/tests/routes/test_workspaces.py b/tests/routes/test_workspaces.py index 8d30058f..c773af29 100644 --- a/tests/routes/test_workspaces.py +++ b/tests/routes/test_workspaces.py @@ -457,3 +457,22 @@ def test_revoke_invitation(client, user_session): assert response.status_code == 302 assert invite.is_revoked + + +def test_resend_invitation_sends_email(client, user_session, queue): + user = UserFactory.create() + workspace = WorkspaceFactory.create() + ws_role = WorkspaceRoleFactory.create( + user=user, workspace=workspace, status=WorkspaceRoleStatus.PENDING + ) + invite = InvitationFactory.create( + user_id=user.id, + workspace_role_id=ws_role.id, + status=InvitationStatus.PENDING, + ) + user_session(workspace.owner) + client.post( + url_for("workspaces.resend_invitation", workspace_id=workspace.id, token=invite.token) + ) + + assert len(queue.get_queue()) == 1 From 248df9e18498c9e4f6a4adb27ba2ed52577a53a7 Mon Sep 17 00:00:00 2001 From: richard-dds Date: Mon, 12 Nov 2018 14:33:11 -0500 Subject: [PATCH 08/10] Formatting --- atst/routes/workspaces.py | 4 +--- tests/routes/test_workspaces.py | 10 ++++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/atst/routes/workspaces.py b/atst/routes/workspaces.py index 51d04546..101a4e7c 100644 --- a/atst/routes/workspaces.py +++ b/atst/routes/workspaces.py @@ -379,7 +379,5 @@ def revoke_invitation(workspace_id, token): @bp.route("/workspaces//invitations//resend", methods=["POST"]) def resend_invitation(workspace_id, token): invite = Invitations.resend(g.current_user, workspace_id, token) - send_invite_email( - g.current_user.full_name, invite.token, invite.user_email - ) + send_invite_email(g.current_user.full_name, invite.token, invite.user_email) return redirect(url_for("workspaces.workspace_members", workspace_id=workspace_id)) diff --git a/tests/routes/test_workspaces.py b/tests/routes/test_workspaces.py index c773af29..3737042e 100644 --- a/tests/routes/test_workspaces.py +++ b/tests/routes/test_workspaces.py @@ -466,13 +466,15 @@ def test_resend_invitation_sends_email(client, user_session, queue): user=user, workspace=workspace, status=WorkspaceRoleStatus.PENDING ) invite = InvitationFactory.create( - user_id=user.id, - workspace_role_id=ws_role.id, - status=InvitationStatus.PENDING, + user_id=user.id, workspace_role_id=ws_role.id, status=InvitationStatus.PENDING ) user_session(workspace.owner) client.post( - url_for("workspaces.resend_invitation", workspace_id=workspace.id, token=invite.token) + url_for( + "workspaces.resend_invitation", + workspace_id=workspace.id, + token=invite.token, + ) ) assert len(queue.get_queue()) == 1 From f6b4b2444f2bdc4e466f0e81e038556cd14586fc Mon Sep 17 00:00:00 2001 From: richard-dds Date: Mon, 12 Nov 2018 15:52:31 -0500 Subject: [PATCH 09/10] Update confirmation copy --- templates/workspaces/members/edit.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/workspaces/members/edit.html b/templates/workspaces/members/edit.html index fcaf9553..0c1e8f7a 100644 --- a/templates/workspaces/members/edit.html +++ b/templates/workspaces/members/edit.html @@ -52,7 +52,7 @@ "Resend Invitation", url_for("workspaces.resend_invitation", workspace_id=workspace.id, token=member.latest_invitation.token), form.csrf_token, - confirm_msg="Are you sure? This will invalidate the previously sent invitation." + confirm_msg="Are you sure? This will send an email to invite the user to join this workspace." )}} {% endif %} From 78e34d4dddcf898e2782b94c0d6b06770711f82b Mon Sep 17 00:00:00 2001 From: richard-dds Date: Tue, 13 Nov 2018 11:31:57 -0500 Subject: [PATCH 10/10] Display an alert when invitations are resent --- atst/models/invitation.py | 4 ++++ atst/routes/workspaces.py | 10 +++++++++- templates/workspaces/members/index.html | 11 +++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/atst/models/invitation.py b/atst/models/invitation.py index f65ff4da..3768849c 100644 --- a/atst/models/invitation.py +++ b/atst/models/invitation.py @@ -85,3 +85,7 @@ class Invitation(Base, TimestampsMixin, AuditableMixin): @property def user_email(self): return self.workspace_role.user.email + + @property + def user_name(self): + return self.workspace_role.user.full_name diff --git a/atst/routes/workspaces.py b/atst/routes/workspaces.py index 101a4e7c..c216d5ee 100644 --- a/atst/routes/workspaces.py +++ b/atst/routes/workspaces.py @@ -103,6 +103,7 @@ def show_workspace(workspace_id): def workspace_members(workspace_id): workspace = Workspaces.get_with_members(g.current_user, workspace_id) new_member_name = http_request.args.get("newMemberName") + resent_invitation_to = http_request.args.get("resentInvitationTo") new_member = next( filter(lambda m: m.user_name == new_member_name, workspace.members), None ) @@ -127,6 +128,7 @@ def workspace_members(workspace_id): status_choices=MEMBER_STATUSES, members=members_list, new_member=new_member, + resent_invitation_to=resent_invitation_to, ) @@ -380,4 +382,10 @@ def revoke_invitation(workspace_id, token): def resend_invitation(workspace_id, token): invite = Invitations.resend(g.current_user, workspace_id, token) send_invite_email(g.current_user.full_name, invite.token, invite.user_email) - return redirect(url_for("workspaces.workspace_members", workspace_id=workspace_id)) + return redirect( + url_for( + "workspaces.workspace_members", + workspace_id=workspace_id, + resentInvitationTo=invite.user_name, + ) + ) diff --git a/templates/workspaces/members/index.html b/templates/workspaces/members/index.html index 9b0e1e5f..0c2c17a2 100644 --- a/templates/workspaces/members/index.html +++ b/templates/workspaces/members/index.html @@ -33,6 +33,17 @@ ) }} {% endif %} +{% if resent_invitation_to %} + {% set message -%} +

Successfully sent a new invitation to {{ resent_invitation_to }}.

+ {%- endset %} + + {{ Alert('Invitation resent', + message=message, + level='success' + ) }} +{% endif %} + {% set member_name = request.args.get("memberName") %} {% set updated_role = request.args.get("updatedRole") %} {% if updated_role %}