Add stat page

This commit is contained in:
cuom1999 2022-09-22 19:23:41 -05:00
parent 1258d6dc25
commit 3da6a25867
8 changed files with 213 additions and 19 deletions

View file

@ -886,7 +886,19 @@ urlpatterns = [
"^language/", "^language/",
include( 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"),
] ]
), ),
), ),

View file

@ -154,7 +154,9 @@ class ProblemData(models.Model):
if self.zipfile: if self.zipfile:
self.zipfile.name = problem_directory_file_helper(new, self.zipfile.name) self.zipfile.name = problem_directory_file_helper(new, self.zipfile.name)
if self.generator: 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: if self.custom_checker:
self.custom_checker.name = problem_directory_file_helper( self.custom_checker.name = problem_directory_file_helper(
new, self.custom_checker.name new, self.custom_checker.name

View file

@ -1,6 +1,7 @@
from itertools import chain, repeat from itertools import chain, repeat
from operator import itemgetter from operator import itemgetter
import json import json
import pandas as pd
from django.conf import settings from django.conf import settings
from django.db.models import Case, Count, FloatField, IntegerField, Value, When 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.http import Http404
from django.views.generic import TemplateView from django.views.generic import TemplateView
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.db import connection
from judge.models import Language, Submission from judge.models import Language, Submission
from judge.utils.stats import ( from judge.utils.stats import (
@ -49,18 +51,18 @@ class StatLanguage(StatViewBase):
other_count = sum(map(itemgetter("count"), languages[num_languages:])) other_count = sum(map(itemgetter("count"), languages[num_languages:]))
return { return {
"labels": list(map(itemgetter("name"), languages[:num_languages])) "labels": list(map(itemgetter("name"), languages[:num_languages]))
+ ["Other"], + ["Other"],
"datasets": [ "datasets": [
{ {
"backgroundColor": chart_colors[:num_languages] + ["#FDB45C"], "backgroundColor": chart_colors[:num_languages] + ["#FDB45C"],
"highlightBackgroundColor": highlight_colors[:num_languages] "highlightBackgroundColor": highlight_colors[:num_languages]
+ ["#FFC870"], + ["#FFC870"],
"data": list(map(itemgetter("count"), languages[:num_languages])) "data": list(map(itemgetter("count"), languages[:num_languages]))
+ [other_count], + [other_count],
}, },
], ],
} }
def ac_language_data(self): def ac_language_data(self):
return self.language_data(Language.objects.annotate(count=self.ac_count)) return self.language_data(Language.objects.annotate(count=self.ac_count))
@ -83,10 +85,12 @@ class StatLanguage(StatViewBase):
return get_pie_chart(data) return get_pie_chart(data)
def ac_rate(self): def ac_rate(self):
rate = CombinedExpression( 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 = ( data = (
Language.objects.annotate(total=Count("submission"), ac_rate=rate) Language.objects.annotate(total=Count("submission"), ac_rate=rate)
@ -96,7 +100,6 @@ class StatLanguage(StatViewBase):
) )
return get_bar_chart(list(data)) return get_bar_chart(list(data))
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context["title"] = _("Language statistics") context["title"] = _("Language statistics")
@ -105,4 +108,93 @@ class StatLanguage(StatViewBase):
context["lang_ac"] = mark_safe(json.dumps(self.ac_language_data())) context["lang_ac"] = mark_safe(json.dumps(self.ac_language_data()))
context["status_counts"] = mark_safe(json.dumps(self.status_data())) context["status_counts"] = mark_safe(json.dumps(self.status_data()))
context["ac_rate"] = mark_safe(json.dumps(self.ac_rate())) context["ac_rate"] = mark_safe(json.dumps(self.ac_rate()))
return context 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

View file

@ -37,4 +37,5 @@ redis
lupa lupa
websocket-client websocket-client
python-memcached python-memcached
numpy numpy
pandas

View file

@ -282,6 +282,7 @@
{% endif %} {% endif %}
{% if request.user.is_superuser %} {% if request.user.is_superuser %}
<li><a href="{{ url('internal_problem') }}">{{ _('Internal') }}</a></li> <li><a href="{{ url('internal_problem') }}">{{ _('Internal') }}</a></li>
<li><a href="{{ url('site_stats') }}">{{ _('Stats') }}</a></li>
{% endif %} {% endif %}
<li><a href="{{ url('user_edit_profile') }}">{{ _('Edit profile') }}</a></li> <li><a href="{{ url('user_edit_profile') }}">{{ _('Edit profile') }}</a></li>
{% if request.user.is_impersonate %} {% if request.user.is_impersonate %}

View file

@ -87,4 +87,24 @@
}); });
return chart; 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;
}
</script> </script>

65
templates/stats/site.html Normal file
View file

@ -0,0 +1,65 @@
{% extends "stats/base.html" %}
{% block media %}
<style>
.graph {
padding-bottom: 3em;
}
</style>
{% endblock %}
{% block chart_body %}
<div class="graph">
<div id="submissions" class="chart">
<canvas width="90%" height="300"></canvas>
</div>
<center><h3>{{ _('Submissions') }}</h3></center>
</div>
<div class="graph">
<div id="contests" class="chart">
<canvas width="90%" height="300"></canvas>
</div>
<center><h3>{{ _('Contests') }}</h3></center>
</div>
<div class="graph">
<div id="new_users" class="chart">
<canvas width="90%" height="300"></canvas>
</div>
<center><h3>{{ _('New users') }}</h3></center>
</div>
<div class="graph">
<div id="groups" class="chart">
<canvas width="90%" height="300"></canvas>
</div>
<center><h3>{{ _('Groups') }}</h3></center>
</div>
<div class="graph">
<div id="comments" class="chart">
<canvas width="90%" height="300"></canvas>
</div>
<center><h3>{{ _('Comments') }}</h3></center>
</div>
<div class="graph">
<div id="chat_messages" class="chart">
<canvas width="90%" height="300"></canvas>
</div>
<center><h3>{{ _('Chat messages') }}</h3></center>
</div>
{% endblock %}
{% block bodyend %}
<script type="text/javascript">
$(function () {
Chart.defaults.global.scaleFontFamily =
Chart.defaults.global.tooltipFontFamily =
Chart.defaults.global.tooltipTitleFontFamily =
$('body').css('font-family');
draw_timeline({{submissions}}, $('#submissions'));
draw_timeline({{new_users}}, $('#new_users'));
draw_timeline({{comments}}, $('#comments'));
draw_timeline({{chat_messages}}, $('#chat_messages'));
draw_timeline({{contests}}, $('#contests'));
draw_timeline({{groups}}, $('#groups'));
});
</script>
{% endblock %}

View file

@ -2,4 +2,5 @@
{% block tabs %} {% block tabs %}
{{ make_tab('language', 'fa-list', url('language_stats'), _('Language')) }} {{ make_tab('language', 'fa-list', url('language_stats'), _('Language')) }}
{{ make_tab('site', 'fa-list', url('site_stats'), _('Site')) }}
{% endblock %} {% endblock %}