Add statistics page

This commit is contained in:
rubenwardy 2022-11-06 17:58:35 +00:00
parent 5c0480b39d
commit 4387e71417
10 changed files with 284 additions and 7 deletions

@ -52,6 +52,11 @@ def get_package_tabs(user: User, package: Package):
"title": gettext("Audit Log"),
"url": package.getURL("packages.audit")
},
{
"id": "stats",
"title": gettext("Statistics"),
"url": package.getURL("packages.stats")
},
{
"id": "share",
"title": gettext("Share and Badges"),

@ -708,3 +708,10 @@ def game_support(package):
return render_template("packages/game_support.html", package=package, form=form,
mod_conf_lines=mod_conf_lines, force_game_detection=force_game_detection,
tabs=get_package_tabs(current_user, package), current_tab="game_support")
@bp.route("/packages/<author>/<name>/stats/")
@is_package_page
def stats(package):
return render_template("packages/stats.html",
package=package, tabs=get_package_tabs(current_user, package), current_tab="stats")

13
app/public/static/libs/chart.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,185 @@
"use strict";
const labelColor = "#bbb";
const gridColor = "#333";
const chartColors = [
"#7eb26d",
"#eab839",
"#6ed0e0",
"#e24d42",
"#1f78c1",
"#ba43a9",
];
function hexToRgb(hex) {
var bigint = parseInt(hex, 16);
var r = (bigint >> 16) & 255;
var g = (bigint >> 8) & 255;
var b = bigint & 255;
return r + "," + g + "," + b;
}
const chartColorsBg = chartColors.map(color => `rgba(${hexToRgb(color.slice(1))}, 0.2)`);
async function load_data() {
const root = document.getElementById("stats-root");
const source = root.getAttribute("data-source");
const response = await fetch(source);
const json = await response.json();
const jsonOther = json.platform_minetest.map((value, i) =>
value + json.platform_other[i]
- json.reason_new[i] - json.reason_dependency[i]
- json.reason_update[i]);
document.getElementById("loading").style.display = "none";
function getData(list) {
return list.map((value, i) => ({ x: json.dates[i], y: value }));
}
function sum(list) {
return list.reduce((acc, x) => acc + x, 0);
}
{
const ctx = document.getElementById("chart-platform").getContext("2d");
const data = {
datasets: [
{ label: "Web / other", data: getData(json.platform_other) },
{ label: "Minetest", data: getData(json.platform_minetest) },
],
};
setup_chart(ctx, data);
}
{
const ctx = document.getElementById("chart-reason").getContext("2d");
const data = {
datasets: [
{ label: "Other / Unknown", data: getData(jsonOther) },
{ label: "Update", data: getData(json.reason_update) },
{ label: "Dependency", data: getData(json.reason_dependency) },
{ label: "New Install", data: getData(json.reason_new) },
],
};
setup_chart(ctx, data);
}
{
const ctx = document.getElementById("chart-reason-pie").getContext("2d");
const data = {
labels: [
"New Install",
"Dependency",
"Update",
"Other / Unknown",
],
datasets: [{
label: "My First Dataset",
data: [
sum(json.reason_new),
sum(json.reason_dependency),
sum(json.reason_update),
sum(jsonOther),
],
backgroundColor: chartColors,
hoverOffset: 4,
borderWidth: 0,
}]
};
const config = {
type: "doughnut",
data: data,
options: {
plugins: {
legend: {
position: "right",
labels: {
color: labelColor,
},
},
},
}
};
new Chart(ctx, config);
}
}
function setup_chart(ctx, data) {
data.datasets = data.datasets.map((set, i) => {
const colorIdx = data.datasets.length - i - 1;
return {
fill: true,
backgroundColor: chartColorsBg[colorIdx],
borderColor: chartColors[colorIdx],
pointBackgroundColor: chartColors[colorIdx],
...set,
};
});
const config = {
type: "line",
data: data,
options: {
responsive: true,
plugins: {
tooltip: {
mode: "index"
},
legend: {
reverse: true,
labels: {
color: labelColor,
}
}
},
interaction: {
mode: "nearest",
axis: "x",
intersect: false
},
scales: {
x: {
type: "time",
time: {
// min: start,
// max: end,
unit: "day",
},
ticks: {
color: labelColor,
},
grid: {
color: gridColor,
}
},
y: {
stacked: true,
min: 0,
precision: 0,
ticks: {
color: labelColor,
},
grid: {
color: gridColor,
}
},
}
}
};
new Chart(ctx, config);
}
$(load_data);

@ -272,3 +272,8 @@ blockquote {
margin-bottom: 0 !important;
}
}
.chart {
width: 100%;
min-height: 400px;
}

