Created
May 8, 2025 18:40
-
-
Save kscottz/55cd4ebc0532b1b6c327fd9f285bb02a to your computer and use it in GitHub Desktop.
This file contains hidden or 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
import requests | |
from collections import defaultdict | |
import time | |
def get_closed_issues_leaderboard(repo_owner, repo_name): | |
""" | |
Fetches closed issues from a public GitHub repository and generates a leaderboard | |
of closed issues by assignee. | |
Args: | |
repo_owner (str): The owner of the GitHub repository (e.g., 'octocat'). | |
repo_name (str): The name of the GitHub repository (e.g., 'Spoon-Knife'). | |
Returns: | |
str: A Markdown formatted string of the leaderboard. | |
""" | |
api_url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/issues" | |
all_issues = [] | |
page = 1 | |
per_page = 100 # Max per page is 100 for REST API | |
print(f"Fetching closed issues from {repo_owner}/{repo_name}...") | |
while True: | |
params = { | |
"state": "closed", | |
"per_page": per_page, | |
"page": page, | |
# You can add a 'since' parameter to fetch issues closed after a certain date | |
# "since": "2024-01-01T00:00:00Z" | |
} | |
try: | |
response = requests.get(api_url, params=params) | |
response.raise_for_status() # Raise an exception for HTTP errors (4xx or 5xx) | |
issues_page = response.json() | |
if not issues_page: | |
break # No more issues on this page, so no more issues to fetch | |
all_issues.extend(issues_page) | |
page += 1 | |
# Check Link header for pagination to determine if there are more pages | |
if 'link' in response.headers: | |
if 'rel="next"' not in response.headers['link']: | |
break # No 'next' link, so no more pages | |
else: | |
# If no link header and issues are less than per_page, assume last page | |
if len(issues_page) < per_page: | |
break | |
# Respect rate limits for unauthenticated requests | |
# GitHub allows 60 requests per hour without authentication. | |
# This simple sleep helps prevent hitting the limit too quickly for small repos. | |
# For larger repos, you might need more sophisticated rate limit handling | |
# using the `X-RateLimit-Remaining` and `X-RateLimit-Reset` headers. | |
time.sleep(0.5) # Sleep for 0.5 seconds between requests | |
except requests.exceptions.RequestException as e: | |
print(f"Error fetching issues: {e}") | |
if response is not None: | |
print(f"Response status: {response.status_code}") | |
print(f"Response headers: {response.headers}") | |
print(f"Response body: {response.text}") | |
return "Error: Could not retrieve leaderboard data." | |
except Exception as e: | |
print(f"An unexpected error occurred: {e}") | |
return "Error: An unexpected error occurred." | |
assignee_counts = defaultdict(int) | |
for issue in all_issues: | |
# Pull requests are also considered "issues" in the GitHub API. | |
# Filter them out if you only want traditional issues. | |
if 'pull_request' in issue: | |
continue | |
# If an issue has multiple assignees, we count it once for each. | |
# If you prefer to count it only for the first assignee, modify this logic. | |
if issue.get('assignees'): | |
for assignee in issue['assignees']: | |
assignee_counts[assignee['login']] += 1 | |
elif issue.get('assignee'): # Fallback for single assignee | |
assignee_counts[issue['assignee']['login']] += 1 | |
# Sort assignees by their closed issue count in descending order | |
sorted_assignees = sorted(assignee_counts.items(), key=lambda item: item[1], reverse=True) | |
if not sorted_assignees: | |
return "## Closed Issues Leaderboard\n\nNo closed issues found with assignees." | |
# Generate Markdown table | |
markdown = "## Closed Issues Leaderboard by Assignee\n\n" | |
markdown += "| Rank | Assignee | Closed Issues |\n" | |
markdown += "|---|---|---|\n" | |
for i, (assignee, count) in enumerate(sorted_assignees): | |
markdown += f"| {i + 1} | @{assignee} | {count} |\n" | |
return markdown | |
if __name__ == "__main__": | |
# --- Configuration for the public repository you want to analyze --- | |
# Example: The 'octocat/Spoon-Knife' repository is a public example. | |
# Replace with your desired public repository's owner and name. | |
repo_owner = "ros2" | |
repo_name = "kilted_tutorial_party" | |
# ----------------------------------------------------------------- | |
leaderboard = get_closed_issues_leaderboard(repo_owner, repo_name) | |
print(leaderboard) | |
# You can also save the leaderboard to a file, e.g., 'leaderboard.md' | |
# with open("leaderboard.md", "w") as f: | |
# f.write(leaderboard) | |
# print("\nLeaderboard saved to leaderboard.md") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment