add repo filter

This commit is contained in:
martin-mfg
2025-05-18 21:53:13 +02:00
parent 12f4f57e25
commit f91b400c43
11 changed files with 284 additions and 62 deletions
+75 -11
View File
@@ -33,6 +33,7 @@ const RANK_ONLY_CARD_DEFAULT_WIDTH = 290;
* @param {number} createTextNodeParams.shiftValuePos Number of pixels the value has to be shifted to the right.
* @param {boolean} createTextNodeParams.bold Whether to bold the label.
* @param {string} createTextNodeParams.number_format The format of numbers on card.
* @param {string} createTextNodeParams.link Url to link to.
* @returns {string} The stats card text item SVG object.
*/
const createTextNode = ({
@@ -46,6 +47,7 @@ const createTextNode = ({
shiftValuePos,
bold,
number_format,
link,
}) => {
const kValue =
number_format.toLowerCase() === "long" ? value : kFormatter(value);
@@ -59,8 +61,11 @@ const createTextNode = ({
</svg>
`
: "";
return `
<g class="stagger" style="animation-delay: ${staggerDelay}ms" transform="translate(25, 0)">
return (
`
<g class="stagger" style="animation-delay: ${staggerDelay}ms" transform="translate(25, 0)">` +
(link ? `<a href="${link}">` : "") +
`
${iconSvg}
<text class="stat ${
bold ? " bold" : "not_bold"
@@ -70,9 +75,12 @@ const createTextNode = ({
x="${(showIcons ? 140 : 120) + shiftValuePos}"
y="12.5"
data-testid="${id}"
>${kValue}${unitSymbol ? ` ${unitSymbol}` : ""}</text>
>${kValue}${unitSymbol ? ` ${unitSymbol}` : ""}</text>` +
(link ? "</a>" : "") +
`
</g>
`;
`
);
};
/**
@@ -199,7 +207,7 @@ const getStyles = ({
* @param {Partial<StatCardOptions>} options The card options.
* @returns {string} The stats card SVG object.
*/
const renderStatsCard = (stats, options = {}) => {
const renderStatsCard = (stats, options = {}, username, repo) => {
const {
name,
totalStars,
@@ -212,6 +220,11 @@ const renderStatsCard = (stats, options = {}) => {
totalDiscussionsStarted,
totalDiscussionsAnswered,
contributedTo,
totalPRsAuthored,
totalPRsCommented,
totalPRsReviewed,
totalIssuesAuthored,
totalIssuesCommented,
rank,
} = stats;
const {
@@ -338,6 +351,53 @@ const renderStatsCard = (stats, options = {}) => {
};
}
let repoFilter = repo ? "repo%3A" + encodeURIComponent(repo) + "+" : "";
if (show.includes("prs_authored")) {
STATS.prs_authored = {
icon: icons.prs,
label: i18n.t("statcard.prs-authored"),
value: totalPRsAuthored,
id: "prs_authored",
link: `https://github.com/search?q=${repoFilter}author%3A${username}&amp;type=pullrequests`,
};
}
if (show.includes("prs_commented")) {
STATS.prs_commented = {
icon: icons.comments,
label: i18n.t("statcard.prs-commented"),
value: totalPRsCommented,
id: "prs_commented",
link: `https://github.com/search?q=${repoFilter}commenter%3A${username}+-author%3A${username}&amp;type=pullrequests`,
};
}
if (show.includes("prs_reviewed")) {
STATS.prs_reviewed = {
icon: icons.reviews,
label: i18n.t("statcard.prs-reviewed"),
value: totalPRsReviewed,
id: "prs_reviewed",
link: `https://github.com/search?q=${repoFilter}reviewed-by%3A${username}+-author%3A${username}&amp;type=pullrequests`,
};
}
if (show.includes("issues_authored")) {
STATS.issues_authored = {
icon: icons.issues,
label: i18n.t("statcard.issues-authored"),
value: totalIssuesAuthored,
id: "issues_authored",
link: `https://github.com/search?q=${repoFilter}author%3A${username}&amp;type=issues`,
};
}
if (show.includes("issues_commented")) {
STATS.issues_commented = {
icon: icons.discussions_started,
label: i18n.t("statcard.issues-commented"),
value: totalIssuesCommented,
id: "issues_commented",
link: `https://github.com/search?q=${repoFilter}commenter%3A${username}+-author%3A${username}&amp;type=issues`,
};
}
STATS.contribs = {
icon: icons.contribs,
label: i18n.t("statcard.contribs"),
@@ -363,6 +423,12 @@ const renderStatsCard = (stats, options = {}) => {
];
const isLongLocale = locale ? longLocales.includes(locale) : false;
// check if all used labels are short
const longLabels =
Object.keys(STATS)
.filter((key) => !hide.includes(key))
.filter((key) => STATS[key].label.length > 18).length > 0;
// filter out hidden stats defined by user & create the text nodes
const statItems = Object.keys(STATS)
.filter((key) => !hide.includes(key))
@@ -376,9 +442,10 @@ const renderStatsCard = (stats, options = {}) => {
unitSymbol: STATS[key].unitSymbol,
index,
showIcons: show_icons,
shiftValuePos: 79.01 + (isLongLocale ? 50 : 0),
shiftValuePos: 29.01 + (longLabels ? 50 : 0) + (isLongLocale ? 50 : 0),
bold: text_bold,
number_format,
link: STATS[key].link,
}),
);
@@ -441,12 +508,9 @@ const renderStatsCard = (stats, options = {}) => {
: RANK_ONLY_CARD_DEFAULT_WIDTH) + iconWidth;
let width = card_width
? isNaN(card_width)
? defaultCardWidth
? Math.max(defaultCardWidth, minCardWidth)
: card_width
: defaultCardWidth;
if (width < minCardWidth) {
width = minCardWidth;
}
: Math.max(defaultCardWidth, minCardWidth);
const card = new Card({
customTitle: custom_title,
+1
View File
@@ -11,6 +11,7 @@ const icons = {
reviews: `<path fill-rule="evenodd" d="M8 2c1.981 0 3.671.992 4.933 2.078 1.27 1.091 2.187 2.345 2.637 3.023a1.62 1.62 0 0 1 0 1.798c-.45.678-1.367 1.932-2.637 3.023C11.67 13.008 9.981 14 8 14c-1.981 0-3.671-.992-4.933-2.078C1.797 10.83.88 9.576.43 8.898a1.62 1.62 0 0 1 0-1.798c.45-.677 1.367-1.931 2.637-3.022C4.33 2.992 6.019 2 8 2ZM1.679 7.932a.12.12 0 0 0 0 .136c.411.622 1.241 1.75 2.366 2.717C5.176 11.758 6.527 12.5 8 12.5c1.473 0 2.825-.742 3.955-1.715 1.124-.967 1.954-2.096 2.366-2.717a.12.12 0 0 0 0-.136c-.412-.621-1.242-1.75-2.366-2.717C10.824 4.242 9.473 3.5 8 3.5c-1.473 0-2.825.742-3.955 1.715-1.124.967-1.954 2.096-2.366 2.717ZM8 10a2 2 0 1 1-.001-3.999A2 2 0 0 1 8 10Z"/>`,
discussions_started: `<path fill-rule="evenodd" d="M1.75 1h8.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 10.25 10H7.061l-2.574 2.573A1.458 1.458 0 0 1 2 11.543V10h-.25A1.75 1.75 0 0 1 0 8.25v-5.5C0 1.784.784 1 1.75 1ZM1.5 2.75v5.5c0 .138.112.25.25.25h1a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h3.5a.25.25 0 0 0 .25-.25v-5.5a.25.25 0 0 0-.25-.25h-8.5a.25.25 0 0 0-.25.25Zm13 2a.25.25 0 0 0-.25-.25h-.5a.75.75 0 0 1 0-1.5h.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 14.25 12H14v1.543a1.458 1.458 0 0 1-2.487 1.03L9.22 12.28a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215l2.22 2.22v-2.19a.75.75 0 0 1 .75-.75h1a.25.25 0 0 0 .25-.25Z" />`,
discussions_answered: `<path fill-rule="evenodd" d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z" />`,
comments: `<path d="M1 2.75C1 1.784 1.784 1 2.75 1h10.5c.966 0 1.75.784 1.75 1.75v7.5A1.75 1.75 0 0 1 13.25 12H9.06l-2.573 2.573A1.458 1.458 0 0 1 4 13.543V12H2.75A1.75 1.75 0 0 1 1 10.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h2a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h4.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z" />`,
gist: `<path fill-rule="evenodd" d="M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v12.5A1.75 1.75 0 0 1 14.25 16H1.75A1.75 1.75 0 0 1 0 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25V1.75a.25.25 0 0 0-.25-.25Zm7.47 3.97a.75.75 0 0 1 1.06 0l2 2a.75.75 0 0 1 0 1.06l-2 2a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734L10.69 8 9.22 6.53a.75.75 0 0 1 0-1.06ZM6.78 6.53 5.31 8l1.47 1.47a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215l-2-2a.75.75 0 0 1 0-1.06l2-2a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Z" />`,
};
+2 -2
View File
@@ -45,7 +45,7 @@ const retryer = async (fetcher, variables, retries = 0) => {
// if rate limit is hit increase the RETRIES and recursively call the retryer
// with username, and current RETRIES
if (isRateExceeded) {
logger.log(`PAT_${retries + 1} Failed`);
logger.log(`PAT_${retries + 1} Failed due to rate limiting`);
retries++;
// directly return from the function
return retryer(fetcher, variables, retries);
@@ -62,7 +62,7 @@ const retryer = async (fetcher, variables, retries = 0) => {
err.response.data.message === "Sorry. Your account was suspended.";
if (isBadCredential || isAccountSuspended) {
logger.log(`PAT_${retries + 1} Failed`);
logger.log(`PAT_${retries + 1} Failed due to bad credentials`);
retries++;
// directly return from the function
return retryer(fetcher, variables, retries);
+67 -6
View File
@@ -167,17 +167,22 @@ const statsFetcher = async ({
* @description Done like this because the GitHub API does not provide a way to fetch all the commits. See
* #92#issuecomment-661026467 and #211 for more information.
*/
const totalCommitsFetcher = async (username) => {
const totalItemsFetcher = async (username, repo, type, filter) => {
if (!githubUsernameRegex.test(username)) {
logger.log("Invalid username provided.");
throw new Error("Invalid username provided.");
}
// https://developer.github.com/v3/search/#search-commits
const fetchTotalCommits = (variables, token) => {
const fetchTotalItems = (variables, token) => {
return axios({
method: "get",
url: `https://api.github.com/search/commits?q=author:${variables.login}`,
url:
`https://api.github.com/search/` +
type +
`?per_page=1&q=` +
filter +
(variables.repo ? `+repo:${variables.repo}` : ``),
headers: {
"Content-Type": "application/json",
Accept: "application/vnd.github.cloak-preview",
@@ -188,7 +193,7 @@ const totalCommitsFetcher = async (username) => {
let res;
try {
res = await retryer(fetchTotalCommits, { login: username });
res = await retryer(fetchTotalItems, { login: username, repo });
} catch (err) {
logger.log(err);
throw new Error(err);
@@ -197,7 +202,7 @@ const totalCommitsFetcher = async (username) => {
const totalCount = res.data.total_count;
if (!totalCount || isNaN(totalCount)) {
throw new CustomError(
"Could not fetch total commits.",
"Could not fetch data from GitHub REST API.",
CustomError.GITHUB_REST_API_ERROR,
);
}
@@ -226,6 +231,12 @@ const fetchStats = async (
include_merged_pull_requests = false,
include_discussions = false,
include_discussions_answers = false,
repo = null,
include_prs_authored = false,
include_prs_commented = false,
include_prs_reviewed = false,
include_issues_authored = false,
include_issues_commented = false,
) => {
if (!username) {
throw new MissingParamError(["username"]);
@@ -243,6 +254,11 @@ const fetchStats = async (
totalDiscussionsStarted: 0,
totalDiscussionsAnswered: 0,
contributedTo: 0,
totalPRsAuthored: 0,
totalPRsCommented: 0,
totalPRsReviewed: 0,
totalIssuesAuthored: 0,
totalIssuesCommented: 0,
rank: { level: "C", percentile: 100 },
};
@@ -280,10 +296,55 @@ const fetchStats = async (
// if include_all_commits, fetch all commits using the REST API.
if (include_all_commits) {
stats.totalCommits = await totalCommitsFetcher(username);
stats.totalCommits = await totalItemsFetcher(
username,
repo,
"commits",
`author:${username}`,
);
} else {
stats.totalCommits = user.contributionsCollection.totalCommitContributions;
}
if (include_prs_authored) {
stats.totalPRsAuthored = await totalItemsFetcher(
username,
repo,
"issues",
`author:${username}+type:pr`,
);
}
if (include_prs_commented) {
stats.totalPRsCommented = await totalItemsFetcher(
username,
repo,
"issues",
`commenter:${username}+-author:${username}+type:pr`,
);
}
if (include_prs_reviewed) {
stats.totalPRsReviewed = await totalItemsFetcher(
username,
repo,
"issues",
`reviewed-by:${username}+-author:${username}+type:pr`,
);
}
if (include_issues_authored) {
stats.totalIssuesAuthored = await totalItemsFetcher(
username,
repo,
"issues",
`author:${username}+type:issue`,
);
}
if (include_issues_commented) {
stats.totalIssuesCommented = await totalItemsFetcher(
username,
repo,
"issues",
`commenter:${username}+-author:${username}+type:issue`,
);
}
stats.totalPRs = user.pullRequests.totalCount;
if (include_merged_pull_requests) {
+5
View File
@@ -36,6 +36,11 @@ export type StatsData = {
totalDiscussionsStarted: number;
totalDiscussionsAnswered: number;
contributedTo: number;
totalPRsAuthored: number;
totalPRsCommented: number;
totalPRsReviewed: number;
totalIssuesAuthored: number;
totalIssuesCommented: number;
rank: { level: string; percentile: number };
};
+15
View File
@@ -325,6 +325,21 @@ const statCardLocales = ({ name, apostrophe }) => {
vi: "Tổng Số Thảo Luận Đã Trả Lời",
se: "Totalt antal diskussioner besvarade",
},
"statcard.prs-authored": {
en: "PRs Created",
},
"statcard.prs-commented": {
en: "PRs Commented",
},
"statcard.prs-reviewed": {
en: "PRs Reviewed",
},
"statcard.issues-authored": {
en: "Issues Created",
},
"statcard.issues-commented": {
en: "Issues Commented",
},
"statcard.prs-merged": {
ar: "مجموع الطلبات المدمجة",
cn: "合并的 PR 总数",