Skip to content
Closed
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
6 changes: 6 additions & 0 deletions netlify.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[build]
publish = "api"
functions = "api"

[build.environment]
NODE_VERSION = "22"
87 changes: 79 additions & 8 deletions src/fetchers/top-languages.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,24 @@ const fetcher = (variables, token) => {
size
node {
color
name
}
}
}
}
}
# fetch user gists (public only) - NEW: Include gists in language calculation
gists(first: 100, privacy: PUBLIC, orderBy: {field: CREATED_AT, direction: DESC}) {
nodes {
files(first: 10) {
name
language {
name
color
}
size
}
}
}
}
}
`,
Expand Down Expand Up @@ -92,6 +104,7 @@ const fetchTopLanguages = async (
}

let repoNodes = res.data.data.user.repositories.nodes;
const gistNodes = res.data.data.user.gists.nodes; // NEW: Extract gist nodes from GraphQL response
/** @type {Record<string, boolean>} */
let repoToHide = {};
const allExcludedRepos = [...exclude_repo, ...excludeRepositories];
Expand Down Expand Up @@ -141,17 +154,75 @@ const fetchTopLanguages = async (
};
}, {});

Object.keys(repoNodes).forEach((name) => {
// Process gists - NEW: Aggregate languages from user's public gists
// Unlike repositories which have pre-aggregated language stats, gists have individual files
// that need to be processed to calculate language usage
const gistLanguages = gistNodes
.filter((node) => node.files && node.files.length > 0) // Only gists with files
.reduce((acc, gist) => {
// Group files by language within this gist
const gistLangs = gist.files
.filter((file) => file.language) // only files with detected languages
.reduce((gistAcc, file) => {
const langName = file.language.name;
if (gistAcc[langName]) {
gistAcc[langName].size += file.size;
} else {
gistAcc[langName] = {
name: langName,
color: file.language.color,
size: file.size,
};
}
return gistAcc;
}, {});

// For each language in this gist, add to the global accumulator
Object.keys(gistLangs).forEach((langName) => {
if (acc[langName]) {
// Language already exists - add size and increment count
acc[langName].size += gistLangs[langName].size;
acc[langName].count += 1; // Count represents number of gists containing this language
} else {
// First time seeing this language
acc[langName] = {
name: langName,
color: gistLangs[langName].color,
size: gistLangs[langName].size,
count: 1, // Count represents number of gists containing this language
};
}
});

return acc;
}, {});

// Merge repo and gist languages - NEW: Combine language data from both sources
// This ensures that languages used in both repositories and gists are properly aggregated
// Languages only in gists will be added, and languages in both will have their stats combined
const allLanguages = { ...repoNodes };
Object.keys(gistLanguages).forEach((langName) => {
if (allLanguages[langName]) {
// Language exists in both repos and gists - combine the stats
allLanguages[langName].size += gistLanguages[langName].size;
allLanguages[langName].count += gistLanguages[langName].count;
} else {
// Language only in gists - add it to the combined results
allLanguages[langName] = gistLanguages[langName];
}
});

Object.keys(allLanguages).forEach((name) => {
// comparison index calculation
repoNodes[name].size =
Math.pow(repoNodes[name].size, size_weight) *
Math.pow(repoNodes[name].count, count_weight);
allLanguages[name].size =
Math.pow(allLanguages[name].size, size_weight) *
Math.pow(allLanguages[name].count, count_weight);
});

const topLangs = Object.keys(repoNodes)
.sort((a, b) => repoNodes[b].size - repoNodes[a].size)
const topLangs = Object.keys(allLanguages)
.sort((a, b) => allLanguages[b].size - allLanguages[a].size)
.reduce((result, key) => {
result[key] = repoNodes[key];
result[key] = allLanguages[key];
return result;
}, {});

Expand Down
73 changes: 68 additions & 5 deletions tests/fetchTopLanguages.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,21 @@ const data_langs = {
},
],
},
gists: {
nodes: [
{
files: [
{ name: "test.py", language: { name: "Python", color: "#3572A5" }, size: 50 },
{ name: "notebook.ipynb", language: { name: "Jupyter Notebook", color: "#DA5B0B" }, size: 75 },
],
},
{
files: [
{ name: "script.py", language: { name: "Python", color: "#3572A5" }, size: 25 },
],
},
],
},
},
},
};
Expand Down Expand Up @@ -78,6 +93,18 @@ describe("FetchTopLanguages", () => {
name: "javascript",
size: 20.000000000000004,
},
Python: {
color: "#3572A5",
count: 2,
name: "Python",
size: 12.247448713915892,
},
"Jupyter Notebook": {
color: "#DA5B0B",
count: 1,
name: "Jupyter Notebook",
size: 8.660254037844387,
},
});
});

Expand All @@ -86,17 +113,29 @@ describe("FetchTopLanguages", () => {

let repo = await fetchTopLanguages("anuraghazra", ["test-repo-1"]);
expect(repo).toStrictEqual({
javascript: {
color: "#0ff",
count: 2,
name: "javascript",
size: 200,
},
Python: {
color: "#3572A5",
count: 2,
name: "Python",
size: 75,
},
HTML: {
color: "#0f0",
count: 1,
name: "HTML",
size: 100,
},
javascript: {
color: "#0ff",
count: 2,
name: "javascript",
size: 200,
"Jupyter Notebook": {
color: "#DA5B0B",
count: 1,
name: "Jupyter Notebook",
size: 75,
},
});
});
Expand All @@ -118,6 +157,18 @@ describe("FetchTopLanguages", () => {
name: "javascript",
size: 200,
},
Python: {
color: "#3572A5",
count: 2,
name: "Python",
size: 75,
},
"Jupyter Notebook": {
color: "#DA5B0B",
count: 1,
name: "Jupyter Notebook",
size: 75,
},
});
});

Expand All @@ -138,6 +189,18 @@ describe("FetchTopLanguages", () => {
name: "javascript",
size: 2,
},
Python: {
color: "#3572A5",
count: 2,
name: "Python",
size: 2,
},
"Jupyter Notebook": {
color: "#DA5B0B",
count: 1,
name: "Jupyter Notebook",
size: 1,
},
});
});

Expand Down