mirror of
https://github.com/zulip/zulip.git
synced 2025-11-11 09:27:43 +00:00
streams: Fix events send when archiving and unarchiving streams.
(cherry picked from commit 7c470f0161)
This commit is contained in:
5
api_docs/unmerged.d/ZF-960986
Normal file
5
api_docs/unmerged.d/ZF-960986
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
* [`GET /events`](/api/get-events): Archiving and unarchiving
|
||||||
|
streams now send `update` events to clients that declared
|
||||||
|
the `archived_channels` client capability. `delete` and `create`
|
||||||
|
events are still sent to clients that did not declare
|
||||||
|
`archived_channels` client capability.
|
||||||
@@ -159,7 +159,17 @@ def do_deactivate_stream(stream: Stream, *, acting_user: UserProfile | None) ->
|
|||||||
for group in default_stream_groups_for_stream:
|
for group in default_stream_groups_for_stream:
|
||||||
do_remove_streams_from_default_stream_group(stream.realm, group, [stream])
|
do_remove_streams_from_default_stream_group(stream.realm, group, [stream])
|
||||||
|
|
||||||
send_stream_deletion_event(stream.realm, affected_user_ids, [stream])
|
event = dict(
|
||||||
|
type="stream",
|
||||||
|
op="update",
|
||||||
|
stream_id=stream.id,
|
||||||
|
name=stream.name,
|
||||||
|
property="is_archived",
|
||||||
|
value=True,
|
||||||
|
)
|
||||||
|
send_event_on_commit(stream.realm, event, affected_user_ids)
|
||||||
|
|
||||||
|
send_stream_deletion_event(stream.realm, affected_user_ids, [stream], for_archiving=True)
|
||||||
|
|
||||||
event_time = timezone_now()
|
event_time = timezone_now()
|
||||||
RealmAuditLog.objects.create(
|
RealmAuditLog.objects.create(
|
||||||
@@ -271,9 +281,25 @@ def do_unarchive_stream(stream: Stream, new_name: str, *, acting_user: UserProfi
|
|||||||
recent_traffic = get_streams_traffic({stream.id}, realm)
|
recent_traffic = get_streams_traffic({stream.id}, realm)
|
||||||
|
|
||||||
notify_user_ids = list(can_access_stream_metadata_user_ids(stream))
|
notify_user_ids = list(can_access_stream_metadata_user_ids(stream))
|
||||||
|
|
||||||
|
event = dict(
|
||||||
|
type="stream",
|
||||||
|
op="update",
|
||||||
|
stream_id=stream.id,
|
||||||
|
name=stream.name,
|
||||||
|
property="is_archived",
|
||||||
|
value=False,
|
||||||
|
)
|
||||||
|
send_event_on_commit(stream.realm, event, notify_user_ids)
|
||||||
|
|
||||||
anonymous_group_membership = get_anonymous_group_membership_dict_for_streams([stream])
|
anonymous_group_membership = get_anonymous_group_membership_dict_for_streams([stream])
|
||||||
send_stream_creation_event(
|
send_stream_creation_event(
|
||||||
realm, stream, notify_user_ids, recent_traffic, anonymous_group_membership
|
realm,
|
||||||
|
stream,
|
||||||
|
notify_user_ids,
|
||||||
|
recent_traffic,
|
||||||
|
anonymous_group_membership,
|
||||||
|
for_unarchiving=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
sender = get_system_bot(settings.NOTIFICATION_BOT, stream.realm_id)
|
sender = get_system_bot(settings.NOTIFICATION_BOT, stream.realm_id)
|
||||||
|
|||||||
@@ -529,6 +529,7 @@ def check_stream_update(
|
|||||||
"name",
|
"name",
|
||||||
"stream_id",
|
"stream_id",
|
||||||
"first_message_id",
|
"first_message_id",
|
||||||
|
"is_archived",
|
||||||
}
|
}
|
||||||
|
|
||||||
if prop == "description":
|
if prop == "description":
|
||||||
@@ -565,6 +566,9 @@ def check_stream_update(
|
|||||||
elif prop == "is_announcement_only":
|
elif prop == "is_announcement_only":
|
||||||
assert extra_keys == set()
|
assert extra_keys == set()
|
||||||
assert isinstance(value, bool)
|
assert isinstance(value, bool)
|
||||||
|
elif prop == "is_archived":
|
||||||
|
assert extra_keys == set()
|
||||||
|
assert isinstance(value, bool)
|
||||||
else:
|
else:
|
||||||
raise AssertionError(f"Unknown property: {prop}")
|
raise AssertionError(f"Unknown property: {prop}")
|
||||||
|
|
||||||
|
|||||||
@@ -1309,71 +1309,46 @@ def apply_event(
|
|||||||
if event["op"] == "delete":
|
if event["op"] == "delete":
|
||||||
deleted_stream_ids = {stream["stream_id"] for stream in event["streams"]}
|
deleted_stream_ids = {stream["stream_id"] for stream in event["streams"]}
|
||||||
|
|
||||||
updated_first_message_ids: dict[int, int] = dict()
|
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
|
]
|
||||||
|
|
||||||
for stream in state["unsubscribed"]:
|
state["unsubscribed"] = [
|
||||||
if stream["stream_id"] in deleted_stream_ids:
|
stream
|
||||||
stream["is_archived"] = True
|
for stream in state["unsubscribed"]
|
||||||
if stream["first_message_id"] is None:
|
if stream["stream_id"] not in deleted_stream_ids
|
||||||
new_first_message_id = Stream.objects.get(
|
]
|
||||||
id=stream["stream_id"]
|
|
||||||
).first_message_id
|
|
||||||
assert new_first_message_id is not None
|
|
||||||
stream["first_message_id"] = new_first_message_id
|
|
||||||
updated_first_message_ids[stream["stream_id"]] = new_first_message_id
|
|
||||||
|
|
||||||
for stream in state["never_subscribed"]:
|
state["never_subscribed"] = [
|
||||||
if stream["stream_id"] in deleted_stream_ids:
|
stream
|
||||||
stream["is_archived"] = True
|
for stream in state["never_subscribed"]
|
||||||
if stream["first_message_id"] is None:
|
if stream["stream_id"] not in deleted_stream_ids
|
||||||
new_first_message_id = Stream.objects.get(
|
]
|
||||||
id=stream["stream_id"]
|
|
||||||
).first_message_id
|
|
||||||
assert new_first_message_id is not None
|
|
||||||
stream["first_message_id"] = new_first_message_id
|
|
||||||
updated_first_message_ids[stream["stream_id"]] = new_first_message_id
|
|
||||||
|
|
||||||
if "streams" in state:
|
if "streams" in state:
|
||||||
for stream in state["streams"]:
|
state["streams"] = [
|
||||||
if stream["stream_id"] in deleted_stream_ids:
|
s for s in state["streams"] if s["stream_id"] not in deleted_stream_ids
|
||||||
stream["is_archived"] = True
|
|
||||||
if stream["stream_id"] in updated_first_message_ids:
|
|
||||||
stream["first_message_id"] = updated_first_message_ids[
|
|
||||||
stream["stream_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"] = [
|
|
||||||
stream
|
|
||||||
for stream in state["never_subscribed"]
|
|
||||||
if stream["stream_id"] not in deleted_stream_ids
|
|
||||||
]
|
|
||||||
|
|
||||||
if "streams" in state:
|
|
||||||
state["streams"] = [
|
|
||||||
s for s in state["streams"] if s["stream_id"] not in deleted_stream_ids
|
|
||||||
]
|
|
||||||
|
|
||||||
if event["op"] == "update":
|
if event["op"] == "update":
|
||||||
# For legacy reasons, we call stream data 'subscriptions' in
|
# For legacy reasons, we call stream data 'subscriptions' in
|
||||||
# the state var here, for the benefit of the JS code.
|
# the state var here, for the benefit of the JS code.
|
||||||
|
for obj in state["subscriptions"]:
|
||||||
|
if obj["name"].lower() == event["name"].lower():
|
||||||
|
obj[event["property"]] = event["value"]
|
||||||
|
if event["property"] == "description":
|
||||||
|
obj["rendered_description"] = event["rendered_description"]
|
||||||
|
if event.get("history_public_to_subscribers") is not None:
|
||||||
|
obj["history_public_to_subscribers"] = event[
|
||||||
|
"history_public_to_subscribers"
|
||||||
|
]
|
||||||
|
if event.get("is_web_public") is not None:
|
||||||
|
obj["is_web_public"] = event["is_web_public"]
|
||||||
|
|
||||||
|
updated_first_message_ids = dict()
|
||||||
for sub_list in [
|
for sub_list in [
|
||||||
state["subscriptions"],
|
|
||||||
state["unsubscribed"],
|
state["unsubscribed"],
|
||||||
state["never_subscribed"],
|
state["never_subscribed"],
|
||||||
]:
|
]:
|
||||||
@@ -1388,6 +1363,17 @@ def apply_event(
|
|||||||
]
|
]
|
||||||
if event.get("is_web_public") is not None:
|
if event.get("is_web_public") is not None:
|
||||||
obj["is_web_public"] = event["is_web_public"]
|
obj["is_web_public"] = event["is_web_public"]
|
||||||
|
if (
|
||||||
|
event["property"] == "is_archived"
|
||||||
|
and event["value"]
|
||||||
|
and obj["first_message_id"] is None
|
||||||
|
):
|
||||||
|
new_first_message_id = Stream.objects.get(
|
||||||
|
id=obj["stream_id"]
|
||||||
|
).first_message_id
|
||||||
|
assert new_first_message_id is not None
|
||||||
|
obj["first_message_id"] = new_first_message_id
|
||||||
|
updated_first_message_ids[obj["stream_id"]] = new_first_message_id
|
||||||
# Also update the pure streams data
|
# Also update the pure streams data
|
||||||
if "streams" in state:
|
if "streams" in state:
|
||||||
for stream in state["streams"]:
|
for stream in state["streams"]:
|
||||||
@@ -1403,6 +1389,13 @@ def apply_event(
|
|||||||
]
|
]
|
||||||
if event.get("is_web_public") is not None:
|
if event.get("is_web_public") is not None:
|
||||||
stream["is_web_public"] = event["is_web_public"]
|
stream["is_web_public"] = event["is_web_public"]
|
||||||
|
if (
|
||||||
|
event["property"] == "is_archived"
|
||||||
|
and stream["stream_id"] in updated_first_message_ids
|
||||||
|
):
|
||||||
|
stream["first_message_id"] = updated_first_message_ids[
|
||||||
|
stream["stream_id"]
|
||||||
|
]
|
||||||
|
|
||||||
elif event["type"] == "default_streams":
|
elif event["type"] == "default_streams":
|
||||||
state["realm_default_streams"] = event["default_streams"]
|
state["realm_default_streams"] = event["default_streams"]
|
||||||
|
|||||||
@@ -148,11 +148,13 @@ def send_stream_creation_event(
|
|||||||
user_ids: list[int],
|
user_ids: list[int],
|
||||||
recent_traffic: dict[int, int] | None = None,
|
recent_traffic: dict[int, int] | None = None,
|
||||||
anonymous_group_membership: dict[int, UserGroupMembersData] | None = None,
|
anonymous_group_membership: dict[int, UserGroupMembersData] | None = None,
|
||||||
|
for_unarchiving: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
event = dict(
|
event = dict(
|
||||||
type="stream",
|
type="stream",
|
||||||
op="create",
|
op="create",
|
||||||
streams=[stream_to_dict(stream, recent_traffic, anonymous_group_membership)],
|
streams=[stream_to_dict(stream, recent_traffic, anonymous_group_membership)],
|
||||||
|
for_unarchiving=for_unarchiving,
|
||||||
)
|
)
|
||||||
send_event_on_commit(realm, event, user_ids)
|
send_event_on_commit(realm, event, user_ids)
|
||||||
|
|
||||||
@@ -1753,7 +1755,7 @@ def check_update_all_streams_active_status(
|
|||||||
|
|
||||||
|
|
||||||
def send_stream_deletion_event(
|
def send_stream_deletion_event(
|
||||||
realm: Realm, user_ids: Iterable[int], streams: list[Stream]
|
realm: Realm, user_ids: Iterable[int], streams: list[Stream], for_archiving: bool = False
|
||||||
) -> None:
|
) -> None:
|
||||||
stream_deletion_event = dict(
|
stream_deletion_event = dict(
|
||||||
type="stream",
|
type="stream",
|
||||||
@@ -1761,6 +1763,7 @@ def send_stream_deletion_event(
|
|||||||
# "streams" is deprecated, kept only for compatibility.
|
# "streams" is deprecated, kept only for compatibility.
|
||||||
streams=[dict(stream_id=stream.id) for stream in streams],
|
streams=[dict(stream_id=stream.id) for stream in streams],
|
||||||
stream_ids=[stream.id for stream in streams],
|
stream_ids=[stream.id for stream in streams],
|
||||||
|
for_archiving=for_archiving,
|
||||||
)
|
)
|
||||||
send_event_on_commit(realm, stream_deletion_event, user_ids)
|
send_event_on_commit(realm, stream_deletion_event, user_ids)
|
||||||
|
|
||||||
|
|||||||
@@ -1286,11 +1286,19 @@ paths:
|
|||||||
private channel is made public, or a guest user is subscribed
|
private channel is made public, or a guest user is subscribed
|
||||||
to a public (or private) channel.
|
to a public (or private) channel.
|
||||||
|
|
||||||
|
This event is also sent when a channel is unarchived but only
|
||||||
|
to clients that did not declare the `archived_channels` [client
|
||||||
|
capability][client-capabilities].
|
||||||
|
|
||||||
Note that organization administrators who are not subscribed will
|
Note that organization administrators who are not subscribed will
|
||||||
not be able to see content on the channel; just that it exists.
|
not be able to see content on the channel; just that it exists.
|
||||||
|
|
||||||
**Changes**: Prior to Zulip 8.0 (feature level 220), this event was
|
**Changes**: Prior to Zulip 11.0 (feature level ZF-960986), this
|
||||||
incorrectly not sent to guest users a web-public channel was created.
|
event was sent to all the users who could see the channel when it
|
||||||
|
was unarchived.
|
||||||
|
|
||||||
|
Prior to Zulip 8.0 (feature level 220), this event was incorrectly
|
||||||
|
not sent to guest users a web-public channel was created.
|
||||||
|
|
||||||
Prior to Zulip 8.0 (feature level 205), this event was not sent
|
Prior to Zulip 8.0 (feature level 205), this event was not sent
|
||||||
when a user gained access to a channel due to their role changing.
|
when a user gained access to a channel due to their role changing.
|
||||||
@@ -1353,16 +1361,23 @@ paths:
|
|||||||
}
|
}
|
||||||
- type: object
|
- type: object
|
||||||
description: |
|
description: |
|
||||||
Event sent to all users who can see a channel when it is deactivated.
|
Event sent when a user loses access to a channel they previously
|
||||||
|
[could access](/help/channel-permissions) because they are
|
||||||
This event is also sent when a user loses access to a channel they
|
unsubscribed from a private channel or their [role](/help/user-roles)
|
||||||
previously [could access](/help/channel-permissions) because they
|
|
||||||
are unsubscribed from a private channel or their [role](/help/user-roles)
|
|
||||||
has changed.
|
has changed.
|
||||||
|
|
||||||
**Changes**: Prior to Zulip 8.0 (feature level 205), this event was
|
This event is also sent when a channel is archived but only
|
||||||
not sent when a user lost access to a channel due to their role
|
to clients that did not declare the `archived_channels` [client
|
||||||
changing.
|
capability][client-capabilities].
|
||||||
|
|
||||||
|
**Changes**: Prior to Zulip 11.0 (feature level ZF-960986), this
|
||||||
|
event was sent to all the users who could see the channel when it
|
||||||
|
was archived.
|
||||||
|
|
||||||
|
Prior to Zulip 8.0 (feature level 205), this event was not sent
|
||||||
|
when a user lost access to a channel due to their role changing.
|
||||||
|
|
||||||
|
[client-capabilities]: /api/register-queue#parameter-client_capabilities
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
$ref: "#/components/schemas/EventIdSchema"
|
$ref: "#/components/schemas/EventIdSchema"
|
||||||
@@ -1419,10 +1434,21 @@ paths:
|
|||||||
[GET /streams](/api/get-streams#response) response
|
[GET /streams](/api/get-streams#response) response
|
||||||
for details on the various properties of a channel.
|
for details on the various properties of a channel.
|
||||||
|
|
||||||
**Changes**: Before Zulip 9.0 (feature level 256), this event
|
This event is also sent when archiving or unarchiving a
|
||||||
was never sent when the `first_message_id` property of a channel
|
channel to all the users who can see that channel exists
|
||||||
was updated because the oldest message that had been sent to it
|
but only to the clients that declared the `archived_channels`
|
||||||
|
[client capability][client-capabilities].
|
||||||
|
|
||||||
|
**Changes**: Prior to Zulip 11.0 (feature level ZF-960986),
|
||||||
|
this event was never sent when archiving or unarchiving
|
||||||
|
a channel.
|
||||||
|
|
||||||
|
Before Zulip 9.0 (feature level 256), this event was never
|
||||||
|
sent when the `first_message_id` property of a channel was
|
||||||
|
updated because the oldest message that had been sent to it
|
||||||
changed.
|
changed.
|
||||||
|
|
||||||
|
[client-capabilities]: /api/register-queue#parameter-client_capabilities
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
$ref: "#/components/schemas/EventIdSchema"
|
$ref: "#/components/schemas/EventIdSchema"
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ from zerver.actions.streams import (
|
|||||||
do_change_subscription_property,
|
do_change_subscription_property,
|
||||||
do_deactivate_stream,
|
do_deactivate_stream,
|
||||||
do_rename_stream,
|
do_rename_stream,
|
||||||
|
do_unarchive_stream,
|
||||||
)
|
)
|
||||||
from zerver.actions.submessage import do_add_submessage
|
from zerver.actions.submessage import do_add_submessage
|
||||||
from zerver.actions.typing import (
|
from zerver.actions.typing import (
|
||||||
@@ -362,6 +363,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,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -3751,7 +3753,19 @@ class NormalActionsTest(BaseAction):
|
|||||||
include_streams=include_streams, archived_channels=True
|
include_streams=include_streams, archived_channels=True
|
||||||
) as events:
|
) as events:
|
||||||
do_deactivate_stream(stream, acting_user=None)
|
do_deactivate_stream(stream, acting_user=None)
|
||||||
|
check_stream_update("events[0]", events[0])
|
||||||
|
self.assertEqual(events[0]["stream_id"], stream.id)
|
||||||
|
self.assertEqual(events[0]["property"], "is_archived")
|
||||||
|
self.assertEqual(events[0]["value"], True)
|
||||||
|
|
||||||
|
do_unarchive_stream(stream, stream.name, acting_user=None)
|
||||||
|
|
||||||
|
with self.verify_action(
|
||||||
|
include_streams=include_streams, archived_channels=False
|
||||||
|
) as events:
|
||||||
|
do_deactivate_stream(stream, acting_user=None)
|
||||||
check_stream_delete("events[0]", events[0])
|
check_stream_delete("events[0]", events[0])
|
||||||
|
self.assertEqual(events[0]["stream_ids"], [stream.id])
|
||||||
|
|
||||||
def test_admin_deactivate_unsubscribed_stream(self) -> None:
|
def test_admin_deactivate_unsubscribed_stream(self) -> None:
|
||||||
self.set_up_db_for_testing_user_access()
|
self.set_up_db_for_testing_user_access()
|
||||||
@@ -3768,7 +3782,37 @@ class NormalActionsTest(BaseAction):
|
|||||||
|
|
||||||
with self.verify_action(num_events=1, archived_channels=True) as events:
|
with self.verify_action(num_events=1, archived_channels=True) as events:
|
||||||
do_deactivate_stream(stream, acting_user=iago)
|
do_deactivate_stream(stream, acting_user=iago)
|
||||||
|
check_stream_update("events[0]", events[0])
|
||||||
|
self.assertEqual(events[0]["stream_id"], stream.id)
|
||||||
|
self.assertEqual(events[0]["property"], "is_archived")
|
||||||
|
self.assertEqual(events[0]["value"], True)
|
||||||
|
|
||||||
|
do_unarchive_stream(stream, stream.name, acting_user=iago)
|
||||||
|
|
||||||
|
with self.verify_action(num_events=1, archived_channels=False) as events:
|
||||||
|
do_deactivate_stream(stream, acting_user=iago)
|
||||||
check_stream_delete("events[0]", events[0])
|
check_stream_delete("events[0]", events[0])
|
||||||
|
self.assertEqual(events[0]["stream_ids"], [stream.id])
|
||||||
|
|
||||||
|
def test_unarchiving_stream(self) -> None:
|
||||||
|
iago = self.example_user("iago")
|
||||||
|
stream = self.make_stream("test_stream")
|
||||||
|
do_deactivate_stream(stream, acting_user=iago)
|
||||||
|
|
||||||
|
with self.verify_action(num_events=1, archived_channels=False) as events:
|
||||||
|
do_unarchive_stream(stream, stream.name, acting_user=iago)
|
||||||
|
check_stream_create("events[0]", events[0])
|
||||||
|
self.assert_length(events[0]["streams"], 1)
|
||||||
|
self.assertEqual(events[0]["streams"][0]["stream_id"], stream.id)
|
||||||
|
|
||||||
|
do_deactivate_stream(stream, acting_user=iago)
|
||||||
|
|
||||||
|
with self.verify_action(num_events=1, archived_channels=True) as events:
|
||||||
|
do_unarchive_stream(stream, stream.name, acting_user=iago)
|
||||||
|
check_stream_update("events[0]", events[0])
|
||||||
|
self.assertEqual(events[0]["stream_id"], stream.id)
|
||||||
|
self.assertEqual(events[0]["property"], "is_archived")
|
||||||
|
self.assertEqual(events[0]["value"], False)
|
||||||
|
|
||||||
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()
|
||||||
@@ -3784,7 +3828,7 @@ class NormalActionsTest(BaseAction):
|
|||||||
|
|
||||||
with self.verify_action(num_events=2, archived_channels=True) 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_update("events[0]", events[0])
|
||||||
|
|
||||||
# Test that if the subscribers of deactivated stream are involved in
|
# Test that if the subscribers of deactivated stream are involved in
|
||||||
# DMs with guest, then the guest does not get "remove" event for them.
|
# DMs with guest, then the guest does not get "remove" event for them.
|
||||||
@@ -3798,7 +3842,7 @@ class NormalActionsTest(BaseAction):
|
|||||||
|
|
||||||
with self.verify_action(num_events=2, archived_channels=True) 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_update("events[0]", events[0])
|
||||||
|
|
||||||
def test_subscribe_other_user_never_subscribed(self) -> None:
|
def test_subscribe_other_user_never_subscribed(self) -> None:
|
||||||
for i, include_streams in enumerate([True, False]):
|
for i, include_streams in enumerate([True, False]):
|
||||||
@@ -4919,6 +4963,38 @@ class SubscribeActionTest(BaseAction):
|
|||||||
acting_user=iago,
|
acting_user=iago,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Check updating description, stream permission for an unsubscribed streams.
|
||||||
|
self.user_profile = self.example_user("hamlet")
|
||||||
|
self.unsubscribe(self.example_user("hamlet"), stream.name)
|
||||||
|
with self.verify_action(include_subscribers=include_subscribers, num_events=1) as events:
|
||||||
|
do_change_stream_description(
|
||||||
|
stream, "description", acting_user=self.example_user("hamlet")
|
||||||
|
)
|
||||||
|
check_stream_update("events[0]", events[0])
|
||||||
|
|
||||||
|
with self.verify_action(include_subscribers=include_subscribers, num_events=1) as events:
|
||||||
|
do_change_stream_permission(
|
||||||
|
stream,
|
||||||
|
invite_only=False,
|
||||||
|
history_public_to_subscribers=True,
|
||||||
|
is_web_public=True,
|
||||||
|
acting_user=iago,
|
||||||
|
)
|
||||||
|
check_stream_update("events[0]", events[0])
|
||||||
|
|
||||||
|
with self.verify_action(include_subscribers=include_subscribers, num_events=1) as events:
|
||||||
|
do_change_stream_permission(
|
||||||
|
stream,
|
||||||
|
invite_only=True,
|
||||||
|
history_public_to_subscribers=False,
|
||||||
|
is_web_public=False,
|
||||||
|
acting_user=iago,
|
||||||
|
)
|
||||||
|
check_stream_update("events[0]", events[0])
|
||||||
|
|
||||||
|
# Subscribe the user again for further tests.
|
||||||
|
self.subscribe(self.example_user("hamlet"), stream.name)
|
||||||
|
|
||||||
self.user_profile = self.example_user("hamlet")
|
self.user_profile = self.example_user("hamlet")
|
||||||
with self.verify_action(include_subscribers=include_subscribers, num_events=2) as events:
|
with self.verify_action(include_subscribers=include_subscribers, num_events=2) as events:
|
||||||
do_change_stream_message_retention_days(stream, self.example_user("hamlet"), -1)
|
do_change_stream_message_retention_days(stream, self.example_user("hamlet"), -1)
|
||||||
|
|||||||
@@ -2052,34 +2052,47 @@ class StreamAdminTest(ZulipTestCase):
|
|||||||
acting_user=zoe,
|
acting_user=zoe,
|
||||||
)
|
)
|
||||||
self.subscribe(self.example_user("cordelia"), "stream_private_name1")
|
self.subscribe(self.example_user("cordelia"), "stream_private_name1")
|
||||||
with self.capture_send_event_calls(expected_num_events=2) as events:
|
with self.capture_send_event_calls(expected_num_events=3) as events:
|
||||||
do_unarchive_stream(stream, new_name="private", acting_user=None)
|
do_unarchive_stream(stream, new_name="private", acting_user=None)
|
||||||
|
|
||||||
stream = Stream.objects.get(id=stream.id)
|
stream = Stream.objects.get(id=stream.id)
|
||||||
self.assertFalse(stream.is_web_public)
|
self.assertFalse(stream.is_web_public)
|
||||||
|
|
||||||
|
# Clients will get this event only if they support
|
||||||
|
# archived_channels client capability.
|
||||||
|
self.assertEqual(events[0]["event"]["op"], "update")
|
||||||
|
self.assertEqual(events[0]["event"]["stream_id"], stream.id)
|
||||||
|
self.assertEqual(events[0]["event"]["property"], "is_archived")
|
||||||
|
self.assertEqual(events[0]["event"]["value"], False)
|
||||||
|
|
||||||
# Tell all users with metadata access that the stream exists.
|
# Tell all users with metadata access that the stream exists.
|
||||||
self.assertEqual(events[0]["event"]["op"], "create")
|
# This event will only be sent to clients that do not support
|
||||||
self.assertEqual(events[0]["event"]["streams"][0]["name"], "private")
|
# archived_channels client capability, as clients supporting
|
||||||
self.assertEqual(events[0]["event"]["streams"][0]["stream_id"], stream.id)
|
# archived_channels client capability will already know that
|
||||||
notified_user_ids = set(events[0]["users"])
|
# the stream exists.
|
||||||
self.assertEqual(
|
self.assertEqual(events[1]["event"]["op"], "create")
|
||||||
notified_user_ids,
|
self.assertEqual(events[1]["event"]["streams"][0]["name"], "private")
|
||||||
can_access_stream_metadata_user_ids(stream),
|
self.assertEqual(events[1]["event"]["streams"][0]["stream_id"], stream.id)
|
||||||
)
|
|
||||||
self.assertIn(self.example_user("cordelia").id, notified_user_ids)
|
for event in [events[0], events[1]]:
|
||||||
# An important corner case is that all organization admins are notified.
|
notified_user_ids = set(event["users"])
|
||||||
self.assertIn(self.example_user("iago").id, notified_user_ids)
|
self.assertEqual(
|
||||||
# The current user, Hamlet was made an admin and thus should be notified too.
|
notified_user_ids,
|
||||||
self.assertIn(aaron.id, notified_user_ids)
|
can_access_stream_metadata_user_ids(stream),
|
||||||
# Channel admin should be notified.
|
)
|
||||||
self.assertIn(self.example_user("aaron").id, notified_user_ids)
|
self.assertIn(self.example_user("cordelia").id, notified_user_ids)
|
||||||
# User belonging to `can_add_subscribers_group` should be notified.
|
# An important corner case is that all organization admins are notified.
|
||||||
self.assertIn(prospero.id, notified_user_ids)
|
self.assertIn(self.example_user("iago").id, notified_user_ids)
|
||||||
# User belonging to `can_subscribe_group` should be notified.
|
# The current user, Hamlet was made an admin and thus should be notified too.
|
||||||
self.assertIn(zoe.id, notified_user_ids)
|
self.assertIn(aaron.id, notified_user_ids)
|
||||||
# Guest user should not be notified.
|
# Channel admin should be notified.
|
||||||
self.assertNotIn(self.example_user("polonius").id, notified_user_ids)
|
self.assertIn(self.example_user("aaron").id, notified_user_ids)
|
||||||
|
# User belonging to `can_add_subscribers_group` should be notified.
|
||||||
|
self.assertIn(prospero.id, notified_user_ids)
|
||||||
|
# User belonging to `can_subscribe_group` should be notified.
|
||||||
|
self.assertIn(zoe.id, notified_user_ids)
|
||||||
|
# Guest user should not be notified.
|
||||||
|
self.assertNotIn(self.example_user("polonius").id, notified_user_ids)
|
||||||
|
|
||||||
def test_unarchive_stream(self) -> None:
|
def test_unarchive_stream(self) -> None:
|
||||||
hamlet = self.example_user("hamlet")
|
hamlet = self.example_user("hamlet")
|
||||||
@@ -2093,20 +2106,33 @@ class StreamAdminTest(ZulipTestCase):
|
|||||||
self.subscribe(hamlet, stream.name)
|
self.subscribe(hamlet, stream.name)
|
||||||
self.subscribe(cordelia, stream.name)
|
self.subscribe(cordelia, stream.name)
|
||||||
do_deactivate_stream(stream, acting_user=None)
|
do_deactivate_stream(stream, acting_user=None)
|
||||||
with self.capture_send_event_calls(expected_num_events=2) as events:
|
with self.capture_send_event_calls(expected_num_events=3) as events:
|
||||||
do_unarchive_stream(stream, new_name="new_stream", acting_user=None)
|
do_unarchive_stream(stream, new_name="new_stream", acting_user=None)
|
||||||
|
|
||||||
|
# Clients will get this event only if they support
|
||||||
|
# archived_channels client capability.
|
||||||
|
self.assertEqual(events[0]["event"]["op"], "update")
|
||||||
|
self.assertEqual(events[0]["event"]["stream_id"], stream.id)
|
||||||
|
self.assertEqual(events[0]["event"]["property"], "is_archived")
|
||||||
|
self.assertEqual(events[0]["event"]["value"], False)
|
||||||
|
|
||||||
# Tell all users with metadata access that the stream exists.
|
# Tell all users with metadata access that the stream exists.
|
||||||
self.assertEqual(events[0]["event"]["op"], "create")
|
# This event will only be sent to clients that do not support
|
||||||
self.assertEqual(events[0]["event"]["streams"][0]["name"], "new_stream")
|
# archived_channels client capability, as clients supporting
|
||||||
self.assertEqual(events[0]["event"]["streams"][0]["stream_id"], stream.id)
|
# archived_channels client capability will already know that
|
||||||
notified_user_ids = set(events[0]["users"])
|
# the stream exists.
|
||||||
self.assertCountEqual(
|
self.assertEqual(events[1]["event"]["op"], "create")
|
||||||
notified_user_ids,
|
self.assertEqual(events[1]["event"]["streams"][0]["name"], "new_stream")
|
||||||
set(active_non_guest_user_ids(stream.realm_id)),
|
self.assertEqual(events[1]["event"]["streams"][0]["stream_id"], stream.id)
|
||||||
)
|
|
||||||
# Guest user should not be notified.
|
for event in [events[0], events[1]]:
|
||||||
self.assertNotIn(self.example_user("polonius").id, notified_user_ids)
|
notified_user_ids = set(event["users"])
|
||||||
|
self.assertCountEqual(
|
||||||
|
notified_user_ids,
|
||||||
|
set(active_non_guest_user_ids(stream.realm_id)),
|
||||||
|
)
|
||||||
|
# Guest user should not be notified.
|
||||||
|
self.assertNotIn(self.example_user("polonius").id, notified_user_ids)
|
||||||
|
|
||||||
stream = Stream.objects.get(id=stream.id)
|
stream = Stream.objects.get(id=stream.id)
|
||||||
self.assertFalse(stream.deactivated)
|
self.assertFalse(stream.deactivated)
|
||||||
@@ -3333,7 +3359,7 @@ class StreamAdminTest(ZulipTestCase):
|
|||||||
realm = stream.realm
|
realm = stream.realm
|
||||||
stream_id = stream.id
|
stream_id = stream.id
|
||||||
|
|
||||||
with self.capture_send_event_calls(expected_num_events=2) as events:
|
with self.capture_send_event_calls(expected_num_events=3) as events:
|
||||||
result = self.client_delete("/json/streams/" + str(stream_id))
|
result = self.client_delete("/json/streams/" + str(stream_id))
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
|
|
||||||
@@ -3344,10 +3370,21 @@ class StreamAdminTest(ZulipTestCase):
|
|||||||
self.assertEqual(sub_events, [])
|
self.assertEqual(sub_events, [])
|
||||||
|
|
||||||
stream_events = [e for e in events if e["event"]["type"] == "stream"]
|
stream_events = [e for e in events if e["event"]["type"] == "stream"]
|
||||||
self.assert_length(stream_events, 1)
|
self.assert_length(stream_events, 2)
|
||||||
event = stream_events[0]["event"]
|
|
||||||
self.assertEqual(event["op"], "delete")
|
# Clients will get this event only if they support
|
||||||
self.assertEqual(event["streams"][0]["stream_id"], stream.id)
|
# archived_channels client capability.
|
||||||
|
update_event = stream_events[0]["event"]
|
||||||
|
self.assertEqual(update_event["op"], "update")
|
||||||
|
self.assertEqual(update_event["stream_id"], stream.id)
|
||||||
|
self.assertEqual(update_event["property"], "is_archived")
|
||||||
|
self.assertEqual(update_event["value"], True)
|
||||||
|
|
||||||
|
# This event will only be sent to clients that do not support
|
||||||
|
# archived_channels client capability.
|
||||||
|
delete_event = stream_events[1]["event"]
|
||||||
|
self.assertEqual(delete_event["op"], "delete")
|
||||||
|
self.assertEqual(delete_event["streams"][0]["stream_id"], stream.id)
|
||||||
|
|
||||||
hashed_stream_id = hashlib.sha512(str(stream_id).encode()).hexdigest()[0:7]
|
hashed_stream_id = hashlib.sha512(str(stream_id).encode()).hexdigest()[0:7]
|
||||||
old_deactivated_stream_name = hashed_stream_id + "!DEACTIVATED:" + active_name
|
old_deactivated_stream_name = hashed_stream_id + "!DEACTIVATED:" + active_name
|
||||||
|
|||||||
@@ -258,6 +258,15 @@ class ClientDescriptor:
|
|||||||
# clients who can filter out deactivated groups by themselves.
|
# clients who can filter out deactivated groups by themselves.
|
||||||
# Other clients receive 'remove' event.
|
# Other clients receive 'remove' event.
|
||||||
return self.include_deactivated_groups
|
return self.include_deactivated_groups
|
||||||
|
if (
|
||||||
|
event["type"] == "stream"
|
||||||
|
and event["op"] == "update"
|
||||||
|
and event["property"] == "is_archived"
|
||||||
|
):
|
||||||
|
# 'update' events for archiving and unarchiving streams are
|
||||||
|
# only sent to clients that can process archived channels.
|
||||||
|
# Other clients receive "create" and "delete" events.
|
||||||
|
return self.archived_channels
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# TODO: Refactor so we don't need this function
|
# TODO: Refactor so we don't need this function
|
||||||
@@ -1624,6 +1633,28 @@ def process_user_group_name_update_event(event: Mapping[str, Any], users: Iterab
|
|||||||
client.add_event(user_group_event)
|
client.add_event(user_group_event)
|
||||||
|
|
||||||
|
|
||||||
|
def process_stream_creation_event(event: Mapping[str, Any], users: Iterable[int]) -> None:
|
||||||
|
stream_create_event = dict(event)
|
||||||
|
event_for_unarchiving_stream = stream_create_event.pop("for_unarchiving", False)
|
||||||
|
for user_profile_id in users:
|
||||||
|
for client in get_client_descriptors_for_user(user_profile_id):
|
||||||
|
if client.accepts_event(stream_create_event):
|
||||||
|
if event_for_unarchiving_stream and client.archived_channels:
|
||||||
|
continue
|
||||||
|
client.add_event(stream_create_event)
|
||||||
|
|
||||||
|
|
||||||
|
def process_stream_deletion_event(event: Mapping[str, Any], users: Iterable[int]) -> None:
|
||||||
|
stream_delete_event = dict(event)
|
||||||
|
event_for_archiving_stream = stream_delete_event.pop("for_archiving", False)
|
||||||
|
for user_profile_id in users:
|
||||||
|
for client in get_client_descriptors_for_user(user_profile_id):
|
||||||
|
if client.accepts_event(stream_delete_event):
|
||||||
|
if event_for_archiving_stream and client.archived_channels:
|
||||||
|
continue
|
||||||
|
client.add_event(stream_delete_event)
|
||||||
|
|
||||||
|
|
||||||
def process_user_topic_event(event: Mapping[str, Any], users: Iterable[int]) -> None:
|
def process_user_topic_event(event: Mapping[str, Any], users: Iterable[int]) -> None:
|
||||||
empty_topic_name_fallback_event: Mapping[str, Any] | dict[str, Any]
|
empty_topic_name_fallback_event: Mapping[str, Any] | dict[str, Any]
|
||||||
if event.get("topic_name") == "":
|
if event.get("topic_name") == "":
|
||||||
@@ -1721,6 +1752,10 @@ def process_notification(notice: Mapping[str, Any]) -> None:
|
|||||||
and event["flag"] == "read"
|
and event["flag"] == "read"
|
||||||
):
|
):
|
||||||
process_mark_message_unread_event(event, cast(list[int], users))
|
process_mark_message_unread_event(event, cast(list[int], users))
|
||||||
|
elif event["type"] == "stream" and event["op"] == "create":
|
||||||
|
process_stream_creation_event(event, cast(list[int], users))
|
||||||
|
elif event["type"] == "stream" and event["op"] == "delete":
|
||||||
|
process_stream_deletion_event(event, cast(list[int], users))
|
||||||
elif event["type"] == "cleanup_queue":
|
elif event["type"] == "cleanup_queue":
|
||||||
# cleanup_event_queue may generate this event to forward cleanup
|
# cleanup_event_queue may generate this event to forward cleanup
|
||||||
# requests to the right shard.
|
# requests to the right shard.
|
||||||
|
|||||||
Reference in New Issue
Block a user