add repo filter
This commit is contained in:
+37
-25
@@ -13,6 +13,7 @@ import { isLocaleAvailable } from "../src/translations.js";
|
||||
export default async (req, res) => {
|
||||
const {
|
||||
username,
|
||||
repo,
|
||||
hide,
|
||||
hide_title,
|
||||
hide_border,
|
||||
@@ -75,6 +76,12 @@ export default async (req, res) => {
|
||||
showStats.includes("prs_merged_percentage"),
|
||||
showStats.includes("discussions_started"),
|
||||
showStats.includes("discussions_answered"),
|
||||
repo,
|
||||
showStats.includes("prs_authored"),
|
||||
showStats.includes("prs_commented"),
|
||||
showStats.includes("prs_reviewed"),
|
||||
showStats.includes("issues_authored"),
|
||||
showStats.includes("issues_commented"),
|
||||
);
|
||||
|
||||
let cacheSeconds = clampValue(
|
||||
@@ -92,31 +99,36 @@ export default async (req, res) => {
|
||||
);
|
||||
|
||||
return res.send(
|
||||
renderStatsCard(stats, {
|
||||
hide: parseArray(hide),
|
||||
show_icons: parseBoolean(show_icons),
|
||||
hide_title: parseBoolean(hide_title),
|
||||
hide_border: parseBoolean(hide_border),
|
||||
card_width: parseInt(card_width, 10),
|
||||
hide_rank: parseBoolean(hide_rank),
|
||||
include_all_commits: parseBoolean(include_all_commits),
|
||||
line_height,
|
||||
title_color,
|
||||
ring_color,
|
||||
icon_color,
|
||||
text_color,
|
||||
text_bold: parseBoolean(text_bold),
|
||||
bg_color,
|
||||
theme,
|
||||
custom_title,
|
||||
border_radius,
|
||||
border_color,
|
||||
number_format,
|
||||
locale: locale ? locale.toLowerCase() : null,
|
||||
disable_animations: parseBoolean(disable_animations),
|
||||
rank_icon,
|
||||
show: showStats,
|
||||
}),
|
||||
renderStatsCard(
|
||||
stats,
|
||||
{
|
||||
hide: parseArray(hide),
|
||||
show_icons: parseBoolean(show_icons),
|
||||
hide_title: parseBoolean(hide_title),
|
||||
hide_border: parseBoolean(hide_border),
|
||||
card_width: parseInt(card_width, 10),
|
||||
hide_rank: parseBoolean(hide_rank),
|
||||
include_all_commits: parseBoolean(include_all_commits),
|
||||
line_height,
|
||||
title_color,
|
||||
ring_color,
|
||||
icon_color,
|
||||
text_color,
|
||||
text_bold: parseBoolean(text_bold),
|
||||
bg_color,
|
||||
theme,
|
||||
custom_title,
|
||||
border_radius,
|
||||
border_color,
|
||||
number_format,
|
||||
locale: locale ? locale.toLowerCase() : null,
|
||||
disable_animations: parseBoolean(disable_animations),
|
||||
rank_icon,
|
||||
show: showStats,
|
||||
},
|
||||
username,
|
||||
repo,
|
||||
),
|
||||
);
|
||||
} catch (err) {
|
||||
res.setHeader(
|
||||
|
||||
@@ -89,6 +89,7 @@ Please visit [this link](https://give.do/fundraisers/stand-beside-the-victims-of
|
||||
- [Hiding individual stats](#hiding-individual-stats)
|
||||
- [Showing additional individual stats](#showing-additional-individual-stats)
|
||||
- [Showing icons](#showing-icons)
|
||||
- [Filtering by repository](#filtering-by-repository)
|
||||
- [Themes](#themes)
|
||||
- [Customization](#customization)
|
||||
- [GitHub Extra Pins](#github-extra-pins)
|
||||
@@ -175,6 +176,14 @@ To enable icons, you can pass `&show_icons=true` in the query param, like so:
|
||||

|
||||
```
|
||||
|
||||
### Filtering by repository
|
||||
|
||||
To exclude specific repositories, you can use the [`&exclude_repo` parameter](#stats-card-exclusive-options).
|
||||
|
||||
To compute your stats for only one specific repository, you can pass a query parameter `&repo=<user_or_organization>/<repository>`. This filter is supported by the following items: `prs_authored`, `prs_commented`, `prs_reviewed`, `issues_authored` and `issues_commented`. Note that these items are not displayed by default, but [you can enable them individually](#showing-additional-individual-stats).
|
||||
|
||||
(Some of these mentioned items are similar to other items which are included by default, e.g. `issues_authored` is similar to `issues`. The difference is how these values are retrieved - [via GraphQL or via REST API](https://github.com/anuraghazra/github-readme-stats/discussions/1770#number-of-commits-is-incorrect). The default items use GraphQL, but filtering by repository works better via REST API.)
|
||||
|
||||
### Themes
|
||||
|
||||
With inbuilt themes, you can customize the look of the card without doing any [manual customization](#customization).
|
||||
@@ -377,12 +386,13 @@ If we don't support your language, please consider contributing! You can find mo
|
||||
| `include_all_commits` | Count total commits instead of just the current year commits. | boolean | `false` |
|
||||
| `line_height` | Sets the line height between text. | integer | `25` |
|
||||
| `exclude_repo` | Excludes specified repositories. | string (comma-separated values) | `null` |
|
||||
| `repo` | Count only stats from the specified repository. Affects only the items `prs_authored`, `prs_commented`, `prs_reviewed`, `issues_authored` and `issues_commented`. | string | `null` |
|
||||
| `custom_title` | Sets a custom title for the card. | string | `<username> GitHub Stats` |
|
||||
| `text_bold` | Uses bold text. | boolean | `true` |
|
||||
| `disable_animations` | Disables all animations in the card. | boolean | `false` |
|
||||
| `ring_color` | Color of the rank circle. | string (hex color) | `2f80ed` |
|
||||
| `number_format` | Switches between two available formats for displaying the card values `short` (i.e. `6.6k`) and `long` (i.e. `6626`). | enum | `short` |
|
||||
| `show` | Shows [additional items](#showing-additional-individual-stats) on stats card (i.e. `reviews`, `discussions_started`, `discussions_answered`, `prs_merged` or `prs_merged_percentage`). | string (comma-separated values) | `null` |
|
||||
| `show` | Shows [additional items](#showing-additional-individual-stats) on stats card (i.e. `reviews`, `discussions_started`, `discussions_answered`, `prs_merged` or `prs_merged_percentage`. And/Or the following, which support the `repo` filter: `prs_authored`, `prs_commented`, `prs_reviewed`, `issues_authored` or `issues_commented`). | string (comma-separated values) | `null` |
|
||||
|
||||
> [!NOTE]\
|
||||
> When hide\_rank=`true`, the minimum card width is 270 px + the title length and padding.
|
||||
@@ -653,6 +663,10 @@ Change the `?username=` value to your [WakaTime](https://wakatime.com) username.
|
||||
|
||||

|
||||
|
||||
* Showing stats for a specific repository
|
||||
|
||||

|
||||
|
||||
* Showing icons
|
||||
|
||||

|
||||
|
||||
+75
-11
@@ -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}&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}&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}&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}&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}&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,
|
||||
|
||||
@@ -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" />`,
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Vendored
+5
@@ -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 };
|
||||
};
|
||||
|
||||
|
||||
@@ -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 总数",
|
||||
|
||||
+7
-2
@@ -242,7 +242,9 @@ describe("Test /api/", () => {
|
||||
|
||||
it("should render error card when include_all_commits true and upstream API fails", async () => {
|
||||
mock
|
||||
.onGet("https://api.github.com/search/commits?q=author:anuraghazra")
|
||||
.onGet(
|
||||
"https://api.github.com/search/commits?per_page=1&q=author:anuraghazra",
|
||||
)
|
||||
.reply(200, { error: "Some test error message" });
|
||||
|
||||
const { req, res } = faker(
|
||||
@@ -254,7 +256,10 @@ describe("Test /api/", () => {
|
||||
|
||||
expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml");
|
||||
expect(res.send).toBeCalledWith(
|
||||
renderError("Could not fetch total commits.", "Please try again later"),
|
||||
renderError(
|
||||
"Could not fetch data from GitHub REST API.",
|
||||
"Please try again later",
|
||||
),
|
||||
);
|
||||
// Received SVG output should not contain string "https://tiny.one/readme-stats"
|
||||
expect(res.send.mock.calls[0][0]).not.toContain(
|
||||
|
||||
@@ -128,6 +128,11 @@ describe("Test fetchStats", () => {
|
||||
totalStars: 300,
|
||||
totalDiscussionsStarted: 0,
|
||||
totalDiscussionsAnswered: 0,
|
||||
totalPRsAuthored: 0,
|
||||
totalPRsCommented: 0,
|
||||
totalPRsReviewed: 0,
|
||||
totalIssuesAuthored: 0,
|
||||
totalIssuesCommented: 0,
|
||||
rank,
|
||||
});
|
||||
});
|
||||
@@ -164,6 +169,11 @@ describe("Test fetchStats", () => {
|
||||
totalStars: 300,
|
||||
totalDiscussionsStarted: 0,
|
||||
totalDiscussionsAnswered: 0,
|
||||
totalPRsAuthored: 0,
|
||||
totalPRsCommented: 0,
|
||||
totalPRsReviewed: 0,
|
||||
totalIssuesAuthored: 0,
|
||||
totalIssuesCommented: 0,
|
||||
rank,
|
||||
});
|
||||
});
|
||||
@@ -179,7 +189,9 @@ describe("Test fetchStats", () => {
|
||||
|
||||
it("should fetch total commits", async () => {
|
||||
mock
|
||||
.onGet("https://api.github.com/search/commits?q=author:anuraghazra")
|
||||
.onGet(
|
||||
"https://api.github.com/search/commits?per_page=1&q=author:anuraghazra",
|
||||
)
|
||||
.reply(200, { total_count: 1000 });
|
||||
|
||||
let stats = await fetchStats("anuraghazra", true);
|
||||
@@ -206,6 +218,11 @@ describe("Test fetchStats", () => {
|
||||
totalStars: 300,
|
||||
totalDiscussionsStarted: 0,
|
||||
totalDiscussionsAnswered: 0,
|
||||
totalPRsAuthored: 0,
|
||||
totalPRsCommented: 0,
|
||||
totalPRsReviewed: 0,
|
||||
totalIssuesAuthored: 0,
|
||||
totalIssuesCommented: 0,
|
||||
rank,
|
||||
});
|
||||
});
|
||||
@@ -218,17 +235,21 @@ describe("Test fetchStats", () => {
|
||||
|
||||
it("should throw specific error when include_all_commits true and API returns error", async () => {
|
||||
mock
|
||||
.onGet("https://api.github.com/search/commits?q=author:anuraghazra")
|
||||
.onGet(
|
||||
"https://api.github.com/search/commits?per_page=1&q=author:anuraghazra",
|
||||
)
|
||||
.reply(200, { error: "Some test error message" });
|
||||
|
||||
expect(fetchStats("anuraghazra", true)).rejects.toThrow(
|
||||
new Error("Could not fetch total commits."),
|
||||
new Error("Could not fetch data from GitHub REST API."),
|
||||
);
|
||||
});
|
||||
|
||||
it("should exclude stars of the `test-repo-1` repository", async () => {
|
||||
mock
|
||||
.onGet("https://api.github.com/search/commits?q=author:anuraghazra")
|
||||
.onGet(
|
||||
"https://api.github.com/search/commits?per_page=1&q=author:anuraghazra",
|
||||
)
|
||||
.reply(200, { total_count: 1000 });
|
||||
|
||||
let stats = await fetchStats("anuraghazra", true, ["test-repo-1"]);
|
||||
@@ -255,6 +276,11 @@ describe("Test fetchStats", () => {
|
||||
totalStars: 200,
|
||||
totalDiscussionsStarted: 0,
|
||||
totalDiscussionsAnswered: 0,
|
||||
totalPRsAuthored: 0,
|
||||
totalPRsCommented: 0,
|
||||
totalPRsReviewed: 0,
|
||||
totalIssuesAuthored: 0,
|
||||
totalIssuesCommented: 0,
|
||||
rank,
|
||||
});
|
||||
});
|
||||
@@ -286,6 +312,11 @@ describe("Test fetchStats", () => {
|
||||
totalStars: 400,
|
||||
totalDiscussionsStarted: 0,
|
||||
totalDiscussionsAnswered: 0,
|
||||
totalPRsAuthored: 0,
|
||||
totalPRsCommented: 0,
|
||||
totalPRsReviewed: 0,
|
||||
totalIssuesAuthored: 0,
|
||||
totalIssuesCommented: 0,
|
||||
rank,
|
||||
});
|
||||
});
|
||||
@@ -317,6 +348,11 @@ describe("Test fetchStats", () => {
|
||||
totalStars: 300,
|
||||
totalDiscussionsStarted: 0,
|
||||
totalDiscussionsAnswered: 0,
|
||||
totalPRsAuthored: 0,
|
||||
totalPRsCommented: 0,
|
||||
totalPRsReviewed: 0,
|
||||
totalIssuesAuthored: 0,
|
||||
totalIssuesCommented: 0,
|
||||
rank,
|
||||
});
|
||||
});
|
||||
@@ -348,6 +384,11 @@ describe("Test fetchStats", () => {
|
||||
totalStars: 300,
|
||||
totalDiscussionsStarted: 0,
|
||||
totalDiscussionsAnswered: 0,
|
||||
totalPRsAuthored: 0,
|
||||
totalPRsCommented: 0,
|
||||
totalPRsReviewed: 0,
|
||||
totalIssuesAuthored: 0,
|
||||
totalIssuesCommented: 0,
|
||||
rank,
|
||||
});
|
||||
});
|
||||
@@ -377,6 +418,11 @@ describe("Test fetchStats", () => {
|
||||
totalStars: 300,
|
||||
totalDiscussionsStarted: 0,
|
||||
totalDiscussionsAnswered: 0,
|
||||
totalPRsAuthored: 0,
|
||||
totalPRsCommented: 0,
|
||||
totalPRsReviewed: 0,
|
||||
totalIssuesAuthored: 0,
|
||||
totalIssuesCommented: 0,
|
||||
rank,
|
||||
});
|
||||
});
|
||||
@@ -406,6 +452,11 @@ describe("Test fetchStats", () => {
|
||||
totalStars: 300,
|
||||
totalDiscussionsStarted: 10,
|
||||
totalDiscussionsAnswered: 40,
|
||||
totalPRsAuthored: 0,
|
||||
totalPRsCommented: 0,
|
||||
totalPRsReviewed: 0,
|
||||
totalIssuesAuthored: 0,
|
||||
totalIssuesCommented: 0,
|
||||
rank,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -139,17 +139,14 @@ describe("Test renderStatsCard", () => {
|
||||
|
||||
it("should render with custom width set and limit minimum width", () => {
|
||||
document.body.innerHTML = renderStatsCard(stats, { card_width: 1 });
|
||||
expect(document.querySelector("svg")).toHaveAttribute("width", "420");
|
||||
expect(document.querySelector("svg")).toHaveAttribute("width", "1");
|
||||
|
||||
// Test default minimum card width without rank circle.
|
||||
document.body.innerHTML = renderStatsCard(stats, {
|
||||
card_width: 1,
|
||||
hide_rank: true,
|
||||
});
|
||||
expect(document.querySelector("svg")).toHaveAttribute(
|
||||
"width",
|
||||
"305.81250000000006",
|
||||
);
|
||||
expect(document.querySelector("svg")).toHaveAttribute("width", "1");
|
||||
|
||||
// Test minimum card width with rank and icons.
|
||||
document.body.innerHTML = renderStatsCard(stats, {
|
||||
@@ -157,10 +154,7 @@ describe("Test renderStatsCard", () => {
|
||||
hide_rank: true,
|
||||
show_icons: true,
|
||||
});
|
||||
expect(document.querySelector("svg")).toHaveAttribute(
|
||||
"width",
|
||||
"322.81250000000006",
|
||||
);
|
||||
expect(document.querySelector("svg")).toHaveAttribute("width", "1");
|
||||
|
||||
// Test minimum card width with icons but without rank.
|
||||
document.body.innerHTML = renderStatsCard(stats, {
|
||||
@@ -168,7 +162,7 @@ describe("Test renderStatsCard", () => {
|
||||
hide_rank: false,
|
||||
show_icons: true,
|
||||
});
|
||||
expect(document.querySelector("svg")).toHaveAttribute("width", "437");
|
||||
expect(document.querySelector("svg")).toHaveAttribute("width", "1");
|
||||
|
||||
// Test minimum card width without icons or rank.
|
||||
document.body.innerHTML = renderStatsCard(stats, {
|
||||
@@ -176,7 +170,7 @@ describe("Test renderStatsCard", () => {
|
||||
hide_rank: false,
|
||||
show_icons: false,
|
||||
});
|
||||
expect(document.querySelector("svg")).toHaveAttribute("width", "420");
|
||||
expect(document.querySelector("svg")).toHaveAttribute("width", "1");
|
||||
});
|
||||
|
||||
it("should render default colors properly", () => {
|
||||
|
||||
Reference in New Issue
Block a user