mirror of https://github.com/zulip/zulip.git
invites: Add option to receive notification on accepted invitations.
Previously, when a referrer's invitation to Zulip was accepted, they got a notification from notification-bot indicating their invitation has been accepted. This commit adds an option for referrer to decide whether he wants to receive the direct notification from the notification-bot. Fixes: #20398
This commit is contained in:
parent
5da629895f
commit
4cce94b667
|
@ -20,6 +20,13 @@ format used by the Zulip server that they are interacting with.
|
|||
|
||||
## Changes in Zulip 9.0
|
||||
|
||||
**Feature level 267**
|
||||
|
||||
* [`GET /invites`](/api/get-invites),[`POST /invites`](/api/send-invites): Added
|
||||
`notify_referrer_on_join` parameter, indicating whether the referrer has opted
|
||||
to receive a direct message from the notification bot whenever a user joins
|
||||
via this invitation.
|
||||
|
||||
**Feature level 266**
|
||||
|
||||
* `PATCH /realm`, [`POST /register`](/api/register-queue),
|
||||
|
|
|
@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.9.3"
|
|||
# Changes should be accompanied by documentation explaining what the
|
||||
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
||||
# entries in the endpoint's documentation in `zulip.yaml`.
|
||||
API_FEATURE_LEVEL = 266
|
||||
API_FEATURE_LEVEL = 267
|
||||
|
||||
# Bump the minor PROVISION_VERSION to indicate that folks should provision
|
||||
# only when going from an old version of the code to a newer version. Bump
|
||||
|
|
|
@ -47,6 +47,7 @@ function reset_error_messages(): void {
|
|||
function get_common_invitation_data(): {
|
||||
csrfmiddlewaretoken: string;
|
||||
invite_as: number;
|
||||
notify_referrer_on_join: boolean;
|
||||
stream_ids: string;
|
||||
invite_expires_in_minutes: string;
|
||||
invitee_emails: string;
|
||||
|
@ -56,6 +57,7 @@ function get_common_invitation_data(): {
|
|||
$<HTMLSelectOneElement>("select:not([multiple])#invite_as").val()!,
|
||||
10,
|
||||
);
|
||||
const notify_referrer_on_join = $("#receive-invite-acceptance-notification").is(":checked");
|
||||
const raw_expires_in = $<HTMLSelectOneElement>("select:not([multiple])#expires_in").val()!;
|
||||
// See settings_config.expires_in_values for why we do this conversion.
|
||||
let expires_in: number | null;
|
||||
|
@ -82,6 +84,7 @@ function get_common_invitation_data(): {
|
|||
const data = {
|
||||
csrfmiddlewaretoken: csrf_token,
|
||||
invite_as,
|
||||
notify_referrer_on_join,
|
||||
stream_ids: JSON.stringify(stream_ids),
|
||||
invite_expires_in_minutes: JSON.stringify(expires_in),
|
||||
invitee_emails: pills
|
||||
|
@ -459,9 +462,11 @@ function open_invite_user_modal(e: JQuery.ClickEvent<Document, undefined>): void
|
|||
switch (key) {
|
||||
case "invite-email-tab":
|
||||
$("#invitee_emails_container").show();
|
||||
$("#receive-invite-acceptance-notification-container").show();
|
||||
break;
|
||||
case "invite-link-tab":
|
||||
$("#invitee_emails_container").hide();
|
||||
$("#receive-invite-acceptance-notification-container").hide();
|
||||
break;
|
||||
}
|
||||
toggle_invite_submit_button(key);
|
||||
|
|
|
@ -26,6 +26,7 @@ export const invite_schema = z.intersection(
|
|||
expiry_date: z.number().nullable(),
|
||||
id: z.number(),
|
||||
invited_as: z.number(),
|
||||
notify_referrer_on_join: z.boolean(),
|
||||
}),
|
||||
z.discriminatedUnion("is_multiuse", [
|
||||
z.object({
|
||||
|
@ -46,6 +47,7 @@ type Invite = z.output<typeof invite_schema> & {
|
|||
disable_buttons?: boolean;
|
||||
referrer_name?: string;
|
||||
img_src?: string;
|
||||
notify_referrer_on_join?: boolean;
|
||||
};
|
||||
|
||||
const meta = {
|
||||
|
|
|
@ -26,6 +26,13 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group new-style" id="receive-invite-acceptance-notification-container">
|
||||
<label class="checkbox display-block">
|
||||
<input type="checkbox" id="receive-invite-acceptance-notification" checked/>
|
||||
<span></span>
|
||||
{{t "Send me a direct message when my invitation is accepted" }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="expires_in" class="modal-field-label">{{t "Invitation expires after" }}</label>
|
||||
<select id="expires_in" class="invite-user-select modal_select bootstrap-focus-style">
|
||||
|
|
|
@ -291,6 +291,7 @@ def process_new_human_user(
|
|||
and prereg_user is not None
|
||||
and prereg_user.referred_by is not None
|
||||
and prereg_user.referred_by.is_active
|
||||
and prereg_user.notify_referrer_on_join
|
||||
):
|
||||
# This is a cross-realm direct message.
|
||||
with override_language(prereg_user.referred_by.default_language):
|
||||
|
|
|
@ -174,6 +174,7 @@ def do_invite_users(
|
|||
user_profile: UserProfile,
|
||||
invitee_emails: Collection[str],
|
||||
streams: Collection[Stream],
|
||||
notify_referrer_on_join: bool = True,
|
||||
*,
|
||||
invite_expires_in_minutes: Optional[int],
|
||||
include_realm_default_subscriptions: bool,
|
||||
|
@ -264,6 +265,7 @@ def do_invite_users(
|
|||
invited_as=invite_as,
|
||||
realm=realm,
|
||||
include_realm_default_subscriptions=include_realm_default_subscriptions,
|
||||
notify_referrer_on_join=notify_referrer_on_join,
|
||||
)
|
||||
prereg_user.save()
|
||||
stream_ids = [stream.id for stream in streams]
|
||||
|
@ -318,6 +320,7 @@ def do_get_invites_controlled_by_user(user_profile: UserProfile) -> List[Dict[st
|
|||
id=invitee.id,
|
||||
invited_as=invitee.invited_as,
|
||||
is_multiuse=False,
|
||||
notify_referrer_on_join=invitee.notify_referrer_on_join,
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
# Generated by Django 5.0.6 on 2024-06-28 17:34
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("zerver", "0541_alter_realmauditlog_options"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="preregistrationuser",
|
||||
name="notify_referrer_on_join",
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
]
|
|
@ -69,6 +69,7 @@ class PreregistrationUser(models.Model):
|
|||
full_name = models.CharField(max_length=UserProfile.MAX_NAME_LENGTH, null=True)
|
||||
full_name_validated = models.BooleanField(default=False)
|
||||
referred_by = models.ForeignKey(UserProfile, null=True, on_delete=CASCADE)
|
||||
notify_referrer_on_join = models.BooleanField(default=True)
|
||||
streams = models.ManyToManyField("zerver.Stream")
|
||||
invited_at = models.DateTimeField(auto_now=True)
|
||||
realm_creation = models.BooleanField(default=False)
|
||||
|
|
|
@ -12425,6 +12425,7 @@ paths:
|
|||
invited: 1710606654,
|
||||
invited_as: 200,
|
||||
invited_by_user_id: 9,
|
||||
notify_referrer_on_join: true,
|
||||
is_multiuse: false,
|
||||
},
|
||||
{
|
||||
|
@ -12434,6 +12435,7 @@ paths:
|
|||
invited_as: 400,
|
||||
invited_by_user_id: 9,
|
||||
is_multiuse: true,
|
||||
notify_referrer_on_join: true,
|
||||
link_url: "https://example.zulipchat.com/join/yddhtzk4jgl7rsmazc5fyyyy/",
|
||||
},
|
||||
],
|
||||
|
@ -12511,6 +12513,18 @@ paths:
|
|||
type: boolean
|
||||
default: false
|
||||
example: false
|
||||
notify_referrer_on_join:
|
||||
description: |
|
||||
A boolean indicating whether the referrer would like to receive a
|
||||
direct message from [notification
|
||||
bot](/help/configure-automated-notices) when a user account is created
|
||||
using this invitation.
|
||||
|
||||
**Changes**: New in Zulip 9.0 (feature level 267). Previously,
|
||||
referrers always received such direct messages.
|
||||
type: boolean
|
||||
example: false
|
||||
default: true
|
||||
required:
|
||||
- invitee_emails
|
||||
- stream_ids
|
||||
|
@ -20688,6 +20702,15 @@ components:
|
|||
description: |
|
||||
The email address the invitation was sent to. This will not be present when
|
||||
`is_multiuse` is `true` (i.e. the invitation is a reusable invitation link).
|
||||
notify_referrer_on_join:
|
||||
type: boolean
|
||||
description: |
|
||||
A boolean indicating whether the referrer has opted to receive a direct
|
||||
message from [notification bot](/help/configure-automated-notices) when a user
|
||||
account is created using this invitation.
|
||||
|
||||
**Changes**: New in Zulip 9.0 (feature level 267). Previously,
|
||||
referrers always received such direct messages.
|
||||
link_url:
|
||||
type: string
|
||||
description: |
|
||||
|
|
|
@ -175,6 +175,7 @@ class InviteUserBase(ZulipTestCase):
|
|||
self,
|
||||
invitee_emails: str,
|
||||
stream_names: Sequence[str],
|
||||
notify_referrer_on_join: bool = True,
|
||||
invite_expires_in_minutes: Optional[int] = INVITATION_LINK_VALIDITY_MINUTES,
|
||||
body: str = "",
|
||||
invite_as: int = PreregistrationUser.INVITE_AS["MEMBER"],
|
||||
|
@ -206,6 +207,7 @@ class InviteUserBase(ZulipTestCase):
|
|||
"include_realm_default_subscriptions": orjson.dumps(
|
||||
include_realm_default_subscriptions
|
||||
).decode(),
|
||||
"notify_referrer_on_join": orjson.dumps(notify_referrer_on_join).decode(),
|
||||
},
|
||||
subdomain=realm.string_id if realm else "zulip",
|
||||
)
|
||||
|
@ -1046,6 +1048,51 @@ earl-test@zulip.com""",
|
|||
]
|
||||
)
|
||||
|
||||
def test_direct_notification_for_accepted_invitation(self) -> None:
|
||||
"""
|
||||
New test to check if notification-bot message is sent to the referrer
|
||||
when notify_referrer_on_join is false.
|
||||
"""
|
||||
|
||||
self.login("hamlet")
|
||||
user_profile = self.example_user("hamlet")
|
||||
invitee = self.nonreg_email("alice")
|
||||
realm = get_realm("zulip")
|
||||
self.assert_json_success(self.invite(invitee, ["Denmark"], False))
|
||||
self.assertTrue(find_key_by_email(invitee))
|
||||
self.submit_reg_form_for_user(invitee, "password")
|
||||
|
||||
assert user_profile.recipient_id is not None
|
||||
invite_acceptance_notification_message = Message.objects.filter(
|
||||
recipient_id=user_profile.recipient_id, realm=realm
|
||||
).last()
|
||||
|
||||
self.assertIsNone(
|
||||
invite_acceptance_notification_message,
|
||||
"Unexpected message found from notification-bot for accepted invitations.",
|
||||
)
|
||||
|
||||
self.login("hamlet")
|
||||
new_invitee = self.nonreg_email("bob")
|
||||
self.assert_json_success(self.invite(new_invitee, ["Denmark"], True))
|
||||
self.assertTrue(find_key_by_email(new_invitee))
|
||||
self.submit_reg_form_for_user(new_invitee, "new_password")
|
||||
|
||||
new_invitee_profile = self.nonreg_user("bob")
|
||||
new_invite_acceptance_notification_message = Message.objects.filter(
|
||||
recipient_id=user_profile.recipient_id, realm=realm
|
||||
).last()
|
||||
|
||||
assert new_invite_acceptance_notification_message is not None
|
||||
self.assertEqual(
|
||||
new_invite_acceptance_notification_message.sender.email, "notification-bot@zulip.com"
|
||||
)
|
||||
self.assertTrue(
|
||||
new_invite_acceptance_notification_message.content.startswith(
|
||||
f"@_**{new_invitee_profile.full_name}|{new_invitee_profile.id}** accepted your",
|
||||
)
|
||||
)
|
||||
|
||||
def test_max_invites_model(self) -> None:
|
||||
realm = get_realm("zulip")
|
||||
self.assertEqual(realm.max_invites, settings.INVITES_DEFAULT_REALM_DAILY_MAX)
|
||||
|
@ -2302,7 +2349,7 @@ class InvitationsTestCase(InviteUserBase):
|
|||
self.login("iago")
|
||||
invitee = "resend@zulip.com"
|
||||
|
||||
self.assert_json_success(self.invite(invitee, ["Denmark"], None))
|
||||
self.assert_json_success(self.invite(invitee, ["Denmark"], True, None))
|
||||
prereg_user = PreregistrationUser.objects.get(email=invitee)
|
||||
|
||||
# Verify and then clear from the outbox the original invite email
|
||||
|
|
|
@ -58,6 +58,9 @@ def invite_users_backend(
|
|||
),
|
||||
default=PreregistrationUser.INVITE_AS["MEMBER"],
|
||||
),
|
||||
notify_referrer_on_join: bool = REQ(
|
||||
"notify_referrer_on_join", json_validator=check_bool, default=True
|
||||
),
|
||||
stream_ids: List[int] = REQ(json_validator=check_list(check_int)),
|
||||
include_realm_default_subscriptions: bool = REQ(json_validator=check_bool, default=False),
|
||||
) -> HttpResponse:
|
||||
|
@ -99,6 +102,7 @@ def invite_users_backend(
|
|||
user_profile,
|
||||
invitee_emails,
|
||||
streams,
|
||||
notify_referrer_on_join,
|
||||
invite_expires_in_minutes=invite_expires_in_minutes,
|
||||
include_realm_default_subscriptions=include_realm_default_subscriptions,
|
||||
invite_as=invite_as,
|
||||
|
|
Loading…
Reference in New Issue