diff --git a/portal/services/github.py b/portal/services/github.py index 3737441..6e76f1c 100644 --- a/portal/services/github.py +++ b/portal/services/github.py @@ -138,3 +138,53 @@ def get_repository_details(client: Client, repo_url: str): result = client.execute(query, variable_values={"owner": owner, "name": name}) return result + +def get_repository_commits(client: Client, repo_url: str, since: str): + owner, name = repo_url.split("/")[-2:] + query = gql( + """ + query RepoCommits($owner: String!, $name: String!, $since: GitTimestamp!) { + repository(owner: $owner, name: $name) { + refs(refPrefix: "refs/heads/", orderBy: {direction: DESC, field: TAG_COMMIT_DATE}, first: 100) { + edges { + node { + ... on Ref { + name + target { + ... on Commit { + history(since: $since) { + edges { + node { + ... on Commit { + commitUrl + committedDate + additions + deletions + committer { + user { + login + } + } + } + } + } + } + } + } + } + } + } + } + } + rateLimit { + limit + cost + remaining + resetAt + } + } + """ + ) + + result = client.execute(query, variable_values={"owner": owner, "name": name, "since": since}) + return result \ No newline at end of file diff --git a/portal/tasks.py b/portal/tasks.py index c48a351..69ecf28 100644 --- a/portal/tasks.py +++ b/portal/tasks.py @@ -5,10 +5,10 @@ from django.db.models import Manager from django.utils import timezone from requests import HTTPError +from django.core.cache import cache -from portal.models import Meeting -from portal.services import discord - +from portal.models import Meeting, Semester +from portal.services import discord, github @shared_task def delete_discord_channels(channel_ids: list[str]): @@ -32,3 +32,20 @@ def meetings_alert(): discord.send_message(os.environ["DISCORD_ALERTS_CHANNEL_ID"], { "content": f"Meeting **{meeting}** does not have presentation slides added yet!" }) + +@shared_task +def get_commits(): + semester = Semester.objects.latest("start_date") + for project in semester.projects.filter(is_approved=True).all(): + for repo in project.repositories.all(): + commits = {} + result = github.get_repository_commits(github.client_factory(), repo.url, semester.start_date.strftime('%Y-%m-%dT%H:%M:%S.%f%z')) + for branch in result["repository"]["refs"]["edges"]: + for commit in branch["node"]["target"]["history"]["edges"]: + if (commit["node"]["committer"]["user"] != None): + commits[commit["node"]["commitUrl"]] = { + "committer": commit["node"]["committer"]["user"]["login"], + "additions": commit["node"]["additions"], + "deletions": commit["node"]["deletions"] + } + cache.set(f"repo_commits_{repo.url}", commits, 60 * 60 * 24) \ No newline at end of file diff --git a/portal/templates/portal/commits/index.html b/portal/templates/portal/commits/index.html new file mode 100644 index 0000000..c24635e --- /dev/null +++ b/portal/templates/portal/commits/index.html @@ -0,0 +1,58 @@ +{% extends "portal/base.html" %} +{% load portal_extras %} + +{% block ogp %} + + + + + +{% endblock %} + +{% block title %} +Commits | RCOS IO +{% endblock %} + +{% block content %} +
+
+

Commits

+ + {% for url, commit in commits.items %} + {% if commit.committer == username %} +
+ {{ url }} + +{{ commit.additions }} + -{{ commit.deletions }} +
+ {% endif %} + {% endfor %} + {% if commits.items|length > 0 %} +
+ + {% else %} +
No Commits Available.
+ {% endif %} +
+
+ +{{ commits|json_script:"commits" }} + + +{% endblock %} \ No newline at end of file diff --git a/portal/templates/portal/includes/navbar.html b/portal/templates/portal/includes/navbar.html index 73f0572..c52d3fa 100644 --- a/portal/templates/portal/includes/navbar.html +++ b/portal/templates/portal/includes/navbar.html @@ -21,6 +21,7 @@ {% if request.user.is_authenticated and request.user.is_approved %} {% include "portal/includes/navbaritem.html" with view_name="small_groups_index" display="Small Groups" %} + {% include "portal/includes/navbaritem.html" with view_name="commits_index" display="Commits" %} {% endif %} {% include "portal/includes/navbaritem.html" with view_name="organizations_index" display="Organizations" %} {% include "portal/includes/navbaritem.html" with view_name="handbook" display="Handbook" %} diff --git a/portal/urls.py b/portal/urls.py index 9718151..af1c7f4 100644 --- a/portal/urls.py +++ b/portal/urls.py @@ -9,6 +9,7 @@ from portal.views.mentors import MentorApplicationView, mentor_applications_index from portal.views.organizations import organizations_index from portal.views.small_groups import SmallGroupIndexView, small_group_detail +from portal.views.commits import commits_index from .views.auth import ( discord_flow_callback, @@ -144,4 +145,10 @@ import_google_form_projects, name="import_projects", ), + # Commits + path( + "commits/", + commits_index, + name="commits_index" + ) ] diff --git a/portal/views/commits.py b/portal/views/commits.py new file mode 100644 index 0000000..da4cda2 --- /dev/null +++ b/portal/views/commits.py @@ -0,0 +1,27 @@ + + +from django.http import HttpRequest, HttpResponse +from django.template.response import TemplateResponse +from django.shortcuts import get_object_or_404 +from django.contrib.auth.decorators import login_required +from portal.services import github +from django.core.cache import cache; +from portal.tasks import get_commits; + +@login_required +def commits_index(request: HttpRequest) -> HttpResponse: + active_enrollment = request.user.get_active_enrollment() + commits = {} + + if active_enrollment is not None: + repos = active_enrollment.project.repositories.all() + for repo in repos: + repo_commits = cache.get(f"repo_commits_{repo.url}") + if repo_commits: + for url in repo_commits: + commits[url] = repo_commits[url] + + return TemplateResponse(request, "portal/commits/index.html", { + "commits": commits, + "username": request.user.github_username + }) \ No newline at end of file diff --git a/rcos_io/celery.py b/rcos_io/celery.py index b1609ac..fae947e 100644 --- a/rcos_io/celery.py +++ b/rcos_io/celery.py @@ -1,6 +1,8 @@ import os from celery import Celery +from celery.schedules import crontab +from celery.signals import worker_ready # Set the default Django settings module for the 'celery' program. os.environ.setdefault("DJANGO_SETTINGS_MODULE", "rcos_io.settings") @@ -15,3 +17,15 @@ # Load task modules from all registered Django apps. app.autodiscover_tasks() + +app.conf.beat_schedule = { + 'get_commits': { + 'task': 'portal.tasks.get_commits', + 'schedule': crontab(minute=0, hour=0) + }, +} + +@worker_ready.connect +def startup(sender, **kwargs): + with sender.app.connection() as conn: + sender.app.send_task("portal.tasks.get_commits", connection=conn) \ No newline at end of file