Skip to content

Instantly share code, notes, and snippets.

@bedilbek
Last active December 21, 2024 09:33
Show Gist options
  • Select an option

  • Save bedilbek/2536ba5709aca06ad3622687d2c88d5a to your computer and use it in GitHub Desktop.

Select an option

Save bedilbek/2536ba5709aca06ad3622687d2c88d5a to your computer and use it in GitHub Desktop.
Github Activity Summary for last N days

Github Summary Generator

Install the requirements:

pip install markdown requests

Change the following lines in the code:

username = ""
token = ""
org_name = ""  # Optional, set to None if not needed

Run the summary generator:

python summary_github_generator.py

It will generate 2 files:

  1. work_activity_report.md - Markdown output of the summary
  2. work_activity_report.html- HTML output of markdown summary

Here below is a sample markdown of the summary:

Work Activity Report

Period: 2024-12-13 to 2024-12-21

Summary

  • Total Commits: 0
  • Pull Requests Created: 0
  • Code Reviews Performed: 0
  • Issues Created: 0
  • Issue Comments: 0
  • PR Comments: 0
  • Discussion Comments: 0

Projects Worked On

Detailed Activity

Recent Commits

Pull Requests Created

Code Reviews

Issue Comments

Pull Request Comments

from datetime import datetime, timedelta
import markdown
import requests
class WorkActivityReport:
def __init__(self, username, token, org_name=None):
"""
Initialize with GitHub username, token and optional organization name
"""
self.username = username
self.headers = {
'Authorization': f'token {token}',
'Accept': 'application/vnd.github.v3+json'
}
self.base_url = 'https://api.github.com'
self.org_name = org_name
def get_user_events(self, days_back=7):
"""
Fetch user's events with detailed information
"""
events_url = f'{self.base_url}/users/{self.username}/events'
events = []
page = 1
cutoff_date = datetime.now() - timedelta(days=days_back)
while True:
response = requests.get(
events_url,
headers=self.headers,
params={'page': page, 'per_page': 100}
)
if response.status_code != 200:
break
page_events = response.json()
if not page_events:
break
filtered_events = [
event for event in page_events
if datetime.strptime(event['created_at'], '%Y-%m-%dT%H:%M:%SZ') > cutoff_date
]
events.extend(filtered_events)
if not filtered_events and page_events:
break
page += 1
return events
def format_comment_body(self, body):
"""Format comment body with proper line breaks and indentation"""
if not body:
return ""
lines = []
for line in body.splitlines():
# Check if line starts with a date pattern [YYYY-MM-DD]
if line.strip().startswith('[20'):
# Add extra newline before date-prefixed lines
lines.append('')
lines.append(line)
else:
lines.append(line)
# Remove empty lines from the beginning
while lines and not lines[0].strip():
lines.pop(0)
return '\n '.join(lines)
def get_repos_summary(self):
"""
Get summary of user's repositories
"""
repos_url = f'{self.base_url}/users/{self.username}/repos'
repos = []
page = 1
while True:
response = requests.get(
repos_url,
headers=self.headers,
params={'page': page, 'per_page': 100}
)
if response.status_code != 200:
break
page_repos = response.json()
if not page_repos:
break
repos.extend(page_repos)
page += 1
return repos
def generate_detailed_report(self, days_back=7):
events = self.get_user_events(days_back)
report = {
'period': {
'start': (datetime.now() - timedelta(days=days_back)).strftime('%Y-%m-%d'),
'end': datetime.now().strftime('%Y-%m-%d')
},
'summary': {
'commits': 0,
'prs_created': 0,
'prs_reviewed': 0,
'issues_created': 0,
'issues_commented': 0,
'pr_comments': 0,
'code_reviews': 0,
'discussion_comments': 0
},
'detailed_activity': {
'commits': [],
'pull_requests': [],
'issues': [],
'reviews': [],
'issue_comments': [],
'pr_comments': [],
'discussion_comments': []
},
'projects_worked_on': set()
}
for event in events:
repo_name = event['repo']['name']
if self.org_name and not repo_name.startswith(f"{self.org_name}/"):
continue
report['projects_worked_on'].add(repo_name)
etype = event['type']
payload = event['payload']
if etype == 'PushEvent':
commits = payload.get('commits', [])
report['summary']['commits'] += len(commits)
for commit in commits:
sha = commit.get('sha', '')
report['detailed_activity']['commits'].append({
'repo': repo_name,
'message': commit.get('message', ''),
'date': event.get('created_at', ''),
'sha': sha,
'url': f"https://github.com/{repo_name}/commit/{sha}" if sha else ''
})
elif etype == 'PullRequestEvent':
if payload.get('action') == 'opened':
pr = payload.get('pull_request', {})
number = str(pr.get('number', ''))
report['summary']['prs_created'] += 1
report['detailed_activity']['pull_requests'].append({
'repo': repo_name,
'title': pr.get('title', ''),
'state': pr.get('state', ''),
'date': pr.get('created_at', ''),
'number': number,
'url': f"https://github.com/{repo_name}/pull/{number}" if number else ''
})
elif etype == 'IssuesEvent':
if payload.get('action') == 'opened':
issue = payload.get('issue', {})
issue_url = issue.get('html_url', '')
report['summary']['issues_created'] += 1
report['detailed_activity']['issues'].append({
'repo': repo_name,
'title': issue.get('title', ''),
'state': issue.get('state', ''),
'date': issue.get('created_at', ''),
'url': issue_url
})
elif etype == 'PullRequestReviewEvent':
pr = payload.get('pull_request', {})
review = payload.get('review', {})
pr_number = str(pr.get('number', ''))
report['summary']['code_reviews'] += 1
report['detailed_activity']['reviews'].append({
'repo': repo_name,
'pr_title': pr.get('title', ''),
'state': review.get('state', ''),
'date': review.get('submitted_at', ''),
'number': pr_number,
'url': f"https://github.com/{repo_name}/pull/{pr_number}" if pr_number else ''
})
elif etype == 'IssueCommentEvent':
issue = payload.get('issue', {})
comment = payload.get('comment', {})
issue_number = str(issue.get('number', ''))
issue_url = f"https://github.com/{repo_name}/issues/{issue_number}" if issue_number else ''
report['summary']['issues_commented'] += 1
report['detailed_activity']['issue_comments'].append({
'repo': repo_name,
'issue_title': issue.get('title', ''),
'comment_body': comment.get('body', ''),
'date': comment.get('created_at', ''),
'number': issue_number,
'url': issue_url
})
elif etype == 'PullRequestReviewCommentEvent':
pr = payload.get('pull_request', {})
comment = payload.get('comment', {})
comment_url = comment.get('html_url', '')
report['summary']['pr_comments'] += 1
report['detailed_activity']['pr_comments'].append({
'repo': repo_name,
'pr_title': pr.get('title', ''),
'comment_body': comment.get('body', ''),
'date': comment.get('created_at', ''),
'url': comment_url
})
elif etype == 'DiscussionCommentEvent':
discussion = payload.get('discussion', {})
comment = payload.get('comment', {})
comment_url = comment.get('html_url', '')
report['summary']['discussion_comments'] += 1
report['detailed_activity']['discussion_comments'].append({
'repo': repo_name,
'discussion_title': discussion.get('title', ''),
'comment_body': comment.get('body', ''),
'date': comment.get('created_at', ''),
'url': comment_url
})
return report
def construct_github_url(self, repo, type_='repo', number=None):
"""Construct GitHub URLs for different types of content"""
base_url = f"https://github.com/{repo}"
if type_ == 'repo':
return base_url
elif type_ == 'issue':
return f"{base_url}/issues/{number}"
elif type_ == 'pr':
return f"{base_url}/pull/{number}"
return base_url
def generate_markdown_report(self, days_back=7):
"""
Generate a formatted markdown report with clickable URLs.
"""
report = self.generate_detailed_report(days_back)
def format_activities(activities, format_str):
return '\n'.join(
format_str(activity)
for activity in sorted(activities, key=lambda x: x['date'], reverse=True)
)
# Use a helper to indent multi-line strings:
import textwrap
def indent_comment(comment_body):
return textwrap.indent(self.format_comment_body(comment_body), " ")
md_content = [
f"# Work Activity Report",
f"## Period: {report['period']['start']} to {report['period']['end']}",
"\n### Summary",
f"- Total Commits: {report['summary']['commits']}",
f"- Pull Requests Created: {report['summary']['prs_created']}",
f"- Code Reviews Performed: {report['summary']['code_reviews']}",
f"- Issues Created: {report['summary']['issues_created']}",
f"- Issue Comments: {report['summary']['issues_commented']}",
f"- PR Comments: {report['summary']['pr_comments']}",
f"- Discussion Comments: {report['summary']['discussion_comments']}",
"",
"### Projects Worked On",
'\n'.join('- ' + project for project in sorted(report['projects_worked_on'])),
"",
"### Detailed Activity",
"",
"#### Recent Commits",
format_activities(
report['detailed_activity']['commits'],
lambda
c: f"- [{c['date'][:10]}] [{c['message'].split(chr(10))[0]}]({c['url']}) ({c['repo']})"
),
"",
"#### Pull Requests Created",
format_activities(
report['detailed_activity']['pull_requests'],
lambda
pr: f"- [{pr['date'][:10]}] [{pr['title']}]({pr['url']}) ({pr['repo']}) - {pr['state']}"
),
"",
"#### Code Reviews",
format_activities(
report['detailed_activity']['reviews'],
lambda
r: f"- [{r['date'][:10]}] Reviewed: [{r['pr_title']}]({r['url']}) ({r['repo']}) - {r['state']}"
),
"",
"#### Issue Comments",
'\n\n'.join(
f"- [{comment['date'][:10]}] On issue: "
f"[{comment['issue_title']}]({comment['url']}) ({comment['repo']})\n\n "
f"{self.format_comment_body(comment['comment_body'])}"
for comment in sorted(
report['detailed_activity']['issue_comments'],
key=lambda x: x['date'],
reverse=True
)
),
"",
"#### Pull Request Comments",
"\n\n".join(
f"- [{c['date'][:10]}] On PR: "
f"[{c['pr_title']}]({c['url']}) ({c['repo']})\n\n"
f"{indent_comment(c['comment_body'])}"
for c in sorted(
report['detailed_activity']['pr_comments'],
key=lambda x: x['date'],
reverse=True
)
),
""
]
return '\n'.join(md_content)
def save_report(self, days_back=7, output_format='both'):
"""
Save the report in specified format(s)
"""
md_content = self.generate_markdown_report(days_back)
# Save markdown version
with open('work_activity_report.md', 'w') as f:
f.write(md_content)
if output_format in ['html', 'both']:
html_doc = markdown_content_to_html_content(md_content)
with open('work_activity_report.html', 'w') as f:
f.write(html_doc)
def markdown_content_to_html_content(markdown_content: str) -> str:
"""
Convert markdown content to HTML content
"""
# Convert to HTML using markdown with extensions
html_content = markdown.markdown(markdown_content, extensions=['tables', 'fenced_code'])
html_doc = f"""
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Work Activity Report</title>
<style>
body {{
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
max-width: 1200px;
margin: 40px auto;
padding: 0 20px;
line-height: 1.5;
}}
h1, h2, h3, h4 {{ color: #24292e; }}
a {{
color: #0366d6;
text-decoration: none;
}}
a:hover {{
text-decoration: underline;
}}
li {{
margin: 8px 0;
color: #24292e;
}}
code {{
background-color: #f6f8fa;
padding: 2px 5px;
border-radius: 3px;
font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
}}
pre {{
background-color: #f6f8fa;
padding: 16px;
border-radius: 6px;
overflow: auto;
}}
</style>
</head>
<body>
{html_content}
</body>
</html>
"""
return html_doc
if __name__ == "__main__":
# Replace these with your actual values
username = ""
token = ""
org_name = "" # Optional, set to None if not needed
reporter = WorkActivityReport(username, token, org_name)
reporter.save_report(days_back=7, output_format='both')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment