Skip to content

Commit

Permalink
Merge pull request #13 from stevebrownlee/develop
Browse files Browse the repository at this point in the history
Phase 1 of Github Classroom obsolescence
  • Loading branch information
stevebrownlee authored Jul 7, 2024
2 parents 2607cf2 + ca47ee8 commit 9bc6ffc
Show file tree
Hide file tree
Showing 14 changed files with 921 additions and 278 deletions.
175 changes: 175 additions & 0 deletions LearningAPI/migrations/0048_add_assessment_url_to_db_function.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# Generated by Django 4.2.8 on 2024-01-10 18:42

from django.db import migrations


class Migration(migrations.Migration):
dependencies = [
("LearningAPI", "0047_add_capstone_status_id_to_db_function"),
]

operations = [
migrations.RunSQL(
"""
DROP FUNCTION IF EXISTS get_cohort_student_data(INT);
CREATE FUNCTION get_cohort_student_data(selected_cohort_id INT)
RETURNS TABLE (
user_id INT,
student_name TEXT,
score INT,
github_handle TEXT,
extra_data TEXT,
current_cohort TEXT,
current_cohort_id INT,
assessment_status_id INT,
assessment_url TEXT,
current_project_id INT,
current_project_index INT,
current_project_name TEXT,
current_book_id INT,
current_book_index INT,
current_book_name TEXT,
student_notes TEXT,
student_tags TEXT,
capstone_proposals TEXT,
project_duration DOUBLE PRECISION
) AS $$
BEGIN
RETURN QUERY
SELECT
nu.id::int AS user_id,
au."first_name" || ' ' || au."last_name" AS student_name,
COALESCE(lr.total_score, 0)::int AS score,
nu.github_handle::text,
social.extra_data::text,
c.name::text AS current_cohort,
c.id::int AS current_cohort_id,
COALESCE(sa.status_id::int, 0) AS assessment_status_id,
sa.url::text AS assessment_url,
sp.project_id::int AS current_project_id,
p.index::int AS current_project_index,
p.name::text AS current_project_name,
b.id::int AS current_book_id,
b.index::int AS current_book_index,
b.name::text AS current_book_name,
COALESCE(
json_agg(
json_build_object(
'note_id', sn.id,
'note', sn.note,
'created_on', sn.created_on
)
)
)::text AS student_notes,
COALESCE(
(
SELECT json_agg(
json_build_object(
'id', st."id",
'tag', t."name"
)
)
FROM "LearningAPI_studenttag" st
LEFT JOIN "LearningAPI_tag" t ON t."id" = st."tag_id"
WHERE st."student_id" = nu.id
)
, '[]')::text AS student_tags,
COALESCE(
(
SELECT json_agg(
json_build_object(
'id', c."id",
'status', ps.status,
'current_status_id', ps.id,
'proposal_url', c."proposal_url",
'created_on', tl.date,
'course_name', cr.name
)
)
FROM "LearningAPI_capstone" c
LEFT JOIN (
SELECT DISTINCT ON (
ct.capstone_id, c.course_id
) *
FROM "LearningAPI_capstonetimeline" ct
JOIN "LearningAPI_capstone" c ON c.id = ct.capstone_id
ORDER BY
c.course_id,
ct.capstone_id,
date desc
) tl ON tl.capstone_id = c.id
LEFT JOIN "LearningAPI_proposalstatus" ps ON ps."id" = tl.status_id
LEFT JOIN "LearningAPI_course" cr ON c.course_id = cr.id
WHERE c."student_id" = nu.id
), '[]'
)::text AS capstone_proposals,
CASE
WHEN sa.id IS NOT NULL AND sa.assessment_id = la.id THEN
(
EXTRACT(YEAR FROM AGE(NOW(), sa.date_created)) * 365 +
EXTRACT(MONTH FROM AGE(NOW(), sa.date_created)) * 30 +
EXTRACT(DAY FROM AGE(NOW(), sa.date_created))
)::double precision
ELSE
(
EXTRACT(YEAR FROM AGE(NOW(), sp.date_created)) * 365 +
EXTRACT(MONTH FROM AGE(NOW(), sp.date_created)) * 30 +
EXTRACT(DAY FROM AGE(NOW(), sp.date_created))
)::double precision
END AS project_duration
FROM "LearningAPI_nssuser" nu
JOIN "auth_user" au ON au."id" = nu."user_id"
LEFT JOIN "LearningAPI_nssusercohort" nc ON nc."nss_user_id" = nu."id"
LEFT JOIN "LearningAPI_cohort" c ON c."id" = nc."cohort_id"
LEFT JOIN "LearningAPI_studentnote" sn ON sn."student_id" = nu."id"
LEFT JOIN "LearningAPI_studenttag" stg ON stg."student_id" = nu."id"
LEFT JOIN "LearningAPI_tag" tag ON stg.tag_id = tag.id
LEFT JOIN "socialaccount_socialaccount" social ON social.user_id = nu.user_id
LEFT JOIN "LearningAPI_capstone" sc ON sc.student_id = nu."id"
LEFT JOIN "LearningAPI_studentproject" sp
ON sp."student_id" = nu."id"
AND sp.id = (
SELECT id
FROM "LearningAPI_studentproject"
WHERE "student_id" = nu."id"
ORDER BY id DESC
LIMIT 1
)
LEFT JOIN "LearningAPI_project" p ON p."id" = sp."project_id"
LEFT JOIN "LearningAPI_book" b ON b."id" = p."book_id"
LEFT JOIN "LearningAPI_assessment" la
ON b.id = la.book_id
LEFT JOIN "LearningAPI_studentassessment" sa
ON sa."student_id" = nu."id"
AND sa."assessment_id" = la."id"
AND sa."date_created" = (
SELECT MAX("date_created")
FROM "LearningAPI_studentassessment"
WHERE "student_id" = nu."id"
AND "assessment_id" = la."id"
)
AND sa.assessment_id = la.id
LEFT JOIN (
SELECT lr."student_id", SUM(lw."weight") AS total_score
FROM "LearningAPI_learningrecord" lr
JOIN "LearningAPI_learningweight" lw ON lw."id" = lr."weight_id"
WHERE lr."achieved" = true
GROUP BY lr."student_id"
) lr ON lr."student_id" = nu."id"
WHERE nc."cohort_id" = selected_cohort_id
AND au.is_active = TRUE
AND au.is_staff = FALSE
GROUP BY nu.id, nu.github_handle, social.extra_data, sa.url,
student_name, current_cohort, current_cohort_id, assessment_status_id,
current_project_id, current_project_index, current_project_name,
project_duration, current_book_id, current_book_index, current_book_name,
score
ORDER BY b.index ASC,
p.index ASC;
END;
$$ LANGUAGE plpgsql;
""",
""
),
]
5 changes: 4 additions & 1 deletion LearningAPI/models/people/nssuser.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ def assessment_overview(self):
"name": assessment.assessment.name,
"status": assessment.status.status,
"book": assessment.assessment.assigned_book,
"reviewed_by": assessment.instructor.user.first_name
"reviewed_by": assessment.instructor.user.first_name,
"github_url": assessment.url
})
return assessment_list

