events: Add "user_settings_object" to client_capabilities.

This commit adds "user_settings_object" field to
client_capabilities which will be used to determine
if the client needs 'update_display_settings' and
'update_global_notifications' event.
This commit is contained in:
Sahil Batra 2021-07-24 23:21:25 +05:30 committed by Tim Abbott
parent 7959ae3fab
commit 0364d0c8ca
9 changed files with 70 additions and 2 deletions

View File

@ -17,6 +17,12 @@ below features are supported.
`user_settings`. The previous `update_display_settings` and `user_settings`. The previous `update_display_settings` and
`update_global_notifications` event types are still supported `update_global_notifications` event types are still supported
for backwards compatibility, but will be removed in future. for backwards compatibility, but will be removed in future.
* [`POST /register`](/api/register-queue): Added the new
`user_settings_object` property to supported `client_capabilities`.
* [`GET /events`](/api/get-events): `update_display_settings` and
`update_global_notifications` are sent only when `user_settings_object`
is not included in the `client_capabilities` when registering the
event queue.
**Feature level 88** **Feature level 88**

View File

@ -1217,6 +1217,7 @@ def do_events_register(
"user_avatar_url_field_optional", False "user_avatar_url_field_optional", False
) )
stream_typing_notifications = client_capabilities.get("stream_typing_notifications", False) stream_typing_notifications = client_capabilities.get("stream_typing_notifications", False)
user_settings_object = client_capabilities.get("user_settings_object", False)
if user_profile.realm.email_address_visibility != Realm.EMAIL_ADDRESS_VISIBILITY_EVERYONE: if user_profile.realm.email_address_visibility != Realm.EMAIL_ADDRESS_VISIBILITY_EVERYONE:
# If real email addresses are not available to the user, their # If real email addresses are not available to the user, their
@ -1248,6 +1249,7 @@ def do_events_register(
narrow=narrow, narrow=narrow,
bulk_message_deletion=bulk_message_deletion, bulk_message_deletion=bulk_message_deletion,
stream_typing_notifications=stream_typing_notifications, stream_typing_notifications=stream_typing_notifications,
user_settings_object=user_settings_object,
) )
if queue_id is None: if queue_id is None:

View File

@ -135,6 +135,7 @@ def build_page_params_for_home_page_load(
"bulk_message_deletion": True, "bulk_message_deletion": True,
"user_avatar_url_field_optional": True, "user_avatar_url_field_optional": True,
"stream_typing_notifications": False, # Set this to True when frontend support is implemented. "stream_typing_notifications": False, # Set this to True when frontend support is implemented.
"user_settings_object": False, # Set this to True when frontend support is implemented.
} }
if user_profile is not None: if user_profile is not None:

View File

@ -242,7 +242,9 @@ paths:
- type: object - type: object
description: | description: |
Event sent to a user's clients when that user's display settings Event sent to a user's clients when that user's display settings
have changed. have changed with an additional rule that it is only sent to
clients that did not include `user_settings_object` in their
`client_capabilities` when registering the event queue.
**Changes**: Deprecated in Zulip 5.0 (feature level 89), replaced by **Changes**: Deprecated in Zulip 5.0 (feature level 89), replaced by
the `user_settings` event type. the `user_settings` event type.
@ -283,7 +285,10 @@ paths:
- type: object - type: object
description: | description: |
Event sent to a user's clients when that user's [notification Event sent to a user's clients when that user's [notification
settings](/api/update-settings) have changed. settings](/api/update-settings) have changed with an additional
rule that it is only sent to clients that did not include
`user_settings_object` in their `client_capabilities` when
registering the event queue.
**Changes**: Deprecated in Zulip 5.0 (feature level 89), replaced by **Changes**: Deprecated in Zulip 5.0 (feature level 89), replaced by
the `user_settings` event type. the `user_settings` event type.
@ -8260,6 +8265,14 @@ paths:
New in Zulip 4.0 (feature level 58). This capability is New in Zulip 4.0 (feature level 58). This capability is
for backwards-compatibility; it will be required in a for backwards-compatibility; it will be required in a
future server release. future server release.
* `user_settings_object`: Boolean for whether the client supports the modern
`user_settings` event type. If False, the server will additionally send the
legacy `update_display_settings` and `update_global_notifications` event
types for backwards-compatibility with clients that predate this API migration.
New in Zulip 4.0 (feature level 89). This capability is for
backwards-compatibility; it will be removed in a future server release.
content: content:
application/json: application/json:
schema: schema:

View File

@ -241,6 +241,7 @@ class BaseAction(ZulipTestCase):
num_events: int = 1, num_events: int = 1,
bulk_message_deletion: bool = True, bulk_message_deletion: bool = True,
stream_typing_notifications: bool = True, stream_typing_notifications: bool = True,
user_settings_object: bool = False,
) -> List[Dict[str, Any]]: ) -> 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.
@ -267,6 +268,7 @@ class BaseAction(ZulipTestCase):
narrow=[], narrow=[],
bulk_message_deletion=bulk_message_deletion, bulk_message_deletion=bulk_message_deletion,
stream_typing_notifications=stream_typing_notifications, stream_typing_notifications=stream_typing_notifications,
user_settings_object=user_settings_object,
) )
) )
@ -2057,6 +2059,31 @@ class NormalActionsTest(BaseAction):
with self.assertRaises(RestartEventException): with self.assertRaises(RestartEventException):
self.verify_action(lambda: send_restart_events(immediate=True)) self.verify_action(lambda: send_restart_events(immediate=True))
def test_display_setting_event_not_sent(self) -> None:
events = self.verify_action(
lambda: do_set_user_display_setting(
self.user_profile,
"default_view",
"all_messages",
),
state_change_expected=True,
user_settings_object=True,
)
check_user_settings_update("events[0]", events[0])
def test_notification_setting_event_not_sent(self) -> None:
events = self.verify_action(
lambda: do_change_notification_settings(
self.user_profile,
"enable_sounds",
False,
acting_user=self.user_profile,
),
state_change_expected=True,
user_settings_object=True,
)
check_user_settings_update("events[0]", events[0])
class RealmPropertyActionTest(BaseAction): class RealmPropertyActionTest(BaseAction):
def do_set_realm_property_test(self, name: str) -> None: def do_set_realm_property_test(self, name: str) -> None:

