Add port statistics api endpoint

This commit is contained in:
arjunsalyan
2020-06-10 15:40:56 +05:30
committed by Arjun Salyan
parent c181163fbf
commit 57ea4891f4
5 changed files with 153 additions and 16 deletions
+90
View File
@@ -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
View File
@@ -1,4 +1,5 @@
from django.urls import path
from stats import views
urlpatterns = [
+13
View File
@@ -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
View File
@@ -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)
+2
View File
@@ -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'),