From 7959ae3fab97afaf9bea8892dea7635929cd62af Mon Sep 17 00:00:00 2001 From: Sahil Batra Date: Mon, 26 Jul 2021 12:05:27 +0530 Subject: [PATCH] events: Add new event type 'user_settings' for updating user settings. We send a event with type 'user_settings' on updating user's display and notification settings. The old event types - 'update_global_notifications' and 'update_display_settings', are still supported for backwards compatibility. --- templates/zerver/api/changelog.md | 7 ++++ version.py | 2 +- zerver/lib/actions.py | 37 +++++++++++++++++---- zerver/lib/event_schema.py | 39 ++++++++++++++++++++++ zerver/lib/events.py | 10 ++++++ zerver/openapi/zulip.yaml | 54 +++++++++++++++++++++++++++++++ zerver/tests/test_events.py | 41 +++++++++++++++-------- 7 files changed, 169 insertions(+), 21 deletions(-) diff --git a/templates/zerver/api/changelog.md b/templates/zerver/api/changelog.md index d16a6d0e1f..319ad7f2b0 100644 --- a/templates/zerver/api/changelog.md +++ b/templates/zerver/api/changelog.md @@ -11,6 +11,13 @@ below features are supported. ## Changes in Zulip 5.0 +**Feature level 89** + +* [`GET /events`](/api/get-events): Introduced new event type + `user_settings`. The previous `update_display_settings` and + `update_global_notifications` event types are still supported + for backwards compatibility, but will be removed in future. + **Feature level 88** * [`POST /register`](/api/register-queue): Added `zulip_merge_base` diff --git a/version.py b/version.py index a43521ae5b..f14d012c28 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 = 88 +API_FEATURE_LEVEL = 89 # 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/lib/actions.py b/zerver/lib/actions.py index 244679732f..8d5df38e6e 100644 --- a/zerver/lib/actions.py +++ b/zerver/lib/actions.py @@ -5043,10 +5043,10 @@ def do_change_notification_settings( user_profile.save(update_fields=[name]) event = { - "type": "update_global_notifications", - "user": user_profile.email, - "notification_name": name, - "setting": value, + "type": "user_settings", + "op": "update", + "property": name, + "value": value, } event_time = timezone_now() RealmAuditLog.objects.create( @@ -5066,6 +5066,16 @@ def do_change_notification_settings( send_event(user_profile.realm, event, [user_profile.id]) + # This legacy event format is for backwards-compatiblity with + # clients that don't support the new user_settings event type. + legacy_event = { + "type": "update_global_notifications", + "user": user_profile.email, + "notification_name": name, + "setting": value, + } + send_event(user_profile.realm, legacy_event, [user_profile.id]) + def do_set_user_display_setting( user_profile: UserProfile, setting_name: str, setting_value: Union[bool, str, int] @@ -5077,7 +5087,22 @@ def do_set_user_display_setting( assert isinstance(setting_value, property_type) setattr(user_profile, setting_name, setting_value) user_profile.save(update_fields=[setting_name]) + event = { + "type": "user_settings", + "op": "update", + "property": setting_name, + "value": setting_value, + } + if setting_name == "default_language": + assert isinstance(setting_value, str) + event["language_name"] = get_language_name(setting_value) + + send_event(user_profile.realm, event, [user_profile.id]) + + # This legacy event format is for backwards-compatiblity with + # clients that don't support the new user_settings event type. + legacy_event = { "type": "update_display_settings", "user": user_profile.email, "setting_name": setting_name, @@ -5085,9 +5110,9 @@ def do_set_user_display_setting( } if setting_name == "default_language": assert isinstance(setting_value, str) - event["language_name"] = get_language_name(setting_value) + legacy_event["language_name"] = get_language_name(setting_value) - send_event(user_profile.realm, event, [user_profile.id]) + send_event(user_profile.realm, legacy_event, [user_profile.id]) # Updates to the timezone display setting are sent to all users if setting_name == "timezone": diff --git a/zerver/lib/event_schema.py b/zerver/lib/event_schema.py index 32979e89de..ac6f2325b6 100644 --- a/zerver/lib/event_schema.py +++ b/zerver/lib/event_schema.py @@ -1398,6 +1398,21 @@ update_display_settings_event = event_dict_type( ) _check_update_display_settings = make_checker(update_display_settings_event) +user_settings_update_event = event_dict_type( + required_keys=[ + ("type", Equals("user_settings")), + ("op", Equals("update")), + ("property", str), + ("value", value_type), + ], + optional_keys=[ + # force vertical + ("language_name", str), + ], +) + +_check_user_settings_update = make_checker(user_settings_update_event) + def check_update_display_settings( var_name: str, @@ -1425,6 +1440,30 @@ def check_update_display_settings( assert "language_name" not in event.keys() +def check_user_settings_update( + var_name: str, + event: Dict[str, object], +) -> None: + _check_user_settings_update(var_name, event) + setting_name = event["property"] + value = event["value"] + + assert isinstance(setting_name, str) + if setting_name == "timezone": + assert isinstance(value, str) + elif setting_name in UserProfile.property_types: + setting_type = UserProfile.property_types[setting_name] + assert isinstance(value, setting_type) + else: + setting_type = UserProfile.notification_setting_types[setting_name] + assert isinstance(value, setting_type) + + if setting_name == "default_language": + assert "language_name" in event.keys() + else: + assert "language_name" not in event.keys() + + update_global_notifications_event = event_dict_type( required_keys=[ ("type", Equals("update_global_notifications")), diff --git a/zerver/lib/events.py b/zerver/lib/events.py index 9c72b901e3..84999e8e4f 100644 --- a/zerver/lib/events.py +++ b/zerver/lib/events.py @@ -1102,6 +1102,16 @@ def apply_event( elif event["type"] == "update_global_notifications": assert event["notification_name"] in UserProfile.notification_setting_types state[event["notification_name"]] = event["setting"] + elif event["type"] == "user_settings": + # timezone setting is not included in property_types or + # notification_setting_types dicts, because this setting + # is not a part of UserBaseSettings class. + if event["property"] != "timezone": + assert ( + event["property"] in UserProfile.property_types + or event["property"] in UserProfile.notification_setting_types + ) + state[event["property"]] = event["value"] elif event["type"] == "invites_changed": pass elif event["type"] == "user_group": diff --git a/zerver/openapi/zulip.yaml b/zerver/openapi/zulip.yaml index 11ce820364..44c04a6a06 100644 --- a/zerver/openapi/zulip.yaml +++ b/zerver/openapi/zulip.yaml @@ -243,6 +243,10 @@ paths: description: | Event sent to a user's clients when that user's display settings have changed. + + **Changes**: Deprecated in Zulip 5.0 (feature level 89), replaced by + the `user_settings` event type. + deprecated: true properties: id: $ref: "#/components/schemas/EventIdSchema" @@ -280,6 +284,10 @@ paths: description: | Event sent to a user's clients when that user's [notification settings](/api/update-settings) have changed. + + **Changes**: Deprecated in Zulip 5.0 (feature level 89), replaced by + the `user_settings` event type. + deprecated: true properties: id: $ref: "#/components/schemas/EventIdSchema" @@ -307,6 +315,52 @@ paths: "setting": true, "id": 0, } + - type: object + description: | + Event sent to a user's clients when that user's settings + have changed. + + **Changes**: New in Zulip 5.0 (feature level 89), replacing the + previous `update_display_settings` and `update_global_notifications` + event types, which are still present for backwards compatibility reasons. + properties: + id: + $ref: "#/components/schemas/EventIdSchema" + type: + allOf: + - $ref: "#/components/schemas/EventTypeSchema" + - enum: + - user_settings + op: + type: string + enum: + - update + property: + type: string + description: | + Name of the changed setting. + value: + description: | + New value of the changed setting. + oneOf: + - type: boolean + - type: integer + - type: string + language_name: + description: | + Present only if the setting to be changed is + `default_language`. Contains the name of the + new default language in English. + type: string + additionalProperties: false + example: + { + "type": "user_settings", + "op": "update", + "property": "high_contrast_mode", + "value": false, + "id": 0, + } - type: object description: | Event sent generally to all users in an organization for changes diff --git a/zerver/tests/test_events.py b/zerver/tests/test_events.py index 920627f5e6..e39665f9bb 100644 --- a/zerver/tests/test_events.py +++ b/zerver/tests/test_events.py @@ -161,6 +161,7 @@ from zerver.lib.event_schema import ( check_user_group_remove, check_user_group_remove_members, check_user_group_update, + check_user_settings_update, check_user_status, ) from zerver.lib.events import ( @@ -1430,9 +1431,11 @@ class NormalActionsTest(BaseAction): notification_setting, setting_value, acting_user=self.user_profile, - ) + ), + num_events=2, ) - check_update_global_notifications("events[0]", events[0], setting_value) + check_user_settings_update("events[0]", events[0]) + check_update_global_notifications("events[1]", events[1], setting_value) # Also test with notification_settings_null=True events = self.verify_action( @@ -1444,8 +1447,10 @@ class NormalActionsTest(BaseAction): ), notification_settings_null=True, state_change_expected=False, + num_events=2, ) - check_update_global_notifications("events[0]", events[0], setting_value) + check_user_settings_update("events[0]", events[0]) + check_update_global_notifications("events[1]", events[1], setting_value) def test_change_notification_sound(self) -> None: notification_setting = "notification_sound" @@ -1453,9 +1458,11 @@ class NormalActionsTest(BaseAction): events = self.verify_action( lambda: do_change_notification_settings( self.user_profile, notification_setting, "ding", acting_user=self.user_profile - ) + ), + num_events=2, ) - check_update_global_notifications("events[0]", events[0], "ding") + check_user_settings_update("events[0]", events[0]) + check_update_global_notifications("events[1]", events[1], "ding") def test_change_desktop_icon_count_display(self) -> None: notification_setting = "desktop_icon_count_display" @@ -1463,16 +1470,20 @@ class NormalActionsTest(BaseAction): events = self.verify_action( lambda: do_change_notification_settings( self.user_profile, notification_setting, 2, acting_user=self.user_profile - ) + ), + num_events=2, ) - check_update_global_notifications("events[0]", events[0], 2) + check_user_settings_update("events[0]", events[0]) + check_update_global_notifications("events[1]", events[1], 2) events = self.verify_action( lambda: do_change_notification_settings( self.user_profile, notification_setting, 1, acting_user=self.user_profile - ) + ), + num_events=2, ) - check_update_global_notifications("events[0]", events[0], 1) + check_user_settings_update("events[0]", events[0]) + check_update_global_notifications("events[1]", events[1], 1) def test_realm_update_plan_type(self) -> None: realm = self.user_profile.realm @@ -2142,7 +2153,7 @@ class UserDisplayActionTest(BaseAction): color_scheme=[2, 3, 1], ) - num_events = 1 + num_events = 2 values = test_changes.get(setting_name) property_type = UserProfile.property_types[setting_name] @@ -2161,7 +2172,8 @@ class UserDisplayActionTest(BaseAction): num_events=num_events, ) - check_update_display_settings("events[0]", events[0]) + check_user_settings_update("events[0]", events[0]) + check_update_display_settings("events[1]", events[1]) def test_set_user_display_settings(self) -> None: for prop in UserProfile.property_types: @@ -2169,7 +2181,7 @@ class UserDisplayActionTest(BaseAction): def test_set_user_timezone(self) -> None: values = ["America/Denver", "Pacific/Pago_Pago", "Pacific/Galapagos", ""] - num_events = 2 + num_events = 3 for value in values: events = self.verify_action( @@ -2177,8 +2189,9 @@ class UserDisplayActionTest(BaseAction): num_events=num_events, ) - check_update_display_settings("events[0]", events[0]) - check_realm_user_update("events[1]", events[1], "timezone") + check_user_settings_update("events[0]", events[0]) + check_update_display_settings("events[1]", events[1]) + check_realm_user_update("events[2]", events[2], "timezone") class SubscribeActionTest(BaseAction):