fix(contributors): show real GitHub commits per contributor#302
Conversation
The "RECENT COMMITS" panel in the contributor detail (right side of the
#contributors view) was rendering deterministic mock SHAs and made-up
messages like `refactor(ui): keyboard nav` for every selected dev. Users
landing on jobs.riteshrana.engineer/#contributors and clicking a name
saw fabricated history.
Replaces the local `recentCommits(c)` stub with a `useRecentCommits()`
hook that lazily fetches `GET /repos/{repo}/commits?author={handle}` from
the GitHub REST API when a contributor is selected, with a module-level
session cache keyed by handle so repeated clicks don't re-hit the API.
Each row now shows the real 7-char SHA, the actual commit subject (first
line of the message, truncated to 80 chars), and a human-readable "ago"
derived from the commit author date. Rows link to the commit on GitHub.
Loading / empty / error states are surfaced explicitly:
- "loading from github…" while in-flight
- "no commits authored by @{handle} in this repo" when the user is only
a co-author (e.g. @15045 / 小吴 on #296) — verified locally.
- "could not load commits (…)" with the failure reason on rate-limit or
network failure (graceful degradation).
Bumps the `?v=7` cache-bust on all bundles in docs/index.html to `?v=8`
so existing visitors fetch the new contributors.jsx.
Verified against http://localhost:8765/#contributors:
- @ambicuity → shows #299, #300, #301 etc. with correct ages (19m, 27m, …).
- @rvac-bucky → shows #265, #247, #166, #193.
- @15045 → empty-state message (co-author only, never the author).
|
Warning Review limit reached
More reviews will be available in 12 minutes and 40 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Code Review
This pull request replaces the deterministic mock commits with live data fetched from the GitHub API for each contributor. It introduces a session-scoped cache, a custom React hook useRecentCommits, and a relative time formatter. The review feedback highlights two key improvement opportunities: allowing retries when a fetch request fails instead of permanently caching the error state, and using Math.floor instead of Math.round in the relative time formatter to prevent awkward boundary values like '60s' or '60m'.
| if (!RECENT_COMMITS_CACHE.has(handle)) { | ||
| RECENT_COMMITS_CACHE.set(handle, { state: 'loading', commits: [] }); |
There was a problem hiding this comment.
Currently, if a request to fetch recent commits fails (e.g., due to a temporary network issue or rate limit), the error state is cached in RECENT_COMMITS_CACHE. Because RECENT_COMMITS_CACHE.has(handle) is used to check for cached entries, the application will never attempt to refetch the commits for that contributor during the session, even if the user clicks away and clicks back. Allowing a retry when the cached state is 'error' improves usability and resilience against temporary network glitches.
const cached = RECENT_COMMITS_CACHE.get(handle);
if (!cached || cached.state === 'error') {
RECENT_COMMITS_CACHE.set(handle, { state: 'loading', commits: [] });| if (diffSec < 60) return `${Math.round(diffSec)}s`; | ||
| if (diffSec < 3600) return `${Math.round(diffSec / 60)}m`; | ||
| if (diffSec < 86400) return `${Math.round(diffSec / 3600)}h`; | ||
| if (diffSec < 86400 * 7) return `${Math.round(diffSec / 86400)}d`; | ||
| if (diffSec < 86400 * 30) return `${Math.round(diffSec / (86400 * 7))}w`; | ||
| if (diffSec < 86400 * 365) return `${Math.round(diffSec / (86400 * 30))}mo`; | ||
| return `${Math.round(diffSec / (86400 * 365))}y`; |
There was a problem hiding this comment.
Using Math.round for relative time formatting can lead to awkward boundary values such as 60s, 60m, or 24h (e.g., when diffSec is 59.6 seconds, it is less than 60, so it rounds to 60s instead of transitioning to 1m). Using Math.floor ensures standard relative time intervals (e.g., 59s, 59m, 23h) and a smoother transition between units.
| if (diffSec < 60) return `${Math.round(diffSec)}s`; | |
| if (diffSec < 3600) return `${Math.round(diffSec / 60)}m`; | |
| if (diffSec < 86400) return `${Math.round(diffSec / 3600)}h`; | |
| if (diffSec < 86400 * 7) return `${Math.round(diffSec / 86400)}d`; | |
| if (diffSec < 86400 * 30) return `${Math.round(diffSec / (86400 * 7))}w`; | |
| if (diffSec < 86400 * 365) return `${Math.round(diffSec / (86400 * 30))}mo`; | |
| return `${Math.round(diffSec / (86400 * 365))}y`; | |
| if (diffSec < 60) return Math.floor(diffSec) + "s"; | |
| if (diffSec < 3600) return Math.floor(diffSec / 60) + "m"; | |
| if (diffSec < 86400) return Math.floor(diffSec / 3600) + "h"; | |
| if (diffSec < 86400 * 7) return Math.floor(diffSec / 86400) + "d"; | |
| if (diffSec < 86400 * 30) return Math.floor(diffSec / (86400 * 7)) + "w"; | |
| if (diffSec < 86400 * 365) return Math.floor(diffSec / (86400 * 30)) + "mo"; | |
| return Math.floor(diffSec / (86400 * 365)) + "y"; |
Why
The "RECENT COMMITS" panel in the right-side detail of
#contributorswas rendering deterministic mock SHAs and made-up messages likerefactor(ui): keyboard navfor every selected dev. Anyone landing on jobs.riteshrana.engineer/#contributors and clicking a contributor saw fabricated history that had nothing to do with that person.What
Replaces the local mock generator in
docs/terminal/contributors.jsxwith auseRecentCommits()hook that fetchesGET /repos/{repo}/commits?author={handle}from the GitHub REST API on selection. A module-level cache keyed by handle dedupes within a session so re-clicking a contributor doesn't re-hit the API.Each row now shows:
States surfaced explicitly (no silent failures):
loading from github…while in-flightno commits authored by @{handle} in this repofor co-author-only contributors (e.g. @15045 / 小吴 — co-author on fix: replace hardcoded timeout with DEFAULT_TIMEOUT constant #296 but never the author).could not load commits (...)with the failure reason on rate-limit/network failure.Cache-bust
?v=7→?v=8on alldocs/index.htmlbundles so existing visitors pick up the new code.Verification (local —
python3 -m http.server 8765, Playwright)Screenshot saved to
contributors-real-commits.png(not committed).