From b9248c75f4edc3225c690dfb61d2b4db49b0bf15 Mon Sep 17 00:00:00 2001 From: Sahil Batra Date: Mon, 27 Jun 2022 22:09:33 +0530 Subject: [PATCH] stream: Add do_change_can_remove_subscribers_group and field to objects. This commit adds do_change_can_remove_subscriber_group function for changing can_remove_subscribers_group field of a stream. We also add can_remove_subscribers_group_id field to stream and subscription objects. This function will be helpful for writing tests in next commit. We would add API and UI support to change this setting in further commits. --- frontend_tests/node_tests/lib/events.js | 2 ++ templates/zerver/api/changelog.md | 7 +++++ version.py | 2 +- zerver/actions/streams.py | 38 +++++++++++++++++++++++++ zerver/lib/event_schema.py | 4 +++ zerver/lib/subscription_info.py | 6 ++++ zerver/lib/types.py | 4 +++ zerver/models.py | 3 ++ zerver/openapi/zulip.yaml | 25 ++++++++++++++++ zerver/tests/test_events.py | 12 ++++++++ 10 files changed, 102 insertions(+), 1 deletion(-) diff --git a/frontend_tests/node_tests/lib/events.js b/frontend_tests/node_tests/lib/events.js index 17708a4e44..fe523d8f22 100644 --- a/frontend_tests/node_tests/lib/events.js +++ b/frontend_tests/node_tests/lib/events.js @@ -50,6 +50,7 @@ exports.test_streams = { is_web_public: false, message_retention_days: null, stream_post_policy: 1, + can_remove_subscribers_group_id: 2, }, test: { name: "test", @@ -64,6 +65,7 @@ exports.test_streams = { is_announcement_only: false, message_retention_days: null, stream_post_policy: 1, + can_remove_subscribers_group_id: 2, }, }; diff --git a/templates/zerver/api/changelog.md b/templates/zerver/api/changelog.md index 23cadede50..926e03944d 100644 --- a/templates/zerver/api/changelog.md +++ b/templates/zerver/api/changelog.md @@ -20,6 +20,13 @@ format used by the Zulip server that they are interacting with. ## Changes in Zulip 6.0 +**Feature level 142** + +* [`GET users/me/subscriptions`](/api/get-subscriptions), [`GET + /streams`](/api/get-streams), [`POST /register`](/api/register-queue), + [`GET /events`](/api/get-events): Added `can_remove_subscribers_group_id` + field to Stream and Subscription objects. + **Feature level 141** * [`POST /register`](/api/register-queue), [`PATCH diff --git a/version.py b/version.py index d12d074ded..3acc51e97e 100644 --- a/version.py +++ b/version.py @@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.4.3" # Changes should be accompanied by documentation explaining what the # new level means in templates/zerver/api/changelog.md, as well as # "**Changes**" entries in the endpoint's documentation in `zulip.yaml`. -API_FEATURE_LEVEL = 141 +API_FEATURE_LEVEL = 142 # 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 diff --git a/zerver/actions/streams.py b/zerver/actions/streams.py index 0e35224cd1..bd20a175b9 100644 --- a/zerver/actions/streams.py +++ b/zerver/actions/streams.py @@ -68,6 +68,7 @@ from zerver.models import ( Recipient, Stream, Subscription, + UserGroup, UserProfile, active_non_guest_user_ids, get_system_bot, @@ -289,6 +290,7 @@ def send_subscription_add_events( stream_weekly_traffic=stream_info.stream_weekly_traffic, subscribers=stream_info.subscribers, # Fields from Stream.API_FIELDS + can_remove_subscribers_group_id=stream_dict["can_remove_subscribers_group_id"], date_created=stream_dict["date_created"], description=stream_dict["description"], first_message_id=stream_dict["first_message_id"], @@ -1317,3 +1319,39 @@ def do_change_stream_message_retention_days( old_value=old_message_retention_days_value, new_value=message_retention_days, ) + + +def do_change_can_remove_subscribers_group( + stream: Stream, user_group: UserGroup, *, acting_user: Optional[UserProfile] = None +) -> None: + old_user_group = stream.can_remove_subscribers_group + old_user_group_id = None + if old_user_group is not None: + old_user_group_id = old_user_group.id + + stream.can_remove_subscribers_group = user_group + stream.save() + RealmAuditLog.objects.create( + realm=stream.realm, + acting_user=acting_user, + modified_stream=stream, + event_type=RealmAuditLog.STREAM_CAN_REMOVE_SUBSCRIBERS_GROUP_CHANGED, + event_time=timezone_now(), + extra_data=orjson.dumps( + { + RealmAuditLog.OLD_VALUE: old_user_group_id, + RealmAuditLog.NEW_VALUE: user_group.id, + } + ).decode(), + ) + event = dict( + op="update", + type="stream", + property="can_remove_subscribers_group_id", + value=user_group.id, + stream_id=stream.id, + name=stream.name, + ) + transaction.on_commit( + lambda: send_event(stream.realm, event, can_access_stream_user_ids(stream)) + ) diff --git a/zerver/lib/event_schema.py b/zerver/lib/event_schema.py index c81328937e..8de368bb1f 100644 --- a/zerver/lib/event_schema.py +++ b/zerver/lib/event_schema.py @@ -50,6 +50,7 @@ from zerver.models import Realm, RealmUserDefault, Stream, UserProfile # These fields are used for "stream" events, and are included in the # larger "subscription" events that also contain personal settings. basic_stream_fields = [ + ("can_remove_subscribers_group_id", int), ("date_created", int), ("description", str), ("first_message_id", OptionalType(int)), @@ -1287,6 +1288,9 @@ def check_stream_update( elif prop == "stream_post_policy": assert extra_keys == set() assert value in Stream.STREAM_POST_POLICY_TYPES + elif prop == "can_remove_subscribers_group_id": + assert extra_keys == set() + assert isinstance(value, int) else: raise AssertionError(f"Unknown property: {prop}") diff --git a/zerver/lib/subscription_info.py b/zerver/lib/subscription_info.py index c01e752091..09bfd0e4f1 100644 --- a/zerver/lib/subscription_info.py +++ b/zerver/lib/subscription_info.py @@ -40,6 +40,7 @@ def get_web_public_subs(realm: Realm) -> SubscriptionInfo: subscribed = [] for stream in get_web_public_streams_queryset(realm): # Add Stream fields. + can_remove_subscribers_group_id = stream.can_remove_subscribers_group_id date_created = datetime_to_timestamp(stream.date_created) description = stream.description first_message_id = stream.first_message_id @@ -71,6 +72,7 @@ def get_web_public_subs(realm: Realm) -> SubscriptionInfo: sub = SubscriptionStreamDict( audible_notifications=audible_notifications, + can_remove_subscribers_group_id=can_remove_subscribers_group_id, color=color, date_created=date_created, description=description, @@ -110,6 +112,7 @@ def build_stream_dict_for_sub( recent_traffic: Dict[int, int], ) -> SubscriptionStreamDict: # Handle Stream.API_FIELDS + can_remove_subscribers_group_id = raw_stream_dict["can_remove_subscribers_group_id"] date_created = datetime_to_timestamp(raw_stream_dict["date_created"]) description = raw_stream_dict["description"] first_message_id = raw_stream_dict["first_message_id"] @@ -153,6 +156,7 @@ def build_stream_dict_for_sub( # Our caller may add a subscribers field. return SubscriptionStreamDict( audible_notifications=audible_notifications, + can_remove_subscribers_group_id=can_remove_subscribers_group_id, color=color, date_created=date_created, description=description, @@ -182,6 +186,7 @@ def build_stream_dict_for_never_sub( raw_stream_dict: RawStreamDict, recent_traffic: Dict[int, int], ) -> NeverSubscribedStreamDict: + can_remove_subscribers_group_id = raw_stream_dict["can_remove_subscribers_group_id"] date_created = datetime_to_timestamp(raw_stream_dict["date_created"]) description = raw_stream_dict["description"] first_message_id = raw_stream_dict["first_message_id"] @@ -202,6 +207,7 @@ def build_stream_dict_for_never_sub( # Our caller may add a subscribers field. return NeverSubscribedStreamDict( + can_remove_subscribers_group_id=can_remove_subscribers_group_id, date_created=date_created, description=description, first_message_id=first_message_id, diff --git a/zerver/lib/types.py b/zerver/lib/types.py index 4bee9e53bc..abffc35c35 100644 --- a/zerver/lib/types.py +++ b/zerver/lib/types.py @@ -177,6 +177,7 @@ class RawStreamDict(TypedDict): are needed to encode the stream for the API. """ + can_remove_subscribers_group_id: int date_created: datetime.datetime description: str email_token: str @@ -216,6 +217,7 @@ class SubscriptionStreamDict(TypedDict): """ audible_notifications: Optional[bool] + can_remove_subscribers_group_id: int color: str date_created: int description: str @@ -242,6 +244,7 @@ class SubscriptionStreamDict(TypedDict): class NeverSubscribedStreamDict(TypedDict): + can_remove_subscribers_group_id: int date_created: int description: str first_message_id: Optional[int] @@ -264,6 +267,7 @@ class APIStreamDict(TypedDict): with few exceptions and possible additional fields. """ + can_remove_subscribers_group_id: int date_created: int description: str first_message_id: Optional[int] diff --git a/zerver/models.py b/zerver/models.py index d22ac552c2..a5969f8e7d 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -2552,6 +2552,7 @@ class Stream(models.Model): "name", "rendered_description", "stream_post_policy", + "can_remove_subscribers_group_id", ] @staticmethod @@ -2561,6 +2562,7 @@ class Stream(models.Model): def to_dict(self) -> APIStreamDict: return APIStreamDict( + can_remove_subscribers_group_id=self.can_remove_subscribers_group_id, date_created=datetime_to_timestamp(self.date_created), description=self.description, first_message_id=self.first_message_id, @@ -4364,6 +4366,7 @@ class AbstractRealmAuditLog(models.Model): STREAM_REACTIVATED = 604 STREAM_MESSAGE_RETENTION_DAYS_CHANGED = 605 STREAM_PROPERTY_CHANGED = 607 + STREAM_CAN_REMOVE_SUBSCRIBERS_GROUP_CHANGED = 608 # The following values are only for RemoteZulipServerAuditLog # Values should be exactly 10000 greater than the corresponding diff --git a/zerver/openapi/zulip.yaml b/zerver/openapi/zulip.yaml index ee8a3eb7cd..713c9220ad 100644 --- a/zerver/openapi/zulip.yaml +++ b/zerver/openapi/zulip.yaml @@ -644,6 +644,7 @@ paths: "in_home_view": true, "email_address": "test_stream.af64447e9e39374841063747ade8e6b0.show-sender@testserver", "stream_weekly_traffic": null, + "can_remove_subscribers_group_id": 2, "subscribers": [10], }, ], @@ -1143,6 +1144,7 @@ paths: "first_message_id": null, "message_retention_days": null, "is_announcement_only": false, + "can_remove_subscribers_group_id": 2, }, ], "id": 0, @@ -1188,6 +1190,7 @@ paths: "first_message_id": null, "message_retention_days": null, "is_announcement_only": false, + "can_remove_subscribers_group_id": 2, }, ], "id": 0, @@ -1745,6 +1748,7 @@ paths: "first_message_id": 1, "message_retention_days": null, "is_announcement_only": false, + "can_remove_subscribers_group_id": 2, }, { "name": "Denmark", @@ -1758,6 +1762,7 @@ paths: "first_message_id": 4, "message_retention_days": null, "is_announcement_only": false, + "can_remove_subscribers_group_id": 2, }, { "name": "Verona", @@ -1771,6 +1776,7 @@ paths: "first_message_id": 6, "message_retention_days": null, "is_announcement_only": false, + "can_remove_subscribers_group_id": 2, }, ], }, @@ -1815,6 +1821,7 @@ paths: "first_message_id": 1, "message_retention_days": null, "is_announcement_only": false, + "can_remove_subscribers_group_id": 2, }, ], "id": 0, @@ -10089,6 +10096,7 @@ paths: first_message_id: nullable: true is_announcement_only: {} + can_remove_subscribers_group_id: {} stream_weekly_traffic: type: integer nullable: true @@ -13879,6 +13887,7 @@ paths: first_message_id: nullable: true is_announcement_only: {} + can_remove_subscribers_group_id: {} is_default: type: boolean description: | @@ -13989,6 +13998,7 @@ paths: "rendered_description": "

