remote_activity: Add 'guest users' and 'non guest users' count column.

This commit adds two columns named 'Guest users' and
'Non guest users' to respresent count of such users.

We query 'RemoteRealmAuditLog' to get the data.
This commit is contained in:
Prakhar Pratyush
2023-12-05 13:39:29 +05:30
committed by Tim Abbott
parent 35ddb994e9
commit 14b1539647
3 changed files with 137 additions and 3 deletions

View File

@@ -1,9 +1,73 @@
from datetime import timedelta
from unittest import mock
from django.utils.timezone import now as timezone_now
from zerver.lib.test_classes import ZulipTestCase
from zerver.models import Client, UserActivity, UserProfile
from zilencer.models import RemoteRealmAuditLog, get_remote_server_guest_and_non_guest_count
event_time = timezone_now() - timedelta(days=3)
data_list = [
{
"server_id": 1,
"realm_id": 1,
"event_type": RemoteRealmAuditLog.USER_CREATED,
"event_time": event_time,
"extra_data": {
RemoteRealmAuditLog.ROLE_COUNT: {
RemoteRealmAuditLog.ROLE_COUNT_HUMANS: {
UserProfile.ROLE_REALM_ADMINISTRATOR: 10,
UserProfile.ROLE_REALM_OWNER: 10,
UserProfile.ROLE_MODERATOR: 10,
UserProfile.ROLE_MEMBER: 10,
UserProfile.ROLE_GUEST: 10,
}
}
},
},
{
"server_id": 1,
"realm_id": 1,
"event_type": RemoteRealmAuditLog.USER_ROLE_CHANGED,
"event_time": event_time,
"extra_data": {
RemoteRealmAuditLog.ROLE_COUNT: {
RemoteRealmAuditLog.ROLE_COUNT_HUMANS: {
UserProfile.ROLE_REALM_ADMINISTRATOR: 20,
UserProfile.ROLE_REALM_OWNER: 0,
UserProfile.ROLE_MODERATOR: 0,
UserProfile.ROLE_MEMBER: 20,
UserProfile.ROLE_GUEST: 10,
}
}
},
},
{
"server_id": 1,
"realm_id": 2,
"event_type": RemoteRealmAuditLog.USER_CREATED,
"event_time": event_time,
"extra_data": {
RemoteRealmAuditLog.ROLE_COUNT: {
RemoteRealmAuditLog.ROLE_COUNT_HUMANS: {
UserProfile.ROLE_REALM_ADMINISTRATOR: 10,
UserProfile.ROLE_REALM_OWNER: 10,
UserProfile.ROLE_MODERATOR: 0,
UserProfile.ROLE_MEMBER: 10,
UserProfile.ROLE_GUEST: 5,
}
}
},
},
{
"server_id": 1,
"realm_id": 2,
"event_type": RemoteRealmAuditLog.USER_CREATED,
"event_time": event_time,
"extra_data": {},
},
]
class ActivityTest(ZulipTestCase):
@@ -35,7 +99,8 @@ class ActivityTest(ZulipTestCase):
result = self.client_get("/activity")
self.assertEqual(result.status_code, 200)
with self.assert_database_query_count(4):
RemoteRealmAuditLog.objects.bulk_create([RemoteRealmAuditLog(**data) for data in data_list])
with self.assert_database_query_count(6):
result = self.client_get("/activity/remote")
self.assertEqual(result.status_code, 200)
@@ -51,3 +116,12 @@ class ActivityTest(ZulipTestCase):
with self.assert_database_query_count(5):
result = self.client_get(f"/user_activity/{iago.id}/")
self.assertEqual(result.status_code, 200)
def test_get_remote_server_guest_and_non_guest_count(self) -> None:
RemoteRealmAuditLog.objects.bulk_create([RemoteRealmAuditLog(**data) for data in data_list])
remote_server_counts = get_remote_server_guest_and_non_guest_count(
server_id=1, event_time=timezone_now()
)
self.assertEqual(remote_server_counts.non_guest_user_count, 70)
self.assertEqual(remote_server_counts.guest_user_count, 15)

