Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 1 addition & 14 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
"private": true,
"version": "0.0.0",
"type": "module",

"scripts": {
"dev": "vite --host",
"build": "vite build",
Expand All @@ -14,7 +13,6 @@
"docker:dev": "docker compose --profile dev up --build",
"docker:prod": "docker compose --profile prod up -d --build"
},

"dependencies": {
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
Expand All @@ -30,14 +28,14 @@
"octokit": "^4.0.2",
"postcss": "^8.4.47",
"react": "^18.3.1",
"react-activity-calendar": "^3.2.0",
"react-dom": "^18.3.1",
"react-hot-toast": "^2.4.1",
"react-icons": "^5.3.0",
"react-router-dom": "^6.28.0",
"recharts": "^3.8.1",
"tailwindcss": "^3.4.14"
},

"devDependencies": {
"@eslint/js": "^9.13.0",
"@testing-library/jest-dom": "^6.9.1",
Expand All @@ -49,33 +47,22 @@
"@types/react-dom": "^18.3.7",
"@types/react-redux": "^7.1.34",
"@types/react-router-dom": "^5.3.3",

"@vitejs/plugin-react-swc": "^3.5.0",

"autoprefixer": "^10.4.20",
"bcryptjs": "^3.0.3",

"eslint": "^9.13.0",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.14",

"express-session": "^1.18.2",

"globals": "^15.11.0",

"jasmine": "^5.13.0",
"jasmine-spec-reporter": "^7.0.0",

"jsdom": "^29.1.1",

"passport": "^0.7.0",
"passport-local": "^1.0.0",

"supertest": "^7.2.2",

"typescript-eslint": "^8.59.3",

"vite": "^5.4.10",
"vitest": "^4.1.6"
}
Expand Down
26 changes: 26 additions & 0 deletions src/components/ContributionHeatmap.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from "react";
import { ActivityCalendar } from "react-activity-calendar";
import { Paper, Typography } from "@mui/material";

interface ContributionHeatmapProps {
data: {
date: string;
count: number;
level: 0 | 1 | 2 | 3 | 4;
}[];
}

const ContributionHeatmap: React.FC<ContributionHeatmapProps> = ({ data }) => {
return (
<Paper elevation={2} sx={{ p: 3, mt: 4 }}>
<Typography variant="h6" gutterBottom>
Contribution Activity
</Typography>

<ActivityCalendar data={data} theme={{light: ['#ebedf0', '#9be9a8', '#40c463', '#30a14e', '#216e39'],
dark: ['#161b22', '#0e4429', '#006d32', '#26a641', '#39d353'],}} />
</Paper>
);
};

export default ContributionHeatmap;
63 changes: 62 additions & 1 deletion src/hooks/useGitHubData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ interface GitHubItem {
html_url: string;
}

interface ContributionDay {
date: string;
count: number;
level: 0 | 1 | 2 | 3 | 4;
}

interface FetchFilters {
search?: string;
repo?: string;
Expand All @@ -31,6 +37,7 @@ export const useGitHubData = (
const [totalIssues, setTotalIssues] = useState(0);
const [totalPrs, setTotalPrs] = useState(0);
const [rateLimited, setRateLimited] = useState(false);
const [contributionData, setContributionData] = useState<ContributionDay[]>([]);

// Prevent stale responses overwriting latest data
const lastRequestId = useRef(0);
Expand Down Expand Up @@ -86,6 +93,53 @@ export const useGitHubData = (
};
};

const fetchContributionData = async (
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.

`
query($login: String!) {
user(login: $login) {
contributionsCollection {
contributionCalendar {
weeks {
contributionDays {
date
contributionCount
contributionLevel
}
}
}
}
}
}
`,
{
login: username,
}
);

return response.user.contributionsCollection
.contributionCalendar.weeks
.flatMap((week: any) =>
week.contributionDays.map((day: any) => ({
date: day.date,
count: day.contributionCount,
level:
day.contributionLevel === "NONE"
? 0
: day.contributionLevel === "FIRST_QUARTILE"
? 1
: day.contributionLevel === "SECOND_QUARTILE"
? 2
: day.contributionLevel === "THIRD_QUARTILE"
? 3
: 4,
}))
);
};

const fetchData = useCallback(
async (
username: string,
Expand Down Expand Up @@ -113,6 +167,7 @@ export const useGitHubData = (
activeTab === 'pr' || activeTab === 'both';

const requests: Promise<any>[] = [];
const contributionRequest = fetchContributionData(octokit, username);

if (shouldFetchIssues) {
requests.push(
Expand Down Expand Up @@ -140,7 +195,11 @@ export const useGitHubData = (
);
}

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

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.


// Ignore stale requests
if (requestId !== lastRequestId.current) {
Expand Down Expand Up @@ -186,6 +245,7 @@ export const useGitHubData = (
}

setRateLimited(false);
setContributionData(contributionResult);
} catch (err: unknown) {
if (requestId !== lastRequestId.current) {
return;
Expand Down Expand Up @@ -244,6 +304,7 @@ export const useGitHubData = (
prs,
totalIssues,
totalPrs,
contributionData,
loading,
error,
rateLimited,
Expand Down
10 changes: 9 additions & 1 deletion src/pages/Tracker/Tracker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { useTheme } from "@mui/material/styles";
import { useGitHubAuth } from "../../hooks/useGitHubAuth";
import { useGitHubData } from "../../hooks/useGitHubData";
import { KeyIcon } from "lucide-react";
import ContributionHeatmap from "../../components/ContributionHeatmap";

const ROWS_PER_PAGE = 10;

Expand All @@ -55,15 +56,17 @@ const Home: React.FC = () => {
setUsername,
token,
setToken,
error: authError,
getOctokit,
} = useGitHubAuth();

const authError = "";

const {
issues,
prs,
totalIssues,
totalPrs,
contributionData,
loading,
error: dataError,
fetchData,
Expand Down Expand Up @@ -396,6 +399,11 @@ const Home: React.FC = () => {
</TableContainer>
</Box>
)}
{contributionData.length > 0 ? (
<ContributionHeatmap data={contributionData} />
) : (
<p>No contribution data available</p>
)}
Comment on lines +402 to +406

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).

</Container>
);
};
Expand Down
Loading