Expand All @@ -83,6 +84,7 @@ def current_cohort(self):
"zoom_url": assignment.cohort.info.zoom_url,
"start": assignment.cohort.start_date,
"end": assignment.cohort.end_date,
"ic": assignment.cohort.slack_channel,
"github_org": assignment.cohort.info.student_organization_url,
"courses": assignment.cohort.courses.order_by('index').values('course__name', 'course__id', 'active'),
}
Expand All @@ -95,5 +97,6 @@ def current_cohort(self):
"id": assignment.cohort.id,
"start": assignment.cohort.start_date,
"end": assignment.cohort.end_date,
"ic": assignment.cohort.slack_channel,
"courses": assignment.cohort.courses.order_by('index').values('course__name', 'course__id', 'active'),
}
9 changes: 3 additions & 6 deletions LearningAPI/models/skill/learning_record_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,10 @@

class LearningRecordEntry(models.Model):
"""Model for tracking individual notes on learning objectives per student"""
record = models.ForeignKey(
"LearningRecord", on_delete=models.CASCADE, related_name="entries")
record = models.ForeignKey("LearningRecord", on_delete=models.CASCADE, related_name="entries")
note = models.TextField()
recorded_on = models.DateField(
null=False, blank=True, default=datetime.date.today, editable=False)
instructor = models.ForeignKey(
"NssUser", on_delete=models.CASCADE, related_name='student_records')
recorded_on = models.DateField(null=False, blank=True, default=datetime.date.today, editable=False)
instructor = models.ForeignKey("NssUser", on_delete=models.CASCADE, related_name='student_records')