View File

@@ -11,6 +11,7 @@ from analytics.views.activity_common import (
remote_installation_support_link,
)
from zerver.decorator import require_server_admin
from zilencer.models import get_remote_server_guest_and_non_guest_count
@require_server_admin
@@ -73,6 +74,8 @@ def get_remote_server_activity(request: HttpRequest) -> HttpResponse:
"Mobile users",
"Last update time",
"Mobile pushes forwarded",
"Non guest users",
"Guest users",
"Links",
]
@@ -83,6 +86,9 @@ def get_remote_server_activity(request: HttpRequest) -> HttpResponse:
stats = remote_installation_stats_link(row[0])
support = remote_installation_support_link(row[1])
links = stats + " " + support
remote_server_counts = get_remote_server_guest_and_non_guest_count(row[0])
row.append(remote_server_counts.non_guest_user_count)
row.append(remote_server_counts.guest_user_count)
row.append(links)
for i, col in enumerate(cols):
if col == "Last update time":

View File

@@ -1,18 +1,21 @@
# https://github.com/typeddjango/django-stubs/issues/1698
# mypy: disable-error-code="explicit-override"
from dataclasses import dataclass
from datetime import datetime
from typing import List, Tuple
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
from django.db.models import Q, UniqueConstraint
from django.db.models import Max, Q, UniqueConstraint
from django.utils.timezone import now as timezone_now
from typing_extensions import override
from analytics.models import BaseCount
from zerver.lib.rate_limiter import RateLimitedObject
from zerver.lib.rate_limiter import rules as rate_limiter_rules
from zerver.models import AbstractPushDeviceToken, AbstractRealmAuditLog, Realm
from zerver.models import AbstractPushDeviceToken, AbstractRealmAuditLog, Realm, UserProfile
def get_remote_server_by_uuid(uuid: str) -> "RemoteZulipServer":
@@ -309,3 +312,54 @@ class RateLimitedRemoteZulipServer(RateLimitedObject):
@override
def rules(self) -> List[Tuple[int, int]]:
return rate_limiter_rules[self.domain]
@dataclass
class RemoteCustomerUserCount:
guest_user_count: int
non_guest_user_count: int
def get_remote_server_guest_and_non_guest_count(
server_id: int, event_time: datetime = timezone_now()
) -> RemoteCustomerUserCount:
# For each realm hosted on the server, find the latest audit log
# entry indicating the number of active users in that realm.
realm_last_audit_log_ids = (
RemoteRealmAuditLog.objects.filter(
server_id=server_id,
event_type__in=RemoteRealmAuditLog.SYNCED_BILLING_EVENTS,
event_time__lte=event_time,
)
# Important: extra_data is empty for some pre-2020 audit logs
# prior to the introduction of realm_user_count_by_role
# logging. Meanwhile, modern Zulip servers using
# bulk_create_users to create the users in the system bot
# realm also generate such audit logs. Such audit logs should
# never be the latest in a normal realm.
.exclude(extra_data={})
.values("realm_id")
.annotate(max_id=Max("id"))
.values_list("max_id", flat=True)
)
extra_data_list = RemoteRealmAuditLog.objects.filter(
id__in=list(realm_last_audit_log_ids)
).values_list("extra_data", flat=True)
# Now we add up the user counts from the different realms.
guest_count = 0
non_guest_count = 0
for extra_data in extra_data_list:
humans_count_dict = extra_data[RemoteRealmAuditLog.ROLE_COUNT][
RemoteRealmAuditLog.ROLE_COUNT_HUMANS
]
for role_type in UserProfile.ROLE_TYPES:
if role_type == UserProfile.ROLE_GUEST:
guest_count += humans_count_dict[str(role_type)]
else:
non_guest_count += humans_count_dict[str(role_type)]
return RemoteCustomerUserCount(
non_guest_user_count=non_guest_count, guest_user_count=guest_count
)