Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Customization of ownerAffiliations with parameter #1122

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ vercel_token
# IDE
.vscode
*.code-workspace

.vercel
2 changes: 2 additions & 0 deletions api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export default async (req, res) => {
disable_animations,
border_radius,
border_color,
role,
} = req.query;
res.setHeader("Content-Type", "image/svg+xml");

Expand All @@ -53,6 +54,7 @@ export default async (req, res) => {
parseBoolean(count_private),
parseBoolean(include_all_commits),
parseArray(exclude_repo),
parseArray(role),
);

const cacheSeconds = clampValue(
Expand Down
2 changes: 2 additions & 0 deletions api/top-langs.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export default async (req, res) => {
locale,
border_radius,
border_color,
role,
disable_animations,
} = req.query;
res.setHeader("Content-Type", "image/svg+xml");
Expand All @@ -45,6 +46,7 @@ export default async (req, res) => {
const topLangs = await fetchTopLanguages(
username,
parseArray(exclude_repo),
parseArray(role),
);

const cacheSeconds = clampValue(
Expand Down
38 changes: 38 additions & 0 deletions src/common/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -300,11 +300,16 @@ const CONSTANTS = {
ONE_DAY: 86400,
};

const OWNER_AFFILIATIONS = ["OWNER", "COLLABORATOR", "ORGANIZATION_MEMBER"];

const SECONDARY_ERROR_MESSAGES = {
MAX_RETRY:
"Please add an env variable called PAT_1 with your github token in vercel",
USER_NOT_FOUND: "Make sure the provided username is not an organization",
GRAPHQL_ERROR: "Please try again later",
INVALID_AFFILIATION: `Invalid owner affiliations. Valid values are: ${OWNER_AFFILIATIONS.join(
", ",
)}`,
};

/**
Expand All @@ -324,6 +329,7 @@ class CustomError extends Error {
static MAX_RETRY = "MAX_RETRY";
static USER_NOT_FOUND = "USER_NOT_FOUND";
static GRAPHQL_ERROR = "GRAPHQL_ERROR";
static INVALID_AFFILIATION = "INVALID_AFFILIATION";
}

/**
Expand Down Expand Up @@ -423,6 +429,36 @@ const parseEmojis = (str) => {
return toEmoji.get(emoji) || "";
});
};
/**
* Parse owner affiliations.
*
* @param {string[]} affiliations
* @returns {string[]} Parsed affiliations.
*
* @throws {CustomError} If affiliations contains invalid values.
*/
const parseOwnerAffiliations = (affiliations) => {
// Set default value for ownerAffiliations.
// NOTE: Done here since parseArray() will always return an empty array even nothing
//was specified.
affiliations =
affiliations && affiliations.length > 0
? affiliations.map((affiliation) => affiliation.toUpperCase())
: ["OWNER"];

// Check if ownerAffiliations contains valid values.
if (
affiliations.some(
(affiliation) => !OWNER_AFFILIATIONS.includes(affiliation),
)
) {
throw new CustomError(
"Invalid query parameter",
CustomError.INVALID_AFFILIATION,
);
}
return affiliations;
};

export {
ERROR_CARD_LENGTH,
Expand All @@ -441,10 +477,12 @@ export {
wrapTextMultiline,
logger,
CONSTANTS,
OWNER_AFFILIATIONS,
CustomError,
MissingParamError,
measureText,
lowercaseTrim,
chunkArray,
parseEmojis,
parseOwnerAffiliations,
};
25 changes: 18 additions & 7 deletions src/fetchers/stats-fetcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ import {
MissingParamError,
request,
wrapTextMultiline,
parseOwnerAffiliations,
} from "../common/utils.js";

dotenv.config();

// GraphQL queries.
const GRAPHQL_REPOS_FIELD = `
repositories(first: 100, ownerAffiliations: OWNER, orderBy: {direction: DESC, field: STARGAZERS}, after: $after) {
repositories(first: 100, after: $after, ownerAffiliations: $ownerAffiliations, orderBy: {direction: DESC, field: STARGAZERS}) {
totalCount
nodes {
name
Expand All @@ -32,15 +33,15 @@ const GRAPHQL_REPOS_FIELD = `
`;

const GRAPHQL_REPOS_QUERY = `
query userInfo($login: String!, $after: String) {
user(login: $login) {
query userInfo($login: String!, $after: String, $ownerAffiliations: [RepositoryAffiliation]) {
user(login: $login, ownerAffiliations: $ownerAffiliations) {
${GRAPHQL_REPOS_FIELD}
}
}
`;

const GRAPHQL_STATS_QUERY = `
query userInfo($login: String!, $after: String) {
query userInfo($login: String!, $after: String, $ownerAffiliations: [RepositoryAffiliation]) {
user(login: $login) {
name
login
Expand Down Expand Up @@ -92,16 +93,22 @@ const fetcher = (variables, token) => {
* Fetch stats information for a given username.
*
* @param {string} username Github username.
* @param {string[]} ownerAffiliations The owner affiliations to filter by. Default: OWNER.
* @returns {Promise<import('../common/types').StatsFetcher>} GraphQL Stats object.
*
* @description This function supports multi-page fetching if the 'FETCH_MULTI_PAGE_STARS' environment variable is set to true.
*/
const statsFetcher = async (username) => {
const statsFetcher = async (username, ownerAffiliations) => {
let stats;
let hasNextPage = true;
let endCursor = null;
while (hasNextPage) {
const variables = { login: username, first: 100, after: endCursor };
const variables = {
login: username,
first: 100,
after: endCursor,
ownerAffiliations: ownerAffiliations,
};
let res = await retryer(fetcher, variables);
if (res.data.errors) return res;

Expand Down Expand Up @@ -175,13 +182,16 @@ const totalCommitsFetcher = async (username) => {
* @param {string} username GitHub username.
* @param {boolean} count_private Include private contributions.
* @param {boolean} include_all_commits Include all commits.
* @param {string[]} exclude_repo Repositories to exclude. Default: [].
* @param {string[]} ownerAffiliations Owner affiliations. Default: OWNER.
* @returns {Promise<import("./types").StatsData>} Stats data.
*/
const fetchStats = async (
username,
count_private = false,
include_all_commits = false,
exclude_repo = [],
ownerAffiliations = [],
) => {
if (!username) throw new MissingParamError(["username"]);

Expand All @@ -194,8 +204,9 @@ const fetchStats = async (
contributedTo: 0,
rank: { level: "C", score: 0 },
};
ownerAffiliations = parseOwnerAffiliations(ownerAffiliations);

let res = await statsFetcher(username);
let res = await statsFetcher(username, ownerAffiliations);

// Catch GraphQL errors.
if (res.data.errors) {
Expand Down
19 changes: 13 additions & 6 deletions src/fetchers/top-languages-fetcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
MissingParamError,
request,
wrapTextMultiline,
parseOwnerAffiliations,
} from "../common/utils.js";

/**
Expand All @@ -19,10 +20,10 @@ const fetcher = (variables, token) => {
return request(
{
query: `
query userInfo($login: String!) {
query userInfo($login: String!, $ownerAffiliations: [RepositoryAffiliation]) {
user(login: $login) {
# fetch only owner repos & not forks
repositories(ownerAffiliations: OWNER, isFork: false, first: 100) {
# do not fetch forks
repositories(ownerAffiliations: $ownerAffiliations, isFork: false, first: 100) {
rickstaa marked this conversation as resolved.
Show resolved Hide resolved
rickstaa marked this conversation as resolved.
Show resolved Hide resolved
nodes {
name
languages(first: 10, orderBy: {field: SIZE, direction: DESC}) {
Expand Down Expand Up @@ -51,13 +52,19 @@ const fetcher = (variables, token) => {
* Fetch top languages for a given username.
*
* @param {string} username GitHub username.
* @param {string[]} exclude_repo List of repositories to exclude.
* @param {string[]} exclude_repo List of repositories to exclude. Default: [].
* @param {string[]} ownerAffiliations The owner affiliations to filter by. Default: OWNER.
* @returns {Promise<import("./types").TopLangData>} Top languages data.
*/
const fetchTopLanguages = async (username, exclude_repo = []) => {
const fetchTopLanguages = async (
username,
exclude_repo = [],
ownerAffiliations = [],
) => {
if (!username) throw new MissingParamError(["username"]);
ownerAffiliations = parseOwnerAffiliations(ownerAffiliations);

const res = await retryer(fetcher, { login: username });
const res = await retryer(fetcher, { login: username, ownerAffiliations });

if (res.data.errors) {
logger.error(res.data.errors);
Expand Down
2 changes: 1 addition & 1 deletion vercel.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"functions": {
"api/*.js": {
"memory": 128,
"maxDuration": 30
"maxDuration": 10
rickstaa marked this conversation as resolved.
Show resolved Hide resolved
}
},
"redirects": [
Expand Down