mirror of
https://github.com/zulip/zulip.git
synced 2025-11-05 14:35:27 +00:00
subscription: Include archived channels in streams list.
`is_archived` field is added to the stream and types. Include a new `archived_channeels` client capability, to allow clients to access data on archived channels, without breaking backwards-compatibility for existing clients that don't know how to handle these. Also, included `exclude_archived` parameter to `/get-streams`, which defaults to `true` as basic clients may not be interested in archived streams.
This commit is contained in:
@@ -20,6 +20,24 @@ format used by the Zulip server that they are interacting with.
|
|||||||
|
|
||||||
## Changes in Zulip 10.0
|
## Changes in Zulip 10.0
|
||||||
|
|
||||||
|
**Feature level 315**
|
||||||
|
|
||||||
|
* [POST /register](/api/register-queue), [`GET
|
||||||
|
/streams/{stream_id}`](/api/get-stream-by-id), [`GET
|
||||||
|
/events`](/api/get-events), [GET
|
||||||
|
/users/me/subscriptions](/api/get-subscriptions): The `is_archived`
|
||||||
|
property has been added to channel and subscription objects.
|
||||||
|
|
||||||
|
* [`GET /streams`](/api/get-streams): The new parameter
|
||||||
|
`exclude_archived` controls whether archived channels should be
|
||||||
|
returned.
|
||||||
|
|
||||||
|
* [`POST /register`](/api/register-queue): The new `archived_channels`
|
||||||
|
[client
|
||||||
|
capability](/api/register-queue#parameter-client_capabilities)
|
||||||
|
allows the client to specify whether it supports archived channels
|
||||||
|
being present in the response.
|
||||||
|
|
||||||
**Feature level 314**
|
**Feature level 314**
|
||||||
|
|
||||||
* `PATCH /realm`, [`POST /register`](/api/register-queue),
|
* `PATCH /realm`, [`POST /register`](/api/register-queue),
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ DESKTOP_WARNING_VERSION = "5.9.3"
|
|||||||
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
||||||
# entries in the endpoint's documentation in `zulip.yaml`.
|
# entries in the endpoint's documentation in `zulip.yaml`.
|
||||||
|
|
||||||
API_FEATURE_LEVEL = 314 # Last bumped for create_multiuse_invite_group api changes.
|
API_FEATURE_LEVEL = 315 # Last bumped for `is_archived`
|
||||||
|
|
||||||
# Bump the minor PROVISION_VERSION to indicate that folks should provision
|
# 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
|
# only when going from an old version of the code to a newer version. Bump
|
||||||
|
|||||||
@@ -435,6 +435,7 @@ def send_subscription_add_events(
|
|||||||
stream_weekly_traffic=stream_dict["stream_weekly_traffic"],
|
stream_weekly_traffic=stream_dict["stream_weekly_traffic"],
|
||||||
subscribers=stream_subscribers,
|
subscribers=stream_subscribers,
|
||||||
# Fields from Stream.API_FIELDS
|
# Fields from Stream.API_FIELDS
|
||||||
|
is_archived=stream_dict["is_archived"],
|
||||||
can_remove_subscribers_group=stream_dict["can_remove_subscribers_group"],
|
can_remove_subscribers_group=stream_dict["can_remove_subscribers_group"],
|
||||||
creator_id=stream_dict["creator_id"],
|
creator_id=stream_dict["creator_id"],
|
||||||
date_created=stream_dict["date_created"],
|
date_created=stream_dict["date_created"],
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ from zerver.models import Realm, RealmUserDefault, Stream, UserProfile
|
|||||||
# These fields are used for "stream" events, and are included in the
|
# These fields are used for "stream" events, and are included in the
|
||||||
# larger "subscription" events that also contain personal settings.
|
# larger "subscription" events that also contain personal settings.
|
||||||
default_stream_fields = [
|
default_stream_fields = [
|
||||||
|
("is_archived", bool),
|
||||||
("can_remove_subscribers_group", int),
|
("can_remove_subscribers_group", int),
|
||||||
("creator_id", OptionalType(int)),
|
("creator_id", OptionalType(int)),
|
||||||
("date_created", int),
|
("date_created", int),
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ def fetch_initial_state_data(
|
|||||||
linkifier_url_template: bool = False,
|
linkifier_url_template: bool = False,
|
||||||
user_list_incomplete: bool = False,
|
user_list_incomplete: bool = False,
|
||||||
include_deactivated_groups: bool = False,
|
include_deactivated_groups: bool = False,
|
||||||
|
archived_channels: bool = False,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""When `event_types` is None, fetches the core data powering the
|
"""When `event_types` is None, fetches the core data powering the
|
||||||
web app's `page_params` and `/api/v1/register` (for mobile/terminal
|
web app's `page_params` and `/api/v1/register` (for mobile/terminal
|
||||||
@@ -654,6 +655,7 @@ def fetch_initial_state_data(
|
|||||||
sub_info = gather_subscriptions_helper(
|
sub_info = gather_subscriptions_helper(
|
||||||
user_profile,
|
user_profile,
|
||||||
include_subscribers=include_subscribers,
|
include_subscribers=include_subscribers,
|
||||||
|
include_archived_channels=archived_channels,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
sub_info = get_web_public_subs(realm)
|
sub_info = get_web_public_subs(realm)
|
||||||
@@ -787,6 +789,7 @@ def apply_events(
|
|||||||
linkifier_url_template: bool,
|
linkifier_url_template: bool,
|
||||||
user_list_incomplete: bool,
|
user_list_incomplete: bool,
|
||||||
include_deactivated_groups: bool,
|
include_deactivated_groups: bool,
|
||||||
|
archived_channels: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
for event in events:
|
for event in events:
|
||||||
if fetch_event_types is not None and event["type"] not in fetch_event_types:
|
if fetch_event_types is not None and event["type"] not in fetch_event_types:
|
||||||
@@ -809,6 +812,7 @@ def apply_events(
|
|||||||
linkifier_url_template=linkifier_url_template,
|
linkifier_url_template=linkifier_url_template,
|
||||||
user_list_incomplete=user_list_incomplete,
|
user_list_incomplete=user_list_incomplete,
|
||||||
include_deactivated_groups=include_deactivated_groups,
|
include_deactivated_groups=include_deactivated_groups,
|
||||||
|
archived_channels=archived_channels,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -823,6 +827,7 @@ def apply_event(
|
|||||||
linkifier_url_template: bool,
|
linkifier_url_template: bool,
|
||||||
user_list_incomplete: bool,
|
user_list_incomplete: bool,
|
||||||
include_deactivated_groups: bool,
|
include_deactivated_groups: bool,
|
||||||
|
archived_channels: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
if event["type"] == "message":
|
if event["type"] == "message":
|
||||||
state["max_message_id"] = max(state["max_message_id"], event["message"]["id"])
|
state["max_message_id"] = max(state["max_message_id"], event["message"]["id"])
|
||||||
@@ -1210,17 +1215,30 @@ def apply_event(
|
|||||||
s for s in state["streams"] if s["stream_id"] not in deleted_stream_ids
|
s for s in state["streams"] if s["stream_id"] not in deleted_stream_ids
|
||||||
]
|
]
|
||||||
|
|
||||||
state["subscriptions"] = [
|
if archived_channels:
|
||||||
stream
|
for stream in state["subscriptions"]:
|
||||||
for stream in state["subscriptions"]
|
if stream["stream_id"] in deleted_stream_ids:
|
||||||
if stream["stream_id"] not in deleted_stream_ids
|
stream["is_archived"] = True
|
||||||
]
|
|
||||||
|
|
||||||
state["unsubscribed"] = [
|
for stream in state["unsubscribed"]:
|
||||||
stream
|
if stream["stream_id"] in deleted_stream_ids:
|
||||||
for stream in state["unsubscribed"]
|
stream["is_archived"] = True
|
||||||
if stream["stream_id"] not in deleted_stream_ids
|
stream["first_message_id"] = Stream.objects.get(
|
||||||
]
|
id=stream["stream_id"]
|
||||||
|
).first_message_id
|
||||||
|
|
||||||
|
else:
|
||||||
|
state["subscriptions"] = [
|
||||||
|
stream
|
||||||
|
for stream in state["subscriptions"]
|
||||||
|
if stream["stream_id"] not in deleted_stream_ids
|
||||||
|
]
|
||||||
|
|
||||||
|
state["unsubscribed"] = [
|
||||||
|
stream
|
||||||
|
for stream in state["unsubscribed"]
|
||||||
|
if stream["stream_id"] not in deleted_stream_ids
|
||||||
|
]
|
||||||
|
|
||||||
state["never_subscribed"] = [
|
state["never_subscribed"] = [
|
||||||
stream
|
stream
|
||||||
@@ -1720,6 +1738,7 @@ class ClientCapabilities(TypedDict):
|
|||||||
linkifier_url_template: NotRequired[bool]
|
linkifier_url_template: NotRequired[bool]
|
||||||
user_list_incomplete: NotRequired[bool]
|
user_list_incomplete: NotRequired[bool]
|
||||||
include_deactivated_groups: NotRequired[bool]
|
include_deactivated_groups: NotRequired[bool]
|
||||||
|
archived_channels: NotRequired[bool]
|
||||||
|
|
||||||
|
|
||||||
def do_events_register(
|
def do_events_register(
|
||||||
@@ -1757,6 +1776,7 @@ def do_events_register(
|
|||||||
linkifier_url_template = client_capabilities.get("linkifier_url_template", False)
|
linkifier_url_template = client_capabilities.get("linkifier_url_template", False)
|
||||||
user_list_incomplete = client_capabilities.get("user_list_incomplete", False)
|
user_list_incomplete = client_capabilities.get("user_list_incomplete", False)
|
||||||
include_deactivated_groups = client_capabilities.get("include_deactivated_groups", False)
|
include_deactivated_groups = client_capabilities.get("include_deactivated_groups", False)
|
||||||
|
archived_channels = client_capabilities.get("archived_channels", False)
|
||||||
|
|
||||||
if fetch_event_types is not None:
|
if fetch_event_types is not None:
|
||||||
event_types_set: set[str] | None = set(fetch_event_types)
|
event_types_set: set[str] | None = set(fetch_event_types)
|
||||||
@@ -1789,6 +1809,7 @@ def do_events_register(
|
|||||||
user_avatar_url_field_optional=user_avatar_url_field_optional,
|
user_avatar_url_field_optional=user_avatar_url_field_optional,
|
||||||
user_settings_object=user_settings_object,
|
user_settings_object=user_settings_object,
|
||||||
user_list_incomplete=user_list_incomplete,
|
user_list_incomplete=user_list_incomplete,
|
||||||
|
archived_channels=archived_channels,
|
||||||
# These presence params are a noop, because presence is not included.
|
# These presence params are a noop, because presence is not included.
|
||||||
slim_presence=True,
|
slim_presence=True,
|
||||||
presence_last_update_id_fetched_by_client=None,
|
presence_last_update_id_fetched_by_client=None,
|
||||||
@@ -1828,6 +1849,7 @@ def do_events_register(
|
|||||||
linkifier_url_template=linkifier_url_template,
|
linkifier_url_template=linkifier_url_template,
|
||||||
user_list_incomplete=user_list_incomplete,
|
user_list_incomplete=user_list_incomplete,
|
||||||
include_deactivated_groups=include_deactivated_groups,
|
include_deactivated_groups=include_deactivated_groups,
|
||||||
|
archived_channels=archived_channels,
|
||||||
)
|
)
|
||||||
|
|
||||||
if queue_id is None:
|
if queue_id is None:
|
||||||
@@ -1850,6 +1872,7 @@ def do_events_register(
|
|||||||
linkifier_url_template=linkifier_url_template,
|
linkifier_url_template=linkifier_url_template,
|
||||||
user_list_incomplete=user_list_incomplete,
|
user_list_incomplete=user_list_incomplete,
|
||||||
include_deactivated_groups=include_deactivated_groups,
|
include_deactivated_groups=include_deactivated_groups,
|
||||||
|
archived_channels=archived_channels,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Apply events that came in while we were fetching initial data
|
# Apply events that came in while we were fetching initial data
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ def build_page_params_for_home_page_load(
|
|||||||
linkifier_url_template=True,
|
linkifier_url_template=True,
|
||||||
user_list_incomplete=True,
|
user_list_incomplete=True,
|
||||||
include_deactivated_groups=True,
|
include_deactivated_groups=True,
|
||||||
|
archived_channels=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
if user_profile is not None:
|
if user_profile is not None:
|
||||||
|
|||||||
@@ -872,6 +872,7 @@ def stream_to_dict(stream: Stream, recent_traffic: dict[int, int] | None = None)
|
|||||||
stream_weekly_traffic = None
|
stream_weekly_traffic = None
|
||||||
|
|
||||||
return APIStreamDict(
|
return APIStreamDict(
|
||||||
|
is_archived=stream.deactivated,
|
||||||
can_remove_subscribers_group=stream.can_remove_subscribers_group_id,
|
can_remove_subscribers_group=stream.can_remove_subscribers_group_id,
|
||||||
creator_id=stream.creator_id,
|
creator_id=stream.creator_id,
|
||||||
date_created=datetime_to_timestamp(stream.date_created),
|
date_created=datetime_to_timestamp(stream.date_created),
|
||||||
@@ -902,6 +903,7 @@ def get_streams_for_user(
|
|||||||
include_public: bool = True,
|
include_public: bool = True,
|
||||||
include_web_public: bool = False,
|
include_web_public: bool = False,
|
||||||
include_subscribed: bool = True,
|
include_subscribed: bool = True,
|
||||||
|
exclude_archived: bool = True,
|
||||||
include_all_active: bool = False,
|
include_all_active: bool = False,
|
||||||
include_owner_subscribed: bool = False,
|
include_owner_subscribed: bool = False,
|
||||||
) -> list[Stream]:
|
) -> list[Stream]:
|
||||||
@@ -910,8 +912,11 @@ def get_streams_for_user(
|
|||||||
|
|
||||||
include_public = include_public and user_profile.can_access_public_streams()
|
include_public = include_public and user_profile.can_access_public_streams()
|
||||||
|
|
||||||
# Start out with all active streams in the realm.
|
# Start out with all streams in the realm.
|
||||||
query = Stream.objects.filter(realm=user_profile.realm, deactivated=False)
|
query = Stream.objects.filter(realm=user_profile.realm)
|
||||||
|
|
||||||
|
if exclude_archived:
|
||||||
|
query = query.filter(deactivated=False)
|
||||||
|
|
||||||
if include_all_active:
|
if include_all_active:
|
||||||
streams = query.only(*Stream.API_FIELDS)
|
streams = query.only(*Stream.API_FIELDS)
|
||||||
@@ -965,6 +970,7 @@ def do_get_streams(
|
|||||||
include_public: bool = True,
|
include_public: bool = True,
|
||||||
include_web_public: bool = False,
|
include_web_public: bool = False,
|
||||||
include_subscribed: bool = True,
|
include_subscribed: bool = True,
|
||||||
|
exclude_archived: bool = True,
|
||||||
include_all_active: bool = False,
|
include_all_active: bool = False,
|
||||||
include_default: bool = False,
|
include_default: bool = False,
|
||||||
include_owner_subscribed: bool = False,
|
include_owner_subscribed: bool = False,
|
||||||
@@ -976,6 +982,7 @@ def do_get_streams(
|
|||||||
include_public,
|
include_public,
|
||||||
include_web_public,
|
include_web_public,
|
||||||
include_subscribed,
|
include_subscribed,
|
||||||
|
exclude_archived,
|
||||||
include_all_active,
|
include_all_active,
|
||||||
include_owner_subscribed,
|
include_owner_subscribed,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ from zerver.lib.types import (
|
|||||||
SubscriptionStreamDict,
|
SubscriptionStreamDict,
|
||||||
)
|
)
|
||||||
from zerver.models import Realm, Stream, Subscription, UserProfile
|
from zerver.models import Realm, Stream, Subscription, UserProfile
|
||||||
from zerver.models.streams import get_active_streams
|
from zerver.models.streams import get_all_streams
|
||||||
|
|
||||||
|
|
||||||
def get_web_public_subs(realm: Realm) -> SubscriptionInfo:
|
def get_web_public_subs(realm: Realm) -> SubscriptionInfo:
|
||||||
@@ -42,6 +42,7 @@ def get_web_public_subs(realm: Realm) -> SubscriptionInfo:
|
|||||||
subscribed = []
|
subscribed = []
|
||||||
for stream in get_web_public_streams_queryset(realm):
|
for stream in get_web_public_streams_queryset(realm):
|
||||||
# Add Stream fields.
|
# Add Stream fields.
|
||||||
|
is_archived = stream.deactivated
|
||||||
can_remove_subscribers_group_id = stream.can_remove_subscribers_group_id
|
can_remove_subscribers_group_id = stream.can_remove_subscribers_group_id
|
||||||
creator_id = stream.creator_id
|
creator_id = stream.creator_id
|
||||||
date_created = datetime_to_timestamp(stream.date_created)
|
date_created = datetime_to_timestamp(stream.date_created)
|
||||||
@@ -73,6 +74,7 @@ def get_web_public_subs(realm: Realm) -> SubscriptionInfo:
|
|||||||
wildcard_mentions_notify = True
|
wildcard_mentions_notify = True
|
||||||
|
|
||||||
sub = SubscriptionStreamDict(
|
sub = SubscriptionStreamDict(
|
||||||
|
is_archived=is_archived,
|
||||||
audible_notifications=audible_notifications,
|
audible_notifications=audible_notifications,
|
||||||
can_remove_subscribers_group=can_remove_subscribers_group_id,
|
can_remove_subscribers_group=can_remove_subscribers_group_id,
|
||||||
color=color,
|
color=color,
|
||||||
@@ -115,6 +117,7 @@ def build_unsubscribed_sub_from_stream_dict(
|
|||||||
can_remove_subscribers_group_id=stream_dict["can_remove_subscribers_group"],
|
can_remove_subscribers_group_id=stream_dict["can_remove_subscribers_group"],
|
||||||
creator_id=stream_dict["creator_id"],
|
creator_id=stream_dict["creator_id"],
|
||||||
date_created=timestamp_to_datetime(stream_dict["date_created"]),
|
date_created=timestamp_to_datetime(stream_dict["date_created"]),
|
||||||
|
deactivated=stream_dict["is_archived"],
|
||||||
description=stream_dict["description"],
|
description=stream_dict["description"],
|
||||||
first_message_id=stream_dict["first_message_id"],
|
first_message_id=stream_dict["first_message_id"],
|
||||||
history_public_to_subscribers=stream_dict["history_public_to_subscribers"],
|
history_public_to_subscribers=stream_dict["history_public_to_subscribers"],
|
||||||
@@ -144,6 +147,7 @@ def build_stream_dict_for_sub(
|
|||||||
recent_traffic: dict[int, int] | None,
|
recent_traffic: dict[int, int] | None,
|
||||||
) -> SubscriptionStreamDict:
|
) -> SubscriptionStreamDict:
|
||||||
# Handle Stream.API_FIELDS
|
# Handle Stream.API_FIELDS
|
||||||
|
is_archived = raw_stream_dict["deactivated"]
|
||||||
can_remove_subscribers_group_id = raw_stream_dict["can_remove_subscribers_group_id"]
|
can_remove_subscribers_group_id = raw_stream_dict["can_remove_subscribers_group_id"]
|
||||||
creator_id = raw_stream_dict["creator_id"]
|
creator_id = raw_stream_dict["creator_id"]
|
||||||
date_created = datetime_to_timestamp(raw_stream_dict["date_created"])
|
date_created = datetime_to_timestamp(raw_stream_dict["date_created"])
|
||||||
@@ -187,6 +191,7 @@ def build_stream_dict_for_sub(
|
|||||||
|
|
||||||
# Our caller may add a subscribers field.
|
# Our caller may add a subscribers field.
|
||||||
return SubscriptionStreamDict(
|
return SubscriptionStreamDict(
|
||||||
|
is_archived=is_archived,
|
||||||
audible_notifications=audible_notifications,
|
audible_notifications=audible_notifications,
|
||||||
can_remove_subscribers_group=can_remove_subscribers_group_id,
|
can_remove_subscribers_group=can_remove_subscribers_group_id,
|
||||||
color=color,
|
color=color,
|
||||||
@@ -218,6 +223,7 @@ def build_stream_dict_for_never_sub(
|
|||||||
raw_stream_dict: RawStreamDict,
|
raw_stream_dict: RawStreamDict,
|
||||||
recent_traffic: dict[int, int] | None,
|
recent_traffic: dict[int, int] | None,
|
||||||
) -> NeverSubscribedStreamDict:
|
) -> NeverSubscribedStreamDict:
|
||||||
|
is_archived = raw_stream_dict["deactivated"]
|
||||||
can_remove_subscribers_group_id = raw_stream_dict["can_remove_subscribers_group_id"]
|
can_remove_subscribers_group_id = raw_stream_dict["can_remove_subscribers_group_id"]
|
||||||
creator_id = raw_stream_dict["creator_id"]
|
creator_id = raw_stream_dict["creator_id"]
|
||||||
date_created = datetime_to_timestamp(raw_stream_dict["date_created"])
|
date_created = datetime_to_timestamp(raw_stream_dict["date_created"])
|
||||||
@@ -244,6 +250,7 @@ def build_stream_dict_for_never_sub(
|
|||||||
|
|
||||||
# Our caller may add a subscribers field.
|
# Our caller may add a subscribers field.
|
||||||
return NeverSubscribedStreamDict(
|
return NeverSubscribedStreamDict(
|
||||||
|
is_archived=is_archived,
|
||||||
can_remove_subscribers_group=can_remove_subscribers_group_id,
|
can_remove_subscribers_group=can_remove_subscribers_group_id,
|
||||||
creator_id=creator_id,
|
creator_id=creator_id,
|
||||||
date_created=date_created,
|
date_created=date_created,
|
||||||
@@ -447,9 +454,12 @@ def has_metadata_access_to_previously_subscribed_stream(
|
|||||||
def gather_subscriptions_helper(
|
def gather_subscriptions_helper(
|
||||||
user_profile: UserProfile,
|
user_profile: UserProfile,
|
||||||
include_subscribers: bool = True,
|
include_subscribers: bool = True,
|
||||||
|
include_archived_channels: bool = False,
|
||||||
) -> SubscriptionInfo:
|
) -> SubscriptionInfo:
|
||||||
realm = user_profile.realm
|
realm = user_profile.realm
|
||||||
all_streams = get_active_streams(realm).values(
|
all_streams = get_all_streams(
|
||||||
|
realm, include_archived_channels=include_archived_channels
|
||||||
|
).values(
|
||||||
*Stream.API_FIELDS,
|
*Stream.API_FIELDS,
|
||||||
# The realm_id and recipient_id are generally not needed in the API.
|
# The realm_id and recipient_id are generally not needed in the API.
|
||||||
"realm_id",
|
"realm_id",
|
||||||
@@ -517,7 +527,9 @@ def gather_subscriptions_helper(
|
|||||||
unsubscribed.append(stream_dict)
|
unsubscribed.append(stream_dict)
|
||||||
|
|
||||||
if user_profile.can_access_public_streams():
|
if user_profile.can_access_public_streams():
|
||||||
never_subscribed_stream_ids = set(all_streams_map) - sub_unsub_stream_ids
|
never_subscribed_stream_ids = {
|
||||||
|
stream["id"] for stream in all_streams if not stream["deactivated"]
|
||||||
|
} - sub_unsub_stream_ids
|
||||||
else:
|
else:
|
||||||
web_public_stream_ids = {stream["id"] for stream in all_streams if stream["is_web_public"]}
|
web_public_stream_ids = {stream["id"] for stream in all_streams if stream["is_web_public"]}
|
||||||
never_subscribed_stream_ids = web_public_stream_ids - sub_unsub_stream_ids
|
never_subscribed_stream_ids = web_public_stream_ids - sub_unsub_stream_ids
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ class RawStreamDict(TypedDict):
|
|||||||
can_remove_subscribers_group_id: int
|
can_remove_subscribers_group_id: int
|
||||||
creator_id: int | None
|
creator_id: int | None
|
||||||
date_created: datetime
|
date_created: datetime
|
||||||
|
deactivated: bool
|
||||||
description: str
|
description: str
|
||||||
first_message_id: int | None
|
first_message_id: int | None
|
||||||
history_public_to_subscribers: bool
|
history_public_to_subscribers: bool
|
||||||
@@ -193,6 +194,7 @@ class SubscriptionStreamDict(TypedDict):
|
|||||||
in_home_view: bool
|
in_home_view: bool
|
||||||
invite_only: bool
|
invite_only: bool
|
||||||
is_announcement_only: bool
|
is_announcement_only: bool
|
||||||
|
is_archived: bool
|
||||||
is_muted: bool
|
is_muted: bool
|
||||||
is_web_public: bool
|
is_web_public: bool
|
||||||
message_retention_days: int | None
|
message_retention_days: int | None
|
||||||
@@ -208,6 +210,7 @@ class SubscriptionStreamDict(TypedDict):
|
|||||||
|
|
||||||
|
|
||||||
class NeverSubscribedStreamDict(TypedDict):
|
class NeverSubscribedStreamDict(TypedDict):
|
||||||
|
is_archived: bool
|
||||||
can_remove_subscribers_group: int
|
can_remove_subscribers_group: int
|
||||||
creator_id: int | None
|
creator_id: int | None
|
||||||
date_created: int
|
date_created: int
|
||||||
@@ -232,6 +235,7 @@ class DefaultStreamDict(TypedDict):
|
|||||||
with few exceptions and possible additional fields.
|
with few exceptions and possible additional fields.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
is_archived: bool
|
||||||
can_remove_subscribers_group: int
|
can_remove_subscribers_group: int
|
||||||
creator_id: int | None
|
creator_id: int | None
|
||||||
date_created: int
|
date_created: int
|
||||||
|
|||||||
@@ -182,6 +182,7 @@ class Stream(models.Model):
|
|||||||
API_FIELDS = [
|
API_FIELDS = [
|
||||||
"creator_id",
|
"creator_id",
|
||||||
"date_created",
|
"date_created",
|
||||||
|
"deactivated",
|
||||||
"description",
|
"description",
|
||||||
"first_message_id",
|
"first_message_id",
|
||||||
"history_public_to_subscribers",
|
"history_public_to_subscribers",
|
||||||
@@ -197,6 +198,7 @@ class Stream(models.Model):
|
|||||||
|
|
||||||
def to_dict(self) -> DefaultStreamDict:
|
def to_dict(self) -> DefaultStreamDict:
|
||||||
return DefaultStreamDict(
|
return DefaultStreamDict(
|
||||||
|
is_archived=self.deactivated,
|
||||||
can_remove_subscribers_group=self.can_remove_subscribers_group_id,
|
can_remove_subscribers_group=self.can_remove_subscribers_group_id,
|
||||||
creator_id=self.creator_id,
|
creator_id=self.creator_id,
|
||||||
date_created=datetime_to_timestamp(self.date_created),
|
date_created=datetime_to_timestamp(self.date_created),
|
||||||
@@ -229,6 +231,16 @@ def get_active_streams(realm: Realm) -> QuerySet[Stream]:
|
|||||||
return Stream.objects.filter(realm=realm, deactivated=False)
|
return Stream.objects.filter(realm=realm, deactivated=False)
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_streams(realm: Realm, include_archived_channels: bool = True) -> QuerySet[Stream]:
|
||||||
|
"""
|
||||||
|
Return all streams for `include_archived_channels`= true (including invite-only and deactivated streams).
|
||||||
|
"""
|
||||||
|
if not include_archived_channels:
|
||||||
|
return get_active_streams(realm)
|
||||||
|
|
||||||
|
return Stream.objects.filter(realm=realm)
|
||||||
|
|
||||||
|
|
||||||
def get_linkable_streams(realm_id: int) -> QuerySet[Stream]:
|
def get_linkable_streams(realm_id: int) -> QuerySet[Stream]:
|
||||||
"""
|
"""
|
||||||
This returns the streams that we are allowed to linkify using
|
This returns the streams that we are allowed to linkify using
|
||||||
|
|||||||
@@ -694,6 +694,7 @@ paths:
|
|||||||
{
|
{
|
||||||
"name": "test",
|
"name": "test",
|
||||||
"stream_id": 9,
|
"stream_id": 9,
|
||||||
|
"is_archived": false,
|
||||||
"creator_id": null,
|
"creator_id": null,
|
||||||
"description": "",
|
"description": "",
|
||||||
"rendered_description": "",
|
"rendered_description": "",
|
||||||
@@ -1337,6 +1338,7 @@ paths:
|
|||||||
{
|
{
|
||||||
"name": "private",
|
"name": "private",
|
||||||
"stream_id": 12,
|
"stream_id": 12,
|
||||||
|
"is_archived": false,
|
||||||
"description": "",
|
"description": "",
|
||||||
"rendered_description": "",
|
"rendered_description": "",
|
||||||
"date_created": 1691057093,
|
"date_created": 1691057093,
|
||||||
@@ -1395,6 +1397,7 @@ paths:
|
|||||||
{
|
{
|
||||||
"name": "private",
|
"name": "private",
|
||||||
"stream_id": 12,
|
"stream_id": 12,
|
||||||
|
"is_archived": true,
|
||||||
"description": "",
|
"description": "",
|
||||||
"rendered_description": "",
|
"rendered_description": "",
|
||||||
"date_created": 1691057093,
|
"date_created": 1691057093,
|
||||||
@@ -1994,6 +1997,7 @@ paths:
|
|||||||
{
|
{
|
||||||
"name": "Scotland",
|
"name": "Scotland",
|
||||||
"stream_id": 3,
|
"stream_id": 3,
|
||||||
|
"is_archived": false,
|
||||||
"description": "Located in the United Kingdom",
|
"description": "Located in the United Kingdom",
|
||||||
"rendered_description": "<p>Located in the United Kingdom</p>",
|
"rendered_description": "<p>Located in the United Kingdom</p>",
|
||||||
"date_created": 1691057093,
|
"date_created": 1691057093,
|
||||||
@@ -2010,6 +2014,7 @@ paths:
|
|||||||
{
|
{
|
||||||
"name": "Denmark",
|
"name": "Denmark",
|
||||||
"stream_id": 1,
|
"stream_id": 1,
|
||||||
|
"is_archived": false,
|
||||||
"description": "A Scandinavian country",
|
"description": "A Scandinavian country",
|
||||||
"rendered_description": "<p>A Scandinavian country</p>",
|
"rendered_description": "<p>A Scandinavian country</p>",
|
||||||
"date_created": 1691057093,
|
"date_created": 1691057093,
|
||||||
@@ -2026,6 +2031,7 @@ paths:
|
|||||||
{
|
{
|
||||||
"name": "Verona",
|
"name": "Verona",
|
||||||
"stream_id": 5,
|
"stream_id": 5,
|
||||||
|
"is_archived": false,
|
||||||
"description": "A city in Italy",
|
"description": "A city in Italy",
|
||||||
"rendered_description": "<p>A city in Italy</p>",
|
"rendered_description": "<p>A city in Italy</p>",
|
||||||
"date_created": 1691057093,
|
"date_created": 1691057093,
|
||||||
@@ -2073,6 +2079,7 @@ paths:
|
|||||||
{
|
{
|
||||||
"name": "Scotland",
|
"name": "Scotland",
|
||||||
"stream_id": 3,
|
"stream_id": 3,
|
||||||
|
"is_archived": false,
|
||||||
"description": "Located in the United Kingdom",
|
"description": "Located in the United Kingdom",
|
||||||
"rendered_description": "<p>Located in the United Kingdom</p>",
|
"rendered_description": "<p>Located in the United Kingdom</p>",
|
||||||
"date_created": 1691057093,
|
"date_created": 1691057093,
|
||||||
@@ -10178,6 +10185,7 @@ paths:
|
|||||||
"creator_id": null,
|
"creator_id": null,
|
||||||
"description": "A Scandinavian country",
|
"description": "A Scandinavian country",
|
||||||
"desktop_notifications": true,
|
"desktop_notifications": true,
|
||||||
|
"is_archived": false,
|
||||||
"is_muted": false,
|
"is_muted": false,
|
||||||
"invite_only": false,
|
"invite_only": false,
|
||||||
"name": "Denmark",
|
"name": "Denmark",
|
||||||
@@ -10192,6 +10200,7 @@ paths:
|
|||||||
"creator_id": 8,
|
"creator_id": 8,
|
||||||
"description": "Located in the United Kingdom",
|
"description": "Located in the United Kingdom",
|
||||||
"desktop_notifications": true,
|
"desktop_notifications": true,
|
||||||
|
"is_archived": false,
|
||||||
"is_muted": false,
|
"is_muted": false,
|
||||||
"invite_only": false,
|
"invite_only": false,
|
||||||
"name": "Scotland",
|
"name": "Scotland",
|
||||||
@@ -14079,6 +14088,15 @@ paths:
|
|||||||
**Changes**: New in Zulip 10.0 (feature level 294). This
|
**Changes**: New in Zulip 10.0 (feature level 294). This
|
||||||
capability is for backwards-compatibility.
|
capability is for backwards-compatibility.
|
||||||
|
|
||||||
|
- `archived_channels`: Boolean for whether the client supports processing
|
||||||
|
[archived channels](/help/archive-a-channel) in the `stream` and
|
||||||
|
`subscription` event types. If `false`, the server will not include data
|
||||||
|
related to archived channels in the `register` response or in events.
|
||||||
|
<br />
|
||||||
|
**Changes**: New in Zulip 10.0 (feature level 315). This allows clients to
|
||||||
|
access archived channels, without breaking backwards-compatibility for
|
||||||
|
existing clients.
|
||||||
|
|
||||||
[help-linkifiers]: /help/add-a-custom-linkifier
|
[help-linkifiers]: /help/add-a-custom-linkifier
|
||||||
[rfc6570]: https://www.rfc-editor.org/rfc/rfc6570.html
|
[rfc6570]: https://www.rfc-editor.org/rfc/rfc6570.html
|
||||||
[events-linkifiers]: /api/get-events#realm_linkifiers
|
[events-linkifiers]: /api/get-events#realm_linkifiers
|
||||||
@@ -14784,6 +14802,7 @@ paths:
|
|||||||
properties:
|
properties:
|
||||||
stream_id: {}
|
stream_id: {}
|
||||||
name: {}
|
name: {}
|
||||||
|
is_archived: {}
|
||||||
description: {}
|
description: {}
|
||||||
date_created: {}
|
date_created: {}
|
||||||
creator_id:
|
creator_id:
|
||||||
@@ -19562,6 +19581,16 @@ paths:
|
|||||||
type: boolean
|
type: boolean
|
||||||
default: true
|
default: true
|
||||||
example: false
|
example: false
|
||||||
|
- name: exclude_archived
|
||||||
|
in: query
|
||||||
|
description: |
|
||||||
|
Whether to exclude archived streams from the results.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 10.0 (feature level 315).
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
example: true
|
||||||
- name: include_all_active
|
- name: include_all_active
|
||||||
in: query
|
in: query
|
||||||
description: |
|
description: |
|
||||||
@@ -19612,6 +19641,7 @@ paths:
|
|||||||
properties:
|
properties:
|
||||||
stream_id: {}
|
stream_id: {}
|
||||||
name: {}
|
name: {}
|
||||||
|
is_archived: {}
|
||||||
description: {}
|
description: {}
|
||||||
date_created: {}
|
date_created: {}
|
||||||
creator_id:
|
creator_id:
|
||||||
@@ -19655,6 +19685,7 @@ paths:
|
|||||||
required:
|
required:
|
||||||
- stream_id
|
- stream_id
|
||||||
- name
|
- name
|
||||||
|
- is_archived
|
||||||
- description
|
- description
|
||||||
- date_created
|
- date_created
|
||||||
- creator_id
|
- creator_id
|
||||||
@@ -19683,6 +19714,7 @@ paths:
|
|||||||
"history_public_to_subscribers": false,
|
"history_public_to_subscribers": false,
|
||||||
"invite_only": true,
|
"invite_only": true,
|
||||||
"is_announcement_only": false,
|
"is_announcement_only": false,
|
||||||
|
"is_archived": false,
|
||||||
"is_default": false,
|
"is_default": false,
|
||||||
"is_web_public": false,
|
"is_web_public": false,
|
||||||
"message_retention_days": null,
|
"message_retention_days": null,
|
||||||
@@ -19701,6 +19733,7 @@ paths:
|
|||||||
"history_public_to_subscribers": true,
|
"history_public_to_subscribers": true,
|
||||||
"invite_only": false,
|
"invite_only": false,
|
||||||
"is_announcement_only": false,
|
"is_announcement_only": false,
|
||||||
|
"is_archived": false,
|
||||||
"is_default": true,
|
"is_default": true,
|
||||||
"is_web_public": false,
|
"is_web_public": false,
|
||||||
"message_retention_days": null,
|
"message_retention_days": null,
|
||||||
@@ -19768,6 +19801,7 @@ paths:
|
|||||||
"creator_id": null,
|
"creator_id": null,
|
||||||
"invite_only": false,
|
"invite_only": false,
|
||||||
"is_announcement_only": false,
|
"is_announcement_only": false,
|
||||||
|
"is_archived": false,
|
||||||
"is_web_public": false,
|
"is_web_public": false,
|
||||||
"message_retention_days": null,
|
"message_retention_days": null,
|
||||||
"name": "Denmark",
|
"name": "Denmark",
|
||||||
@@ -21555,6 +21589,7 @@ components:
|
|||||||
properties:
|
properties:
|
||||||
stream_id: {}
|
stream_id: {}
|
||||||
name: {}
|
name: {}
|
||||||
|
is_archived: {}
|
||||||
description: {}
|
description: {}
|
||||||
date_created: {}
|
date_created: {}
|
||||||
creator_id:
|
creator_id:
|
||||||
@@ -21588,6 +21623,7 @@ components:
|
|||||||
required:
|
required:
|
||||||
- stream_id
|
- stream_id
|
||||||
- name
|
- name
|
||||||
|
- is_archived
|
||||||
- description
|
- description
|
||||||
- date_created
|
- date_created
|
||||||
- creator_id
|
- creator_id
|
||||||
@@ -21608,6 +21644,7 @@ components:
|
|||||||
properties:
|
properties:
|
||||||
stream_id: {}
|
stream_id: {}
|
||||||
name: {}
|
name: {}
|
||||||
|
is_archived: {}
|
||||||
description: {}
|
description: {}
|
||||||
date_created: {}
|
date_created: {}
|
||||||
creator_id:
|
creator_id:
|
||||||
@@ -21626,6 +21663,7 @@ components:
|
|||||||
required:
|
required:
|
||||||
- stream_id
|
- stream_id
|
||||||
- name
|
- name
|
||||||
|
- is_archived
|
||||||
- description
|
- description
|
||||||
- date_created
|
- date_created
|
||||||
- creator_id
|
- creator_id
|
||||||
@@ -21651,6 +21689,13 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
description: |
|
description: |
|
||||||
The name of the channel.
|
The name of the channel.
|
||||||
|
is_archived:
|
||||||
|
type: boolean
|
||||||
|
description: |
|
||||||
|
A boolean indicating whether the channel is [archived](/help/archive-a-channel).
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 10.0 (feature level 315).
|
||||||
|
Previously, this endpoint never returned archived channels.
|
||||||
description:
|
description:
|
||||||
type: string
|
type: string
|
||||||
description: |
|
description: |
|
||||||
@@ -22753,6 +22798,16 @@ components:
|
|||||||
was named `can_remove_subscribers_group_id`.
|
was named `can_remove_subscribers_group_id`.
|
||||||
|
|
||||||
New in Zulip 6.0 (feature level 142).
|
New in Zulip 6.0 (feature level 142).
|
||||||
|
is_archived:
|
||||||
|
type: boolean
|
||||||
|
description: |
|
||||||
|
A boolean indicating whether the channel is [archived](/help/archive-a-channel).
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 10.0 (feature level 315).
|
||||||
|
Previously, subscriptions only included active
|
||||||
|
channels. Note that some endpoints will never return archived
|
||||||
|
channels unless the client declares explicit support for
|
||||||
|
them via the `archived_channels` client capability.
|
||||||
DefaultChannelGroup:
|
DefaultChannelGroup:
|
||||||
type: object
|
type: object
|
||||||
description: |
|
description: |
|
||||||
|
|||||||
@@ -305,6 +305,7 @@ class BaseAction(ZulipTestCase):
|
|||||||
user_list_incomplete: bool = False,
|
user_list_incomplete: bool = False,
|
||||||
client_is_old: bool = False,
|
client_is_old: bool = False,
|
||||||
include_deactivated_groups: bool = False,
|
include_deactivated_groups: bool = False,
|
||||||
|
archived_channels: bool = False,
|
||||||
) -> Iterator[list[dict[str, Any]]]:
|
) -> Iterator[list[dict[str, Any]]]:
|
||||||
"""
|
"""
|
||||||
Make sure we have a clean slate of client descriptors for these tests.
|
Make sure we have a clean slate of client descriptors for these tests.
|
||||||
@@ -354,6 +355,7 @@ class BaseAction(ZulipTestCase):
|
|||||||
linkifier_url_template=linkifier_url_template,
|
linkifier_url_template=linkifier_url_template,
|
||||||
user_list_incomplete=user_list_incomplete,
|
user_list_incomplete=user_list_incomplete,
|
||||||
include_deactivated_groups=include_deactivated_groups,
|
include_deactivated_groups=include_deactivated_groups,
|
||||||
|
archived_channels=archived_channels,
|
||||||
)
|
)
|
||||||
|
|
||||||
if client_is_old:
|
if client_is_old:
|
||||||
@@ -395,6 +397,7 @@ class BaseAction(ZulipTestCase):
|
|||||||
linkifier_url_template=linkifier_url_template,
|
linkifier_url_template=linkifier_url_template,
|
||||||
user_list_incomplete=user_list_incomplete,
|
user_list_incomplete=user_list_incomplete,
|
||||||
include_deactivated_groups=include_deactivated_groups,
|
include_deactivated_groups=include_deactivated_groups,
|
||||||
|
archived_channels=archived_channels,
|
||||||
)
|
)
|
||||||
post_process_state(self.user_profile, hybrid_state, notification_settings_null)
|
post_process_state(self.user_profile, hybrid_state, notification_settings_null)
|
||||||
after = orjson.dumps(hybrid_state)
|
after = orjson.dumps(hybrid_state)
|
||||||
@@ -425,6 +428,7 @@ class BaseAction(ZulipTestCase):
|
|||||||
linkifier_url_template=linkifier_url_template,
|
linkifier_url_template=linkifier_url_template,
|
||||||
user_list_incomplete=user_list_incomplete,
|
user_list_incomplete=user_list_incomplete,
|
||||||
include_deactivated_groups=include_deactivated_groups,
|
include_deactivated_groups=include_deactivated_groups,
|
||||||
|
archived_channels=archived_channels,
|
||||||
)
|
)
|
||||||
post_process_state(self.user_profile, normal_state, notification_settings_null)
|
post_process_state(self.user_profile, normal_state, notification_settings_null)
|
||||||
self.match_states(hybrid_state, normal_state, events)
|
self.match_states(hybrid_state, normal_state, events)
|
||||||
@@ -3355,6 +3359,23 @@ class NormalActionsTest(BaseAction):
|
|||||||
check_stream_delete("events[0]", events[0])
|
check_stream_delete("events[0]", events[0])
|
||||||
self.assertIsNone(events[0]["streams"][0]["stream_weekly_traffic"])
|
self.assertIsNone(events[0]["streams"][0]["stream_weekly_traffic"])
|
||||||
|
|
||||||
|
def test_admin_deactivate_unsubscribed_stream(self) -> None:
|
||||||
|
self.set_up_db_for_testing_user_access()
|
||||||
|
stream = self.make_stream("test_stream")
|
||||||
|
iago = self.example_user("iago")
|
||||||
|
realm = iago.realm
|
||||||
|
self.user_profile = self.example_user("iago")
|
||||||
|
|
||||||
|
self.subscribe(iago, stream.name)
|
||||||
|
self.assertCountEqual(self.users_subscribed_to_stream(stream.name, realm), [iago])
|
||||||
|
|
||||||
|
self.unsubscribe(iago, stream.name)
|
||||||
|
self.assertCountEqual(self.users_subscribed_to_stream(stream.name, realm), [])
|
||||||
|
|
||||||
|
with self.verify_action(num_events=1, archived_channels=True) as events:
|
||||||
|
do_deactivate_stream(stream, acting_user=iago)
|
||||||
|
check_stream_delete("events[0]", events[0])
|
||||||
|
|
||||||
def test_user_losing_access_on_deactivating_stream(self) -> None:
|
def test_user_losing_access_on_deactivating_stream(self) -> None:
|
||||||
self.set_up_db_for_testing_user_access()
|
self.set_up_db_for_testing_user_access()
|
||||||
polonius = self.example_user("polonius")
|
polonius = self.example_user("polonius")
|
||||||
@@ -3367,7 +3388,7 @@ class NormalActionsTest(BaseAction):
|
|||||||
self.users_subscribed_to_stream(stream.name, realm), [hamlet, polonius]
|
self.users_subscribed_to_stream(stream.name, realm), [hamlet, polonius]
|
||||||
)
|
)
|
||||||
|
|
||||||
with self.verify_action(num_events=2) as events:
|
with self.verify_action(num_events=2, archived_channels=True) as events:
|
||||||
do_deactivate_stream(stream, acting_user=None)
|
do_deactivate_stream(stream, acting_user=None)
|
||||||
check_stream_delete("events[0]", events[0])
|
check_stream_delete("events[0]", events[0])
|
||||||
check_realm_user_remove("events[1]", events[1])
|
check_realm_user_remove("events[1]", events[1])
|
||||||
@@ -3383,7 +3404,7 @@ class NormalActionsTest(BaseAction):
|
|||||||
self.users_subscribed_to_stream(stream.name, realm), [iago, polonius, shiva]
|
self.users_subscribed_to_stream(stream.name, realm), [iago, polonius, shiva]
|
||||||
)
|
)
|
||||||
|
|
||||||
with self.verify_action(num_events=2) as events:
|
with self.verify_action(num_events=2, archived_channels=True) as events:
|
||||||
do_deactivate_stream(stream, acting_user=None)
|
do_deactivate_stream(stream, acting_user=None)
|
||||||
check_stream_delete("events[0]", events[0])
|
check_stream_delete("events[0]", events[0])
|
||||||
check_realm_user_remove("events[1]", events[1])
|
check_realm_user_remove("events[1]", events[1])
|
||||||
|
|||||||
@@ -648,7 +648,7 @@ class TestOutgoingWebhookMessaging(ZulipTestCase):
|
|||||||
|
|
||||||
prev_message = self.get_second_to_last_message()
|
prev_message = self.get_second_to_last_message()
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
"tried to send a message to channel #**Denmark**, but that channel does not exist",
|
"Failure! Bot is unavailable",
|
||||||
prev_message.content,
|
prev_message.content,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -238,8 +238,8 @@ class TestMiscStuff(ZulipTestCase):
|
|||||||
"""Verify that all the fields from `Stream.API_FIELDS` and `Subscription.API_FIELDS` present
|
"""Verify that all the fields from `Stream.API_FIELDS` and `Subscription.API_FIELDS` present
|
||||||
in `APIStreamDict` and `APISubscriptionDict`, respectively.
|
in `APIStreamDict` and `APISubscriptionDict`, respectively.
|
||||||
"""
|
"""
|
||||||
expected_fields = set(Stream.API_FIELDS) | {"stream_id"}
|
expected_fields = set(Stream.API_FIELDS) | {"stream_id", "is_archived"}
|
||||||
expected_fields -= {"id", "can_remove_subscribers_group_id"}
|
expected_fields -= {"id", "can_remove_subscribers_group_id", "deactivated"}
|
||||||
expected_fields |= {"can_remove_subscribers_group"}
|
expected_fields |= {"can_remove_subscribers_group"}
|
||||||
|
|
||||||
stream_dict_fields = set(APIStreamDict.__annotations__.keys())
|
stream_dict_fields = set(APIStreamDict.__annotations__.keys())
|
||||||
@@ -2508,6 +2508,13 @@ class StreamAdminTest(ZulipTestCase):
|
|||||||
public_streams = [s["name"] for s in self.assert_json_success(result)["streams"]]
|
public_streams = [s["name"] for s in self.assert_json_success(result)["streams"]]
|
||||||
self.assertNotIn(deactivated_stream_name, public_streams)
|
self.assertNotIn(deactivated_stream_name, public_streams)
|
||||||
|
|
||||||
|
# It shows up with `exclude_archived` parameter set to false.
|
||||||
|
result = self.client_get(
|
||||||
|
"/json/streams", {"exclude_archived": "false", "include_all_active": "true"}
|
||||||
|
)
|
||||||
|
streams = [s["name"] for s in self.assert_json_success(result)["streams"]]
|
||||||
|
self.assertIn(deactivated_stream_name, streams)
|
||||||
|
|
||||||
# You can't subscribe to archived stream.
|
# You can't subscribe to archived stream.
|
||||||
result = self.common_subscribe_to_streams(
|
result = self.common_subscribe_to_streams(
|
||||||
self.example_user("hamlet"), [deactivated_stream_name], allow_fail=True
|
self.example_user("hamlet"), [deactivated_stream_name], allow_fail=True
|
||||||
@@ -5464,10 +5471,10 @@ class SubscriptionAPITest(ZulipTestCase):
|
|||||||
self.assert_length(result, 1)
|
self.assert_length(result, 1)
|
||||||
self.assertEqual(result[0]["stream_id"], stream1.id)
|
self.assertEqual(result[0]["stream_id"], stream1.id)
|
||||||
|
|
||||||
def test_gather_subscriptions_excludes_deactivated_streams(self) -> None:
|
def test_gather_subscriptions_deactivated_streams(self) -> None:
|
||||||
"""
|
"""
|
||||||
Check that gather_subscriptions_helper does not include deactivated streams in its
|
Check that gather_subscriptions_helper does/doesn't include deactivated streams in its
|
||||||
results.
|
results with `exclude_archived` parameter.
|
||||||
"""
|
"""
|
||||||
realm = get_realm("zulip")
|
realm = get_realm("zulip")
|
||||||
admin_user = self.example_user("iago")
|
admin_user = self.example_user("iago")
|
||||||
@@ -5497,6 +5504,10 @@ class SubscriptionAPITest(ZulipTestCase):
|
|||||||
admin_after_delete = gather_subscriptions_helper(admin_user)
|
admin_after_delete = gather_subscriptions_helper(admin_user)
|
||||||
non_admin_after_delete = gather_subscriptions_helper(non_admin_user)
|
non_admin_after_delete = gather_subscriptions_helper(non_admin_user)
|
||||||
|
|
||||||
|
admin_after_delete_include_archived = gather_subscriptions_helper(
|
||||||
|
admin_user, include_archived_channels=True
|
||||||
|
)
|
||||||
|
|
||||||
# Compare results - should be 1 stream less
|
# Compare results - should be 1 stream less
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
len(admin_before_delete.subscriptions) == len(admin_after_delete.subscriptions) + 1,
|
len(admin_before_delete.subscriptions) == len(admin_after_delete.subscriptions) + 1,
|
||||||
@@ -5508,6 +5519,14 @@ class SubscriptionAPITest(ZulipTestCase):
|
|||||||
"Expected exactly 1 less stream from gather_subscriptions_helper",
|
"Expected exactly 1 less stream from gather_subscriptions_helper",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Compare results - should be the same number of streams
|
||||||
|
self.assertTrue(
|
||||||
|
len(admin_before_delete.subscriptions) + len(admin_before_delete.unsubscribed)
|
||||||
|
== len(admin_after_delete_include_archived.subscriptions)
|
||||||
|
+ len(admin_after_delete_include_archived.unsubscribed),
|
||||||
|
"Expected exact number of streams from gather_subscriptions_helper",
|
||||||
|
)
|
||||||
|
|
||||||
def test_validate_user_access_to_subscribers_helper(self) -> None:
|
def test_validate_user_access_to_subscribers_helper(self) -> None:
|
||||||
"""
|
"""
|
||||||
Ensure the validate_user_access_to_subscribers_helper is properly raising
|
Ensure the validate_user_access_to_subscribers_helper is properly raising
|
||||||
@@ -5944,6 +5963,7 @@ class GetSubscribersTest(ZulipTestCase):
|
|||||||
|
|
||||||
def verify_sub_fields(self, sub_data: SubscriptionInfo) -> None:
|
def verify_sub_fields(self, sub_data: SubscriptionInfo) -> None:
|
||||||
other_fields = {
|
other_fields = {
|
||||||
|
"is_archived",
|
||||||
"is_announcement_only",
|
"is_announcement_only",
|
||||||
"in_home_view",
|
"in_home_view",
|
||||||
"stream_id",
|
"stream_id",
|
||||||
@@ -5952,7 +5972,7 @@ class GetSubscribersTest(ZulipTestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
expected_fields = set(Stream.API_FIELDS) | set(Subscription.API_FIELDS) | other_fields
|
expected_fields = set(Stream.API_FIELDS) | set(Subscription.API_FIELDS) | other_fields
|
||||||
expected_fields -= {"id", "can_remove_subscribers_group_id"}
|
expected_fields -= {"id", "can_remove_subscribers_group_id", "deactivated"}
|
||||||
expected_fields |= {"can_remove_subscribers_group"}
|
expected_fields |= {"can_remove_subscribers_group"}
|
||||||
|
|
||||||
for lst in [sub_data.subscriptions, sub_data.unsubscribed]:
|
for lst in [sub_data.subscriptions, sub_data.unsubscribed]:
|
||||||
@@ -5960,6 +5980,7 @@ class GetSubscribersTest(ZulipTestCase):
|
|||||||
self.assertEqual(set(sub), expected_fields)
|
self.assertEqual(set(sub), expected_fields)
|
||||||
|
|
||||||
other_fields = {
|
other_fields = {
|
||||||
|
"is_archived",
|
||||||
"is_announcement_only",
|
"is_announcement_only",
|
||||||
"stream_id",
|
"stream_id",
|
||||||
"stream_weekly_traffic",
|
"stream_weekly_traffic",
|
||||||
@@ -5967,7 +5988,7 @@ class GetSubscribersTest(ZulipTestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
expected_fields = set(Stream.API_FIELDS) | other_fields
|
expected_fields = set(Stream.API_FIELDS) | other_fields
|
||||||
expected_fields -= {"id", "can_remove_subscribers_group_id"}
|
expected_fields -= {"id", "can_remove_subscribers_group_id", "deactivated"}
|
||||||
expected_fields |= {"can_remove_subscribers_group"}
|
expected_fields |= {"can_remove_subscribers_group"}
|
||||||
|
|
||||||
for never_sub in sub_data.never_subscribed:
|
for never_sub in sub_data.never_subscribed:
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ def request_event_queue(
|
|||||||
linkifier_url_template: bool = False,
|
linkifier_url_template: bool = False,
|
||||||
user_list_incomplete: bool = False,
|
user_list_incomplete: bool = False,
|
||||||
include_deactivated_groups: bool = False,
|
include_deactivated_groups: bool = False,
|
||||||
|
archived_channels: bool = False,
|
||||||
) -> str | None:
|
) -> str | None:
|
||||||
if not settings.USING_TORNADO:
|
if not settings.USING_TORNADO:
|
||||||
return None
|
return None
|
||||||
@@ -115,6 +116,7 @@ def request_event_queue(
|
|||||||
"linkifier_url_template": orjson.dumps(linkifier_url_template),
|
"linkifier_url_template": orjson.dumps(linkifier_url_template),
|
||||||
"user_list_incomplete": orjson.dumps(user_list_incomplete),
|
"user_list_incomplete": orjson.dumps(user_list_incomplete),
|
||||||
"include_deactivated_groups": orjson.dumps(include_deactivated_groups),
|
"include_deactivated_groups": orjson.dumps(include_deactivated_groups),
|
||||||
|
"archived_channels": orjson.dumps(archived_channels),
|
||||||
}
|
}
|
||||||
|
|
||||||
if event_types is not None:
|
if event_types is not None:
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ class ClientDescriptor:
|
|||||||
linkifier_url_template: bool = False,
|
linkifier_url_template: bool = False,
|
||||||
user_list_incomplete: bool = False,
|
user_list_incomplete: bool = False,
|
||||||
include_deactivated_groups: bool = False,
|
include_deactivated_groups: bool = False,
|
||||||
|
archived_channels: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
# TODO: We eventually want to upstream this code to the caller, but
|
# TODO: We eventually want to upstream this code to the caller, but
|
||||||
# serialization concerns make it a bit difficult.
|
# serialization concerns make it a bit difficult.
|
||||||
@@ -110,6 +111,7 @@ class ClientDescriptor:
|
|||||||
self.linkifier_url_template = linkifier_url_template
|
self.linkifier_url_template = linkifier_url_template
|
||||||
self.user_list_incomplete = user_list_incomplete
|
self.user_list_incomplete = user_list_incomplete
|
||||||
self.include_deactivated_groups = include_deactivated_groups
|
self.include_deactivated_groups = include_deactivated_groups
|
||||||
|
self.archived_channels = archived_channels
|
||||||
|
|
||||||
# Default for lifespan_secs is DEFAULT_EVENT_QUEUE_TIMEOUT_SECS;
|
# Default for lifespan_secs is DEFAULT_EVENT_QUEUE_TIMEOUT_SECS;
|
||||||
# but users can set it as high as MAX_QUEUE_TIMEOUT_SECS.
|
# but users can set it as high as MAX_QUEUE_TIMEOUT_SECS.
|
||||||
@@ -141,6 +143,7 @@ class ClientDescriptor:
|
|||||||
linkifier_url_template=self.linkifier_url_template,
|
linkifier_url_template=self.linkifier_url_template,
|
||||||
user_list_incomplete=self.user_list_incomplete,
|
user_list_incomplete=self.user_list_incomplete,
|
||||||
include_deactivated_groups=self.include_deactivated_groups,
|
include_deactivated_groups=self.include_deactivated_groups,
|
||||||
|
archived_channels=self.archived_channels,
|
||||||
)
|
)
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -178,6 +181,7 @@ class ClientDescriptor:
|
|||||||
d.get("linkifier_url_template", False),
|
d.get("linkifier_url_template", False),
|
||||||
d.get("user_list_incomplete", False),
|
d.get("user_list_incomplete", False),
|
||||||
d.get("include_deactivated_groups", False),
|
d.get("include_deactivated_groups", False),
|
||||||
|
d.get("archived_channels", False),
|
||||||
)
|
)
|
||||||
ret.last_connection_time = d["last_connection_time"]
|
ret.last_connection_time = d["last_connection_time"]
|
||||||
return ret
|
return ret
|
||||||
|
|||||||
@@ -210,6 +210,10 @@ def get_events_backend(
|
|||||||
Json[bool],
|
Json[bool],
|
||||||
ApiParamConfig(documentation_status=DocumentationStatus.INTENTIONALLY_UNDOCUMENTED),
|
ApiParamConfig(documentation_status=DocumentationStatus.INTENTIONALLY_UNDOCUMENTED),
|
||||||
] = False,
|
] = False,
|
||||||
|
archived_channels: Annotated[
|
||||||
|
Json[bool],
|
||||||
|
ApiParamConfig(documentation_status=DocumentationStatus.INTENTIONALLY_UNDOCUMENTED),
|
||||||
|
] = False,
|
||||||
) -> HttpResponse:
|
) -> HttpResponse:
|
||||||
if narrow is None:
|
if narrow is None:
|
||||||
narrow = []
|
narrow = []
|
||||||
@@ -248,6 +252,7 @@ def get_events_backend(
|
|||||||
linkifier_url_template=linkifier_url_template,
|
linkifier_url_template=linkifier_url_template,
|
||||||
user_list_incomplete=user_list_incomplete,
|
user_list_incomplete=user_list_incomplete,
|
||||||
include_deactivated_groups=include_deactivated_groups,
|
include_deactivated_groups=include_deactivated_groups,
|
||||||
|
archived_channels=archived_channels,
|
||||||
)
|
)
|
||||||
|
|
||||||
result = in_tornado_thread(fetch_events)(
|
result = in_tornado_thread(fetch_events)(
|
||||||
|
|||||||
@@ -847,6 +847,7 @@ def get_streams_backend(
|
|||||||
include_public: Json[bool] = True,
|
include_public: Json[bool] = True,
|
||||||
include_web_public: Json[bool] = False,
|
include_web_public: Json[bool] = False,
|
||||||
include_subscribed: Json[bool] = True,
|
include_subscribed: Json[bool] = True,
|
||||||
|
exclude_archived: Json[bool] = True,
|
||||||
include_all_active: Json[bool] = False,
|
include_all_active: Json[bool] = False,
|
||||||
include_default: Json[bool] = False,
|
include_default: Json[bool] = False,
|
||||||
include_owner_subscribed: Json[bool] = False,
|
include_owner_subscribed: Json[bool] = False,
|
||||||
@@ -856,6 +857,7 @@ def get_streams_backend(
|
|||||||
include_public=include_public,
|
include_public=include_public,
|
||||||
include_web_public=include_web_public,
|
include_web_public=include_web_public,
|
||||||
include_subscribed=include_subscribed,
|
include_subscribed=include_subscribed,
|
||||||
|
exclude_archived=exclude_archived,
|
||||||
include_all_active=include_all_active,
|
include_all_active=include_all_active,
|
||||||
include_default=include_default,
|
include_default=include_default,
|
||||||
include_owner_subscribed=include_owner_subscribed,
|
include_owner_subscribed=include_owner_subscribed,
|
||||||
|
|||||||
Reference in New Issue
Block a user