View File

@ -75,6 +75,7 @@ def request_event_queue(
narrow: Iterable[Sequence[str]] = [], narrow: Iterable[Sequence[str]] = [],
bulk_message_deletion: bool = False, bulk_message_deletion: bool = False,
stream_typing_notifications: bool = False, stream_typing_notifications: bool = False,
user_settings_object: bool = False,
) -> Optional[str]: ) -> Optional[str]:
if not settings.USING_TORNADO: if not settings.USING_TORNADO:
@ -95,6 +96,7 @@ def request_event_queue(
"lifespan_secs": queue_lifespan_secs, "lifespan_secs": queue_lifespan_secs,
"bulk_message_deletion": orjson.dumps(bulk_message_deletion), "bulk_message_deletion": orjson.dumps(bulk_message_deletion),
"stream_typing_notifications": orjson.dumps(stream_typing_notifications), "stream_typing_notifications": orjson.dumps(stream_typing_notifications),
"user_settings_object": orjson.dumps(user_settings_object),
} }
if event_types is not None: if event_types is not None:

View File

@ -95,6 +95,7 @@ class ClientDescriptor:
narrow: Collection[Sequence[str]] = [], narrow: Collection[Sequence[str]] = [],
bulk_message_deletion: bool = False, bulk_message_deletion: bool = False,
stream_typing_notifications: bool = False, stream_typing_notifications: bool = False,
user_settings_object: bool = False,
) -> None: ) -> None:
# These objects are serialized on shutdown and restored on restart. # These objects are serialized on shutdown and restored on restart.
# If fields are added or semantics are changed, temporary code must be # If fields are added or semantics are changed, temporary code must be
@ -117,6 +118,7 @@ class ClientDescriptor:
self.narrow_filter = build_narrow_filter(narrow) self.narrow_filter = build_narrow_filter(narrow)
self.bulk_message_deletion = bulk_message_deletion self.bulk_message_deletion = bulk_message_deletion
self.stream_typing_notifications = stream_typing_notifications self.stream_typing_notifications = stream_typing_notifications
self.user_settings_object = user_settings_object
# 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.
@ -143,6 +145,7 @@ class ClientDescriptor:
client_type_name=self.client_type_name, client_type_name=self.client_type_name,
bulk_message_deletion=self.bulk_message_deletion, bulk_message_deletion=self.bulk_message_deletion,
stream_typing_notifications=self.stream_typing_notifications, stream_typing_notifications=self.stream_typing_notifications,
user_settings_object=self.user_settings_object,
) )
def __repr__(self) -> str: def __repr__(self) -> str:
@ -174,6 +177,7 @@ class ClientDescriptor:
d.get("narrow", []), d.get("narrow", []),
d.get("bulk_message_deletion", False), d.get("bulk_message_deletion", False),
d.get("stream_typing_notifications", False), d.get("stream_typing_notifications", False),
d.get("user_settings_object", False),
) )
ret.last_connection_time = d["last_connection_time"] ret.last_connection_time = d["last_connection_time"]
return ret return ret
@ -217,6 +221,14 @@ class ClientDescriptor:
# delivered if the stream_typing_notifications # delivered if the stream_typing_notifications
# client_capability is enabled, for backwards compatibility. # client_capability is enabled, for backwards compatibility.
return self.stream_typing_notifications return self.stream_typing_notifications
if self.user_settings_object and event["type"] in [
"update_display_settings",
"update_global_notifications",
]:
# 'update_display_settings' and 'update_global_notifications'
# events are sent only if user_settings_object is False,
# otherwise only 'user_settings' event is sent.
return False
return True return True
# TODO: Refactor so we don't need this function # TODO: Refactor so we don't need this function

View File

@ -103,6 +103,9 @@ def get_events_backend(
stream_typing_notifications: bool = REQ( stream_typing_notifications: bool = REQ(
default=False, json_validator=check_bool, intentionally_undocumented=True default=False, json_validator=check_bool, intentionally_undocumented=True
), ),
user_settings_object: bool = REQ(
default=False, json_validator=check_bool, intentionally_undocumented=True
),
) -> HttpResponse: ) -> HttpResponse:
if all_public_streams and not user_profile.can_access_public_streams(): if all_public_streams and not user_profile.can_access_public_streams():
raise JsonableError(_("User not authorized for this query")) raise JsonableError(_("User not authorized for this query"))
@ -147,6 +150,7 @@ def get_events_backend(
narrow=narrow, narrow=narrow,
bulk_message_deletion=bulk_message_deletion, bulk_message_deletion=bulk_message_deletion,
stream_typing_notifications=stream_typing_notifications, stream_typing_notifications=stream_typing_notifications,
user_settings_object=user_settings_object,
) )
result = fetch_events(events_query) result = fetch_events(events_query)

View File

@ -54,6 +54,7 @@ def events_register_backend(
("bulk_message_deletion", check_bool), ("bulk_message_deletion", check_bool),
("user_avatar_url_field_optional", check_bool), ("user_avatar_url_field_optional", check_bool),
("stream_typing_notifications", check_bool), ("stream_typing_notifications", check_bool),
("user_settings_object", check_bool),
], ],
value_validator=check_bool, value_validator=check_bool,
), ),