diff --git a/static/js/invite.js b/static/js/invite.js index a336c119631028..6dd67a58b352b2 100644 --- a/static/js/invite.js +++ b/static/js/invite.js @@ -19,6 +19,7 @@ import * as settings_config from "./settings_config"; import * as stream_data from "./stream_data"; import * as ui from "./ui"; import * as ui_report from "./ui_report"; +import {user_settings} from "./user_settings"; import * as util from "./util"; function reset_error_messages() { @@ -68,6 +69,15 @@ function submit_invitation_form() { const invitee_emails_group = invitee_emails.closest(".control-group"); const data = get_common_invitation_data(); data.invitee_emails = $("#invitee_emails").val(); + const receive_private_message_on_invitee_signup = JSON.stringify( + $("#receive_pm").prop("checked"), + ); + + channel.patch({ + url: "/json/settings", + idempotent: true, + data: {receive_private_message_on_invitee_signup}, + }); channel.post({ url: "/json/invites", @@ -126,6 +136,7 @@ function submit_invitation_form() { $("#submit-invitation").text($t({defaultMessage: "Invite"})); $("#submit-invitation").prop("disabled", false); $("#invitee_emails").focus(); + $("#receive_pm").prop("checked", true); ui.get_scroll_element($("#invite_user_form .modal-body"))[0].scrollTop = 0; }, }); @@ -211,6 +222,7 @@ export function initialize() { development_environment: page_params.development_environment, invite_as_options: settings_config.user_role_values, expires_in_options: settings_config.expires_in_values, + receive_pm_enabled: user_settings.receive_private_message_on_invitee_signup, }); $(".app").append(rendered); @@ -237,6 +249,7 @@ export function initialize() { $("#multiuse_radio_section").show(); $("#invite-method-choice").hide(); $("#invitee_emails").prop("disabled", true); + $("#receive_pm").prop("disabled", true); $("#submit-invitation").text($t({defaultMessage: "Generate invite link"})); $("#submit-invitation").data("loading-text", $t({defaultMessage: "Generating link..."})); reset_error_messages(); @@ -244,6 +257,7 @@ export function initialize() { $("#invite-user").on("change", "#generate_multiuse_invite_radio", () => { $("#invitee_emails").prop("disabled", false); + $("#receive_pm").prop("disabled", false); $("#submit-invitation").text($t({defaultMessage: "Invite"})); $("#submit-invitation").data("loading-text", $t({defaultMessage: "Inviting..."})); $("#multiuse_radio_section").hide(); diff --git a/static/js/user_settings.ts b/static/js/user_settings.ts index bb6bbff0fcbb4a..c366cd71b55936 100644 --- a/static/js/user_settings.ts +++ b/static/js/user_settings.ts @@ -37,6 +37,7 @@ export type UserSettingsType = { send_stream_typing_notifications: boolean; send_private_typing_notifications: boolean; send_read_receipts: boolean; + receive_private_message_on_invitee_signup: boolean; }; export let user_settings = {} as UserSettingsType; diff --git a/static/templates/invite_user.hbs b/static/templates/invite_user.hbs index edbd6ddbecff8e..21123911ae8649 100644 --- a/static/templates/invite_user.hbs +++ b/static/templates/invite_user.hbs @@ -60,6 +60,13 @@ +
+ +
diff --git a/templates/zerver/api/changelog.md b/templates/zerver/api/changelog.md index 83a7424ee36c29..df9998b1230d8b 100644 --- a/templates/zerver/api/changelog.md +++ b/templates/zerver/api/changelog.md @@ -20,6 +20,10 @@ format used by the Zulip server that they are interacting with. ## Changes in Zulip 5.0 +**Feature level 115** + +* [`PATCH /settings`](/api/update-settings), [`POST /register`](/api/register-queue), [`PATCH /settings`](/api/get-server-settings): Added new user notification setting `receive_private_message_on_invitee_signup` to allow user to choose whether or not to get private message when an invitee joins. + **Feature level 114** * [`GET /events`](/api/get-events): Added `rendering_only` field to diff --git a/version.py b/version.py index 18816eda0b4af2..71905ba4258abf 100644 --- a/version.py +++ b/version.py @@ -33,7 +33,7 @@ # Changes should be accompanied by documentation explaining what the # new level means in templates/zerver/api/changelog.md, as well as # "**Changes**" entries in the endpoint's documentation in `zulip.yaml`. -API_FEATURE_LEVEL = 114 +API_FEATURE_LEVEL = 115 # 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 diff --git a/zerver/lib/actions.py b/zerver/lib/actions.py index c0dfe63e2ae6c0..f8ca06bbe461b8 100644 --- a/zerver/lib/actions.py +++ b/zerver/lib/actions.py @@ -511,6 +511,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.referred_by.receive_private_message_on_invitee_signup ): # This is a cross-realm private message. with override_language(prereg_user.referred_by.default_language): @@ -7622,7 +7623,10 @@ def do_invite_users( for email in validated_emails: # The logged in user is the referrer. prereg_user = PreregistrationUser( - email=email, referred_by=user_profile, invited_as=invite_as, realm=user_profile.realm + email=email, + referred_by=user_profile, + invited_as=invite_as, + realm=user_profile.realm, ) prereg_user.save() stream_ids = [stream.id for stream in streams] diff --git a/zerver/migrations/0375_add_field_receive_private_message_on_invitee_signup.py b/zerver/migrations/0375_add_field_receive_private_message_on_invitee_signup.py new file mode 100644 index 00000000000000..c32b01bc92d463 --- /dev/null +++ b/zerver/migrations/0375_add_field_receive_private_message_on_invitee_signup.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.10 on 2022-01-29 16:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('zerver', '0374_backfill_user_delete_realmauditlog'), + ] + + operations = [ + migrations.AddField( + model_name='realmuserdefault', + name='receive_private_message_on_invitee_signup', + field=models.BooleanField(default=True), + ), + migrations.AddField( + model_name='userprofile', + name='receive_private_message_on_invitee_signup', + field=models.BooleanField(default=True), + ), + ] diff --git a/zerver/models.py b/zerver/models.py index 162df0f6b5e90e..a453541c5f4f70 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -1492,6 +1492,7 @@ class UserBaseSettings(models.Model): enable_marketing_emails: bool = models.BooleanField(default=True) realm_name_in_notifications: bool = models.BooleanField(default=False) presence_enabled: bool = models.BooleanField(default=True) + receive_private_message_on_invitee_signup: bool = models.BooleanField(default=True) # Whether or not the user wants to sync their drafts. enable_drafts_synchronization = models.BooleanField(default=True) @@ -1542,7 +1543,10 @@ class UserBaseSettings(models.Model): ) notification_setting_types = { - **notification_settings_legacy + **notification_settings_legacy, + **dict( + receive_private_message_on_invitee_signup=bool, + ), } # Add new notifications settings here. # Define the types of the various automatically managed properties diff --git a/zerver/openapi/zulip.yaml b/zerver/openapi/zulip.yaml index 2b81dc081ba415..fd068f8caca8ab 100644 --- a/zerver/openapi/zulip.yaml +++ b/zerver/openapi/zulip.yaml @@ -7898,6 +7898,15 @@ paths: schema: type: boolean example: true + - name: receive_private_message_on_invitee_signup + in: query + description: | + Whether or not to receive private message on invitee signup. + + **Changes**: New in Zulip 5.0 (feature level 115). + schema: + type: boolean + example: true responses: "200": description: Success @@ -9764,6 +9773,12 @@ paths: read messages. **Changes**: New in Zulip 5.0 (feature level 105). + receive_private_message_on_invitee_signup: + type: boolean + description: | + Whether or not to receive private message on invitee signup. + + **Changes**: New in Zulip 5.0 (feature level 115). has_zoom_token: type: boolean description: | @@ -11476,6 +11491,12 @@ paths: read messages. **Changes**: New in Zulip 5.0 (feature level 105). + receive_private_message_on_invitee_signup: + type: boolean + description: | + Whether or not to receive private message on invitee signup. + + **Changes**: New in Zulip 5.0 (feature level 115). realm_users: type: array description: | @@ -11669,6 +11690,10 @@ paths: Present if `realm_user` is present in `fetch_event_types`. The full name of the current user. + receive_private_message_on_invitee_signup: + type: boolean + description: | + Whether or not to receive private message on invitee signup. cross_realm_bots: type: array description: | @@ -12559,6 +12584,15 @@ paths: schema: type: boolean example: true + - name: receive_private_message_on_invitee_signup + in: query + description: | + Whether or not to receive private message on invitee signup. + + **Changes**: New in Zulip 5.0 (feature level 115). + schema: + type: boolean + example: true responses: "200": description: Success diff --git a/zerver/tests/test_signup.py b/zerver/tests/test_signup.py index 2887531af826dc..00d2a96b595860 100644 --- a/zerver/tests/test_signup.py +++ b/zerver/tests/test_signup.py @@ -35,6 +35,7 @@ do_change_full_name, do_change_realm_subdomain, do_change_user_role, + do_change_user_setting, do_create_default_stream_group, do_create_multiuse_invite_link, do_create_realm, @@ -1619,6 +1620,48 @@ def test_invite_user_signup_initial_history(self) -> None: self.assertTrue(invitee_msg.content.startswith("Hello, and welcome to Zulip!")) self.assertNotIn("demo organization", invitee_msg.content) + # when receive_private_message_on_invitee_signup is set to False + secret_msg_id = self.send_stream_message( + self.example_user("hamlet"), + private_stream_name, + topic_name="Secret topic", + content="Secret message", + ) + invitee = self.nonreg_email("bob") + self.assert_json_success(self.invite(invitee, [private_stream_name, "Denmark"])) + self.assertTrue(find_key_by_email(invitee)) + + prereg_user = PreregistrationUser.objects.get(email=invitee) + do_change_user_setting( + prereg_user.referred_by, + "receive_private_message_on_invitee_signup", + False, + acting_user=prereg_user.referred_by, + ) + self.submit_reg_form_for_user(invitee, "password") + invitee_profile = self.nonreg_user("bob") + invitee_msg_ids = [ + um.message_id for um in UserMessage.objects.filter(user_profile=invitee_profile) + ] + self.assertTrue(public_msg_id in invitee_msg_ids) + self.assertFalse(secret_msg_id in invitee_msg_ids) + self.assertFalse(invitee_profile.is_realm_admin) + + invitee_msg, signups_stream_msg, secret_msg = Message.objects.all().order_by("-id")[0:3] + + self.assertEqual(secret_msg.id, secret_msg_id) + + self.assertEqual(signups_stream_msg.sender.email, "notification-bot@zulip.com") + self.assertTrue( + signups_stream_msg.content.startswith( + f"@_**bob_zulip.com|{invitee_profile.id}** just signed up", + ) + ) + + self.assertEqual(invitee_msg.sender.email, "welcome-bot@zulip.com") + self.assertTrue(invitee_msg.content.startswith("Hello, and welcome to Zulip!")) + self.assertNotIn("demo organization", invitee_msg.content) + def test_multi_user_invite(self) -> None: """ Invites multiple users with a variety of delimiters. diff --git a/zerver/views/realm.py b/zerver/views/realm.py index 8dbff794b72d6a..62f7224d588ed2 100644 --- a/zerver/views/realm.py +++ b/zerver/views/realm.py @@ -402,6 +402,9 @@ def update_realm_user_settings_defaults( json_validator=check_bool, default=None ), send_read_receipts: Optional[bool] = REQ(json_validator=check_bool, default=None), + receive_private_message_on_invitee_signup: Optional[bool] = REQ( + json_validator=check_bool, default=None + ), ) -> HttpResponse: if notification_sound is not None or email_notifications_batching_period_seconds is not None: check_settings_values(notification_sound, email_notifications_batching_period_seconds) diff --git a/zerver/views/user_settings.py b/zerver/views/user_settings.py index db74f15583eafe..dfa12d8e12f57f 100644 --- a/zerver/views/user_settings.py +++ b/zerver/views/user_settings.py @@ -204,6 +204,9 @@ def json_change_settings( ), send_stream_typing_notifications: Optional[bool] = REQ(json_validator=check_bool, default=None), send_read_receipts: Optional[bool] = REQ(json_validator=check_bool, default=None), + receive_private_message_on_invitee_signup: Optional[bool] = REQ( + json_validator=check_bool, default=None + ), ) -> HttpResponse: if ( default_language is not None