A Scandinavian country

", "stream_id": 7, "stream_post_policy": 1, + "can_remove_subscribers_group_id": 2, }, } "400": @@ -15075,6 +15085,7 @@ components: first_message_id: nullable: true is_announcement_only: {} + can_remove_subscribers_group_id: {} BasicStreamBase: type: object description: | @@ -15176,6 +15187,13 @@ components: **Changes**: Deprecated in Zulip 3.0 (feature level 1). Clients should use `stream_post_policy` instead. + can_remove_subscribers_group_id: + type: integer + description: | + ID of the user group whose members are allowed to unsubscribe others + from the stream. + + **Changes**: New in Zulip 6.0 (feature level 142). BasicBot: allOf: - $ref: "#/components/schemas/BasicBotBase" @@ -15792,6 +15810,13 @@ components: Null means the stream was recently created and there is insufficient data to estimate the average traffic. + can_remove_subscribers_group_id: + type: integer + description: | + ID of the user group whose members are allowed to unsubscribe others + from the stream. + + **Changes**: New in Zulip 6.0 (feature level 142). DefaultStreamGroup: type: object description: | diff --git a/zerver/tests/test_events.py b/zerver/tests/test_events.py index adb5c3def3..0cf2bc3a33 100644 --- a/zerver/tests/test_events.py +++ b/zerver/tests/test_events.py @@ -79,6 +79,7 @@ from zerver.actions.realm_settings import ( from zerver.actions.streams import ( bulk_add_subscriptions, bulk_remove_subscriptions, + do_change_can_remove_subscribers_group, do_change_stream_description, do_change_stream_message_retention_days, do_change_stream_permission, @@ -2885,6 +2886,17 @@ class SubscribeActionTest(BaseAction): events = self.verify_action(action, include_subscribers=include_subscribers, num_events=2) check_stream_update("events[0]", events[0]) + moderators_group = UserGroup.objects.get( + name=UserGroup.MODERATORS_GROUP_NAME, + is_system_group=True, + realm=self.user_profile.realm, + ) + action = lambda: do_change_can_remove_subscribers_group( + stream, moderators_group, acting_user=self.example_user("hamlet") + ) + events = self.verify_action(action, include_subscribers=include_subscribers, num_events=1) + check_stream_update("events[0]", events[0]) + # Subscribe to a totally new invite-only stream, so it's just Hamlet on it stream = self.make_stream("private", self.user_profile.realm, invite_only=True) stream.message_retention_days = 10