mirror of
https://github.com/minetest/contentdb.git
synced 2025-01-08 22:17:34 +01:00
Stats: Add ability to select date range
This commit is contained in:
parent
516361345e
commit
80c42637df
@ -23,7 +23,6 @@ from flask import request, jsonify, current_app, Response
|
||||
from flask_login import current_user, login_required
|
||||
from sqlalchemy.orm import joinedload
|
||||
from sqlalchemy.sql.expression import func
|
||||
from werkzeug.datastructures import ResponseCacheControl
|
||||
|
||||
from app import csrf
|
||||
from app.logic.graphs import get_package_stats, get_package_stats_for_user, get_all_package_stats
|
||||
@ -31,7 +30,7 @@ from app.markdown import render_markdown
|
||||
from app.models import Tag, PackageState, PackageType, Package, db, PackageRelease, Permission, ForumTopic, \
|
||||
MinetestRelease, APIToken, PackageScreenshot, License, ContentWarning, User, PackageReview, Thread
|
||||
from app.querybuilder import QueryBuilder
|
||||
from app.utils import is_package_page, get_int_or_abort, url_set_query, abs_url, isYes
|
||||
from app.utils import is_package_page, get_int_or_abort, url_set_query, abs_url, isYes, get_request_date
|
||||
from . import bp
|
||||
from .auth import is_api_authd
|
||||
from .support import error, api_create_vcs_release, api_create_zip_release, api_create_screenshot, \
|
||||
@ -483,7 +482,9 @@ def list_all_reviews():
|
||||
@cors_allowed
|
||||
@cached(300)
|
||||
def package_stats(package: Package):
|
||||
return jsonify(get_package_stats(package))
|
||||
start = get_request_date("start")
|
||||
end = get_request_date("end")
|
||||
return jsonify(get_package_stats(package, start, end))
|
||||
|
||||
|
||||
@bp.route("/api/package_stats/")
|
||||
@ -645,7 +646,9 @@ def user_stats(username: str):
|
||||
if user is None:
|
||||
error(404, "User not found")
|
||||
|
||||
return jsonify(get_package_stats_for_user(user))
|
||||
start = get_request_date("start")
|
||||
end = get_request_date("end")
|
||||
return jsonify(get_package_stats_for_user(user, start, end))
|
||||
|
||||
|
||||
@bp.route("/api/cdb_schema/")
|
||||
|
@ -725,8 +725,12 @@ def game_support(package):
|
||||
@bp.route("/packages/<author>/<name>/stats/")
|
||||
@is_package_page
|
||||
def statistics(package):
|
||||
start = request.args.get("start")
|
||||
end = request.args.get("end")
|
||||
|
||||
return render_template("packages/stats.html",
|
||||
package=package, tabs=get_package_tabs(current_user, package), current_tab="stats")
|
||||
package=package, tabs=get_package_tabs(current_user, package), current_tab="stats",
|
||||
start=start, end=end, options=get_daterange_options(), noindex=start or end)
|
||||
|
||||
|
||||
@bp.route("/packages/<author>/<name>/stats.csv")
|
||||
|
@ -23,8 +23,8 @@ from flask_login import current_user, login_required
|
||||
from sqlalchemy import func
|
||||
|
||||
from app.models import *
|
||||
from app.tasks.forumtasks import checkForumAccount
|
||||
from . import bp
|
||||
from ...utils import get_daterange_options
|
||||
|
||||
|
||||
@bp.route("/users/", methods=["GET"])
|
||||
@ -249,4 +249,7 @@ def statistics(username):
|
||||
|
||||
downloads = db.session.query(func.sum(Package.downloads)).filter(Package.author==user).one()
|
||||
|
||||
return render_template("users/stats.html", user=user, downloads=downloads[0])
|
||||
start = request.args.get("start")
|
||||
end = request.args.get("end")
|
||||
return render_template("users/stats.html", user=user, downloads=downloads[0],
|
||||
start=start, end=end, options=get_daterange_options(), noindex=start or end)
|
||||
|
@ -125,8 +125,11 @@ Tokens can be attained by visiting [Settings > API Tokens](/user/tokens/).
|
||||
* Returns daily stats for package, or null if there is no data.
|
||||
* Daily date is done based on the UTC timezone.
|
||||
* EXPERIMENTAL. This API may change without warning.
|
||||
* Query args:
|
||||
* `start`: start date, inclusive. Optional. Default: 2022-10-01. UTC.
|
||||
* `end`: end date, inclusive. Optional. Default: today. UTC.
|
||||
* An object with the following keys:
|
||||
* `start`: start date, inclusive. Ex: 2022-10-22.
|
||||
* `start`: start date, inclusive. Ex: 2022-10-22. M
|
||||
* `end`: end date, inclusive. Ex: 2022-11-05.
|
||||
* `platform_minetest`: list of integers per day.
|
||||
* `platform_other`: list of integers per day.
|
||||
@ -374,6 +377,9 @@ Example:
|
||||
* Returns daily stats for the user's packages, or null if there is no data.
|
||||
* Daily date is done based on the UTC timezone.
|
||||
* EXPERIMENTAL. This API may change without warning.
|
||||
* Query args:
|
||||
* `start`: start date, inclusive. Optional. Default: 2022-10-01. UTC.
|
||||
* `end`: end date, inclusive. Optional. Default: today. UTC.
|
||||
* A table with the following keys:
|
||||
* `from`: start date, inclusive. Ex: 2022-10-22.
|
||||
* `end`: end date, inclusive. Ex: 2022-11-05.
|
||||
|
@ -41,24 +41,36 @@ def _flatten_data(stats):
|
||||
return result
|
||||
|
||||
|
||||
def get_package_stats(package: Package):
|
||||
stats = package.daily_stats.order_by(db.asc(PackageDailyStats.date)).all()
|
||||
def get_package_stats(package: Package, start_date: Optional[datetime.date], end_date: Optional[datetime.date]):
|
||||
query = package.daily_stats.order_by(db.asc(PackageDailyStats.date))
|
||||
if start_date:
|
||||
query = query.filter(PackageDailyStats.date >= start_date)
|
||||
if end_date:
|
||||
query = query.filter(PackageDailyStats.date <= end_date)
|
||||
|
||||
stats = query.all()
|
||||
if len(stats) == 0:
|
||||
return None
|
||||
|
||||
return _flatten_data(stats)
|
||||
|
||||
|
||||
def get_package_stats_for_user(user: User):
|
||||
stats = db.session \
|
||||
def get_package_stats_for_user(user: User, start_date: Optional[datetime.date], end_date: Optional[datetime.date]):
|
||||
query = db.session \
|
||||
.query(PackageDailyStats.date,
|
||||
func.sum(PackageDailyStats.platform_minetest).label("platform_minetest"),
|
||||
func.sum(PackageDailyStats.platform_other).label("platform_other"),
|
||||
func.sum(PackageDailyStats.reason_new).label("reason_new"),
|
||||
func.sum(PackageDailyStats.reason_dependency).label("reason_dependency"),
|
||||
func.sum(PackageDailyStats.reason_update).label("reason_update")) \
|
||||
.filter(PackageDailyStats.package.has(author_id=user.id)) \
|
||||
.order_by(db.asc(PackageDailyStats.date)) \
|
||||
.filter(PackageDailyStats.package.has(author_id=user.id))
|
||||
|
||||
if start_date:
|
||||
query = query.filter(PackageDailyStats.date >= start_date)
|
||||
if end_date:
|
||||
query = query.filter(PackageDailyStats.date <= end_date)
|
||||
|
||||
stats = query.order_by(db.asc(PackageDailyStats.date)) \
|
||||
.group_by(PackageDailyStats.date) \
|
||||
.all()
|
||||
if len(stats) == 0:
|
||||
@ -122,9 +134,15 @@ def get_package_overview_for_user(user: Optional[User], start_date: datetime.dat
|
||||
return result
|
||||
|
||||
|
||||
def get_all_package_stats():
|
||||
end_date = datetime.datetime.utcnow().date()
|
||||
start_date = (datetime.datetime.utcnow() - datetime.timedelta(days=29)).date()
|
||||
def get_all_package_stats(start_date: Optional[datetime.date], end_date: Optional[datetime.date]):
|
||||
now_date = datetime.datetime.utcnow().date()
|
||||
if end_date is None or end_date > now_date:
|
||||
end_date = now_date
|
||||
|
||||
min_start_date = (datetime.datetime.utcnow() - datetime.timedelta(days=29)).date()
|
||||
if start_date is None or start_date < min_start_date:
|
||||
start_date = min_start_date
|
||||
|
||||
return {
|
||||
"start": start_date.isoformat(),
|
||||
"end": end_date.isoformat(),
|
||||
|
@ -30,6 +30,20 @@
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro render_daterange_selector(options) %}
|
||||
<nav class="dropdown d-inline-block">
|
||||
<button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownDateRange" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
{{ _("Date range...") }}
|
||||
</button>
|
||||
<div class="dropdown-menu" aria-labelledby="dropdownDateRange">
|
||||
{% for option in options %}
|
||||
<a class="dropdown-item" href="{{ option[1] }}">{{ option[0] }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</nav>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro render_package_stats(source, downloads) %}
|
||||
<noscript>
|
||||
<p class="alert alert-danger">
|
||||
|
@ -4,7 +4,8 @@
|
||||
{{ _("Statistics") }} - {{ package.title }}
|
||||
{% endblock %}
|
||||
|
||||
{% from "macros/stats.html" import render_package_stats, render_package_stats_js, render_package_selector %}
|
||||
{% from "macros/stats.html" import render_package_stats, render_package_stats_js,
|
||||
render_package_selector, render_daterange_selector with context %}
|
||||
|
||||
{% block scriptextra %}
|
||||
{{ render_package_stats_js() }}
|
||||
@ -16,8 +17,9 @@
|
||||
<i class="fas fa-download mr-1"></i>
|
||||
{{ _("Download (.csv)") }}
|
||||
</a>
|
||||
{{ render_daterange_selector(options) }}
|
||||
{{ render_package_selector(package.author, package=package) }}
|
||||
</div>
|
||||
<h2 class="mt-0">{{ _("Statistics") }}</h2>
|
||||
{{ render_package_stats(package.getURL('api.package_stats'), package.downloads) }}
|
||||
{{ render_package_stats(package.getURL('api.package_stats', start=start, end=end), package.downloads) }}
|
||||
{% endblock %}
|
||||
|
@ -4,7 +4,8 @@
|
||||
{{ _("Statistics for %(display_name)s's packages", display_name=user.display_name) }}
|
||||
{% endblock %}
|
||||
|
||||
{% from "macros/stats.html" import render_package_stats, render_package_stats_js, render_package_selector %}
|
||||
{% from "macros/stats.html" import render_package_stats, render_package_stats_js,
|
||||
render_package_selector, render_daterange_selector with context %}
|
||||
|
||||
{% block scriptextra %}
|
||||
{{ render_package_stats_js() }}
|
||||
@ -12,8 +13,9 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="float-right">
|
||||
{{ render_daterange_selector(options) }}
|
||||
{{ render_package_selector(user, package=None) }}
|
||||
</div>
|
||||
<h2 class="mt-0">{{ self.title() }}</h2>
|
||||
{{ render_package_stats(url_for("api.user_stats", username=user.username), downloads) }}
|
||||
{{ render_package_stats(url_for("api.user_stats", username=user.username, start=start, end=end), downloads) }}
|
||||
{% endblock %}
|
||||
|
@ -14,11 +14,12 @@
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from urllib.parse import urljoin, urlparse, urlunparse
|
||||
|
||||
import typing
|
||||
import user_agents
|
||||
from flask import request, abort
|
||||
from flask_babel import LazyString
|
||||
from werkzeug.datastructures import MultiDict
|
||||
|
||||
from app.models import *
|
||||
@ -88,6 +89,7 @@ def url_set_query(**kwargs):
|
||||
|
||||
return url_for(request.endpoint, **dargs)
|
||||
|
||||
|
||||
def get_int_or_abort(v, default=None):
|
||||
if v is None:
|
||||
return default
|
||||
@ -97,6 +99,7 @@ def get_int_or_abort(v, default=None):
|
||||
except ValueError:
|
||||
abort(400)
|
||||
|
||||
|
||||
def is_user_bot():
|
||||
user_agent = request.headers.get('User-Agent')
|
||||
if user_agent is None:
|
||||
@ -104,3 +107,28 @@ def is_user_bot():
|
||||
|
||||
user_agent = user_agents.parse(user_agent)
|
||||
return user_agent.is_bot
|
||||
|
||||
|
||||
def get_request_date(key: str) -> typing.Optional[datetime.date]:
|
||||
val = request.args.get(key)
|
||||
if val is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
return datetime.datetime.strptime(val, "%Y-%m-%d").date()
|
||||
except ValueError:
|
||||
abort(400)
|
||||
|
||||
|
||||
def get_daterange_options() -> List[Tuple[LazyString, str]]:
|
||||
now = datetime.datetime.utcnow().date()
|
||||
days30 = (datetime.datetime.utcnow() - datetime.timedelta(days=30)).date()
|
||||
days90 = (datetime.datetime.utcnow() - datetime.timedelta(days=90)).date()
|
||||
year_start = datetime.date(now.year, 1, 1)
|
||||
|
||||
return [
|
||||
(lazy_gettext("All time"), url_set_query(start="2022-10-23", end=now.isoformat())),
|
||||
(lazy_gettext("Last 30 days"), url_set_query(start=days30.isoformat(), end=now.isoformat())),
|
||||
(lazy_gettext("Last 90 days"), url_set_query(start=days90.isoformat(), end=now.isoformat())),
|
||||
(lazy_gettext("Year to date"), url_set_query(start=year_start, end=now.isoformat())),
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user