-
Notifications
You must be signed in to change notification settings - Fork 426
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add lecture participation tracker using GitHub Actions
- Loading branch information
1 parent
9fa68a7
commit fd80125
Showing
2 changed files
with
260 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |