mirror of
https://github.com/zulip/zulip.git
synced 2025-11-01 12:33:40 +00:00
streams: Add subscriber_count to page load data.
This commit is contained in:
@@ -20,6 +20,15 @@ format used by the Zulip server that they are interacting with.
|
||||
|
||||
## Changes in Zulip 11.0
|
||||
|
||||
**Feature level 394**
|
||||
|
||||
* [`POST /register`](/api/register-queue), [`GET
|
||||
/events`](/api/get-events), [`GET /streams`](/api/get-streams),
|
||||
[`GET /streams/{stream_id}`](/api/get-stream-by-id):: Added a new
|
||||
field `subscriber_count` to Stream and Subscription objects with the
|
||||
total number of non-deactivated users who are subscribed to the
|
||||
channel.
|
||||
|
||||
**Feature level 393**
|
||||
|
||||
* [`PATCH /messages/{message_id}`](/api/delete-message),
|
||||
|
||||
@@ -34,7 +34,7 @@ DESKTOP_WARNING_VERSION = "5.9.3"
|
||||
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
||||
# entries in the endpoint's documentation in `zulip.yaml`.
|
||||
|
||||
API_FEATURE_LEVEL = 393
|
||||
API_FEATURE_LEVEL = 394
|
||||
|
||||
# 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
|
||||
|
||||
@@ -475,6 +475,7 @@ def send_subscription_add_events(
|
||||
rendered_description=stream_dict["rendered_description"],
|
||||
stream_id=stream_dict["stream_id"],
|
||||
stream_post_policy=stream_dict["stream_post_policy"],
|
||||
subscriber_count=stream_dict["subscriber_count"],
|
||||
topics_policy=stream_dict["topics_policy"],
|
||||
# Computed fields not present in Stream.API_FIELDS
|
||||
is_announcement_only=stream_dict["is_announcement_only"],
|
||||
|
||||
@@ -1630,6 +1630,10 @@ def apply_event(
|
||||
if sub["stream_id"] == event["stream_id"]:
|
||||
sub[event["property"]] = event["value"]
|
||||
elif event["op"] == "peer_add":
|
||||
# Note: We don't update subscriber_count here, since we
|
||||
# have no way to know whether the added subscriber is
|
||||
# already in our count or not. The opposite decision would
|
||||
# be defensible, but this is less code.
|
||||
if include_subscribers:
|
||||
stream_ids = set(event["stream_ids"])
|
||||
user_ids = set(event["user_ids"])
|
||||
@@ -1644,6 +1648,7 @@ def apply_event(
|
||||
subscribers = set(sub["subscribers"]) | user_ids
|
||||
sub["subscribers"] = sorted(subscribers)
|
||||
elif event["op"] == "peer_remove":
|
||||
# Note: We don't update subscriber_count here, as with peer_add.
|
||||
if include_subscribers:
|
||||
stream_ids = set(event["stream_ids"])
|
||||
user_ids = set(event["user_ids"])
|
||||
|
||||
@@ -1538,6 +1538,7 @@ def stream_to_dict(
|
||||
stream_post_policy=stream_post_policy,
|
||||
is_announcement_only=stream_post_policy == Stream.STREAM_POST_POLICY_ADMINS,
|
||||
stream_weekly_traffic=stream_weekly_traffic,
|
||||
subscriber_count=stream.subscriber_count,
|
||||
topics_policy=StreamTopicsPolicyEnum(stream.topics_policy).name,
|
||||
)
|
||||
|
||||
|
||||
@@ -144,6 +144,7 @@ def get_web_public_subs(
|
||||
stream_id=stream_id,
|
||||
stream_post_policy=stream_post_policy,
|
||||
stream_weekly_traffic=stream_weekly_traffic,
|
||||
subscriber_count=stream.subscriber_count,
|
||||
topics_policy=StreamTopicsPolicyEnum(topics_policy).name,
|
||||
wildcard_mentions_notify=wildcard_mentions_notify,
|
||||
)
|
||||
@@ -219,6 +220,7 @@ def build_stream_api_dict(
|
||||
stream_id=raw_stream_dict["id"],
|
||||
stream_post_policy=raw_stream_dict["stream_post_policy"],
|
||||
stream_weekly_traffic=stream_weekly_traffic,
|
||||
subscriber_count=raw_stream_dict["subscriber_count"],
|
||||
topics_policy=raw_stream_dict["topics_policy"],
|
||||
is_announcement_only=is_announcement_only,
|
||||
is_recently_active=raw_stream_dict["is_recently_active"],
|
||||
@@ -251,6 +253,7 @@ def build_stream_dict_for_sub(
|
||||
stream_id = stream_dict["stream_id"]
|
||||
stream_post_policy = stream_dict["stream_post_policy"]
|
||||
stream_weekly_traffic = stream_dict["stream_weekly_traffic"]
|
||||
subscriber_count = stream_dict["subscriber_count"]
|
||||
topics_policy = stream_dict["topics_policy"]
|
||||
is_announcement_only = stream_dict["is_announcement_only"]
|
||||
is_recently_active = stream_dict["is_recently_active"]
|
||||
@@ -301,6 +304,7 @@ def build_stream_dict_for_sub(
|
||||
stream_id=stream_id,
|
||||
stream_post_policy=stream_post_policy,
|
||||
stream_weekly_traffic=stream_weekly_traffic,
|
||||
subscriber_count=subscriber_count,
|
||||
topics_policy=topics_policy,
|
||||
wildcard_mentions_notify=wildcard_mentions_notify,
|
||||
)
|
||||
@@ -326,6 +330,7 @@ def build_stream_dict_for_never_sub(
|
||||
rendered_description = raw_stream_dict["rendered_description"]
|
||||
stream_id = raw_stream_dict["id"]
|
||||
stream_post_policy = raw_stream_dict["stream_post_policy"]
|
||||
subscriber_count = raw_stream_dict["subscriber_count"]
|
||||
topics_policy = raw_stream_dict["topics_policy"]
|
||||
|
||||
if recent_traffic is not None:
|
||||
@@ -378,6 +383,7 @@ def build_stream_dict_for_never_sub(
|
||||
stream_id=stream_id,
|
||||
stream_post_policy=stream_post_policy,
|
||||
stream_weekly_traffic=stream_weekly_traffic,
|
||||
subscriber_count=subscriber_count,
|
||||
topics_policy=topics_policy,
|
||||
)
|
||||
|
||||
|
||||
@@ -178,6 +178,7 @@ class RawStreamDict(TypedDict):
|
||||
name: str
|
||||
rendered_description: str
|
||||
stream_post_policy: int
|
||||
subscriber_count: int
|
||||
topics_policy: str
|
||||
|
||||
|
||||
@@ -235,6 +236,7 @@ class SubscriptionStreamDict(TypedDict):
|
||||
stream_id: int
|
||||
stream_post_policy: int
|
||||
stream_weekly_traffic: int | None
|
||||
subscriber_count: int
|
||||
subscribers: NotRequired[list[int]]
|
||||
partial_subscribers: NotRequired[list[int]]
|
||||
topics_policy: str
|
||||
@@ -264,6 +266,7 @@ class NeverSubscribedStreamDict(TypedDict):
|
||||
stream_id: int
|
||||
stream_post_policy: int
|
||||
stream_weekly_traffic: int | None
|
||||
subscriber_count: int
|
||||
subscribers: NotRequired[list[int]]
|
||||
partial_subscribers: NotRequired[list[int]]
|
||||
topics_policy: str
|
||||
@@ -295,6 +298,7 @@ class DefaultStreamDict(TypedDict):
|
||||
rendered_description: str
|
||||
stream_id: int # `stream_id` represents `id` of the `Stream` object in `API_FIELDS`
|
||||
stream_post_policy: int
|
||||
subscriber_count: int
|
||||
topics_policy: str
|
||||
# Computed fields not specified in `Stream.API_FIELDS`
|
||||
is_announcement_only: bool
|
||||
|
||||
@@ -258,6 +258,7 @@ class Stream(models.Model):
|
||||
"message_retention_days",
|
||||
"name",
|
||||
"rendered_description",
|
||||
"subscriber_count",
|
||||
"can_add_subscribers_group_id",
|
||||
"can_administer_channel_group_id",
|
||||
"can_send_message_group_id",
|
||||
|
||||
@@ -1367,6 +1367,7 @@ paths:
|
||||
"can_remove_subscribers_group": 2,
|
||||
"can_subscribe_group": 2,
|
||||
"stream_weekly_traffic": null,
|
||||
"subscriber_count": 0,
|
||||
},
|
||||
],
|
||||
"id": 0,
|
||||
@@ -16231,6 +16232,7 @@ paths:
|
||||
can_administer_channel_group: {}
|
||||
can_send_message_group: {}
|
||||
can_subscribe_group: {}
|
||||
subscriber_count: {}
|
||||
stream_weekly_traffic:
|
||||
type: integer
|
||||
nullable: true
|
||||
@@ -21470,6 +21472,7 @@ paths:
|
||||
can_administer_channel_group: {}
|
||||
can_send_message_group: {}
|
||||
can_subscribe_group: {}
|
||||
subscriber_count: {}
|
||||
stream_weekly_traffic:
|
||||
type: integer
|
||||
nullable: true
|
||||
@@ -21515,6 +21518,7 @@ paths:
|
||||
- can_subscribe_group
|
||||
- stream_weekly_traffic
|
||||
- is_recently_active
|
||||
- subscriber_count
|
||||
example:
|
||||
{
|
||||
"msg": "",
|
||||
@@ -21543,6 +21547,7 @@ paths:
|
||||
"stream_id": 2,
|
||||
"stream_post_policy": 1,
|
||||
"stream_weekly_traffic": null,
|
||||
subscriber_count: 20,
|
||||
},
|
||||
{
|
||||
"can_add_subscribers_group": 9,
|
||||
@@ -21566,6 +21571,7 @@ paths:
|
||||
"stream_id": 1,
|
||||
"stream_post_policy": 1,
|
||||
"stream_weekly_traffic": null,
|
||||
subscriber_count: 10,
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -21621,6 +21627,7 @@ paths:
|
||||
"can_remove_subscribers_group": 2,
|
||||
"can_subscribe_group": 2,
|
||||
"stream_weekly_traffic": null,
|
||||
subscriber_count: 12,
|
||||
},
|
||||
}
|
||||
"400":
|
||||
@@ -23939,6 +23946,7 @@ components:
|
||||
can_administer_channel_group: {}
|
||||
can_send_message_group: {}
|
||||
can_subscribe_group: {}
|
||||
subscriber_count: {}
|
||||
stream_weekly_traffic:
|
||||
type: integer
|
||||
nullable: true
|
||||
@@ -23965,6 +23973,7 @@ components:
|
||||
- rendered_description
|
||||
- is_web_public
|
||||
- stream_post_policy
|
||||
- subscriber_count
|
||||
- message_retention_days
|
||||
- history_public_to_subscribers
|
||||
- first_message_id
|
||||
@@ -24134,6 +24143,27 @@ components:
|
||||
$ref: "#/components/schemas/CanSendMessageGroup"
|
||||
can_subscribe_group:
|
||||
$ref: "#/components/schemas/CanSubscribeGroup"
|
||||
subscriber_count:
|
||||
type: number
|
||||
description: |
|
||||
The total number of non-deactivated users (including bots) who
|
||||
are subscribed to the channel. Clients are responsible for updating
|
||||
this value using `peer_add` and `peer_remove` events.
|
||||
|
||||
The server's internals cannot guarantee this value is correctly
|
||||
synced with `peer_add` and `peer_remove` events for the channel. As
|
||||
a result, if a (rare) race occurs between a change in the channel's
|
||||
subscribers and fetching this value, it is possible for a client
|
||||
that is correctly following the events protocol to end up with a
|
||||
permanently off-by-one error in the channel's subscriber count.
|
||||
|
||||
Clients are recommended to fetch full subscriber data for a channel
|
||||
in contexts where it is important to avoid this risk. The official
|
||||
web application, for example, uses this field primarily while
|
||||
waiting to fetch a given channel's full subscriber list from the
|
||||
server.
|
||||
|
||||
**Changes**: New in Zulip 11.0 (feature level 394).
|
||||
BasicBot:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/BasicBotBase"
|
||||
@@ -25214,6 +25244,27 @@ components:
|
||||
channels. Note that some endpoints will never return archived
|
||||
channels unless the client declares explicit support for
|
||||
them via the `archived_channels` client capability.
|
||||
subscriber_count:
|
||||
type: number
|
||||
description: |
|
||||
The total number of non-deactivated users (including bots) who
|
||||
are subscribed to the channel. Clients are responsible for updating
|
||||
this value using `peer_add` and `peer_remove` events.
|
||||
|
||||
The server's internals cannot guarantee this value is correctly
|
||||
synced with `peer_add` and `peer_remove` events for the channel. As
|
||||
a result, if a (rare) race occurs between a change in the channel's
|
||||
subscribers and fetching this value, it is possible for a client
|
||||
that is correctly following the events protocol to end up with a
|
||||
permanently off-by-one error in the channel's subscriber count.
|
||||
|
||||
Clients are recommended to fetch full subscriber data for a channel
|
||||
in contexts where it is important to avoid this risk. The official
|
||||
web application, for example, uses this field primarily while
|
||||
waiting to fetch a given channel's full subscriber list from the
|
||||
server.
|
||||
|
||||
**Changes**: New in Zulip 11.0 (feature level 394).
|
||||
DefaultChannelGroup:
|
||||
type: object
|
||||
description: |
|
||||
|
||||
@@ -496,13 +496,25 @@ class BaseAction(ZulipTestCase):
|
||||
for u in state["never_subscribed"]:
|
||||
if "subscribers" in u:
|
||||
u["subscribers"].sort()
|
||||
# this isn't guaranteed to match
|
||||
del u["subscriber_count"]
|
||||
if "subscriptions" in state:
|
||||
for u in state["subscriptions"]:
|
||||
if "subscribers" in u:
|
||||
u["subscribers"].sort()
|
||||
# this isn't guaranteed to match
|
||||
del u["subscriber_count"]
|
||||
state["subscriptions"] = {u["name"]: u for u in state["subscriptions"]}
|
||||
if "unsubscribed" in state:
|
||||
for u in state["unsubscribed"]:
|
||||
# this isn't guaranteed to match
|
||||
del u["subscriber_count"]
|
||||
state["unsubscribed"] = {u["name"]: u for u in state["unsubscribed"]}
|
||||
if "streams" in state:
|
||||
for stream in state["streams"]:
|
||||
if "subscriber_count" in stream:
|
||||
# this isn't guaranteed to match
|
||||
del stream["subscriber_count"]
|
||||
if "realm_bots" in state:
|
||||
state["realm_bots"] = {u["email"]: u for u in state["realm_bots"]}
|
||||
# Since time is different for every call, just fix the value
|
||||
|
||||
Reference in New Issue
Block a user