Skip to content

Commit b98fe9f

Browse files
committed
feat: improved output format with stats and timeline
- Output to stdout by default - Added summary stats at the top - Show only first line of commit messages - Added optional timeline in details block - Improved team report with better stats
1 parent 7c3f1f5 commit b98fe9f

1 file changed

Lines changed: 127 additions & 55 deletions

File tree

whatdidyougetdone.py

Lines changed: 127 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,12 @@
1919
import os
2020
from pathlib import Path
2121
from typing import Optional
22-
import sys
23-
import webbrowser
2422
from github import Github
2523

2624
# Get script directory
2725
SCRIPT_DIR = Path(__file__).parent.absolute()
2826

2927

30-
def preview_in_browser(filename: Path) -> None:
31-
"""Open file in browser if in interactive mode."""
32-
if sys.stdout.isatty():
33-
if click.confirm("Open in browser?"):
34-
webbrowser.open(f"file://{filename.absolute()}")
35-
36-
3728
def get_github_token() -> str:
3829
"""Get GitHub token from environment or GitHub CLI."""
3930
# Try environment variable first
@@ -106,10 +97,25 @@ def get_user_activity(username: str, days: int = 7):
10697
return activities
10798

10899

109-
def generate_report(username: str, days: int = 7):
100+
def generate_report(username: str, days: int = 7, include_timeline: bool = False):
110101
"""Generate a markdown report of user activity."""
111102
activities = get_user_activity(username, days)
112103

104+
# Calculate summary stats
105+
total_commits = sum(1 for a in activities if a["type"] == "commit")
106+
total_prs = sum(1 for a in activities if a["type"] == "pr")
107+
active_repos = len({a["repo"] for a in activities})
108+
109+
# Generate markdown
110+
report = f"# What did {username} get done?\n\n"
111+
report += f"Activity report for the last {days} days:\n\n"
112+
113+
# Summary stats
114+
report += "## Summary\n\n"
115+
report += f"- 💻 {total_commits} commits\n"
116+
report += f"- 🔀 {total_prs} pull requests\n"
117+
report += f"- 📦 {active_repos} active repositories\n\n"
118+
113119
# Group by repo
114120
repos: dict[str, list[dict]] = {}
115121
for activity in activities:
@@ -118,12 +124,10 @@ def generate_report(username: str, days: int = 7):
118124
repos[repo] = []
119125
repos[repo].append(activity)
120126

121-
# Generate markdown
122-
report = f"# What did {username} get done?\n\n"
123-
report += f"Activity report for the last {days} days:\n\n"
124-
127+
# Activity by repo
128+
report += "## Activity by Repository\n\n"
125129
for repo, acts in repos.items():
126-
report += f"## {repo}\n\n"
130+
report += f"### {repo}\n\n"
127131
# Group by type
128132
commits = [act for act in acts if act["type"] == "commit"]
129133
prs = [act for act in acts if act["type"] == "pr"]
@@ -137,21 +141,27 @@ def generate_report(username: str, days: int = 7):
137141
# Skip merge commits and commits that are part of PRs
138142
if act["message"].startswith("Merge") or "Co-authored-by" in act["message"]:
139143
continue
140-
report += f"- 💻 {act['message']}\n"
144+
# Take only first line of commit message
145+
message = act["message"].split("\n")[0]
146+
report += f"- 💻 {message}\n"
147+
148+
# Optional timeline
149+
if include_timeline:
150+
report += "\n<details><summary>Detailed Timeline</summary>\n\n"
151+
for act in sorted(activities, key=lambda x: x["date"], reverse=True):
152+
date_str = act["date"].strftime("%Y-%m-%d %H:%M")
153+
if act["type"] == "commit":
154+
message = act["message"].split("\n")[0]
155+
report += f"- {date_str} 💻 [{act['repo']}] {message}\n"
156+
elif act["type"] == "pr":
157+
report += (
158+
f"- {date_str} 🔀 [{act['repo']}] {act['title']} ({act['state']})\n"
159+
)
160+
report += "\n</details>\n"
141161

142162
return report
143163

144164

145-
def save_report(username: str, report: str) -> Path:
146-
"""Save report to file."""
147-
reports_dir = SCRIPT_DIR / "reports"
148-
reports_dir.mkdir(exist_ok=True)
149-
150-
filename = reports_dir / f"{username}-{datetime.now().strftime('%Y-%m-%d')}.md"
151-
filename.write_text(report)
152-
return filename
153-
154-
155165
@click.group()
156166
def cli():
157167
"""What did you get done? - Activity report generator"""
@@ -161,63 +171,125 @@ def cli():
161171
@cli.command()
162172
@click.argument("username")
163173
@click.option("--days", default=7, help="Number of days to look back")
164-
@click.option("--output", help="Output file (default: reports/<username>-<date>.md)")
165-
def report(username: str, days: int, output: Optional[str]):
174+
@click.option("--file", help="Save output to file instead of stdout")
175+
@click.option("--timeline", is_flag=True, help="Include detailed timeline")
176+
def report(username: str, days: int, file: Optional[str], timeline: bool):
166177
"""Generate activity report for a GitHub user"""
167178
setup_github()
168179

169180
# Generate report
170-
report_text = generate_report(username, days)
181+
report_text = generate_report(username, days, include_timeline=timeline)
171182

