Skip to content

feat: add GitHub contribution heatmap using GraphQL API#713

Open
ananyadarna wants to merge 1 commit into
GitMetricsLab:mainfrom
ananyadarna:feature/contribution-heatmap-710
Open

feat: add GitHub contribution heatmap using GraphQL API#713
ananyadarna wants to merge 1 commit into
GitMetricsLab:mainfrom
ananyadarna:feature/contribution-heatmap-710

Conversation

@ananyadarna
Copy link
Copy Markdown
Contributor

@ananyadarna ananyadarna commented Jun 4, 2026

Related Issue


Description

Implemented a GitHub-style contribution heatmap for tracked users using real contribution data fetched from the GitHub GraphQL API.

Changes Made

  • Added a new ContributionHeatmap component
  • Integrated GitHub GraphQL Contribution Calendar API
  • Added contribution data fetching logic in useGitHubData
  • Connected contribution data to the Tracker page
  • Applied GitHub-style contribution color palette
  • Added responsive heatmap layout
  • Added handling for empty contribution data to prevent runtime errors
  • Displayed real user contribution activity instead of mock data

How Has This Been Tested?

  • Tested locally using npm run dev
  • Verified contribution data loads successfully for GitHub users
  • Verified heatmap renders correctly using real GitHub GraphQL data
  • Tested responsiveness across different screen sizes
  • Confirmed no TypeScript errors
  • Verified successful production build
npm run build

Screenshots

After

image

Type of Change

  • Bug fix
  • New feature
  • Code style update
  • Breaking change
  • Documentation update

Summary by CodeRabbit

  • New Features
    • Added a contribution heatmap visualization to display GitHub activity history in calendar format, showing contribution counts and intensity levels across dates.

@netlify
Copy link
Copy Markdown

netlify Bot commented Jun 4, 2026

Deploy Preview for github-spy ready!

Name Link
🔨 Latest commit 9e36753
🔍 Latest deploy log https://app.netlify.com/projects/github-spy/deploys/6a216c695be7a20008329da2
😎 Deploy Preview https://deploy-preview-713--github-spy.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 4, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR implements a GitHub-style contribution calendar (heatmap) visualization on the Tracker dashboard. It extends the useGitHubData hook to fetch 52-week contribution data via GraphQL API, creates a new ContributionHeatmap component wrapping the react-activity-calendar library, integrates it into the Tracker page below existing stats, and adds the required npm dependency.

Changes

Contribution Heatmap Feature

Layer / File(s) Summary
Contribution data fetching in useGitHubData hook
src/hooks/useGitHubData.ts
Adds ContributionDay type for per-day calendar data, initializes contributionData state, implements fetchContributionData GraphQL query that maps string contribution levels to numeric 0–4, wires fetch into existing Promise.all concurrency, updates state, and extends hook return with contributionData.
ContributionHeatmap UI component
src/components/ContributionHeatmap.tsx
New component wrapping ActivityCalendar inside MUI Paper with Typography header, typed to accept ContributionDay[] data, applies hardcoded light/dark theme palette.
Tracker page integration
src/pages/Tracker/Tracker.tsx
Imports ContributionHeatmap, extracts contributionData from hook, conditionally renders heatmap with "No contribution data available" fallback.
Package dependency
package.json
Adds react-activity-calendar runtime dependency, reformats scripts/dependencies/devDependencies sections to remove extra blank lines.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

type:feature, level:intermediate, quality:clean

Poem

🐰 A heatmap hops to life so bright,
Contribution cells in shades of light,
GitHub's green grid now found with glee,
On every developer's dashboard spree! 📊

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding a GitHub contribution heatmap using the GraphQL API, which is the primary objective of the pull request.
Description check ✅ Passed The description follows the template structure, includes the related issue, detailed changes, comprehensive testing results, and a screenshot, fully addressing all key template sections.
Linked Issues check ✅ Passed The PR successfully implements all coding requirements from issue #710: adds a ContributionHeatmap component, integrates GitHub GraphQL API for contribution data, uses react-activity-calendar library, places the heatmap on the dashboard, and handles empty data gracefully.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the contribution heatmap feature outlined in issue #710; package.json updates, new component, hook modifications, and page integration are all in scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/hooks/useGitHubData.ts (1)

