mirror of
https://github.com/zulip/zulip.git
synced 2025-10-23 04:52:12 +00:00
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:
committed by
Tim Abbott
parent
35ddb994e9
commit
14b1539647
@@ -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)
|
||||
|
@@ -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":
|
||||
|
@@ -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
|
||||
)
|
||||
|
Reference in New Issue
Block a user