2021-06-18 00:07:45 +02:00
|
|
|
import itertools
|
2024-01-16 14:29:03 +01:00
|
|
|
import re
|
2024-01-16 15:11:10 +01:00
|
|
|
from dataclasses import dataclass
|
2021-06-18 00:07:45 +02:00
|
|
|
from datetime import datetime
|
2024-07-12 02:30:17 +02:00
|
|
|
from typing import Any, Collection, Optional
|
2021-06-18 00:07:45 +02:00
|
|
|
|
2023-03-04 01:52:14 +01:00
|
|
|
from django.db.models import QuerySet
|
2021-06-18 00:07:45 +02:00
|
|
|
from django.http import HttpRequest, HttpResponse, HttpResponseNotFound
|
|
|
|
from django.shortcuts import render
|
|
|
|
from django.utils.timezone import now as timezone_now
|
2024-01-16 15:11:10 +01:00
|
|
|
from markupsafe import Markup
|
2021-06-18 00:07:45 +02:00
|
|
|
|
2024-01-29 13:47:19 +01:00
|
|
|
from corporate.lib.activity import (
|
2024-02-05 19:07:13 +01:00
|
|
|
format_optional_datetime,
|
2021-06-18 00:07:45 +02:00
|
|
|
make_table,
|
2022-12-02 14:40:26 +01:00
|
|
|
realm_stats_link,
|
2021-06-18 00:07:45 +02:00
|
|
|
user_activity_link,
|
|
|
|
)
|
|
|
|
from zerver.decorator import require_server_admin
|
|
|
|
from zerver.models import Realm, UserActivity
|
2024-01-16 15:11:10 +01:00
|
|
|
from zerver.models.users import UserProfile
|
2021-06-18 00:07:45 +02:00
|
|
|
|
|
|
|
|
2024-01-16 15:11:10 +01:00
|
|
|
@dataclass
|
|
|
|
class UserActivitySummary:
|
|
|
|
user_name: str
|
|
|
|
user_id: int
|
|
|
|
user_type: str
|
|
|
|
messages_sent: int
|
|
|
|
last_heard_from: Optional[datetime]
|
|
|
|
last_message_sent: Optional[datetime]
|
|
|
|
|
|
|
|
|
|
|
|
def get_user_activity_records_for_realm(realm: str) -> QuerySet[UserActivity]:
|
2021-06-18 00:07:45 +02:00
|
|
|
fields = [
|
|
|
|
"user_profile__full_name",
|
|
|
|
"user_profile__delivery_email",
|
2024-01-16 15:11:10 +01:00
|
|
|
"user_profile__is_bot",
|
|
|
|
"user_profile__bot_type",
|
2021-06-18 00:07:45 +02:00
|
|
|
"query",
|
|
|
|
"count",
|
|
|
|
"last_visit",
|
|
|
|
]
|
|
|
|
|
2024-01-16 15:11:10 +01:00
|
|
|
records = (
|
|
|
|
UserActivity.objects.filter(
|
|
|
|
user_profile__realm__string_id=realm,
|
|
|
|
user_profile__is_active=True,
|
|
|
|
)
|
|
|
|
.order_by("user_profile__delivery_email", "-last_visit")
|
|
|
|
.select_related("user_profile")
|
|
|
|
.only(*fields)
|
2021-06-18 00:07:45 +02:00
|
|
|
)
|
|
|
|
return records
|
|
|
|
|
|
|
|
|
2024-01-16 15:11:10 +01:00
|
|
|
def get_user_activity_summary(records: Collection[UserActivity]) -> UserActivitySummary:
|
2024-01-16 14:29:03 +01:00
|
|
|
if records:
|
|
|
|
first_record = next(iter(records))
|
2024-01-16 15:11:10 +01:00
|
|
|
name = first_record.user_profile.full_name
|
|
|
|
user_profile_id = first_record.user_profile.id
|
|
|
|
if not first_record.user_profile.is_bot:
|
|
|
|
user_type = "Human"
|
|
|
|
else:
|
|
|
|
assert first_record.user_profile.bot_type is not None
|
|
|
|
bot_type = first_record.user_profile.bot_type
|
|
|
|
user_type = UserProfile.BOT_TYPES[bot_type]
|
|
|
|
|
|
|
|
messages = 0
|
|
|
|
heard_from: Optional[datetime] = None
|
|
|
|
last_sent: Optional[datetime] = None
|
2024-01-16 14:29:03 +01:00
|
|
|
|
|
|
|
for record in records:
|
|
|
|
query = str(record.query)
|
2024-01-16 15:11:10 +01:00
|
|
|
visit = record.last_visit
|
2024-01-16 14:29:03 +01:00
|
|
|
|
2024-01-16 15:11:10 +01:00
|
|
|
if heard_from is None:
|
|
|
|
heard_from = visit
|
|
|
|
else:
|
|
|
|
heard_from = max(visit, heard_from)
|
2024-01-16 14:29:03 +01:00
|
|
|
|
2024-04-26 20:30:22 +02:00
|
|
|
if ("send_message" in query) or re.search(r"/api/.*/external/.*", query):
|
2024-03-09 07:56:59 +01:00
|
|
|
messages += record.count
|
2024-01-16 15:11:10 +01:00
|
|
|
if last_sent is None:
|
|
|
|
last_sent = visit
|
|
|
|
else:
|
|
|
|
last_sent = max(visit, last_sent)
|
|
|
|
|
|
|
|
return UserActivitySummary(
|
|
|
|
user_name=name,
|
|
|
|
user_id=user_profile_id,
|
|
|
|
user_type=user_type,
|
|
|
|
messages_sent=messages,
|
|
|
|
last_heard_from=heard_from,
|
|
|
|
last_message_sent=last_sent,
|
|
|
|
)
|
2024-01-16 14:29:03 +01:00
|
|
|
|
|
|
|
|
2021-06-18 00:07:45 +02:00
|
|
|
def realm_user_summary_table(
|
2024-07-12 02:30:17 +02:00
|
|
|
all_records: QuerySet[UserActivity], admin_emails: set[str], title: str, stats_link: Markup
|
2024-01-16 15:11:10 +01:00
|
|
|
) -> str:
|
2024-07-12 02:30:17 +02:00
|
|
|
user_records: dict[str, UserActivitySummary] = {}
|
2021-06-18 00:07:45 +02:00
|
|
|
|
2022-06-15 23:55:20 +02:00
|
|
|
def by_email(record: UserActivity) -> str:
|
2021-06-18 00:07:45 +02:00
|
|
|
return record.user_profile.delivery_email
|
|
|
|
|
|
|
|
for email, records in itertools.groupby(all_records, by_email):
|
|
|
|
user_records[email] = get_user_activity_summary(list(records))
|
|
|
|
|
2021-07-26 17:32:10 +02:00
|
|
|
def is_recent(val: datetime) -> bool:
|
2021-06-18 00:07:45 +02:00
|
|
|
age = timezone_now() - val
|
|
|
|
return age.total_seconds() < 5 * 60
|
|
|
|
|
2024-01-16 15:11:10 +01:00
|
|
|
cols = [
|
|
|
|
"Name",
|
|
|
|
"Email",
|
|
|
|
"User type",
|
|
|
|
"Messages sent",
|
2024-02-05 19:07:13 +01:00
|
|
|
"Last heard from (UTC)",
|
|
|
|
"Last message sent (UTC)",
|
2024-01-16 15:11:10 +01:00
|
|
|
]
|
|
|
|
|
2021-06-18 00:07:45 +02:00
|
|
|
rows = []
|
|
|
|
for email, user_summary in user_records.items():
|
2024-01-16 15:11:10 +01:00
|
|
|
email_link = user_activity_link(email, user_summary.user_id)
|
|
|
|
cells = [
|
|
|
|
user_summary.user_name,
|
|
|
|
email_link,
|
|
|
|
user_summary.user_type,
|
|
|
|
user_summary.messages_sent,
|
|
|
|
]
|
2024-02-05 19:07:13 +01:00
|
|
|
cells.append(format_optional_datetime(user_summary.last_heard_from))
|
|
|
|
cells.append(format_optional_datetime(user_summary.last_message_sent))
|
2024-01-16 15:11:10 +01:00
|
|
|
|
2021-06-18 00:07:45 +02:00
|
|
|
row_class = ""
|
2024-01-16 15:11:10 +01:00
|
|
|
if user_summary.last_heard_from and is_recent(user_summary.last_heard_from):
|
|
|
|
row_class += " recently_active"
|
|
|
|
if email in admin_emails:
|
|
|
|
row_class += " admin"
|
|
|
|
|
2021-06-18 00:07:45 +02:00
|
|
|
row = dict(cells=cells, row_class=row_class)
|
|
|
|
rows.append(row)
|
|
|
|
|
2024-07-12 02:30:17 +02:00
|
|
|
def by_last_heard_from(row: dict[str, Any]) -> str:
|
2024-01-16 15:11:10 +01:00
|
|
|
return row["cells"][4]
|
2021-06-18 00:07:45 +02:00
|
|
|
|
2024-01-16 15:11:10 +01:00
|
|
|
rows = sorted(rows, key=by_last_heard_from, reverse=True)
|
2024-01-25 15:42:50 +01:00
|
|
|
content = make_table(title, cols, rows, stats_link=stats_link, has_row_class=True)
|
2024-01-16 15:11:10 +01:00
|
|
|
return content
|
2021-06-18 00:07:45 +02:00
|
|
|
|
|
|
|
|
|
|
|
@require_server_admin
|
|
|
|
def get_realm_activity(request: HttpRequest, realm_str: str) -> HttpResponse:
|
|
|
|
try:
|
|
|
|
admins = Realm.objects.get(string_id=realm_str).get_human_admin_users()
|
|
|
|
except Realm.DoesNotExist:
|
|
|
|
return HttpResponseNotFound()
|
|
|
|
|
|
|
|
admin_emails = {admin.delivery_email for admin in admins}
|
2024-01-16 15:11:10 +01:00
|
|
|
all_records = get_user_activity_records_for_realm(realm_str)
|
2022-12-02 14:40:26 +01:00
|
|
|
realm_stats = realm_stats_link(realm_str)
|
2024-01-16 15:11:10 +01:00
|
|
|
title = realm_str
|
|
|
|
content = realm_user_summary_table(all_records, admin_emails, title, realm_stats)
|
2022-12-02 14:40:26 +01:00
|
|
|
|
2021-06-18 00:07:45 +02:00
|
|
|
return render(
|
|
|
|
request,
|
2024-01-29 14:53:53 +01:00
|
|
|
"corporate/activity/activity.html",
|
2024-01-16 15:11:10 +01:00
|
|
|
context=dict(
|
|
|
|
data=content,
|
|
|
|
title=title,
|
|
|
|
is_home=False,
|
|
|
|
),
|
2021-06-18 00:07:45 +02:00
|
|
|
)
|