diff --git a/frontend_tests/node_tests/stream_data.js b/frontend_tests/node_tests/stream_data.js index 3f584faeca..bd59fa9db5 100644 --- a/frontend_tests/node_tests/stream_data.js +++ b/frontend_tests/node_tests/stream_data.js @@ -182,9 +182,23 @@ run_test('subscribers', () => { full_name: 'George', user_id: 103, }; - people.add(fred); - people.add(not_fred); - people.add(george); + people.add_in_realm(fred); + people.add_in_realm(not_fred); + people.add_in_realm(george); + + function potential_subscriber_ids() { + const users = stream_data.potential_subscribers(sub); + return users.map((u) => u.user_id).sort(); + } + + assert.deepEqual( + potential_subscriber_ids(), + [ + fred.user_id, + not_fred.user_id, + george.user_id, + ] + ); stream_data.set_subscribers(sub, [fred.user_id, george.user_id]); stream_data.update_calculated_fields(sub); @@ -192,6 +206,13 @@ run_test('subscribers', () => { assert(stream_data.is_user_subscribed('Rome', george.user_id)); assert(!stream_data.is_user_subscribed('Rome', not_fred.user_id)); + assert.deepEqual( + potential_subscriber_ids(), + [ + not_fred.user_id, + ] + ); + stream_data.set_subscribers(sub, []); const brutus = { diff --git a/static/js/people.js b/static/js/people.js index c72a5dae99..20e3579cf9 100644 --- a/static/js/people.js +++ b/static/js/people.js @@ -675,6 +675,16 @@ exports.filter_all_persons = function (pred) { return ret; }; +exports.filter_all_users = function (pred) { + const ret = []; + for (const person of active_user_dict.values()) { + if (pred(person)) { + ret.push(person); + } + } + return ret; +}; + exports.get_realm_persons = function () { return Array.from(active_user_dict.values()); }; diff --git a/static/js/stream_data.js b/static/js/stream_data.js index 19315f1988..d0c6f51e46 100644 --- a/static/js/stream_data.js +++ b/static/js/stream_data.js @@ -371,6 +371,39 @@ exports.update_subscribers_count = function (sub) { sub.subscriber_count = count; }; +exports.potential_subscribers = function (sub) { + /* + This is a list of unsubscribed users + for the current stream, who the current + user could potentially subscribe to the + stream. This may include some bots. + + We currently use it for typeahead in + stream_edit.js. + + This may be a superset of the actual + subscribers that you can change in some cases + (like if you're a guest?); we should refine this + going forward, especially if we use it for something + other than typeahead. (The guest use case + may be moot now for other reasons.) + */ + + function is_potential_subscriber(person) { + // Use verbose style to force better test + // coverage, plus we may add more conditions over + // time. + if (sub.subscribers.has(person.user_id)) { + return false; + } + + return true; + } + + return people.filter_all_users(is_potential_subscriber); + +}; + exports.update_stream_email_address = function (sub, email) { sub.email_address = email; }; diff --git a/static/js/stream_edit.js b/static/js/stream_edit.js index 59cc82e613..2a37265828 100644 --- a/static/js/stream_edit.js +++ b/static/js/stream_edit.js @@ -216,7 +216,7 @@ function show_subscription_settings(sub_row) { }).init(); sub_settings.find('input[name="principal"]').typeahead({ - source: people.get_realm_persons, // This is a function. + source: () => stream_data.potential_subscribers(sub), items: 5, highlighter: function (item) { return typeahead_helper.render_person(item); @@ -227,10 +227,8 @@ function show_subscription_settings(sub_row) { return false; } // Case-insensitive. - const item_matches = item.email.toLowerCase().includes(query) || - item.full_name.toLowerCase().includes(query); - const is_subscribed = stream_data.is_user_subscribed(sub.name, item.user_id); - return item_matches && !is_subscribed; + return item.email.toLowerCase().includes(query) || + item.full_name.toLowerCase().includes(query); }, sorter: function (matches) { const current_stream = compose_state.stream_name();