Repository: pypi/warehouse

Issue:
Confirmation message when declining an invitation
When a user has been invited to join an organization, the user can decline the invitation. When declining the invitation, there is no confirmation message that asks 'Are you sure?'. When declining an invitation, the user should be able to provide a message to the Owner. The message can then be shared with the Owner in the declination email that is sent to the Owner.

To replicate this issue-

1. Log in
2. Click on 'Your organizations'
3. Under 'Pending invitations', click on 'Decline'

A banner appears that the invitation has been declined. But there is no confirmation message.
![org8](https://user-images.githubusercontent.com/37237726/175117523-a14217d1-8723-4bcc-9f18-05f55c3f7bea.png)




Discussion:
Priority 3/3

Resolving patch:
diff --git a/warehouse/accounts/views.py b/warehouse/accounts/views.py
--- a/warehouse/accounts/views.py
+++ b/warehouse/accounts/views.py
@@ -881,6 +881,7 @@ def _error(message):
     elif request.method == "POST" and "decline" in request.POST:
         organization_service.delete_organization_invite(organization_invite.id)
         submitter_user = user_service.get_user(data.get("submitter_id"))
+        message = request.params.get("message", "")
         organization.record_event(
             tag="organization:organization_role:declined",
             ip_address=request.remote_addr,
@@ -911,6 +912,7 @@ def _error(message):
             owner_users,
             user=user,
             organization_name=organization.name,
+            message=message,
         )
         send_declined_as_invited_organization_member_email(
             request,
diff --git a/warehouse/email/__init__.py b/warehouse/email/__init__.py
--- a/warehouse/email/__init__.py
+++ b/warehouse/email/__init__.py
@@ -448,10 +448,12 @@ def send_organization_member_invite_declined_email(
     *,
     user,
     organization_name,
+    message,
 ):
     return {
         "username": user.username,
         "organization_name": organization_name,
+        "message": message,
     }
 
 


Test patch:
diff --git a/tests/unit/accounts/test_views.py b/tests/unit/accounts/test_views.py
--- a/tests/unit/accounts/test_views.py
+++ b/tests/unit/accounts/test_views.py
@@ -2363,10 +2363,13 @@ def test_verify_organization_role_declined(
             user=owner_user,
             role_name=OrganizationRoleType.Owner,
         )
+        message = "Some reason to decline."
 
         db_request.user = user
         db_request.method = "POST"
-        db_request.POST.update({"token": "RANDOM_KEY", "decline": "Decline"})
+        db_request.POST.update(
+            {"token": "RANDOM_KEY", "decline": "Decline", "message": message}
+        )
         db_request.route_path = pretend.call_recorder(lambda name: "/")
         db_request.remote_addr = "192.168.1.1"
         db_request.session.flash = pretend.call_recorder(lambda *a, **kw: None)
@@ -2411,6 +2414,7 @@ def test_verify_organization_role_declined(
                 {owner_user},
                 user=user,
                 organization_name=organization.name,
+                message=message,
             )
         ]
         assert declined_as_invited_organization_member_email.calls == [
diff --git a/tests/unit/email/test_init.py b/tests/unit/email/test_init.py
--- a/tests/unit/email/test_init.py
+++ b/tests/unit/email/test_init.py
@@ -1918,6 +1918,7 @@ def organization_invite(self, pyramid_user):
         EmailFactory.create(user=self.user, verified=True)
         self.desired_role = "Manager"
         self.organization_name = "example"
+        self.message = "test message"
         self.email_token = "token"
         self.token_age = 72 * 60 * 60
 
@@ -2159,11 +2160,13 @@ def test_send_organization_member_invite_declined_email(
             self.initiator_user,
             user=self.user,
             organization_name=self.organization_name,
+            message=self.message,
         )
 
         assert result == {
             "username": self.user.username,
             "organization_name": self.organization_name,
+            "message": self.message,
         }
         subject_renderer.assert_(**result)
         body_renderer.assert_(**result)