class Meta:
ordering = ("-recorded_on",)
77 changes: 77 additions & 0 deletions LearningAPI/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import json
import time
import os
import requests
from requests.exceptions import ConnectionError


class GithubRequest(object):
def __init__(self):
self.headers = {
"Content-Type": "application/json",
"Accept": "Accept: application/vnd.github+json",
"User-Agent": "nss/ticket-migrator",
"X-GitHub-Api-Version": "2022-11-28",
"Authorization": f'Bearer {os.getenv("GITHUB_TOKEN")}'
}

def get(self, url):
return self.request_with_retry(
lambda: requests.get(url=url, headers=self.headers))

def put(self, url, data):
json_data = json.dumps(data)

return self.request_with_retry(
lambda: requests.put(url=url, data=json_data, headers=self.headers))

def post(self, url, data):
json_data = json.dumps(data)

try:
result = self.request_with_retry(
lambda: requests.post(url=url, data=json_data, headers=self.headers))

return result

except TimeoutError:
print("Request timed out. Trying next...")

except ConnectionError:
print("Request timed out. Trying next...")

return None

def request_with_retry(self, request):
retry_after_seconds = 1800
number_of_retries = 0

response = request()

while response.status_code == 403 and number_of_retries <= 10:
number_of_retries += 1

os.system('cls' if os.name == 'nt' else 'clear')
self.sleep_with_countdown(retry_after_seconds)

response = request()

return response

def sleep_with_countdown(self, countdown_seconds):
ticks = countdown_seconds * 2
for count in range(ticks, -1, -1):
remaining = str(int(0.5 + count / 2)).rjust(2)
spinner = ['-', '/', '|', '\\'][count % 4]

progress = '=' * (ticks - count)
if count:
progress = progress[:-1] + '>'

print(
f'[bright_white] {spinner} [{progress.ljust(ticks)}] {remaining}[/bright_white]', end='\r')

if count:
time.sleep(0.5)

print()
2 changes: 0 additions & 2 deletions LearningAPI/views/course_view.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from django.utils.decorators import method_decorator
from django.http import HttpResponseServerError
from django.db.models import Avg, F, Func, FloatField, fields, ExpressionWrapper

from rest_framework import serializers, status
from rest_framework.decorators import action
Expand All @@ -10,7 +9,6 @@
from LearningAPI.decorators import is_instructor
from LearningAPI.models.coursework import (
Course, Book, Project, CohortCourse,
StudentProject
)
from LearningAPI.models.people import Assessment

Expand Down
43 changes: 28 additions & 15 deletions LearningAPI/views/notify.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,33 @@
from rest_framework.response import Response
from LearningAPI.models.people import NssUser

def slack_notify(message, channel):
"""
Sends a notification message to a Slack channel.
Args:
message (str): The message to send.
channel (str): The Slack channel to send the message to.
Raises:
requests.exceptions.Timeout: If the request to the Slack API times out.
"""

headers = {
"Content-Type": "application/x-www-form-urlencoded"
}

requests.post(
"https://slack.com/api/chat.postMessage",
data={
"text": message,
"token": os.getenv("SLACK_BOT_TOKEN"),
"channel": channel
},
headers=headers,
timeout=10
)

@api_view(['POST'])
def notify(request):
"""
Expand All @@ -28,20 +55,6 @@ def notify(request):
slack_channel = student.assigned_cohorts.order_by("-id").first().cohort.slack_channel

message = request.data.get("message")

headers = {
"Content-Type": "application/x-www-form-urlencoded"
}

requests.post(
"https://slack.com/api/chat.postMessage",
data={
"text": message,
"token": os.getenv("SLACK_BOT_TOKEN"),
"channel": slack_channel
},
headers=headers,
timeout=10
)
slack_notify(message, slack_channel)

return Response({ 'message': 'Notification sent to Slack!'}, status=200)
1 change: 1 addition & 0 deletions LearningAPI/views/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ def get_project(self, obj):
"id": project.project.id,
"name": project.project.name,
"book_name": project.project.book.name,
"book_id": project.project.book.id,
}
else:
return {
Expand Down
Loading

0 comments on commit 9bc6ffc

Please sign in to comment.