mirror of
https://github.com/macports/macports-webapp.git
synced 2026-07-13 03:18:42 -07:00
Add port statistics api endpoint
This commit is contained in:
committed by
Arjun Salyan
parent
c181163fbf
commit
57ea4891f4
@@ -1 +1,91 @@
|
||||
import datetime
|
||||
|
||||
from rest_framework import serializers
|
||||
from django.db.models import Count, Subquery, Q
|
||||
|
||||
from stats.models import PortInstallation
|
||||
from stats.models import Submission
|
||||
from stats.utilities.sort_by_version import sort_list_of_dicts_by_version
|
||||
from stats.validators import ALLOWED_DAYS_FOR_STATS as ALLOWED_DAYS
|
||||
from stats.validators import ALLOWED_PROPERTIES
|
||||
|
||||
|
||||
class PortStatisticsSerializer(serializers.Serializer):
|
||||
result = serializers.SerializerMethodField()
|
||||
|
||||
sort_by = None
|
||||
days = None
|
||||
days_ago = None
|
||||
properties = []
|
||||
port_name = None
|
||||
|
||||
class Meta:
|
||||
fields = ('property', 'result')
|
||||
|
||||
def validate_context(self):
|
||||
days = self.context.get('days', '30')
|
||||
days_ago = self.context.get('days_ago', '0')
|
||||
if (not days.isnumeric()) or (not days_ago.isnumeric()):
|
||||
return False
|
||||
|
||||
days = int(days)
|
||||
days_ago = int(days_ago)
|
||||
|
||||
if (days not in ALLOWED_DAYS) or (days_ago not in ALLOWED_DAYS):
|
||||
return False
|
||||
|
||||
self.days = int(days)
|
||||
self.days_ago = int(days_ago)
|
||||
return self.validate_properties()
|
||||
|
||||
def validate_properties(self):
|
||||
properties = self.context.get('property')
|
||||
for p in properties:
|
||||
if p not in ALLOWED_PROPERTIES:
|
||||
return False
|
||||
self.properties = properties
|
||||
return self.validate_port()
|
||||
|
||||
def validate_sorting(self):
|
||||
sort_by = self.context.get('sort_by')
|
||||
if sort_by == "" or sort_by is None:
|
||||
return
|
||||
if sort_by == "variants" or sort_by == "requested":
|
||||
return
|
||||
if sort_by not in ALLOWED_PROPERTIES or sort_by not in self.properties:
|
||||
return
|
||||
self.sort_by = sort_by
|
||||
return
|
||||
|
||||
def validate_port(self):
|
||||
port_name = self.context.get('name')
|
||||
if port_name is None or port_name == "":
|
||||
return False
|
||||
self.port_name = port_name
|
||||
return True
|
||||
|
||||
def generate_time_range_query(self):
|
||||
is_valid = self.validate_context()
|
||||
if not is_valid:
|
||||
return PortInstallation.objects.none()
|
||||
|
||||
end_date = datetime.datetime.now(tz=datetime.timezone.utc) - datetime.timedelta(days=self.days_ago)
|
||||
start_date = end_date - datetime.timedelta(days=self.days)
|
||||
submissions = Submission.objects.defer('raw_json').filter(timestamp__range=[start_date, end_date]).order_by('user', '-timestamp').distinct('user')
|
||||
port_installations = PortInstallation.objects\
|
||||
.filter(
|
||||
submission_id__in=Subquery(submissions.values('id')),
|
||||
port__iexact=self.port_name
|
||||
)\
|
||||
.select_related('submission')\
|
||||
.only(
|
||||
'submission__user_id',
|
||||
)
|
||||
return port_installations
|
||||
|
||||
def get_result(self, obj):
|
||||
result = self.generate_time_range_query().values(*self.properties).annotate(count=Count('submission__user_id'))
|
||||
self.validate_sorting()
|
||||
if self.sort_by:
|
||||
result = sort_list_of_dicts_by_version(list(result), self.sort_by)
|
||||
return result
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from django.urls import path
|
||||
|
||||
from stats import views
|
||||
|
||||
urlpatterns = [
|
||||
|
||||
@@ -2,6 +2,19 @@ import itertools
|
||||
|
||||
ALLOWED_DAYS_FOR_STATS = [0, 7, 30, 90, 180, 365]
|
||||
ALLOWED_COLUMNS_FOR_SORT = ['port', '-port', 'total_count', '-total_count', '-req_count', 'req_count']
|
||||
ALLOWED_PROPERTIES = [
|
||||
'requested',
|
||||
'version',
|
||||
'variants',
|
||||
'submission__os_version',
|
||||
'submission__xcode_version',
|
||||
'submission__clt_version',
|
||||
'submission__os_arch',
|
||||
'submission__build_arch',
|
||||
'submission__platform',
|
||||
'submission__macports_version',
|
||||
'submission__cxx_stdlib'
|
||||
]
|
||||
|
||||
|
||||
def validate_stats_days(value):
|
||||
|
||||
+47
-16
@@ -5,12 +5,15 @@ from django.shortcuts import render
|
||||
from django.http import HttpResponse
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
|
||||
from django.db.models import Subquery, Count, Case, IntegerField, When, Q
|
||||
from django.db.models.functions import TruncMonth, Lower
|
||||
from django.db.models import Subquery, Count, Case, IntegerField, When
|
||||
from django.db.models.functions import TruncMonth
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from stats.models import Submission, PortInstallation
|
||||
from stats.validators import validate_stats_days, validate_columns_port_installations, validate_unique_columns_port_installations, ALLOWED_DAYS_FOR_STATS
|
||||
from stats.utilities.sort_by_version import sort_list_of_dicts_by_version
|
||||
from stats.serializers import PortStatisticsSerializer
|
||||
|
||||
|
||||
def stats(request):
|
||||
@@ -36,15 +39,25 @@ def stats(request):
|
||||
last_week_unique = all_submissions.filter(timestamp__week=current_week - 1).distinct('user').count()
|
||||
|
||||
# Number of unique users vs month
|
||||
users_by_month = Submission.objects.annotate(month=TruncMonth('timestamp')).values('month').annotate(num=Count('user_id', distinct=True))[:12]
|
||||
users_by_month = Submission.objects.annotate(month=TruncMonth('timestamp')).values('month').annotate(
|
||||
num=Count('user_id', distinct=True))[:12]
|
||||
|
||||
# System Stats for Current Users
|
||||
submissions_last_x_days = Submission.objects.filter(timestamp__range=[start_date, end_date]).order_by('user', '-timestamp').distinct('user')
|
||||
submissions_last_x_days = Submission.objects.filter(timestamp__range=[start_date, end_date]).order_by('user',
|
||||
'-timestamp').distinct(
|
||||
'user')
|
||||
submissions_unique = Submission.objects.filter(id__in=Subquery(submissions_last_x_days.values('id')))
|
||||
macports_version = sort_list_of_dicts_by_version(list(submissions_unique.values('macports_version').annotate(num=Count('macports_version'))), 'macports_version')
|
||||
os_version_and_clt_version = sort_list_of_dicts_by_version(list(submissions_unique.values('clt_version', 'os_version').annotate(num=Count('user_id', distinct=True))), 'os_version')
|
||||
os_version_build_arch_and_stdlib = sort_list_of_dicts_by_version(list(submissions_unique.values('os_version', 'build_arch', 'cxx_stdlib').annotate(num=Count('user_id', distinct=True))), 'os_version')
|
||||
os_version_and_xcode_version = sort_list_of_dicts_by_version(list(submissions_unique.values('xcode_version', 'os_version').annotate(num=Count('user_id', distinct=True))), 'os_version')
|
||||
macports_version = sort_list_of_dicts_by_version(
|
||||
list(submissions_unique.values('macports_version').annotate(num=Count('macports_version'))), 'macports_version')
|
||||
os_version_and_clt_version = sort_list_of_dicts_by_version(
|
||||
list(submissions_unique.values('clt_version', 'os_version').annotate(num=Count('user_id', distinct=True))),
|
||||
'os_version')
|
||||
os_version_build_arch_and_stdlib = sort_list_of_dicts_by_version(list(
|
||||
submissions_unique.values('os_version', 'build_arch', 'cxx_stdlib').annotate(
|
||||
num=Count('user_id', distinct=True))), 'os_version')
|
||||
os_version_and_xcode_version = sort_list_of_dicts_by_version(
|
||||
list(submissions_unique.values('xcode_version', 'os_version').annotate(num=Count('user_id', distinct=True))),
|
||||
'os_version')
|
||||
|
||||
return render(request, 'stats/stats.html', {
|
||||
'total_submissions': all_submissions.count(),
|
||||
@@ -121,14 +134,17 @@ def stats_port_installations_filter(request):
|
||||
|
||||
days = int(days)
|
||||
|
||||
submissions_unique = Submission.objects.filter(timestamp__gte=datetime.datetime.now(tz=datetime.timezone.utc)-datetime.timedelta(days=days)).order_by('user', '-timestamp').distinct('user')
|
||||
installations = PortInstallation.objects.order_by('port')\
|
||||
.filter(submission_id__in=Subquery(submissions_unique.values('id')))\
|
||||
.values('port').annotate(total_count=Count('port'))\
|
||||
.annotate(req_count=Count(Case(When(requested=True, then=1), output_field=IntegerField())))\
|
||||
.exclude(port__icontains='mpstats')\
|
||||
.filter(port__icontains=search_by)\
|
||||
.extra(select={'port': 'lower(port)'})\
|
||||
submissions_unique = Submission.objects.filter(
|
||||
timestamp__gte=datetime.datetime.now(tz=datetime.timezone.utc) - datetime.timedelta(days=days)).order_by('user',
|
||||
'-timestamp').distinct(
|
||||
'user')
|
||||
installations = PortInstallation.objects.order_by('port') \
|
||||
.filter(submission_id__in=Subquery(submissions_unique.values('id'))) \
|
||||
.values('port').annotate(total_count=Count('port')) \
|
||||
.annotate(req_count=Count(Case(When(requested=True, then=1), output_field=IntegerField()))) \
|
||||
.exclude(port__icontains='mpstats') \
|
||||
.filter(port__icontains=search_by) \
|
||||
.extra(select={'port': 'lower(port)'}) \
|
||||
.order_by(order_by_1, order_by_2, order_by_3)
|
||||
|
||||
paginated_obj = Paginator(installations, 100)
|
||||
@@ -169,3 +185,18 @@ def stats_submit(request):
|
||||
return HttpResponse("Something went wrong")
|
||||
else:
|
||||
return HttpResponse("Method Not Allowed")
|
||||
|
||||
|
||||
class PortStatisticsAPIView(APIView):
|
||||
def get(self, request):
|
||||
result = PortStatisticsSerializer(
|
||||
PortInstallation.objects.all(),
|
||||
context={
|
||||
'name': request.GET.get('name'),
|
||||
'days': request.GET.get('days', "30"),
|
||||
'days_ago': request.GET.get('days_ago', "0"),
|
||||
'property': request.GET.getlist('property'),
|
||||
'sort_by': request.GET.get('sort_by')
|
||||
}
|
||||
)
|
||||
return Response(result.data)
|
||||
|
||||
@@ -13,6 +13,7 @@ from category.urls import router as category_router
|
||||
from buildhistory.urls import router as buildhistory_router
|
||||
from maintainer.urls import router as maintainer_router
|
||||
from variant.urls import router as variants_router
|
||||
from stats.views import PortStatisticsAPIView
|
||||
|
||||
# Router for rest framework
|
||||
router = routers.DefaultRouter()
|
||||
@@ -36,6 +37,7 @@ urlpatterns = [
|
||||
path('maintainers/', include('maintainer.urls')),
|
||||
path('port/', include('port.urls')),
|
||||
url(r"api/v1/", include(router.urls)),
|
||||
url(r"api/v1/statistics/port", PortStatisticsAPIView.as_view()),
|
||||
path('categories/', include('category.urls')),
|
||||
path('variants/', include('variant.urls')),
|
||||
path('ports/search/', views.search, name='ports_search'),
|
||||
|
||||
Reference in New Issue
Block a user