mirror of
https://github.com/zulip/zulip.git
synced 2025-11-03 13:33:24 +00:00
stream_edit: Fetch subscribers before showing subscriber tab.
Work towards #34244. Now that we're supporting partial subscriber data, we might need to fetch the full list of subscribers when opening the subscribers tab of the edit channel modal. This commit handles a slow load with a loading spinner while we fetch the data, and also makes sure to ignore the data if it's received after it stops being relevant (in case the user has another stream's data open).
This commit is contained in:
@@ -216,6 +216,21 @@ export function get_subscribers(stream_id: number): number[] {
|
|||||||
return [...subscribers.keys()];
|
return [...subscribers.keys()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function get_all_subscribers(
|
||||||
|
stream_id: number,
|
||||||
|
retry_on_failure = true,
|
||||||
|
): Promise<number[] | null> {
|
||||||
|
// This function parallels `get_subscribers` but ensures we include all
|
||||||
|
// subscribers, possibly fetching that data from the server.
|
||||||
|
const subscribers = await get_full_subscriber_set(stream_id, retry_on_failure);
|
||||||
|
// This means the request failed, which can only happen if `retry_on_failure`
|
||||||
|
// is false.
|
||||||
|
if (subscribers === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return [...subscribers.keys()];
|
||||||
|
}
|
||||||
|
|
||||||
export function set_subscribers(stream_id: number, user_ids: number[], full_data = true): void {
|
export function set_subscribers(stream_id: number, user_ids: number[], full_data = true): void {
|
||||||
const subscribers = new LazySet(user_ids);
|
const subscribers = new LazySet(user_ids);
|
||||||
stream_subscribers.set(stream_id, subscribers);
|
stream_subscribers.set(stream_id, subscribers);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import * as hash_parser from "./hash_parser.ts";
|
|||||||
import {$t, $t_html} from "./i18n.ts";
|
import {$t, $t_html} from "./i18n.ts";
|
||||||
import * as ListWidget from "./list_widget.ts";
|
import * as ListWidget from "./list_widget.ts";
|
||||||
import type {ListWidget as ListWidgetType} from "./list_widget.ts";
|
import type {ListWidget as ListWidgetType} from "./list_widget.ts";
|
||||||
|
import * as loading from "./loading.ts";
|
||||||
import * as peer_data from "./peer_data.ts";
|
import * as peer_data from "./peer_data.ts";
|
||||||
import * as people from "./people.ts";
|
import * as people from "./people.ts";
|
||||||
import type {User} from "./people.ts";
|
import type {User} from "./people.ts";
|
||||||
@@ -128,8 +129,30 @@ export function enable_subscriber_management({
|
|||||||
$parent_container.find(".stream_subscription_request_result").empty();
|
$parent_container.find(".stream_subscription_request_result").empty();
|
||||||
});
|
});
|
||||||
|
|
||||||
const user_ids = peer_data.get_subscribers(stream_id);
|
|
||||||
const user_can_remove_subscribers = stream_data.can_unsubscribe_others(sub);
|
const user_can_remove_subscribers = stream_data.can_unsubscribe_others(sub);
|
||||||
|
void render_subscriber_list_widget(sub, user_can_remove_subscribers, $parent_container);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function render_subscriber_list_widget(
|
||||||
|
sub: StreamSubscription,
|
||||||
|
user_can_remove_subscribers: boolean,
|
||||||
|
$parent_container: JQuery,
|
||||||
|
): Promise<void> {
|
||||||
|
$(".subscriber_list_settings_container").toggleClass("no-display", true);
|
||||||
|
loading.make_indicator($(".subscriber-list-settings-loading"), {
|
||||||
|
text: $t({defaultMessage: "Loading…"}),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Because we're using `retry_on_failure=true`, this will only return once it
|
||||||
|
// succeeds, so we can't get `null`.
|
||||||
|
const user_ids = await peer_data.get_all_subscribers(sub.stream_id, true);
|
||||||
|
assert(user_ids !== null);
|
||||||
|
|
||||||
|
// Make sure we're still editing this stream after waiting for subscriber data.
|
||||||
|
if (!hash_parser.is_editing_stream(sub.stream_id)) {
|
||||||
|
blueslip.info("ignoring subscriber data for stream that is no longer being edited");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// We track a single subscribers_list_widget for this module, since we
|
// We track a single subscribers_list_widget for this module, since we
|
||||||
// only ever have one list of subscribers visible at a time.
|
// only ever have one list of subscribers visible at a time.
|
||||||
@@ -139,6 +162,8 @@ export function enable_subscriber_management({
|
|||||||
user_ids,
|
user_ids,
|
||||||
user_can_remove_subscribers,
|
user_can_remove_subscribers,
|
||||||
});
|
});
|
||||||
|
loading.destroy_indicator($(".subscriber-list-settings-loading"));
|
||||||
|
$(".subscriber_list_settings_container").toggleClass("no-display", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function make_list_widget({
|
function make_list_widget({
|
||||||
@@ -424,16 +449,23 @@ export function update_subscribers_list(sub: StreamSubscription): void {
|
|||||||
// from an existing stream or a user group.
|
// from an existing stream or a user group.
|
||||||
const subscriber_ids = peer_data.get_subscribers(sub.stream_id);
|
const subscriber_ids = peer_data.get_subscribers(sub.stream_id);
|
||||||
update_subscribers_list_widget(subscriber_ids);
|
update_subscribers_list_widget(subscriber_ids);
|
||||||
$(".subscriber_list_settings_container").show();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function update_subscribers_list_widget(subscriber_ids: number[]): void {
|
function update_subscribers_list_widget(subscriber_ids: number[]): void {
|
||||||
|
// This can happen if we're still fetching user ids in
|
||||||
|
// `render_subscriber_list_widget`, but we'll render the widget with
|
||||||
|
// fetched ids after the fetch is complete, so we don't need to do
|
||||||
|
// anything here.
|
||||||
|
if (subscribers_list_widget === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// This re-renders the subscribers_list_widget with a new
|
// This re-renders the subscribers_list_widget with a new
|
||||||
// list of subscriber_ids.
|
// list of subscriber_ids.
|
||||||
const users = people.get_users_from_ids(subscriber_ids);
|
const users = people.get_users_from_ids(subscriber_ids);
|
||||||
people.sort_but_pin_current_user_on_top(users);
|
people.sort_but_pin_current_user_on_top(users);
|
||||||
subscribers_list_widget.replace_list_data(users);
|
subscribers_list_widget.replace_list_data(users);
|
||||||
|
$(".subscriber_list_settings_container").show();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function rerender_subscribers_list(sub: sub_store.StreamSubscription): void {
|
export function rerender_subscribers_list(sub: sub_store.StreamSubscription): void {
|
||||||
@@ -452,7 +484,6 @@ export function rerender_subscribers_list(sub: sub_store.StreamSubscription): vo
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const user_ids = peer_data.get_subscribers(sub.stream_id);
|
|
||||||
const user_can_remove_subscribers = stream_data.can_unsubscribe_others(sub);
|
const user_can_remove_subscribers = stream_data.can_unsubscribe_others(sub);
|
||||||
const $parent_container = stream_settings_containers
|
const $parent_container = stream_settings_containers
|
||||||
.get_edit_container(sub)
|
.get_edit_container(sub)
|
||||||
@@ -463,12 +494,7 @@ export function rerender_subscribers_list(sub: sub_store.StreamSubscription): vo
|
|||||||
can_remove_subscribers: user_can_remove_subscribers,
|
can_remove_subscribers: user_can_remove_subscribers,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
subscribers_list_widget = make_list_widget({
|
void render_subscriber_list_widget(sub, user_can_remove_subscribers, $parent_container);
|
||||||
$parent_container,
|
|
||||||
name: "stream_subscribers",
|
|
||||||
user_ids,
|
|
||||||
user_can_remove_subscribers,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initialize(): void {
|
export function initialize(): void {
|
||||||
|
|||||||
@@ -136,6 +136,17 @@ h4.user_group_setting_subsection_title {
|
|||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.subscriber_list_settings_container.no-display {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subscriber-list-settings-loading {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: auto;
|
||||||
|
margin-top: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
.member-list-box,
|
.member-list-box,
|
||||||
.subscriber-list-box {
|
.subscriber-list-box {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{{#if render_subscribers}}
|
{{#if render_subscribers}}
|
||||||
<div class="subscriber_list_settings_container">
|
<div class="subscriber_list_settings_container no-display">
|
||||||
<h4 class="stream_setting_subsection_title">
|
<h4 class="stream_setting_subsection_title">
|
||||||
{{t "Add subscribers" }}
|
{{t "Add subscribers" }}
|
||||||
</h4>
|
</h4>
|
||||||
@@ -20,4 +20,5 @@
|
|||||||
{{> stream_members_table .}}
|
{{> stream_members_table .}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="subscriber-list-settings-loading"></div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|||||||
@@ -302,6 +302,19 @@ test("maybe_fetch_stream_subscribers", async () => {
|
|||||||
assert.equal(await peer_data.maybe_fetch_is_user_subscribed(india.stream_id, 2, false), true);
|
assert.equal(await peer_data.maybe_fetch_is_user_subscribed(india.stream_id, 2, false), true);
|
||||||
assert.equal(await peer_data.maybe_fetch_is_user_subscribed(india.stream_id, 5, false), false);
|
assert.equal(await peer_data.maybe_fetch_is_user_subscribed(india.stream_id, 5, false), false);
|
||||||
|
|
||||||
|
peer_data.clear_for_testing();
|
||||||
|
assert.deepEqual(await peer_data.get_subscribers(india.stream_id), []);
|
||||||
|
assert.deepEqual(await peer_data.get_all_subscribers(india.stream_id), [1, 2, 3, 4]);
|
||||||
|
|
||||||
|
peer_data.clear_for_testing();
|
||||||
|
channel.get = () => {
|
||||||
|
throw new Error("error");
|
||||||
|
};
|
||||||
|
blueslip.expect("error", "Failure fetching channel subscribers");
|
||||||
|
// retry is false, so we get null because there was an error
|
||||||
|
assert.deepEqual(await peer_data.get_all_subscribers(india.stream_id, false), null);
|
||||||
|
blueslip.reset();
|
||||||
|
|
||||||
channel.get = () => {
|
channel.get = () => {
|
||||||
throw new Error("error");
|
throw new Error("error");
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user