166 lines
4.6 KiB
JavaScript
166 lines
4.6 KiB
JavaScript
// @ts-check
|
|
|
|
import { retryer } from "../common/retryer.js";
|
|
import { logger, request } from "../common/utils.js";
|
|
import { excludeRepositories } from "../common/envs.js";
|
|
import { CustomError, MissingParamError } from "../common/error.js";
|
|
import { wrapTextMultiline } from "../common/fmt.js";
|
|
|
|
/**
|
|
* @typedef {import("axios").AxiosRequestHeaders} AxiosRequestHeaders Axios request headers.
|
|
* @typedef {import("axios").AxiosResponse} AxiosResponse Axios response.
|
|
*/
|
|
|
|
/**
|
|
* Top languages fetcher object.
|
|
*
|
|
* @param {AxiosRequestHeaders} variables Fetcher variables.
|
|
* @param {string} token GitHub token.
|
|
* @returns {Promise<AxiosResponse>} Languages fetcher response.
|
|
*/
|
|
const fetcher = (variables, token) => {
|
|
return request(
|
|
{
|
|
query: `
|
|
query userInfo($login: String!) {
|
|
user(login: $login) {
|
|
# fetch only owner repos & not forks
|
|
repositories(ownerAffiliations: OWNER, isFork: false, first: 100) {
|
|
nodes {
|
|
name
|
|
languages(first: 10, orderBy: {field: SIZE, direction: DESC}) {
|
|
edges {
|
|
size
|
|
node {
|
|
color
|
|
name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
variables,
|
|
},
|
|
{
|
|
Authorization: `token ${token}`,
|
|
},
|
|
);
|
|
};
|
|
|
|
/**
|
|
* @typedef {import("./types").TopLangData} TopLangData Top languages data.
|
|
*/
|
|
|
|
/**
|
|
* Fetch top languages for a given username.
|
|
*
|
|
* @param {string} username GitHub username.
|
|
* @param {string[]} exclude_repo List of repositories to exclude.
|
|
* @param {number} size_weight Weightage to be given to size.
|
|
* @param {number} count_weight Weightage to be given to count.
|
|
* @returns {Promise<TopLangData>} Top languages data.
|
|
*/
|
|
const fetchTopLanguages = async (
|
|
username,
|
|
exclude_repo = [],
|
|
size_weight = 1,
|
|
count_weight = 0,
|
|
) => {
|
|
if (!username) {
|
|
throw new MissingParamError(["username"]);
|
|
}
|
|
|
|
const res = await retryer(fetcher, { login: username });
|
|
|
|
if (res.data.errors) {
|
|
logger.error(res.data.errors);
|
|
if (res.data.errors[0].type === "NOT_FOUND") {
|
|
throw new CustomError(
|
|
res.data.errors[0].message || "Could not fetch user.",
|
|
CustomError.USER_NOT_FOUND,
|
|
);
|
|
}
|
|
if (res.data.errors[0].message) {
|
|
throw new CustomError(
|
|
wrapTextMultiline(res.data.errors[0].message, 90, 1)[0],
|
|
res.statusText,
|
|
);
|
|
}
|
|
throw new CustomError(
|
|
"Something went wrong while trying to retrieve the language data using the GraphQL API.",
|
|
CustomError.GRAPHQL_ERROR,
|
|
);
|
|
}
|
|
|
|
let repoNodes = res.data.data.user.repositories.nodes;
|
|
let repoToHide = {};
|
|
const allExcludedRepos = [...exclude_repo, ...excludeRepositories];
|
|
|
|
// populate repoToHide map for quick lookup
|
|
// while filtering out
|
|
if (allExcludedRepos) {
|
|
allExcludedRepos.forEach((repoName) => {
|
|
repoToHide[repoName] = true;
|
|
});
|
|
}
|
|
|
|
// filter out repositories to be hidden
|
|
repoNodes = repoNodes
|
|
.sort((a, b) => b.size - a.size)
|
|
.filter((name) => !repoToHide[name.name]);
|
|
|
|
let repoCount = 0;
|
|
|
|
repoNodes = repoNodes
|
|
.filter((node) => node.languages.edges.length > 0)
|
|
// flatten the list of language nodes
|
|
.reduce((acc, curr) => curr.languages.edges.concat(acc), [])
|
|
.reduce((acc, prev) => {
|
|
// get the size of the language (bytes)
|
|
let langSize = prev.size;
|
|
|
|
// if we already have the language in the accumulator
|
|
// & the current language name is same as previous name
|
|
// add the size to the language size and increase repoCount.
|
|
if (acc[prev.node.name] && prev.node.name === acc[prev.node.name].name) {
|
|
langSize = prev.size + acc[prev.node.name].size;
|
|
repoCount += 1;
|
|
} else {
|
|
// reset repoCount to 1
|
|
// language must exist in at least one repo to be detected
|
|
repoCount = 1;
|
|
}
|
|
return {
|
|
...acc,
|
|
[prev.node.name]: {
|
|
name: prev.node.name,
|
|
color: prev.node.color,
|
|
size: langSize,
|
|
count: repoCount,
|
|
},
|
|
};
|
|
}, {});
|
|
|
|
Object.keys(repoNodes).forEach((name) => {
|
|
// comparison index calculation
|
|
repoNodes[name].size =
|
|
Math.pow(repoNodes[name].size, size_weight) *
|
|
Math.pow(repoNodes[name].count, count_weight);
|
|
});
|
|
|
|
const topLangs = Object.keys(repoNodes)
|
|
.sort((a, b) => repoNodes[b].size - repoNodes[a].size)
|
|
.reduce((result, key) => {
|
|
result[key] = repoNodes[key];
|
|
return result;
|
|
}, {});
|
|
|
|
return topLangs;
|
|
};
|
|
|
|
export { fetchTopLanguages };
|
|
export default fetchTopLanguages;
|