diff --git a/tests/conftest.py b/tests/conftest.py index c4dc1c6532..07e8313da2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1021,25 +1021,25 @@ def user_dict(logged_on_user: Dict[str, Any]) -> Dict[str, Dict[str, Any]]: "emailgateway@zulip.com": { "email": "emailgateway@zulip.com", "full_name": "Email Gateway", - "status": "inactive", + "status": "bot", "user_id": 6, }, "feedback@zulip.com": { "email": "feedback@zulip.com", "full_name": "Zulip Feedback Bot", - "status": "inactive", + "status": "bot", "user_id": 1, }, "notification-bot@zulip.com": { "email": "notification-bot@zulip.com", "full_name": "Notification Bot", - "status": "inactive", + "status": "bot", "user_id": 5, }, "welcome-bot@zulip.com": { "email": "welcome-bot@zulip.com", "full_name": "Welcome Bot", - "status": "inactive", + "status": "bot", "user_id": 4, }, } @@ -1075,12 +1075,6 @@ def user_list(logged_on_user: Dict[str, Any]) -> List[Dict[str, Any]]: "status": "active", "user_id": logged_on_user["user_id"], }, - { - "email": "emailgateway@zulip.com", - "full_name": "Email Gateway", - "status": "inactive", - "user_id": 6, - }, { "full_name": "Human 1", "email": "person1@example.com", @@ -1105,22 +1099,28 @@ def user_list(logged_on_user: Dict[str, Any]) -> List[Dict[str, Any]]: "user_id": 14, "status": "inactive", }, + { + "email": "emailgateway@zulip.com", + "full_name": "Email Gateway", + "status": "bot", + "user_id": 6, + }, { "email": "notification-bot@zulip.com", "full_name": "Notification Bot", - "status": "inactive", + "status": "bot", "user_id": 5, }, { "email": "welcome-bot@zulip.com", "full_name": "Welcome Bot", - "status": "inactive", + "status": "bot", "user_id": 4, }, { "email": "feedback@zulip.com", "full_name": "Zulip Feedback Bot", - "status": "inactive", + "status": "bot", "user_id": 1, }, ] diff --git a/zulipterminal/config/symbols.py b/zulipterminal/config/symbols.py index 4870d9ca7a..2b49e8e429 100644 --- a/zulipterminal/config/symbols.py +++ b/zulipterminal/config/symbols.py @@ -23,5 +23,6 @@ STATUS_IDLE = "◒" STATUS_OFFLINE = "○" STATUS_INACTIVE = "•" +BOT_MARKER = "♟" AUTOHIDE_TAB_LEFT_ARROW = "❰" AUTOHIDE_TAB_RIGHT_ARROW = "❱" diff --git a/zulipterminal/config/themes.py b/zulipterminal/config/themes.py index 5872572adb..3d497294ca 100644 --- a/zulipterminal/config/themes.py +++ b/zulipterminal/config/themes.py @@ -35,6 +35,7 @@ 'user_idle' : '', 'user_offline' : '', 'user_inactive' : '', + 'user_bot' : '', 'title' : 'bold', 'column_title' : 'bold', 'time' : '', diff --git a/zulipterminal/config/ui_mappings.py b/zulipterminal/config/ui_mappings.py index 22d6092dca..8f94c5f3d6 100644 --- a/zulipterminal/config/ui_mappings.py +++ b/zulipterminal/config/ui_mappings.py @@ -8,6 +8,7 @@ from zulipterminal.api_types import EditPropagateMode from zulipterminal.config.symbols import ( + BOT_MARKER, STATUS_ACTIVE, STATUS_IDLE, STATUS_INACTIVE, @@ -25,12 +26,16 @@ } +UserStatus = Literal["active", "idle", "offline", "inactive", "bot"] + # Mapping that binds user activity status to corresponding markers. -STATE_ICON = { +# NOTE: Ordering of keys affects display order +STATE_ICON: Dict[UserStatus, str] = { "active": STATUS_ACTIVE, "idle": STATUS_IDLE, "offline": STATUS_OFFLINE, "inactive": STATUS_INACTIVE, + "bot": BOT_MARKER, } diff --git a/zulipterminal/model.py b/zulipterminal/model.py index f82556380f..999be50cc4 100644 --- a/zulipterminal/model.py +++ b/zulipterminal/model.py @@ -2,6 +2,7 @@ Defines the `Model`, fetching and storing data retrieved from the Zulip server """ +import itertools import json import time from collections import defaultdict @@ -46,6 +47,7 @@ from zulipterminal.config.ui_mappings import ( EDIT_TOPIC_POLICY, ROLE_BY_ID, + STATE_ICON, StreamAccessType, ) from zulipterminal.helper import ( @@ -1033,7 +1035,10 @@ def get_all_users(self) -> List[Dict[str, Any]]: } continue email = user["email"] - if email in presences: # presences currently subset of all users + if user["is_bot"]: + # Bot has no dynamic status, so avoid presence lookup + status = "bot" + elif email in presences: # presences currently subset of all users """ * Aggregate our information on a user's presence across their * clients. @@ -1084,6 +1089,7 @@ def get_all_users(self) -> List[Dict[str, Any]]: "user_id": user["user_id"], "status": status, } + self._all_users_by_id[user["user_id"]] = user self.user_id_email_dict[user["user_id"]] = email @@ -1094,41 +1100,33 @@ def get_all_users(self) -> List[Dict[str, Any]]: "full_name": bot["full_name"], "email": email, "user_id": bot["user_id"], - "status": "inactive", + "status": "bot", } self._cross_realm_bots_by_id[bot["user_id"]] = bot self._all_users_by_id[bot["user_id"]] = bot self.user_id_email_dict[bot["user_id"]] = email - # Generate filtered lists for active & idle users - active = [ - properties - for properties in self.user_dict.values() - if properties["status"] == "active" - ] - idle = [ - properties - for properties in self.user_dict.values() - if properties["status"] == "idle" - ] - offline = [ - properties - for properties in self.user_dict.values() - if properties["status"] == "offline" - ] - inactive = [ - properties - for properties in self.user_dict.values() - if properties["status"] == "inactive" - ] + # Generate filtered lists for each status + ordered_statuses = list(STATE_ICON.keys()) + presences_by_status = { + status: sorted( + [ + properties + for properties in self.user_dict.values() + if properties["status"] == status + ], + key=lambda user: user["full_name"].casefold(), + ) + for status in ordered_statuses + } + user_list = list( + itertools.chain.from_iterable( + presences_by_status[status] for status in ordered_statuses + ) + ) + user_list.insert(0, current_user) # Add current user to the top of the list - # Construct user_list from sorted components of each list - user_list = sorted(active, key=lambda u: u["full_name"].casefold()) - user_list += sorted(idle, key=lambda u: u["full_name"].casefold()) - user_list += sorted(offline, key=lambda u: u["full_name"].casefold()) - user_list += sorted(inactive, key=lambda u: u["full_name"].casefold()) - # Add current user to the top of the list - user_list.insert(0, current_user) + # NOTE: Do this after generating user_list to avoid current_user duplication self.user_dict[current_user["email"]] = current_user self.user_id_email_dict[self.user_id] = current_user["email"] diff --git a/zulipterminal/themes/gruvbox_dark.py b/zulipterminal/themes/gruvbox_dark.py index 5e0c232521..1abd77906e 100644 --- a/zulipterminal/themes/gruvbox_dark.py +++ b/zulipterminal/themes/gruvbox_dark.py @@ -30,6 +30,7 @@ 'user_idle' : (Color.NEUTRAL_YELLOW, Color.DARK0_HARD), 'user_offline' : (Color.LIGHT2, Color.DARK0_HARD), 'user_inactive' : (Color.LIGHT2, Color.DARK0_HARD), + 'user_bot' : (Color.LIGHT2, Color.DARK0_HARD), 'title' : (Color.LIGHT2__BOLD, Color.DARK0_HARD), 'column_title' : (Color.LIGHT2__BOLD, Color.DARK0_HARD), 'time' : (Color.BRIGHT_BLUE, Color.DARK0_HARD), diff --git a/zulipterminal/themes/gruvbox_light.py b/zulipterminal/themes/gruvbox_light.py index 7c536de97a..e477a9f086 100644 --- a/zulipterminal/themes/gruvbox_light.py +++ b/zulipterminal/themes/gruvbox_light.py @@ -29,6 +29,7 @@ 'user_idle' : (Color.NEUTRAL_YELLOW, Color.LIGHT0_HARD), 'user_offline' : (Color.DARK2, Color.LIGHT0_HARD), 'user_inactive' : (Color.DARK2, Color.LIGHT0_HARD), + 'user_bot' : (Color.DARK2, Color.LIGHT0_HARD), 'title' : (Color.DARK2__BOLD, Color.LIGHT0_HARD), 'column_title' : (Color.DARK2__BOLD, Color.LIGHT0_HARD), 'time' : (Color.FADED_BLUE, Color.LIGHT0_HARD), diff --git a/zulipterminal/themes/zt_blue.py b/zulipterminal/themes/zt_blue.py index d6fed8b0cc..eb99c8dc0a 100644 --- a/zulipterminal/themes/zt_blue.py +++ b/zulipterminal/themes/zt_blue.py @@ -24,6 +24,7 @@ 'user_idle' : (Color.DARK_GRAY, Color.LIGHT_BLUE), 'user_offline' : (Color.BLACK, Color.LIGHT_BLUE), 'user_inactive' : (Color.BLACK, Color.LIGHT_BLUE), + 'user_bot' : (Color.BLACK, Color.LIGHT_BLUE), 'title' : (Color.WHITE__BOLD, Color.DARK_BLUE), 'column_title' : (Color.BLACK__BOLD, Color.LIGHT_BLUE), 'time' : (Color.DARK_BLUE, Color.LIGHT_BLUE), diff --git a/zulipterminal/themes/zt_dark.py b/zulipterminal/themes/zt_dark.py index 36791644ee..69a5f4ad75 100644 --- a/zulipterminal/themes/zt_dark.py +++ b/zulipterminal/themes/zt_dark.py @@ -24,6 +24,7 @@ 'user_idle' : (Color.YELLOW, Color.BLACK), 'user_offline' : (Color.WHITE, Color.BLACK), 'user_inactive' : (Color.WHITE, Color.BLACK), + 'user_bot' : (Color.WHITE, Color.BLACK), 'title' : (Color.WHITE__BOLD, Color.BLACK), 'column_title' : (Color.WHITE__BOLD, Color.BLACK), 'time' : (Color.LIGHT_BLUE, Color.BLACK), diff --git a/zulipterminal/themes/zt_light.py b/zulipterminal/themes/zt_light.py index 1ca6b94547..6b0ee5709a 100644 --- a/zulipterminal/themes/zt_light.py +++ b/zulipterminal/themes/zt_light.py @@ -24,6 +24,7 @@ 'user_idle' : (Color.DARK_BLUE, Color.WHITE), 'user_offline' : (Color.BLACK, Color.WHITE), 'user_inactive' : (Color.BLACK, Color.WHITE), + 'user_bot' : (Color.BLACK, Color.WHITE), 'title' : (Color.WHITE__BOLD, Color.DARK_GRAY), 'column_title' : (Color.BLACK__BOLD, Color.WHITE), 'time' : (Color.DARK_BLUE, Color.WHITE),