mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	Unless there is a limit on `id`, always provide a `realm_id` limit as well. We also notate which index is expected to be used in each query.
		
			
				
	
	
		
			246 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			246 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
import itertools
 | 
						|
from datetime import datetime
 | 
						|
from typing import Any, Dict, List, Optional, Set, Tuple
 | 
						|
 | 
						|
from django.db import connection
 | 
						|
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 psycopg2.sql import SQL
 | 
						|
 | 
						|
from analytics.views.activity_common import (
 | 
						|
    format_date_for_activity_reports,
 | 
						|
    get_user_activity_summary,
 | 
						|
    make_table,
 | 
						|
    realm_stats_link,
 | 
						|
    user_activity_link,
 | 
						|
)
 | 
						|
from zerver.decorator import require_server_admin
 | 
						|
from zerver.models import Realm, UserActivity
 | 
						|
 | 
						|
 | 
						|
def get_user_activity_records_for_realm(realm: str, is_bot: bool) -> QuerySet[UserActivity]:
 | 
						|
    fields = [
 | 
						|
        "user_profile__full_name",
 | 
						|
        "user_profile__delivery_email",
 | 
						|
        "query",
 | 
						|
        "client__name",
 | 
						|
        "count",
 | 
						|
        "last_visit",
 | 
						|
    ]
 | 
						|
 | 
						|
    records = UserActivity.objects.filter(
 | 
						|
        user_profile__realm__string_id=realm,
 | 
						|
        user_profile__is_active=True,
 | 
						|
        user_profile__is_bot=is_bot,
 | 
						|
    )
 | 
						|
    records = records.order_by("user_profile__delivery_email", "-last_visit")
 | 
						|
    records = records.select_related("user_profile", "client").only(*fields)
 | 
						|
    return records
 | 
						|
 | 
						|
 | 
						|
def realm_user_summary_table(
 | 
						|
    all_records: QuerySet[UserActivity], admin_emails: Set[str]
 | 
						|
) -> Tuple[Dict[str, Any], str]:
 | 
						|
    user_records = {}
 | 
						|
 | 
						|
    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 get_last_visit(user_summary: Dict[str, Dict[str, datetime]], k: str) -> Optional[datetime]:
 | 
						|
        if k in user_summary:
 | 
						|
            return user_summary[k]["last_visit"]
 | 
						|
        else:
 | 
						|
            return None
 | 
						|
 | 
						|
    def get_count(user_summary: Dict[str, Dict[str, str]], k: str) -> str:
 | 
						|
        if k in user_summary:
 | 
						|
            return user_summary[k]["count"]
 | 
						|
        else:
 | 
						|
            return ""
 | 
						|
 | 
						|
    def is_recent(val: datetime) -> bool:
 | 
						|
        age = timezone_now() - val
 | 
						|
        return age.total_seconds() < 5 * 60
 | 
						|
 | 
						|
    rows = []
 | 
						|
    for email, user_summary in user_records.items():
 | 
						|
        email_link = user_activity_link(email, user_summary["user_profile_id"])
 | 
						|
        sent_count = get_count(user_summary, "send")
 | 
						|
        cells = [user_summary["name"], email_link, sent_count]
 | 
						|
        row_class = ""
 | 
						|
        for field in ["use", "send", "pointer", "desktop", "ZulipiOS", "Android"]:
 | 
						|
            visit = get_last_visit(user_summary, field)
 | 
						|
            if field == "use":
 | 
						|
                if visit and is_recent(visit):
 | 
						|
                    row_class += " recently_active"
 | 
						|
                if email in admin_emails:
 | 
						|
                    row_class += " admin"
 | 
						|
            val = format_date_for_activity_reports(visit)
 | 
						|
            cells.append(val)
 | 
						|
        row = dict(cells=cells, row_class=row_class)
 | 
						|
        rows.append(row)
 | 
						|
 | 
						|
    def by_used_time(row: Dict[str, Any]) -> str:
 | 
						|
        return row["cells"][3]
 | 
						|
 | 
						|
    rows = sorted(rows, key=by_used_time, reverse=True)
 | 
						|
 | 
						|
    cols = [
 | 
						|
        "Name",
 | 
						|
        "Email",
 | 
						|
        "Total sent",
 | 
						|
        "Heard from",
 | 
						|
        "Message sent",
 | 
						|
        "Pointer motion",
 | 
						|
        "Desktop",
 | 
						|
        "ZulipiOS",
 | 
						|
        "Android",
 | 
						|
    ]
 | 
						|
 | 
						|
    title = "Summary"
 | 
						|
 | 
						|
    content = make_table(title, cols, rows, has_row_class=True)
 | 
						|
    return user_records, content
 | 
						|
 | 
						|
 | 
						|
def realm_client_table(user_summaries: Dict[str, Dict[str, Any]]) -> str:
 | 
						|
    exclude_keys = [
 | 
						|
        "internal",
 | 
						|
        "name",
 | 
						|
        "user_profile_id",
 | 
						|
        "use",
 | 
						|
        "send",
 | 
						|
        "pointer",
 | 
						|
        "website",
 | 
						|
        "desktop",
 | 
						|
    ]
 | 
						|
 | 
						|
    rows = []
 | 
						|
    for email, user_summary in user_summaries.items():
 | 
						|
        email_link = user_activity_link(email, user_summary["user_profile_id"])
 | 
						|
        name = user_summary["name"]
 | 
						|
        for k, v in user_summary.items():
 | 
						|
            if k in exclude_keys:
 | 
						|
                continue
 | 
						|
            client = k
 | 
						|
            count = v["count"]
 | 
						|
            last_visit = v["last_visit"]
 | 
						|
            row = [
 | 
						|
                format_date_for_activity_reports(last_visit),
 | 
						|
                client,
 | 
						|
                name,
 | 
						|
                email_link,
 | 
						|
                count,
 | 
						|
            ]
 | 
						|
            rows.append(row)
 | 
						|
 | 
						|
    rows = sorted(rows, key=lambda r: r[0], reverse=True)
 | 
						|
 | 
						|
    cols = [
 | 
						|
        "Last visit",
 | 
						|
        "Client",
 | 
						|
        "Name",
 | 
						|
        "Email",
 | 
						|
        "Count",
 | 
						|
    ]
 | 
						|
 | 
						|
    title = "Clients"
 | 
						|
 | 
						|
    return make_table(title, cols, rows)
 | 
						|
 | 
						|
 | 
						|
def sent_messages_report(realm: str) -> str:
 | 
						|
    title = "Recently sent messages for " + realm
 | 
						|
 | 
						|
    cols = [
 | 
						|
        "Date",
 | 
						|
        "Humans",
 | 
						|
        "Bots",
 | 
						|
    ]
 | 
						|
 | 
						|
    # Uses index: zerver_message_realm_date_sent
 | 
						|
    query = SQL(
 | 
						|
        """
 | 
						|
        select
 | 
						|
            series.day::date,
 | 
						|
            user_messages.humans,
 | 
						|
            user_messages.bots
 | 
						|
        from (
 | 
						|
            select generate_series(
 | 
						|
                (now()::date - interval '2 week'),
 | 
						|
                now()::date,
 | 
						|
                interval '1 day'
 | 
						|
            ) as day
 | 
						|
        ) as series
 | 
						|
        left join (
 | 
						|
            select
 | 
						|
                date_sent::date date_sent,
 | 
						|
                count(*) filter (where not up.is_bot) as humans,
 | 
						|
                count(*) filter (where up.is_bot) as bots
 | 
						|
            from zerver_message m
 | 
						|
            join zerver_userprofile up on up.id = m.sender_id
 | 
						|
            join zerver_realm r on r.id = up.realm_id
 | 
						|
            where
 | 
						|
                r.string_id = %s
 | 
						|
            and
 | 
						|
                date_sent > now() - interval '2 week'
 | 
						|
            and
 | 
						|
                m.realm_id = r.id
 | 
						|
            group by
 | 
						|
                date_sent::date
 | 
						|
            order by
 | 
						|
                date_sent::date
 | 
						|
        ) user_messages on
 | 
						|
            series.day = user_messages.date_sent
 | 
						|
    """
 | 
						|
    )
 | 
						|
    cursor = connection.cursor()
 | 
						|
    cursor.execute(query, [realm])
 | 
						|
    rows = cursor.fetchall()
 | 
						|
    cursor.close()
 | 
						|
 | 
						|
    return make_table(title, cols, rows)
 | 
						|
 | 
						|
 | 
						|
@require_server_admin
 | 
						|
def get_realm_activity(request: HttpRequest, realm_str: str) -> HttpResponse:
 | 
						|
    data: List[Tuple[str, str]] = []
 | 
						|
    all_user_records: Dict[str, Any] = {}
 | 
						|
 | 
						|
    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}
 | 
						|
 | 
						|
    for is_bot, page_title in [(False, "Humans"), (True, "Bots")]:
 | 
						|
        all_records = get_user_activity_records_for_realm(realm_str, is_bot)
 | 
						|
 | 
						|
        user_records, content = realm_user_summary_table(all_records, admin_emails)
 | 
						|
        all_user_records.update(user_records)
 | 
						|
 | 
						|
        data += [(page_title, content)]
 | 
						|
 | 
						|
    page_title = "Clients"
 | 
						|
    content = realm_client_table(all_user_records)
 | 
						|
    data += [(page_title, content)]
 | 
						|
 | 
						|
    page_title = "History"
 | 
						|
    content = sent_messages_report(realm_str)
 | 
						|
    data += [(page_title, content)]
 | 
						|
 | 
						|
    title = realm_str
 | 
						|
    realm_stats = realm_stats_link(realm_str)
 | 
						|
 | 
						|
    return render(
 | 
						|
        request,
 | 
						|
        "analytics/activity.html",
 | 
						|
        context=dict(data=data, realm_stats_link=realm_stats, title=title),
 | 
						|
    )
 |