@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}title{% endblock %} - {{ config.USER_APP_NAME }}</title>
<link rel="stylesheet" type="text/css" href="/static/libs/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="/static/custom.css?v=35">
<link rel="stylesheet" type="text/css" href="/static/custom.css?v=36">
<link rel="search" type="application/opensearchdescription+xml" href="/static/opensearch.xml" title="ContentDB" />
<link rel="shortcut icon" href="/favicon-16.png" sizes="16x16">
<link rel="icon" href="/favicon-128.png" sizes="128x128">

@ -0,0 +1,54 @@
{% extends "packages/package_base.html" %}
{% block title %}
{{ _("Statistics") }}
{% endblock %}
{% block scriptextra %}
<script src="/static/libs/chart.min.js"></script>
<script src="/static/libs/chartjs-adapter-date-fns.bundle.min.js"></script>
<script src="/static/package_stats.js"></script>
{% endblock %}
{% block content %}
<h2 class="mt-0">{{ self.title() }}</h2>
<noscript>
<p class="alert alert-danger">
{{ _("JavaScript is needed for graphs") }}
</p>
</noscript>
<div class="row mb-5">
<div class="col-md-4">
<div class="card h-100">
<div class="card-body media align-items-center">
<i class="fas fa-download ml-2 mr-4 text-size" style="font-size: 45px; color: #999;"></i>
<div class="media-body">
<div class="mt-0 h4">
{{ package.downloads }}
</div>
<div class="my-0">
{{ _("Lifetime downloads") }}
</div>
</div>
</div>
</div>
</div>
</div>
<div id="loading">{{ _("Loading...") }}</div>
<div id="stats-root" data-source="{{ package.getURL('api.package_stats') }}">
<h3>{{ _("Downloads") }}</h3>
<h4>{{ _("Client") }}</h4>
<p class="text-muted">
{{ _("This is a stacked area graph. For total downloads, look at the combined height.") }}
</p>
<canvas id="chart-platform" class="chart"></canvas>
<h4 class="mt-5">{{ _("Reason") }}</h4>
<p class="text-muted">
{{ _("This is a stacked area graph. For total downloads, look at the combined height.") }}
</p>
<canvas id="chart-reason" class="chart"></canvas>
<canvas id="chart-reason-pie" class="chart mt-4"></canvas>
</div>
{% endblock %}

@ -164,8 +164,7 @@
</span>
</a>
{% if release %}
<a class="btn" rel="nofollow" href="{{ package.getURL("packages.download") }}" title="{{ _("Downloads") }}"
download="{{ release.getDownloadFileName() }}">
<a class="btn" rel="nofollow" href="{{ package.getURL('packages.stats') }}" title="{{ _('Statistics') }}">
<i class="fas fa-download"></i>
<span class="count">{{ package.downloads }}</span>
</a>
@ -544,16 +543,19 @@
</div>
<p class="mt-3">
<a href="{{ package.getURL('packages.stats') }}">
<i class="fas fa-chart-line mr-1"></i>
{{ _("Statistics") }}
</a>
{% if package.approved and current_user != package.author %}
|
<a href="{{ url_for('report.report', url=url_current()) }}">
<i class="fas fa-flag mr-1"></i>
{{ _("Report") }}
</a>
{% endif %}
{% if package.checkPerm(current_user, "EDIT_PACKAGE") or package.checkPerm(current_user, "APPROVE_NEW") %}
{% if package.approved and current_user != package.author %}
|
{% endif %}
|
<a href="{{ package.getURL('packages.audit') }}">
{{ _("See audit log") }}
</a>

@ -82,7 +82,6 @@ def url_set_query(**kwargs):
else:
args.setlist(key, [ value ])
dargs = dict(args.lists())
if request.view_args:
dargs.update(request.view_args)