172-
# Save report
173-
if output:
174-
filename = Path(output)
183+
# Output report
184+
if file:
185+
Path(file).write_text(report_text)
186+
print(f"Report saved to: {file}")
175187
else:
176-
filename = save_report(username, report_text)
177-
178-
print(f"Report saved to: {filename}")
179-
preview_in_browser(filename)
188+
print(report_text)
180189

181190

182191
@cli.command()
183192
@click.argument("usernames", nargs=-1)
184193
@click.option("--days", default=7, help="Number of days to look back")
185-
def team(usernames: tuple[str], days: int):
194+
@click.option("--file", help="Save output to file instead of stdout")
195+
@click.option("--timeline", is_flag=True, help="Include detailed timeline")
196+
def team(usernames: tuple[str], days: int, file: Optional[str], timeline: bool):
186197
"""Generate team activity report"""
187198
setup_github()
188199

189200
# Generate combined report
190201
report = "# Team Activity Report\n\n"
191202
report += f"Activity for the last {days} days\n\n"
192203

204+
# Team summary
205+
total_team_commits = 0
206+
total_team_prs = 0
207+
active_repos: set[str] = set()
208+
209+
# Collect all activities first
210+
all_activities: list[tuple[str, dict]] = []
193211
for username in usernames:
194212
activities = get_user_activity(username, days)
195-
report += f"## {username}\n\n"
213+
all_activities.extend((username, a) for a in activities)
196214

197-
# Count activities
215+
# Update team stats
198216
commit_count = sum(1 for a in activities if a["type"] == "commit")
199217
pr_count = sum(1 for a in activities if a["type"] == "pr")
218+
active_repos.update(a["repo"] for a in activities)
200219

201-
report += f"- 💻 {commit_count} commits\n"
202-
report += f"- 🔀 {pr_count} pull requests\n\n"
220+
total_team_commits += commit_count
221+
total_team_prs += pr_count
203222

204-
# Add details
205-
for activity in sorted(activities, key=lambda x: x["date"], reverse=True):
206-
if activity["type"] == "commit":
207-
report += f"- [{activity['repo']}] {activity['message']}\n"
208-
elif activity["type"] == "pr":
209-
report += f"- [{activity['repo']}] {activity['title']} ({activity['state']})\n"
210-
report += "\n"
223+
# Team summary
224+
report += "## Team Summary\n\n"
225+
report += f"- 👥 {len(usernames)} team members\n"
226+
report += f"- 💻 {total_team_commits} commits\n"
227+
report += f"- 🔀 {total_team_prs} pull requests\n"
228+
report += f"- 📦 {len(active_repos)} active repositories\n\n"
211229

212-
# Save report
213-
reports_dir = SCRIPT_DIR / "reports"
214-
reports_dir.mkdir(exist_ok=True)
230+
# Per-user summary
231+
for username in usernames:
232+
user_activities = [a for u, a in all_activities if u == username]
233+
report += f"## {username}\n\n"
215234

216-
filename = reports_dir / f"team-{datetime.now().strftime('%Y-%m-%d')}.md"
217-
filename.write_text(report)
235+
# User stats
236+
commit_count = sum(1 for a in user_activities if a["type"] == "commit")
237+
pr_count = sum(1 for a in user_activities if a["type"] == "pr")
238+
user_repos = len({a["repo"] for a in user_activities})
218239

219-
print(f"Team report saved to: {filename}")
220-
preview_in_browser(filename)
240+
report += f"- 💻 {commit_count} commits\n"
241+
report += f"- 🔀 {pr_count} pull requests\n"
242+
report += f"- 📦 {user_repos} active repositories\n\n"
243+
244+
# Group by repo
245+
repos: dict[str, list[dict]] = {}
246+
for activity in user_activities:
247+
repo = activity["repo"]
248+
if repo not in repos:
249+
repos[repo] = []
250+
repos[repo].append(activity)
251+
252+
for repo, acts in repos.items():
253+
report += f"### {repo}\n\n"
254+
# Group by type
255+
commits = [act for act in acts if act["type"] == "commit"]
256+
prs = [act for act in acts if act["type"] == "pr"]
257+
258+
# Show PRs first
259+
for act in sorted(prs, key=lambda x: x["date"], reverse=True):
260+
report += f"- 🔀 {act['title']} ({act['state']})\n"
261+
262+
# Then commits
263+
for act in sorted(commits, key=lambda x: x["date"], reverse=True):
264+
if (
265+
act["message"].startswith("Merge")
266+
or "Co-authored-by" in act["message"]
267+
):
268+
continue
269+
message = act["message"].split("\n")[0]
270+
report += f"- 💻 {message}\n"
271+
report += "\n"
272+
273+
# Optional timeline
274+
if timeline:
275+
report += "\n<details><summary>Team Timeline</summary>\n\n"
276+
for username, act in sorted(
277+
all_activities, key=lambda x: x[1]["date"], reverse=True
278+
):
279+
date_str = act["date"].strftime("%Y-%m-%d %H:%M")
280+
if act["type"] == "commit":
281+
message = act["message"].split("\n")[0]
282+
report += f"- {date_str} 💻 {username} [{act['repo']}] {message}\n"
283+
elif act["type"] == "pr":
284+
report += f"- {date_str} 🔀 {username} [{act['repo']}] {act['title']} ({act['state']})\n"
285+
report += "\n</details>\n"
286+
287+
# Output report
288+
if file:
289+
Path(file).write_text(report)
290+
print(f"Report saved to: {file}")
291+
else:
292+
print(report)
221293

222294

223295
if __name__ == "__main__":

0 commit comments

Comments
 (0)