mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	refactor: Remove subscribers from stream_data subs.
This sets us up to use better system-wide data structures for tracking subscribers. Basically, instead of storing subscriber data on the "sub" objects in stream_data.js, we instead have a parallel data structure called stream_subscribers. We also have stream_create, stream_edit, and friends use helper functions rather than accessing sub.subscribers directly.
This commit is contained in:
		@@ -22,8 +22,6 @@ set_global("compose_actions", {
 | 
			
		||||
    update_placeholder_text: noop,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {LazySet} = zrequire("lazy_set");
 | 
			
		||||
 | 
			
		||||
const _navigator = {
 | 
			
		||||
    platform: "",
 | 
			
		||||
};
 | 
			
		||||
@@ -961,16 +959,19 @@ run_test("finish", () => {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
run_test("warn_if_private_stream_is_linked", () => {
 | 
			
		||||
    stream_data.add_sub({
 | 
			
		||||
    const test_sub = {
 | 
			
		||||
        name: compose_state.stream_name(),
 | 
			
		||||
        subscribers: new LazySet([1, 2]),
 | 
			
		||||
        stream_id: 99,
 | 
			
		||||
    });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    stream_data.add_sub(test_sub);
 | 
			
		||||
    stream_data.set_subscribers(test_sub, [1, 2]);
 | 
			
		||||
 | 
			
		||||
    let denmark = {
 | 
			
		||||
        stream_id: 100,
 | 
			
		||||
        name: "Denmark",
 | 
			
		||||
        subscribers: new LazySet([1, 2, 3]),
 | 
			
		||||
    };
 | 
			
		||||
    stream_data.set_subscribers(denmark, [1, 2, 3]);
 | 
			
		||||
 | 
			
		||||
    function test_noop_case(invite_only) {
 | 
			
		||||
        compose_state.set_message_type("stream");
 | 
			
		||||
@@ -1016,7 +1017,6 @@ run_test("warn_if_private_stream_is_linked", () => {
 | 
			
		||||
    denmark = {
 | 
			
		||||
        invite_only: true,
 | 
			
		||||
        name: "Denmark",
 | 
			
		||||
        subscribers: new LazySet([1]),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    compose.warn_if_private_stream_is_linked(denmark);
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@ const {strict: assert} = require("assert");
 | 
			
		||||
 | 
			
		||||
const _ = require("lodash");
 | 
			
		||||
 | 
			
		||||
const {set_global, stub_out_jquery, zrequire} = require("../zjsunit/namespace");
 | 
			
		||||
const {set_global, stub_out_jquery, with_field, zrequire} = require("../zjsunit/namespace");
 | 
			
		||||
const {run_test} = require("../zjsunit/test");
 | 
			
		||||
 | 
			
		||||
set_global("page_params", {
 | 
			
		||||
@@ -179,7 +179,7 @@ run_test("unsubscribe", () => {
 | 
			
		||||
 | 
			
		||||
run_test("subscribers", () => {
 | 
			
		||||
    stream_data.clear_subscriptions();
 | 
			
		||||
    let sub = {name: "Rome", subscribed: true, stream_id: 1};
 | 
			
		||||
    let sub = {name: "Rome", subscribed: true, stream_id: 1001};
 | 
			
		||||
 | 
			
		||||
    stream_data.add_sub(sub);
 | 
			
		||||
 | 
			
		||||
@@ -203,7 +203,7 @@ run_test("subscribers", () => {
 | 
			
		||||
    people.add_active_user(george);
 | 
			
		||||
 | 
			
		||||
    function potential_subscriber_ids() {
 | 
			
		||||
        const users = stream_data.potential_subscribers(sub);
 | 
			
		||||
        const users = stream_data.potential_subscribers(sub.stream_id);
 | 
			
		||||
        return users.map((u) => u.user_id).sort();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -268,7 +268,7 @@ run_test("subscribers", () => {
 | 
			
		||||
    const bad_stream_id = 999999;
 | 
			
		||||
    blueslip.expect(
 | 
			
		||||
        "warn",
 | 
			
		||||
        "We got a remove_subscriber call for a non-existent stream " + bad_stream_id,
 | 
			
		||||
        "We got a remove_subscriber call for an untracked stream " + bad_stream_id,
 | 
			
		||||
    );
 | 
			
		||||
    ok = stream_data.remove_subscriber(bad_stream_id, brutus.user_id);
 | 
			
		||||
    assert(!ok);
 | 
			
		||||
@@ -313,7 +313,7 @@ run_test("subscribers", () => {
 | 
			
		||||
    assert.equal(stream_data.is_user_subscribed(sub.stream_id, brutus.user_id), undefined);
 | 
			
		||||
 | 
			
		||||
    // Verify that we don't crash and return false for a bad stream.
 | 
			
		||||
    blueslip.expect("warn", "We got an add_subscriber call for a non-existent stream.");
 | 
			
		||||
    blueslip.expect("warn", "We got an add_subscriber call for an untracked stream: 9999999");
 | 
			
		||||
    ok = stream_data.add_subscriber(9999999, brutus.user_id);
 | 
			
		||||
    assert(!ok);
 | 
			
		||||
 | 
			
		||||
@@ -607,7 +607,7 @@ run_test("get_subscriber_count", () => {
 | 
			
		||||
    };
 | 
			
		||||
    stream_data.clear_subscriptions();
 | 
			
		||||
 | 
			
		||||
    blueslip.expect("warn", "We got a get_subscriber_count count call for a non-existent stream.");
 | 
			
		||||
    blueslip.expect("warn", "We got a get_subscriber_count call for an untracked stream: 102");
 | 
			
		||||
    assert.equal(stream_data.get_subscriber_count(india.stream_id), undefined);
 | 
			
		||||
 | 
			
		||||
    stream_data.add_sub(india);
 | 
			
		||||
@@ -630,9 +630,8 @@ run_test("get_subscriber_count", () => {
 | 
			
		||||
    stream_data.add_subscriber(india.stream_id, 103);
 | 
			
		||||
    assert.equal(stream_data.get_subscriber_count(india.stream_id), 2);
 | 
			
		||||
 | 
			
		||||
    const sub = stream_data.get_sub_by_name("India");
 | 
			
		||||
    delete sub.subscribers;
 | 
			
		||||
    assert.deepStrictEqual(stream_data.get_subscriber_count(india.stream_id), 0);
 | 
			
		||||
    stream_data.remove_subscriber(india.stream_id, 103);
 | 
			
		||||
    assert.deepStrictEqual(stream_data.get_subscriber_count(india.stream_id), 1);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
run_test("notifications", () => {
 | 
			
		||||
@@ -976,15 +975,15 @@ run_test("filter inactives", () => {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
run_test("is_subscriber_subset", () => {
 | 
			
		||||
    function make_sub(user_ids) {
 | 
			
		||||
        const sub = {};
 | 
			
		||||
    function make_sub(stream_id, user_ids) {
 | 
			
		||||
        const sub = {stream_id};
 | 
			
		||||
        stream_data.set_subscribers(sub, user_ids);
 | 
			
		||||
        return sub;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const sub_a = make_sub([1, 2]);
 | 
			
		||||
    const sub_b = make_sub([2, 3]);
 | 
			
		||||
    const sub_c = make_sub([1, 2, 3]);
 | 
			
		||||
    const sub_a = make_sub(301, [1, 2]);
 | 
			
		||||
    const sub_b = make_sub(302, [2, 3]);
 | 
			
		||||
    const sub_c = make_sub(303, [1, 2, 3]);
 | 
			
		||||
 | 
			
		||||
    // The bogus case should not come up in normal
 | 
			
		||||
    // use.
 | 
			
		||||
@@ -1088,3 +1087,26 @@ run_test("all_topics_in_cache", () => {
 | 
			
		||||
    sub.first_message_id = 2;
 | 
			
		||||
    assert.equal(stream_data.all_topics_in_cache(sub), true);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
run_test("warn if subscribers are missing", () => {
 | 
			
		||||
    // This should only happen in this contrived test situation.
 | 
			
		||||
    stream_data.clear_subscriptions();
 | 
			
		||||
    const sub = {
 | 
			
		||||
        name: "test",
 | 
			
		||||
        stream_id: 3,
 | 
			
		||||
        can_access_subscribers: true,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    with_field(
 | 
			
		||||
        stream_data,
 | 
			
		||||
        "get_sub_by_id",
 | 
			
		||||
        () => sub,
 | 
			
		||||
        () => {
 | 
			
		||||
            blueslip.expect("warn", "We called is_user_subscribed for an untracked stream: 3");
 | 
			
		||||
            stream_data.is_user_subscribed(sub.stream_id, me.user_id);
 | 
			
		||||
 | 
			
		||||
            blueslip.expect("warn", "We called get_subscribers for an untracked stream: 3");
 | 
			
		||||
            assert.deepEqual(stream_data.get_subscribers(sub.stream_id), []);
 | 
			
		||||
        },
 | 
			
		||||
    );
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -7,8 +7,6 @@ const {set_global, zrequire} = require("../zjsunit/namespace");
 | 
			
		||||
const {run_test} = require("../zjsunit/test");
 | 
			
		||||
const {make_zjquery} = require("../zjsunit/zjquery");
 | 
			
		||||
 | 
			
		||||
const {LazySet} = zrequire("lazy_set");
 | 
			
		||||
 | 
			
		||||
const noop = () => {};
 | 
			
		||||
stub_templates(() => noop);
 | 
			
		||||
 | 
			
		||||
@@ -79,16 +77,17 @@ const denmark = {
 | 
			
		||||
    stream_id: 1,
 | 
			
		||||
    name: "Denmark",
 | 
			
		||||
    subscribed: true,
 | 
			
		||||
    subscribers: new LazySet([me.user_id, mark.user_id]),
 | 
			
		||||
    render_subscribers: true,
 | 
			
		||||
    should_display_subscription_button: true,
 | 
			
		||||
};
 | 
			
		||||
stream_data.set_subscribers(denmark, [me.user_id, mark.user_id]);
 | 
			
		||||
 | 
			
		||||
const sweden = {
 | 
			
		||||
    stream_id: 2,
 | 
			
		||||
    name: "Sweden",
 | 
			
		||||
    subscribed: false,
 | 
			
		||||
    subscribers: new LazySet([mark.user_id, jill.user_id]),
 | 
			
		||||
};
 | 
			
		||||
stream_data.set_subscribers(sweden, [mark.user_id, jill.user_id]);
 | 
			
		||||
 | 
			
		||||
const subs = [denmark, sweden];
 | 
			
		||||
for (const sub of subs) {
 | 
			
		||||
@@ -134,7 +133,7 @@ run_test("subscriber_pills", () => {
 | 
			
		||||
    let add_subscribers_request = false;
 | 
			
		||||
    stream_edit.invite_user_to_stream = (user_ids, sub) => {
 | 
			
		||||
        assert.equal(sub.stream_id, denmark.stream_id);
 | 
			
		||||
        assert.deepEqual(user_ids.sort(), expected_user_ids);
 | 
			
		||||
        assert.deepEqual(user_ids.sort(), expected_user_ids.sort());
 | 
			
		||||
        add_subscribers_request = true;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
@@ -197,8 +196,8 @@ run_test("subscriber_pills", () => {
 | 
			
		||||
        (function test_source() {
 | 
			
		||||
            const result = config.source.call(fake_this);
 | 
			
		||||
            const taken_ids = stream_pill.get_stream_ids(stream_edit.pill_widget);
 | 
			
		||||
            const stream_ids = result.map((stream) => stream.stream_id).sort();
 | 
			
		||||
            let expected_ids = subs.map((stream) => stream.stream_id).sort();
 | 
			
		||||
            const stream_ids = Array.from(result, (stream) => stream.stream_id).sort();
 | 
			
		||||
            let expected_ids = Array.from(subs, (stream) => stream.stream_id).sort();
 | 
			
		||||
            expected_ids = expected_ids.filter((id) => !taken_ids.includes(id));
 | 
			
		||||
            assert.deepEqual(stream_ids, expected_ids);
 | 
			
		||||
        })();
 | 
			
		||||
@@ -232,9 +231,9 @@ run_test("subscriber_pills", () => {
 | 
			
		||||
 | 
			
		||||
    // We cannot subscribe ourselves (`me`) as
 | 
			
		||||
    // we are already subscribed to denmark stream.
 | 
			
		||||
    const potential_denmark_stream_subscribers = denmark.subscribers
 | 
			
		||||
        .map()
 | 
			
		||||
        .filter((id) => id !== me.user_id);
 | 
			
		||||
    const potential_denmark_stream_subscribers = Array.from(
 | 
			
		||||
        stream_data.get_subscribers(denmark.stream_id),
 | 
			
		||||
    ).filter((id) => id !== me.user_id);
 | 
			
		||||
 | 
			
		||||
    // denmark.stream_id is stubbed. Thus request is
 | 
			
		||||
    // sent to add all subscribers of stream Denmark.
 | 
			
		||||
@@ -269,7 +268,7 @@ run_test("subscriber_pills", () => {
 | 
			
		||||
    // But only one request for mark is sent even though a mark user
 | 
			
		||||
    // pill is created and mark is also a subscriber of Denmark stream.
 | 
			
		||||
    user_pill.get_user_ids = () => [mark.user_id, fred.user_id];
 | 
			
		||||
    stream_pill.get_user_ids = () => denmark.subscribers.map();
 | 
			
		||||
    stream_pill.get_user_ids = () => stream_data.get_subscribers(denmark.stream_id);
 | 
			
		||||
    expected_user_ids = potential_denmark_stream_subscribers.concat(fred.user_id);
 | 
			
		||||
    add_subscribers_handler(event);
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -421,9 +421,12 @@ run_test("remove_deactivated_user_from_all_streams", () => {
 | 
			
		||||
 | 
			
		||||
    dev_help.can_access_subscribers = true;
 | 
			
		||||
 | 
			
		||||
    // assert starting state
 | 
			
		||||
    assert(!stream_data.is_user_subscribed(dev_help.stream_id, george.user_id));
 | 
			
		||||
 | 
			
		||||
    // verify that deactivating user should unsubscribe user from all streams
 | 
			
		||||
    assert(stream_data.add_subscriber(dev_help.stream_id, george.user_id));
 | 
			
		||||
    assert(dev_help.subscribers.has(george.user_id));
 | 
			
		||||
    assert(stream_data.is_user_subscribed(dev_help.stream_id, george.user_id));
 | 
			
		||||
 | 
			
		||||
    stream_events.remove_deactivated_user_from_all_streams(george.user_id);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -47,8 +47,9 @@ run_test("filter_table", () => {
 | 
			
		||||
            name: "Denmark",
 | 
			
		||||
            stream_id: 1,
 | 
			
		||||
            description: "Copenhagen",
 | 
			
		||||
            subscribers: {size: 1},
 | 
			
		||||
            subscribers: [1],
 | 
			
		||||
            stream_weekly_traffic: null,
 | 
			
		||||
            color: "red",
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            elem: "poland",
 | 
			
		||||
@@ -56,8 +57,9 @@ run_test("filter_table", () => {
 | 
			
		||||
            name: "Poland",
 | 
			
		||||
            stream_id: 2,
 | 
			
		||||
            description: "monday",
 | 
			
		||||
            subscribers: {size: 3},
 | 
			
		||||
            subscribers: [1, 2, 3],
 | 
			
		||||
            stream_weekly_traffic: 13,
 | 
			
		||||
            color: "red",
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            elem: "pomona",
 | 
			
		||||
@@ -65,8 +67,9 @@ run_test("filter_table", () => {
 | 
			
		||||
            name: "Pomona",
 | 
			
		||||
            stream_id: 3,
 | 
			
		||||
            description: "college",
 | 
			
		||||
            subscribers: {size: 0},
 | 
			
		||||
            subscribers: [],
 | 
			
		||||
            stream_weekly_traffic: 0,
 | 
			
		||||
            color: "red",
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            elem: "cpp",
 | 
			
		||||
@@ -74,8 +77,9 @@ run_test("filter_table", () => {
 | 
			
		||||
            name: "C++",
 | 
			
		||||
            stream_id: 4,
 | 
			
		||||
            description: "programming lang",
 | 
			
		||||
            subscribers: {size: 2},
 | 
			
		||||
            subscribers: [1, 2],
 | 
			
		||||
            stream_weekly_traffic: 6,
 | 
			
		||||
            color: "red",
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            elem: "zzyzx",
 | 
			
		||||
@@ -83,13 +87,14 @@ run_test("filter_table", () => {
 | 
			
		||||
            name: "Zzyzx",
 | 
			
		||||
            stream_id: 5,
 | 
			
		||||
            description: "california town",
 | 
			
		||||
            subscribers: {size: 2},
 | 
			
		||||
            subscribers: [1, 2],
 | 
			
		||||
            stream_weekly_traffic: 6,
 | 
			
		||||
            color: "red",
 | 
			
		||||
        },
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    for (const sub of sub_row_data) {
 | 
			
		||||
        stream_data.add_sub(sub);
 | 
			
		||||
        stream_data.create_sub_from_server_data(sub);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let populated_subs;
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,6 @@ const pygments_data = zrequire("pygments_data", "generated/pygments_data.json");
 | 
			
		||||
const actual_pygments_data = {...pygments_data};
 | 
			
		||||
const ct = zrequire("composebox_typeahead");
 | 
			
		||||
const th = zrequire("typeahead_helper");
 | 
			
		||||
const {LazySet} = zrequire("lazy_set");
 | 
			
		||||
 | 
			
		||||
let next_id = 0;
 | 
			
		||||
 | 
			
		||||
@@ -42,18 +41,27 @@ stream_data.create_streams([
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
run_test("sort_streams", () => {
 | 
			
		||||
    const popular = new LazySet([1, 2, 3, 4, 5, 6]);
 | 
			
		||||
    const popular = [1, 2, 3, 4, 5, 6];
 | 
			
		||||
 | 
			
		||||
    const unpopular = new LazySet([1]);
 | 
			
		||||
    const unpopular = [1];
 | 
			
		||||
 | 
			
		||||
    let test_streams = [
 | 
			
		||||
        {name: "Dev", pin_to_top: false, subscribers: unpopular, subscribed: true},
 | 
			
		||||
        {name: "Docs", pin_to_top: false, subscribers: popular, subscribed: true},
 | 
			
		||||
        {name: "Derp", pin_to_top: false, subscribers: unpopular, subscribed: true},
 | 
			
		||||
        {name: "Denmark", pin_to_top: true, subscribers: popular, subscribed: true},
 | 
			
		||||
        {name: "dead", pin_to_top: false, subscribers: unpopular, subscribed: true},
 | 
			
		||||
        {stream_id: 101, name: "Dev", pin_to_top: false, subscribers: unpopular, subscribed: true},
 | 
			
		||||
        {stream_id: 102, name: "Docs", pin_to_top: false, subscribers: popular, subscribed: true},
 | 
			
		||||
        {stream_id: 103, name: "Derp", pin_to_top: false, subscribers: unpopular, subscribed: true},
 | 
			
		||||
        {stream_id: 104, name: "Denmark", pin_to_top: true, subscribers: popular, subscribed: true},
 | 
			
		||||
        {stream_id: 105, name: "dead", pin_to_top: false, subscribers: unpopular, subscribed: true},
 | 
			
		||||
    ];
 | 
			
		||||
    test_streams.forEach(stream_data.update_calculated_fields);
 | 
			
		||||
 | 
			
		||||
    function process_test_streams() {
 | 
			
		||||
        for (const test_stream of test_streams) {
 | 
			
		||||
            stream_data.set_subscribers(test_stream, test_stream.subscribers);
 | 
			
		||||
            delete test_stream.subscribers;
 | 
			
		||||
            stream_data.update_calculated_fields(test_stream);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    process_test_streams();
 | 
			
		||||
 | 
			
		||||
    stream_data.is_active = function (sub) {
 | 
			
		||||
        return sub.name !== "dead";
 | 
			
		||||
@@ -68,12 +76,44 @@ run_test("sort_streams", () => {
 | 
			
		||||
 | 
			
		||||
    // Test sort streams with description
 | 
			
		||||
    test_streams = [
 | 
			
		||||
        {name: "Dev", description: "development help", subscribers: unpopular, subscribed: true},
 | 
			
		||||
        {name: "Docs", description: "writing docs", subscribers: popular, subscribed: true},
 | 
			
		||||
        {name: "Derp", description: "derping around", subscribers: unpopular, subscribed: true},
 | 
			
		||||
        {name: "Denmark", description: "visiting Denmark", subscribers: popular, subscribed: true},
 | 
			
		||||
        {name: "dead", description: "dead stream", subscribers: unpopular, subscribed: true},
 | 
			
		||||
        {
 | 
			
		||||
            stream_id: 201,
 | 
			
		||||
            name: "Dev",
 | 
			
		||||
            description: "development help",
 | 
			
		||||
            subscribers: unpopular,
 | 
			
		||||
            subscribed: true,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            stream_id: 202,
 | 
			
		||||
            name: "Docs",
 | 
			
		||||
            description: "writing docs",
 | 
			
		||||
            subscribers: popular,
 | 
			
		||||
            subscribed: true,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            stream_id: 203,
 | 
			
		||||
            name: "Derp",
 | 
			
		||||
            description: "derping around",
 | 
			
		||||
            subscribers: unpopular,
 | 
			
		||||
            subscribed: true,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            stream_id: 204,
 | 
			
		||||
            name: "Denmark",
 | 
			
		||||
            description: "visiting Denmark",
 | 
			
		||||
            subscribers: popular,
 | 
			
		||||
            subscribed: true,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            stream_id: 205,
 | 
			
		||||
            name: "dead",
 | 
			
		||||
            description: "dead stream",
 | 
			
		||||
            subscribers: unpopular,
 | 
			
		||||
            subscribed: true,
 | 
			
		||||
        },
 | 
			
		||||
    ];
 | 
			
		||||
    process_test_streams();
 | 
			
		||||
 | 
			
		||||
    test_streams.forEach(stream_data.update_calculated_fields);
 | 
			
		||||
    test_streams = th.sort_streams(test_streams, "wr");
 | 
			
		||||
    assert.deepEqual(test_streams[0].name, "Docs"); // Description match
 | 
			
		||||
@@ -84,14 +124,50 @@ run_test("sort_streams", () => {
 | 
			
		||||
 | 
			
		||||
    // Test sort both subscribed and unsubscribed streams.
 | 
			
		||||
    test_streams = [
 | 
			
		||||
        {name: "Dev", description: "Some devs", subscribed: true, subscribers: popular},
 | 
			
		||||
        {name: "East", description: "Developing east", subscribed: true, subscribers: popular},
 | 
			
		||||
        {name: "New", description: "No match", subscribed: true, subscribers: popular},
 | 
			
		||||
        {name: "Derp", description: "Always Derping", subscribed: false, subscribers: popular},
 | 
			
		||||
        {name: "Ether", description: "Destroying ether", subscribed: false, subscribers: popular},
 | 
			
		||||
        {name: "Mew", description: "Cat mews", subscribed: false, subscribers: popular},
 | 
			
		||||
        {
 | 
			
		||||
            stream_id: 301,
 | 
			
		||||
            name: "Dev",
 | 
			
		||||
            description: "Some devs",
 | 
			
		||||
            subscribed: true,
 | 
			
		||||
            subscribers: popular,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            stream_id: 302,
 | 
			
		||||
            name: "East",
 | 
			
		||||
            description: "Developing east",
 | 
			
		||||
            subscribed: true,
 | 
			
		||||
            subscribers: popular,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            stream_id: 303,
 | 
			
		||||
            name: "New",
 | 
			
		||||
            description: "No match",
 | 
			
		||||
            subscribed: true,
 | 
			
		||||
            subscribers: popular,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            stream_id: 304,
 | 
			
		||||
            name: "Derp",
 | 
			
		||||
            description: "Always Derping",
 | 
			
		||||
            subscribed: false,
 | 
			
		||||
            subscribers: popular,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            stream_id: 305,
 | 
			
		||||
            name: "Ether",
 | 
			
		||||
            description: "Destroying ether",
 | 
			
		||||
            subscribed: false,
 | 
			
		||||
            subscribers: popular,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            stream_id: 306,
 | 
			
		||||
            name: "Mew",
 | 
			
		||||
            description: "Cat mews",
 | 
			
		||||
            subscribed: false,
 | 
			
		||||
            subscribers: popular,
 | 
			
		||||
        },
 | 
			
		||||
    ];
 | 
			
		||||
    test_streams.forEach(stream_data.update_calculated_fields);
 | 
			
		||||
    process_test_streams();
 | 
			
		||||
 | 
			
		||||
    test_streams = th.sort_streams(test_streams, "d");
 | 
			
		||||
    assert.deepEqual(test_streams[0].name, "Dev"); // Subscribed and stream name starts with query
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,9 @@ async function test_mention(page) {
 | 
			
		||||
    await common.ensure_enter_does_not_send(page);
 | 
			
		||||
 | 
			
		||||
    console.log("Checking for all everyone warning");
 | 
			
		||||
    const stream_size = await page.evaluate(() => stream_data.get_sub("Verona").subscribers.size);
 | 
			
		||||
    const stream_size = await page.evaluate(() =>
 | 
			
		||||
        stream_data.get_subscriber_count(stream_data.get_sub("Verona").stream_id),
 | 
			
		||||
    );
 | 
			
		||||
    const threshold = await page.evaluate(() => {
 | 
			
		||||
        compose.wildcard_mention_large_stream_threshold = 5;
 | 
			
		||||
        return compose.wildcard_mention_large_stream_threshold;
 | 
			
		||||
 
 | 
			
		||||
@@ -289,7 +289,7 @@ exports.show_new_stream_modal = function () {
 | 
			
		||||
        const elem = $(this);
 | 
			
		||||
        const stream_id = Number.parseInt(elem.attr("data-stream-id"), 10);
 | 
			
		||||
        const checked = elem.find("input").prop("checked");
 | 
			
		||||
        const subscriber_ids = stream_data.get_sub_by_id(stream_id).subscribers;
 | 
			
		||||
        const subscriber_ids = new Set(stream_data.get_subscribers(stream_id));
 | 
			
		||||
 | 
			
		||||
        $("#user-checkboxes label.checkbox").each(function () {
 | 
			
		||||
            const user_elem = $(this);
 | 
			
		||||
 
 | 
			
		||||
@@ -93,6 +93,12 @@ let filter_out_inactives = false;
 | 
			
		||||
const stream_ids_by_name = new FoldDict();
 | 
			
		||||
const default_stream_ids = new Set();
 | 
			
		||||
 | 
			
		||||
// This maps a stream_id to a LazySet of user_ids who are subscribed.
 | 
			
		||||
// We maintain the invariant that this has keys for all all stream_ids
 | 
			
		||||
// that we track in the other data structures.  We intialize it during
 | 
			
		||||
// clear_subscriptions.
 | 
			
		||||
let stream_subscribers;
 | 
			
		||||
 | 
			
		||||
exports.stream_privacy_policy_values = {
 | 
			
		||||
    public: {
 | 
			
		||||
        code: "public",
 | 
			
		||||
@@ -133,8 +139,11 @@ exports.stream_post_policy_values = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
exports.clear_subscriptions = function () {
 | 
			
		||||
    // This function is only used once at page load, and then
 | 
			
		||||
    // it should only be used in tests.
 | 
			
		||||
    stream_info = new BinaryDict((sub) => sub.subscribed);
 | 
			
		||||
    subs_by_stream_id = new Map();
 | 
			
		||||
    stream_subscribers = new Map();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
exports.clear_subscriptions();
 | 
			
		||||
@@ -193,10 +202,14 @@ exports.subscribe_myself = function (sub) {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
exports.is_subscriber_subset = function (sub1, sub2) {
 | 
			
		||||
    if (sub1.subscribers && sub2.subscribers) {
 | 
			
		||||
        const sub2_set = sub2.subscribers;
 | 
			
		||||
    const stream_id1 = sub1.stream_id;
 | 
			
		||||
    const stream_id2 = sub2.stream_id;
 | 
			
		||||
 | 
			
		||||
        return Array.from(sub1.subscribers.keys()).every((key) => sub2_set.has(key));
 | 
			
		||||
    const sub1_set = stream_subscribers.get(stream_id1);
 | 
			
		||||
    const sub2_set = stream_subscribers.get(stream_id2);
 | 
			
		||||
 | 
			
		||||
    if (sub1_set && sub2_set) {
 | 
			
		||||
        return Array.from(sub1_set.keys()).every((key) => sub2_set.has(key));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return false;
 | 
			
		||||
@@ -216,8 +229,8 @@ exports.add_sub = function (sub) {
 | 
			
		||||
    // We use create_sub_from_server_data at page load.
 | 
			
		||||
    // We use create_streams for new streams in live-update events.
 | 
			
		||||
 | 
			
		||||
    if (!Object.prototype.hasOwnProperty.call(sub, "subscribers")) {
 | 
			
		||||
        sub.subscribers = new LazySet([]);
 | 
			
		||||
    if (!stream_subscribers.has(sub.stream_id)) {
 | 
			
		||||
        exports.set_subscribers(sub, []);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    stream_info.set(sub.name, sub);
 | 
			
		||||
@@ -431,11 +444,13 @@ exports.get_colors = function () {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
exports.update_subscribers_count = function (sub) {
 | 
			
		||||
    const count = sub.subscribers.size;
 | 
			
		||||
    sub.subscriber_count = count;
 | 
			
		||||
    // This is part of an unfortunate legacy hack, where we
 | 
			
		||||
    // put calculated fields onto the sub object instead of
 | 
			
		||||
    // letting callers build their own objects.
 | 
			
		||||
    sub.subscriber_count = exports.get_subscriber_count(sub.stream_id);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
exports.potential_subscribers = function (sub) {
 | 
			
		||||
exports.potential_subscribers = function (stream_id) {
 | 
			
		||||
    /*
 | 
			
		||||
        This is a list of unsubscribed users
 | 
			
		||||
        for the current stream, who the current
 | 
			
		||||
@@ -453,11 +468,13 @@ exports.potential_subscribers = function (sub) {
 | 
			
		||||
        may be moot now for other reasons.)
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    const subscribers = stream_subscribers.get(stream_id);
 | 
			
		||||
 | 
			
		||||
    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)) {
 | 
			
		||||
        if (subscribers.has(person.user_id)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -472,15 +489,14 @@ exports.update_stream_email_address = function (sub, email) {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
exports.get_subscriber_count = function (stream_id) {
 | 
			
		||||
    const sub = exports.get_sub_by_id(stream_id);
 | 
			
		||||
    if (sub === undefined) {
 | 
			
		||||
        blueslip.warn("We got a get_subscriber_count count call for a non-existent stream.");
 | 
			
		||||
    const subscribers = stream_subscribers.get(stream_id);
 | 
			
		||||
 | 
			
		||||
    if (!subscribers) {
 | 
			
		||||
        blueslip.warn("We got a get_subscriber_count call for an untracked stream: " + stream_id);
 | 
			
		||||
        return undefined;
 | 
			
		||||
    }
 | 
			
		||||
    if (!sub.subscribers) {
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
    return sub.subscribers.size;
 | 
			
		||||
 | 
			
		||||
    return subscribers.size;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
exports.update_stream_post_policy = function (sub, stream_post_policy) {
 | 
			
		||||
@@ -693,14 +709,26 @@ exports.maybe_get_stream_name = function (stream_id) {
 | 
			
		||||
    return stream.name;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
exports.get_subscribers = (stream_id) => {
 | 
			
		||||
    const subscribers = stream_subscribers.get(stream_id);
 | 
			
		||||
 | 
			
		||||
    if (typeof subscribers === "undefined") {
 | 
			
		||||
        blueslip.warn("We called get_subscribers for an untracked stream: " + stream_id);
 | 
			
		||||
        return [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return Array.from(subscribers.keys());
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
exports.set_subscribers = function (sub, user_ids) {
 | 
			
		||||
    sub.subscribers = new LazySet(user_ids || []);
 | 
			
		||||
    const subscribers = new LazySet(user_ids || []);
 | 
			
		||||
    stream_subscribers.set(sub.stream_id, subscribers);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
exports.add_subscriber = function (stream_id, user_id) {
 | 
			
		||||
    const sub = exports.get_sub_by_id(stream_id);
 | 
			
		||||
    if (typeof sub === "undefined") {
 | 
			
		||||
        blueslip.warn("We got an add_subscriber call for a non-existent stream.");
 | 
			
		||||
    const subscribers = stream_subscribers.get(stream_id);
 | 
			
		||||
    if (typeof subscribers === "undefined") {
 | 
			
		||||
        blueslip.warn("We got an add_subscriber call for an untracked stream: " + stream_id);
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    const person = people.get_by_user_id(user_id);
 | 
			
		||||
@@ -708,23 +736,23 @@ exports.add_subscriber = function (stream_id, user_id) {
 | 
			
		||||
        blueslip.error("We tried to add invalid subscriber: " + user_id);
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    sub.subscribers.add(user_id);
 | 
			
		||||
    subscribers.add(user_id);
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
exports.remove_subscriber = function (stream_id, user_id) {
 | 
			
		||||
    const sub = exports.get_sub_by_id(stream_id);
 | 
			
		||||
    if (typeof sub === "undefined") {
 | 
			
		||||
        blueslip.warn("We got a remove_subscriber call for a non-existent stream " + stream_id);
 | 
			
		||||
    const subscribers = stream_subscribers.get(stream_id);
 | 
			
		||||
    if (typeof subscribers === "undefined") {
 | 
			
		||||
        blueslip.warn("We got a remove_subscriber call for an untracked stream " + stream_id);
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    if (!sub.subscribers.has(user_id)) {
 | 
			
		||||
    if (!subscribers.has(user_id)) {
 | 
			
		||||
        blueslip.warn("We tried to remove invalid subscriber: " + user_id);
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    sub.subscribers.delete(user_id);
 | 
			
		||||
    subscribers.delete(user_id);
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
};
 | 
			
		||||
@@ -744,7 +772,13 @@ exports.is_user_subscribed = function (stream_id, user_id) {
 | 
			
		||||
        return undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return sub.subscribers.has(user_id);
 | 
			
		||||
    const subscribers = stream_subscribers.get(stream_id);
 | 
			
		||||
    if (typeof subscribers === "undefined") {
 | 
			
		||||
        blueslip.warn("We called is_user_subscribed for an untracked stream: " + stream_id);
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return subscribers.has(user_id);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
exports.create_streams = function (streams) {
 | 
			
		||||
@@ -902,9 +936,7 @@ exports.sort_for_stream_settings = function (stream_ids, order) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function by_subscriber_count(id_a, id_b) {
 | 
			
		||||
        const out =
 | 
			
		||||
            exports.get_sub_by_id(id_b).subscribers.size -
 | 
			
		||||
            exports.get_sub_by_id(id_a).subscribers.size;
 | 
			
		||||
        const out = exports.get_subscriber_count(id_b) - exports.get_subscriber_count(id_a);
 | 
			
		||||
        if (out === 0) {
 | 
			
		||||
            return by_stream_name(id_a, id_b);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -327,11 +327,12 @@ function show_subscription_settings(sub) {
 | 
			
		||||
    const list = get_subscriber_list(sub_settings);
 | 
			
		||||
    list.empty();
 | 
			
		||||
 | 
			
		||||
    const users = exports.get_users_from_subscribers(sub.subscribers);
 | 
			
		||||
    const user_ids = stream_data.get_subscribers(sub.stream_id);
 | 
			
		||||
    const users = exports.get_users_from_subscribers(user_ids);
 | 
			
		||||
    exports.sort_but_pin_current_user_on_top(users);
 | 
			
		||||
 | 
			
		||||
    function get_users_for_subscriber_typeahead() {
 | 
			
		||||
        const potential_subscribers = stream_data.potential_subscribers(sub);
 | 
			
		||||
        const potential_subscribers = stream_data.potential_subscribers(stream_id);
 | 
			
		||||
        return user_pill.filter_taken_users(potential_subscribers, exports.pill_widget);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -36,13 +36,11 @@ exports.get_stream_name_from_item = function (item) {
 | 
			
		||||
 | 
			
		||||
function get_user_ids_from_subs(items) {
 | 
			
		||||
    let user_ids = [];
 | 
			
		||||
    const stream_ids = items.map((item) => item.stream_id);
 | 
			
		||||
    for (const stream_id of stream_ids) {
 | 
			
		||||
        const sub = stream_data.get_sub_by_id(stream_id);
 | 
			
		||||
        if (!sub) {
 | 
			
		||||
            continue;
 | 
			
		||||
    for (const item of items) {
 | 
			
		||||
        // only some of our items have streams (for copy-from-stream)
 | 
			
		||||
        if (item.stream_id !== undefined) {
 | 
			
		||||
            user_ids = user_ids.concat(stream_data.get_subscribers(item.stream_id));
 | 
			
		||||
        }
 | 
			
		||||
        user_ids = user_ids.concat(sub.subscribers.map());
 | 
			
		||||
    }
 | 
			
		||||
    return user_ids;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -194,7 +194,8 @@ exports.update_subscribers_list = function (sub) {
 | 
			
		||||
    if (!sub.can_access_subscribers) {
 | 
			
		||||
        $(".subscriber_list_settings_container").hide();
 | 
			
		||||
    } else {
 | 
			
		||||
        const users = stream_edit.get_users_from_subscribers(sub.subscribers);
 | 
			
		||||
        const subscribers = stream_data.get_subscribers(sub.stream_id);
 | 
			
		||||
        const users = stream_edit.get_users_from_subscribers(subscribers);
 | 
			
		||||
 | 
			
		||||
        /*
 | 
			
		||||
            We try to find a subscribers list that is already in the
 | 
			
		||||
 
 | 
			
		||||
@@ -389,7 +389,9 @@ exports.compare_by_activity = function (stream_a, stream_b) {
 | 
			
		||||
    if (diff !== 0) {
 | 
			
		||||
        return diff;
 | 
			
		||||
    }
 | 
			
		||||
    diff = stream_b.subscribers.size - stream_a.subscribers.size;
 | 
			
		||||
    diff =
 | 
			
		||||
        stream_data.get_subscriber_count(stream_b.stream_id) -
 | 
			
		||||
        stream_data.get_subscriber_count(stream_a.stream_id);
 | 
			
		||||
    if (diff !== 0) {
 | 
			
		||||
        return diff;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user