user-status: Update `presence_enabled` with changes to user status `away`.

When a user toggles a status update for `away=True|False`, we now update
their `presence_enabled` setting to match (`away!=presence_enabled`).

First step of making user status `away` updates a deprecated way to
access presence_enabled for clients supporting older servers, and
checkpoint commit before migrating users with a current UserStatus
of `status=AWAY` to have their `presence_enabled` set to `False`.

Note that when user status `away` is updated, we now send 4 events:
user_status, user_settings, presence, and update_global_notifications.

Also, this means that these updates change the UserPresence.status
value, which impacts the test for importing and exporting user
information.

Part of transitioning from 'unavailable' user status feature to
'invisible mode' user presence feature.
This commit is contained in:
Lauryn Menard 2022-09-22 11:56:58 +02:00 committed by Tim Abbott
parent 3428fe86d6
commit 843eb4e4fc
4 changed files with 62 additions and 10 deletions

View File

@ -1,5 +1,6 @@
from typing import Optional from typing import Optional
from zerver.actions.user_settings import do_change_user_setting
from zerver.lib.user_status import update_user_status from zerver.lib.user_status import update_user_status
from zerver.models import UserProfile, UserStatus, active_user_ids from zerver.models import UserProfile, UserStatus, active_user_ids
from zerver.tornado.django_api import send_event from zerver.tornado.django_api import send_event
@ -14,6 +15,13 @@ def do_update_user_status(
emoji_code: Optional[str], emoji_code: Optional[str],
reaction_type: Optional[str], reaction_type: Optional[str],
) -> None: ) -> None:
# Deprecated way for clients to access the user's `presence_enabled`
# setting, with away != presence_enabled.
if away is not None:
user_setting = "presence_enabled"
value = not away
do_change_user_setting(user_profile, user_setting, value, acting_user=user_profile)
if away is None: if away is None:
status = None status = None
elif away: elif away:

View File

