Skip to content

Commit

Permalink
feat: add lecture participation tracker using GitHub Actions
Browse files Browse the repository at this point in the history
  • Loading branch information
bjornthiberg committed Sep 11, 2024
1 parent 9fa68a7 commit fd80125
Show file tree
Hide file tree
Showing 2 changed files with 260 additions and 0 deletions.
36 changes: 36 additions & 0 deletions .github/workflows/lecture_participation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Lecture Participation Information

on:
issue_comment:
types: [created]

permissions:
issues: write

jobs:
track-participation:
if: github.event.issue.title == 'Lecture participation tracker 2024'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Setup Python
uses: actions/[email protected]
with:
python-version: '3.x'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
if [ -f ./tools/requirements.txt ]; then pip install -r ./tools/requirements.txt; fi
- name: Set timezone to Europe/Stockholm
run: sudo timedatectl set-timezone Europe/Stockholm

- name: Update participation tracker
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO_FULLNAME: ${{ github.repository }}
TRACKER_ISSUE_NUMBER: ${{ github.event.issue.number }}

run: python ./tools/track_participation.py --printMarkdown --publish
224 changes: 224 additions & 0 deletions tools/track_participation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Filename: track_participation.py

import sys
import os
import logging
from github import Github, GithubException
import getopt
from datetime import datetime, timedelta
from prettytable import PrettyTable

PRINT_PARTICIPATION = False
PRINT_IN_MARKDOWN = False
PUBLISH = False

GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
REPO_FULLNAME = os.getenv("REPO_FULLNAME")
ISSUE_NUMBER = os.getenv("TRACKER_ISSUE_NUMBER")

# Lecture details
LECTURE_DATES_TO_NUMBER = {
"2024-09-04": 2, "2024-09-11": 3, "2024-09-18": 4, "2024-09-25": 5,
"2024-10-02": 6, "2024-10-09": 7
}

LECTURE_DATES_TO_START_TIME = {
"2024-09-04": 13, "2024-09-11": 13, "2024-09-18": 13, "2024-09-25": 13,
"2024-10-02": 13, "2024-10-09": 13
}

COMMENTING_DURATION_HOURS = 5 # 5 hours to give some leeway.


def main():
handle_args(sys.argv[1:])

repo, issue = get_repo_and_issue()

participation = get_participation(issue)

print_content = ""

if PRINT_PARTICIPATION:
print_content = get_participation_text(participation)

if PRINT_IN_MARKDOWN:
print_content = get_participation_markdown(participation)

if PUBLISH:
update_issue_description(participation, issue)
print("Updated issue description\n")

print(print_content)


def get_repo_and_issue():
"""
Attempts to fetch the GitHub repository and issue using environment variables.
Exits on failure.
"""
try:
github = Github(GITHUB_TOKEN)
repo = github.get_repo(REPO_FULLNAME)
issue = repo.get_issue(number=int(ISSUE_NUMBER))
return repo, issue
except GithubException as e:
logging.error(f"GitHub API error: {str(e)}")
sys.exit(1)


def get_participation(tracking_issue):
"""
Gets participation by checking each comment on the tracker issue.
Adds the lecture to the comment author if comment exists. Except if:
comment is from collaborator
comment is made outside of allowed time.
"""
participation = {}

try:
collaborators = {collaborator.login for collaborator in tracking_issue.repository.get_collaborators()}
except GithubException as e:
logging.error(f"GitHub API error: {str(e)}")
sys.exit(1)

for comment in tracking_issue.get_comments():
author = comment.user.login
comment_time = comment.created_at.astimezone()

# Ignore collaborators' comments
if author in collaborators:
continue

if is_valid_lecture_time(comment_time):
lecture_date = comment_time.strftime("%Y-%m-%d")
if author not in participation:
participation[author] = {lecture_date}
else:
participation[author].add(lecture_date)

return participation


def is_valid_lecture_time(comment_time):
"""
Checks if the comment was made on a valid day and time.
"""
lecture_date_str = comment_time.strftime("%Y-%m-%d")

if lecture_date_str not in LECTURE_DATES_TO_START_TIME:
# comment not made on a lecture day
return False

lecture_start_hour = LECTURE_DATES_TO_START_TIME[lecture_date_str]

allowed_period_start = comment_time.replace(hour=lecture_start_hour, minute=0, second=0, microsecond=0)
allowed_period_end = allowed_period_start + timedelta(hours=COMMENTING_DURATION_HOURS)

# Check if the comment time falls within the valid window
return allowed_period_start <= comment_time <= allowed_period_end


def update_issue_description(participation, issue):
"""
Updates the repository issue description in markdown to reflect new participation.
"""
content = get_participation_markdown(participation)
try:
issue.edit(body=content)
except GithubException as e:
logging.error(f"GitHub API error: {str(e)}")
sys.exit(1)


def get_participation_markdown(participation):
"""
Returns markdown table representation of participation.
"""
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

content = f"Here we track active participation in lectures.\n\n"
content += ("To do this, you record as a comment the question you make to presentations or demos during the "
"lectures.\n\n")
content += "Also, provide the title of the presentation/demo.\n\n"
content += f"### Lecture Participation Stats (Updated on {current_time})\n\n"
content += "| Index | Student Name | Number of Lectures Attended | Lecture(s) attended |\n"
content += "|-------|--------------|-------------------|----------------|\n"

index = 1
for author, lectures in participation.items():
lecture_numbers = [f"L{LECTURE_DATES_TO_NUMBER[lecture]}" for lecture in sorted(lectures)]
lectures_list = " ".join(map(str, lecture_numbers))
total_lectures = len(lectures)
content += f"| {index} | {author} | {total_lectures} | {lectures_list} |\n"
index += 1

return content


def get_participation_text(participation):
"""
Returns plaintext table representation of participation.
"""
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

table = PrettyTable()
table.field_names = ["Index", "Student Name", "Number of Lectures Attended", "Lecture(s) attended"]

index = 1
for author, lectures in participation.items():
lecture_numbers = [f"L{LECTURE_DATES_TO_NUMBER[lecture]}" for lecture in sorted(lectures)]
lectures_list = " ".join(map(str, lecture_numbers))
total_lectures = len(lectures)
table.add_row([index, author, total_lectures, lectures_list])
index += 1

return_str = f"Lecture Participation Stats (Updated on {current_time})\n\n"
return_str += table.get_string()

return return_str


def handle_args(argv):
global PRINT_PARTICIPATION
global PRINT_IN_MARKDOWN
global PUBLISH

try:
opts, args = getopt.getopt(argv, "hm", ["help", "printPlain", "printMarkdown", "publish"])
except getopt.GetoptError as error:
logging.error(error)
print_help_info()
sys.exit(2)

for opt, _ in opts:
if opt == "--help":
print_help_info()
sys.exit()
elif opt == "--printPlain":
PRINT_PARTICIPATION = True
elif opt == "--printMarkdown":
PRINT_IN_MARKDOWN = True
elif opt == "--publish":
PUBLISH = True


def print_help_info():
print('')
print('DD2482 Student Lecture Participation Tracker Tool')
print('optional:')
print(' --printPlain Print lecture participation')
print(' --printMarkdown Print participation in markdown syntax')
print(' --publish Update the participation tracker issue')
print('')
print('track_participation.py --help to display this help info')


if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
if not GITHUB_TOKEN or not REPO_FULLNAME or not ISSUE_NUMBER:
logging.error("Required environment variables (GITHUB_TOKEN, REPO_FULLNAME, ISSUE_NUMBER) are missing")
sys.exit(1)
main()

0 comments on commit fd80125

Please sign in to comment.