From 65ff054f57f942ae7910053f623d650d18d7e65b Mon Sep 17 00:00:00 2001 From: Elijah Melton Date: Mon, 23 Dec 2024 02:24:00 -0800 Subject: [PATCH 1/2] added endpoint for signup event observability --- .../0006_interviewpool_timestamp.py | 18 ++++++++ server/interview/models.py | 1 + server/interview/urls.py | 5 +++ server/interview/views.py | 44 ++++++++++++++++--- 4 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 server/interview/migrations/0006_interviewpool_timestamp.py diff --git a/server/interview/migrations/0006_interviewpool_timestamp.py b/server/interview/migrations/0006_interviewpool_timestamp.py new file mode 100644 index 0000000..e9a75a0 --- /dev/null +++ b/server/interview/migrations/0006_interviewpool_timestamp.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.17 on 2024-12-23 09:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('interview', '0005_rename_technical_question_interview_technical_questions'), + ] + + operations = [ + migrations.AddField( + model_name='interviewpool', + name='timestamp', + field=models.DateTimeField(auto_now_add=True, null=True), + ), + ] diff --git a/server/interview/models.py b/server/interview/models.py index 29741c1..dbc76dd 100644 --- a/server/interview/models.py +++ b/server/interview/models.py @@ -48,6 +48,7 @@ class InterviewPool(models.Model): on_delete=models.CASCADE, primary_key=True ) + timestamp = models.DateTimeField(auto_now_add=True, null=True) def __str__(self): return f"Interview Pool: {self.member}" diff --git a/server/interview/urls.py b/server/interview/urls.py index 93c348b..2bdbcfe 100644 --- a/server/interview/urls.py +++ b/server/interview/urls.py @@ -76,5 +76,10 @@ "assign//", views.InterviewAssignQuestionRandomIndividual.as_view(), name="assign-interview-question" + ), + path( + "signups/", + views.get_signup_data, + name="get-signup-data" ) ] diff --git a/server/interview/views.py b/server/interview/views.py index 370de91..14df117 100644 --- a/server/interview/views.py +++ b/server/interview/views.py @@ -63,6 +63,35 @@ def is_valid_availability(availability): ) +from rest_framework.decorators import api_view, permission_classes +from rest_framework.permissions import IsAdminUser +from django.utils import timezone +from datetime import timedelta + +@api_view(['GET']) +@permission_classes([]) +def get_signup_data(request): + days = int(request.query_params.get('days', 14)) + end_date = timezone.now() + start_date = end_date - timedelta(days=days) + + signups = InterviewPool.objects.filter( + timestamp__isnull=False, + timestamp__gte=start_date, + timestamp__lte=end_date + ).values('member__username', 'member_id', 'timestamp') + + signup_data = [ + { + 'username': signup['member__username'], + 'user_id': signup['member_id'], + 'timestamp': int(signup['timestamp'].timestamp() * 1000) + } + for signup in signups + ] + + return Response(signup_data) + # Create your views here. class AuthenticatedMemberSignupForInterview(APIView): permission_classes = [IsAuthenticated, IsVerified] @@ -119,16 +148,19 @@ def post(self, request): # Check if user is already in InterviewPool try: - InterviewPool.objects.get(member=request.user) + + ent = InterviewPool.objects.get(member=request.user) + ent.timestamp = timezone.now() + ent.save() interview_availability.save() logger.info( - f"User {request.user.username} updated their interview availability" + "User %s signed up for an interview at %s", + request.user.username, + ent.timestamp.isoformat() ) return Response( - { - "detail": "You have successfully updated your interview availability." - }, - status=status.HTTP_200_OK, + {"detail": "You have successfully signed up for an interview."}, + status=status.HTTP_201_CREATED, ) except InterviewPool.DoesNotExist: From 2e0c3610cb8d04a735b699eef0332634f33045c2 Mon Sep 17 00:00:00 2001 From: Elijah Melton Date: Sun, 29 Dec 2024 15:19:38 -0800 Subject: [PATCH 2/2] change to class based component and fix imports --- server/interview/urls.py | 2 +- server/interview/views.py | 107 +++++++++++++++++++------------------- 2 files changed, 54 insertions(+), 55 deletions(-) diff --git a/server/interview/urls.py b/server/interview/urls.py index 2bdbcfe..ed7ecdc 100644 --- a/server/interview/urls.py +++ b/server/interview/urls.py @@ -79,7 +79,7 @@ ), path( "signups/", - views.get_signup_data, + views.GetSignupData.as_view(), name="get-signup-data" ) ] diff --git a/server/interview/views.py b/server/interview/views.py index 14df117..c43771b 100644 --- a/server/interview/views.py +++ b/server/interview/views.py @@ -1,35 +1,36 @@ -from ast import In -from datetime import datetime -from datetime import datetime +from datetime import datetime, timedelta +from django.utils import timezone import random -from rest_framework import generics +from rest_framework import generics, status, permissions from rest_framework.permissions import IsAuthenticated -from django.db.models import Max +from rest_framework.response import Response +from rest_framework.views import APIView +from django.db.models import Max, Q +from django.core.exceptions import ValidationError +from django.db import transaction +import logging + import server.settings as settings -from questions.models import TechnicalQuestion, BehavioralQuestion +from questions.models import ( + TechnicalQuestion, + BehavioralQuestion, + TechnicalQuestionQueue, +) from custom_auth.permissions import IsAdmin, IsVerified from members.serializers import UserSerializer from members.models import User -from questions.models import TechnicalQuestion, BehavioralQuestion, TechnicalQuestionQueue -from questions.serializers import BehavioralQuestionSerializer, TechnicalQuestionSerializer +from questions.serializers import ( + BehavioralQuestionSerializer, + TechnicalQuestionSerializer, +) from .algorithm import CommonAvailabilityStableMatching from .notification import ( interview_paired_notification_html, interview_unpaired_notification_html, send_email, ) -from .models import Interview +from .models import Interview, InterviewAvailability, InterviewPool from .serializers import InterviewSerializer -from .models import InterviewAvailability, InterviewPool, Interview -from rest_framework import status -from rest_framework.response import Response -from rest_framework.views import APIView -from django.core.exceptions import ValidationError -from rest_framework import permissions -from django.db import transaction -from django.utils import timezone -from django.db.models import Q -import logging logger = logging.getLogger(__name__) @@ -63,34 +64,29 @@ def is_valid_availability(availability): ) -from rest_framework.decorators import api_view, permission_classes -from rest_framework.permissions import IsAdminUser -from django.utils import timezone -from datetime import timedelta - -@api_view(['GET']) -@permission_classes([]) -def get_signup_data(request): - days = int(request.query_params.get('days', 14)) - end_date = timezone.now() - start_date = end_date - timedelta(days=days) - - signups = InterviewPool.objects.filter( - timestamp__isnull=False, - timestamp__gte=start_date, - timestamp__lte=end_date - ).values('member__username', 'member_id', 'timestamp') - - signup_data = [ - { - 'username': signup['member__username'], - 'user_id': signup['member_id'], - 'timestamp': int(signup['timestamp'].timestamp() * 1000) - } - for signup in signups - ] +class GetSignupData(APIView): + permission_classes = [IsAdmin] + + def get(self, request): + days = int(request.query_params.get("days", 14)) + end_date = timezone.now() + start_date = end_date - timedelta(days=days) + + signups = InterviewPool.objects.filter( + timestamp__isnull=False, timestamp__gte=start_date, timestamp__lte=end_date + ).values("member__username", "member_id", "timestamp") + + signup_data = [ + { + "username": signup["member__username"], + "user_id": signup["member_id"], + "timestamp": int(signup["timestamp"].timestamp() * 1000), + } + for signup in signups + ] + + return Response(signup_data) - return Response(signup_data) # Create your views here. class AuthenticatedMemberSignupForInterview(APIView): @@ -156,7 +152,7 @@ def post(self, request): logger.info( "User %s signed up for an interview at %s", request.user.username, - ent.timestamp.isoformat() + ent.timestamp.isoformat(), ) return Response( {"detail": "You have successfully signed up for an interview."}, @@ -330,7 +326,10 @@ def post(self, request): InterviewPool.objects.filter(member__in=[p1, p2]).delete() # update question positions in queue - new_position = TechnicalQuestionQueue.objects.aggregate(Max('position'))['position__max'] + 1 + new_position = ( + TechnicalQuestionQueue.objects.aggregate(Max("position"))["position__max"] + + 1 + ) for tq in question_queues: tq.position = new_position tq.save() @@ -550,12 +549,10 @@ def get(self, request, interview_id): { "interview_id": interview.interview_id, "technical_questions": [ - question - for question in interview.technical_questions.all() + question for question in interview.technical_questions.all() ], "behavioral_questions": [ - question - for question in interview.behavioral_questions.all() + question for question in interview.behavioral_questions.all() ], } ) @@ -1002,13 +999,15 @@ def get(self, request): # question visibility is_interviewer = interview.interviewer == request.user - interview_has_passed = interview.date_effective + timezone.timedelta(days=7) < timezone.now() + interview_has_passed = ( + interview.date_effective + timezone.timedelta(days=7) + < timezone.now() + ) is_completed = interview.status in [ "inactive_completed", "inactive_incomplete", ] - if not (is_interviewer or is_completed or interview_has_passed): interview_data.pop("technical_questions", None) interview_data.pop("behavioral_questions", None)