@ -1143,16 +1143,20 @@ class NormalActionsTest(BaseAction):
def test_away_events(self) -> None: def test_away_events(self) -> None:
client = get_client("website") client = get_client("website")
# Set all
away_val = True
events = self.verify_action( events = self.verify_action(
lambda: do_update_user_status( lambda: do_update_user_status(
user_profile=self.user_profile, user_profile=self.user_profile,
away=True, away=away_val,
status_text="out to lunch", status_text="out to lunch",
emoji_name="car", emoji_name="car",
emoji_code="1f697", emoji_code="1f697",
reaction_type=UserStatus.UNICODE_EMOJI, reaction_type=UserStatus.UNICODE_EMOJI,
client_id=client.id, client_id=client.id,
) ),
num_events=4,
) )
check_user_status( check_user_status(
@ -1160,16 +1164,29 @@ class NormalActionsTest(BaseAction):
events[0], events[0],
{"away", "status_text", "emoji_name", "emoji_code", "reaction_type"}, {"away", "status_text", "emoji_name", "emoji_code", "reaction_type"},
) )
check_user_settings_update("events[1]", events[1])
check_update_global_notifications("events[2]", events[2], not away_val)
check_presence(
"events[3]",
events[3],
has_email=True,
presence_key="website",
status="active" if not away_val else "idle",
)
# Remove all
away_val = False
events = self.verify_action( events = self.verify_action(
lambda: do_update_user_status( lambda: do_update_user_status(
user_profile=self.user_profile, user_profile=self.user_profile,
away=False, away=away_val,
status_text="", status_text="",
emoji_name="", emoji_name="",
emoji_code="", emoji_code="",
reaction_type=UserStatus.UNICODE_EMOJI, reaction_type=UserStatus.UNICODE_EMOJI,
client_id=client.id, client_id=client.id,
) ),
num_events=4,
) )
check_user_status( check_user_status(
@ -1177,21 +1194,43 @@ class NormalActionsTest(BaseAction):
events[0], events[0],
{"away", "status_text", "emoji_name", "emoji_code", "reaction_type"}, {"away", "status_text", "emoji_name", "emoji_code", "reaction_type"},
) )
check_user_settings_update("events[1]", events[1])
check_update_global_notifications("events[2]", events[2], not away_val)
check_presence(
"events[3]",
events[3],
has_email=True,
presence_key="website",
status="active" if not away_val else "idle",
)
# Only set away
away_val = True
events = self.verify_action( events = self.verify_action(
lambda: do_update_user_status( lambda: do_update_user_status(
user_profile=self.user_profile, user_profile=self.user_profile,
away=True, away=away_val,
status_text=None, status_text=None,
emoji_name=None, emoji_name=None,
emoji_code=None, emoji_code=None,
reaction_type=None, reaction_type=None,
client_id=client.id, client_id=client.id,
) ),
num_events=4,
) )
check_user_status("events[0]", events[0], {"away"}) check_user_status("events[0]", events[0], {"away"})
check_user_settings_update("events[1]", events[1])
check_update_global_notifications("events[2]", events[2], not away_val)
check_presence(
"events[3]",
events[3],
has_email=True,
presence_key="website",
status="active" if not away_val else "idle",
)
# Only set status_text
events = self.verify_action( events = self.verify_action(
lambda: do_update_user_status( lambda: do_update_user_status(
user_profile=self.user_profile, user_profile=self.user_profile,

View File

@ -1722,7 +1722,7 @@ class SingleUserExportTest(ExportFile):
do_update_user_status( do_update_user_status(
user_profile=cordelia, user_profile=cordelia,
away=True, away=None,
status_text="on vacation", status_text="on vacation",
client_id=client.id, client_id=client.id,
emoji_name=None, emoji_name=None,
@ -1744,7 +1744,7 @@ class SingleUserExportTest(ExportFile):
def zerver_userstatus(records: List[Record]) -> None: def zerver_userstatus(records: List[Record]) -> None:
rec = records[-1] rec = records[-1]
self.assertEqual(rec["status_text"], "on vacation") self.assertEqual(rec["status_text"], "on vacation")
self.assertEqual(rec["status"], UserStatus.AWAY) self.assertEqual(rec["status"], UserStatus.NORMAL)
do_mute_topic(cordelia, scotland, "bagpipe music") do_mute_topic(cordelia, scotland, "bagpipe music")
do_mute_topic(othello, scotland, "nessie") do_mute_topic(othello, scotland, "nessie")

View File

@ -174,10 +174,10 @@ class UserStatusTest(ZulipTestCase):
self.assertEqual(away_user_ids, {cordelia.id}) self.assertEqual(away_user_ids, {cordelia.id})
def update_status_and_assert_event( def update_status_and_assert_event(
self, payload: Dict[str, Any], expected_event: Dict[str, Any] self, payload: Dict[str, Any], expected_event: Dict[str, Any], num_events: int = 1
) -> None: ) -> None:
events: List[Mapping[str, Any]] = [] events: List[Mapping[str, Any]] = []
with self.tornado_redirected_to_list(events, expected_num_events=1): with self.tornado_redirected_to_list(events, expected_num_events=num_events):
result = self.client_post("/json/users/me/status", payload) result = self.client_post("/json/users/me/status", payload)
self.assert_json_success(result) self.assert_json_success(result)
self.assertEqual(events[0]["event"], expected_event) self.assertEqual(events[0]["event"], expected_event)
@ -232,6 +232,7 @@ class UserStatusTest(ZulipTestCase):
expected_event=dict( expected_event=dict(
type="user_status", user_id=hamlet.id, away=True, status_text="on vacation" type="user_status", user_id=hamlet.id, away=True, status_text="on vacation"
), ),
num_events=4,
) )
self.assertEqual( self.assertEqual(
user_status_info(hamlet), user_status_info(hamlet),
@ -252,6 +253,7 @@ class UserStatusTest(ZulipTestCase):
emoji_code="1f697", emoji_code="1f697",
reaction_type=UserStatus.UNICODE_EMOJI, reaction_type=UserStatus.UNICODE_EMOJI,
), ),
num_events=4,
) )
self.assertEqual( self.assertEqual(
user_status_info(hamlet), user_status_info(hamlet),
@ -278,6 +280,7 @@ class UserStatusTest(ZulipTestCase):
emoji_code="", emoji_code="",
reaction_type=UserStatus.UNICODE_EMOJI, reaction_type=UserStatus.UNICODE_EMOJI,
), ),
num_events=4,
) )
self.assertEqual( self.assertEqual(
user_status_info(hamlet), user_status_info(hamlet),
@ -288,6 +291,7 @@ class UserStatusTest(ZulipTestCase):
self.update_status_and_assert_event( self.update_status_and_assert_event(
payload=dict(away=orjson.dumps(False).decode()), payload=dict(away=orjson.dumps(False).decode()),
expected_event=dict(type="user_status", user_id=hamlet.id, away=False), expected_event=dict(type="user_status", user_id=hamlet.id, away=False),
num_events=4,
) )
away_user_ids = get_away_user_ids(realm_id=realm_id) away_user_ids = get_away_user_ids(realm_id=realm_id)
self.assertEqual(away_user_ids, set()) self.assertEqual(away_user_ids, set())
@ -318,6 +322,7 @@ class UserStatusTest(ZulipTestCase):
self.update_status_and_assert_event( self.update_status_and_assert_event(
payload=dict(away=orjson.dumps(True).decode()), payload=dict(away=orjson.dumps(True).decode()),
expected_event=dict(type="user_status", user_id=hamlet.id, away=True), expected_event=dict(type="user_status", user_id=hamlet.id, away=True),
num_events=4,
) )
away_user_ids = get_away_user_ids(realm_id=realm_id) away_user_ids = get_away_user_ids(realm_id=realm_id)
self.assertEqual(away_user_ids, {hamlet.id}) self.assertEqual(away_user_ids, {hamlet.id})