-
Notifications
You must be signed in to change notification settings - Fork 3
Gamify repo - Add contributor leaderboard and points system #82
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds a comprehensive gamification system to incentivize repository contributions through a point-based leaderboard with badges, streaks, and multipliers. The system includes Python scripts for points management, automated GitHub Actions workflows for updates, and an embedded leaderboard display in the main README.
Key changes:
- Point calculation engine with multipliers, bonuses, and streak tracking
- SQLite database schema for contributors, contributions, and badges
- Automated leaderboard updates via GitHub Actions running every 6 hours
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 27 comments.
Show a summary per file
| File | Description |
|---|---|
gamification/python/points_service.py |
Core points calculation logic with priority multipliers, speed bonuses, and streak rewards |
gamification/python/database.py |
Database layer handling contributors, contributions, badges, and leaderboard queries |
gamification/python/manage_points.py |
Interactive CLI tool for maintainers to manually award points |
gamification/python/update_readme.py |
Script to generate and inject HTML leaderboard into main README |
gamification/python/generate_leaderboard_md.py |
Generates detailed leaderboard markdown file |
gamification/database/schema.sql |
SQLite schema defining tables, indexes, and seeding default actions/badges |
gamification/python/gamification.db |
Pre-populated SQLite database with sample data |
.github/workflows/update-leaderboard.yml |
Scheduled workflow to update leaderboard every 6 hours |
.github/workflows/auto-award-points.yml |
Event-driven workflow to auto-award points on PR merge and issue closure |
gamification/README.md |
Comprehensive documentation of the gamification system |
gamification/LEADERBOARD.md |
Generated leaderboard page with top contributors |
gamification/.gitignore |
Python and database-specific ignore patterns |
README.md |
Updated with embedded gamification leaderboard section |
| - name: Update main README with leaderboard | ||
| run: | | ||
| cd gamification/python | ||
| python update_readme.py |
Copilot
AI
Nov 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential database file not found error. The workflow assumes gamification.db exists, but if this is the first run or the database doesn't exist, Python scripts may fail. Consider adding initialization logic or checking for database existence before running update scripts.
| - name: Commit database changes | ||
| run: | | ||
| git config --local user.email "github-actions[bot]@users.noreply.github.com" | ||
| git config --local user.name "github-actions[bot]" | ||
| git add gamification/python/gamification.db | ||
| git diff --quiet && git diff --staged --quiet || git commit -m "chore: update gamification points [skip ci]" | ||
| git push | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
Copilot
AI
Nov 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Security risk: Committing database file directly. The workflow commits gamification.db directly to the repository, which can lead to merge conflicts and data inconsistencies when multiple workflows run concurrently. Consider using a database service or implementing proper locking mechanisms.
| - name: Commit database changes | |
| run: | | |
| git config --local user.email "github-actions[bot]@users.noreply.github.com" | |
| git config --local user.name "github-actions[bot]" | |
| git add gamification/python/gamification.db | |
| git diff --quiet && git diff --staged --quiet || git commit -m "chore: update gamification points [skip ci]" | |
| git push | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # (Removed the "Commit database changes" step to prevent committing and pushing the database file) |
gamification/database/schema.sql
Outdated
| id INTEGER PRIMARY KEY AUTOINCREMENT, | ||
| name VARCHAR(255) UNIQUE NOT NULL, | ||
| description TEXT, | ||
| tier VARCHAR(50) NOT NULL, -- Bronze, Silver, Gold, Platinum, Special |
Copilot
AI
Nov 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Misleading comment in database schema. The comment states "Bronze, Silver, Gold, Platinum, Special" for badge tiers, but there's no enforcement of these values in the database schema. Consider adding a CHECK constraint to ensure only valid tier values can be inserted.
| tier VARCHAR(50) NOT NULL, -- Bronze, Silver, Gold, Platinum, Special | |
| tier VARCHAR(50) NOT NULL CHECK (tier IN ('Bronze', 'Silver', 'Gold', 'Platinum', 'Special')), -- Bronze, Silver, Gold, Platinum, Special |
gamification/python/database.py
Outdated
| for badge in badges: | ||
| # Check if already awarded | ||
| cursor.execute( | ||
| "SELECT id FROM contributor_badges WHERE contributor_id = ? AND badge_id = ?", | ||
| (contributor_id, badge['id']) | ||
| ) | ||
| if cursor.fetchone(): | ||
| continue | ||
|
|
||
| # Check criteria | ||
| criteria = json.loads(badge['criteria']) if badge['criteria'] else {} | ||
|
|
||
| if self._check_badge_criteria(contributor, category_stats, criteria): | ||
| if self.award_badge(contributor_id, badge['id']): | ||
| newly_awarded.append(badge) |
Copilot
AI
Nov 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
N+1 query problem. For each badge being checked (lines 277-291), a separate query is executed to check if it's already awarded (lines 279-283). This results in N+1 database queries. Consider fetching all awarded badges for the contributor in a single query before the loop and checking membership in memory.
gamification/python/update_readme.py
Outdated
| conn = sqlite3.connect(db_path) | ||
| cursor = conn.cursor() | ||
|
|
||
| # Get top 10 contributors | ||
| cursor.execute(""" | ||
| SELECT | ||
| c.github_username, | ||
| c.total_points, | ||
| c.current_streak, | ||
| COUNT(con.id) as contribution_count | ||
| FROM contributors c | ||
| LEFT JOIN contributions con ON c.id = con.contributor_id | ||
| GROUP BY c.github_username | ||
| ORDER BY c.total_points DESC | ||
| LIMIT 10 | ||
| """) | ||
|
|
||
| rows = cursor.fetchall() | ||
| conn.close() | ||
|
|
Copilot
AI
Nov 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing error handling for database connection. If sqlite3.connect() fails, the connection won't be properly closed. Consider wrapping database operations in a try-finally block or using a context manager.
| conn = sqlite3.connect(db_path) | |
| cursor = conn.cursor() | |
| # Get top 10 contributors | |
| cursor.execute(""" | |
| SELECT | |
| c.github_username, | |
| c.total_points, | |
| c.current_streak, | |
| COUNT(con.id) as contribution_count | |
| FROM contributors c | |
| LEFT JOIN contributions con ON c.id = con.contributor_id | |
| GROUP BY c.github_username | |
| ORDER BY c.total_points DESC | |
| LIMIT 10 | |
| """) | |
| rows = cursor.fetchall() | |
| conn.close() | |
| with sqlite3.connect(db_path) as conn: | |
| cursor = conn.cursor() | |
| # Get top 10 contributors | |
| cursor.execute(""" | |
| SELECT | |
| c.github_username, | |
| c.total_points, | |
| c.current_streak, | |
| COUNT(con.id) as contribution_count | |
| FROM contributors c | |
| LEFT JOIN contributions con ON c.id = con.contributor_id | |
| GROUP BY c.github_username | |
| ORDER BY c.total_points DESC | |
| LIMIT 10 | |
| """) | |
| rows = cursor.fetchall() | |
gamification/python/manage_points.py
Outdated
| username, | ||
| 'pr_merged', | ||
| { | ||
| 'pr_number': int(pr_number), |
Copilot
AI
Nov 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing error handling for invalid input. If the user enters an invalid PR number (non-numeric), int(pr_number) will raise a ValueError. Wrap this in a try-except block to provide a user-friendly error message.
|
|
||
| # Check if this is a bug fix (higher points) | ||
| if pr_data.get('is_bug_fix'): | ||
| action = 'bug_fixed' |
Copilot
AI
Nov 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Variable action is not used.
| action = 'bug_fixed' |
gamification/python/database.py
Outdated
|
|
||
| import sqlite3 | ||
| import json | ||
| from datetime import datetime, timedelta |
Copilot
AI
Nov 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import of 'timedelta' is not used.
| from datetime import datetime, timedelta | |
| from datetime import datetime |
| Handles all logic for calculating points with multipliers, bonuses, and streaks. | ||
| """ | ||
|
|
||
| from datetime import datetime, timedelta |
Copilot
AI
Nov 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import of 'timedelta' is not used.
| from datetime import datetime, timedelta | |
| from datetime import datetime |
gamification/python/update_readme.py
Outdated
| """ | ||
|
|
||
| import sqlite3 | ||
| import os |
Copilot
AI
Nov 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import of 'os' is not used.
| import os |
| pull_request_review: | ||
| types: [submitted] | ||
| pull_request: | ||
| types: [closed] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you also add this -
branches: [main] # Only award points for merges to main
README.md
Outdated
| <table> | ||
| <thead> | ||
| <tr> | ||
| <th align="center">🏆 Rank</th> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you remove the test data?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
may be use something like this -
🎉 Be the first contributor!
README.md
Outdated
|
|
||
| **[View full leaderboard and badges →](gamification/LEADERBOARD.md)** | ||
|
|
||
| *Leaderboard updated every 6 hours by GitHub Actions. [Learn about our gamification system →](gamification/README.md)* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is correct, this gets triggered whenever a certain action happens. for e.g., when someone closes a github pr in this repo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you confirm this and make the change?
scripts/assign_points.py
Outdated
| pr_body = (pull_request.get('body') or '').lower() | ||
| if 'doc' in pr_title or 'readme' in pr_title or 'doc' in pr_body: | ||
| points += cfg['points']['documentation'] | ||
| scoring_breakdown.append(f"documentation: +{cfg['points']['documentation']}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this only checks the title and body and not the actual files, so if a pr's title is "documented bug" it gets the documentation changes related points, even though the files changed are not related to documentation. can you please modify this code to check the files?
scripts/config_points.yml
Outdated
| issue_resolved: 1 # Points for closing an issue | ||
| documentation: 3 # Points for documentation improvements (README, docs/ folder) | ||
| security_fix: 8 # Points for security-related fixes | ||
| code_contribution: 2 # Points for general code contributions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we have implementation for the code_contribution? I'm unable to find it
| scoring_breakdown.append(f"security_fix: +{cfg['points']['security_fix']}") | ||
|
|
||
| # Check for issue closed event | ||
| elif action == "closed" and issue and not pull_request: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's check if there is a way to award only if the state_reason=="completed". Otherwise the points will be awarded if I open an issue and close it without any reason.
…verify issue state_reason, remove test data and unimplemented code_contribution
scripts/assign_points.py
Outdated
|
|
||
| # Check for documentation changes by examining files changed | ||
| # We need to make an API call to get the list of files | ||
| import requests |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is already imported at the beginning, can we remove it?
| # Check for documentation changes by examining files changed | ||
| # We need to make an API call to get the list of files | ||
| import requests | ||
| repo = os.getenv('GITHUB_REPOSITORY') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you might have to "import os" at the beginning. Can you try to create some tests locally and run these changes to see if they work? copilot can create tests
scripts/assign_points.py
Outdated
|
|
||
| # Check for security fixes (labels or keywords) | ||
| labels = [label.get('name', '').lower() for label in pull_request.get('labels', [])] | ||
| if 'security' in labels or any('security' in label for label in labels): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we make this change - if any('security' in label for label in labels)
| if: > | ||
| github.event_name == 'pull_request_review' || | ||
| (github.event_name == 'pull_request' && github.event.pull_request.merged == true) || | ||
| github.event_name == 'issues' || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
here we might have to check if the issue is closed. Can you check the documentation and see if we have to change it from "github.event_name == 'issues'" to "github.event_name == 'issues' && github.event.action == 'closed'"
No description provided.