From f4e6f2f89ba6bc28a2af64f75469bd24cbe4b464 Mon Sep 17 00:00:00 2001 From: Aman Agrawal Date: Mon, 19 May 2025 12:46:38 +0530 Subject: [PATCH] events: Add option to send partial data. Server can now send partial data to the client to help in developement. We don't want this to be widely used right now, hence no documentation changes have been made. This will likely be a check on client capability later. --- zerver/lib/users.py | 3 +++ zerver/models/users.py | 21 +++++++++++++++++++++ zerver/tests/test_users.py | 21 +++++++++++++++++++++ zproject/computed_settings.py | 2 ++ 4 files changed, 47 insertions(+) diff --git a/zerver/lib/users.py b/zerver/lib/users.py index 8f76e996e5..7cf3a25a61 100644 --- a/zerver/lib/users.py +++ b/zerver/lib/users.py @@ -43,6 +43,7 @@ from zerver.models.users import ( active_user_ids, base_bulk_get_user_queryset, base_get_user_queryset, + get_partial_realm_user_dicts, get_realm_user_dicts, get_realm_user_dicts_from_ids, get_user_by_id_in_realm_including_cross_realm, @@ -1058,6 +1059,8 @@ def get_user_dicts_in_realm( ) -> tuple[list[RawUserDict], list[APIUserDict]]: if user_ids is not None: all_user_dicts = get_realm_user_dicts_from_ids(realm.id, user_ids) + elif settings.PARTIAL_USERS: + all_user_dicts = get_partial_realm_user_dicts(realm.id, user_profile) else: all_user_dicts = get_realm_user_dicts(realm.id) if check_user_can_access_all_users(user_profile): diff --git a/zerver/models/users.py b/zerver/models/users.py index a8e82dd0e3..d7c347704c 100644 --- a/zerver/models/users.py +++ b/zerver/models/users.py @@ -1116,6 +1116,27 @@ def get_realm_user_dicts(realm_id: int) -> list[RawUserDict]: ) +def get_partial_realm_user_dicts( + realm_id: int, user_profile: UserProfile | None +) -> list[RawUserDict]: + """Returns a subset of the users in the realm, guaranteed to + include the current user as well as all bots in the realm. + """ + + # Currently, we send the minimum set of users permitted by the API. + user_selection_clause = Q(is_bot=True) + if user_profile is not None: + user_selection_clause |= Q(id=user_profile.id) + + return list( + UserProfile.objects.filter(realm_id=realm_id) + .filter( + user_selection_clause, + ) + .values(*realm_user_dict_fields) + ) + + def get_realm_user_dicts_from_ids(realm_id: int, user_ids: list[int]) -> list[RawUserDict]: return list( UserProfile.objects.filter( diff --git a/zerver/tests/test_users.py b/zerver/tests/test_users.py index 35b0b587b5..5dc7a23719 100644 --- a/zerver/tests/test_users.py +++ b/zerver/tests/test_users.py @@ -3165,6 +3165,17 @@ class GetProfileTest(ZulipTestCase): result = self.client_get(f"/json/users/{user.id}") self.assert_json_error(result, "Insufficient permission") + with self.settings(PARTIAL_USERS=True), self.assert_database_query_count(9): + result = self.client_get("/json/users") + self.assert_json_success(result) + + result_dict = orjson.loads(result.content) + all_fetched_users = result_dict["members"] + self.assertEqual( + len(all_fetched_users), + UserProfile.objects.filter(realm=hamlet.realm, is_bot=True).count() + 1, + ) + def test_get_inaccessible_user_ids(self) -> None: polonius = self.example_user("polonius") bot = self.example_user("default_bot") @@ -3232,6 +3243,16 @@ class GetProfileTest(ZulipTestCase): user_ids_to_fetch, ) + with self.settings(PARTIAL_USERS=True), self.assert_database_query_count(4): + result = self.client_get("/json/users") + self.assert_json_success(result) + result_dict = orjson.loads(result.content) + all_fetched_users = result_dict["members"] + self.assertEqual( + len(all_fetched_users), + UserProfile.objects.filter(realm=hamlet.realm, is_bot=True).count(), + ) + class DeleteUserTest(ZulipTestCase): def test_do_delete_user(self) -> None: diff --git a/zproject/computed_settings.py b/zproject/computed_settings.py index 18be84e12f..2815949255 100644 --- a/zproject/computed_settings.py +++ b/zproject/computed_settings.py @@ -1307,3 +1307,5 @@ SCIM_SERVICE_PROVIDER = { # Which API key to use will be determined based on TOPIC_SUMMARIZATION_MODEL. TOPIC_SUMMARIZATION_API_KEY = get_secret("topic_summarization_api_key", None) + +PARTIAL_USERS = bool(os.environ.get("PARTIAL_USERS"))