diff --git a/frontend_tests/casper_tests/10-admin.js b/frontend_tests/casper_tests/10-admin.js index fbd97b29b3..44b2f0a4c6 100644 --- a/frontend_tests/casper_tests/10-admin.js +++ b/frontend_tests/casper_tests/10-admin.js @@ -28,9 +28,9 @@ casper.then(function () { casper.test.info('Changing notifications stream to Verona by filtering with "verona"'); casper.click("#id_realm_notifications_stream > button.dropdown-toggle"); - casper.waitUntilVisible('ul.dropdown-menu', function () { - casper.sendKeys('.dropdown-search > input[type=text]', 'verona'); - casper.click(".dropdown-list-body li.stream_name"); + casper.waitUntilVisible('#id_realm_notifications_stream ul.dropdown-menu', function () { + casper.sendKeys('#id_realm_notifications_stream .dropdown-search > input[type=text]', 'verona'); + casper.click("#id_realm_notifications_stream .dropdown-list-body li.stream_name"); }); casper.waitUntilVisible('#admin-realm-notifications-stream-status', function () { @@ -49,6 +49,32 @@ casper.then(function () { }); }); +// Test changing signup notifications stream +casper.then(function () { + casper.test.info('Changing signup notifications stream to Verona by filtering with "verona"'); + casper.click("#id_realm_signup_notifications_stream > button.dropdown-toggle"); + + casper.waitUntilVisible('#id_realm_signup_notifications_stream ul.dropdown-menu', function () { + casper.sendKeys('#id_realm_signup_notifications_stream .dropdown-search > input[type=text]', 'verona'); + casper.click("#id_realm_signup_notifications_stream .dropdown-list-body li.stream_name"); + }); + + casper.waitUntilVisible('#admin-realm-signup-notifications-stream-status', function () { + casper.test.assertSelectorHasText('#admin-realm-signup-notifications-stream-status', + 'Signup notifications stream changed!'); + casper.test.assertSelectorHasText('#realm_signup_notifications_stream_name', '#Verona'); + }); +}); + +casper.then(function () { + casper.click(".signup-notifications-stream-disable"); + casper.waitUntilVisible('#admin-realm-signup-notifications-stream-status', function () { + casper.test.assertSelectorHasText('#admin-realm-signup-notifications-stream-status', + 'Signup notifications stream disabled!'); + casper.test.assertSelectorHasText('#realm_signup_notifications_stream_name', 'Disabled'); + }); +}); + // Test permissions setting casper.then(function () { casper.click("li[data-section='organization-permissions']"); diff --git a/frontend_tests/node_tests/settings_org.js b/frontend_tests/node_tests/settings_org.js index 85078c27b5..62be2124ca 100644 --- a/frontend_tests/node_tests/settings_org.js +++ b/frontend_tests/node_tests/settings_org.js @@ -387,6 +387,31 @@ function test_disable_notifications_stream(disable_notifications_stream) { 'translated: Failed to change notifications stream!'); } +function test_disable_signup_notifications_stream(disable_signup_notifications_stream) { + var success_callback; + var error_callback; + channel.patch = function (req) { + assert.equal(req.url, '/json/realm'); + assert.equal(req.data.signup_notifications_stream_id, '-1'); + success_callback = req.success; + error_callback = req.error; + }; + + disable_signup_notifications_stream(); + + var response_data = { + signup_notifications_stream_id: -1, + }; + + success_callback(response_data); + assert.equal($('#admin-realm-signup-notifications-stream-status').val(), + 'translated: Signup notifications stream disabled!'); + + error_callback({}); + assert.equal($('#admin-realm-signup-notifications-stream-status').val(), + 'translated: Failed to change signup notifications stream!'); +} + function test_change_allow_subdomains(change_allow_subdomains) { var ev = { stopPropagation: noop, @@ -460,6 +485,7 @@ function test_change_allow_subdomains(change_allow_subdomains) { $('#id_realm_allow_message_editing').change = set_callback('change_message_editing'); $('#submit-add-realm-domain').click = set_callback('add_realm_domain'); $('.notifications-stream-disable').click = set_callback('disable_notifications_stream'); + $('.signup-notifications-stream-disable').click = set_callback('disable_signup_notifications_stream'); var submit_settings_form; var submit_permissions_form; @@ -505,6 +531,7 @@ function test_change_allow_subdomains(change_allow_subdomains) { test_change_invite_required(callbacks.change_invite_required); test_change_message_editing(callbacks.change_message_editing); test_disable_notifications_stream(callbacks.disable_notifications_stream); + test_disable_signup_notifications_stream(callbacks.disable_signup_notifications_stream); test_change_allow_subdomains(change_allow_subdomains); }()); @@ -562,4 +589,19 @@ function test_change_allow_subdomains(change_allow_subdomains) { settings_org.render_notifications_stream_ui(); assert.equal(elem.text(), 'translated: Disabled'); assert(elem.hasClass('text-warning')); + + elem = $('#realm_signup_notifications_stream_name'); + stream_data.get_sub_by_id = function (stream_id) { + assert.equal(stream_id, 75); + return { name: 'some_stream' }; + }; + settings_org.render_signup_notifications_stream_ui(75); + assert.equal(elem.text(), '#some_stream'); + assert(!elem.hasClass('text-warning')); + + stream_data.get_sub_by_id = noop; + settings_org.render_signup_notifications_stream_ui(); + assert.equal(elem.text(), 'translated: Disabled'); + assert(elem.hasClass('text-warning')); + }()); diff --git a/static/js/admin.js b/static/js/admin.js index c26abd5ed2..9bd97ef1a7 100644 --- a/static/js/admin.js +++ b/static/js/admin.js @@ -51,6 +51,7 @@ function _setup_page() { realm_default_language: page_params.realm_default_language, realm_waiting_period_threshold: page_params.realm_waiting_period_threshold, realm_notifications_stream_id: page_params.realm_notifications_stream_id, + realm_signup_notifications_stream_id: page_params.realm_signup_notifications_stream_id, is_admin: page_params.is_admin, realm_icon_source: page_params.realm_icon_source, realm_icon_url: page_params.realm_icon_url, diff --git a/static/js/server_events_dispatch.js b/static/js/server_events_dispatch.js index 2dbf0bfcad..a001b1f334 100644 --- a/static/js/server_events_dispatch.js +++ b/static/js/server_events_dispatch.js @@ -69,6 +69,7 @@ exports.dispatch_normal_event = function dispatch_normal_event(event) { name: notifications.redraw_title, name_changes_disabled: settings_org.toggle_name_change_display, notifications_stream_id: noop, + signup_notifications_stream_id: noop, restricted_to_domain: noop, waiting_period_threshold: noop, }; @@ -83,6 +84,9 @@ exports.dispatch_normal_event = function dispatch_normal_event(event) { } else if (event.property === 'notifications_stream_id') { settings_org.render_notifications_stream_ui( page_params.realm_notifications_stream_id); + } else if (event.property === 'signup_notifications_stream_id') { + settings_org.render_signup_notifications_stream_ui( + page_params.realm_signup_notifications_stream_id); } } else if (event.op === 'update_dict' && event.property === 'default') { _.each(event.data, function (value, key) { @@ -193,6 +197,11 @@ exports.dispatch_normal_event = function dispatch_normal_event(event) { settings_org.render_notifications_stream_ui( page_params.realm_notifications_stream_id); } + if (page_params.realm_signup_notifications_stream_id === stream.stream_id) { + page_params.realm_signup_notifications_stream_id = -1; + settings_org.render_signup_notifications_stream_ui( + page_params.realm_signup_notifications_stream_id); + } }); } break; diff --git a/static/js/settings_org.js b/static/js/settings_org.js index f600d6d0de..9cb5228cf1 100644 --- a/static/js/settings_org.js +++ b/static/js/settings_org.js @@ -139,7 +139,7 @@ exports.populate_notifications_stream_dropdown = function (stream_list) { var search_input = $("#id_realm_notifications_stream .dropdown-search > input[type=text]"); list_render(dropdown_list_body, stream_list, { - name: "admin-realm-dropdown-stream-list", + name: "admin-realm-notifications-stream-dropdown-list", modifier: function (item) { return templates.render("admin-realm-dropdown-stream-list", { stream: item }); }, @@ -165,6 +165,48 @@ exports.populate_notifications_stream_dropdown = function (stream_list) { }); }; +exports.render_signup_notifications_stream_ui = function (stream_id) { + var elem = $('#realm_signup_notifications_stream_name'); + + var name = stream_data.maybe_get_stream_name(stream_id); + + if (!name) { + elem.text(i18n.t("Disabled")); + elem.addClass("text-warning"); + return; + } + + // Happy path + elem.text('#' + name); + elem.removeClass('text-warning'); +}; + +exports.populate_signup_notifications_stream_dropdown = function (stream_list) { + var dropdown_list_body = $("#id_realm_signup_notifications_stream .dropdown-list-body").expectOne(); + var search_input = $("#id_realm_signup_notifications_stream .dropdown-search > input[type=text]"); + + list_render(dropdown_list_body, stream_list, { + name: "admin-realm-signup-notifications-stream-dropdown-list", + modifier: function (item) { + return templates.render("admin-realm-dropdown-stream-list", { stream: item }); + }, + filter: { + element: search_input, + callback: function (item, value) { + return item.name.toLowerCase().indexOf(value) >= 0; + }, + }, + }).init(); + + $("#id_realm_signup_notifications_stream .dropdown-search").click(function (e) { + e.stopPropagation(); + }); + + $("#id_realm_signup_notifications_stream .dropdown-toggle").click(function () { + search_input.val("").trigger("input"); + }); +}; + function property_type_status_element(element) { return $("#admin-realm-" + element.split('_').join('-') + "-status").expectOne(); } @@ -176,9 +218,12 @@ function _set_up() { // Populate notifications stream modal if (page_params.is_admin) { - exports.populate_notifications_stream_dropdown(stream_data.get_streams_for_settings_page()); + var streams = stream_data.get_streams_for_settings_page(); + exports.populate_notifications_stream_dropdown(streams); + exports.populate_signup_notifications_stream_dropdown(streams); } exports.render_notifications_stream_ui(page_params.realm_notifications_stream_id); + exports.render_signup_notifications_stream_ui(page_params.realm_signup_notifications_stream_id); // Populate realm domains exports.populate_realm_domains(page_params.realm_domains); @@ -691,6 +736,52 @@ function _set_up() { update_notifications_stream(-1); }); + var signup_notifications_stream_status = $("#admin-realm-signup-notifications-stream-status").expectOne(); + function update_signup_notifications_stream(new_signup_notifications_stream_id) { + exports.render_signup_notifications_stream_ui(new_signup_notifications_stream_id); + signup_notifications_stream_status.hide(); + var stringified_id = JSON.stringify(parseInt(new_signup_notifications_stream_id, 10)); + var url = "/json/realm"; + var data = { + signup_notifications_stream_id: stringified_id, + }; + + channel.patch({ + url: url, + data: data, + + success: function (response_data) { + if (response_data.signup_notifications_stream_id !== undefined) { + if (response_data.signup_notifications_stream_id < 0) { + ui_report.success(i18n.t("Signup notifications stream disabled!"), signup_notifications_stream_status); + } else { + ui_report.success(i18n.t("Signup notifications stream changed!"), signup_notifications_stream_status); + } + } + }, + error: function (xhr) { + ui_report.error(i18n.t("Failed to change signup notifications stream!"), xhr, signup_notifications_stream_status); + }, + }); + } + + dropdown_menu = $("#id_realm_signup_notifications_stream .dropdown-menu"); + $("#id_realm_signup_notifications_stream .dropdown-list-body").on("click keypress", ".stream_name", function (e) { + if (e.type === "keypress") { + if (e.which === 13) { + dropdown_menu.dropdown("toggle"); + } else { + return; + } + } + + update_signup_notifications_stream($(this).attr("data-stream-id")); + }); + + $(".signup-notifications-stream-disable").click(function () { + update_signup_notifications_stream(-1); + }); + function upload_realm_icon(file_input) { var form_data = new FormData(); diff --git a/static/styles/settings.css b/static/styles/settings.css index 34137b42df..7c0ad87301 100644 --- a/static/styles/settings.css +++ b/static/styles/settings.css @@ -519,7 +519,8 @@ input[type=checkbox].inline-block { text-align: left; } -#realm_notifications_stream_label > button { +#realm_notifications_stream_label > button, +#realm_signup_notifications_stream_label > button { margin: 0px 5px; } @@ -1125,11 +1126,13 @@ input[type=checkbox].inline-block { border-radius: 0px; } -#id_realm_notifications_stream .dropdown-search > input[type=text] { +#id_realm_notifications_stream .dropdown-search > input[type=text], +#id_realm_signup_notifications_stream .dropdown-search > input[type=text] { margin: 9px; } -#id_realm_notifications_stream .dropdown-list-body { +#id_realm_notifications_stream .dropdown-list-body, +#id_realm_signup_notifications_stream .dropdown-list-body { position: relative; height: auto; max-height: 200px; diff --git a/static/templates/settings/organization-settings-admin.handlebars b/static/templates/settings/organization-settings-admin.handlebars index c8b727e932..3f25895d67 100644 --- a/static/templates/settings/organization-settings-admin.handlebars +++ b/static/templates/settings/organization-settings-admin.handlebars @@ -1,6 +1,7 @@
+
@@ -150,7 +151,7 @@
+
+ + {{#if is_admin }} + + {{/if}} +
+
diff --git a/zerver/lib/actions.py b/zerver/lib/actions.py index 178b033c0d..7eb6972125 100644 --- a/zerver/lib/actions.py +++ b/zerver/lib/actions.py @@ -574,6 +574,18 @@ def do_set_realm_notifications_stream(realm, stream, stream_id): ) send_event(event, active_user_ids(realm.id)) +def do_set_realm_signup_notifications_stream(realm, stream, stream_id): + # type: (Realm, Stream, int) -> None + realm.signup_notifications_stream = stream + realm.save(update_fields=['signup_notifications_stream']) + event = dict( + type="realm", + op="update", + property="signup_notifications_stream_id", + value=stream_id + ) + send_event(event, active_user_ids(realm.id)) + def do_deactivate_realm(realm): # type: (Realm) -> None """ diff --git a/zerver/lib/events.py b/zerver/lib/events.py index 32461c5ae3..ead78b37c6 100644 --- a/zerver/lib/events.py +++ b/zerver/lib/events.py @@ -172,6 +172,12 @@ def fetch_initial_state_data(user_profile, event_types, queue_id, client_gravata else: state['realm_notifications_stream_id'] = -1 + if user_profile.realm.get_signup_notifications_stream(): + signup_notifications_stream = user_profile.realm.get_signup_notifications_stream() + state['realm_signup_notifications_stream_id'] = signup_notifications_stream.id + else: + state['realm_signup_notifications_stream_id'] = -1 + if want('realm_domains'): state['realm_domains'] = get_realm_domains(user_profile.realm) diff --git a/zerver/tests/test_events.py b/zerver/tests/test_events.py index b7213f29ed..ff295695de 100644 --- a/zerver/tests/test_events.py +++ b/zerver/tests/test_events.py @@ -68,6 +68,7 @@ from zerver.lib.actions import ( do_set_realm_property, do_set_user_display_setting, do_set_realm_notifications_stream, + do_set_realm_signup_notifications_stream, do_unmute_topic, do_update_embedded_data, do_update_message, @@ -1354,6 +1355,24 @@ class EventsRegisterTest(ZulipTestCase): error = schema_checker('events[0]', events[0]) self.assert_on_error(error) + def test_change_realm_signup_notifications_stream(self) -> None: + schema_checker = self.check_events_dict([ + ('type', equals('realm')), + ('op', equals('update')), + ('property', equals('signup_notifications_stream_id')), + ('value', check_int), + ]) + + stream = get_stream("Rome", self.user_profile.realm) + + for signup_notifications_stream, signup_notifications_stream_id in ((stream, stream.id), (None, -1)): + events = self.do_test( + lambda: do_set_realm_signup_notifications_stream(self.user_profile.realm, + signup_notifications_stream, + signup_notifications_stream_id)) + error = schema_checker('events[0]', events[0]) + self.assert_on_error(error) + def test_change_is_admin(self) -> None: schema_checker = self.check_events_dict([ ('type', equals('realm_user')), diff --git a/zerver/tests/test_home.py b/zerver/tests/test_home.py index 5f6adb7b9e..ab26e7e814 100644 --- a/zerver/tests/test_home.py +++ b/zerver/tests/test_home.py @@ -138,6 +138,7 @@ class HomeTest(ZulipTestCase): "realm_presence_disabled", "realm_restricted_to_domain", "realm_show_digest_email", + "realm_signup_notifications_stream_id", "realm_uri", "realm_user_groups", "realm_users", @@ -387,6 +388,17 @@ class HomeTest(ZulipTestCase): user.save() return user + def test_signup_notifications_stream(self): + # type: () -> None + email = self.example_email("hamlet") + realm = get_realm('zulip') + realm.signup_notifications_stream = get_stream('Denmark', realm) + realm.save() + self.login(email) + result = self._get_home_page() + page_params = self._get_page_params(result) + self.assertEqual(page_params['realm_signup_notifications_stream_id'], get_stream('Denmark', realm).id) + @slow('creating users and loading home page') def test_people(self) -> None: hamlet = self.example_user('hamlet') diff --git a/zerver/tests/test_realm.py b/zerver/tests/test_realm.py index 5a1936c55a..eb3006a050 100644 --- a/zerver/tests/test_realm.py +++ b/zerver/tests/test_realm.py @@ -201,6 +201,32 @@ class RealmTest(ZulipTestCase): do_deactivate_stream(notifications_stream) self.assertIsNone(realm.get_notifications_stream()) + def test_change_signup_notifications_stream(self) -> None: + # We need an admin user. + email = 'iago@zulip.com' + self.login(email) + + disabled_signup_notifications_stream_id = -1 + req = dict(signup_notifications_stream_id = ujson.dumps(disabled_signup_notifications_stream_id)) + result = self.client_patch('/json/realm', req) + self.assert_json_success(result) + realm = get_realm('zulip') + self.assertEqual(realm.signup_notifications_stream, None) + + new_signup_notifications_stream_id = 4 + req = dict(signup_notifications_stream_id = ujson.dumps(new_signup_notifications_stream_id)) + result = self.client_patch('/json/realm', req) + self.assert_json_success(result) + realm = get_realm('zulip') + self.assertEqual(realm.signup_notifications_stream.id, new_signup_notifications_stream_id) + + invalid_signup_notifications_stream_id = 1234 + req = dict(signup_notifications_stream_id = ujson.dumps(invalid_signup_notifications_stream_id)) + result = self.client_patch('/json/realm', req) + self.assert_json_error(result, 'Invalid stream id') + realm = get_realm('zulip') + self.assertNotEqual(realm.signup_notifications_stream.id, invalid_signup_notifications_stream_id) + def test_get_default_signup_notifications_stream(self) -> None: realm = get_realm("zulip") verona = get_stream("verona", realm) diff --git a/zerver/views/realm.py b/zerver/views/realm.py index 74875d3b2d..748f7942f8 100644 --- a/zerver/views/realm.py +++ b/zerver/views/realm.py @@ -8,6 +8,7 @@ from zerver.lib.actions import ( do_set_realm_message_editing, do_set_realm_authentication_methods, do_set_realm_notifications_stream, + do_set_realm_signup_notifications_stream, do_set_realm_property, ) from zerver.lib.i18n import get_available_language_codes @@ -40,8 +41,9 @@ def update_realm(request, user_profile, name=REQ(validator=check_string, default waiting_period_threshold=REQ(converter=to_non_negative_int, default=None), authentication_methods=REQ(validator=check_dict([]), default=None), notifications_stream_id=REQ(validator=check_int, default=None), + signup_notifications_stream_id=REQ(validator=check_int, default=None), message_retention_days=REQ(converter=to_not_negative_int_or_none, default=None)): - # type: (HttpRequest, UserProfile, Optional[str], Optional[str], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[int], Optional[bool], Optional[str], Optional[int], Optional[Dict[Any,Any]], Optional[int], Optional[int]) -> HttpResponse + # type: (HttpRequest, UserProfile, Optional[str], Optional[str], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[int], Optional[bool], Optional[str], Optional[int], Optional[Dict[Any,Any]], Optional[int], Optional[int], Optional[int]) -> HttpResponse realm = user_profile.realm # Additional validation/error checking beyond types go here, so @@ -92,8 +94,8 @@ def update_realm(request, user_profile, name=REQ(validator=check_string, default message_content_edit_limit_seconds) data['allow_message_editing'] = allow_message_editing data['message_content_edit_limit_seconds'] = message_content_edit_limit_seconds - # Realm.notifications_stream is not a boolean, Text or integer field, and thus doesn't fit - # into the do_set_realm_property framework. + # Realm.notifications_stream and Realm.signup_notifications_stream are not boolean, + # Text or integer field, and thus doesn't fit into the do_set_realm_property framework. if notifications_stream_id is not None: if realm.notifications_stream is None or (realm.notifications_stream.id != notifications_stream_id): @@ -105,4 +107,15 @@ def update_realm(request, user_profile, name=REQ(validator=check_string, default notifications_stream_id) data['notifications_stream_id'] = notifications_stream_id + if signup_notifications_stream_id is not None: + if realm.signup_notifications_stream is None or (realm.signup_notifications_stream.id != + signup_notifications_stream_id): + new_signup_notifications_stream = None + if signup_notifications_stream_id >= 0: + (new_signup_notifications_stream, recipient, sub) = access_stream_by_id( + user_profile, signup_notifications_stream_id) + do_set_realm_signup_notifications_stream(realm, new_signup_notifications_stream, + signup_notifications_stream_id) + data['signup_notifications_stream_id'] = signup_notifications_stream_id + return json_success(data)