diff --git a/api/index.js b/api/index.js index 2029367ca3eb9..61910c6d81627 100644 --- a/api/index.js +++ b/api/index.js @@ -20,6 +20,7 @@ export default async (req, res) => { hide_rank, show_icons, include_all_commits, + commits_year, line_height, title_color, ring_color, @@ -75,6 +76,7 @@ export default async (req, res) => { showStats.includes("prs_merged_percentage"), showStats.includes("discussions_started"), showStats.includes("discussions_answered"), + parseInt(commits_year, 10), ); let cacheSeconds = clampValue( @@ -102,6 +104,7 @@ export default async (req, res) => { card_width: parseInt(card_width, 10), hide_rank: parseBoolean(hide_rank), include_all_commits: parseBoolean(include_all_commits), + commits_year: parseInt(commits_year, 10), line_height, title_color, ring_color, diff --git a/readme.md b/readme.md index fc35f827af02c..17266e682019b 100644 --- a/readme.md +++ b/readme.md @@ -167,6 +167,14 @@ You can pass a query parameter `&show=` to show any specific additional stats wi ![Anurag's GitHub stats](https://github-readme-stats.vercel.app/api?username=anuraghazra&show=reviews,discussions_started,discussions_answered,prs_merged,prs_merged_percentage) ``` +### Showing commits count for specified year + +You can specify a year and fetch only the commits that were made in that year by passing `&commits_year=YYYY` to the parameter. + +```md +![Anurag's GitHub stats](https://github-readme-stats.vercel.app/api?username=anuraghazra&commits_year=2020) +``` + ### Showing icons To enable icons, you can pass `&show_icons=true` in the query param, like so: @@ -383,6 +391,7 @@ If we don't support your language, please consider contributing! You can find mo | `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` | +| `commits_year` | Count commits of the entire year | integer _(YYYY)_ | ` (one year to date)`. > [!NOTE]\ > When hide\_rank=`true`, the minimum card width is 270 px + the title length and padding. diff --git a/src/cards/stats-card.js b/src/cards/stats-card.js index d3b056424419d..b1215694ea785 100644 --- a/src/cards/stats-card.js +++ b/src/cards/stats-card.js @@ -187,6 +187,20 @@ const getStyles = ({ `; }; +/** + * Return the label for commits according to the selected options + * + * @param {boolean} include_all_commits Option to include all years + * @param {number|undefined} commits_year Option to include only selected year + * @returns {string} The label corresponding to the options. + */ +const getTotalCommitsYearLabel = (include_all_commits, commits_year) => + include_all_commits + ? "" + : commits_year + ? ` (${commits_year})` + : ` (last year)`; + /** * @typedef {import('../fetchers/types').StatsData} StatsData * @typedef {import('./types').StatCardOptions} StatCardOptions @@ -222,6 +236,7 @@ const renderStatsCard = (stats, options = {}) => { card_width, hide_rank = false, include_all_commits = false, + commits_year, line_height = 25, title_color, ring_color, @@ -273,9 +288,10 @@ const renderStatsCard = (stats, options = {}) => { }; STATS.commits = { icon: icons.commits, - label: `${i18n.t("statcard.commits")}${ - include_all_commits ? "" : ` (${new Date().getFullYear()})` - }`, + label: `${i18n.t("statcard.commits")}${getTotalCommitsYearLabel( + include_all_commits, + commits_year, + )}`, value: totalCommits, id: "commits", }; @@ -516,9 +532,10 @@ const renderStatsCard = (stats, options = {}) => { .filter((key) => !hide.includes(key)) .map((key) => { if (key === "commits") { - return `${i18n.t("statcard.commits")} ${ - include_all_commits ? "" : `in ${new Date().getFullYear()}` - } : ${totalStars}`; + return `${i18n.t("statcard.commits")} ${getTotalCommitsYearLabel( + include_all_commits, + commits_year, + )} : ${totalStars}`; } return `${STATS[key].label}: ${STATS[key].value}`; }) diff --git a/src/cards/types.d.ts b/src/cards/types.d.ts index 9a21be4a0160a..dbd9f0ae5ff67 100644 --- a/src/cards/types.d.ts +++ b/src/cards/types.d.ts @@ -20,6 +20,7 @@ export type StatCardOptions = CommonOptions & { card_width: number; hide_rank: boolean; include_all_commits: boolean; + commits_year: number; line_height: number | string; custom_title: string; disable_animations: boolean; diff --git a/src/fetchers/stats-fetcher.js b/src/fetchers/stats-fetcher.js index 115cd50a51564..f4d94480e93b0 100644 --- a/src/fetchers/stats-fetcher.js +++ b/src/fetchers/stats-fetcher.js @@ -40,12 +40,14 @@ const GRAPHQL_REPOS_QUERY = ` `; const GRAPHQL_STATS_QUERY = ` - query userInfo($login: String!, $after: String, $includeMergedPullRequests: Boolean!, $includeDiscussions: Boolean!, $includeDiscussionsAnswers: Boolean!) { + query userInfo($login: String!, $after: String, $includeMergedPullRequests: Boolean!, $includeDiscussions: Boolean!, $includeDiscussionsAnswers: Boolean!, $startTime: DateTime) { user(login: $login) { name login - contributionsCollection { + commits: contributionsCollection (from: $startTime) { totalCommitContributions, + } + reviews: contributionsCollection { totalPullRequestReviewContributions } repositoriesContributedTo(first: 1, contributionTypes: [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY]) { @@ -109,6 +111,7 @@ const fetcher = (variables, token) => { * @param {boolean} variables.includeMergedPullRequests Include merged pull requests. * @param {boolean} variables.includeDiscussions Include discussions. * @param {boolean} variables.includeDiscussionsAnswers Include discussions answers. + * @param {string|undefined} variables.startTime Time to start the count of total commits. * @returns {Promise} Axios response. * * @description This function supports multi-page fetching if the 'FETCH_MULTI_PAGE_STARS' environment variable is set to true. @@ -118,6 +121,7 @@ const statsFetcher = async ({ includeMergedPullRequests, includeDiscussions, includeDiscussionsAnswers, + startTime, }) => { let stats; let hasNextPage = true; @@ -130,6 +134,7 @@ const statsFetcher = async ({ includeMergedPullRequests, includeDiscussions, includeDiscussionsAnswers, + startTime, }; let res = await retryer(fetcher, variables); if (res.data.errors) { @@ -217,6 +222,7 @@ const totalCommitsFetcher = async (username) => { * @param {boolean} include_merged_pull_requests Include merged pull requests. * @param {boolean} include_discussions Include discussions. * @param {boolean} include_discussions_answers Include discussions answers. + * @param {number|undefined} commits_year Year to count total commits * @returns {Promise} Stats data. */ const fetchStats = async ( @@ -226,6 +232,7 @@ const fetchStats = async ( include_merged_pull_requests = false, include_discussions = false, include_discussions_answers = false, + commits_year, ) => { if (!username) { throw new MissingParamError(["username"]); @@ -251,6 +258,7 @@ const fetchStats = async ( includeMergedPullRequests: include_merged_pull_requests, includeDiscussions: include_discussions, includeDiscussionsAnswers: include_discussions_answers, + startTime: commits_year ? `${commits_year}-01-01T00:00:00Z` : undefined, }); // Catch GraphQL errors. @@ -282,7 +290,7 @@ const fetchStats = async ( if (include_all_commits) { stats.totalCommits = await totalCommitsFetcher(username); } else { - stats.totalCommits = user.contributionsCollection.totalCommitContributions; + stats.totalCommits = user.commits.totalCommitContributions; } stats.totalPRs = user.pullRequests.totalCount; @@ -291,8 +299,7 @@ const fetchStats = async ( stats.mergedPRsPercentage = (user.mergedPullRequests.totalCount / user.pullRequests.totalCount) * 100; } - stats.totalReviews = - user.contributionsCollection.totalPullRequestReviewContributions; + stats.totalReviews = user.reviews.totalPullRequestReviewContributions; stats.totalIssues = user.openIssues.totalCount + user.closedIssues.totalCount; if (include_discussions) { stats.totalDiscussionsStarted = user.repositoryDiscussions.totalCount; diff --git a/tests/api.test.js b/tests/api.test.js index eee9a1a0a61af..a0eb1be5f37c7 100644 --- a/tests/api.test.js +++ b/tests/api.test.js @@ -38,8 +38,10 @@ const data_stats = { user: { name: stats.name, repositoriesContributedTo: { totalCount: stats.contributedTo }, - contributionsCollection: { + commits: { totalCommitContributions: stats.totalCommits, + }, + reviews: { totalPullRequestReviewContributions: stats.totalReviews, }, pullRequests: { totalCount: stats.totalPRs }, diff --git a/tests/bench/api.bench.js b/tests/bench/api.bench.js index 4796b64306e24..f5f192d97e131 100644 --- a/tests/bench/api.bench.js +++ b/tests/bench/api.bench.js @@ -24,8 +24,10 @@ const data_stats = { user: { name: stats.name, repositoriesContributedTo: { totalCount: stats.contributedTo }, - contributionsCollection: { + commits: { totalCommitContributions: stats.totalCommits, + }, + reviews: { totalPullRequestReviewContributions: stats.totalReviews, }, pullRequests: { totalCount: stats.totalPRs }, diff --git a/tests/fetchStats.test.js b/tests/fetchStats.test.js index ca8d7bc37062e..663ee9f17e6e1 100644 --- a/tests/fetchStats.test.js +++ b/tests/fetchStats.test.js @@ -11,8 +11,10 @@ const data_stats = { user: { name: "Anurag Hazra", repositoriesContributedTo: { totalCount: 61 }, - contributionsCollection: { + commits: { totalCommitContributions: 100, + }, + reviews: { totalPullRequestReviewContributions: 50, }, pullRequests: { totalCount: 300 }, @@ -38,6 +40,9 @@ const data_stats = { }, }; +const data_year2003 = JSON.parse(JSON.stringify(data_stats)); +data_year2003.data.user.commits.totalCommitContributions = 428; + const data_repo = { data: { user: { @@ -91,9 +96,18 @@ const mock = new MockAdapter(axios); beforeEach(() => { process.env.FETCH_MULTI_PAGE_STARS = "false"; // Set to `false` to fetch only one page of stars. mock.onPost("https://api.github.com/graphql").reply((cfg) => { + let req = JSON.parse(cfg.data); + + if ( + req.variables && + req.variables.startTime && + req.variables.startTime.startsWith("2003") + ) { + return [200, data_year2003]; + } return [ 200, - cfg.data.includes("contributionsCollection") ? data_stats : data_repo, + req.query.includes("totalCommitContributions") ? data_stats : data_repo, ]; }); }); @@ -409,4 +423,42 @@ describe("Test fetchStats", () => { rank, }); }); + + it("should get commits of provided year", async () => { + let stats = await fetchStats( + "anuraghazra", + false, + [], + false, + false, + false, + 2003, + ); + + const rank = calculateRank({ + all_commits: false, + commits: 428, + prs: 300, + reviews: 50, + issues: 200, + repos: 5, + stars: 300, + followers: 100, + }); + + expect(stats).toStrictEqual({ + contributedTo: 61, + name: "Anurag Hazra", + totalCommits: 428, + totalIssues: 200, + totalPRs: 300, + totalPRsMerged: 0, + mergedPRsPercentage: 0, + totalReviews: 50, + totalStars: 300, + totalDiscussionsStarted: 0, + totalDiscussionsAnswered: 0, + rank, + }); + }); }); diff --git a/tests/renderStatsCard.test.js b/tests/renderStatsCard.test.js index 973ee0a5a5db6..b83286a4a029d 100644 --- a/tests/renderStatsCard.test.js +++ b/tests/renderStatsCard.test.js @@ -387,9 +387,7 @@ describe("Test renderStatsCard", () => { document.querySelector( 'g[transform="translate(0, 25)"]>.stagger>.stat.bold', ).textContent, - ).toMatchInlineSnapshot( - `"累计提交数(commit) (${new Date().getFullYear()}):"`, - ); + ).toMatchInlineSnapshot(`"累计提交数(commit) (last year):"`); expect( document.querySelector( 'g[transform="translate(0, 50)"]>.stagger>.stat.bold',