Skip to content

Instantly share code, notes, and snippets.

@kscottz
Created May 8, 2025 18:40
Show Gist options
  • Save kscottz/55cd4ebc0532b1b6c327fd9f285bb02a to your computer and use it in GitHub Desktop.
Save kscottz/55cd4ebc0532b1b6c327fd9f285bb02a to your computer and use it in GitHub Desktop.
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