From 3da6a258679bf1f8493f31cf9da6e4ffdea8bcdd Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Thu, 22 Sep 2022 19:23:41 -0500 Subject: [PATCH] Add stat page --- dmoj/urls.py | 14 +++- judge/models/problem_data.py | 4 +- judge/views/stats.py | 124 +++++++++++++++++++++++++++++----- requirements.txt | 3 +- templates/base.html | 1 + templates/stats/media-js.html | 20 ++++++ templates/stats/site.html | 65 ++++++++++++++++++ templates/stats/tab.html | 1 + 8 files changed, 213 insertions(+), 19 deletions(-) create mode 100644 templates/stats/site.html diff --git a/dmoj/urls.py b/dmoj/urls.py index 3468a19..255a951 100644 --- a/dmoj/urls.py +++ b/dmoj/urls.py @@ -886,7 +886,19 @@ urlpatterns = [ "^language/", include( [ - url("^$", stats.StatLanguage.as_view(), name="language_stats"), + url( + "^$", + stats.StatLanguage.as_view(), + name="language_stats", + ), + ] + ), + ), + url( + "^site/", + include( + [ + url("^$", stats.StatSite.as_view(), name="site_stats"), ] ), ), diff --git a/judge/models/problem_data.py b/judge/models/problem_data.py index 1469c3f..1717924 100644 --- a/judge/models/problem_data.py +++ b/judge/models/problem_data.py @@ -154,7 +154,9 @@ class ProblemData(models.Model): if self.zipfile: self.zipfile.name = problem_directory_file_helper(new, self.zipfile.name) if self.generator: - self.generator.name = problem_directory_file_helper(new, self.generator.name) + self.generator.name = problem_directory_file_helper( + new, self.generator.name + ) if self.custom_checker: self.custom_checker.name = problem_directory_file_helper( new, self.custom_checker.name diff --git a/judge/views/stats.py b/judge/views/stats.py index 640088e..5071dd5 100644 --- a/judge/views/stats.py +++ b/judge/views/stats.py @@ -1,6 +1,7 @@ from itertools import chain, repeat from operator import itemgetter import json +import pandas as pd from django.conf import settings from django.db.models import Case, Count, FloatField, IntegerField, Value, When @@ -11,6 +12,7 @@ from django.utils.translation import gettext as _ from django.http import Http404 from django.views.generic import TemplateView from django.utils.safestring import mark_safe +from django.db import connection from judge.models import Language, Submission from judge.utils.stats import ( @@ -49,18 +51,18 @@ class StatLanguage(StatViewBase): other_count = sum(map(itemgetter("count"), languages[num_languages:])) return { - "labels": list(map(itemgetter("name"), languages[:num_languages])) - + ["Other"], - "datasets": [ - { - "backgroundColor": chart_colors[:num_languages] + ["#FDB45C"], - "highlightBackgroundColor": highlight_colors[:num_languages] - + ["#FFC870"], - "data": list(map(itemgetter("count"), languages[:num_languages])) - + [other_count], - }, - ], - } + "labels": list(map(itemgetter("name"), languages[:num_languages])) + + ["Other"], + "datasets": [ + { + "backgroundColor": chart_colors[:num_languages] + ["#FDB45C"], + "highlightBackgroundColor": highlight_colors[:num_languages] + + ["#FFC870"], + "data": list(map(itemgetter("count"), languages[:num_languages])) + + [other_count], + }, + ], + } def ac_language_data(self): return self.language_data(Language.objects.annotate(count=self.ac_count)) @@ -83,10 +85,12 @@ class StatLanguage(StatViewBase): return get_pie_chart(data) - def ac_rate(self): rate = CombinedExpression( - self.ac_count / Count("submission"), "*", Value(100.0), output_field=FloatField() + self.ac_count / Count("submission"), + "*", + Value(100.0), + output_field=FloatField(), ) data = ( Language.objects.annotate(total=Count("submission"), ac_rate=rate) @@ -96,7 +100,6 @@ class StatLanguage(StatViewBase): ) return get_bar_chart(list(data)) - def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["title"] = _("Language statistics") @@ -105,4 +108,93 @@ class StatLanguage(StatViewBase): context["lang_ac"] = mark_safe(json.dumps(self.ac_language_data())) context["status_counts"] = mark_safe(json.dumps(self.status_data())) context["ac_rate"] = mark_safe(json.dumps(self.ac_rate())) - return context \ No newline at end of file + return context + + +def group_data_by_period(dates, data, period="1W"): + df = pd.DataFrame() + df["dates"] = pd.to_datetime(dates) + df["data"] = data + df = df.groupby([pd.Grouper(key="dates", freq=period)])["data"].mean().reset_index() + return list(df["dates"]), list(df["data"]) + + +class StatSite(StatViewBase): + template_name = "stats/site.html" + + def create_dataset(self, query, label): + labels = [] + data = [] + with connection.cursor() as cursor: + cursor.execute(query) + for row in cursor.fetchall(): + data.append(row[0]) + labels.append(row[1]) + + labels, data = group_data_by_period(labels, data, self.period) + + res = { + "labels": labels, + "datasets": [ + { + "label": label, + "data": data, + "borderColor": "rgb(75, 192, 192)", + "pointRadius": 1, + "borderWidth": 2, + } + ], + } + return mark_safe(json.dumps(res, default=str)) + + def get_submissions(self): + query = """ + SELECT COUNT(*), CAST(date AS Date) as day from judge_submission GROUP BY day; + """ + return self.create_dataset(query, _("Submissions")) + + def get_comments(self): + query = """ + SELECT COUNT(*), CAST(time AS Date) as day from judge_comment GROUP BY day; + """ + return self.create_dataset(query, _("Comments")) + + def get_new_users(self): + query = """ + SELECT COUNT(*), CAST(date_joined AS Date) as day from auth_user GROUP BY day; + """ + return self.create_dataset(query, _("New users")) + + def get_chat_messages(self): + query = """ + SELECT COUNT(*), CAST(time AS Date) as day from chat_box_message GROUP BY day; + """ + return self.create_dataset(query, _("Chat messages")) + + def get_contests(self): + query = """ + SELECT COUNT(*), CAST(start_time AS Date) as day from judge_contest GROUP BY day; + """ + return self.create_dataset(query, _("Contests")) + + def get_groups(self): + query = """ + SELECT COUNT(*), CAST(creation_date AS Date) as day from judge_organization GROUP BY day; + """ + return self.create_dataset(query, _("Groups")) + + def get(self, request, *args, **kwargs): + self.period = request.GET.get("period", "1W") + return super().get(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["tab"] = "site" + context["title"] = _("Site statistics") + context["submissions"] = self.get_submissions() + context["comments"] = self.get_comments() + context["new_users"] = self.get_new_users() + context["chat_messages"] = self.get_chat_messages() + context["contests"] = self.get_contests() + context["groups"] = self.get_groups() + return context diff --git a/requirements.txt b/requirements.txt index fdf6768..0743316 100644 --- a/requirements.txt +++ b/requirements.txt @@ -37,4 +37,5 @@ redis lupa websocket-client python-memcached -numpy \ No newline at end of file +numpy +pandas \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index b925c04..d9f86c8 100644 --- a/templates/base.html +++ b/templates/base.html @@ -282,6 +282,7 @@ {% endif %} {% if request.user.is_superuser %}
  • {{ _('Internal') }}
  • +
  • {{ _('Stats') }}
  • {% endif %}
  • {{ _('Edit profile') }}
  • {% if request.user.is_impersonate %} diff --git a/templates/stats/media-js.html b/templates/stats/media-js.html index 69f9ad8..d6eb09d 100644 --- a/templates/stats/media-js.html +++ b/templates/stats/media-js.html @@ -87,4 +87,24 @@ }); return chart; } + + function draw_timeline(data, $chart) { + var ctx = $chart.find('canvas')[0].getContext('2d'); + var chart = new Chart(ctx, { + type: 'line', + data: data, + options: { + maintainAspectRatio: false, + legend: { + display: false, + }, + scales: { + xAxes: [{ + type: 'time', + }] + }, + }, + }); + return chart; + } diff --git a/templates/stats/site.html b/templates/stats/site.html new file mode 100644 index 0000000..aa67a07 --- /dev/null +++ b/templates/stats/site.html @@ -0,0 +1,65 @@ +{% extends "stats/base.html" %} +{% block media %} + +{% endblock %} + +{% block chart_body %} +
    +
    + +
    +

    {{ _('Submissions') }}

    +
    +
    +
    + +
    +

    {{ _('Contests') }}

    +
    +
    +
    + +
    +

    {{ _('New users') }}

    +
    +
    +
    + +
    +

    {{ _('Groups') }}

    +
    +
    +
    + +
    +

    {{ _('Comments') }}

    +
    +
    +
    + +
    +

    {{ _('Chat messages') }}

    +
    +{% endblock %} + +{% block bodyend %} + +{% endblock %} diff --git a/templates/stats/tab.html b/templates/stats/tab.html index 0b7a1d7..92ede48 100644 --- a/templates/stats/tab.html +++ b/templates/stats/tab.html @@ -2,4 +2,5 @@ {% block tabs %} {{ make_tab('language', 'fa-list', url('language_stats'), _('Language')) }} + {{ make_tab('site', 'fa-list', url('site_stats'), _('Site')) }} {% endblock %} \ No newline at end of file