diff --git a/analytics/tests/test_counts.py b/analytics/tests/test_counts.py index fd9836e01b..ca72728e03 100644 --- a/analytics/tests/test_counts.py +++ b/analytics/tests/test_counts.py @@ -37,6 +37,7 @@ from zerver.actions.invites import ( do_resend_user_invite_email, do_revoke_user_invite, ) +from zerver.actions.user_activity import update_user_activity_interval from zerver.lib.actions import ( do_activate_mirror_dummy_user, do_create_realm, @@ -46,7 +47,6 @@ from zerver.lib.actions import ( do_mark_stream_messages_as_read, do_reactivate_user, do_update_message_flags, - update_user_activity_interval, ) from zerver.lib.create_user import create_user from zerver.lib.exceptions import InvitationError diff --git a/zerver/actions/user_activity.py b/zerver/actions/user_activity.py new file mode 100644 index 0000000000..33bc606bd9 --- /dev/null +++ b/zerver/actions/user_activity.py @@ -0,0 +1,54 @@ +import datetime + +from zerver.decorator import statsd_increment +from zerver.lib.queue import queue_json_publish +from zerver.lib.timestamp import datetime_to_timestamp +from zerver.models import UserActivity, UserActivityInterval, UserProfile + + +def do_update_user_activity_interval( + user_profile: UserProfile, log_time: datetime.datetime +) -> None: + effective_end = log_time + UserActivityInterval.MIN_INTERVAL_LENGTH + # This code isn't perfect, because with various races we might end + # up creating two overlapping intervals, but that shouldn't happen + # often, and can be corrected for in post-processing + try: + last = UserActivityInterval.objects.filter(user_profile=user_profile).order_by("-end")[0] + # Two intervals overlap iff each interval ends after the other + # begins. In this case, we just extend the old interval to + # include the new interval. + if log_time <= last.end and effective_end >= last.start: + last.end = max(last.end, effective_end) + last.start = min(last.start, log_time) + last.save(update_fields=["start", "end"]) + return + except IndexError: + pass + + # Otherwise, the intervals don't overlap, so we should make a new one + UserActivityInterval.objects.create( + user_profile=user_profile, start=log_time, end=effective_end + ) + + +@statsd_increment("user_activity") +def do_update_user_activity( + user_profile_id: int, client_id: int, query: str, count: int, log_time: datetime.datetime +) -> None: + (activity, created) = UserActivity.objects.get_or_create( + user_profile_id=user_profile_id, + client_id=client_id, + query=query, + defaults={"last_visit": log_time, "count": count}, + ) + + if not created: + activity.count += count + activity.last_visit = log_time + activity.save(update_fields=["last_visit", "count"]) + + +def update_user_activity_interval(user_profile: UserProfile, log_time: datetime.datetime) -> None: + event = {"user_profile_id": user_profile.id, "time": datetime_to_timestamp(log_time)} + queue_json_publish("user_activity_interval", event) diff --git a/zerver/lib/actions.py b/zerver/lib/actions.py index 9c873573df..b1900fc70c 100644 --- a/zerver/lib/actions.py +++ b/zerver/lib/actions.py @@ -45,6 +45,7 @@ from zerver.actions.default_streams import ( get_default_streams_for_realm, ) from zerver.actions.invites import notify_invites_changed, revoke_invites_generated_by_user +from zerver.actions.user_activity import update_user_activity_interval from zerver.actions.user_groups import ( do_send_user_group_members_update_event, update_users_in_full_members_system_group, @@ -216,8 +217,6 @@ from zerver.models import ( Service, Stream, Subscription, - UserActivity, - UserActivityInterval, UserGroup, UserGroupMembership, UserMessage, @@ -5397,49 +5396,6 @@ def get_default_subs(user_profile: UserProfile) -> List[Stream]: return get_default_streams_for_realm(user_profile.realm_id) -def do_update_user_activity_interval( - user_profile: UserProfile, log_time: datetime.datetime -) -> None: - effective_end = log_time + UserActivityInterval.MIN_INTERVAL_LENGTH - # This code isn't perfect, because with various races we might end - # up creating two overlapping intervals, but that shouldn't happen - # often, and can be corrected for in post-processing - try: - last = UserActivityInterval.objects.filter(user_profile=user_profile).order_by("-end")[0] - # Two intervals overlap iff each interval ends after the other - # begins. In this case, we just extend the old interval to - # include the new interval. - if log_time <= last.end and effective_end >= last.start: - last.end = max(last.end, effective_end) - last.start = min(last.start, log_time) - last.save(update_fields=["start", "end"]) - return - except IndexError: - pass - - # Otherwise, the intervals don't overlap, so we should make a new one - UserActivityInterval.objects.create( - user_profile=user_profile, start=log_time, end=effective_end - ) - - -@statsd_increment("user_activity") -def do_update_user_activity( - user_profile_id: int, client_id: int, query: str, count: int, log_time: datetime.datetime -) -> None: - (activity, created) = UserActivity.objects.get_or_create( - user_profile_id=user_profile_id, - client_id=client_id, - query=query, - defaults={"last_visit": log_time, "count": count}, - ) - - if not created: - activity.count += count - activity.last_visit = log_time - activity.save(update_fields=["last_visit", "count"]) - - def send_presence_changed(user_profile: UserProfile, presence: UserPresence) -> None: # Most presence data is sent to clients in the main presence # endpoint in response to the user's own presence; this results @@ -5534,11 +5490,6 @@ def do_update_user_presence( send_presence_changed(user_profile, presence) -def update_user_activity_interval(user_profile: UserProfile, log_time: datetime.datetime) -> None: - event = {"user_profile_id": user_profile.id, "time": datetime_to_timestamp(log_time)} - queue_json_publish("user_activity_interval", event) - - def update_user_presence( user_profile: UserProfile, client: Client, diff --git a/zerver/tests/test_import_export.py b/zerver/tests/test_import_export.py index 3e7e2fe777..85357fd989 100644 --- a/zerver/tests/test_import_export.py +++ b/zerver/tests/test_import_export.py @@ -14,6 +14,7 @@ from zerver.actions.alert_words import do_add_alert_words from zerver.actions.realm_emoji import check_add_realm_emoji from zerver.actions.realm_icon import do_change_icon_source from zerver.actions.realm_logo import do_change_logo_source +from zerver.actions.user_activity import do_update_user_activity, do_update_user_activity_interval from zerver.actions.user_topics import do_mute_topic from zerver.lib import upload from zerver.lib.actions import ( @@ -23,8 +24,6 @@ from zerver.lib.actions import ( do_create_user, do_deactivate_user, do_mute_user, - do_update_user_activity, - do_update_user_activity_interval, do_update_user_custom_profile_data_if_changed, do_update_user_presence, do_update_user_status, diff --git a/zerver/worker/queue_processors.py b/zerver/worker/queue_processors.py index 840d85f93c..ae69ef3afe 100644 --- a/zerver/worker/queue_processors.py +++ b/zerver/worker/queue_processors.py @@ -48,12 +48,11 @@ from zulip_bots.lib import extract_query_without_mention from zerver.actions.invites import do_send_confirmation_email from zerver.actions.realm_export import notify_realm_export +from zerver.actions.user_activity import do_update_user_activity, do_update_user_activity_interval from zerver.context_processors import common_context from zerver.lib.actions import ( do_mark_stream_messages_as_read, do_update_embedded_data, - do_update_user_activity, - do_update_user_activity_interval, do_update_user_presence, internal_send_private_message, render_incoming_message,