16-20: ⚡ Quick win

Export and reuse ContributionDay across hook/component boundaries.

ContributionDay is currently duplicated (here and in src/components/ContributionHeatmap.tsx), which risks drift in a shared contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useGitHubData.ts` around lines 16 - 20, Duplicate ContributionDay
types cause drift; export the ContributionDay interface from where it's defined
(ContributionDay in useGitHubData) or move it to a shared type module and then
import that single definition into ContributionHeatmap.tsx (and any other
consumers) instead of redefining it. Update the export (export interface
ContributionDay { ... }) and replace the local duplicate in
ContributionHeatmap.tsx with an import of ContributionDay so both use the same
type symbol.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/hooks/useGitHubData.ts`:
- Around line 198-202: The current Promise.all([Promise.allSettled(requests),
contributionRequest]) will reject if contributionRequest fails and skip handling
the already-settled requests; change the flow in useGitHubData.ts to await
Promise.allSettled(requests) first (assign to results), then await
contributionRequest inside a try/catch (assign to contributionResult or a safe
default on error) so that failures fetching contributions do not prevent
executing setIssues and setPrs; reference the variables requests,
contributionRequest, results, contributionResult, setIssues and setPrs when
making the change.
- Line 100: Create a proper TypeScript interface for the GraphQL response (e.g.,
GitHubContributionsResponse with typed user, contributionsCollection,
contributionCalendar, weeks, days, contributionLevel as string union) and
replace the untyped call by invoking
octokit.graphql<GitHubContributionsResponse>(...) instead of casting to any;
adjust the response variable type accordingly (replace response: any). Update
the flatMap/map callbacks to use explicit types for week and day (e.g., week:
GitHubWeek, day: GitHubDay) and convert contributionLevel
(NONE|FIRST_QUARTILE|SECOND_QUARTILE|THIRD_QUARTILE|FOURTH_QUARTILE) to
ContributionDay['level'] numbers (0–4) in the mapping logic. Ensure you handle
user possibly being null in the typed response before accessing
contributionsCollection. Finally, replace Promise<any>[] with a typed
Promise<YourTypedResult>[] (use the actual return type used by the caller) to
remove any remaining explicit any usage.

In `@src/pages/Tracker/Tracker.tsx`:
- Around line 402-406: The empty-state message is shown before the fetch
completes; update the render logic around ContributionHeatmap and
contributionData to only show "No contribution data available" after the fetch
has finished (e.g., use an existing or new fetch status flag like isLoading or
hasFetched). Concretely, add or use a boolean such as
isContribLoading/isContribFetched in the Tracker component and change the
conditional so ContributionHeatmap is shown when contributionData.length > 0, a
loading spinner/placeholder is shown when isContribLoading is true, and the
empty-state message is shown only when !isContribLoading && isContribFetched &&
contributionData.length === 0; update the fetch handler that fills
contributionData to set these flags (e.g., in the async fetch function that
populates contributionData).

---

Nitpick comments:
In `@src/hooks/useGitHubData.ts`:
- Around line 16-20: Duplicate ContributionDay types cause drift; export the
ContributionDay interface from where it's defined (ContributionDay in
useGitHubData) or move it to a shared type module and then import that single
definition into ContributionHeatmap.tsx (and any other consumers) instead of
redefining it. Update the export (export interface ContributionDay { ... }) and
replace the local duplicate in ContributionHeatmap.tsx with an import of
ContributionDay so both use the same type symbol.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 29752a50-eaa9-48d8-ab43-fed405834d9b

📥 Commits

Reviewing files that changed from the base of the PR and between 53f820b and 9e36753.

📒 Files selected for processing (4)
  • package.json
  • src/components/ContributionHeatmap.tsx
  • src/hooks/useGitHubData.ts
  • src/pages/Tracker/Tracker.tsx

