mirror of https://github.com/zulip/zulip.git
175 lines
5.1 KiB
Python
175 lines
5.1 KiB
Python
import itertools
|
|
import re
|
|
from collections.abc import Collection
|
|
from dataclasses import dataclass
|
|
from datetime import datetime
|
|
from typing import Any
|
|
|
|
from django.db.models import QuerySet
|
|
from django.http import HttpRequest, HttpResponse, HttpResponseNotFound
|
|
from django.shortcuts import render
|
|
from django.utils.timezone import now as timezone_now
|
|
from markupsafe import Markup
|
|
|
|
from corporate.lib.activity import (
|
|
format_optional_datetime,
|
|
make_table,
|
|
realm_stats_link,
|
|
user_activity_link,
|
|
)
|
|
from zerver.decorator import require_server_admin
|
|
from zerver.models import Realm, UserActivity
|
|
from zerver.models.users import UserProfile
|
|
|
|
|
|
@dataclass
|
|
class UserActivitySummary:
|
|
user_name: str
|
|
user_id: int
|
|
user_type: str
|
|
messages_sent: int
|
|
last_heard_from: datetime | None
|
|
last_message_sent: datetime | None
|
|
|
|
|
|
def get_user_activity_records_for_realm(realm: str) -> QuerySet[UserActivity]:
|
|
fields = [
|
|
"user_profile__full_name",
|
|
"user_profile__delivery_email",
|
|
"user_profile__is_bot",
|
|
"user_profile__bot_type",
|
|
"query",
|
|
"count",
|
|
"last_visit",
|
|
]
|
|
|
|
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)
|
|
)
|
|
return records
|
|
|
|
|
|
def get_user_activity_summary(records: Collection[UserActivity]) -> UserActivitySummary:
|
|
if records:
|
|
first_record = next(iter(records))
|
|
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: datetime | None = None
|
|
last_sent: datetime | None = None
|
|
|
|
for record in records:
|
|
query = str(record.query)
|
|
visit = record.last_visit
|
|
|
|
if heard_from is None:
|
|
heard_from = visit
|
|
else:
|
|
heard_from = max(visit, heard_from)
|
|
|
|
if ("send_message" in query) or re.search(r"/api/.*/external/.*", query):
|
|
messages += record.count
|
|
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,
|
|
)
|
|
|
|
|
|
def realm_user_summary_table(
|
|
all_records: QuerySet[UserActivity], admin_emails: set[str], title: str, stats_link: Markup
|
|
) -> str:
|
|
user_records: dict[str, UserActivitySummary] = {}
|
|
|
|
def by_email(record: UserActivity) -> str:
|
|
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))
|
|
|
|
def is_recent(val: datetime) -> bool:
|
|
age = timezone_now() - val
|
|
return age.total_seconds() < 5 * 60
|
|
|
|
cols = [
|
|
"Name",
|
|
"Email",
|
|
"User type",
|
|
"Messages sent",
|
|
"Last heard from (UTC)",
|
|
"Last message sent (UTC)",
|
|
]
|
|
|
|
rows = []
|
|
for email, user_summary in user_records.items():
|
|
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,
|
|
]
|
|
cells.append(format_optional_datetime(user_summary.last_heard_from))
|
|
cells.append(format_optional_datetime(user_summary.last_message_sent))
|
|
|
|
row_class = ""
|
|
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"
|
|
|
|
row = dict(cells=cells, row_class=row_class)
|
|
rows.append(row)
|
|
|
|
def by_last_heard_from(row: dict[str, Any]) -> str:
|
|
return row["cells"][4]
|
|
|
|
rows = sorted(rows, key=by_last_heard_from, reverse=True)
|
|
content = make_table(title, cols, rows, stats_link=stats_link, has_row_class=True)
|
|
return content
|
|
|
|
|
|
@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}
|
|
all_records = get_user_activity_records_for_realm(realm_str)
|
|
realm_stats = realm_stats_link(realm_str)
|
|
title = realm_str
|
|
content = realm_user_summary_table(all_records, admin_emails, title, realm_stats)
|
|
|
|
return render(
|
|
request,
|
|
"corporate/activity/activity.html",
|
|
context=dict(
|
|
data=content,
|
|
title=title,
|
|
is_home=False,
|
|
),
|
|
)
|