octokit: Octokit,
username: string
): Promise<ContributionDay[]> => {
const response: any = await (octokit as any).graphql(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/hooks/useGitHubData.ts"
echo "== File: $FILE =="
ls -l "$FILE"

echo "== Lines around 80-160 =="
nl -ba "$FILE" | sed -n '80,170p'

echo "== Find explicit any in file =="
rg -n ":\s*any|as\s+any|no-explicit-any|eslint-disable.*any" "$FILE" || true

echo "== Find graphql< or graphql< typed usage in file =="
rg -n "graphql<|\.graphql\(" "$FILE"

Repository: GitMetricsLab/github_tracker

Length of output: 274


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/hooks/useGitHubData.ts"
echo "== File: $FILE =="
ls -l "$FILE"

echo "== Lines 80-170 (with numbers) =="
python3 - <<'PY'
import itertools
path="src/hooks/useGitHubData.ts"
start, end = 80, 170
with open(path,'r',encoding='utf-8') as f:
    for i, line in enumerate(f, start=1):
        if start <= i <= end:
            print(f"{i:4d}\t{line.rstrip()}")
        elif i > end:
            break
PY

echo "== Explicit any patterns in file =="
rg -n ":\s*any|as\s+any|no-explicit-any|eslint-disable.*any" "$FILE" || true

echo "== graphql usage in file =="
rg -n "graphql<|\.graphql\(" "$FILE" || true

Repository: GitMetricsLab/github_tracker

Length of output: 3076


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="src/hooks/useGitHubData.ts"

echo "== Find ContributionDay type definition =="
rg -n "type ContributionDay|interface ContributionDay|ContributionDay\[" "$FILE" || true

echo "== Show region around ContributionDay definition (if found) =="
python3 - <<'PY'
import re
path="src/hooks/useGitHubData.ts"
start_lines=[]
with open(path,'r',encoding='utf-8') as f:
    for i,line in enumerate(f, start=1):
        if re.search(r'\btype\s+ContributionDay\b|\binterface\s+ContributionDay\b', line):
            start_lines.append(i)
for i in start_lines:
    start=max(1,i-10); end=i+60
    print(f"\n--- {path}:{start}-{end} ---")
    with open(path,'r',encoding='utf-8') as f:
        for ln,txt in enumerate(f, start=1):
            if start<=ln<=end:
                print(f"{ln:4d}\t{txt.rstrip()}")
PY

Repository: GitMetricsLab/github_tracker

Length of output: 2419


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="src/hooks/useGitHubData.ts"

echo "== All occurrences of 'any' in file =="
rg -n "\bany\b" "$FILE" || true

echo "== Show region around graphql call and fetchContributionData =="
python3 - <<'PY'
path="src/hooks/useGitHubData.ts"
start,end=90,155
with open(path,'r',encoding='utf-8') as f:
    for i,line in enumerate(f, start=1):
        if start<=i<=end:
            print(f"{i:4d}\t{line.rstrip()}")
PY

echo "== Show import section (first 80 lines) =="
python3 - <<'PY'
path="src/hooks/useGitHubData.ts"
end=80
with open(path,'r',encoding='utf-8') as f:
    for i,line in enumerate(f, start=1):
        if i<=end:
            print(f"{i:4d}\t{line.rstrip()}")
PY

Repository: GitMetricsLab/github_tracker

Length of output: 4615


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "\.graphql<|\.graphql\(" -S . || true
rg -n "function getOctokit|getOctokit\s*=" -S . || true
rg -n "useGitHubData" -S src || true

Repository: GitMetricsLab/github_tracker

Length of output: 448


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/hooks/useGitHubAuth.ts"
echo "== File: $FILE =="
ls -l "$FILE"

python3 - <<'PY'
path="src/hooks/useGitHubAuth.ts"
with open(path,'r',encoding='utf-8') as f:
    for i,line in enumerate(f, start=1):
        if i<=220:
            print(f"{i:4d}\t{line.rstrip()}")
PY

Repository: GitMetricsLab/github_tracker

Length of output: 832


🌐 Web query:

GitHub GraphQL contributionLevel values NONE FIRST_QUARTILE SECOND_QUARTILE THIRD_QUARTILE FOURTH_QUARTILE

💡 Result:

In the GitHub GraphQL API, the ContributionLevel enum defines the intensity of a user's contributions on a specific day relative to other days [1][2]. The values are defined as follows: NONE: No contributions occurred [1][2]. FIRST_QUARTILE: The lowest 25% of days with contributions [1][2]. SECOND_QUARTILE: The second lowest 25% of days with contributions; contains more contributions than the first quartile [1][2]. THIRD_QUARTILE: The second highest 25% of days with contributions; contains more contributions than the second quartile, but less than the fourth quartile [1][2]. FOURTH_QUARTILE: The highest 25% of days with contributions; contains more contributions than the third quartile [1][2]. This enum is typically used in the contributionsCollection object to help determine the visual representation of activity on a contribution calendar [1][3].

Citations:


🌐 Web query:

@octokit/core Octokit graphql method TypeScript generics graphql<T>

💡 Result:

In @octokit/core, the graphql method supports TypeScript generics to specify the expected return type of your GraphQL query [1]. You can apply these generics directly to the graphql call using the syntax octokit.graphql(query, variables) [1]. When you provide a type , the library will treat the response data as that type [1]. If you do not provide a generic, the return type defaults to any (or a general structure depending on your specific version and configuration) [1]. Example usage: interface MyData { repository: { name: string; stargazers: { totalCount: number; }; }; } const response = await octokit.graphql( query, { owner: "octokit", name: "core" }); Additionally, for general GraphQL usage with Octokit, you can import and use GraphQlQueryResponseData from @octokit/graphql to help define your response structures [2][3]. For more advanced scenarios, such as paginated GraphQL queries, you can also use generics with the pagination plugin, as seen in @octokit/plugin-paginate-graphql [4].

Citations:


Replace explicit any with typed GraphQL response models.

Lines 100, 125, and 126 use any (const response: any = await (octokit as any).graphql(...), week: any, day: any), violating @typescript-eslint/no-explicit-any and weakening type safety in the contributions fetch path (notably user can be null).

  • Add a typed GraphQL response interface and use octokit.graphql<...>(...).
  • Map contributionLevel (NONE | FIRST_QUARTILE | ...) to the existing ContributionDay['level'] (0 | 1 | 2 | 3 | 4) and remove any from the flatMap/map callbacks.
  • Also remove Promise<any>[] at line 169.
🧰 Tools
🪛 ESLint

[error] 100-100: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 100-100: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useGitHubData.ts` at line 100, Create a proper TypeScript interface
for the GraphQL response (e.g., GitHubContributionsResponse with typed user,
contributionsCollection, contributionCalendar, weeks, days, contributionLevel as
string union) and replace the untyped call by invoking
octokit.graphql<GitHubContributionsResponse>(...) instead of casting to any;
adjust the response variable type accordingly (replace response: any). Update
the flatMap/map callbacks to use explicit types for week and day (e.g., week:
GitHubWeek, day: GitHubDay) and convert contributionLevel
(NONE|FIRST_QUARTILE|SECOND_QUARTILE|THIRD_QUARTILE|FOURTH_QUARTILE) to
ContributionDay['level'] numbers (0–4) in the mapping logic. Ensure you handle
user possibly being null in the typed response before accessing
contributionsCollection. Finally, replace Promise<any>[] with a typed
Promise<YourTypedResult>[] (use the actual return type used by the caller) to
remove any remaining explicit any usage.

Comment on lines +198 to +202
const [results, contributionResult] =
await Promise.all([
Promise.allSettled(requests),
contributionRequest,
]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the relevant section around the cited lines
nl -ba src/hooks/useGitHubData.ts | sed -n '150,320p'

Repository: GitMetricsLab/github_tracker

Length of output: 115


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect around the cited lines using cat -n (nl isn't available)
cat -n src/hooks/useGitHubData.ts | sed -n '160,290p'

# Find all occurrences of Promise.all / allSettled around contributionRequest
rg -n "contributionRequest|Promise\.all\(|allSettled\(" src/hooks/useGitHubData.ts

Repository: GitMetricsLab/github_tracker

Length of output: 4601


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the helper implementation and where contribution state is defined
rg -n "function fetchContributionData|const fetchContributionData|fetchContributionData\\(" src/hooks/useGitHubData.ts
rg -n "setContributionData\\(|contributionData" src/hooks/useGitHubData.ts

# Print the top of the file to capture types/state initialization
head -n 140 src/hooks/useGitHubData.ts

Repository: GitMetricsLab/github_tracker

Length of output: 3637


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect further down for the catch block and any other contribution handling
cat -n src/hooks/useGitHubData.ts | sed -n '290,420p'

# Also search for setContributionData usages to ensure no other fallback exists
rg -n "setContributionData\\(" src/hooks/useGitHubData.ts

Repository: GitMetricsLab/github_tracker

Length of output: 725


Prevent contribution (heatmap) fetch failures from aborting issues/PR rendering.

Promise.all([Promise.allSettled(requests), contributionRequest]) rejects as soon as contributionRequest rejects, skipping the already-settled results handling and the setIssues/setPrs updates.

💡 Suggested fix
-        const [results, contributionResult] =
-          await Promise.all([
-            Promise.allSettled(requests),
-            contributionRequest,
-        ]);
+        const [results, contributionResult] = await Promise.all([
+          Promise.allSettled(requests),
+          contributionRequest
+            .then((value) => ({ status: 'fulfilled' as const, value }))
+            .catch((reason) => ({ status: 'rejected' as const, reason })),
+        ]);
...
-        setRateLimited(false);
-        setContributionData(contributionResult);
+        setRateLimited(false);
+        if (contributionResult.status === 'fulfilled') {
+          setContributionData(contributionResult.value);
+        } else {
+          setContributionData([]);
+          setError((prev) => prev || 'Contribution activity is currently unavailable.');
+        }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useGitHubData.ts` around lines 198 - 202, The current
Promise.all([Promise.allSettled(requests), contributionRequest]) will reject if
contributionRequest fails and skip handling the already-settled requests; change
the flow in useGitHubData.ts to await Promise.allSettled(requests) first (assign
to results), then await contributionRequest inside a try/catch (assign to
contributionResult or a safe default on error) so that failures fetching
contributions do not prevent executing setIssues and setPrs; reference the
variables requests, contributionRequest, results, contributionResult, setIssues
and setPrs when making the change.

Comment on lines +402 to +406
{contributionData.length > 0 ? (
<ContributionHeatmap data={contributionData} />
) : (
<p>No contribution data available</p>
)}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Gate the empty-state message to post-fetch state.

This block currently shows “No contribution data available” even before a fetch completes, which is misleading.

💡 Suggested fix
-      {contributionData.length > 0 ? (
-        <ContributionHeatmap data={contributionData} />
-      ) : (
-        <p>No contribution data available</p>
-      )}
+      {username && !loading && !dataError && (
+        contributionData.length > 0 ? (
+          <ContributionHeatmap data={contributionData} />
+        ) : (
+          <p>No contribution data available</p>
+        )
+      )}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/Tracker/Tracker.tsx` around lines 402 - 406, The empty-state
message is shown before the fetch completes; update the render logic around
ContributionHeatmap and contributionData to only show "No contribution data
available" after the fetch has finished (e.g., use an existing or new fetch
status flag like isLoading or hasFetched). Concretely, add or use a boolean such
as isContribLoading/isContribFetched in the Tracker component and change the
conditional so ContributionHeatmap is shown when contributionData.length > 0, a
loading spinner/placeholder is shown when isContribLoading is true, and the
empty-state message is shown only when !isContribLoading && isContribFetched &&
contributionData.length === 0; update the fetch handler that fills
contributionData to set these flags (e.g., in the async fetch function that
populates contributionData).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🚀 Feature: Contribution heatmap — GitHub-style calendar grid for tracked users

1 participant