From 077d40561a4eb74ab56a81942155985a7579103a Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Sat, 28 Jan 2023 15:22:02 +0100 Subject: [PATCH 01/82] feat: add PAT monitoring functions (#2178) * feat: add PAT monitoring functions This commit adds two monitoring functions that can be used to check whether the PATs are functioning correctly: - status/up: Returns whether the PATs are rate limited. - status/pat-info: Returns information about the PATs. * feat: add shields.io dynamic badge json response This commit adds the ability to set the return format of the `/api/status/up` cloud function. When this format is set to `shields` a dynamic shields.io badge json is returned. * feat: add 'json' type to up monitor * feat: cleanup status functions * ci: decrease pat-info rate limiting time * feat: decrease monitoring functions rate limits * refactor: pat code * feat: add PAT monitoring functions This commit adds two monitoring functions that can be used to check whether the PATs are functioning correctly: - status/up: Returns whether the PATs are rate limited. - status/pat-info: Returns information about the PATs. * feat: add shields.io dynamic badge json response This commit adds the ability to set the return format of the `/api/status/up` cloud function. When this format is set to `shields` a dynamic shields.io badge json is returned. * feat: add 'json' type to up monitor * feat: cleanup status functions * ci: decrease pat-info rate limiting time * feat: decrease monitoring functions rate limits * refactor: pat code * test: fix pat-info tests * Update api/status/pat-info.js Co-authored-by: Anurag Hazra * test: fix broken tests * chore: fix suspended account * chore: simplify and refactor * chore: fix test * chore: add resetIn field --------- Co-authored-by: Anurag --- api/status/pat-info.js | 131 ++++++++++++++++++++++ api/status/up.js | 103 +++++++++++++++++ package-lock.json | 2 +- package.json | 2 +- src/common/utils.js | 14 +++ tests/pat-info.test.js | 241 ++++++++++++++++++++++++++++++++++++++++ tests/status.up.test.js | 194 ++++++++++++++++++++++++++++++++ 7 files changed, 685 insertions(+), 2 deletions(-) create mode 100644 api/status/pat-info.js create mode 100644 api/status/up.js create mode 100644 tests/pat-info.test.js create mode 100644 tests/status.up.test.js diff --git a/api/status/pat-info.js b/api/status/pat-info.js new file mode 100644 index 00000000000000..720611d4249192 --- /dev/null +++ b/api/status/pat-info.js @@ -0,0 +1,131 @@ +/** + * @file Contains a simple cloud function that can be used to check which PATs are no + * longer working. It returns a list of valid PATs, expired PATs and PATs with errors. + * + * @description This function is currently rate limited to 1 request per 10 minutes. + */ + +import { logger, request, dateDiff } from "../../src/common/utils.js"; +export const RATE_LIMIT_SECONDS = 60 * 10; // 1 request per 10 minutes + +/** + * Simple uptime check fetcher for the PATs. + * + * @param {import('axios').AxiosRequestHeaders} variables + * @param {string} token + */ +const uptimeFetcher = (variables, token) => { + return request( + { + query: ` + query { + rateLimit { + remaining + resetAt + }, + }`, + variables, + }, + { + Authorization: `bearer ${token}`, + }, + ); +}; + +const getAllPATs = () => { + return Object.keys(process.env).filter((key) => /PAT_\d*$/.exec(key)); +}; + +/** + * Check whether any of the PATs is expired. + */ +const getPATInfo = async (fetcher, variables) => { + const details = {}; + const PATs = getAllPATs(); + + for (const pat of PATs) { + try { + const response = await fetcher(variables, process.env[pat]); + const errors = response.data.errors; + const hasErrors = Boolean(errors); + const errorType = errors?.[0]?.type; + const isRateLimited = + (hasErrors && errorType === "RATE_LIMITED") || + response.data.data?.rateLimit?.remaining === 0; + + // Store PATs with errors. + if (hasErrors && errorType !== "RATE_LIMITED") { + details[pat] = { + status: "error", + error: { + type: errors[0].type, + message: errors[0].message, + }, + }; + continue; + } else if (isRateLimited) { + const date1 = new Date(); + const date2 = new Date(response.data?.data?.rateLimit?.resetAt); + details[pat] = { + status: "exhausted", + remaining: 0, + resetIn: dateDiff(date2, date1) + " minutes", + }; + } else { + details[pat] = { + status: "valid", + remaining: response.data.data.rateLimit.remaining, + }; + } + } catch (err) { + // Store the PAT if it is expired. + const errorMessage = err.response?.data?.message?.toLowerCase(); + if (errorMessage === "bad credentials") { + details[pat] = { + status: "expired", + }; + } else if (errorMessage === "sorry. your account was suspended.") { + details[pat] = { + status: "suspended", + }; + } else { + throw err; + } + } + } + + const filterPATsByStatus = (status) => { + return Object.keys(details).filter((pat) => details[pat].status === status); + }; + + return { + validPATs: filterPATsByStatus("valid"), + expiredPATs: filterPATsByStatus("expired"), + exhaustedPATS: filterPATsByStatus("exhausted"), + errorPATs: filterPATsByStatus("error"), + details, + }; +}; + +/** + * Cloud function that returns information about the used PATs. + */ +export default async (_, res) => { + res.setHeader("Content-Type", "application/json"); + try { + // Add header to prevent abuse. + const PATsInfo = await getPATInfo(uptimeFetcher, {}); + if (PATsInfo) { + res.setHeader( + "Cache-Control", + `max-age=0, s-maxage=${RATE_LIMIT_SECONDS}`, + ); + } + res.send(JSON.stringify(PATsInfo, null, 2)); + } catch (err) { + // Throw error if something went wrong. + logger.error(err); + res.setHeader("Cache-Control", "no-store"); + res.send("Something went wrong: " + err.message); + } +}; diff --git a/api/status/up.js b/api/status/up.js new file mode 100644 index 00000000000000..33fe8f900c3958 --- /dev/null +++ b/api/status/up.js @@ -0,0 +1,103 @@ +/** + * @file Contains a simple cloud function that can be used to check if the PATs are still + * functional. + * + * @description This function is currently rate limited to 1 request per 10 minutes. + */ + +import retryer from "../../src/common/retryer.js"; +import { logger, request } from "../../src/common/utils.js"; + +export const RATE_LIMIT_SECONDS = 60 * 10; // 1 request per 10 minutes + +/** + * Simple uptime check fetcher for the PATs. + * + * @param {import('axios').AxiosRequestHeaders} variables + * @param {string} token + */ +const uptimeFetcher = (variables, token) => { + return request( + { + query: ` + query { + rateLimit { + remaining + } + } + `, + variables, + }, + { + Authorization: `bearer ${token}`, + }, + ); +}; + +/** + * Creates Json response that can be used for shields.io dynamic card generation. + * + * @param {*} up Whether the PATs are up or not. + * @returns Dynamic shields.io JSON response object. + * + * @see https://shields.io/endpoint. + */ +const shieldsUptimeBadge = (up) => { + const schemaVersion = 1; + const isError = true; + const label = "Public Instance"; + const message = up ? "up" : "down"; + const color = up ? "brightgreen" : "red"; + return { + schemaVersion, + label, + message, + color, + isError, + }; +}; + +/** + * Cloud function that returns whether the PATs are still functional. + */ +export default async (req, res) => { + let { type } = req.query; + type = type ? type.toLowerCase() : "boolean"; + + res.setHeader("Content-Type", "application/json"); + + try { + let PATsValid = true; + try { + await retryer(uptimeFetcher, {}); + } catch (err) { + PATsValid = false; + } + + if (PATsValid) { + res.setHeader( + "Cache-Control", + `max-age=0, s-maxage=${RATE_LIMIT_SECONDS}`, + ); + } else { + res.setHeader("Cache-Control", "no-store"); + } + + switch (type) { + case "shields": + res.send(shieldsUptimeBadge(PATsValid)); + break; + case "json": + res.send({ up: PATsValid }); + break; + default: + res.send(PATsValid); + break; + } + } catch (err) { + // Return fail boolean if something went wrong. + logger.error(err); + res.setHeader("Cache-Control", "no-store"); + res.send("Something went wrong: " + err.message); + } +}; diff --git a/package-lock.json b/package-lock.json index 048c316bfde58f..ebc7570a419236 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "@testing-library/dom": "^8.17.1", "@testing-library/jest-dom": "^5.16.5", "@uppercod/css-to-object": "^1.1.1", - "axios-mock-adapter": "^1.18.1", + "axios-mock-adapter": "^1.21.2", "color-contrast-checker": "^2.1.0", "hjson": "^3.2.2", "husky": "^8.0.0", diff --git a/package.json b/package.json index 95b1a11dad93f3..606d4f5440a242 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "@testing-library/dom": "^8.17.1", "@testing-library/jest-dom": "^5.16.5", "@uppercod/css-to-object": "^1.1.1", - "axios-mock-adapter": "^1.18.1", + "axios-mock-adapter": "^1.21.2", "color-contrast-checker": "^2.1.0", "hjson": "^3.2.2", "husky": "^8.0.0", diff --git a/src/common/utils.js b/src/common/utils.js index 1215fc9ac8cc21..c600c717ae3e52 100644 --- a/src/common/utils.js +++ b/src/common/utils.js @@ -424,6 +424,19 @@ const parseEmojis = (str) => { }); }; +/** + * Get diff in minutes + * @param {Date} d1 + * @param {Date} d2 + * @returns {number} + */ +const dateDiff = (d1, d2) => { + const date1 = new Date(d1); + const date2 = new Date(d2); + const diff = date1.getTime() - date2.getTime(); + return Math.round(diff / (1000 * 60)); +}; + export { ERROR_CARD_LENGTH, renderError, @@ -447,4 +460,5 @@ export { lowercaseTrim, chunkArray, parseEmojis, + dateDiff, }; diff --git a/tests/pat-info.test.js b/tests/pat-info.test.js new file mode 100644 index 00000000000000..9635ab24c837c4 --- /dev/null +++ b/tests/pat-info.test.js @@ -0,0 +1,241 @@ +/** + * @file Tests for the status/pat-info cloud function. + */ +import dotenv from "dotenv"; +dotenv.config(); + +import { jest } from "@jest/globals"; +import axios from "axios"; +import MockAdapter from "axios-mock-adapter"; +import patInfo, { RATE_LIMIT_SECONDS } from "../api/status/pat-info.js"; + +const mock = new MockAdapter(axios); + +const successData = { + data: { + rateLimit: { + remaining: 4986, + }, + }, +}; + +const faker = (query) => { + const req = { + query: { ...query }, + }; + const res = { + setHeader: jest.fn(), + send: jest.fn(), + }; + + return { req, res }; +}; + +const rate_limit_error = { + errors: [ + { + type: "RATE_LIMITED", + message: "API rate limit exceeded for user ID.", + }, + ], + data: { + rateLimit: { + resetAt: Date.now(), + }, + }, +}; + +const other_error = { + errors: [ + { + type: "SOME_ERROR", + message: "This is a error", + }, + ], +}; + +const bad_credentials_error = { + message: "Bad credentials", +}; + +afterEach(() => { + mock.reset(); +}); + +describe("Test /api/status/pat-info", () => { + beforeAll(() => { + // reset patenv first so that dotenv doesn't populate them with local envs + process.env = {}; + process.env.PAT_1 = "testPAT1"; + process.env.PAT_2 = "testPAT2"; + process.env.PAT_3 = "testPAT3"; + process.env.PAT_4 = "testPAT4"; + }); + + it("should return only 'validPATs' if all PATs are valid", async () => { + mock + .onPost("https://api.github.com/graphql") + .replyOnce(200, rate_limit_error) + .onPost("https://api.github.com/graphql") + .reply(200, successData); + + const { req, res } = faker({}, {}); + await patInfo(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "application/json"); + expect(res.send).toBeCalledWith( + JSON.stringify( + { + validPATs: ["PAT_2", "PAT_3", "PAT_4"], + expiredPATs: [], + exhaustedPATS: ["PAT_1"], + errorPATs: [], + details: { + PAT_1: { + status: "exhausted", + remaining: 0, + resetIn: "0 minutes", + }, + PAT_2: { + status: "valid", + remaining: 4986, + }, + PAT_3: { + status: "valid", + remaining: 4986, + }, + PAT_4: { + status: "valid", + remaining: 4986, + }, + }, + }, + null, + 2, + ), + ); + }); + + it("should return `errorPATs` if a PAT causes an error to be thrown", async () => { + mock + .onPost("https://api.github.com/graphql") + .replyOnce(200, other_error) + .onPost("https://api.github.com/graphql") + .reply(200, successData); + + const { req, res } = faker({}, {}); + await patInfo(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "application/json"); + expect(res.send).toBeCalledWith( + JSON.stringify( + { + validPATs: ["PAT_2", "PAT_3", "PAT_4"], + expiredPATs: [], + exhaustedPATS: [], + errorPATs: ["PAT_1"], + details: { + PAT_1: { + status: "error", + error: { + type: "SOME_ERROR", + message: "This is a error", + }, + }, + PAT_2: { + status: "valid", + remaining: 4986, + }, + PAT_3: { + status: "valid", + remaining: 4986, + }, + PAT_4: { + status: "valid", + remaining: 4986, + }, + }, + }, + null, + 2, + ), + ); + }); + + it("should return `expiredPaths` if a PAT returns a 'Bad credentials' error", async () => { + mock + .onPost("https://api.github.com/graphql") + .replyOnce(404, bad_credentials_error) + .onPost("https://api.github.com/graphql") + .reply(200, successData); + + const { req, res } = faker({}, {}); + await patInfo(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "application/json"); + expect(res.send).toBeCalledWith( + JSON.stringify( + { + validPATs: ["PAT_2", "PAT_3", "PAT_4"], + expiredPATs: ["PAT_1"], + exhaustedPATS: [], + errorPATs: [], + details: { + PAT_1: { + status: "expired", + }, + PAT_2: { + status: "valid", + remaining: 4986, + }, + PAT_3: { + status: "valid", + remaining: 4986, + }, + PAT_4: { + status: "valid", + remaining: 4986, + }, + }, + }, + null, + 2, + ), + ); + }); + + it("should throw an error if something goes wrong", async () => { + mock.onPost("https://api.github.com/graphql").networkError(); + + const { req, res } = faker({}, {}); + await patInfo(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "application/json"); + expect(res.send).toBeCalledWith("Something went wrong: Network Error"); + }); + + it("should have proper cache when no error is thrown", async () => { + mock.onPost("https://api.github.com/graphql").reply(200, successData); + + const { req, res } = faker({}, {}); + await patInfo(req, res); + + expect(res.setHeader.mock.calls).toEqual([ + ["Content-Type", "application/json"], + ["Cache-Control", `max-age=0, s-maxage=${RATE_LIMIT_SECONDS}`], + ]); + }); + + it("should have proper cache when error is thrown", async () => { + mock.reset(); + mock.onPost("https://api.github.com/graphql").networkError(); + + const { req, res } = faker({}, {}); + await patInfo(req, res); + + expect(res.setHeader.mock.calls).toEqual([ + ["Content-Type", "application/json"], + ["Cache-Control", "no-store"], + ]); + }); +}); diff --git a/tests/status.up.test.js b/tests/status.up.test.js new file mode 100644 index 00000000000000..7cf0144b7112dc --- /dev/null +++ b/tests/status.up.test.js @@ -0,0 +1,194 @@ +/** + * @file Tests for the status/up cloud function. + */ +import { jest } from "@jest/globals"; +import axios from "axios"; +import MockAdapter from "axios-mock-adapter"; +import up, { RATE_LIMIT_SECONDS } from "../api/status/up.js"; + +const mock = new MockAdapter(axios); + +const successData = { + rateLimit: { + remaining: 4986, + }, +}; + +const faker = (query) => { + const req = { + query: { ...query }, + }; + const res = { + setHeader: jest.fn(), + send: jest.fn(), + }; + + return { req, res }; +}; + +const rate_limit_error = { + errors: [ + { + type: "RATE_LIMITED", + }, + ], +}; + +const bad_credentials_error = { + message: "Bad credentials", +}; + +const shields_up = { + schemaVersion: 1, + label: "Public Instance", + isError: true, + message: "up", + color: "brightgreen", +}; +const shields_down = { + schemaVersion: 1, + label: "Public Instance", + isError: true, + message: "down", + color: "red", +}; + +afterEach(() => { + mock.reset(); +}); + +describe("Test /api/status/up", () => { + it("should return `true` if request was successful", async () => { + mock.onPost("https://api.github.com/graphql").replyOnce(200, successData); + + const { req, res } = faker({}, {}); + await up(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "application/json"); + expect(res.send).toBeCalledWith(true); + }); + + it("should return `false` if all PATs are rate limited", async () => { + mock.onPost("https://api.github.com/graphql").reply(200, rate_limit_error); + + const { req, res } = faker({}, {}); + await up(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "application/json"); + expect(res.send).toBeCalledWith(false); + }); + + it("should return JSON `true` if request was successful and type='json'", async () => { + mock.onPost("https://api.github.com/graphql").replyOnce(200, successData); + + const { req, res } = faker({ type: "json" }, {}); + await up(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "application/json"); + expect(res.send).toBeCalledWith({ up: true }); + }); + + it("should return JSON `false` if all PATs are rate limited and type='json'", async () => { + mock.onPost("https://api.github.com/graphql").reply(200, rate_limit_error); + + const { req, res } = faker({ type: "json" }, {}); + await up(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "application/json"); + expect(res.send).toBeCalledWith({ up: false }); + }); + + it("should return UP shields.io config if request was successful and type='shields'", async () => { + mock.onPost("https://api.github.com/graphql").replyOnce(200, successData); + + const { req, res } = faker({ type: "shields" }, {}); + await up(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "application/json"); + expect(res.send).toBeCalledWith(shields_up); + }); + + it("should return DOWN shields.io config if all PATs are rate limited and type='shields'", async () => { + mock.onPost("https://api.github.com/graphql").reply(200, rate_limit_error); + + const { req, res } = faker({ type: "shields" }, {}); + await up(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "application/json"); + expect(res.send).toBeCalledWith(shields_down); + }); + + it("should return `true` if the first PAT is rate limited but the second PATs works", async () => { + mock + .onPost("https://api.github.com/graphql") + .replyOnce(200, rate_limit_error) + .onPost("https://api.github.com/graphql") + .replyOnce(200, successData); + + const { req, res } = faker({}, {}); + await up(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "application/json"); + expect(res.send).toBeCalledWith(true); + }); + + it("should return `true` if the first PAT has 'Bad credentials' but the second PAT works", async () => { + mock + .onPost("https://api.github.com/graphql") + .replyOnce(404, bad_credentials_error) + .onPost("https://api.github.com/graphql") + .replyOnce(200, successData); + + const { req, res } = faker({}, {}); + await up(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "application/json"); + expect(res.send).toBeCalledWith(true); + }); + + it("should return `false` if all pats have 'Bad credentials'", async () => { + mock + .onPost("https://api.github.com/graphql") + .reply(404, bad_credentials_error); + + const { req, res } = faker({}, {}); + await up(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "application/json"); + expect(res.send).toBeCalledWith(false); + }); + + it("should throw an error if the request fails", async () => { + mock.onPost("https://api.github.com/graphql").networkError(); + + const { req, res } = faker({}, {}); + await up(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "application/json"); + expect(res.send).toBeCalledWith(false); + }); + + it("should have proper cache when no error is thrown", async () => { + mock.onPost("https://api.github.com/graphql").replyOnce(200, successData); + + const { req, res } = faker({}, {}); + await up(req, res); + + expect(res.setHeader.mock.calls).toEqual([ + ["Content-Type", "application/json"], + ["Cache-Control", `max-age=0, s-maxage=${RATE_LIMIT_SECONDS}`], + ]); + }); + + it("should have proper cache when error is thrown", async () => { + mock.onPost("https://api.github.com/graphql").networkError(); + + const { req, res } = faker({}, {}); + await up(req, res); + + expect(res.setHeader.mock.calls).toEqual([ + ["Content-Type", "application/json"], + ["Cache-Control", "no-store"], + ]); + }); +}); From 7aa502d45377c73f89c6d1096c29cd862188aa2e Mon Sep 17 00:00:00 2001 From: Anurag Hazra Date: Sat, 28 Jan 2023 20:32:40 +0530 Subject: [PATCH 02/82] chore: minor changes in pat info (#2481) --- api/status/pat-info.js | 14 +++++++++++--- tests/pat-info.test.js | 9 ++++++--- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/api/status/pat-info.js b/api/status/pat-info.js index 720611d4249192..775e06896427cc 100644 --- a/api/status/pat-info.js +++ b/api/status/pat-info.js @@ -6,7 +6,7 @@ */ import { logger, request, dateDiff } from "../../src/common/utils.js"; -export const RATE_LIMIT_SECONDS = 60 * 10; // 1 request per 10 minutes +export const RATE_LIMIT_SECONDS = 60 * 5; // 1 request per 10 minutes /** * Simple uptime check fetcher for the PATs. @@ -98,12 +98,20 @@ const getPATInfo = async (fetcher, variables) => { return Object.keys(details).filter((pat) => details[pat].status === status); }; + const sortedDetails = Object.keys(details) + .sort() + .reduce((obj, key) => { + obj[key] = details[key]; + return obj; + }, {}); + return { validPATs: filterPATsByStatus("valid"), expiredPATs: filterPATsByStatus("expired"), - exhaustedPATS: filterPATsByStatus("exhausted"), + exhaustedPATs: filterPATsByStatus("exhausted"), + suspendedPATs: filterPATsByStatus("suspended"), errorPATs: filterPATsByStatus("error"), - details, + details: sortedDetails, }; }; diff --git a/tests/pat-info.test.js b/tests/pat-info.test.js index 9635ab24c837c4..23aca8c40e5ca3 100644 --- a/tests/pat-info.test.js +++ b/tests/pat-info.test.js @@ -88,7 +88,8 @@ describe("Test /api/status/pat-info", () => { { validPATs: ["PAT_2", "PAT_3", "PAT_4"], expiredPATs: [], - exhaustedPATS: ["PAT_1"], + exhaustedPATs: ["PAT_1"], + suspendedPATs: [], errorPATs: [], details: { PAT_1: { @@ -132,7 +133,8 @@ describe("Test /api/status/pat-info", () => { { validPATs: ["PAT_2", "PAT_3", "PAT_4"], expiredPATs: [], - exhaustedPATS: [], + exhaustedPATs: [], + suspendedPATs: [], errorPATs: ["PAT_1"], details: { PAT_1: { @@ -178,7 +180,8 @@ describe("Test /api/status/pat-info", () => { { validPATs: ["PAT_2", "PAT_3", "PAT_4"], expiredPATs: ["PAT_1"], - exhaustedPATS: [], + exhaustedPATs: [], + suspendedPATs: [], errorPATs: [], details: { PAT_1: { From 112000667c01f18fd161f204ae3ee796ec2e3011 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Sun, 29 Jan 2023 15:19:01 +0100 Subject: [PATCH 03/82] ci: add update languages action (#2484) * ci: add update languages action * ci: make sure PR is created when upstream languages are updated --- .github/workflows/update-langs.yaml | 44 +++++++++++++++++++++++++++++ src/common/languageColors.json | 18 ++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 .github/workflows/update-langs.yaml diff --git a/.github/workflows/update-langs.yaml b/.github/workflows/update-langs.yaml new file mode 100644 index 00000000000000..ad6bfb6213b8f7 --- /dev/null +++ b/.github/workflows/update-langs.yaml @@ -0,0 +1,44 @@ +name: Update supported languages +on: + schedule: + - cron: "0 0 */30 * *" + +jobs: + updateLanguages: + if: github.repository == 'anuraghazra/github-readme-stats' + name: Update supported languages + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [16.x] + + steps: + - uses: actions/checkout@v3 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: npm + + - name: Install dependencies + run: npm ci + env: + CI: true + + - name: Run update-languages-json.js script + run: npm run generate-langs-json + + - name: Create Pull Request if upstream language file is changed + uses: peter-evans/create-pull-request@v4 + with: + commit-message: "refactor: update languages JSON" + branch: "update_langs/patch" + delete-branch: true + title: Update languages JSON + body: + "The + [update-langs](https://github.com/anuraghazra/github-readme-stats/actions/workflows/update-langs.yaml) + action found new/updated languages in the [upstream languages JSON + file](https://raw.githubusercontent.com/github/linguist/master/lib/linguist/languages.yml)." + labels: "ci, lang-card" diff --git a/src/common/languageColors.json b/src/common/languageColors.json index 7e8cd551264b8a..47bfb1cfa3435d 100644 --- a/src/common/languageColors.json +++ b/src/common/languageColors.json @@ -102,6 +102,7 @@ "Csound Score": "#1a1a1a", "Cuda": "#3A4E3A", "Curry": "#531242", + "Cypher": "#34c0eb", "Cython": "#fedf5b", "D": "#ba595e", "DM": "#447265", @@ -124,6 +125,7 @@ "Earthly": "#2af0ff", "Easybuild": "#069406", "Ecere Projects": "#913960", + "Ecmarkup": "#eb8131", "EditorConfig": "#fff1f2", "Eiffel": "#4d6977", "Elixir": "#6e4a7e", @@ -215,6 +217,7 @@ "Idris": "#b30000", "Ignore List": "#000000", "ImageJ Macro": "#99AAFF", + "Imba": "#16cec6", "Inno Setup": "#264b99", "Io": "#a9188d", "Ioke": "#078193", @@ -286,6 +289,7 @@ "Mathematica": "#dd1100", "Max": "#c4a79c", "Mercury": "#ff2b2b", + "Mermaid": "#ff3670", "Meson": "#007800", "Metal": "#8f14e9", "MiniYAML": "#ff1111", @@ -318,6 +322,10 @@ "Nu": "#c9df40", "NumPy": "#9C8AF9", "Nunjucks": "#3d8137", + "OASv2-json": "#85ea2d", + "OASv2-yaml": "#85ea2d", + "OASv3-json": "#85ea2d", + "OASv3-yaml": "#85ea2d", "OCaml": "#3be133", "ObjectScript": "#424893", "Objective-C": "#438eff", @@ -327,14 +335,18 @@ "Omgrofl": "#cabbff", "Opal": "#f7ede0", "Open Policy Agent": "#7d9199", + "OpenAPI Specification v2": "#85ea2d", + "OpenAPI Specification v3": "#85ea2d", "OpenCL": "#ed2e2d", "OpenEdge ABL": "#5ce600", "OpenQASM": "#AA70FF", "OpenSCAD": "#e5cd45", + "Option List": "#476732", "Org": "#77aa99", "Oxygene": "#cdd0e3", "Oz": "#fab738", "P4": "#7055b5", + "PDDL": "#0d00ff", "PEG.js": "#234d6b", "PHP": "#4F5D95", "PLSQL": "#dad8d8", @@ -351,6 +363,7 @@ "PigLatin": "#fcd7de", "Pike": "#005390", "PogoScript": "#d80074", + "Polar": "#ae81ff", "Portugol": "#f8bd00", "PostCSS": "#dc3a0c", "PostScript": "#da291c", @@ -414,6 +427,7 @@ "Sass": "#a53b70", "Scala": "#c22d40", "Scaml": "#bd181a", + "Scenic": "#fdc700", "Scheme": "#1e4aec", "Scilab": "#ca0f21", "Self": "#0579aa", @@ -421,6 +435,7 @@ "Shell": "#89e051", "ShellCheck Config": "#cecfcb", "Shen": "#120F14", + "Simple File Verification": "#C9BFED", "Singularity": "#64E6AD", "Slash": "#007eff", "Slice": "#003fa2", @@ -428,6 +443,7 @@ "SmPL": "#c94949", "Smalltalk": "#596706", "Smarty": "#f0c040", + "Smithy": "#c44536", "Solidity": "#AA6746", "SourcePawn": "#f69e1d", "Squirrel": "#800000", @@ -478,6 +494,7 @@ "Vim Script": "#199f4b", "Vim Snippet": "#199f4b", "Visual Basic .NET": "#945db7", + "Visual Basic 6.0": "#2c6353", "Volt": "#1F1F1F", "Vue": "#41b883", "Vyper": "#2980b9", @@ -514,6 +531,7 @@ "fish": "#4aae47", "hoon": "#00b171", "jq": "#c7254e", + "just": "#384d54", "kvlang": "#1da6e0", "mIRC Script": "#3d57c3", "mcfunction": "#E22837", From 888663a47728a4e82a78dcc6fd95ce53bdaffc70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EB=AF=BC=EC=A7=80?= <68285922+Meezzi@users.noreply.github.com> Date: Tue, 14 Feb 2023 09:50:12 +0900 Subject: [PATCH 04/82] Add `rose` theme (#2480) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 강민지 <68285922+Kminzzi@users.noreply.github.com> --- themes/index.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/themes/index.js b/themes/index.js index a5d3abae8cb6f0..60825d132a1191 100644 --- a/themes/index.js +++ b/themes/index.js @@ -374,6 +374,13 @@ export const themes = { border_color: "170F0C", bg_color: "170F0C", }, + rose: { + title_color: "8d192b", + text_color: "862931", + icon_color: "B71F36", + border_color: "e9d8d4", + bg_color: "e9d8d4", + }, }; export default themes; From ba7c2f8b55eac8452e479c8bd38b044d204d0424 Mon Sep 17 00:00:00 2001 From: Amir Date: Thu, 16 Feb 2023 04:53:11 +0330 Subject: [PATCH 05/82] Support hide_progress for top-langs feature (#2514) * Add support for hide_progress in top languages feature * Fix mistake * Add documents for all languages * Remove unnecessary value check * Update top-languages-card.js * Revert document for all languages except English * Update documentation * Update documentation --------- Co-authored-by: Zohan Subhash --- api/top-langs.js | 2 ++ readme.md | 13 +++++++++++ src/cards/top-languages-card.js | 39 +++++++++++++++++++++++---------- src/cards/types.d.ts | 1 + 4 files changed, 44 insertions(+), 11 deletions(-) diff --git a/api/top-langs.js b/api/top-langs.js index 19cccb894e33a9..e67d9533234417 100644 --- a/api/top-langs.js +++ b/api/top-langs.js @@ -30,6 +30,7 @@ export default async (req, res) => { border_radius, border_color, disable_animations, + hide_progress, } = req.query; res.setHeader("Content-Type", "image/svg+xml"); @@ -77,6 +78,7 @@ export default async (req, res) => { border_color, locale: locale ? locale.toLowerCase() : null, disable_animations: parseBoolean(disable_animations), + hide_progress: parseBoolean(hide_progress), }), ); } catch (err) { diff --git a/readme.md b/readme.md index 678c5c0b14af48..29852bf9c65159 100644 --- a/readme.md +++ b/readme.md @@ -305,6 +305,7 @@ You can provide multiple comma-separated values in the bg_color option to render - `exclude_repo` - Exclude specified repositories _(Comma-separated values)_. Default: `[] (blank array)`. - `custom_title` - Sets a custom title for the card _(string)_. Default `Most Used Languages`. - `disable_animations` - Disables all animations in the card _(boolean)_. Default: `false`. +- `hide_progress` - It uses the compact layout option, hides percentages, and removes the bars. Default: `false`. > **Warning** > Language names should be URI-escaped, as specified in [Percent Encoding](https://en.wikipedia.org/wiki/Percent-encoding) @@ -398,6 +399,14 @@ You can use the `&layout=compact` option to change the card design. [![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=compact)](https://github.com/anuraghazra/github-readme-stats) ``` +### Hide Progress Bars + +You can use the `&hide_progress=true` option to hide the percentages and the progress bars (layout will be automatically set to `compact`). + +```md +[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) +``` + ### Demo [![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra)](https://github.com/anuraghazra/github-readme-stats) @@ -406,6 +415,10 @@ You can use the `&layout=compact` option to change the card design. [![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=compact)](https://github.com/anuraghazra/github-readme-stats) +- Hidden progress bars + +[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) + # Wakatime Week Stats Change the `?username=` value to your [Wakatime](https://wakatime.com) username. diff --git a/src/cards/top-languages-card.js b/src/cards/top-languages-card.js index 9396ff8e73d5ef..be1328c0c8fe3c 100644 --- a/src/cards/top-languages-card.js +++ b/src/cards/top-languages-card.js @@ -76,10 +76,11 @@ const createProgressTextNode = ({ width, color, name, progress, index }) => { * @param {object} props Function properties. * @param {Lang} props.lang Programming language object. * @param {number} props.totalSize Total size of all languages. + * @param {boolean} props.hideProgress Whether to hide percentage. * @param {number} props.index Index of the programming language. * @returns {string} Compact layout programming language SVG node. */ -const createCompactLangNode = ({ lang, totalSize, index }) => { +const createCompactLangNode = ({ lang, totalSize, hideProgress, index }) => { const percentage = ((lang.size / totalSize) * 100).toFixed(2); const staggerDelay = (index + 3) * 150; const color = lang.color || "#858585"; @@ -88,7 +89,7 @@ const createCompactLangNode = ({ lang, totalSize, index }) => { - ${lang.name} ${percentage}% + ${lang.name} ${hideProgress ? "" : percentage + "%"} `; @@ -100,9 +101,10 @@ const createCompactLangNode = ({ lang, totalSize, index }) => { * @param {object[]} props Function properties. * @param {Lang[]} props.langs Array of programming languages. * @param {number} props.totalSize Total size of all languages. + * @param {boolean} props.hideProgress Whether to hide percentage. * @returns {string} Programming languages SVG node. */ -const createLanguageTextNode = ({ langs, totalSize }) => { +const createLanguageTextNode = ({ langs, totalSize, hideProgress }) => { const longestLang = getLongestLang(langs); const chunked = chunkArray(langs, langs.length / 2); const layouts = chunked.map((array) => { @@ -111,6 +113,7 @@ const createLanguageTextNode = ({ langs, totalSize }) => { createCompactLangNode({ lang, totalSize, + hideProgress, index, }), ); @@ -160,9 +163,10 @@ const renderNormalLayout = (langs, width, totalLanguageSize) => { * @param {Lang[]} langs Array of programming languages. * @param {number} width Card width. * @param {number} totalLanguageSize Total size of all languages. + * @param {boolean} hideProgress Whether to hide progress bar. * @returns {string} Compact layout card SVG object. */ -const renderCompactLayout = (langs, width, totalLanguageSize) => { +const renderCompactLayout = (langs, width, totalLanguageSize, hideProgress) => { const paddingRight = 50; const offsetWidth = width - paddingRight; // progressOffset holds the previous language's width and used to offset the next language @@ -193,15 +197,21 @@ const renderCompactLayout = (langs, width, totalLanguageSize) => { .join(""); return ` - + ${ + !hideProgress + ? ` + ${compactProgressBar} - - + ` + : "" + } + ${createLanguageTextNode({ langs, totalSize: totalLanguageSize, + hideProgress: hideProgress, })} `; @@ -276,6 +286,7 @@ const renderTopLanguages = (topLangs, options = {}) => { text_color, bg_color, hide, + hide_progress, theme, layout, custom_title, @@ -305,11 +316,17 @@ const renderTopLanguages = (topLangs, options = {}) => { let height = calculateNormalLayoutHeight(langs.length); let finalLayout = ""; - if (layout === "compact") { + if (layout === "compact" || hide_progress == true) { width = width + 50; // padding - height = calculateCompactLayoutHeight(langs.length); - - finalLayout = renderCompactLayout(langs, width, totalLanguageSize); + height = + calculateCompactLayoutHeight(langs.length) + (hide_progress ? -25 : 0); + + finalLayout = renderCompactLayout( + langs, + width, + totalLanguageSize, + hide_progress, + ); } else { finalLayout = renderNormalLayout(langs, width, totalLanguageSize); } diff --git a/src/cards/types.d.ts b/src/cards/types.d.ts index c5945d48be71e4..52ee0edb6a4592 100644 --- a/src/cards/types.d.ts +++ b/src/cards/types.d.ts @@ -38,6 +38,7 @@ export type TopLangOptions = CommonOptions & { custom_title: string; langs_count: number; disable_animations: boolean; + hide_progress: boolean; }; type WakaTimeOptions = CommonOptions & { From 5f20e6c97a35d77c4b2839b340bfc188ed23a056 Mon Sep 17 00:00:00 2001 From: Mohamed Hassan Date: Sat, 25 Feb 2023 08:49:30 +0200 Subject: [PATCH 06/82] add holi_theme (#2539) * add holi_theme * add holi_theme --- themes/index.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/themes/index.js b/themes/index.js index 60825d132a1191..5ed0f782fd90a0 100644 --- a/themes/index.js +++ b/themes/index.js @@ -381,6 +381,13 @@ export const themes = { border_color: "e9d8d4", bg_color: "e9d8d4", }, + holi_theme: { + title_color: "5FABEE", + text_color: "D6E7FF", + icon_color: "5FABEE", + border_color: "85A4C0", + bg_color: "030314", + }, }; export default themes; From a6ff0fa521f87f112f124bc7a9d0b435d4d166b4 Mon Sep 17 00:00:00 2001 From: Oleksandr Perlov Date: Sat, 25 Feb 2023 08:50:00 +0200 Subject: [PATCH 07/82] Add one_dark_pro (#2507) This colors were taken from One Dark Pro theme in VSCode extention. Please add it and I will use it in my profile Co-authored-by: Zohan Subhash --- themes/index.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/themes/index.js b/themes/index.js index 5ed0f782fd90a0..e55c12f89bdce1 100644 --- a/themes/index.js +++ b/themes/index.js @@ -374,6 +374,12 @@ export const themes = { border_color: "170F0C", bg_color: "170F0C", }, + one_dark_pro: { + title_color: "61AFEF", + text_color: "E5C06E", + icon_color: "C678DD", + border_color: "3B4048", + bg_color: "23272E", rose: { title_color: "8d192b", text_color: "862931", From f3f7a4837d48cb82abd61e8b16631f0f161fca9f Mon Sep 17 00:00:00 2001 From: Cateline Mnemosyne <123184375+catelinemnemosyne@users.noreply.github.com> Date: Sat, 25 Feb 2023 09:55:12 +0100 Subject: [PATCH 08/82] fix: fix JSON themes bug. (#2544) This fixes a JSON bug that was introduced in https://github.com/anuraghazra/github-readme-stats/pull/2507. --- themes/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/themes/index.js b/themes/index.js index e55c12f89bdce1..01c9a8eee79d69 100644 --- a/themes/index.js +++ b/themes/index.js @@ -380,6 +380,7 @@ export const themes = { icon_color: "C678DD", border_color: "3B4048", bg_color: "23272E", + }, rose: { title_color: "8d192b", text_color: "862931", From 55a303b4a621b9f1ac1ce0c6fe298139df7d2ac1 Mon Sep 17 00:00:00 2001 From: Zohan Subhash Date: Sat, 25 Feb 2023 17:20:14 +0530 Subject: [PATCH 09/82] Add auto-labelling for documentation updates (#2526) --- .github/labeler.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/labeler.yml b/.github/labeler.yml index be97765f07e42b..fad3eeeb8d1014 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,3 +1,4 @@ themes: themes/index.js doc-translation: docs/* card-i18n: src/translations.js +documentation: readme.md From 91345ed55fab44acac016a25d7083fc74b0b1592 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Sat, 25 Feb 2023 12:51:54 +0100 Subject: [PATCH 10/82] ci: fix unsafe directory bug (#2518) This commit fixes a bug that was introduced due to a upstream change in the git package. See https://stackoverflow.com/questions/71849415/i-cannot-add-the-parent-directory-to-safe-directory-in-git/71904131#71904131 for more information. --- .github/workflows/generate-theme-doc.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/generate-theme-doc.yml b/.github/workflows/generate-theme-doc.yml index d5fac06381943b..75f6511f09015d 100644 --- a/.github/workflows/generate-theme-doc.yml +++ b/.github/workflows/generate-theme-doc.yml @@ -23,6 +23,10 @@ jobs: node-version: ${{ matrix.node-version }} cache: npm + # Fix the unsafe repo error which was introduced by the CVE-2022-24765 git patches. + - name: Fix unsafe repo error + run: git config --global --add safe.directory ${{ github.workspace }} + - name: npm install, generate readme run: | npm ci From 8898d013b67481844213e065407cbe64ef6f3292 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20Gon=C3=A7alves?= <89359384+raphaelricardo10@users.noreply.github.com> Date: Sat, 25 Feb 2023 09:02:42 -0300 Subject: [PATCH 11/82] Update readme.md (#2414) fix: missing "&" in show_icons=true in Showing icons section --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 29852bf9c65159..83f573eddef12e 100644 --- a/readme.md +++ b/readme.md @@ -133,7 +133,7 @@ You can add the count of all your private contributions to the total commits cou ### Showing icons -To enable icons, you can pass `show_icons=true` in the query param, like so: +To enable icons, you can pass `&show_icons=true` in the query param, like so: ```md ![Anurag's GitHub stats](https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true) From a3c6f874af5c7140c67d3db4e15f85fb92171fd8 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Sat, 25 Feb 2023 13:32:08 +0100 Subject: [PATCH 12/82] test: update snapshots (#2519) --- src/cards/wakatime-card.js | 26 ++++++++++- .../renderWakatimeCard.test.js.snap | 44 +++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/src/cards/wakatime-card.js b/src/cards/wakatime-card.js index e7af1df710f9cd..2c329558b8a354 100644 --- a/src/cards/wakatime-card.js +++ b/src/cards/wakatime-card.js @@ -118,6 +118,7 @@ const createTextNode = ({ // @ts-ignore name: label, progressBarBackgroundColor, + delay: staggerDelay + 300, }); return ` @@ -276,11 +277,12 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => { } else { finalLayout = flexLayout({ items: filteredLanguages.length - ? filteredLanguages.map((language) => { + ? filteredLanguages.map((language, index) => { return createTextNode({ id: language.name, label: language.name, value: language.text, + index: index, percent: language.percent, // @ts-ignore progressBarColor: titleColor, @@ -321,7 +323,29 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => { card.setCSS( ` ${cssStyles} + @keyframes slideInAnimation { + from { + width: 0; + } + to { + width: calc(100%-100px); + } + } + @keyframes growWidthAnimation { + from { + width: 0; + } + to { + width: 100%; + } + } .lang-name { font: 400 11px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor} } + #rect-mask rect{ + animation: slideInAnimation 1s ease-in-out forwards; + } + .lang-progress{ + animation: growWidthAnimation 0.6s ease-in-out forwards; + } `, ); diff --git a/tests/__snapshots__/renderWakatimeCard.test.js.snap b/tests/__snapshots__/renderWakatimeCard.test.js.snap index 1c0bd701fbbfe0..6dfaf98e9742a1 100644 --- a/tests/__snapshots__/renderWakatimeCard.test.js.snap +++ b/tests/__snapshots__/renderWakatimeCard.test.js.snap @@ -69,7 +69,29 @@ exports[`Test Render Wakatime Card should render correctly with compact layout 1 } + @keyframes slideInAnimation { + from { + width: 0; + } + to { + width: calc(100%-100px); + } + } + @keyframes growWidthAnimation { + from { + width: 0; + } + to { + width: 100%; + } + } .lang-name { font: 400 11px 'Segoe UI', Ubuntu, Sans-Serif; fill: #434d58 } + #rect-mask rect{ + animation: slideInAnimation 1s ease-in-out forwards; + } + .lang-progress{ + animation: growWidthAnimation 0.6s ease-in-out forwards; + } @@ -227,7 +249,29 @@ exports[`Test Render Wakatime Card should render correctly with compact layout w } + @keyframes slideInAnimation { + from { + width: 0; + } + to { + width: calc(100%-100px); + } + } + @keyframes growWidthAnimation { + from { + width: 0; + } + to { + width: 100%; + } + } .lang-name { font: 400 11px 'Segoe UI', Ubuntu, Sans-Serif; fill: #434d58 } + #rect-mask rect{ + animation: slideInAnimation 1s ease-in-out forwards; + } + .lang-progress{ + animation: growWidthAnimation 0.6s ease-in-out forwards; + } From 2ab8b85ae39e7b3307c27328916b97e96f1da00f Mon Sep 17 00:00:00 2001 From: Rehman Date: Sat, 25 Feb 2023 19:01:49 +0530 Subject: [PATCH 13/82] fix: for issue #2534 (#2536) --- src/cards/top-languages-card.js | 3 +-- tests/renderTopLanguages.test.js | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/cards/top-languages-card.js b/src/cards/top-languages-card.js index be1328c0c8fe3c..ce8e12a839c773 100644 --- a/src/cards/top-languages-card.js +++ b/src/cards/top-languages-card.js @@ -13,7 +13,7 @@ import { import { langCardLocales } from "../translations.js"; const DEFAULT_CARD_WIDTH = 300; -const MIN_CARD_WIDTH = 230; +const MIN_CARD_WIDTH = 280; const DEFAULT_LANGS_COUNT = 5; const DEFAULT_LANG_COLOR = "#858585"; const CARD_PADDING = 25; @@ -317,7 +317,6 @@ const renderTopLanguages = (topLangs, options = {}) => { let finalLayout = ""; if (layout === "compact" || hide_progress == true) { - width = width + 50; // padding height = calculateCompactLayoutHeight(langs.length) + (hide_progress ? -25 : 0); diff --git a/tests/renderTopLanguages.test.js b/tests/renderTopLanguages.test.js index 8ae4bbd0c16e6c..de9e21f129bdfb 100644 --- a/tests/renderTopLanguages.test.js +++ b/tests/renderTopLanguages.test.js @@ -216,7 +216,7 @@ describe("Test renderTopLanguages", () => { ); expect(queryAllByTestId(document.body, "lang-progress")[0]).toHaveAttribute( "width", - "120", + "100", ); expect(queryAllByTestId(document.body, "lang-name")[1]).toHaveTextContent( @@ -224,7 +224,7 @@ describe("Test renderTopLanguages", () => { ); expect(queryAllByTestId(document.body, "lang-progress")[1]).toHaveAttribute( "width", - "120", + "100", ); expect(queryAllByTestId(document.body, "lang-name")[2]).toHaveTextContent( @@ -232,7 +232,7 @@ describe("Test renderTopLanguages", () => { ); expect(queryAllByTestId(document.body, "lang-progress")[2]).toHaveAttribute( "width", - "60", + "50", ); }); From 1d528da1dcfe301861810e7e559e70da262c4d4f Mon Sep 17 00:00:00 2001 From: Zohan Subhash Date: Sat, 25 Feb 2023 19:34:00 +0530 Subject: [PATCH 14/82] Add option to deploy using other services (#2525) * Create express.js * Update readme.md * Update readme.md --- express.js | 15 +++++++++++++++ readme.md | 25 +++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 express.js diff --git a/express.js b/express.js new file mode 100644 index 00000000000000..0a139625e06bbf --- /dev/null +++ b/express.js @@ -0,0 +1,15 @@ +import statsCard from './api/index.js' +import repoCard from './api/pin.js' +import langCard from './api/top-langs.js' +import wakatimeCard from './api/wakatime.js' +import express from 'express' +import dotenv from 'dotenv' + +dotenv.config() +const app = express() +app.listen(process.env.port || 9000) + +app.get('/', statsCard) +app.get('/pin', repoCard) +app.get('/top-langs', langCard) +app.get('/wakatime', wakatimeCard) diff --git a/readme.md b/readme.md index 83f573eddef12e..7a602284601261 100644 --- a/readme.md +++ b/readme.md @@ -92,7 +92,9 @@ Visit and make a small donation to hel - [Repo Card Exclusive Options](#repo-card-exclusive-options) - [Language Card Exclusive Options](#language-card-exclusive-options) - [Wakatime Card Exclusive Option](#wakatime-card-exclusive-options) -- [Deploy Yourself](#deploy-on-your-own-vercel-instance) +- [Deploy Yourself](#deploy-on-your-own) + - [On Vercel](#on-vercel) + - [On other platforms](#on-other-platforms) - [Keep your fork up to date](#keep-your-fork-up-to-date) # GitHub Stats Card @@ -509,7 +511,9 @@ By default, GitHub does not lay out the cards side by side. To do that, you can ``` -## Deploy on your own Vercel instance +## Deploy on your own + +### On Vercel #### :film_projector: [Check Out Step By Step Video Tutorial By @codeSTACKr](https://youtu.be/n6d4KHSKqGk?t=107) @@ -546,6 +550,23 @@ Since the GitHub API only allows 5k requests per hour, my `https://github-readme +### On other platforms + +> **Warning** +> This way of using GRS is not officially supported and was added to cater to some particular use cases where Vercel could not be used (e.g. #2341). The support for this method, therefore, is limited. + +
+:hammer_and_wrench: Step-by-step guide for deploying on other platforms + +1. Fork or clone this repo as per your needs +2. Add `express` to the dependencies section of `package.json` +https://github.com/anuraghazra/github-readme-stats/blob/ba7c2f8b55eac8452e479c8bd38b044d204d0424/package.json#L54-L61 +3. Run `npm i` if needed (initial setup) +4. Run `node express.js` to start the server, or set the entry point to `express.js` in `package.json` if you're deploying on a managed service +https://github.com/anuraghazra/github-readme-stats/blob/ba7c2f8b55eac8452e479c8bd38b044d204d0424/package.json#L11 +5. You're done 🎉 +
+ ### Keep your fork up to date You can keep your fork, and thus your private Vercel instance up to date with the upstream using GitHubs' [Sync Fork button](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork). You can also use the [pull](https://github.com/wei/pull) package created by [@wei](https://github.com/wei) to automate this process. From 82224fa68a453c82b7d5458c5431d3efb01f1853 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Sat, 25 Feb 2023 15:12:11 +0100 Subject: [PATCH 15/82] ci: update e2e tests (#2548) --- .gitignore | 2 ++ tests/e2e/e2e.test.js | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 2cdfa3d3348087..25017502d486aa 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ vercel_token # IDE .vscode *.code-workspace + +.vercel diff --git a/tests/e2e/e2e.test.js b/tests/e2e/e2e.test.js index 402e210fcee17d..f34859d4c8be08 100644 --- a/tests/e2e/e2e.test.js +++ b/tests/e2e/e2e.test.js @@ -15,14 +15,14 @@ const REPO = "curly-fiesta"; const USER = "catelinemnemosyne"; const STATS_DATA = { name: "Cateline Mnemosyne", - totalPRs: 1, - totalCommits: 7, + totalPRs: 2, + totalCommits: 8, totalIssues: 1, totalStars: 1, contributedTo: 1, rank: { level: "A+", - score: 50.893750297869225, + score: 50.88831151384285, }, }; From d5fbfb4345a89c264b8b07b16dedf933fa60691a Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Sat, 25 Feb 2023 15:14:47 +0100 Subject: [PATCH 16/82] ci: fix a bug in the theme preview action (#2549) --- scripts/preview-theme.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/preview-theme.js b/scripts/preview-theme.js index 38faf873ce3d5e..aae892c602fdbb 100644 --- a/scripts/preview-theme.js +++ b/scripts/preview-theme.js @@ -298,12 +298,13 @@ const themeNameAlreadyExists = (name) => { return themes[name] !== undefined; }; +const DRY_RUN = process.env.DRY_RUN === "true" || false; + /** * Main function. */ export const run = async (prNumber) => { try { - const dryRun = process.env.DRY_RUN === "true" || false; debug("Retrieve action information from context..."); debug(`Context: ${inspect(github.context)}`); let commentBody = ` @@ -513,7 +514,7 @@ export const run = async (prNumber) => { // Create or update theme-preview comment. debug("Create or update theme-preview comment..."); let comment_url; - if (!dryRun) { + if (!DRY_RUN) { comment_url = await upsertComment(octokit, { comment_id: comment?.id, issue_number: pullRequestId, @@ -535,7 +536,7 @@ export const run = async (prNumber) => { const reviewReason = themesValid ? undefined : INVALID_REVIEW_COMMENT(comment_url); - if (!dryRun) { + if (!DRY_RUN) { await addReview( octokit, pullRequestId, @@ -558,7 +559,7 @@ export const run = async (prNumber) => { } } catch (error) { debug("Set review state to `REQUEST_CHANGES` and add `invalid` label..."); - if (!dryRun) { + if (!DRY_RUN) { await addReview( octokit, pullRequestId, From 252c2b419d8adbba76e02463b198409f7d1d5977 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Sun, 26 Feb 2023 03:44:42 +0100 Subject: [PATCH 17/82] refactor: format code (#2550) --- express.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/express.js b/express.js index 0a139625e06bbf..6ce92ff0351817 100644 --- a/express.js +++ b/express.js @@ -1,15 +1,15 @@ -import statsCard from './api/index.js' -import repoCard from './api/pin.js' -import langCard from './api/top-langs.js' -import wakatimeCard from './api/wakatime.js' -import express from 'express' -import dotenv from 'dotenv' +import statsCard from "./api/index.js"; +import repoCard from "./api/pin.js"; +import langCard from "./api/top-langs.js"; +import wakatimeCard from "./api/wakatime.js"; +import express from "express"; +import dotenv from "dotenv"; -dotenv.config() -const app = express() -app.listen(process.env.port || 9000) +dotenv.config(); +const app = express(); +app.listen(process.env.port || 9000); -app.get('/', statsCard) -app.get('/pin', repoCard) -app.get('/top-langs', langCard) -app.get('/wakatime', wakatimeCard) +app.get("/", statsCard); +app.get("/pin", repoCard); +app.get("/top-langs", langCard); +app.get("/wakatime", wakatimeCard); From d60d53cdb43c27d48ba2fc2973ae8143579b6dd9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 1 Mar 2023 06:47:56 +0530 Subject: [PATCH 18/82] refactor: update languages JSON (#2554) Co-authored-by: rickstaa --- src/common/languageColors.json | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/common/languageColors.json b/src/common/languageColors.json index 47bfb1cfa3435d..b50cba6f30b084 100644 --- a/src/common/languageColors.json +++ b/src/common/languageColors.json @@ -79,6 +79,7 @@ "Ceylon": "#dfa535", "Chapel": "#8dc63f", "ChucK": "#3f8000", + "Circom": "#707575", "Cirru": "#ccccff", "Clarion": "#db901e", "Clarity": "#5546ff", @@ -116,6 +117,7 @@ "DirectX 3D File": "#aace60", "Dockerfile": "#384d54", "Dogescript": "#cca760", + "Dotenv": "#e5d559", "Dylan": "#6c616e", "E": "#ccce35", "ECL": "#8a1267", @@ -149,7 +151,7 @@ "Forth": "#341708", "Fortran": "#4d41b1", "Fortran Free Form": "#4d41b1", - "FreeBasic": "#867db1", + "FreeBasic": "#141AC9", "FreeMarker": "#0050b2", "Frege": "#00cafe", "Futhark": "#5f021f", @@ -182,6 +184,7 @@ "Go": "#00ADD8", "Go Checksums": "#00ADD8", "Go Module": "#00ADD8", + "Godot Resource": "#355570", "Golo": "#88562A", "Gosu": "#82937f", "Grace": "#615f8b", @@ -192,6 +195,7 @@ "Groovy": "#4298b8", "Groovy Server Pages": "#4298b8", "HAProxy": "#106da9", + "HCL": "#844FBA", "HLSL": "#aace60", "HOCON": "#9ff8ee", "HTML": "#e34c26", @@ -225,6 +229,7 @@ "Isabelle ROOT": "#FEFE00", "J": "#9EEDFF", "JAR Manifest": "#b07219", + "JCL": "#d90e09", "JFlex": "#DBCA00", "JSON": "#292929", "JSON with Comments": "#292929", @@ -247,9 +252,11 @@ "Jsonnet": "#0064bd", "Julia": "#a270ba", "Jupyter Notebook": "#DA5B0B", + "Just": "#384d54", "KRL": "#28430A", "Kaitai Struct": "#773b37", "KakouneScript": "#6f8042", + "KerboScript": "#41adf0", "KiCad Layout": "#2f4aab", "KiCad Legacy Layout": "#2f4aab", "KiCad Schematic": "#2f4aab", @@ -362,6 +369,7 @@ "PicoLisp": "#6067af", "PigLatin": "#fcd7de", "Pike": "#005390", + "PlantUML": "#fbbd16", "PogoScript": "#d80074", "Polar": "#ae81ff", "Portugol": "#f8bd00", @@ -379,6 +387,7 @@ "Puppet": "#302B6D", "PureBasic": "#5a6986", "PureScript": "#1D222D", + "Pyret": "#ee1e10", "Python": "#3572A5", "Python console": "#3572A5", "Python traceback": "#3572A5", @@ -531,7 +540,6 @@ "fish": "#4aae47", "hoon": "#00b171", "jq": "#c7254e", - "just": "#384d54", "kvlang": "#1da6e0", "mIRC Script": "#3d57c3", "mcfunction": "#E22837", From 8849b5f5fc708ea2f64d5c7176580252e17bc81a Mon Sep 17 00:00:00 2001 From: Zohan Subhash Date: Wed, 1 Mar 2023 15:03:49 +0530 Subject: [PATCH 19/82] Preview theme workflow fix (#2557) * Fix octokit error * ci: make octokit instance global --------- Co-authored-by: rickstaa --- scripts/preview-theme.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/scripts/preview-theme.js b/scripts/preview-theme.js index aae892c602fdbb..d94d084aa85e76 100644 --- a/scripts/preview-theme.js +++ b/scripts/preview-theme.js @@ -44,6 +44,9 @@ const REQUIRED_COLOR_PROPS = ACCEPTED_COLOR_PROPS.slice(0, 4); const INVALID_REVIEW_COMMENT = (commentUrl) => `Some themes are invalid. See the [Automated Theme Preview](${commentUrl}) comment above for more information.`; +// Retrieve octokit instance. +const OCTOKIT = github.getOctokit(getGithubToken()); + /** * Retrieve PR number from the event payload. * @@ -312,7 +315,6 @@ export const run = async (prNumber) => { \r${THEME_CONTRIB_GUIDELINESS} `; const ccc = new ColorContrastChecker(); - const octokit = github.getOctokit(getGithubToken()); const pullRequestId = prNumber ? prNumber : getPrNumber(); const commenter = getCommenter(); const { owner, repo } = getRepoInfo(github.context); @@ -322,7 +324,7 @@ export const run = async (prNumber) => { // Retrieve the PR diff and preview-theme comment. debug("Retrieve PR diff..."); - const res = await octokit.pulls.get({ + const res = await OCTOKIT.pulls.get({ owner, repo, pull_number: pullRequestId, @@ -332,7 +334,7 @@ export const run = async (prNumber) => { }); debug("Retrieve preview-theme comment..."); const comment = await findComment( - octokit, + OCTOKIT, pullRequestId, owner, repo, @@ -515,7 +517,7 @@ export const run = async (prNumber) => { debug("Create or update theme-preview comment..."); let comment_url; if (!DRY_RUN) { - comment_url = await upsertComment(octokit, { + comment_url = await upsertComment(OCTOKIT, { comment_id: comment?.id, issue_number: pullRequestId, owner, @@ -538,7 +540,7 @@ export const run = async (prNumber) => { : INVALID_REVIEW_COMMENT(comment_url); if (!DRY_RUN) { await addReview( - octokit, + OCTOKIT, pullRequestId, owner, repo, @@ -546,7 +548,7 @@ export const run = async (prNumber) => { reviewReason, ); await addRemoveLabel( - octokit, + OCTOKIT, pullRequestId, owner, repo, @@ -561,7 +563,7 @@ export const run = async (prNumber) => { debug("Set review state to `REQUEST_CHANGES` and add `invalid` label..."); if (!DRY_RUN) { await addReview( - octokit, + OCTOKIT, pullRequestId, owner, repo, @@ -569,7 +571,7 @@ export const run = async (prNumber) => { error.message, ); await addRemoveLabel( - octokit, + OCTOKIT, pullRequestId, owner, repo, From a1c3c6accc1e8a0be00dd0db5a32d63d86055daa Mon Sep 17 00:00:00 2001 From: Zohan Subhash Date: Wed, 1 Mar 2023 21:21:25 +0530 Subject: [PATCH 20/82] ci: preview theme workflow fix (#2559) * Fix octokit error * ci: make octokit instance global * Fix preview theme (move declarations to global) * refactor: make constants uppercase --------- Co-authored-by: rickstaa --- scripts/preview-theme.js | 50 ++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/scripts/preview-theme.js b/scripts/preview-theme.js index d94d084aa85e76..f5a24a80a326d6 100644 --- a/scripts/preview-theme.js +++ b/scripts/preview-theme.js @@ -46,6 +46,8 @@ const INVALID_REVIEW_COMMENT = (commentUrl) => // Retrieve octokit instance. const OCTOKIT = github.getOctokit(getGithubToken()); +const PULL_REQUEST_ID = prNumber ? prNumber : getPrNumber(); +const { OWNER, REPO } = getRepoInfo(github.context); /** * Retrieve PR number from the event payload. @@ -315,19 +317,17 @@ export const run = async (prNumber) => { \r${THEME_CONTRIB_GUIDELINESS} `; const ccc = new ColorContrastChecker(); - const pullRequestId = prNumber ? prNumber : getPrNumber(); const commenter = getCommenter(); - const { owner, repo } = getRepoInfo(github.context); - debug(`Owner: ${owner}`); - debug(`Repo: ${repo}`); + debug(`Owner: ${OWNER}`); + debug(`Repo: ${REPO}`); debug(`Commenter: ${commenter}`); // Retrieve the PR diff and preview-theme comment. debug("Retrieve PR diff..."); const res = await OCTOKIT.pulls.get({ - owner, - repo, - pull_number: pullRequestId, + OWNER, + REPO, + pull_number: PULL_REQUEST_ID, mediaType: { format: "diff", }, @@ -335,9 +335,9 @@ export const run = async (prNumber) => { debug("Retrieve preview-theme comment..."); const comment = await findComment( OCTOKIT, - pullRequestId, - owner, - repo, + PULL_REQUEST_ID, + OWNER, + REPO, commenter, ); @@ -519,9 +519,9 @@ export const run = async (prNumber) => { if (!DRY_RUN) { comment_url = await upsertComment(OCTOKIT, { comment_id: comment?.id, - issue_number: pullRequestId, - owner, - repo, + issue_number: PULL_REQUEST_ID, + OWNER, + REPO, body: commentBody, }); } else { @@ -541,17 +541,17 @@ export const run = async (prNumber) => { if (!DRY_RUN) { await addReview( OCTOKIT, - pullRequestId, - owner, - repo, + PULL_REQUEST_ID, + OWNER, + REPO, reviewState, reviewReason, ); await addRemoveLabel( OCTOKIT, - pullRequestId, - owner, - repo, + PULL_REQUEST_ID, + OWNER, + REPO, "invalid", !themesValid, ); @@ -564,17 +564,17 @@ export const run = async (prNumber) => { if (!DRY_RUN) { await addReview( OCTOKIT, - pullRequestId, - owner, - repo, + PULL_REQUEST_ID, + OWNER, + REPO, "REQUEST_CHANGES", error.message, ); await addRemoveLabel( OCTOKIT, - pullRequestId, - owner, - repo, + PULL_REQUEST_ID, + OWNER, + REPO, "invalid", true, ); From 9ec2c8367a38e008a1c4ea740d2408bdc3c20842 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Thu, 2 Mar 2023 03:14:43 +0100 Subject: [PATCH 21/82] refactor: fix code comments and change 'up' rate limit (#2560) --- api/status/pat-info.js | 4 ++-- api/status/up.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/status/pat-info.js b/api/status/pat-info.js index 775e06896427cc..69d869ea2553e6 100644 --- a/api/status/pat-info.js +++ b/api/status/pat-info.js @@ -2,11 +2,11 @@ * @file Contains a simple cloud function that can be used to check which PATs are no * longer working. It returns a list of valid PATs, expired PATs and PATs with errors. * - * @description This function is currently rate limited to 1 request per 10 minutes. + * @description This function is currently rate limited to 1 request per 5 minutes. */ import { logger, request, dateDiff } from "../../src/common/utils.js"; -export const RATE_LIMIT_SECONDS = 60 * 5; // 1 request per 10 minutes +export const RATE_LIMIT_SECONDS = 60 * 5; // 1 request per 5 minutes /** * Simple uptime check fetcher for the PATs. diff --git a/api/status/up.js b/api/status/up.js index 33fe8f900c3958..678a20b0b5c147 100644 --- a/api/status/up.js +++ b/api/status/up.js @@ -2,13 +2,13 @@ * @file Contains a simple cloud function that can be used to check if the PATs are still * functional. * - * @description This function is currently rate limited to 1 request per 10 minutes. + * @description This function is currently rate limited to 1 request per 5 minutes. */ import retryer from "../../src/common/retryer.js"; import { logger, request } from "../../src/common/utils.js"; -export const RATE_LIMIT_SECONDS = 60 * 10; // 1 request per 10 minutes +export const RATE_LIMIT_SECONDS = 60 * 5; // 1 request per 5 minutes /** * Simple uptime check fetcher for the PATs. From 7bc8f19a7fd7787287c02e42761602746e55c1d2 Mon Sep 17 00:00:00 2001 From: Zohan Subhash Date: Thu, 2 Mar 2023 22:51:39 +0530 Subject: [PATCH 22/82] Preview action fix (#2561) * Fix error * refactor: remove unused code --------- Co-authored-by: Rick Staa --- scripts/preview-theme.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/preview-theme.js b/scripts/preview-theme.js index f5a24a80a326d6..baccaa61a3a781 100644 --- a/scripts/preview-theme.js +++ b/scripts/preview-theme.js @@ -46,7 +46,7 @@ const INVALID_REVIEW_COMMENT = (commentUrl) => // Retrieve octokit instance. const OCTOKIT = github.getOctokit(getGithubToken()); -const PULL_REQUEST_ID = prNumber ? prNumber : getPrNumber(); +const PULL_REQUEST_ID = getPrNumber(); const { OWNER, REPO } = getRepoInfo(github.context); /** @@ -308,7 +308,7 @@ const DRY_RUN = process.env.DRY_RUN === "true" || false; /** * Main function. */ -export const run = async (prNumber) => { +export const run = async () => { try { debug("Retrieve action information from context..."); debug(`Context: ${inspect(github.context)}`); From 976771080facb86c3e4b455fef18278021f7f984 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Fri, 3 Mar 2023 09:07:02 +0100 Subject: [PATCH 23/82] ci: fix theme preview action (#2563) --- scripts/preview-theme.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/preview-theme.js b/scripts/preview-theme.js index baccaa61a3a781..2cfe0f25cad3ae 100644 --- a/scripts/preview-theme.js +++ b/scripts/preview-theme.js @@ -46,8 +46,8 @@ const INVALID_REVIEW_COMMENT = (commentUrl) => // Retrieve octokit instance. const OCTOKIT = github.getOctokit(getGithubToken()); -const PULL_REQUEST_ID = getPrNumber(); const { OWNER, REPO } = getRepoInfo(github.context); +var PULL_REQUEST_ID; /** * Retrieve PR number from the event payload. @@ -318,6 +318,7 @@ export const run = async () => { `; const ccc = new ColorContrastChecker(); const commenter = getCommenter(); + PULL_REQUEST_ID = getPrNumber(); debug(`Owner: ${OWNER}`); debug(`Repo: ${REPO}`); debug(`Commenter: ${commenter}`); From 1e61f9f3fe955fa25ab27dd1e06ddbfd788f4fda Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Fri, 3 Mar 2023 15:57:11 +0100 Subject: [PATCH 24/82] fix theme preview (#2564) * ci: fix theme preview action * fix: fix some bugs in the 'theme-preveiw' action --- scripts/preview-theme.js | 41 +++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/scripts/preview-theme.js b/scripts/preview-theme.js index 2cfe0f25cad3ae..c0bb9eb5522d82 100644 --- a/scripts/preview-theme.js +++ b/scripts/preview-theme.js @@ -43,12 +43,24 @@ const ACCEPTED_COLOR_PROPS = Object.keys(COLOR_PROPS); const REQUIRED_COLOR_PROPS = ACCEPTED_COLOR_PROPS.slice(0, 4); const INVALID_REVIEW_COMMENT = (commentUrl) => `Some themes are invalid. See the [Automated Theme Preview](${commentUrl}) comment above for more information.`; - -// Retrieve octokit instance. -const OCTOKIT = github.getOctokit(getGithubToken()); -const { OWNER, REPO } = getRepoInfo(github.context); +var OCTOKIT; +var OWNER; +var REPO; var PULL_REQUEST_ID; +/** + * Incorrect JSON format error. + * @extends Error + * @param {string} message Error message. + * @returns {Error} IncorrectJsonFormatError. + */ +class IncorrectJsonFormatError extends Error { + constructor(message) { + super(message); + this.name = "IncorrectJsonFormatError"; + } +} + /** * Retrieve PR number from the event payload. * @@ -274,7 +286,9 @@ const parseJSON = (json) => { if (typeof parsedJson === "object") { return parsedJson; } else { - throw new Error("PR diff is not a valid theme JSON object."); + throw new IncorrectJsonFormatError( + "PR diff is not a valid theme JSON object.", + ); } } catch (error) { let parsedJson = json @@ -289,7 +303,9 @@ const parseJSON = (json) => { } return Hjson.parse(parsedJson.join("")); } else { - throw error; + throw new IncorrectJsonFormatError( + `Theme JSON file could not be parsed: ${error.message}`, + ); } } }; @@ -317,6 +333,11 @@ export const run = async () => { \r${THEME_CONTRIB_GUIDELINESS} `; const ccc = new ColorContrastChecker(); + OCTOKIT = github.getOctokit(getGithubToken()); + PULL_REQUEST_ID = getPrNumber(); + const { owner, repo } = getRepoInfo(github.context); + OWNER = owner; + REPO = repo; const commenter = getCommenter(); PULL_REQUEST_ID = getPrNumber(); debug(`Owner: ${OWNER}`); @@ -326,8 +347,8 @@ export const run = async () => { // Retrieve the PR diff and preview-theme comment. debug("Retrieve PR diff..."); const res = await OCTOKIT.pulls.get({ - OWNER, - REPO, + owner: OWNER, + repo: REPO, pull_number: PULL_REQUEST_ID, mediaType: { format: "diff", @@ -569,7 +590,9 @@ export const run = async () => { OWNER, REPO, "REQUEST_CHANGES", - error.message, + "**Something went wrong in the theme preview action:** `" + + error.message + + "`", ); await addRemoveLabel( OCTOKIT, From ed18914fa4c131b076cbc6cff29db3ffbdc48787 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Sun, 5 Mar 2023 11:22:08 +0100 Subject: [PATCH 25/82] ci: fixes theme preview action (#2566) --- scripts/preview-theme.js | 44 ++++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/scripts/preview-theme.js b/scripts/preview-theme.js index c0bb9eb5522d82..3179780d8438c2 100644 --- a/scripts/preview-theme.js +++ b/scripts/preview-theme.js @@ -143,15 +143,36 @@ const findComment = async (octokit, issueNumber, owner, repo, commenter) => { * Create or update the preview comment. * * @param {Object} octokit Octokit instance. - * @param {Object} props Comment properties. + * @param {number} issueNumber Issue number. + * @param {Object} repo Repository name. + * @param {Object} owner Owner of the repository. + * @param {number} commentId Comment ID. + * @param {string} body Comment body. * @return {string} The comment URL. */ -const upsertComment = async (octokit, props) => { +const upsertComment = async ( + octokit, + issueNumber, + repo, + owner, + commentId, + body, +) => { let resp; - if (props.comment_id !== undefined) { - resp = await octokit.issues.updateComment(props); + if (commentId !== undefined) { + resp = await octokit.issues.updateComment({ + owner, + repo, + comment_id: commentId, + body, + }); } else { - resp = await octokit.issues.createComment(props); + resp = await octokit.issues.createComment({ + owner, + repo, + issue_number: issueNumber, + body, + }); } return resp.data.html_url; }; @@ -539,13 +560,14 @@ export const run = async () => { debug("Create or update theme-preview comment..."); let comment_url; if (!DRY_RUN) { - comment_url = await upsertComment(OCTOKIT, { - comment_id: comment?.id, - issue_number: PULL_REQUEST_ID, - OWNER, + comment_url = await upsertComment( + OCTOKIT, + PULL_REQUEST_ID, REPO, - body: commentBody, - }); + OWNER, + comment?.id, + commentBody, + ); } else { info(`DRY_RUN: Comment body: ${commentBody}`); comment_url = ""; From b93aee34d0101aed1de0b5d7cc68c3bb19614d52 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Mon, 6 Mar 2023 05:03:06 +0100 Subject: [PATCH 26/82] ci: improve theme preview action (#2572) --- scripts/preview-theme.js | 41 +++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/scripts/preview-theme.js b/scripts/preview-theme.js index 3179780d8438c2..e18c01b8615aff 100644 --- a/scripts/preview-theme.js +++ b/scripts/preview-theme.js @@ -312,18 +312,30 @@ const parseJSON = (json) => { ); } } catch (error) { - let parsedJson = json + // Remove trailing commas (if any). + let parsedJson = json.replace(/(,\s*})/g, "}"); + + // Remove JS comments (if any). + parsedJson = parsedJson.replace(/\/\/[A-z\s]*\s/g, ""); + + // Fix incorrect open bracket (if any). + const splitJson = parsedJson .split(/([\s\r\s]*}[\s\r\s]*,[\s\r\s]*)(?=[\w"-]+:)/) - .filter((x) => typeof x !== "string" || !!x.trim()); - if (parsedJson[0].replace(/\s+/g, "") === "},") { - parsedJson[0] = "},"; - if (!/\s*}\s*,?\s*$/.test(parsedJson[1])) { - parsedJson.push(parsedJson.shift()); + .filter((x) => typeof x !== "string" || !!x.trim()); // Split json into array of strings and objects. + if (splitJson[0].replace(/\s+/g, "") === "},") { + splitJson[0] = "},"; + if (!/\s*}\s*,?\s*$/.test(splitJson[1])) { + splitJson.push(splitJson.shift()); } else { - parsedJson.shift(); + splitJson.shift(); } - return Hjson.parse(parsedJson.join("")); - } else { + parsedJson = splitJson.join(""); + } + + // Try to parse the fixed json. + try { + return Hjson.parse(parsedJson); + } catch (error) { throw new IncorrectJsonFormatError( `Theme JSON file could not be parsed: ${error.message}`, ); @@ -387,10 +399,17 @@ export const run = async () => { // Retrieve theme changes from the PR diff. debug("Retrieve themes..."); const diff = parse(res.data); + + // Retrieve all theme changes from the PR diff and convert to JSON. + debug("Retrieve theme changes..."); const content = diff .find((file) => file.to === "themes/index.js") - .chunks[0].changes.filter((c) => c.type === "add") - .map((c) => c.content.replace("+", "")) + .chunks.map((chunk) => + chunk.changes + .filter((c) => c.type === "add") + .map((c) => c.content.replace("+", "")) + .join(""), + ) .join(""); const themeObject = parseJSON(content); if ( From c5063b92b6e260dcc405a0a4cd27552dc103f8f5 Mon Sep 17 00:00:00 2001 From: Etanarvazac Revorix Date: Tue, 7 Mar 2023 20:06:04 -0500 Subject: [PATCH 27/82] Added "Shadow" set (Red, Green, Blue, transparent BG) (#2529) * Added "Shadow" set (Red, Green, Blue, transparent BG) 3 additional themes sticking primarily to flat colors, which the exception of icons and border being slightly darker. All 3 themes also have transparent backgrounds that will show differently per-user via GiHub's own light and dark themes. Transparency should also still provide easy readability for both. * Test Just want to see if we can make the themes have a transparent background. * Shadows moved under Transparent --------- Co-authored-by: Zohan Subhash --- themes/index.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/themes/index.js b/themes/index.js index 01c9a8eee79d69..d348a2ba1a7698 100644 --- a/themes/index.js +++ b/themes/index.js @@ -18,6 +18,27 @@ export const themes = { text_color: "417E87", bg_color: "ffffff00", }, + shadow_red: { + title_color: "9A0000", + text_color: "444", + icon_color: "4F0000", + border_color: "4F0000", + bg_color: "ffffff00", + }, + shadow_green: { + title_color: "007A00", + text_color: "444", + icon_color: "003D00", + border_color: "003D00", + bg_color: "ffffff00", + }, + shadow_blue: { + title_color: "00779A", + text_color: "444", + icon_color: "004450", + border_color: "004490", + bg_color: "ffffff00", + }, dark: { title_color: "fff", icon_color: "79ff97", From 2bd9d457ac7f10bd5f1303c6a14f3ec22c2ee9c6 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Thu, 9 Mar 2023 09:45:50 +0000 Subject: [PATCH 28/82] ci: fix theme docs generate bug (#2573) --- scripts/push-theme-readme.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/push-theme-readme.sh b/scripts/push-theme-readme.sh index 1ab5de474ea5a5..132a4b508e8e48 100755 --- a/scripts/push-theme-readme.sh +++ b/scripts/push-theme-readme.sh @@ -6,6 +6,7 @@ export BRANCH_NAME=updated-theme-readme git --version git config --global user.email "no-reply@githubreadmestats.com" git config --global user.name "GitHub Readme Stats Bot" +git config --global --add safe.directory ${GITHUB_WORKSPACE} git branch -d $BRANCH_NAME || true git checkout -b $BRANCH_NAME git add --all From 32998295b7ef9742ea0168213807d7e8ec5c72a6 Mon Sep 17 00:00:00 2001 From: Eduardo Zaniboni <67515606+eduardozaniboni@users.noreply.github.com> Date: Tue, 14 Mar 2023 06:12:29 -0300 Subject: [PATCH 29/82] update my theme (#2576) --- themes/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/index.js b/themes/index.js index d348a2ba1a7698..0121cf0bc14d1c 100644 --- a/themes/index.js +++ b/themes/index.js @@ -409,7 +409,7 @@ export const themes = { border_color: "e9d8d4", bg_color: "e9d8d4", }, - holi_theme: { + holi: { title_color: "5FABEE", text_color: "D6E7FF", icon_color: "5FABEE", From b928f51442ff224507f519ceaca67d09afffa2b1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 31 Mar 2023 06:40:22 +0530 Subject: [PATCH 30/82] refactor: update languages JSON (#2596) Co-authored-by: rickstaa --- src/common/languageColors.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/common/languageColors.json b/src/common/languageColors.json index b50cba6f30b084..3937eec5a2bf5b 100644 --- a/src/common/languageColors.json +++ b/src/common/languageColors.json @@ -133,6 +133,7 @@ "Elixir": "#6e4a7e", "Elm": "#60B5CC", "Elvish": "#55BB55", + "Elvish Transcript": "#55BB55", "Emacs Lisp": "#c065db", "EmberScript": "#FFF4F3", "Erlang": "#B83998", @@ -453,6 +454,7 @@ "Smalltalk": "#596706", "Smarty": "#f0c040", "Smithy": "#c44536", + "Snakemake": "#419179", "Solidity": "#AA6746", "SourcePawn": "#f69e1d", "Squirrel": "#800000", @@ -466,6 +468,7 @@ "SugarSS": "#2fcc9f", "SuperCollider": "#46390b", "Svelte": "#ff3e00", + "Sway": "#dea584", "Swift": "#F05138", "SystemVerilog": "#DAE1C2", "TI Program": "#A0AA87", From d8244a7fe5eaffaf7264e592290b9b1f9aaf849d Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Sat, 1 Apr 2023 05:47:56 +0100 Subject: [PATCH 31/82] Add format stats option (#2155) * feat: added `format_stats` option (#2128) * refactor: change `format_stats` to `short_values` (#2128) * test: create shorten values test (#2128) * Update readme.md Co-authored-by: Rick Staa * refactor: rename ``short_values`` to ``number_format`` * Update readme.md Co-authored-by: Rick Staa * Update src/cards/stats-card.js Co-authored-by: Rick Staa * refactor: format codebase --------- Co-authored-by: Rick Staa --- api/index.js | 2 ++ readme.md | 1 + src/cards/stats-card.js | 6 +++++- src/cards/types.d.ts | 1 + tests/renderStatsCard.test.js | 9 +++++++++ 5 files changed, 18 insertions(+), 1 deletion(-) diff --git a/api/index.js b/api/index.js index b449d43b490801..e89c74688d1163 100644 --- a/api/index.js +++ b/api/index.js @@ -35,6 +35,7 @@ export default async (req, res) => { locale, disable_animations, border_radius, + number_format, border_color, } = req.query; res.setHeader("Content-Type", "image/svg+xml"); @@ -88,6 +89,7 @@ export default async (req, res) => { custom_title, border_radius, border_color, + number_format, locale: locale ? locale.toLowerCase() : null, disable_animations: parseBoolean(disable_animations), }), diff --git a/readme.md b/readme.md index 7a602284601261..91a1cfa9f88701 100644 --- a/readme.md +++ b/readme.md @@ -289,6 +289,7 @@ You can provide multiple comma-separated values in the bg_color option to render - `text_bold` - Use bold text _(boolean)_. Default: `true`. - `disable_animations` - Disables all animations in the card _(boolean)_. Default: `false`. - `ring_color` - Color of the rank circle _(hex color)_. Defaults to the theme ring color if it exists and otherwise the title color. +- `number_format` - Switch between two available formats for displaying the card values `short` (i.e. `6.6k`) and `long` (i.e. `6626`). Default: `short`. > **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 f39a968f180659..c60ea51d5119c0 100644 --- a/src/cards/stats-card.js +++ b/src/cards/stats-card.js @@ -39,8 +39,10 @@ const createTextNode = ({ showIcons, shiftValuePos, bold, + number_format, }) => { - const kValue = kFormatter(value); + const kValue = + number_format.toLowerCase() === "long" ? value : kFormatter(value); const staggerDelay = (index + 3) * 150; const labelOffset = showIcons ? `x="25"` : ""; @@ -103,6 +105,7 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => { custom_title, border_radius, border_color, + number_format = "short", locale, disable_animations = false, } = options; @@ -192,6 +195,7 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => { showIcons: show_icons, shiftValuePos: 79.01 + (isLongLocale ? 50 : 0), bold: text_bold, + number_format, }), ); diff --git a/src/cards/types.d.ts b/src/cards/types.d.ts index 52ee0edb6a4592..a3abc23e98a36c 100644 --- a/src/cards/types.d.ts +++ b/src/cards/types.d.ts @@ -22,6 +22,7 @@ export type StatCardOptions = CommonOptions & { line_height: number | string; custom_title: string; disable_animations: boolean; + number_format: string; }; export type RepoCardOptions = CommonOptions & { diff --git a/tests/renderStatsCard.test.js b/tests/renderStatsCard.test.js index 748b7a32cd32b6..110121ac9e4b66 100644 --- a/tests/renderStatsCard.test.js +++ b/tests/renderStatsCard.test.js @@ -357,4 +357,13 @@ describe("Test renderStatsCard", () => { document.body.innerHTML = renderStatsCard(stats, {}); expect(document.querySelector("rect")).toHaveAttribute("rx", "4.5"); }); + + it("should shorten values", () => { + stats["totalCommits"] = 1999; + + document.body.innerHTML = renderStatsCard(stats); + expect(getByTestId(document.body, "commits").textContent).toBe("2k"); + document.body.innerHTML = renderStatsCard(stats, { number_format: "long" }); + expect(getByTestId(document.body, "commits").textContent).toBe("1999"); + }); }); From 4d1d83d5e5691a5aaa8a65d230924d6072bbc16d Mon Sep 17 00:00:00 2001 From: Fabiano Couto Date: Sat, 1 Apr 2023 08:22:00 -0300 Subject: [PATCH 32/82] add github_dark_dimmed theme (#2594) * feat(theme): add github_dark_dimmed theme * feat(theme): change github_dark_dimmed icon color * contrast ratio adjustment contrast ratio adjustment on github_dark_dimmed theme * feat(theme): readme preview * feat(theme): github themes next to each other * github themes next to each other --- themes/README.md | 14 ++++++++------ themes/index.js | 7 +++++++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/themes/README.md b/themes/README.md index b8649d43b95644..5993b6a07a9b8b 100644 --- a/themes/README.md +++ b/themes/README.md @@ -32,10 +32,11 @@ Use `?theme=THEME_NAME` parameter like so :- | `jolly` ![jolly][jolly] | `maroongold` ![maroongold][maroongold] | `yeblu` ![yeblu][yeblu] | | `blueberry` ![blueberry][blueberry] | `slateorange` ![slateorange][slateorange] | `kacho_ga` ![kacho_ga][kacho_ga] | | `outrun` ![outrun][outrun] | `ocean_dark` ![ocean_dark][ocean_dark] | `city_lights` ![city_lights][city_lights] | -| `github_dark` ![github_dark][github_dark] | `discord_old_blurple` ![discord_old_blurple][discord_old_blurple] | `aura_dark` ![aura_dark][aura_dark] | +| `github_dark` ![github_dark][github_dark] | `github_dark_dimmed` ![github_dark_dimmed][github_dark_dimmed] | `discord_old_blurple` ![discord_old_blurple][discord_old_blurple] | | `panda` ![panda][panda] | `noctis_minimus` ![noctis_minimus][noctis_minimus] | `cobalt2` ![cobalt2][cobalt2] | | `swift` ![swift][swift] | `aura` ![aura][aura] | `apprentice` ![apprentice][apprentice] | | `moltack` ![moltack][moltack] | `codeSTACKr` ![codeSTACKr][codeSTACKr] | `rose_pine` ![rose_pine][rose_pine] | +| `aura_dark` ![aura_dark][aura_dark] | | | | [Add your theme][add-theme] | | | ## Repo Card @@ -60,10 +61,11 @@ Use `?theme=THEME_NAME` parameter like so :- | `jolly` ![jolly][jolly_repo] | `maroongold` ![maroongold][maroongold_repo] | `yeblu` ![yeblu][yeblu_repo] | | `blueberry` ![blueberry][blueberry_repo] | `slateorange` ![slateorange][slateorange_repo] | `kacho_ga` ![kacho_ga][kacho_ga_repo] | | `outrun` ![outrun][outrun_repo] | `ocean_dark` ![ocean_dark][ocean_dark_repo] | `city_lights` ![city_lights][city_lights_repo] | -| `github_dark` ![github_dark][github_dark_repo] | `discord_old_blurple` ![discord_old_blurple][discord_old_blurple_repo] | `aura_dark` ![aura_dark][aura_dark_repo] | +| `github_dark` ![github_dark][github_dark_repo] | `github_dark_dimmed` ![github_dark_dimmed][github_dark_dimmed_repo] | `discord_old_blurple` ![discord_old_blurple][discord_old_blurple_repo] | | `panda` ![panda][panda_repo] | `noctis_minimus` ![noctis_minimus][noctis_minimus_repo] | `cobalt2` ![cobalt2][cobalt2_repo] | | `swift` ![swift][swift_repo] | `aura` ![aura][aura_repo] | `apprentice` ![apprentice][apprentice_repo] | | `moltack` ![moltack][moltack_repo] | `codeSTACKr` ![codeSTACKr][codeSTACKr_repo] | `rose_pine` ![rose_pine][rose_pine_repo] | +| `aura_dark` ![aura_dark][aura_dark_repo] | | | | [Add your theme][add-theme] | | | @@ -117,8 +119,8 @@ Use `?theme=THEME_NAME` parameter like so :- [ocean_dark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=ocean_dark [city_lights]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=city_lights [github_dark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=github_dark +[github_dark_dimmed]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=github_dark_dimmed [discord_old_blurple]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=discord_old_blurple -[aura_dark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=aura_dark [panda]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=panda [noctis_minimus]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=noctis_minimus [cobalt2]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=cobalt2 @@ -128,7 +130,7 @@ Use `?theme=THEME_NAME` parameter like so :- [moltack]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=moltack [codeSTACKr]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=codeSTACKr [rose_pine]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=rose_pine - +[aura_dark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=aura_dark [default_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=default [default_repocard_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=default_repocard @@ -180,8 +182,8 @@ Use `?theme=THEME_NAME` parameter like so :- [ocean_dark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=ocean_dark [city_lights_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=city_lights [github_dark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=github_dark +[github_dark_dimmed_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=github_dark_dimmed [discord_old_blurple_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=discord_old_blurple -[aura_dark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=aura_dark [panda_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=panda [noctis_minimus_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=noctis_minimus [cobalt2_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=cobalt2 @@ -191,7 +193,7 @@ Use `?theme=THEME_NAME` parameter like so :- [moltack_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=moltack [codeSTACKr_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=codeSTACKr [rose_pine_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=rose_pine - +[aura_dark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=aura_dark [add-theme]: https://github.com/anuraghazra/github-readme-stats/edit/master/themes/index.js diff --git a/themes/index.js b/themes/index.js index 0121cf0bc14d1c..ab8eab6a0d0e92 100644 --- a/themes/index.js +++ b/themes/index.js @@ -321,6 +321,13 @@ export const themes = { text_color: "C3D1D9", bg_color: "0D1117", }, + github_dark_dimmed: { + title_color: "539bf5", + icon_color: "539bf5", + text_color: "ADBAC7", + bg_color: "24292F", + border_color: "373E47", + }, discord_old_blurple: { title_color: "7289DA", icon_color: "7289DA", From 879937c11d08154f9afa0de7dab7b4945e452276 Mon Sep 17 00:00:00 2001 From: Fabiano Couto Date: Mon, 24 Apr 2023 10:56:14 -0300 Subject: [PATCH 33/82] feat(RankIcon): add rank_icon option (#2628) * feat(theme): add github_dark_dimmed theme * feat(theme): change github_dark_dimmed icon color * contrast ratio adjustment contrast ratio adjustment on github_dark_dimmed theme * feat(theme): readme preview * feat(theme): github themes next to each other * github themes next to each other * feat(RankIcon): add rank icon option * feat(RankIcon): extract rankIcon to icons file * feat(RankIcon): update readme * feat(RankIcon): test coverage * Update readme.md Co-authored-by: Rick Staa --------- Co-authored-by: Rick Staa --- api/index.js | 2 ++ readme.md | 13 +++++++++---- src/cards/stats-card.js | 13 +++---------- src/cards/types.d.ts | 4 ++++ src/common/icons.js | 25 ++++++++++++++++++++++++- tests/renderStatsCard.test.js | 17 +++++++++++++++++ 6 files changed, 59 insertions(+), 15 deletions(-) diff --git a/api/index.js b/api/index.js index e89c74688d1163..29ff87f9af8639 100644 --- a/api/index.js +++ b/api/index.js @@ -37,6 +37,7 @@ export default async (req, res) => { border_radius, number_format, border_color, + rank_icon, } = req.query; res.setHeader("Content-Type", "image/svg+xml"); @@ -92,6 +93,7 @@ export default async (req, res) => { number_format, locale: locale ? locale.toLowerCase() : null, disable_animations: parseBoolean(disable_animations), + rank_icon, }), ); } catch (err) { diff --git a/readme.md b/readme.md index 91a1cfa9f88701..fe04a88e81bbab 100644 --- a/readme.md +++ b/readme.md @@ -216,10 +216,10 @@ You can use [GitHub's theme context](https://github.blog/changelog/2021-11-24-sp ##### Use GitHub's new media feature You can use [GitHub's new media feature](https://github.blog/changelog/2022-05-19-specify-theme-context-for-images-in-markdown-beta/) in HTML to specify whether to display images for light or dark themes. This is done using the HTML `` element in combination with the `prefers-color-scheme` media feature. - + ```html - @@ -235,7 +235,7 @@ You can use [GitHub's new media feature](https://github.blog/changelog/2022-05-1 :eyes: Show example - @@ -260,7 +260,7 @@ You can customize the appearance of your `Stats Card` or `Repo Card` however you - `border_color` - Card's border color _(hex color)_. Default: `e4e2e2` (Does not apply when `hide_border` is enabled). - `bg_color` - Card's background color _(hex color)_ **or** a gradient in the form of _angle,start,end_. Default: `fffefe` - `hide_border` - Hides the card's border _(boolean)_. Default: `false` -- `theme` - name of the theme, choose from [all available themes](./themes/README.md). Default: `default` theme. +- `theme` - name of the theme, choose from [all available themes](./themes/README.md). Default: `default` theme. - `cache_seconds` - set the cache header manually _(min: 14400, max: 86400)_. Default: `14400 seconds (4 hours)`. - `locale` - set the language in the card _(e.g. cn, de, es, etc.)_. Default: `en`. - `border_radius` - Corner rounding on the card. Default: `4.5`. @@ -280,6 +280,7 @@ You can provide multiple comma-separated values in the bg_color option to render - `hide_title` - _(boolean)_. Default: `false`. - `card_width` - Set the card's width manually _(number)_. Default: `500px (approx.)`. - `hide_rank` - _(boolean)_ hides the rank and automatically resizes the card width. Default: `false`. +- `rank_icon` - Shows alternative rank icon (i.e. `github` or `default`). Default: `default`. - `show_icons` - _(boolean)_. Default: `false`. - `include_all_commits` - Count total commits instead of just the current year commits _(boolean)_. Default: `false`. - `count_private` - Count private commits _(boolean)_. Default: `false`. @@ -459,6 +460,10 @@ Change the `?username=` value to your [Wakatime](https://wakatime.com) username. ![Anurag's GitHub stats](https://github-readme-stats.vercel.app/api?username=anuraghazra&hide=issues&show_icons=true) +- Shows Github logo instead rank level + +![Anurag's GitHub stats](https://github-readme-stats.vercel.app/api?username=anuraghazra&rank_icon=github) + - Customize Border Color ![Anurag's GitHub stats](https://github-readme-stats.vercel.app/api?username=anuraghazra&border_color=2e4058) diff --git a/src/cards/stats-card.js b/src/cards/stats-card.js index c60ea51d5119c0..cdb468d301f784 100644 --- a/src/cards/stats-card.js +++ b/src/cards/stats-card.js @@ -1,7 +1,7 @@ // @ts-check import { Card } from "../common/Card.js"; import { I18n } from "../common/I18n.js"; -import { icons } from "../common/icons.js"; +import { icons, rankIcon } from "../common/icons.js"; import { clampValue, flexLayout, @@ -108,6 +108,7 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => { number_format = "short", locale, disable_animations = false, + rank_icon = "default", } = options; const lheight = parseInt(String(line_height), 10); @@ -294,15 +295,7 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => { - - ${rank.level} - + ${rankIcon(rank_icon, rank?.level)}
`; diff --git a/src/cards/types.d.ts b/src/cards/types.d.ts index a3abc23e98a36c..02a41b57693877 100644 --- a/src/cards/types.d.ts +++ b/src/cards/types.d.ts @@ -1,4 +1,5 @@ type ThemeNames = keyof typeof import("../../themes/index.js"); +type RankIcon = "default" | "github"; export type CommonOptions = { title_color: string; @@ -23,6 +24,9 @@ export type StatCardOptions = CommonOptions & { custom_title: string; disable_animations: boolean; number_format: string; + ring_color: string; + text_bold: boolean; + rank_icon: RankIcon; }; export type RepoCardOptions = CommonOptions & { diff --git a/src/common/icons.js b/src/common/icons.js index 5282a93ec8725e..948ca0bc427d15 100644 --- a/src/common/icons.js +++ b/src/common/icons.js @@ -8,5 +8,28 @@ const icons = { fork: ``, }; -export { icons }; +/** + * Get rank icon + * + * @returns {string} - The SVG code of the rank icon + */ +const rankIcon = (rankIcon, rankLevel) => { + switch (rankIcon) { + case "github": + return ` + + `; + case "default": + default: + return ` + + ${rankLevel} + + `; + } +}; + +export { icons, rankIcon }; export default icons; diff --git a/tests/renderStatsCard.test.js b/tests/renderStatsCard.test.js index 110121ac9e4b66..25c5feb6048645 100644 --- a/tests/renderStatsCard.test.js +++ b/tests/renderStatsCard.test.js @@ -366,4 +366,21 @@ describe("Test renderStatsCard", () => { document.body.innerHTML = renderStatsCard(stats, { number_format: "long" }); expect(getByTestId(document.body, "commits").textContent).toBe("1999"); }); + + it("should render default rank icon with level A+", () => { + document.body.innerHTML = renderStatsCard(stats, { + rank_icon: "default", + }); + expect(queryByTestId(document.body, "level-rank-icon")).toBeDefined(); + expect( + queryByTestId(document.body, "level-rank-icon").textContent.trim(), + ).toBe("A+"); + }); + + it("should render github rank icon", () => { + document.body.innerHTML = renderStatsCard(stats, { + rank_icon: "github", + }); + expect(queryByTestId(document.body, "github-rank-icon")).toBeDefined(); + }); }); From aec73d293ec6a8523b8889811d2e38f4cd3bc261 Mon Sep 17 00:00:00 2001 From: LOKESH SINGH <77314004+LokeshXs@users.noreply.github.com> Date: Mon, 24 Apr 2023 23:54:05 +0530 Subject: [PATCH 34/82] Updated the custom error there was a Typo. (#2618) "Something went while trying to retrieve the stats data using the GraphQL API." -> "Something went wrong while trying to retrieve the stats data using the GraphQL API." --- src/fetchers/stats-fetcher.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fetchers/stats-fetcher.js b/src/fetchers/stats-fetcher.js index a7df1e504db2f8..fc06fce15fa86b 100644 --- a/src/fetchers/stats-fetcher.js +++ b/src/fetchers/stats-fetcher.js @@ -213,7 +213,7 @@ const fetchStats = async ( ); } throw new CustomError( - "Something went while trying to retrieve the stats data using the GraphQL API.", + "Something went wrong while trying to retrieve the stats data using the GraphQL API.", CustomError.GRAPHQL_ERROR, ); } From be0d96615d9ae3cf4b4fc6cc07921ddca7efae11 Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov <53787217+qwerty541@users.noreply.github.com> Date: Mon, 24 Apr 2023 21:24:41 +0300 Subject: [PATCH 35/82] Fix typos inside wakatime test name (#2617) --- tests/renderWakatimeCard.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/renderWakatimeCard.test.js b/tests/renderWakatimeCard.test.js index 67969bef500637..25e7ab4d802cd3 100644 --- a/tests/renderWakatimeCard.test.js +++ b/tests/renderWakatimeCard.test.js @@ -55,7 +55,7 @@ describe("Test Render Wakatime Card", () => { expect(document.querySelector("rect")).toHaveAttribute("rx", "4.5"); }); - it('should show "no coding activitiy this week" message when there hasn not been activity', () => { + it('should show "no coding activity this week" message when there has not been activity', () => { document.body.innerHTML = renderWakatimeCard( { ...wakaTimeData.data, From da5f82ff02c203a493abd1778715f0f498f7c37d Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov <53787217+qwerty541@users.noreply.github.com> Date: Tue, 25 Apr 2023 09:15:16 +0300 Subject: [PATCH 36/82] removed redundant comparison (#957) --- src/cards/stats-card.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cards/stats-card.js b/src/cards/stats-card.js index cdb468d301f784..92701d8103c950 100644 --- a/src/cards/stats-card.js +++ b/src/cards/stats-card.js @@ -183,7 +183,7 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => { "nl", "zh-tw", ]; - const isLongLocale = longLocales.includes(locale) === true; + const isLongLocale = longLocales.includes(locale); // filter out hidden stats defined by user & create the text nodes const statItems = Object.keys(STATS) From 8aacec098ba92e697c9ec3b5b1b67657bfaf366e Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov <53787217+qwerty541@users.noreply.github.com> Date: Tue, 25 Apr 2023 09:16:37 +0300 Subject: [PATCH 37/82] dev (#1782) --- docs/readme_cn.md | 2 ++ docs/readme_de.md | 2 ++ docs/readme_fr.md | 2 ++ docs/readme_it.md | 2 ++ docs/readme_ja.md | 2 ++ docs/readme_kr.md | 2 ++ docs/readme_nl.md | 2 ++ docs/readme_np.md | 4 ++++ 8 files changed, 18 insertions(+) diff --git a/docs/readme_cn.md b/docs/readme_cn.md index 17fd2b710f2202..f04324c9f44528 100644 --- a/docs/readme_cn.md +++ b/docs/readme_cn.md @@ -53,6 +53,8 @@ Nederlands . नेपाली + . + Türkçe

喜欢这个项目?请考虑捐赠来帮助它完善! diff --git a/docs/readme_de.md b/docs/readme_de.md index 55523fe342ef00..87f68faa7465a1 100644 --- a/docs/readme_de.md +++ b/docs/readme_de.md @@ -54,6 +54,8 @@ Nederlands . नेपाली + . + Türkçe

Du magst das Projekt? Wie wäre es mit einer kleinen Spende um es weiterhin am Leben zu erhalten? diff --git a/docs/readme_fr.md b/docs/readme_fr.md index 20996bd66dda8a..bf0ab9811b4b0d 100644 --- a/docs/readme_fr.md +++ b/docs/readme_fr.md @@ -53,6 +53,8 @@ Nederlands . नेपाली + . + Türkçe

Vous aimez ce projet? Pensez à faire un don pour l'améliorer! diff --git a/docs/readme_it.md b/docs/readme_it.md index 1b2df96a5044d0..05aa3e5c526ae4 100644 --- a/docs/readme_it.md +++ b/docs/readme_it.md @@ -53,6 +53,8 @@ Nederlands . नेपाली + . + Türkçe

Se ti piace questo progetto, considera la possibilità di donare per aiutare a renderlo migliore! diff --git a/docs/readme_ja.md b/docs/readme_ja.md index b00c77a7712a1f..0318984e40b97c 100644 --- a/docs/readme_ja.md +++ b/docs/readme_ja.md @@ -53,6 +53,8 @@ Nederlands . नेपाली + . + Türkçe

このプロジェクトを気に入っていただけましたか?
もしよろしければ、プロジェクトのさらなる改善のために寄付を検討して頂けると嬉しいです!

diff --git a/docs/readme_kr.md b/docs/readme_kr.md index ce0b4ad0379eed..1d64d50d2c0b77 100644 --- a/docs/readme_kr.md +++ b/docs/readme_kr.md @@ -53,6 +53,8 @@ Nederlands . नेपाली + . + Türkçe

기능들이 마음에 드시나요? 괜찮으시다면, 서비스 개선을 위해 기부를 고려해주세요! diff --git a/docs/readme_nl.md b/docs/readme_nl.md index 597f0c86445e36..bab581762572cf 100644 --- a/docs/readme_nl.md +++ b/docs/readme_nl.md @@ -53,6 +53,8 @@ Nederlands . नेपाली + . + Türkçe

Bevalt het project? Doneer om het te verbeteren! diff --git a/docs/readme_np.md b/docs/readme_np.md index 654427fbda6549..510e836f2d8b64 100644 --- a/docs/readme_np.md +++ b/docs/readme_np.md @@ -49,8 +49,12 @@ Italiano · 한국어 + . + Nederlands · नेपाली + . + Türkçe

परियोजना मनपर्‍यो? तपाईं मद्दत गर्न सक्नुहुन्छ यो परियोजना बढ्न From 21a9ba4faeae5072f5c46009ab2f11bc3258508b Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov <53787217+qwerty541@users.noreply.github.com> Date: Tue, 25 Apr 2023 09:17:10 +0300 Subject: [PATCH 38/82] fix path to powered by vercel image (#1792) --- docs/readme_tr.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/readme_tr.md b/docs/readme_tr.md index 7b11cf3706f1e1..4e67c7ed00f134 100644 --- a/docs/readme_tr.md +++ b/docs/readme_tr.md @@ -409,7 +409,7 @@ Teşekkürler! :heart: --- -[![https://vercel.com?utm_source=github_readme_stats_team&utm_campaign=oss](./powered-by-vercel.svg)](https://vercel.com?utm_source=github_readme_stats_team&utm_campaign=oss) +[![https://vercel.com?utm_source=github_readme_stats_team&utm_campaign=oss](../powered-by-vercel.svg)](https://vercel.com?utm_source=github_readme_stats_team&utm_campaign=oss) Katkılara açığız! <3 From da46a5093cd0f6c4a537196cb9e67865bbbe3aed Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov <53787217+qwerty541@users.noreply.github.com> Date: Tue, 25 Apr 2023 09:17:50 +0300 Subject: [PATCH 39/82] Synchonize cache seconds min value inside docs translations with main readme (#2616) --- docs/readme_cn.md | 2 +- docs/readme_de.md | 2 +- docs/readme_es.md | 2 +- docs/readme_fr.md | 2 +- docs/readme_it.md | 2 +- docs/readme_ja.md | 2 +- docs/readme_kr.md | 2 +- docs/readme_nl.md | 2 +- docs/readme_np.md | 2 +- docs/readme_pt-BR.md | 2 +- docs/readme_tr.md | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/readme_cn.md b/docs/readme_cn.md index f04324c9f44528..d6487b66182647 100644 --- a/docs/readme_cn.md +++ b/docs/readme_cn.md @@ -140,7 +140,7 @@ dark, radical, merko, gruvbox, tokyonight, onedark, cobalt, synthwave, highcontr - `bg_color` - 卡片背景颜色 _(十六进制色码)_ **或者** 以 _angle,start,end_ 的形式渐变 - `hide_border` - 隐藏卡的边框 _(布尔值)_ - `theme` - 主题名称,从[所有可用主题](../themes/README.md)中选择 -- `cache_seconds` - 手动设置缓存头 _(最小值: 1800,最大值: 86400)_ +- `cache_seconds` - 手动设置缓存头 _(最小值: 14400,最大值: 86400)_ - `locale` - 在卡片中设置语言 _(例如 cn, de, es, 等等)_ ##### bg_color 渐变 diff --git a/docs/readme_de.md b/docs/readme_de.md index 87f68faa7465a1..8756670eaf4b91 100644 --- a/docs/readme_de.md +++ b/docs/readme_de.md @@ -130,7 +130,7 @@ Du kannst das Erscheinungsbild deiner `Stats Card` oder `Repo Card`, mithilfe vo - `bg_color` - Hintergrundfarbe _(hex color)_ **oder** ein Farbverlauf in der Form von _winkel,start,ende_ - `hide_border` - Blendet den Rand der Karte aus _(Boolean)_ - `theme` - Name des Erscheinungsbildes/Themes [alle verfügbaren Themes](../themes/README.md) -- `cache_seconds` - manuelles festlegen der Cachezeiten _(min: 1800, max: 86400)_ +- `cache_seconds` - manuelles festlegen der Cachezeiten _(min: 14400, max: 86400)_ - `locale` - Stellen Sie die Sprache auf der Karte ein _(z.B. cn, de, es, etc.)_ ##### Farbverlauf in bg_color diff --git a/docs/readme_es.md b/docs/readme_es.md index 5ddceec7b5e99f..b477e5c729cab3 100644 --- a/docs/readme_es.md +++ b/docs/readme_es.md @@ -142,7 +142,7 @@ Puedes personalizar el aspecto de tu `Tarjeta de Estadísticas` o `Tarjeta de Re - `bg_color` - Color de fondo _(hex color)_ - `hide_border` - Oculta el borde de la tarjeta _(booleano)_ - `theme` - Nombre del tema, elige uno de [todos los temas disponible ](../themes/README.md) -- `cache_seconds` - Cache _(min: 1800, max: 86400)_ +- `cache_seconds` - Cache _(min: 14400, max: 86400)_ - `locale` - configurar el idioma en la tarjeta _(p.ej. cn, de, es, etc.)_ ##### Gradiente en `bg_color` diff --git a/docs/readme_fr.md b/docs/readme_fr.md index bf0ab9811b4b0d..ce0d3d495cc10c 100644 --- a/docs/readme_fr.md +++ b/docs/readme_fr.md @@ -140,7 +140,7 @@ Vous pouvez personnaliser l'apparence de votre `Carte des stats` ou `Carte de d - `bg_color` - Couleur du fond de la carte _(hex color)_ **ou** un gradiant de la forme _angle,start,end_ - `hide_border` - Cache la bordure de la carte _(booléen)_ - `theme` - Nom du thème, parmis [tous les thèmes disponibles](../themes/README.md) -- `cache_seconds` - Paramétrer le cache manuellement _(min: 1800, max: 86400)_ +- `cache_seconds` - Paramétrer le cache manuellement _(min: 14400, max: 86400)_ - `locale` - définir la langue de la carte _(par exemple. cn, de, es, etc.)_ ##### Gradient in bg_color diff --git a/docs/readme_it.md b/docs/readme_it.md index 05aa3e5c526ae4..e54af7dc488ad1 100644 --- a/docs/readme_it.md +++ b/docs/readme_it.md @@ -140,7 +140,7 @@ Puoi personalizzare l'aspetto delle tue `Stats Card` o delle `Repo Card` in qual - `bg_color` - Colore dello sfondo _(in esadecimale)_ **oppure** un gradiente nella forma _angolo,inizio,fine_ - `hide_border` - Nasconde il bordo della carta _(booleano)_ - `theme` - Nome del tema, dai un'occhiata a [tutti i temi disponibili](../themes/README.md) -- `cache_seconds` - Specifica manualmente il valore di cache, in secondi _(min: 1800, max: 86400)_ +- `cache_seconds` - Specifica manualmente il valore di cache, in secondi _(min: 14400, max: 86400)_ - `locale` - Impostare la lingua nella scheda _(per esempio. cn, de, es, eccetera.)_ ##### Gradiente nello sfondo diff --git a/docs/readme_ja.md b/docs/readme_ja.md index 0318984e40b97c..2c2def7fca1a61 100644 --- a/docs/readme_ja.md +++ b/docs/readme_ja.md @@ -141,7 +141,7 @@ dark, radical, merko, gruvbox, tokyonight, onedark, cobalt, synthwave, highcontr - `bg_color` - 背景の色 _(16 進数カラーコード)_ **または** _angle,start,end_ の形式でグラデーションを指定することも可 - `hide_border` - カードの境界線を非表示にします _(ブール値)_ - `theme` - [使用可能なテーマ一覧](../themes/README.md) から選んだテーマ名 -- `cache_seconds` - キャッシュ時間の秒数 _(最小値: 1800, 最大値: 86400)_ +- `cache_seconds` - キャッシュ時間の秒数 _(最小値: 14400, 最大値: 86400)_ - `locale` - カードに言語を設定する _(例えば cn, de, es, 等)_ ##### bg_color の グラデーション指定 diff --git a/docs/readme_kr.md b/docs/readme_kr.md index 1d64d50d2c0b77..4a1c57cc1e9778 100644 --- a/docs/readme_kr.md +++ b/docs/readme_kr.md @@ -151,7 +151,7 @@ dark, radical, merko, gruvbox, tokyonight, onedark, cobalt, synthwave, highcontr - `bg_color` - 카드의 배경 색상 _(hex color)_ **혹은** 다음 양식으로 그라데이션 주기 _angle,start,end_ - `hide_border` - 카드의 테두리 표시 여부 _(boolean)_ - `theme` - 테마의 이름, [사용 가능한 모든 테마](../themes/README.md) 에서 선택 -- `cache_seconds` - 수동으로 캐시 헤더 설정 _(min: 1800, max: 86400)_ +- `cache_seconds` - 수동으로 캐시 헤더 설정 _(min: 14400, max: 86400)_ - `locale` - 카드에 표시할 언어 _(e.g. kr, cn, de, es, etc.)_ ##### 배경에 그라데이션 주기 diff --git a/docs/readme_nl.md b/docs/readme_nl.md index bab581762572cf..b279c4f71fff41 100644 --- a/docs/readme_nl.md +++ b/docs/readme_nl.md @@ -144,7 +144,7 @@ Je kan het uiterlijk van je `Statistieken kaart` of `Repo kaart` aanpassen hoe j - `bg_color` - Achtergrond kleur van de kaart _(hex kleur)_ **of** een verloop van kleuren in het formaat van _graden,start,einde_ - `hide_border` - Verbergt de rand van de kaart _(boolean)_ - `theme` - Naam van het thema, kies uit [alle beschikbare thema\'s](../themes/README.md) -- `cache_seconds` - Stel de cache header handmatig in _(min: 1800, max: 86400)_ +- `cache_seconds` - Stel de cache header handmatig in _(min: 14400, max: 86400)_ - `locale` - Stel taal van de kaart in _(e.g. cn, de, es, etc.)_ ##### Kleurenverloop in bg_color (achtergrond kleur): diff --git a/docs/readme_np.md b/docs/readme_np.md index 510e836f2d8b64..e90ee57bfe6b38 100644 --- a/docs/readme_np.md +++ b/docs/readme_np.md @@ -142,7 +142,7 @@ dark, radical, merko, gruvbox, tokyonight, onedark, cobalt, synthwave, highcontr - `bg_color` - Card's background color _(hex color)_ **or** a gradient in the form of _angle,start,end_ - `hide_border` - Hides the card's border _(boolean)_ - `theme` - name of the theme, choose from [all available themes](./themes/README.md) -- `cache_seconds` - set the cache header manually _(min: 1800, max: 86400)_ +- `cache_seconds` - set the cache header manually _(min: 14400, max: 86400)_ - `locale` - set the language in the card _(e.g. cn, de, es, etc.)_ ##### Gradient in bg_color diff --git a/docs/readme_pt-BR.md b/docs/readme_pt-BR.md index 62c23dc55c7e2b..1ac57716009be1 100644 --- a/docs/readme_pt-BR.md +++ b/docs/readme_pt-BR.md @@ -141,7 +141,7 @@ Personalize a aparência do seu `Stats Card` ou `Repo Card` da maneira que desej - `bg_color` - Cor de fundo do cartão _(hex color)_ - `hide_border` - Esconde a borda do cartão _(boleano)_ - `theme` - Nome do tema, escolha em [todos os temas disponíveis](../themes/README.md) -- `cache_seconds` - Defina o cabeçalho do cache manualmente _(min: 1800, max: 86400)_ +- `cache_seconds` - Defina o cabeçalho do cache manualmente _(min: 14400, max: 86400)_ - `locale` - defina o idioma no cartão _(por exemplo. cn, de, es, etc.)_ > Nota sobre o cache: Cartões de repositório tem um cache padrão de 30 minutos (1800 segundos), se o número a contagem de forks e contagem de estrelas é menor que 1 mil o padrão é 2 horas (7200 segundos). Note também que o cache é limitado a um mínimo de 30 minutos e um máximo de 24 horas. diff --git a/docs/readme_tr.md b/docs/readme_tr.md index 4e67c7ed00f134..d8ae9778fb50ca 100644 --- a/docs/readme_tr.md +++ b/docs/readme_tr.md @@ -143,7 +143,7 @@ dark, radical, merko, gruvbox, tokyonight, onedark, cobalt, synthwave, highcontr - `bg_color` - Kartın arkaplan rengi _(hex color / hex rengi)_ **ya da** gradient şeklinde _açı,başlangıç,bitiş_ - `hide_border` - Kartın çerçevelerini gizler _(boolean)_ - `theme` - Temanın rengi [tüm temalar](./themes/README.md) -- `cache_seconds` - Manuel olarak cache'i belirleyebilirsiniz _(en az: 1800, en fazla: 86400)_ +- `cache_seconds` - Manuel olarak cache'i belirleyebilirsiniz _(en az: 14400, en fazla: 86400)_ - `locale` - Karttaki dili seçebilirsiniz _(örneğin; tr, cn, de, es, vb.)_ ##### bg_color'da Gradient From 5577bbf07fae7f0e2fcbed24042a59e5442434dc Mon Sep 17 00:00:00 2001 From: kitswas <90329875+kitswas@users.noreply.github.com> Date: Tue, 25 Apr 2023 11:49:05 +0530 Subject: [PATCH 40/82] New top language algorithm implementation (#1732) * Reduced vercel maxDuration * Implemented new algorithm for Top Langs * Revert "Reduced vercel maxDuration" This reverts commit b0bc626efe12c738cf5005e7f11c7d2a07b6387a. * Added documentation * Fixed broken implementation * Update fetchTopLanguages.test.js Changed tests * Now uses the general formula The parameters p and q can be set by the user. * Updated tests and added new test * Added new test New test for order by repo count. * Updated documentation Added explanation and examples for new options. * Updated documentation This was overwritten in the merge commit. * docs: improve docs and fix tests * Renamed parameters Renamed `p` and `q` to `size_weight` and `count_weight`, respectively. * Updated the documentation Changes introduced in f2516d60a442dfdbb9e24ddda8743664bcb8064d --------- Co-authored-by: rickstaa --- api/top-langs.js | 4 +++ readme.md | 40 +++++++++++++++------ src/fetchers/top-languages-fetcher.js | 24 +++++++++++-- tests/fetchTopLanguages.test.js | 52 ++++++++++++++++++++++++--- 4 files changed, 104 insertions(+), 16 deletions(-) diff --git a/api/top-langs.js b/api/top-langs.js index e67d9533234417..cde0a9af08a93b 100644 --- a/api/top-langs.js +++ b/api/top-langs.js @@ -25,6 +25,8 @@ export default async (req, res) => { layout, langs_count, exclude_repo, + size_weight, + count_weight, custom_title, locale, border_radius, @@ -46,6 +48,8 @@ export default async (req, res) => { const topLangs = await fetchTopLanguages( username, parseArray(exclude_repo), + size_weight, + count_weight, ); const cacheSeconds = clampValue( diff --git a/readme.md b/readme.md index fe04a88e81bbab..76b46e0eaefb5c 100644 --- a/readme.md +++ b/readme.md @@ -310,6 +310,8 @@ You can provide multiple comma-separated values in the bg_color option to render - `custom_title` - Sets a custom title for the card _(string)_. Default `Most Used Languages`. - `disable_animations` - Disables all animations in the card _(boolean)_. Default: `false`. - `hide_progress` - It uses the compact layout option, hides percentages, and removes the bars. Default: `false`. +- `size_weight` - Configures language stats algorithm _(number)_ (see [Language stats algorithm](#Language-stats-algorithm)), defaults to 1. +- `count_weight` - Configures language stats algorithm _(number)_ (see [Language stats algorithm](#Language-stats-algorithm)), defaults to 0. > **Warning** > Language names should be URI-escaped, as specified in [Percent Encoding](https://en.wikipedia.org/wiki/Percent-encoding) @@ -359,7 +361,25 @@ Use [show_owner](#customization) variable to include the repo's owner username The top languages card shows a GitHub user's most frequently used top language. > **Note** -> Top Languages does not indicate my skill level or anything like that; it's a GitHub metric to determine which languages have the most code on GitHub. It is a new feature of github-readme-stats. +> Top Languages does not indicate the user's skill level or anything like that; it's a GitHub metric to determine which languages have the most code on GitHub. It is a new feature of github-readme-stats. + +### Language stats algorithm + +We use the following algorithm to calculate the languages percentages on the language card: + +```js +ranking_index = (byte_count ^ size_weight) * (repo_count ^ count_weight) +``` + +By default, only the byte count is used for determining the languages percentages shown on the language card (i.e. `size_weight=1` and `count_weight=0`). You can, however, use the `&size_weight=` and `&count_weight=` options to weight the language usage calculation. The values must be positive real numbers. [More details about the algorithm can be found here](https://github.com/anuraghazra/github-readme-stats/issues/1600#issuecomment-1046056305). + +- `&size_weight=1&count_weight=0` - _(default)_ Orders by byte count. +- `&size_weight=0.5&count_weight=0.5` - _(recommended)_ Uses both byte and repo count for ranking +- `&size_weight=0&count_weight=1` - Orders by repo count + +```md +[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&size_weight=0.5&count_weight=0.5)](https://github.com/anuraghazra/github-readme-stats) +``` ### Usage @@ -419,7 +439,7 @@ You can use the `&hide_progress=true` option to hide the percentages and the pro [![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=compact)](https://github.com/anuraghazra/github-readme-stats) -- Hidden progress bars +- Hidden progress bars [![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) @@ -564,14 +584,14 @@ Since the GitHub API only allows 5k requests per hour, my `https://github-readme

:hammer_and_wrench: Step-by-step guide for deploying on other platforms -1. Fork or clone this repo as per your needs -2. Add `express` to the dependencies section of `package.json` -https://github.com/anuraghazra/github-readme-stats/blob/ba7c2f8b55eac8452e479c8bd38b044d204d0424/package.json#L54-L61 -3. Run `npm i` if needed (initial setup) -4. Run `node express.js` to start the server, or set the entry point to `express.js` in `package.json` if you're deploying on a managed service -https://github.com/anuraghazra/github-readme-stats/blob/ba7c2f8b55eac8452e479c8bd38b044d204d0424/package.json#L11 -5. You're done 🎉 -
+1. Fork or clone this repo as per your needs +2. Add `express` to the dependencies section of `package.json` + +3. Run `npm i` if needed (initial setup) +4. Run `node express.js` to start the server, or set the entry point to `express.js` in `package.json` if you're deploying on a managed service + +5. You're done 🎉 + ### Keep your fork up to date diff --git a/src/fetchers/top-languages-fetcher.js b/src/fetchers/top-languages-fetcher.js index 86d794435be088..45b2ba7d851837 100644 --- a/src/fetchers/top-languages-fetcher.js +++ b/src/fetchers/top-languages-fetcher.js @@ -54,7 +54,12 @@ const fetcher = (variables, token) => { * @param {string[]} exclude_repo List of repositories to exclude. * @returns {Promise} Top languages data. */ -const fetchTopLanguages = async (username, exclude_repo = []) => { +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 }); @@ -101,6 +106,8 @@ const fetchTopLanguages = async (username, exclude_repo = []) => { .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 @@ -111,9 +118,14 @@ const fetchTopLanguages = async (username, exclude_repo = []) => { // 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. + // 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, @@ -121,10 +133,18 @@ const fetchTopLanguages = async (username, exclude_repo = []) => { 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) => { diff --git a/tests/fetchTopLanguages.test.js b/tests/fetchTopLanguages.test.js index 24416cd294525e..c3f558bf4236ff 100644 --- a/tests/fetchTopLanguages.test.js +++ b/tests/fetchTopLanguages.test.js @@ -60,20 +60,22 @@ const error = { }; describe("FetchTopLanguages", () => { - it("should fetch correct language data", async () => { + it("should fetch correct language data while using the new calculation", async () => { mock.onPost("https://api.github.com/graphql").reply(200, data_langs); - let repo = await fetchTopLanguages("anuraghazra"); + let repo = await fetchTopLanguages("anuraghazra", [], 0.5, 0.5); expect(repo).toStrictEqual({ HTML: { color: "#0f0", + count: 2, name: "HTML", - size: 200, + size: 20.000000000000004, }, javascript: { color: "#0ff", + count: 2, name: "javascript", - size: 200, + size: 20.000000000000004, }, }); }); @@ -85,17 +87,59 @@ describe("FetchTopLanguages", () => { expect(repo).toStrictEqual({ HTML: { color: "#0f0", + count: 1, name: "HTML", size: 100, }, javascript: { color: "#0ff", + count: 2, + name: "javascript", + size: 200, + }, + }); + }); + + it("should fetch correct language data while using the old calculation", async () => { + mock.onPost("https://api.github.com/graphql").reply(200, data_langs); + + let repo = await fetchTopLanguages("anuraghazra", [], 1, 0); + expect(repo).toStrictEqual({ + HTML: { + color: "#0f0", + count: 2, + name: "HTML", + size: 200, + }, + javascript: { + color: "#0ff", + count: 2, name: "javascript", size: 200, }, }); }); + it("should rank languages by the number of repositories they appear in", async () => { + mock.onPost("https://api.github.com/graphql").reply(200, data_langs); + + let repo = await fetchTopLanguages("anuraghazra", [], 0, 1); + expect(repo).toStrictEqual({ + HTML: { + color: "#0f0", + count: 2, + name: "HTML", + size: 2, + }, + javascript: { + color: "#0ff", + count: 2, + name: "javascript", + size: 2, + }, + }); + }); + it("should throw error", async () => { mock.onPost("https://api.github.com/graphql").reply(200, error); From f5f0a79bc12a35c58609f3fa6ced2f6722bbfdf1 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Fri, 28 Apr 2023 04:00:13 +0200 Subject: [PATCH 41/82] feat: improve CONTRIBUTING.md (#2609) --- CONTRIBUTING.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0d4b558abe6f10..7d450d6076d8e7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,11 +2,11 @@ We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's: -- Reporting an issue -- Discussing the current state of the code -- Submitting a fix -- Proposing new features -- Becoming a maintainer +- Reporting [an issue](https://github.com/anuraghazra/github-readme-stats/issues/new?assignees=&labels=bug&template=bug_report.yml). +- [Discussing](https://github.com/anuraghazra/github-readme-stats/discussions) the current state of the code. +- Submitting [a fix](https://github.com/anuraghazra/github-readme-stats/compare). +- Proposing [new features](https://github.com/anuraghazra/github-readme-stats/issues/new?assignees=&labels=enhancement&template=feature_request.yml). +- Becoming a maintainer. ## All Changes Happen Through Pull Requests @@ -33,11 +33,15 @@ _(make sure you already have a [Vercel](https://vercel.com/) account)_ 1. Install [Vercel CLI](https://vercel.com/download). 2. Fork the repository and clone the code to your local machine. 3. Run `npm install` in the repository root. -4. Run the command "vercel" in the root and follow the steps there. +4. Run the command `vercel` in the root and follow the steps there. 5. Open `vercel.json` and set the maxDuration to 10. 6. Create a `.env` file in the root of the directory. -7. In the .env file add a new variable named "PAT_1" with your [GitHub Personal Access Token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token). -8. Run the command "vercel dev" to start a development server at . +7. In the .env file add a new variable named `PAT_1` with your [GitHub Personal Access Token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token). +8. Run the command `vercel dev` to start a development server at . +9. The cards will then be available from this local endpoint (i.e. `https://localhost:3000/api?username=anuraghazra`). + +> **Note** +> You can also debug any tests using the [VSCode Jest extension](https://marketplace.visualstudio.com/items?itemName=Orta.vscode-jest). For more information see https://github.com/jest-community/vscode-jest/issues/912. ## Themes Contribution From fd64333211fa9f39f59028fcb3892d2cf2fbb57a Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Fri, 28 Apr 2023 04:00:33 +0200 Subject: [PATCH 42/82] docs: update give logo (#2605) --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 76b46e0eaefb5c..6b4cefdf84116c 100644 --- a/readme.md +++ b/readme.md @@ -66,7 +66,7 @@

Love the project? Please consider donating to help it improve!

- Give india logo + Give india logo Are you considering supporting the project by donating? Please DO NOT!! From 30a45d3c136033a0459d0c0423a4836d78e8b0e7 Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov <53787217+qwerty541@users.noreply.github.com> Date: Fri, 28 Apr 2023 10:50:57 +0300 Subject: [PATCH 43/82] Fixed typo in word color inside README (#2652) --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 6b4cefdf84116c..538b4e8840646e 100644 --- a/readme.md +++ b/readme.md @@ -183,7 +183,7 @@ We have included a `transparent` theme that has a transparent background. This t ##### Add transparent alpha channel to a themes bg_color -You can use the `bg_color` parameter to make any of [the available themes](./themes/README.md) transparent. This is done by setting the `bg_color` to a colour with a transparent alpha channel (i.e. `bg_color=00000000`): +You can use the `bg_color` parameter to make any of [the available themes](./themes/README.md) transparent. This is done by setting the `bg_color` to a color with a transparent alpha channel (i.e. `bg_color=00000000`): ```md ![Anurag's GitHub stats](https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&bg_color=00000000) From 6eebfe36e8406730ae13d088060eb5ea6503e1d5 Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov <53787217+qwerty541@users.noreply.github.com> Date: Fri, 28 Apr 2023 10:51:24 +0300 Subject: [PATCH 44/82] Fixed docs typo inside src/common/utils.js (#2651) --- src/common/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/utils.js b/src/common/utils.js index c600c717ae3e52..f82544604cf9c2 100644 --- a/src/common/utils.js +++ b/src/common/utils.js @@ -392,7 +392,7 @@ const lowercaseTrim = (name) => name.toLowerCase().trim(); /** * Split array of languages in two columns. * - * @template T Langauge object. + * @template T Language object. * @param {Array} arr Array of languages. * @param {number} perChunk Number of languages per column. * @returns {Array} Array of languages split in two columns. From a82a617a37a986c9ee7f9285190c82d106034974 Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov <53787217+qwerty541@users.noreply.github.com> Date: Fri, 28 Apr 2023 10:51:54 +0300 Subject: [PATCH 45/82] Fixed several typos inside preview theme script (#2650) --- scripts/preview-theme.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/preview-theme.js b/scripts/preview-theme.js index e18c01b8615aff..57b792a369c958 100644 --- a/scripts/preview-theme.js +++ b/scripts/preview-theme.js @@ -26,9 +26,9 @@ const FAIL_TEXT = ` \rUnfortunately, your theme PR contains an error or does not adhere to our [theme guidelines](https://github.com/anuraghazra/github-readme-stats/blob/master/CONTRIBUTING.md#themes-contribution). Please fix the issues below, and we will review your\ \r PR again. This pull request will **automatically close in 20 days** if no changes are made. After this time, you must re-open the PR for it to be reviewed. `; -const THEME_CONTRIB_GUIDELINESS = ` +const THEME_CONTRIB_GUIDELINES = ` \rHi, thanks for the theme contribution. Please read our theme [contribution guidelines](https://github.com/anuraghazra/github-readme-stats/blob/master/CONTRIBUTING.md#themes-contribution). - \rWe are currently only accepting color combinations from any VSCode theme or themes with good colour combinations to minimize bloating the themes collection. + \rWe are currently only accepting color combinations from any VSCode theme or themes with good color combinations to minimize bloating the themes collection. \r> Also, note that if this theme is exclusively for your personal use, then instead of adding it to our theme collection, you can use card [customization options](https://github.com/anuraghazra/github-readme-stats#customization). `; @@ -363,7 +363,7 @@ export const run = async () => { debug(`Context: ${inspect(github.context)}`); let commentBody = ` \r# ${COMMENT_TITLE} - \r${THEME_CONTRIB_GUIDELINESS} + \r${THEME_CONTRIB_GUIDELINES} `; const ccc = new ColorContrastChecker(); OCTOKIT = github.getOctokit(getGithubToken()); From 31d1ab43d0cae0ecbaba5748fb6a5ca9ae1c7609 Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov <53787217+qwerty541@users.noreply.github.com> Date: Fri, 28 Apr 2023 18:48:34 +0300 Subject: [PATCH 46/82] Fixed todo inside stats card data fetcher (#2649) --- src/calculateRank.js | 15 ++++++++------- src/fetchers/stats-fetcher.js | 1 - 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/calculateRank.js b/src/calculateRank.js index 24845bc7d99443..215c24d848c346 100644 --- a/src/calculateRank.js +++ b/src/calculateRank.js @@ -29,13 +29,14 @@ const normalcdf = (mean, sigma, to) => { /** * Calculates the users rank. * - * @param {number} totalRepos Total number of repos. - * @param {number} totalCommits Total number of commits. - * @param {number} contributions The number of contributions. - * @param {number} followers The number of followers. - * @param {number} prs The number of pull requests. - * @param {number} issues The number of issues. - * @param {number} stargazers The number of stars. + * @param {object} params Parameters on which the user's rank depends. + * @param {number} params.totalRepos Total number of repos. + * @param {number} params.totalCommits Total number of commits. + * @param {number} params.contributions The number of contributions. + * @param {number} params.followers The number of followers. + * @param {number} params.prs The number of pull requests. + * @param {number} params.issues The number of issues. + * @param {number} params.stargazers The number of stars. * @returns {{level: string, score: number}}} The users rank. */ const calculateRank = ({ diff --git a/src/fetchers/stats-fetcher.js b/src/fetchers/stats-fetcher.js index fc06fce15fa86b..8603e38bbf59d6 100644 --- a/src/fetchers/stats-fetcher.js +++ b/src/fetchers/stats-fetcher.js @@ -259,7 +259,6 @@ const fetchStats = async ( return prev + curr.stargazers.totalCount; }, 0); - // @ts-ignore // TODO: Fix this. stats.rank = calculateRank({ totalCommits: stats.totalCommits, totalRepos: user.repositories.totalCount, From 3bd6519d39cbd6b0a078af17c2756b2804d85fb3 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Sat, 29 Apr 2023 04:55:37 +0200 Subject: [PATCH 47/82] docs: fix README language card usage order (#2659) --- readme.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/readme.md b/readme.md index 538b4e8840646e..ae18c9e6002c6c 100644 --- a/readme.md +++ b/readme.md @@ -363,6 +363,16 @@ The top languages card shows a GitHub user's most frequently used top language. > **Note** > Top Languages does not indicate the user's skill level or anything like that; it's a GitHub metric to determine which languages have the most code on GitHub. It is a new feature of github-readme-stats. +### Usage + +Copy-paste this code into your readme and change the links. + +Endpoint: `api/top-langs?username=anuraghazra` + +```md +[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra)](https://github.com/anuraghazra/github-readme-stats) +``` + ### Language stats algorithm We use the following algorithm to calculate the languages percentages on the language card: @@ -381,16 +391,6 @@ By default, only the byte count is used for determining the languages percentage [![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&size_weight=0.5&count_weight=0.5)](https://github.com/anuraghazra/github-readme-stats) ``` -### Usage - -Copy-paste this code into your readme and change the links. - -Endpoint: `api/top-langs?username=anuraghazra` - -```md -[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra)](https://github.com/anuraghazra/github-readme-stats) -``` - ### Exclude individual repositories You can use the `&exclude_repo=repo1,repo2` parameter to exclude individual repositories. From 2f4279e73883cb64712a5083a1577c602202b6a4 Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov <53787217+qwerty541@users.noreply.github.com> Date: Sun, 30 Apr 2023 09:20:26 +0300 Subject: [PATCH 48/82] Fixed vscode type error in get card colors function (#2660) --- src/common/utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/utils.js b/src/common/utils.js index f82544604cf9c2..47e106140b6d05 100644 --- a/src/common/utils.js +++ b/src/common/utils.js @@ -194,15 +194,15 @@ const flexLayout = ({ items, gap, direction, sizes = [] }) => { /** * Returns theme based colors with proper overrides and defaults. * - * @param {Object[]} args Function arguments. + * @param {Object} args Function arguments. * @param {string} args.title_color Card title color. * @param {string} args.text_color Card text color. * @param {string} args.icon_color Card icon color. * @param {string} args.bg_color Card background color. * @param {string} args.border_color Card border color. + * @param {string} args.ring_color Card ring color. * @param {string} args.theme Card theme. * @param {string} args.fallbackTheme Fallback theme. - * */ const getCardColors = ({ title_color, From cb99414d3dc6027139bed09006deee0615847239 Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov <53787217+qwerty541@users.noreply.github.com> Date: Mon, 1 May 2023 09:21:29 +0300 Subject: [PATCH 49/82] Show no activity error in compact layout of wakatime card (#2662) --- src/cards/wakatime-card.js | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/cards/wakatime-card.js b/src/cards/wakatime-card.js index 2c329558b8a354..77521966dcd2cb 100644 --- a/src/cards/wakatime-card.js +++ b/src/cards/wakatime-card.js @@ -267,12 +267,20 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
${compactProgressBar} - ${createLanguageTextNode({ - x: 0, - y: 25, - langs: filteredLanguages, - totalSize: 100, - }).join("")} + ${ + filteredLanguages.length + ? createLanguageTextNode({ + x: 0, + y: 25, + langs: filteredLanguages, + totalSize: 100, + }).join("") + : noCodingActivityNode({ + // @ts-ignore + color: textColor, + text: i18n.t("wakatimecard.nocodingactivity"), + }) + } `; } else { finalLayout = flexLayout({ From a4d6fe104f8c7df2c3f0fc6619aa68306e458c5d Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov <53787217+qwerty541@users.noreply.github.com> Date: Wed, 3 May 2023 08:04:33 +0300 Subject: [PATCH 50/82] Fixed card constructor docstring to resolve vscode type errors (#2674) --- src/common/Card.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/common/Card.js b/src/common/Card.js index 2f1d9c29f274de..c61b17db73a2b5 100644 --- a/src/common/Card.js +++ b/src/common/Card.js @@ -12,6 +12,12 @@ class Card { * @param {string?=} args.customTitle Card custom title. * @param {string?=} args.defaultTitle Card default title. * @param {string?=} args.titlePrefixIcon Card title prefix icon. + * @param {object?=} args.colors Card colors arguments. + * @param {string} args.colors.titleColor Card title color. + * @param {string} args.colors.textColor Card text color. + * @param {string} args.colors.iconColor Card icon color. + * @param {string|Array} args.colors.bgColor Card background color. + * @param {string} args.colors.borderColor Card border color. * @returns {Card} Card instance. */ constructor({ From 0dc35316669fffef34cf3a5339fd7f7d985a04f5 Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov <53787217+qwerty541@users.noreply.github.com> Date: Thu, 4 May 2023 10:13:47 +0300 Subject: [PATCH 51/82] Fixed wakatime create text node func docstring to resolve vscode type errors (#2678) --- src/cards/wakatime-card.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/cards/wakatime-card.js b/src/cards/wakatime-card.js index 77521966dcd2cb..4cf06f866fd8a6 100644 --- a/src/cards/wakatime-card.js +++ b/src/cards/wakatime-card.js @@ -86,14 +86,15 @@ const createLanguageTextNode = ({ langs, totalSize, x, y }) => { /** * Create WakaTime text item. * - * @param {Object[]} args The function arguments. - * @param {string} id The id of the text node item. - * @param {string} label The label of the text node item. - * @param {string} value The value of the text node item. - * @param {number} index The index of the text node item. - * @param {percent} percent Percentage of the text node item. - * @param {boolean} hideProgress Whether to hide the progress bar. - * @param {string} progressBarBackgroundColor The color of the progress bar background. + * @param {Object} args The function arguments. + * @param {string} args.id The id of the text node item. + * @param {string} args.label The label of the text node item. + * @param {string} args.value The value of the text node item. + * @param {number} args.index The index of the text node item. + * @param {string} args.percent Percentage of the text node item. + * @param {boolean} args.hideProgress Whether to hide the progress bar. + * @param {string} args.progressBarColor The color of the progress bar. + * @param {string} args.progressBarBackgroundColor The color of the progress bar background. */ const createTextNode = ({ id, From 311204f4f9712a5ab6fb94c616a1c4310dd28006 Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov <53787217+qwerty541@users.noreply.github.com> Date: Fri, 5 May 2023 17:06:52 +0300 Subject: [PATCH 52/82] Fixed docstring for several wakatime card gen functions to resolve multiple type errors (#2690) --- src/cards/wakatime-card.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/cards/wakatime-card.js b/src/cards/wakatime-card.js index 4cf06f866fd8a6..0b2366763d6ef5 100644 --- a/src/cards/wakatime-card.js +++ b/src/cards/wakatime-card.js @@ -36,11 +36,11 @@ const noCodingActivityNode = ({ color, text }) => { /** * Create compact WakaTime layout. * - * @param {Object[]} args The function arguments. - * @param {import("../fetchers/types").WakaTimeLang[]} languages The languages array. - * @param {number} totalSize The total size of the languages. - * @param {number} x The x position of the language node. - * @param {number} y The y position of the language node. + * @param {Object} args The function arguments. + * @param {import("../fetchers/types").WakaTimeLang} args.lang The languages array. + * @param {number} args.totalSize The total size of the languages. + * @param {number} args.x The x position of the language node. + * @param {number} args.y The y position of the language node. */ const createCompactLangNode = ({ lang, totalSize, x, y }) => { const color = languageColors[lang.name] || "#858585"; @@ -58,11 +58,11 @@ const createCompactLangNode = ({ lang, totalSize, x, y }) => { /** * Create WakaTime language text node item. * - * @param {Object[]} args The function arguments. - * @param {import("../fetchers/types").WakaTimeLang} lang The language object. - * @param {number} totalSize The total size of the languages. - * @param {number} x The x position of the language node. - * @param {number} y The y position of the language node. + * @param {Object} args The function arguments. + * @param {import("../fetchers/types").WakaTimeLang[]} args.langs The language objects. + * @param {number} args.totalSize The total size of the languages. + * @param {number} args.x The x position of the language node. + * @param {number} args.y The y position of the language node. */ const createLanguageTextNode = ({ langs, totalSize, x, y }) => { return langs.map((lang, index) => { From c6dab8b0f2a0ecf66b1d7e797579a31c950897e5 Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov <53787217+qwerty541@users.noreply.github.com> Date: Fri, 5 May 2023 17:07:15 +0300 Subject: [PATCH 53/82] Fixed docstring for stats card create text node function to resolve type errors (#2689) --- src/cards/stats-card.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/cards/stats-card.js b/src/cards/stats-card.js index 92701d8103c950..4761d023e4ab1d 100644 --- a/src/cards/stats-card.js +++ b/src/cards/stats-card.js @@ -20,14 +20,16 @@ const RANK_CARD_DEFAULT_WIDTH = 450; /** * Create a stats card text item. * - * @param {object[]} createTextNodeParams Object that contains the createTextNode parameters. + * @param {object} createTextNodeParams Object that contains the createTextNode parameters. + * @param {string} createTextNodeParams.icon The icon to display. * @param {string} createTextNodeParams.label The label to display. - * @param {string} createTextNodeParams.value The value to display. + * @param {number} createTextNodeParams.value The value to display. * @param {string} createTextNodeParams.id The id of the stat. * @param {number} createTextNodeParams.index The index of the stat. * @param {boolean} createTextNodeParams.showIcons Whether to show icons. * @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. * @returns */ const createTextNode = ({ From c656f3df6bd26ebe9e244616618617099a84bfb0 Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov <53787217+qwerty541@users.noreply.github.com> Date: Fri, 5 May 2023 17:07:37 +0300 Subject: [PATCH 54/82] Update codecov action version to resolve github action deprecation warning (#2677) --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fe34668d3e8d27..e8fc84a7df963a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,4 +34,4 @@ jobs: npm run format:check - name: Code Coverage - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v3 From d306bec73e5325869b41592dfc1758119c2a4036 Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov <53787217+qwerty541@users.noreply.github.com> Date: Fri, 5 May 2023 17:09:23 +0300 Subject: [PATCH 55/82] Add PRs cache cleaning workflow (#2661) --- .github/workflows/prs-cache-clean.yml | 33 +++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/workflows/prs-cache-clean.yml diff --git a/.github/workflows/prs-cache-clean.yml b/.github/workflows/prs-cache-clean.yml new file mode 100644 index 00000000000000..8ee4670e015340 --- /dev/null +++ b/.github/workflows/prs-cache-clean.yml @@ -0,0 +1,33 @@ +name: prs cache clean +on: + pull_request: + types: + - closed + +jobs: + cleanup: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Cleanup + run: | + gh extension install actions/gh-actions-cache + + REPO=${{ github.repository }} + BRANCH="refs/pull/${{ github.event.pull_request.number }}/merge" + + echo "Fetching list of cache key" + cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH | cut -f 1 ) + + ## Setting this to not fail the workflow while deleting cache keys. + set +e + echo "Deleting caches..." + for cacheKey in $cacheKeysForPR + do + gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm + done + echo "Done" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 2619c1263876b9ac6eef5fb11737efa7ad2603b0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 May 2023 14:10:17 +0000 Subject: [PATCH 56/82] build(deps): bump yaml from 2.1.3 to 2.2.2 (#2644) Bumps [yaml](https://github.com/eemeli/yaml) from 2.1.3 to 2.2.2. - [Release notes](https://github.com/eemeli/yaml/releases) - [Commits](https://github.com/eemeli/yaml/compare/v2.1.3...v2.2.2) --- updated-dependencies: - dependency-name: yaml dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index ebc7570a419236..a378d89ec88cf8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5581,9 +5581,9 @@ "dev": true }, "node_modules/yaml": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.3.tgz", - "integrity": "sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", + "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", "dev": true, "engines": { "node": ">= 14" @@ -9855,9 +9855,9 @@ "dev": true }, "yaml": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.3.tgz", - "integrity": "sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", + "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", "dev": true }, "yargs": { From a340900e8a1fac282d953fd4c7ac1ae9a459726e Mon Sep 17 00:00:00 2001 From: Fabiano Couto Date: Fri, 5 May 2023 18:12:28 -0300 Subject: [PATCH 57/82] fix(wakatime-fetcher): add default range to prevent user not found error (#2670) * feat(theme): add github_dark_dimmed theme * feat(theme): change github_dark_dimmed icon color * contrast ratio adjustment contrast ratio adjustment on github_dark_dimmed theme * feat(theme): readme preview * feat(theme): github themes next to each other * github themes next to each other * feat(RankIcon): add rank icon option * feat(RankIcon): extract rankIcon to icons file * feat(RankIcon): update readme * feat(RankIcon): test coverage * Update readme.md Co-authored-by: Rick Staa * add wakatime default range * update wakatime demos in readme with most active user * update wakatime demo in readme * remove &range=all_time from wakatime demos in readme --------- Co-authored-by: Rick Staa --- readme.md | 8 ++++---- src/fetchers/wakatime-fetcher.js | 4 +++- tests/fetchWakatime.test.js | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/readme.md b/readme.md index ae18c9e6002c6c..a95022be6bdf52 100644 --- a/readme.md +++ b/readme.md @@ -456,13 +456,13 @@ Change the `?username=` value to your [Wakatime](https://wakatime.com) username. ### Demo -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) - Compact layout -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod&layout=compact)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok&layout=compact)](https://github.com/anuraghazra/github-readme-stats) * * * @@ -520,7 +520,7 @@ Choose from any of the [default themes](#themes) - WakaTime card -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) * * * diff --git a/src/fetchers/wakatime-fetcher.js b/src/fetchers/wakatime-fetcher.js index fa1f3d890920ff..2af8fa9998fb1f 100644 --- a/src/fetchers/wakatime-fetcher.js +++ b/src/fetchers/wakatime-fetcher.js @@ -14,7 +14,9 @@ const fetchWakatimeStats = async ({ username, api_domain, range }) => { const { data } = await axios.get( `https://${ api_domain ? api_domain.replace(/\/$/gi, "") : "wakatime.com" - }/api/v1/users/${username}/stats/${range || ""}?is_including_today=true`, + }/api/v1/users/${username}/stats/${ + range || "all_time" + }?is_including_today=true`, ); return data.data; diff --git a/tests/fetchWakatime.test.js b/tests/fetchWakatime.test.js index 47ca25b7254a6c..04c01ba42c6f04 100644 --- a/tests/fetchWakatime.test.js +++ b/tests/fetchWakatime.test.js @@ -105,7 +105,7 @@ describe("Wakatime fetcher", () => { const username = "anuraghazra"; mock .onGet( - `https://wakatime.com/api/v1/users/${username}/stats/?is_including_today=true`, + `https://wakatime.com/api/v1/users/${username}/stats/all_time?is_including_today=true`, ) .reply(200, wakaTimeData); From 0c2fe4e07bab9c8fd8ec2477766cbf2b1fb11cdc Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov <53787217+qwerty541@users.noreply.github.com> Date: Sat, 6 May 2023 10:43:37 +0300 Subject: [PATCH 58/82] Cover with test changes in #2662 pull request (#2673) --- tests/renderWakatimeCard.test.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/renderWakatimeCard.test.js b/tests/renderWakatimeCard.test.js index 25e7ab4d802cd3..630f259643c78c 100644 --- a/tests/renderWakatimeCard.test.js +++ b/tests/renderWakatimeCard.test.js @@ -67,4 +67,19 @@ describe("Test Render Wakatime Card", () => { "No coding activity this week", ); }); + + it('should show "no coding activity this week" message when using compact layout and there has not been activity', () => { + document.body.innerHTML = renderWakatimeCard( + { + ...wakaTimeData.data, + languages: undefined, + }, + { + layout: "compact", + }, + ); + expect(document.querySelector(".stat").textContent).toBe( + "No coding activity this week", + ); + }); }); From 0caa4c5fd8788bb77406d298dc74fecc7bdc9eed Mon Sep 17 00:00:00 2001 From: Rongrong Date: Sat, 6 May 2023 16:07:03 +0800 Subject: [PATCH 59/82] test: fix mistaken pageInfo.endCursor keys (#2657) The previous mock logic was too simplistic and has been fixed in the commit. If the mock logic had been properly implemented, then the mistaken pageInfo.endCursor keys should have made the test case "should fetch two pages of stars if 'FETCH_MULTI_PAGE_STARS' env variable is set to `true`" stuck. --- tests/api.test.js | 2 +- tests/fetchStats.test.js | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/api.test.js b/tests/api.test.js index 461f3e18abb6da..f11832ef9141c9 100644 --- a/tests/api.test.js +++ b/tests/api.test.js @@ -43,7 +43,7 @@ const data_stats = { nodes: [{ stargazers: { totalCount: 100 } }], pageInfo: { hasNextPage: false, - cursor: "cursor", + endCursor: "cursor", }, }, }, diff --git a/tests/fetchStats.test.js b/tests/fetchStats.test.js index 04e943a75b50ad..08523f3362e0e4 100644 --- a/tests/fetchStats.test.js +++ b/tests/fetchStats.test.js @@ -27,7 +27,7 @@ const data_stats = { ], pageInfo: { hasNextPage: true, - cursor: "cursor", + endCursor: "cursor", }, }, }, @@ -44,7 +44,7 @@ const data_repo = { ], pageInfo: { hasNextPage: false, - cursor: "cursor", + endCursor: "cursor", }, }, }, @@ -64,7 +64,7 @@ const data_repo_zero_stars = { ], pageInfo: { hasNextPage: true, - cursor: "cursor", + endCursor: "cursor", }, }, }, @@ -86,11 +86,12 @@ 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") - .replyOnce(200, data_stats) - .onPost("https://api.github.com/graphql") - .replyOnce(200, data_repo); + mock.onPost("https://api.github.com/graphql").reply((cfg) => { + return [ + 200, + cfg.data.includes("contributionsCollection") ? data_stats : data_repo, + ]; + }); }); afterEach(() => { From 688f4e497593d22d813423248bd96aaf0f0e495f Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Sat, 6 May 2023 11:59:11 +0200 Subject: [PATCH 60/82] docs: update wakatime user (#2697) The old example user doesn't have a WakaTime account anymore. --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index a95022be6bdf52..58af2835d5fbec 100644 --- a/readme.md +++ b/readme.md @@ -448,7 +448,7 @@ You can use the `&hide_progress=true` option to hide the percentages and the pro Change the `?username=` value to your [Wakatime](https://wakatime.com) username. ```md -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) ``` > **Note**: From 6f449a1f0b4cca5d19c16a5822f162921f5cf238 Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov <53787217+qwerty541@users.noreply.github.com> Date: Sun, 7 May 2023 23:03:00 +0300 Subject: [PATCH 61/82] Fixed docstring for get styles function to resolve vscode type errors (#2700) --- src/getStyles.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/getStyles.js b/src/getStyles.js index f7b90f4adc7b4c..7e6113921b60db 100644 --- a/src/getStyles.js +++ b/src/getStyles.js @@ -65,10 +65,11 @@ const getAnimations = () => { /** * Retrieves CSS styles for a card. * - * @param {Object[]} colors The colors to use for the card. + * @param {Object} colors The colors to use for the card. * @param {string} colors.titleColor The title color. * @param {string} colors.textColor The text color. * @param {string} colors.iconColor The icon color. + * @param {string} colors.ringColor The ring color. * @param {boolean} colors.show_icons Whether to show icons. * @param {number} colors.progress The progress value to animate to. * @returns {string} Card CSS styles. From 15436e2aa19de52177409735f67da56f243e4431 Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov <53787217+qwerty541@users.noreply.github.com> Date: Sun, 7 May 2023 23:03:26 +0300 Subject: [PATCH 62/82] Fixed axios imports inside docstrings to resolve vscode type errors (#2699) --- src/fetchers/repo-fetcher.js | 4 ++-- src/fetchers/top-languages-fetcher.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/fetchers/repo-fetcher.js b/src/fetchers/repo-fetcher.js index ff7a2be8164cc7..412d1d4c28cf18 100644 --- a/src/fetchers/repo-fetcher.js +++ b/src/fetchers/repo-fetcher.js @@ -5,9 +5,9 @@ import { MissingParamError, request } from "../common/utils.js"; /** * Repo data fetcher. * - * @param {import('Axios').AxiosRequestHeaders} variables Fetcher variables. + * @param {import('axios').AxiosRequestHeaders} variables Fetcher variables. * @param {string} token GitHub token. - * @returns {Promise} The response. + * @returns {Promise} The response. */ const fetcher = (variables, token) => { return request( diff --git a/src/fetchers/top-languages-fetcher.js b/src/fetchers/top-languages-fetcher.js index 45b2ba7d851837..b57d901afb0a77 100644 --- a/src/fetchers/top-languages-fetcher.js +++ b/src/fetchers/top-languages-fetcher.js @@ -11,7 +11,7 @@ import { /** * Top languages fetcher object. * - * @param {import('Axios').AxiosRequestHeaders} variables Fetcher variables. + * @param {import('axios').AxiosRequestHeaders} variables Fetcher variables. * @param {string} token GitHub token. * @returns {Promise} Languages fetcher response. */ From ce116e2d2f42aad54f9fc8e54f208c6c9b1ff558 Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov <53787217+qwerty541@users.noreply.github.com> Date: Mon, 8 May 2023 10:31:47 +0300 Subject: [PATCH 63/82] Fixed typos inside bug report issue template (#2706) --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 198bc80ef0fe46..367ae4f302d8b0 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -15,7 +15,7 @@ body: required: true - type: textarea attributes: - label: Expected behaviour + label: Expected behavior description: A clear and concise description of what you expected to happen. - type: textarea From 550b88ce878ea19d433b8167731db0a3c306d8a2 Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov <53787217+qwerty541@users.noreply.github.com> Date: Mon, 8 May 2023 10:32:50 +0300 Subject: [PATCH 64/82] Update wakatime user inside docs translations (#2705) --- docs/readme_de.md | 8 ++++---- docs/readme_es.md | 10 +++++----- docs/readme_kr.md | 10 +++++----- docs/readme_nl.md | 8 ++++---- docs/readme_np.md | 8 ++++---- docs/readme_pt-BR.md | 6 +++--- docs/readme_tr.md | 10 +++++----- 7 files changed, 30 insertions(+), 30 deletions(-) diff --git a/docs/readme_de.md b/docs/readme_de.md index 8756670eaf4b91..1ad0d26a390db5 100644 --- a/docs/readme_de.md +++ b/docs/readme_de.md @@ -253,18 +253,18 @@ Du kannst die `&layout=compact` Option nutzen, um das Kartendesign zu ändern. Ändere `?username=` in den eigenen [Wakatime](https://wakatime.com)-Benutzernamen. ```md -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) ``` ### Beispiel -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) - Kompaktes Layout -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod&layout=compact)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok&layout=compact)](https://github.com/anuraghazra/github-readme-stats) --- diff --git a/docs/readme_es.md b/docs/readme_es.md index b477e5c729cab3..5672027ae32d55 100644 --- a/docs/readme_es.md +++ b/docs/readme_es.md @@ -282,18 +282,18 @@ Puedes usar la opción `& layout = compact` para cambiar el diseño de la tarjet cambia el valor del parámetro `?username=` a tu username en [Wakatime](https://wakatime.com). ```md -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) ``` ### Ejemplo -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) - Diseño compacto -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod&layout=compact)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok&layout=compact)](https://github.com/anuraghazra/github-readme-stats) --- @@ -343,7 +343,7 @@ Escoja cualquiera de los [temas por defecto](#themes) - Tarjeta de Wakatime -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) --- diff --git a/docs/readme_kr.md b/docs/readme_kr.md index 4a1c57cc1e9778..0d7bced34ccde0 100644 --- a/docs/readme_kr.md +++ b/docs/readme_kr.md @@ -298,18 +298,18 @@ _참고: `?username=` 속성의 값을 [Wakatime](https://wakatime.com) 계정의 사용자 명(닉네임)으로 바꿔주세요. ```md -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) ``` ### 미리보기 -[![willianrod 님의 wakatime 통계](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok 님의 wakatime 통계](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) -[![willianrod 님의 wakatime 통계](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok 님의 wakatime 통계](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) - 컴팩트한 레이아웃 -[![willianrod 님의 wakatime 통계](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod&layout=compact)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok 님의 wakatime 통계](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok&layout=compact)](https://github.com/anuraghazra/github-readme-stats) --- @@ -359,7 +359,7 @@ _참고: - Wakatime 카드 -[![willianrod 님의 Wakatime 카드](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok 님의 Wakatime 카드](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) --- diff --git a/docs/readme_nl.md b/docs/readme_nl.md index b279c4f71fff41..f70828f142a88c 100644 --- a/docs/readme_nl.md +++ b/docs/readme_nl.md @@ -283,14 +283,14 @@ Je kan de `&layout=compact` optie gebruiken om het kaart ontwerp aan te passen. Verander de `?username=` waarde naar je [Wakatime](https://wakatime.com) gebruikersnaam. ```md -[![willianrod's Wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's Wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) ``` ### Demo -[![willianrod's Wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's Wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) -[![willianrod's Wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's Wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) --- @@ -340,7 +340,7 @@ Kies uit de [standaard thema\'s](#themes) - Wakatime kaart -[![willianrod's Wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's Wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) --- diff --git a/docs/readme_np.md b/docs/readme_np.md index e90ee57bfe6b38..b322545c0235ea 100644 --- a/docs/readme_np.md +++ b/docs/readme_np.md @@ -279,14 +279,14 @@ You can use the `&langs_count=` option to increase or decrease the number of lan Change the `?username=` value to your [Wakatime](https://wakatime.com) username. ```md -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) ``` ### डेमो -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) --- @@ -336,7 +336,7 @@ Change the `?username=` value to your [Wakatime](https://wakatime.com) username. - वक समय कार्ड -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) --- diff --git a/docs/readme_pt-BR.md b/docs/readme_pt-BR.md index 1ac57716009be1..cf6eeedc4ded6a 100644 --- a/docs/readme_pt-BR.md +++ b/docs/readme_pt-BR.md @@ -242,14 +242,14 @@ Utilize a opção `&layout=compact` para mudar o layout do cartão. Altere o valor de `?username=` para o seu username do Wakatime. ```md -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) ``` ### Demonstração -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) --- diff --git a/docs/readme_tr.md b/docs/readme_tr.md index d8ae9778fb50ca..fe5aff738c13a5 100644 --- a/docs/readme_tr.md +++ b/docs/readme_tr.md @@ -282,18 +282,18 @@ Endpoint: `api/top-langs?username=mustafacagri` `?username=` değerini [Wakatime](https://wakatime.com)'daki kullanıcı adınızla değiştirin. ```md -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) ``` ### Demo -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) - Kompakt Düzen -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod&layout=compact)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok&layout=compact)](https://github.com/anuraghazra/github-readme-stats) --- @@ -343,7 +343,7 @@ Endpoint: `api/top-langs?username=mustafacagri` - Wakatime kart -[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats) +[![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) --- From 8ff5a3ba45cb493f61444d2963e969b2b54d91e4 Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov <53787217+qwerty541@users.noreply.github.com> Date: Mon, 8 May 2023 10:33:08 +0300 Subject: [PATCH 65/82] Fixed docstring for create language text node function (#2704) --- src/cards/top-languages-card.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cards/top-languages-card.js b/src/cards/top-languages-card.js index ce8e12a839c773..816b651ea669e1 100644 --- a/src/cards/top-languages-card.js +++ b/src/cards/top-languages-card.js @@ -98,7 +98,7 @@ const createCompactLangNode = ({ lang, totalSize, hideProgress, index }) => { /** * Creates compact layout of text only language nodes. * - * @param {object[]} props Function properties. + * @param {object} props Function properties. * @param {Lang[]} props.langs Array of programming languages. * @param {number} props.totalSize Total size of all languages. * @param {boolean} props.hideProgress Whether to hide percentage. From daa1977ba310f5dcb44f7945e7ecb5537e708c05 Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov <53787217+qwerty541@users.noreply.github.com> Date: Tue, 9 May 2023 10:04:02 +0300 Subject: [PATCH 66/82] Fixed docstring for wakatime card recalculate percentages function (#2710) --- src/cards/wakatime-card.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cards/wakatime-card.js b/src/cards/wakatime-card.js index 0b2366763d6ef5..dacc4227dc2ad6 100644 --- a/src/cards/wakatime-card.js +++ b/src/cards/wakatime-card.js @@ -140,7 +140,7 @@ const createTextNode = ({ * hiding languages. * * @param {import("../fetchers/types").WakaTimeLang[]} languages The languages array. - * @return {import("../fetchers/types").WakaTimeLang[]} The recalculated languages array. + * @return {void} The recalculated languages array. */ const recalculatePercentages = (languages) => { const totalSum = languages.reduce( From c5e7f7b490f3c36857396e8c888d21d9e102c3ae Mon Sep 17 00:00:00 2001 From: Nabil Alamin Date: Tue, 9 May 2023 19:54:34 +0100 Subject: [PATCH 67/82] add pie chart layout to language card (#2099) * add pie chart layout to language card * resolve failing top-lang card tests * scale down pie chart * update readme.md * Update readme.md Co-authored-by: Rick Staa * style: format code * update donut layout to be created without dependencies * minor update * style: format readme * resolve failing tests * refactor: clean up code and add extra tests This commit cleans up the pie chart generation code and adds additional tests. * feat: improve pie chart positioning * rename layout pie to donut * add animation to donut layout * refactor: rename pie and doughnut to donut * feat: decrease donus animation delay --------- Co-authored-by: rickstaa --- package-lock.json | 145 ++++----------- readme.md | 12 ++ src/cards/top-languages-card.js | 301 ++++++++++++++++++++++++++----- src/cards/types.d.ts | 2 +- tests/renderTopLanguages.test.js | 287 ++++++++++++++++++++++++++++- 5 files changed, 588 insertions(+), 159 deletions(-) diff --git a/package-lock.json b/package-lock.json index a378d89ec88cf8..a2156910681da0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1495,31 +1495,19 @@ } }, "node_modules/acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", "dev": true, "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "node_modules/acorn-globals/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" } }, "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", "dev": true, "engines": { "node": ">=0.4.0" @@ -1778,12 +1766,6 @@ "node": ">=8" } }, - "node_modules/browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, "node_modules/browserslist": { "version": "4.21.4", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", @@ -2509,20 +2491,6 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -3865,18 +3833,18 @@ } }, "node_modules/jsdom": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.0.tgz", - "integrity": "sha512-x4a6CKCgx00uCmP+QakBDFXwjAJ69IkkIWHmtmjd3wvXPcdOS44hfX2vqkOQrVrq8l9DhNNADZRXaCEWvgXtVA==", + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.1.tgz", + "integrity": "sha512-pksjj7Rqoa+wdpkKcLzQRHhJCEE42qQhl/xLMUKHgoSejaKOdaXEAnqs6uDNwMl/fciHTzKeR8Wm8cw7N+g98A==", "dev": true, "dependencies": { "abab": "^2.0.6", - "acorn": "^8.7.1", - "acorn-globals": "^6.0.0", + "acorn": "^8.8.0", + "acorn-globals": "^7.0.0", "cssom": "^0.5.0", "cssstyle": "^2.3.0", "data-urls": "^3.0.2", - "decimal.js": "^10.3.1", + "decimal.js": "^10.4.1", "domexception": "^4.0.0", "escodegen": "^2.0.0", "form-data": "^4.0.0", @@ -3884,18 +3852,17 @@ "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.1", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "^7.0.0", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", + "tough-cookie": "^4.1.2", "w3c-xmlserializer": "^3.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^2.0.0", "whatwg-mimetype": "^3.0.0", "whatwg-url": "^11.0.0", - "ws": "^8.8.0", + "ws": "^8.9.0", "xml-name-validator": "^4.0.0" }, "engines": { @@ -5397,15 +5364,6 @@ "node": ">=10.12.0" } }, - "node_modules/w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "dependencies": { - "browser-process-hrtime": "^1.0.0" - } - }, "node_modules/w3c-xmlserializer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz", @@ -6830,27 +6788,19 @@ "dev": true }, "acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", "dev": true, "requires": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - }, - "dependencies": { - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - } + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" } }, "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", "dev": true }, "agent-base": { @@ -7049,12 +6999,6 @@ "fill-range": "^7.0.1" } }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, "browserslist": { "version": "4.21.4", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", @@ -7590,13 +7534,6 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -8609,18 +8546,18 @@ } }, "jsdom": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.0.tgz", - "integrity": "sha512-x4a6CKCgx00uCmP+QakBDFXwjAJ69IkkIWHmtmjd3wvXPcdOS44hfX2vqkOQrVrq8l9DhNNADZRXaCEWvgXtVA==", + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.1.tgz", + "integrity": "sha512-pksjj7Rqoa+wdpkKcLzQRHhJCEE42qQhl/xLMUKHgoSejaKOdaXEAnqs6uDNwMl/fciHTzKeR8Wm8cw7N+g98A==", "dev": true, "requires": { "abab": "^2.0.6", - "acorn": "^8.7.1", - "acorn-globals": "^6.0.0", + "acorn": "^8.8.0", + "acorn-globals": "^7.0.0", "cssom": "^0.5.0", "cssstyle": "^2.3.0", "data-urls": "^3.0.2", - "decimal.js": "^10.3.1", + "decimal.js": "^10.4.1", "domexception": "^4.0.0", "escodegen": "^2.0.0", "form-data": "^4.0.0", @@ -8628,18 +8565,17 @@ "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.1", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "^7.0.0", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", + "tough-cookie": "^4.1.2", "w3c-xmlserializer": "^3.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^2.0.0", "whatwg-mimetype": "^3.0.0", "whatwg-url": "^11.0.0", - "ws": "^8.8.0", + "ws": "^8.9.0", "xml-name-validator": "^4.0.0" } }, @@ -9724,15 +9660,6 @@ "convert-source-map": "^1.6.0" } }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, "w3c-xmlserializer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz", diff --git a/readme.md b/readme.md index 58af2835d5fbec..5f3c125476343a 100644 --- a/readme.md +++ b/readme.md @@ -423,6 +423,14 @@ You can use the `&layout=compact` option to change the card design. [![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=compact)](https://github.com/anuraghazra/github-readme-stats) ``` +### Donut Chart Language Card Layout + +You can use the `&layout=donut` option to change the card design. + +```md +[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=donut)](https://github.com/anuraghazra/github-readme-stats) +``` + ### Hide Progress Bars You can use the `&hide_progress=true` option to hide the percentages and the progress bars (layout will be automatically set to `compact`). @@ -439,6 +447,10 @@ You can use the `&hide_progress=true` option to hide the percentages and the pro [![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=compact)](https://github.com/anuraghazra/github-readme-stats) +- Donut Chart layout + +[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=donut)](https://github.com/anuraghazra/github-readme-stats) + - Hidden progress bars [![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) diff --git a/src/cards/top-languages-card.js b/src/cards/top-languages-card.js index 816b651ea669e1..262ae972a4002a 100644 --- a/src/cards/top-languages-card.js +++ b/src/cards/top-languages-card.js @@ -36,13 +36,134 @@ const getLongestLang = (arr) => ); /** - * Creates a node to display usage of a programming language in percentage - * using text and a horizontal progress bar. + * Convert degrees to radians. + * + * @param {number} angleInDegrees Angle in degrees. + * @returns Angle in radians. + */ +const degreesToRadians = (angleInDegrees) => angleInDegrees * (Math.PI / 180.0); + +/** + * Convert radians to degrees. + * + * @param {number} angleInRadians Angle in radians. + * @returns Angle in degrees. + */ +const radiansToDegrees = (angleInRadians) => angleInRadians / (Math.PI / 180.0); + +/** + * Convert polar coordinates to cartesian coordinates. + * + * @param {number} centerX Center x coordinate. + * @param {number} centerY Center y coordinate. + * @param {number} radius Radius of the circle. + * @param {number} angleInDegrees Angle in degrees. + * @returns {{x: number, y: number}} Cartesian coordinates. + */ +const polarToCartesian = (centerX, centerY, radius, angleInDegrees) => { + const rads = degreesToRadians(angleInDegrees); + return { + x: centerX + radius * Math.cos(rads), + y: centerY + radius * Math.sin(rads), + }; +}; + +/** + * Convert cartesian coordinates to polar coordinates. + * + * @param {number} centerX Center x coordinate. + * @param {number} centerY Center y coordinate. + * @param {number} x Point x coordinate. + * @param {number} y Point y coordinate. + * @returns {{radius: number, angleInDegrees: number}} Polar coordinates. + */ +const cartesianToPolar = (centerX, centerY, x, y) => { + const radius = Math.sqrt(Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2)); + let angleInDegrees = radiansToDegrees(Math.atan2(y - centerY, x - centerX)); + if (angleInDegrees < 0) angleInDegrees += 360; + return { radius, angleInDegrees }; +}; + +/** + * Calculates height for the compact layout. + * + * @param {number} totalLangs Total number of languages. + * @returns {number} Card height. + */ +const calculateCompactLayoutHeight = (totalLangs) => { + return 90 + Math.round(totalLangs / 2) * 25; +}; + +/** + * Calculates height for the normal layout. + * + * @param {number} totalLangs Total number of languages. + * @returns {number} Card height. + */ +const calculateNormalLayoutHeight = (totalLangs) => { + return 45 + (totalLangs + 1) * 40; +}; + +/** + * Calculates height for the donut layout. + * + * @param {number} totalLangs Total number of languages. + * @returns {number} Card height. + */ +const calculateDonutLayoutHeight = (totalLangs) => { + return 215 + Math.max(totalLangs - 5, 0) * 32; +}; + +/** + * Calculates the center translation needed to keep the donut chart centred. + * @param {number} totalLangs Total number of languages. + * @returns {number} Donut center translation. + */ +const donutCenterTranslation = (totalLangs) => { + return -45 + Math.max(totalLangs - 5, 0) * 16; +}; + +/** + * Trim top languages to lang_count while also hiding certain languages. + * + * @param {Record} topLangs Top languages. + * @param {string[]} hide Languages to hide. + * @param {string} langs_count Number of languages to show. + * @returns {{topLangs: Record, totalSize: number}} Trimmed top languages and total size. + */ +const trimTopLanguages = (topLangs, hide, langs_count) => { + let langs = Object.values(topLangs); + let langsToHide = {}; + let langsCount = clampValue(parseInt(langs_count), 1, 10); + + // populate langsToHide map for quick lookup + // while filtering out + if (hide) { + hide.forEach((langName) => { + langsToHide[lowercaseTrim(langName)] = true; + }); + } + + // filter out languages to be hidden + langs = langs + .sort((a, b) => b.size - a.size) + .filter((lang) => { + return !langsToHide[lowercaseTrim(lang.name)]; + }) + .slice(0, langsCount); + + const totalLanguageSize = langs.reduce((acc, curr) => acc + curr.size, 0); + + return { langs, totalLanguageSize }; +}; + +/** + * Create progress bar text item for a programming language. * * @param {object} props Function properties. * @param {number} props.width The card width - * @param {string} props.name Name of the programming language. * @param {string} props.color Color of the programming language. + * @param {string} props.name Name of the programming language. * @param {string} props.progress Usage of the programming language in percentage. * @param {number} props.index Index of the programming language. * @returns {string} Programming language SVG node. @@ -71,7 +192,7 @@ const createProgressTextNode = ({ width, color, name, progress, index }) => { }; /** - * Creates a text only node to display usage of a programming language in percentage. + * Creates compact text item for a programming language. * * @param {object} props Function properties. * @param {Lang} props.lang Programming language object. @@ -96,7 +217,7 @@ const createCompactLangNode = ({ lang, totalSize, hideProgress, index }) => { }; /** - * Creates compact layout of text only language nodes. + * Create compact languages text items for all programming languages. * * @param {object} props Function properties. * @param {Lang[]} props.langs Array of programming languages. @@ -134,7 +255,29 @@ const createLanguageTextNode = ({ langs, totalSize, hideProgress }) => { }; /** - * Renders layout to display user's most frequently used programming languages. + * Create donut languages text items for all programming languages. + * + * @param {object[]} props Function properties. + * @param {Lang[]} props.langs Array of programming languages. + * @param {number} props.totalSize Total size of all languages. + * @returns {string} Donut layout programming language SVG node. + */ +const createDonutLanguagesNode = ({ langs, totalSize }) => { + return flexLayout({ + items: langs.map((lang, index) => { + return createCompactLangNode({ + lang, + totalSize, + index, + }); + }), + gap: 32, + direction: "column", + }).join(""); +}; + +/** + * Renders the default language card layout. * * @param {Lang[]} langs Array of programming languages. * @param {number} width Card width. @@ -158,7 +301,7 @@ const renderNormalLayout = (langs, width, totalLanguageSize) => { }; /** - * Renders compact layout to display user's most frequently used programming languages. + * Renders the compact language card layout. * * @param {Lang[]} langs Array of programming languages. * @param {number} width Card width. @@ -218,60 +361,105 @@ const renderCompactLayout = (langs, width, totalLanguageSize, hideProgress) => { }; /** - * Calculates height for the compact layout. + * Creates the SVG paths for the language donut chart. * - * @param {number} totalLangs Total number of languages. - * @returns {number} Card height. + * @param {number} cx Donut center x-position. + * @param {number} cy Donut center y-position. + * @param {number} radius Donut arc Radius. + * @param {number[]} percentages Array with donut section percentages. + * @returns {{d: string, percent: number}[]} Array of svg path elements */ -const calculateCompactLayoutHeight = (totalLangs) => { - return 90 + Math.round(totalLangs / 2) * 25; -}; +const createDonutPaths = (cx, cy, radius, percentages) => { + const paths = []; + let startAngle = 0; + let endAngle = 0; -/** - * Calculates height for the normal layout. - * - * @param {number} totalLangs Total number of languages. - * @returns {number} Card height. - */ -const calculateNormalLayoutHeight = (totalLangs) => { - return 45 + (totalLangs + 1) * 40; + const totalPercent = percentages.reduce((acc, curr) => acc + curr, 0); + for (let i = 0; i < percentages.length; i++) { + const tmpPath = {}; + + let percent = parseFloat( + ((percentages[i] / totalPercent) * 100).toFixed(2), + ); + + endAngle = 3.6 * percent + startAngle; + const startPoint = polarToCartesian(cx, cy, radius, endAngle - 90); // rotate donut 90 degrees counter-clockwise. + const endPoint = polarToCartesian(cx, cy, radius, startAngle - 90); // rotate donut 90 degrees counter-clockwise. + const largeArc = endAngle - startAngle <= 180 ? 0 : 1; + + tmpPath.percent = percent; + tmpPath.d = `M ${startPoint.x} ${startPoint.y} A ${radius} ${radius} 0 ${largeArc} 0 ${endPoint.x} ${endPoint.y}`; + + paths.push(tmpPath); + startAngle = endAngle; + } + + return paths; }; /** - * Hides languages and trims the list to show only the top N languages. + * Renders the donut language card layout. * - * @param {Record} topLangs Top languages. - * @param {string[]} hide Languages to hide. - * @param {string} langs_count Number of languages to show. + * @param {Lang[]} langs Array of programming languages. + * @param {number} width Card width. + * @param {number} totalLanguageSize Total size of all languages. + * @returns {string} Donut layout card SVG object. */ -const useLanguages = (topLangs, hide, langs_count) => { - let langs = Object.values(topLangs); - let langsToHide = {}; - let langsCount = clampValue(parseInt(langs_count), 1, 10); +const renderDonutLayout = (langs, width, totalLanguageSize) => { + const centerX = width / 3; + const centerY = width / 3; + const radius = centerX - 60; + const strokeWidth = 12; + + const colors = langs.map((lang) => lang.color); + const langsPercents = langs.map((lang) => + parseFloat(((lang.size / totalLanguageSize) * 100).toFixed(2)), + ); - // populate langsToHide map for quick lookup - // while filtering out - if (hide) { - hide.forEach((langName) => { - langsToHide[lowercaseTrim(langName)] = true; - }); - } + const langPaths = createDonutPaths(centerX, centerY, radius, langsPercents); + + const donutPaths = + langs.length === 1 + ? `` + : langPaths + .map((section, index) => { + const staggerDelay = (index + 3) * 100; + const delay = staggerDelay + 300; + + const output = ` + + + + + `; - // filter out languages to be hidden - langs = langs - .sort((a, b) => b.size - a.size) - .filter((lang) => { - return !langsToHide[lowercaseTrim(lang.name)]; - }) - .slice(0, langsCount); + return output; + }) + .join(""); - const totalLanguageSize = langs.reduce((acc, curr) => acc + curr.size, 0); + const donut = `${donutPaths}`; - return { langs, totalLanguageSize }; + return ` + + + ${createDonutLanguagesNode({ langs, totalSize: totalLanguageSize })} + + + + ${donut} + + + `; }; /** - * Renders card to display user's most frequently used programming languages. + * Renders card that display user's most frequently used programming languages. * * @param {import('../fetchers/types').TopLangData} topLangs User's most frequently used programming languages. * @param {Partial} options Card options. @@ -302,7 +490,7 @@ const renderTopLanguages = (topLangs, options = {}) => { translations: langCardLocales, }); - const { langs, totalLanguageSize } = useLanguages( + const { langs, totalLanguageSize } = trimTopLanguages( topLangs, hide, String(langs_count), @@ -326,6 +514,10 @@ const renderTopLanguages = (topLangs, options = {}) => { totalLanguageSize, hide_progress, ); + } else if (layout?.toLowerCase() === "donut") { + height = calculateDonutLayoutHeight(langs.length); + width = width + 50; // padding + finalLayout = renderDonutLayout(langs, width, totalLanguageSize); } else { finalLayout = renderNormalLayout(langs, width, totalLanguageSize); } @@ -394,4 +586,17 @@ const renderTopLanguages = (topLangs, options = {}) => { `); }; -export { renderTopLanguages, MIN_CARD_WIDTH }; +export { + getLongestLang, + degreesToRadians, + radiansToDegrees, + polarToCartesian, + cartesianToPolar, + calculateCompactLayoutHeight, + calculateNormalLayoutHeight, + calculateDonutLayoutHeight, + donutCenterTranslation, + trimTopLanguages, + renderTopLanguages, + MIN_CARD_WIDTH, +}; diff --git a/src/cards/types.d.ts b/src/cards/types.d.ts index 02a41b57693877..fea5aa954222c1 100644 --- a/src/cards/types.d.ts +++ b/src/cards/types.d.ts @@ -39,7 +39,7 @@ export type TopLangOptions = CommonOptions & { hide_border: boolean; card_width: number; hide: string[]; - layout: "compact" | "normal"; + layout: "compact" | "normal" | "donut"; custom_title: string; langs_count: number; disable_animations: boolean; diff --git a/tests/renderTopLanguages.test.js b/tests/renderTopLanguages.test.js index de9e21f129bdfb..e4f47c396b80bd 100644 --- a/tests/renderTopLanguages.test.js +++ b/tests/renderTopLanguages.test.js @@ -1,9 +1,20 @@ import { queryAllByTestId, queryByTestId } from "@testing-library/dom"; import { cssToObject } from "@uppercod/css-to-object"; import { - MIN_CARD_WIDTH, + getLongestLang, + degreesToRadians, + radiansToDegrees, + polarToCartesian, + cartesianToPolar, + calculateCompactLayoutHeight, + calculateNormalLayoutHeight, + calculateDonutLayoutHeight, + donutCenterTranslation, + trimTopLanguages, renderTopLanguages, + MIN_CARD_WIDTH, } from "../src/cards/top-languages-card.js"; + // adds special assertions like toHaveTextContent import "@testing-library/jest-dom"; @@ -27,6 +38,205 @@ const langs = { }, }; +/** + * Retrieve the language percentage from the donut chart SVG. + * @param {string} d The SVG path element. + * @param {number} centerX The center X coordinate of the donut chart. + * @param {number} centerY The center Y coordinate of the donut chart. + * @returns {number} The percentage of the language. + */ +const langPercentFromSvg = (d, centerX, centerY) => { + const dTmp = d + .split(" ") + .filter((x) => !isNaN(x)) + .map((x) => parseFloat(x)); + const endAngle = + cartesianToPolar(centerX, centerY, dTmp[0], dTmp[1]).angleInDegrees + 90; + let startAngle = + cartesianToPolar(centerX, centerY, dTmp[7], dTmp[8]).angleInDegrees + 90; + if (startAngle > endAngle) startAngle -= 360; + return (endAngle - startAngle) / 3.6; +}; + +describe("Test renderTopLanguages helper functions", () => { + it("getLongestLang", () => { + const langArray = Object.values(langs); + expect(getLongestLang(langArray)).toBe(langs.javascript); + }); + + it("degreesToRadians", () => { + expect(degreesToRadians(0)).toBe(0); + expect(degreesToRadians(90)).toBe(Math.PI / 2); + expect(degreesToRadians(180)).toBe(Math.PI); + expect(degreesToRadians(270)).toBe((3 * Math.PI) / 2); + expect(degreesToRadians(360)).toBe(2 * Math.PI); + }); + + it("radiansToDegrees", () => { + expect(radiansToDegrees(0)).toBe(0); + expect(radiansToDegrees(Math.PI / 2)).toBe(90); + expect(radiansToDegrees(Math.PI)).toBe(180); + expect(radiansToDegrees((3 * Math.PI) / 2)).toBe(270); + expect(radiansToDegrees(2 * Math.PI)).toBe(360); + }); + + it("polarToCartesian", () => { + expect(polarToCartesian(100, 100, 60, 0)).toStrictEqual({ x: 160, y: 100 }); + expect(polarToCartesian(100, 100, 60, 45)).toStrictEqual({ + x: 142.42640687119285, + y: 142.42640687119285, + }); + expect(polarToCartesian(100, 100, 60, 90)).toStrictEqual({ + x: 100, + y: 160, + }); + expect(polarToCartesian(100, 100, 60, 135)).toStrictEqual({ + x: 57.573593128807154, + y: 142.42640687119285, + }); + expect(polarToCartesian(100, 100, 60, 180)).toStrictEqual({ + x: 40, + y: 100.00000000000001, + }); + expect(polarToCartesian(100, 100, 60, 225)).toStrictEqual({ + x: 57.57359312880714, + y: 57.573593128807154, + }); + expect(polarToCartesian(100, 100, 60, 270)).toStrictEqual({ + x: 99.99999999999999, + y: 40, + }); + expect(polarToCartesian(100, 100, 60, 315)).toStrictEqual({ + x: 142.42640687119285, + y: 57.57359312880714, + }); + expect(polarToCartesian(100, 100, 60, 360)).toStrictEqual({ + x: 160, + y: 99.99999999999999, + }); + }); + + it("cartesianToPolar", () => { + expect(cartesianToPolar(100, 100, 160, 100)).toStrictEqual({ + radius: 60, + angleInDegrees: 0, + }); + expect( + cartesianToPolar(100, 100, 142.42640687119285, 142.42640687119285), + ).toStrictEqual({ radius: 60.00000000000001, angleInDegrees: 45 }); + expect(cartesianToPolar(100, 100, 100, 160)).toStrictEqual({ + radius: 60, + angleInDegrees: 90, + }); + expect( + cartesianToPolar(100, 100, 57.573593128807154, 142.42640687119285), + ).toStrictEqual({ radius: 60, angleInDegrees: 135 }); + expect(cartesianToPolar(100, 100, 40, 100.00000000000001)).toStrictEqual({ + radius: 60, + angleInDegrees: 180, + }); + expect( + cartesianToPolar(100, 100, 57.57359312880714, 57.573593128807154), + ).toStrictEqual({ radius: 60, angleInDegrees: 225 }); + expect(cartesianToPolar(100, 100, 99.99999999999999, 40)).toStrictEqual({ + radius: 60, + angleInDegrees: 270, + }); + expect( + cartesianToPolar(100, 100, 142.42640687119285, 57.57359312880714), + ).toStrictEqual({ radius: 60.00000000000001, angleInDegrees: 315 }); + expect(cartesianToPolar(100, 100, 160, 99.99999999999999)).toStrictEqual({ + radius: 60, + angleInDegrees: 360, + }); + }); + + it("calculateCompactLayoutHeight", () => { + expect(calculateCompactLayoutHeight(0)).toBe(90); + expect(calculateCompactLayoutHeight(1)).toBe(115); + expect(calculateCompactLayoutHeight(2)).toBe(115); + expect(calculateCompactLayoutHeight(3)).toBe(140); + expect(calculateCompactLayoutHeight(4)).toBe(140); + expect(calculateCompactLayoutHeight(5)).toBe(165); + expect(calculateCompactLayoutHeight(6)).toBe(165); + expect(calculateCompactLayoutHeight(7)).toBe(190); + expect(calculateCompactLayoutHeight(8)).toBe(190); + expect(calculateCompactLayoutHeight(9)).toBe(215); + expect(calculateCompactLayoutHeight(10)).toBe(215); + }); + + it("calculateNormalLayoutHeight", () => { + expect(calculateNormalLayoutHeight(0)).toBe(85); + expect(calculateNormalLayoutHeight(1)).toBe(125); + expect(calculateNormalLayoutHeight(2)).toBe(165); + expect(calculateNormalLayoutHeight(3)).toBe(205); + expect(calculateNormalLayoutHeight(4)).toBe(245); + expect(calculateNormalLayoutHeight(5)).toBe(285); + expect(calculateNormalLayoutHeight(6)).toBe(325); + expect(calculateNormalLayoutHeight(7)).toBe(365); + expect(calculateNormalLayoutHeight(8)).toBe(405); + expect(calculateNormalLayoutHeight(9)).toBe(445); + expect(calculateNormalLayoutHeight(10)).toBe(485); + }); + + it("calculateDonutLayoutHeight", () => { + expect(calculateDonutLayoutHeight(0)).toBe(215); + expect(calculateDonutLayoutHeight(1)).toBe(215); + expect(calculateDonutLayoutHeight(2)).toBe(215); + expect(calculateDonutLayoutHeight(3)).toBe(215); + expect(calculateDonutLayoutHeight(4)).toBe(215); + expect(calculateDonutLayoutHeight(5)).toBe(215); + expect(calculateDonutLayoutHeight(6)).toBe(247); + expect(calculateDonutLayoutHeight(7)).toBe(279); + expect(calculateDonutLayoutHeight(8)).toBe(311); + expect(calculateDonutLayoutHeight(9)).toBe(343); + expect(calculateDonutLayoutHeight(10)).toBe(375); + }); + + it("donutCenterTranslation", () => { + expect(donutCenterTranslation(0)).toBe(-45); + expect(donutCenterTranslation(1)).toBe(-45); + expect(donutCenterTranslation(2)).toBe(-45); + expect(donutCenterTranslation(3)).toBe(-45); + expect(donutCenterTranslation(4)).toBe(-45); + expect(donutCenterTranslation(5)).toBe(-45); + expect(donutCenterTranslation(6)).toBe(-29); + expect(donutCenterTranslation(7)).toBe(-13); + expect(donutCenterTranslation(8)).toBe(3); + expect(donutCenterTranslation(9)).toBe(19); + expect(donutCenterTranslation(10)).toBe(35); + }); + + it("trimTopLanguages", () => { + expect(trimTopLanguages([])).toStrictEqual({ + langs: [], + totalLanguageSize: 0, + }); + expect(trimTopLanguages([langs.javascript])).toStrictEqual({ + langs: [langs.javascript], + totalLanguageSize: 200, + }); + expect( + trimTopLanguages([langs.javascript, langs.HTML], [], 5), + ).toStrictEqual({ + langs: [langs.javascript, langs.HTML], + totalLanguageSize: 400, + }); + expect(trimTopLanguages(langs, [], 5)).toStrictEqual({ + langs: Object.values(langs), + totalLanguageSize: 500, + }); + expect(trimTopLanguages(langs, [], 2)).toStrictEqual({ + langs: Object.values(langs).slice(0, 2), + totalLanguageSize: 400, + }); + expect(trimTopLanguages(langs, ["javascript"], 5)).toStrictEqual({ + langs: [langs.HTML, langs.css], + totalLanguageSize: 300, + }); + }); +}); + describe("Test renderTopLanguages", () => { it("should render correctly", () => { document.body.innerHTML = renderTopLanguages(langs); @@ -236,6 +446,81 @@ describe("Test renderTopLanguages", () => { ); }); + it("should render with layout donut", () => { + document.body.innerHTML = renderTopLanguages(langs, { layout: "donut" }); + + expect(queryByTestId(document.body, "header")).toHaveTextContent( + "Most Used Languages", + ); + + expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent( + "HTML 40.00%", + ); + expect(queryAllByTestId(document.body, "lang-donut")[0]).toHaveAttribute( + "size", + "40", + ); + const d = queryAllByTestId(document.body, "lang-donut")[0] + .getAttribute("d") + .split(" ") + .filter((x) => !isNaN(x)) + .map((x) => parseFloat(x)); + const center = { x: d[7], y: d[7] }; + const HTMLLangPercent = langPercentFromSvg( + queryAllByTestId(document.body, "lang-donut")[0].getAttribute("d"), + center.x, + center.y, + ); + expect(HTMLLangPercent).toBeCloseTo(40); + + expect(queryAllByTestId(document.body, "lang-name")[1]).toHaveTextContent( + "javascript 40.00%", + ); + expect(queryAllByTestId(document.body, "lang-donut")[1]).toHaveAttribute( + "size", + "40", + ); + const javascriptLangPercent = langPercentFromSvg( + queryAllByTestId(document.body, "lang-donut")[1].getAttribute("d"), + center.x, + center.y, + ); + expect(javascriptLangPercent).toBeCloseTo(40); + + expect(queryAllByTestId(document.body, "lang-name")[2]).toHaveTextContent( + "css 20.00%", + ); + expect(queryAllByTestId(document.body, "lang-donut")[2]).toHaveAttribute( + "size", + "20", + ); + const cssLangPercent = langPercentFromSvg( + queryAllByTestId(document.body, "lang-donut")[2].getAttribute("d"), + center.x, + center.y, + ); + expect(cssLangPercent).toBeCloseTo(20); + + expect(HTMLLangPercent + javascriptLangPercent + cssLangPercent).toBe(100); + + // Should render full donut (circle) if one language is 100%. + document.body.innerHTML = renderTopLanguages( + { HTML: langs.HTML }, + { layout: "donut" }, + ); + expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent( + "HTML 100.00%", + ); + expect(queryAllByTestId(document.body, "lang-donut")[0]).toHaveAttribute( + "size", + "100", + ); + expect(queryAllByTestId(document.body, "lang-donut")).toHaveLength(1); + expect(queryAllByTestId(document.body, "lang-donut")[0].tagName).toBe( + "circle", + ); + }); + it("should render a translated title", () => { document.body.innerHTML = renderTopLanguages(langs, { locale: "cn" }); expect(document.getElementsByClassName("header")[0].textContent).toBe( From 4b19453c1ae1ab0ff7292206fae8db85b427369f Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov <53787217+qwerty541@users.noreply.github.com> Date: Wed, 10 May 2023 23:59:00 +0300 Subject: [PATCH 68/82] Fixed docstring for create donut language node function (#2713) --- src/cards/top-languages-card.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cards/top-languages-card.js b/src/cards/top-languages-card.js index 262ae972a4002a..5523c93060bb09 100644 --- a/src/cards/top-languages-card.js +++ b/src/cards/top-languages-card.js @@ -257,7 +257,7 @@ const createLanguageTextNode = ({ langs, totalSize, hideProgress }) => { /** * Create donut languages text items for all programming languages. * - * @param {object[]} props Function properties. + * @param {object} props Function properties. * @param {Lang[]} props.langs Array of programming languages. * @param {number} props.totalSize Total size of all languages. * @returns {string} Donut layout programming language SVG node. From 1f4a2c4d827533813faedf2b40172fe1c221037d Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov <53787217+qwerty541@users.noreply.github.com> Date: Thu, 11 May 2023 09:36:21 +0300 Subject: [PATCH 69/82] Add missing argument to create compact lang node function to resolve vscode type error (#2714) --- src/cards/top-languages-card.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cards/top-languages-card.js b/src/cards/top-languages-card.js index 5523c93060bb09..ed51f1c9ddbfeb 100644 --- a/src/cards/top-languages-card.js +++ b/src/cards/top-languages-card.js @@ -268,6 +268,7 @@ const createDonutLanguagesNode = ({ langs, totalSize }) => { return createCompactLangNode({ lang, totalSize, + hideProgress: false, index, }); }), From ff9839b73caefcfb0185e61f529c7def7398fc1c Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov <53787217+qwerty541@users.noreply.github.com> Date: Thu, 11 May 2023 09:49:57 +0300 Subject: [PATCH 70/82] Top languages card pie layout (#2709) * Top languages card donut layout * Top languages card pie layout * renames * dev * docs * dev * dev * animations * dev * handle one language --- readme.md | 12 +++ src/cards/top-languages-card.js | 115 +++++++++++++++++++++++++++- src/cards/types.d.ts | 2 +- tests/renderTopLanguages.test.js | 127 ++++++++++++++++++++++++++++++- 4 files changed, 250 insertions(+), 6 deletions(-) diff --git a/readme.md b/readme.md index 5f3c125476343a..d1e56775f91a44 100644 --- a/readme.md +++ b/readme.md @@ -431,6 +431,14 @@ You can use the `&layout=donut` option to change the card design. [![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=donut)](https://github.com/anuraghazra/github-readme-stats) ``` +### Pie Chart Language Card Layout + +You can use the `&layout=pie` option to change the card design. + +```md +[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=pie)](https://github.com/anuraghazra/github-readme-stats) +``` + ### Hide Progress Bars You can use the `&hide_progress=true` option to hide the percentages and the progress bars (layout will be automatically set to `compact`). @@ -451,6 +459,10 @@ You can use the `&hide_progress=true` option to hide the percentages and the pro [![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=donut)](https://github.com/anuraghazra/github-readme-stats) +- Pie Chart layout + +[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=pie)](https://github.com/anuraghazra/github-readme-stats) + - Hidden progress bars [![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) diff --git a/src/cards/top-languages-card.js b/src/cards/top-languages-card.js index ed51f1c9ddbfeb..e03e8bcb00a35e 100644 --- a/src/cards/top-languages-card.js +++ b/src/cards/top-languages-card.js @@ -114,6 +114,16 @@ const calculateDonutLayoutHeight = (totalLangs) => { return 215 + Math.max(totalLangs - 5, 0) * 32; }; +/** + * Calculates height for the pie layout. + * + * @param {number} totalLangs Total number of languages. + * @returns {number} Card height. + */ +const calculatePieLayoutHeight = (totalLangs) => { + return 300 + Math.round(totalLangs / 2) * 25; +}; + /** * Calculates the center translation needed to keep the donut chart centred. * @param {number} totalLangs Total number of languages. @@ -361,6 +371,101 @@ const renderCompactLayout = (langs, width, totalLanguageSize, hideProgress) => { `; }; +/** + * Renders pie layout to display user's most frequently used programming languages. + * + * @param {Lang[]} langs Array of programming languages. + * @param {number} totalLanguageSize Total size of all languages. + * @returns {string} Compact layout card SVG object. + */ +const renderPieLayout = (langs, totalLanguageSize) => { + // Pie chart radius and center coordinates + const radius = 90; + const centerX = 150; + const centerY = 100; + + // Start angle for the pie chart parts + let startAngle = 0; + + // Start delay coefficient for the pie chart parts + let startDelayCoefficient = 1; + + // SVG paths + const paths = []; + + // Generate each pie chart part + for (const lang of langs) { + if (langs.length === 1) { + paths.push(` + + `); + break; + } + + const langSizePart = lang.size / totalLanguageSize; + const percentage = langSizePart * 100; + // Calculate the angle for the current part + const angle = langSizePart * 360; + + // Calculate the end angle + const endAngle = startAngle + angle; + + // Calculate the coordinates of the start and end points of the arc + const startPoint = polarToCartesian(centerX, centerY, radius, startAngle); + const endPoint = polarToCartesian(centerX, centerY, radius, endAngle); + + // Determine the large arc flag based on the angle + const largeArcFlag = angle > 180 ? 1 : 0; + + // Calculate delay + const delay = startDelayCoefficient * 100; + + // SVG arc markup + paths.push(` + + + + `); + + // Update the start angle for the next part + startAngle = endAngle; + // Update the start delay coefficient for the next part + startDelayCoefficient += 1; + } + + return ` + + + + ${paths.join("")} + + + + + ${createLanguageTextNode({ + langs, + totalSize: totalLanguageSize, + hideProgress: false, + })} + + + + `; +}; + /** * Creates the SVG paths for the language donut chart. * @@ -505,7 +610,10 @@ const renderTopLanguages = (topLangs, options = {}) => { let height = calculateNormalLayoutHeight(langs.length); let finalLayout = ""; - if (layout === "compact" || hide_progress == true) { + if (layout === "pie") { + height = calculatePieLayoutHeight(langs.length); + finalLayout = renderPieLayout(langs, totalLanguageSize); + } else if (layout === "compact" || hide_progress == true) { height = calculateCompactLayoutHeight(langs.length) + (hide_progress ? -25 : 0); @@ -580,6 +688,10 @@ const renderTopLanguages = (topLangs, options = {}) => { `, ); + if (layout === "pie") { + return card.render(finalLayout); + } + return card.render(` ${finalLayout} @@ -596,6 +708,7 @@ export { calculateCompactLayoutHeight, calculateNormalLayoutHeight, calculateDonutLayoutHeight, + calculatePieLayoutHeight, donutCenterTranslation, trimTopLanguages, renderTopLanguages, diff --git a/src/cards/types.d.ts b/src/cards/types.d.ts index fea5aa954222c1..7945118cbe3848 100644 --- a/src/cards/types.d.ts +++ b/src/cards/types.d.ts @@ -39,7 +39,7 @@ export type TopLangOptions = CommonOptions & { hide_border: boolean; card_width: number; hide: string[]; - layout: "compact" | "normal" | "donut"; + layout: "compact" | "normal" | "donut" | "pie"; custom_title: string; langs_count: number; disable_animations: boolean; diff --git a/tests/renderTopLanguages.test.js b/tests/renderTopLanguages.test.js index e4f47c396b80bd..e4bc56de8dc955 100644 --- a/tests/renderTopLanguages.test.js +++ b/tests/renderTopLanguages.test.js @@ -9,6 +9,7 @@ import { calculateCompactLayoutHeight, calculateNormalLayoutHeight, calculateDonutLayoutHeight, + calculatePieLayoutHeight, donutCenterTranslation, trimTopLanguages, renderTopLanguages, @@ -40,12 +41,13 @@ const langs = { /** * Retrieve the language percentage from the donut chart SVG. + * * @param {string} d The SVG path element. * @param {number} centerX The center X coordinate of the donut chart. * @param {number} centerY The center Y coordinate of the donut chart. * @returns {number} The percentage of the language. */ -const langPercentFromSvg = (d, centerX, centerY) => { +const langPercentFromDonutLayoutSvg = (d, centerX, centerY) => { const dTmp = d .split(" ") .filter((x) => !isNaN(x)) @@ -58,6 +60,34 @@ const langPercentFromSvg = (d, centerX, centerY) => { return (endAngle - startAngle) / 3.6; }; +/** + * Retrieve the language percentage from the pie chart SVG. + * + * @param {string} d The SVG path element. + * @param {number} centerX The center X coordinate of the pie chart. + * @param {number} centerY The center Y coordinate of the pie chart. + * @returns {number} The percentage of the language. + */ +const langPercentFromPieLayoutSvg = (d, centerX, centerY) => { + const dTmp = d + .split(" ") + .filter((x) => !isNaN(x)) + .map((x) => parseFloat(x)); + const startAngle = cartesianToPolar( + centerX, + centerY, + dTmp[2], + dTmp[3], + ).angleInDegrees; + let endAngle = cartesianToPolar( + centerX, + centerY, + dTmp[9], + dTmp[10], + ).angleInDegrees; + return ((endAngle - startAngle) / 360) * 100; +}; + describe("Test renderTopLanguages helper functions", () => { it("getLongestLang", () => { const langArray = Object.values(langs); @@ -193,6 +223,20 @@ describe("Test renderTopLanguages helper functions", () => { expect(calculateDonutLayoutHeight(10)).toBe(375); }); + it("calculatePieLayoutHeight", () => { + expect(calculatePieLayoutHeight(0)).toBe(300); + expect(calculatePieLayoutHeight(1)).toBe(325); + expect(calculatePieLayoutHeight(2)).toBe(325); + expect(calculatePieLayoutHeight(3)).toBe(350); + expect(calculatePieLayoutHeight(4)).toBe(350); + expect(calculatePieLayoutHeight(5)).toBe(375); + expect(calculatePieLayoutHeight(6)).toBe(375); + expect(calculatePieLayoutHeight(7)).toBe(400); + expect(calculatePieLayoutHeight(8)).toBe(400); + expect(calculatePieLayoutHeight(9)).toBe(425); + expect(calculatePieLayoutHeight(10)).toBe(425); + }); + it("donutCenterTranslation", () => { expect(donutCenterTranslation(0)).toBe(-45); expect(donutCenterTranslation(1)).toBe(-45); @@ -466,7 +510,7 @@ describe("Test renderTopLanguages", () => { .filter((x) => !isNaN(x)) .map((x) => parseFloat(x)); const center = { x: d[7], y: d[7] }; - const HTMLLangPercent = langPercentFromSvg( + const HTMLLangPercent = langPercentFromDonutLayoutSvg( queryAllByTestId(document.body, "lang-donut")[0].getAttribute("d"), center.x, center.y, @@ -480,7 +524,7 @@ describe("Test renderTopLanguages", () => { "size", "40", ); - const javascriptLangPercent = langPercentFromSvg( + const javascriptLangPercent = langPercentFromDonutLayoutSvg( queryAllByTestId(document.body, "lang-donut")[1].getAttribute("d"), center.x, center.y, @@ -494,7 +538,7 @@ describe("Test renderTopLanguages", () => { "size", "20", ); - const cssLangPercent = langPercentFromSvg( + const cssLangPercent = langPercentFromDonutLayoutSvg( queryAllByTestId(document.body, "lang-donut")[2].getAttribute("d"), center.x, center.y, @@ -520,6 +564,81 @@ describe("Test renderTopLanguages", () => { "circle", ); }); + it("should render with layout pie", () => { + document.body.innerHTML = renderTopLanguages(langs, { layout: "pie" }); + + expect(queryByTestId(document.body, "header")).toHaveTextContent( + "Most Used Languages", + ); + + expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent( + "HTML 40.00%", + ); + expect(queryAllByTestId(document.body, "lang-pie")[0]).toHaveAttribute( + "size", + "40", + ); + + const d = queryAllByTestId(document.body, "lang-pie")[0] + .getAttribute("d") + .split(" ") + .filter((x) => !isNaN(x)) + .map((x) => parseFloat(x)); + const center = { x: d[0], y: d[1] }; + const HTMLLangPercent = langPercentFromPieLayoutSvg( + queryAllByTestId(document.body, "lang-pie")[0].getAttribute("d"), + center.x, + center.y, + ); + expect(HTMLLangPercent).toBeCloseTo(40); + + expect(queryAllByTestId(document.body, "lang-name")[1]).toHaveTextContent( + "javascript 40.00%", + ); + expect(queryAllByTestId(document.body, "lang-pie")[1]).toHaveAttribute( + "size", + "40", + ); + const javascriptLangPercent = langPercentFromPieLayoutSvg( + queryAllByTestId(document.body, "lang-pie")[1].getAttribute("d"), + center.x, + center.y, + ); + expect(javascriptLangPercent).toBeCloseTo(40); + + expect(queryAllByTestId(document.body, "lang-name")[2]).toHaveTextContent( + "css 20.00%", + ); + expect(queryAllByTestId(document.body, "lang-pie")[2]).toHaveAttribute( + "size", + "20", + ); + const cssLangPercent = langPercentFromPieLayoutSvg( + queryAllByTestId(document.body, "lang-pie")[2].getAttribute("d"), + center.x, + center.y, + ); + expect(cssLangPercent).toBeCloseTo(20); + + expect(HTMLLangPercent + javascriptLangPercent + cssLangPercent).toBe(100); + + // Should render full pie (circle) if one language is 100%. + document.body.innerHTML = renderTopLanguages( + { HTML: langs.HTML }, + { layout: "pie" }, + ); + expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent( + "HTML 100.00%", + ); + expect(queryAllByTestId(document.body, "lang-pie")[0]).toHaveAttribute( + "size", + "100", + ); + expect(queryAllByTestId(document.body, "lang-pie")).toHaveLength(1); + expect(queryAllByTestId(document.body, "lang-pie")[0].tagName).toBe( + "circle", + ); + }); it("should render a translated title", () => { document.body.innerHTML = renderTopLanguages(langs, { locale: "cn" }); From d59a80599f781fd9af4731236519a2e551e97590 Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov Date: Sat, 13 May 2023 14:20:18 +0300 Subject: [PATCH 71/82] Docs: add missing top languages card layouts. (#2717) --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index d1e56775f91a44..f59342f3ab9f45 100644 --- a/readme.md +++ b/readme.md @@ -303,7 +303,7 @@ You can provide multiple comma-separated values in the bg_color option to render - `hide` - Hide the languages specified from the card _(Comma-separated values)_. Default: `[] (blank array)`. - `hide_title` - _(boolean)_. Default: `false`. -- `layout` - Switch between two available layouts `default` & `compact`. Default: `default`. +- `layout` - Switch between four available layouts `normal` & `compact` & `donut` & `pie`. Default: `normal`. - `card_width` - Set the card's width manually _(number)_. Default `300`. - `langs_count` - Show more languages on the card, between 1-10 _(number)_. Default `5`. - `exclude_repo` - Exclude specified repositories _(Comma-separated values)_. Default: `[] (blank array)`. From 6d45f89c9efcccc995a1912695cbe269f0e5c289 Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov Date: Sat, 13 May 2023 14:21:06 +0300 Subject: [PATCH 72/82] Top languages card render test: move repeated code into helper function (#2718) --- tests/renderTopLanguages.test.js | 39 +++++++++++++++++--------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/tests/renderTopLanguages.test.js b/tests/renderTopLanguages.test.js index e4bc56de8dc955..a8bc873a79ef1a 100644 --- a/tests/renderTopLanguages.test.js +++ b/tests/renderTopLanguages.test.js @@ -39,6 +39,19 @@ const langs = { }, }; +/** + * Retrieve number array from SVG path definition string. + * + * @param {string} d SVG path definition string. + * @return {number[]} Resulting numbers array. + */ +const getNumbersFromSvgPathDefinitionAttribute = (d) => { + return d + .split(" ") + .filter((x) => !isNaN(x)) + .map((x) => parseFloat(x)); +}; + /** * Retrieve the language percentage from the donut chart SVG. * @@ -48,10 +61,7 @@ const langs = { * @returns {number} The percentage of the language. */ const langPercentFromDonutLayoutSvg = (d, centerX, centerY) => { - const dTmp = d - .split(" ") - .filter((x) => !isNaN(x)) - .map((x) => parseFloat(x)); + const dTmp = getNumbersFromSvgPathDefinitionAttribute(d); const endAngle = cartesianToPolar(centerX, centerY, dTmp[0], dTmp[1]).angleInDegrees + 90; let startAngle = @@ -69,10 +79,7 @@ const langPercentFromDonutLayoutSvg = (d, centerX, centerY) => { * @returns {number} The percentage of the language. */ const langPercentFromPieLayoutSvg = (d, centerX, centerY) => { - const dTmp = d - .split(" ") - .filter((x) => !isNaN(x)) - .map((x) => parseFloat(x)); + const dTmp = getNumbersFromSvgPathDefinitionAttribute(d); const startAngle = cartesianToPolar( centerX, centerY, @@ -504,11 +511,9 @@ describe("Test renderTopLanguages", () => { "size", "40", ); - const d = queryAllByTestId(document.body, "lang-donut")[0] - .getAttribute("d") - .split(" ") - .filter((x) => !isNaN(x)) - .map((x) => parseFloat(x)); + const d = getNumbersFromSvgPathDefinitionAttribute( + queryAllByTestId(document.body, "lang-donut")[0].getAttribute("d"), + ); const center = { x: d[7], y: d[7] }; const HTMLLangPercent = langPercentFromDonutLayoutSvg( queryAllByTestId(document.body, "lang-donut")[0].getAttribute("d"), @@ -579,11 +584,9 @@ describe("Test renderTopLanguages", () => { "40", ); - const d = queryAllByTestId(document.body, "lang-pie")[0] - .getAttribute("d") - .split(" ") - .filter((x) => !isNaN(x)) - .map((x) => parseFloat(x)); + const d = getNumbersFromSvgPathDefinitionAttribute( + queryAllByTestId(document.body, "lang-pie")[0].getAttribute("d"), + ); const center = { x: d[0], y: d[1] }; const HTMLLangPercent = langPercentFromPieLayoutSvg( queryAllByTestId(document.body, "lang-pie")[0].getAttribute("d"), From 7ec1a76c6590a8c9682aa71d81ca6bef0405a078 Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov Date: Wed, 17 May 2023 09:37:25 +0300 Subject: [PATCH 73/82] Docs (translations): add missing top languages card layouts. (#2728) --- docs/readme_cn.md | 2 +- docs/readme_de.md | 2 +- docs/readme_es.md | 2 +- docs/readme_fr.md | 2 +- docs/readme_it.md | 2 +- docs/readme_ja.md | 2 +- docs/readme_kr.md | 2 +- docs/readme_nl.md | 2 +- docs/readme_np.md | 2 +- docs/readme_pt-BR.md | 2 +- docs/readme_tr.md | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/readme_cn.md b/docs/readme_cn.md index d6487b66182647..3bd2c149c8b91f 100644 --- a/docs/readme_cn.md +++ b/docs/readme_cn.md @@ -171,7 +171,7 @@ dark, radical, merko, gruvbox, tokyonight, onedark, cobalt, synthwave, highcontr - `hide` - 从卡片中隐藏指定语言 _(Comma seperated values)_ - `hide_title` - _(boolean)_ -- `layout` - 在两个可用布局 `default` & `compact` 间切换 +- `layout` - 提供四種佈局 `normal` & `compact` & `donut` & `pie` 间切换 - `card_width` - 手动设置卡片的宽度 _(number)_ > :warning: **重要:** diff --git a/docs/readme_de.md b/docs/readme_de.md index 1ad0d26a390db5..a94e8c2c8613c2 100644 --- a/docs/readme_de.md +++ b/docs/readme_de.md @@ -161,7 +161,7 @@ Du kannst mehrere, mit Kommas separierte, Werte in der bg_color Option angeben, - `hide` - Verbirgt die angegebenen Sprachen von der Karte _(Komma separierte Werte)_ - `hide_title` - _(Boolean)_ -- `layout` - Wechsel zwischen den zwei verfügbaren Layouts `default` & `compact` +- `layout` - Wechseln Sie zwischen den vier verfügbaren Layouts `normal` & `compact` & `donut` & `pie` - `card_width` - Lege die Breite der Karte manuell fest _(Zahl)_ > :warning: **Wichtig:** diff --git a/docs/readme_es.md b/docs/readme_es.md index 5672027ae32d55..a353502ca56974 100644 --- a/docs/readme_es.md +++ b/docs/readme_es.md @@ -175,7 +175,7 @@ Puedes pasar mútliples valores separados por coma en la opción `bg_color` para - `hide` - Oculta de la tarjeta los lenguajes especificados _(valores separados por comas)_ - `hide_title` - _(booleano)_ -- `layout` - Cambia entre los dos diseños disponibles `default` & `compact` +- `layout` - Cambiar entre los cuatro diseños disponibles `normal` & `compact` & `donut` & `pie` - `card_width` - Establece el ancho de la tarjeta manualmente _(número)_ - `langs_count` - Muestra más lenguajes en la tarjeta, entre 1-10, por defecto 5 _(número)_ - `exclude_repo` - Excluye los repositorios especificados _(valores separados por comas)_ diff --git a/docs/readme_fr.md b/docs/readme_fr.md index ce0d3d495cc10c..427f2971451c8f 100644 --- a/docs/readme_fr.md +++ b/docs/readme_fr.md @@ -171,7 +171,7 @@ Vous pouvez fournir plusieurs valeurs (suivie d'une virgule) dans l'option bg_co - `hide` - Masquer les langages spécifiés sur la carte _(Comma seperated values)_ - `hide_title` - Masquer le titre _(boolean)_ -- `layout` - Alterner entre 2 mise en page `default` & `compact` +- `layout` - Alterner entre 4 mise en page `normal` & `compact` & `donut` & `pie` - `card_width` - Fixer la largeur de la carte manuellement _(number)_ > :warning: **Important:** diff --git a/docs/readme_it.md b/docs/readme_it.md index e54af7dc488ad1..4418c333b4be47 100644 --- a/docs/readme_it.md +++ b/docs/readme_it.md @@ -171,7 +171,7 @@ Puoi fornire valori separati da virgola nel parametro bg_color per creare un gra - `hide` - Nasconde un linguaggio specifico _(valori separati da virgola)_ - `hide_title` - Nasconde il titolo _(booleano)_ -- `layout` - Specifica il tipo di layout, `default` (esteso) o `compact` (compatto) +- `layout` - Specificare il tipo di layout, `normal` (esteso), `compact` (compatto), `donut` (ciambella) e `pie` (torta) - `card_width` - Specifica il valore della larghezza _(numero)_ > :warning: **Importante:** diff --git a/docs/readme_ja.md b/docs/readme_ja.md index 2c2def7fca1a61..e6629ae2c97ead 100644 --- a/docs/readme_ja.md +++ b/docs/readme_ja.md @@ -174,7 +174,7 @@ bg_color オプションで複数のカンマ区切りの値を指定してグ - `hide` - 特定の言語を隠す _(カンマ区切りで指定)_ - `hide_title` - _(boolean)_ -- `layout` - `default` か `compact` のいずれかのレイアウトに切り替える +- `layout` - `normal` & `compact` & `donut` & `pie` のいずれかのレイアウトに切り替える - `card_width` - カードの横幅 _(number)_ - `langs_count` - 表示される言語の数 _(1 ~ 10, 初期値 5)_ - `exclude_repo` - 指定されたリポジトリを除外する _(カンマ区切りで指定)_ diff --git a/docs/readme_kr.md b/docs/readme_kr.md index 0d7bced34ccde0..28207af5ed7a62 100644 --- a/docs/readme_kr.md +++ b/docs/readme_kr.md @@ -189,7 +189,7 @@ dark, radical, merko, gruvbox, tokyonight, onedark, cobalt, synthwave, highcontr - `hide` - 카드에서 특정 언어 제외 _(Comma-separated values)_ - `hide_title` - 타이틀 제외 _(boolean)_ -- `layout` - 사용 가능한 두 가지 값, `default` & `compact` 중 표시 형태 선택 +- `layout` - 4가지 값 사용 가능, `normal` & `compact` & `donut` & `pie` 중 표시 형태 선택 - `card_width` - 카드 너비 직접 설정 _(number)_ - `langs_count` - 카드에 표시할 언어의 수 (1-10 사이, 기본 값 : 5) _(number)_ - `exclude_repo` - 통계에 제외할 저장소 지정 _(Comma-separated values)_ diff --git a/docs/readme_nl.md b/docs/readme_nl.md index f70828f142a88c..04ba9cf4bb5163 100644 --- a/docs/readme_nl.md +++ b/docs/readme_nl.md @@ -176,7 +176,7 @@ Je kan meerdere komma verdeelde waarden in de bg_color optie geven om een kleure - `hide` - Verbergt specifieke talen van de kaart _(komma gescheiden waardes)_ - `hide_title` - _(boolean)_ -- `layout` - Keuze voor de twee beschikbare layouts `default` & `compact` +- `layout` - Keuze uit de vier beschikbare indelingen `normal` & `compact` & `donut` & `pie` - `card_width` - Stelt de breedte van de kaart handmatig in. _(nummer)_ - `langs_count` - Laat meer talen op de kaart zien, waarde tussen 1-10, staat standaard op to 5 _(nummer)_ - `exclude_repo` - Verbergt specifieke repositories _(komma gescheiden waardes)_ diff --git a/docs/readme_np.md b/docs/readme_np.md index b322545c0235ea..088039dd3bc664 100644 --- a/docs/readme_np.md +++ b/docs/readme_np.md @@ -174,7 +174,7 @@ You can provide multiple comma-separated values in bg_color option to render a g - `hide` - Hide the languages specified from the card _(Comma-separated values)_ - `hide_title` - _(boolean)_ -- `layout` - Switch between two available layouts `default` & `compact` +- `layout` - Switch between four available layouts `normal` & `compact` & `donut` & `pie`. Default: `normal`. - `card_width` - Set the card's width manually _(number)_ - `langs_count` - Show more languages on the card, between 1-10, defaults to 5 _(number)_ - `exclude_repo` - Exclude specified repositories _(Comma-separated values)_ diff --git a/docs/readme_pt-BR.md b/docs/readme_pt-BR.md index cf6eeedc4ded6a..bcb58f2b3625bf 100644 --- a/docs/readme_pt-BR.md +++ b/docs/readme_pt-BR.md @@ -164,7 +164,7 @@ Personalize a aparência do seu `Stats Card` ou `Repo Card` da maneira que desej - `hide` - Oculta linguagens específicas _(Valores separados por vírgulas)_ - `hide_title` - Oculta o título _(boolean)_ -- `layout` - Alterna entre os dois layouts disponíveis `default` & `compact` +- `layout` - Alterna entre os quatro layouts disponíveis `normal` & `compact` & `donut` & `pie` - `card_width` - Define a largura do cartão manualmente _(number)_ > :warning: **Importante:** diff --git a/docs/readme_tr.md b/docs/readme_tr.md index fe5aff738c13a5..d08a00cb457214 100644 --- a/docs/readme_tr.md +++ b/docs/readme_tr.md @@ -176,7 +176,7 @@ bg_color içerisinde birden fazla rengi gradient olarak göstermek için virgül - `hide` - Belirli bir dili listede gizler _(Virgül ile ayırılmış değerlerle)_ - `hide_title` - _(boolean)_ -- `layout` - Uygun olan iki tasarım / layout arasında değişiklik yapar `default` & `compact` +- `layout` - Dört uygun tasarım / düzen arasında geçiş `normal` & `compact` & `donut` & `pie` - `card_width` - Kartın genişliğini manuel olarak belirler _(number)_ - `langs_count` - 1-10 arasında istediğiniz kadar dil gösterebilirsiniz. Varsayılan: 5 _(number)_ - `exclude_repo` - Belirli repoları listeden çıkartır _(Virgül ile ayırılmış değerlerle)_ From f9427b2a54bbab31aa7874da25a2e3227dbb4e77 Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov Date: Fri, 19 May 2023 11:45:29 +0300 Subject: [PATCH 74/82] Top languages card donut vertical layout (#2701) * Top languages card donut layout * dev * dev * dev * dev --- readme.md | 14 +++- src/cards/top-languages-card.js | 97 ++++++++++++++++++++- src/cards/types.d.ts | 2 +- tests/renderTopLanguages.test.js | 140 +++++++++++++++++++++++++++++++ 4 files changed, 250 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index f59342f3ab9f45..0de7caa4756d5c 100644 --- a/readme.md +++ b/readme.md @@ -303,7 +303,7 @@ You can provide multiple comma-separated values in the bg_color option to render - `hide` - Hide the languages specified from the card _(Comma-separated values)_. Default: `[] (blank array)`. - `hide_title` - _(boolean)_. Default: `false`. -- `layout` - Switch between four available layouts `normal` & `compact` & `donut` & `pie`. Default: `normal`. +- `layout` - Switch between four available layouts `normal` & `compact` & `donut` & `donut-vertical` & `pie`. Default: `normal`. - `card_width` - Set the card's width manually _(number)_. Default `300`. - `langs_count` - Show more languages on the card, between 1-10 _(number)_. Default `5`. - `exclude_repo` - Exclude specified repositories _(Comma-separated values)_. Default: `[] (blank array)`. @@ -431,6 +431,14 @@ You can use the `&layout=donut` option to change the card design. [![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=donut)](https://github.com/anuraghazra/github-readme-stats) ``` +### Donut Vertical Chart Language Card Layout + +You can use the `&layout=donut-vertical` option to change the card design. + +```md +[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=donut-vertical)](https://github.com/anuraghazra/github-readme-stats) +``` + ### Pie Chart Language Card Layout You can use the `&layout=pie` option to change the card design. @@ -459,6 +467,10 @@ You can use the `&hide_progress=true` option to hide the percentages and the pro [![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=donut)](https://github.com/anuraghazra/github-readme-stats) +- Donut Vertical Chart layout + +[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=donut-vertical)](https://github.com/anuraghazra/github-readme-stats) + - Pie Chart layout [![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=pie)](https://github.com/anuraghazra/github-readme-stats) diff --git a/src/cards/top-languages-card.js b/src/cards/top-languages-card.js index e03e8bcb00a35e..c6cedb1fb077c1 100644 --- a/src/cards/top-languages-card.js +++ b/src/cards/top-languages-card.js @@ -84,6 +84,16 @@ const cartesianToPolar = (centerX, centerY, x, y) => { return { radius, angleInDegrees }; }; +/** + * Calculates length of circle. + * + * @param {number} radius Radius of the circle. + * @returns {number} The length of the circle. + */ +const getCircleLength = (radius) => { + return 2 * Math.PI * radius; +}; + /** * Calculates height for the compact layout. * @@ -114,6 +124,16 @@ const calculateDonutLayoutHeight = (totalLangs) => { return 215 + Math.max(totalLangs - 5, 0) * 32; }; +/** + * Calculates height for the donut vertical layout. + * + * @param {number} totalLangs Total number of languages. + * @returns {number} Card height. + */ +const calculateDonutVerticalLayoutHeight = (totalLangs) => { + return 300 + Math.round(totalLangs / 2) * 25; +}; + /** * Calculates height for the pie layout. * @@ -371,6 +391,76 @@ const renderCompactLayout = (langs, width, totalLanguageSize, hideProgress) => { `; }; +/** + * Renders donut vertical layout to display user's most frequently used programming languages. + * + * @param {Lang[]} langs Array of programming languages. + * @param {number} totalLanguageSize Total size of all languages. + * @returns {string} Compact layout card SVG object. + */ +const renderDonutVerticalLayout = (langs, totalLanguageSize) => { + // Donut vertical chart radius and total length + const radius = 80; + const totalCircleLength = getCircleLength(radius); + + // SVG circles + let circles = []; + + // Start indent for donut vertical chart parts + let indent = 0; + + // Start delay coefficient for donut vertical chart parts + let startDelayCoefficient = 1; + + // Generate each donut vertical chart part + for (const lang of langs) { + const percentage = (lang.size / totalLanguageSize) * 100; + const circleLength = totalCircleLength * (percentage / 100); + const delay = startDelayCoefficient * 100; + + circles.push(` + + + + `); + + // Update the indent for the next part + indent += circleLength; + // Update the start delay coefficient for the next part + startDelayCoefficient += 1; + } + + return ` + + + + ${circles.join("")} + + + + + ${createLanguageTextNode({ + langs, + totalSize: totalLanguageSize, + hideProgress: false, + })} + + + + `; +}; + /** * Renders pie layout to display user's most frequently used programming languages. * @@ -613,6 +703,9 @@ const renderTopLanguages = (topLangs, options = {}) => { if (layout === "pie") { height = calculatePieLayoutHeight(langs.length); finalLayout = renderPieLayout(langs, totalLanguageSize); + } else if (layout === "donut-vertical") { + height = calculateDonutVerticalLayoutHeight(langs.length); + finalLayout = renderDonutVerticalLayout(langs, totalLanguageSize); } else if (layout === "compact" || hide_progress == true) { height = calculateCompactLayoutHeight(langs.length) + (hide_progress ? -25 : 0); @@ -688,7 +781,7 @@ const renderTopLanguages = (topLangs, options = {}) => { `, ); - if (layout === "pie") { + if (layout === "pie" || layout === "donut-vertical") { return card.render(finalLayout); } @@ -705,9 +798,11 @@ export { radiansToDegrees, polarToCartesian, cartesianToPolar, + getCircleLength, calculateCompactLayoutHeight, calculateNormalLayoutHeight, calculateDonutLayoutHeight, + calculateDonutVerticalLayoutHeight, calculatePieLayoutHeight, donutCenterTranslation, trimTopLanguages, diff --git a/src/cards/types.d.ts b/src/cards/types.d.ts index 7945118cbe3848..d6a1de05d176f6 100644 --- a/src/cards/types.d.ts +++ b/src/cards/types.d.ts @@ -39,7 +39,7 @@ export type TopLangOptions = CommonOptions & { hide_border: boolean; card_width: number; hide: string[]; - layout: "compact" | "normal" | "donut" | "pie"; + layout: "compact" | "normal" | "donut" | "donut-vertical" | "pie"; custom_title: string; langs_count: number; disable_animations: boolean; diff --git a/tests/renderTopLanguages.test.js b/tests/renderTopLanguages.test.js index a8bc873a79ef1a..ed3bd3d76973ca 100644 --- a/tests/renderTopLanguages.test.js +++ b/tests/renderTopLanguages.test.js @@ -6,9 +6,11 @@ import { radiansToDegrees, polarToCartesian, cartesianToPolar, + getCircleLength, calculateCompactLayoutHeight, calculateNormalLayoutHeight, calculateDonutLayoutHeight, + calculateDonutVerticalLayoutHeight, calculatePieLayoutHeight, donutCenterTranslation, trimTopLanguages, @@ -70,6 +72,20 @@ const langPercentFromDonutLayoutSvg = (d, centerX, centerY) => { return (endAngle - startAngle) / 3.6; }; +/** + * Calculate language percentage for donut vertical chart SVG. + * + * @param {number} partLength Length of current chart part.. + * @param {number} totalCircleLength Total length of circle. + * @return {number} Chart part percentage. + */ +const langPercentFromDonutVerticalLayoutSvg = ( + partLength, + totalCircleLength, +) => { + return (partLength / totalCircleLength) * 100; +}; + /** * Retrieve the language percentage from the pie chart SVG. * @@ -230,6 +246,20 @@ describe("Test renderTopLanguages helper functions", () => { expect(calculateDonutLayoutHeight(10)).toBe(375); }); + it("calculateDonutVerticalLayoutHeight", () => { + expect(calculateDonutVerticalLayoutHeight(0)).toBe(300); + expect(calculateDonutVerticalLayoutHeight(1)).toBe(325); + expect(calculateDonutVerticalLayoutHeight(2)).toBe(325); + expect(calculateDonutVerticalLayoutHeight(3)).toBe(350); + expect(calculateDonutVerticalLayoutHeight(4)).toBe(350); + expect(calculateDonutVerticalLayoutHeight(5)).toBe(375); + expect(calculateDonutVerticalLayoutHeight(6)).toBe(375); + expect(calculateDonutVerticalLayoutHeight(7)).toBe(400); + expect(calculateDonutVerticalLayoutHeight(8)).toBe(400); + expect(calculateDonutVerticalLayoutHeight(9)).toBe(425); + expect(calculateDonutVerticalLayoutHeight(10)).toBe(425); + }); + it("calculatePieLayoutHeight", () => { expect(calculatePieLayoutHeight(0)).toBe(300); expect(calculatePieLayoutHeight(1)).toBe(325); @@ -258,6 +288,18 @@ describe("Test renderTopLanguages helper functions", () => { expect(donutCenterTranslation(10)).toBe(35); }); + it("getCircleLength", () => { + expect(getCircleLength(20)).toBeCloseTo(125.663); + expect(getCircleLength(30)).toBeCloseTo(188.495); + expect(getCircleLength(40)).toBeCloseTo(251.327); + expect(getCircleLength(50)).toBeCloseTo(314.159); + expect(getCircleLength(60)).toBeCloseTo(376.991); + expect(getCircleLength(70)).toBeCloseTo(439.822); + expect(getCircleLength(80)).toBeCloseTo(502.654); + expect(getCircleLength(90)).toBeCloseTo(565.486); + expect(getCircleLength(100)).toBeCloseTo(628.318); + }); + it("trimTopLanguages", () => { expect(trimTopLanguages([])).toStrictEqual({ langs: [], @@ -569,6 +611,104 @@ describe("Test renderTopLanguages", () => { "circle", ); }); + + it("should render with layout donut vertical", () => { + document.body.innerHTML = renderTopLanguages(langs, { + layout: "donut-vertical", + }); + + expect(queryByTestId(document.body, "header")).toHaveTextContent( + "Most Used Languages", + ); + + expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent( + "HTML 40.00%", + ); + expect(queryAllByTestId(document.body, "lang-donut")[0]).toHaveAttribute( + "size", + "40", + ); + + const totalCircleLength = queryAllByTestId( + document.body, + "lang-donut", + )[0].getAttribute("stroke-dasharray"); + + const HTMLLangPercent = langPercentFromDonutVerticalLayoutSvg( + queryAllByTestId(document.body, "lang-donut")[1].getAttribute( + "stroke-dashoffset", + ) - + queryAllByTestId(document.body, "lang-donut")[0].getAttribute( + "stroke-dashoffset", + ), + totalCircleLength, + ); + expect(HTMLLangPercent).toBeCloseTo(40); + + expect(queryAllByTestId(document.body, "lang-name")[1]).toHaveTextContent( + "javascript 40.00%", + ); + expect(queryAllByTestId(document.body, "lang-donut")[1]).toHaveAttribute( + "size", + "40", + ); + const javascriptLangPercent = langPercentFromDonutVerticalLayoutSvg( + queryAllByTestId(document.body, "lang-donut")[2].getAttribute( + "stroke-dashoffset", + ) - + queryAllByTestId(document.body, "lang-donut")[1].getAttribute( + "stroke-dashoffset", + ), + totalCircleLength, + ); + expect(javascriptLangPercent).toBeCloseTo(40); + + expect(queryAllByTestId(document.body, "lang-name")[2]).toHaveTextContent( + "css 20.00%", + ); + expect(queryAllByTestId(document.body, "lang-donut")[2]).toHaveAttribute( + "size", + "20", + ); + const cssLangPercent = langPercentFromDonutVerticalLayoutSvg( + totalCircleLength - + queryAllByTestId(document.body, "lang-donut")[2].getAttribute( + "stroke-dashoffset", + ), + totalCircleLength, + ); + expect(cssLangPercent).toBeCloseTo(20); + + expect(HTMLLangPercent + javascriptLangPercent + cssLangPercent).toBe(100); + }); + + it("should render with layout donut vertical full donut circle of one language is 100%", () => { + document.body.innerHTML = renderTopLanguages( + { HTML: langs.HTML }, + { layout: "donut-vertical" }, + ); + expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent( + "HTML 100.00%", + ); + expect(queryAllByTestId(document.body, "lang-donut")[0]).toHaveAttribute( + "size", + "100", + ); + const totalCircleLength = queryAllByTestId( + document.body, + "lang-donut", + )[0].getAttribute("stroke-dasharray"); + + const HTMLLangPercent = langPercentFromDonutVerticalLayoutSvg( + totalCircleLength - + queryAllByTestId(document.body, "lang-donut")[0].getAttribute( + "stroke-dashoffset", + ), + totalCircleLength, + ); + expect(HTMLLangPercent).toBeCloseTo(100); + }); + it("should render with layout pie", () => { document.body.innerHTML = renderTopLanguages(langs, { layout: "pie" }); From c8d5eda27cc30adf3c73ad01545546b7f5fddbf1 Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov Date: Mon, 22 May 2023 23:39:43 +0300 Subject: [PATCH 75/82] Docs (translations): add top languages card donut vertical layout (#2739) --- docs/readme_cn.md | 2 +- docs/readme_de.md | 2 +- docs/readme_es.md | 2 +- docs/readme_fr.md | 2 +- docs/readme_it.md | 2 +- docs/readme_ja.md | 2 +- docs/readme_kr.md | 2 +- docs/readme_nl.md | 2 +- docs/readme_np.md | 2 +- docs/readme_pt-BR.md | 2 +- docs/readme_tr.md | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/readme_cn.md b/docs/readme_cn.md index 3bd2c149c8b91f..8fda35b348140a 100644 --- a/docs/readme_cn.md +++ b/docs/readme_cn.md @@ -171,7 +171,7 @@ dark, radical, merko, gruvbox, tokyonight, onedark, cobalt, synthwave, highcontr - `hide` - 从卡片中隐藏指定语言 _(Comma seperated values)_ - `hide_title` - _(boolean)_ -- `layout` - 提供四種佈局 `normal` & `compact` & `donut` & `pie` 间切换 +- `layout` - 提供五種佈局 `normal` & `compact` & `donut` & `donut-vertical` & `pie` 间切换 - `card_width` - 手动设置卡片的宽度 _(number)_ > :warning: **重要:** diff --git a/docs/readme_de.md b/docs/readme_de.md index a94e8c2c8613c2..bb3e312909f3a9 100644 --- a/docs/readme_de.md +++ b/docs/readme_de.md @@ -161,7 +161,7 @@ Du kannst mehrere, mit Kommas separierte, Werte in der bg_color Option angeben, - `hide` - Verbirgt die angegebenen Sprachen von der Karte _(Komma separierte Werte)_ - `hide_title` - _(Boolean)_ -- `layout` - Wechseln Sie zwischen den vier verfügbaren Layouts `normal` & `compact` & `donut` & `pie` +- `layout` - Wechseln Sie zwischen den fünf verfügbaren Layouts `normal` & `compact` & `donut` & `donut-vertical` & `pie` - `card_width` - Lege die Breite der Karte manuell fest _(Zahl)_ > :warning: **Wichtig:** diff --git a/docs/readme_es.md b/docs/readme_es.md index a353502ca56974..5815e9089a90cb 100644 --- a/docs/readme_es.md +++ b/docs/readme_es.md @@ -175,7 +175,7 @@ Puedes pasar mútliples valores separados por coma en la opción `bg_color` para - `hide` - Oculta de la tarjeta los lenguajes especificados _(valores separados por comas)_ - `hide_title` - _(booleano)_ -- `layout` - Cambiar entre los cuatro diseños disponibles `normal` & `compact` & `donut` & `pie` +- `layout` - Cambiar entre los cinco diseños disponibles `normal` & `compact` & `donut` & `donut-vertical` & `pie` - `card_width` - Establece el ancho de la tarjeta manualmente _(número)_ - `langs_count` - Muestra más lenguajes en la tarjeta, entre 1-10, por defecto 5 _(número)_ - `exclude_repo` - Excluye los repositorios especificados _(valores separados por comas)_ diff --git a/docs/readme_fr.md b/docs/readme_fr.md index 427f2971451c8f..2a67ef2cd5e02c 100644 --- a/docs/readme_fr.md +++ b/docs/readme_fr.md @@ -171,7 +171,7 @@ Vous pouvez fournir plusieurs valeurs (suivie d'une virgule) dans l'option bg_co - `hide` - Masquer les langages spécifiés sur la carte _(Comma seperated values)_ - `hide_title` - Masquer le titre _(boolean)_ -- `layout` - Alterner entre 4 mise en page `normal` & `compact` & `donut` & `pie` +- `layout` - Alterner entre 5 mise en page `normal` & `compact` & `donut` & `donut-vertical` & `pie` - `card_width` - Fixer la largeur de la carte manuellement _(number)_ > :warning: **Important:** diff --git a/docs/readme_it.md b/docs/readme_it.md index 4418c333b4be47..f1c20d1a48d16d 100644 --- a/docs/readme_it.md +++ b/docs/readme_it.md @@ -171,7 +171,7 @@ Puoi fornire valori separati da virgola nel parametro bg_color per creare un gra - `hide` - Nasconde un linguaggio specifico _(valori separati da virgola)_ - `hide_title` - Nasconde il titolo _(booleano)_ -- `layout` - Specificare il tipo di layout, `normal` (esteso), `compact` (compatto), `donut` (ciambella) e `pie` (torta) +- `layout` - Specificare il tipo di layout, `normal` (esteso), `compact` (compatto), `donut` (ciambella), `donut-vertical` (ciambella verticale) e `pie` (torta) - `card_width` - Specifica il valore della larghezza _(numero)_ > :warning: **Importante:** diff --git a/docs/readme_ja.md b/docs/readme_ja.md index e6629ae2c97ead..4b1dcd2bcf314c 100644 --- a/docs/readme_ja.md +++ b/docs/readme_ja.md @@ -174,7 +174,7 @@ bg_color オプションで複数のカンマ区切りの値を指定してグ - `hide` - 特定の言語を隠す _(カンマ区切りで指定)_ - `hide_title` - _(boolean)_ -- `layout` - `normal` & `compact` & `donut` & `pie` のいずれかのレイアウトに切り替える +- `layout` - `normal` & `compact` & `donut` & `donut-vertical` & `pie` のいずれかのレイアウトに切り替える - `card_width` - カードの横幅 _(number)_ - `langs_count` - 表示される言語の数 _(1 ~ 10, 初期値 5)_ - `exclude_repo` - 指定されたリポジトリを除外する _(カンマ区切りで指定)_ diff --git a/docs/readme_kr.md b/docs/readme_kr.md index 28207af5ed7a62..6113eedc00d102 100644 --- a/docs/readme_kr.md +++ b/docs/readme_kr.md @@ -189,7 +189,7 @@ dark, radical, merko, gruvbox, tokyonight, onedark, cobalt, synthwave, highcontr - `hide` - 카드에서 특정 언어 제외 _(Comma-separated values)_ - `hide_title` - 타이틀 제외 _(boolean)_ -- `layout` - 4가지 값 사용 가능, `normal` & `compact` & `donut` & `pie` 중 표시 형태 선택 +- `layout` - 5가지 값 사용 가능, `normal` & `compact` & `donut` & `donut-vertical` & `pie` 중 표시 형태 선택 - `card_width` - 카드 너비 직접 설정 _(number)_ - `langs_count` - 카드에 표시할 언어의 수 (1-10 사이, 기본 값 : 5) _(number)_ - `exclude_repo` - 통계에 제외할 저장소 지정 _(Comma-separated values)_ diff --git a/docs/readme_nl.md b/docs/readme_nl.md index 04ba9cf4bb5163..8d38d06adbcd11 100644 --- a/docs/readme_nl.md +++ b/docs/readme_nl.md @@ -176,7 +176,7 @@ Je kan meerdere komma verdeelde waarden in de bg_color optie geven om een kleure - `hide` - Verbergt specifieke talen van de kaart _(komma gescheiden waardes)_ - `hide_title` - _(boolean)_ -- `layout` - Keuze uit de vier beschikbare indelingen `normal` & `compact` & `donut` & `pie` +- `layout` - Kies uit de vijf beschikbare lay-outs `normal` & `compact` & `donut` & `donut-vertical` & `pie` - `card_width` - Stelt de breedte van de kaart handmatig in. _(nummer)_ - `langs_count` - Laat meer talen op de kaart zien, waarde tussen 1-10, staat standaard op to 5 _(nummer)_ - `exclude_repo` - Verbergt specifieke repositories _(komma gescheiden waardes)_ diff --git a/docs/readme_np.md b/docs/readme_np.md index 088039dd3bc664..e4914263019034 100644 --- a/docs/readme_np.md +++ b/docs/readme_np.md @@ -174,7 +174,7 @@ You can provide multiple comma-separated values in bg_color option to render a g - `hide` - Hide the languages specified from the card _(Comma-separated values)_ - `hide_title` - _(boolean)_ -- `layout` - Switch between four available layouts `normal` & `compact` & `donut` & `pie`. Default: `normal`. +- `layout` - Switch between five available layouts `normal` & `compact` & `donut` & `donut-vertical` & `pie`. Default: `normal`. - `card_width` - Set the card's width manually _(number)_ - `langs_count` - Show more languages on the card, between 1-10, defaults to 5 _(number)_ - `exclude_repo` - Exclude specified repositories _(Comma-separated values)_ diff --git a/docs/readme_pt-BR.md b/docs/readme_pt-BR.md index bcb58f2b3625bf..a590440ef69ec0 100644 --- a/docs/readme_pt-BR.md +++ b/docs/readme_pt-BR.md @@ -164,7 +164,7 @@ Personalize a aparência do seu `Stats Card` ou `Repo Card` da maneira que desej - `hide` - Oculta linguagens específicas _(Valores separados por vírgulas)_ - `hide_title` - Oculta o título _(boolean)_ -- `layout` - Alterna entre os quatro layouts disponíveis `normal` & `compact` & `donut` & `pie` +- `layout` - Alternar entre os cinco layouts disponíveis `normal` & `compact` & `donut` & `donut-vertical` & `pie` - `card_width` - Define a largura do cartão manualmente _(number)_ > :warning: **Importante:** diff --git a/docs/readme_tr.md b/docs/readme_tr.md index d08a00cb457214..9ee358b97bdece 100644 --- a/docs/readme_tr.md +++ b/docs/readme_tr.md @@ -176,7 +176,7 @@ bg_color içerisinde birden fazla rengi gradient olarak göstermek için virgül - `hide` - Belirli bir dili listede gizler _(Virgül ile ayırılmış değerlerle)_ - `hide_title` - _(boolean)_ -- `layout` - Dört uygun tasarım / düzen arasında geçiş `normal` & `compact` & `donut` & `pie` +- `layout` - Beş uygun tasarım / düzen arasında geçiş yapın `normal` & `compact` & `donut` & `donut-vertical` & `pie` - `card_width` - Kartın genişliğini manuel olarak belirler _(number)_ - `langs_count` - 1-10 arasında istediğiniz kadar dil gösterebilirsiniz. Varsayılan: 5 _(number)_ - `exclude_repo` - Belirli repoları listeden çıkartır _(Virgül ile ayırılmış değerlerle)_ From ff2e02ba6841a263b6dc26807c0ea350fafac03b Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov Date: Mon, 22 May 2023 23:40:21 +0300 Subject: [PATCH 76/82] Docs: fix top languages card available layouts number (#2738) --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 0de7caa4756d5c..58b8509c09c525 100644 --- a/readme.md +++ b/readme.md @@ -303,7 +303,7 @@ You can provide multiple comma-separated values in the bg_color option to render - `hide` - Hide the languages specified from the card _(Comma-separated values)_. Default: `[] (blank array)`. - `hide_title` - _(boolean)_. Default: `false`. -- `layout` - Switch between four available layouts `normal` & `compact` & `donut` & `donut-vertical` & `pie`. Default: `normal`. +- `layout` - Switch between five available layouts `normal` & `compact` & `donut` & `donut-vertical` & `pie`. Default: `normal`. - `card_width` - Set the card's width manually _(number)_. Default `300`. - `langs_count` - Show more languages on the card, between 1-10 _(number)_. Default `5`. - `exclude_repo` - Exclude specified repositories _(Comma-separated values)_. Default: `[] (blank array)`. From c96e84a9ae9a095d3787324f84a04f0024145aff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Rozet?= Date: Fri, 26 May 2023 15:39:35 +0200 Subject: [PATCH 77/82] Ranking System v2 (#1186) * Revise rank calculation * Replace contributions by commits * Lower average stats and S+ threshold * Fix calculateRank.test.js Missing key in dictionary constructor Co-authored-by: Rick Staa * refactor: run prettier * feat: change star weight to 0.75 * Separate PRs and issues * Tweak weights * Add count_private back * fix: enable 'count_private' again * test: fix tests * refactor: improve code formatting * Higher targets --------- Co-authored-by: Rick Staa --- docs/readme_fr.md | 14 +--- readme.md | 15 +--- src/calculateRank.js | 135 +++++++++++++--------------------- src/fetchers/stats-fetcher.js | 39 ++++------ tests/api.test.js | 45 ++---------- tests/calculateRank.test.js | 86 ++++++++++++++++++++-- tests/fetchStats.test.js | 101 ++++++++++--------------- 7 files changed, 192 insertions(+), 243 deletions(-) diff --git a/docs/readme_fr.md b/docs/readme_fr.md index 2a67ef2cd5e02c..1a597dd19c4baa 100644 --- a/docs/readme_fr.md +++ b/docs/readme_fr.md @@ -90,18 +90,6 @@ Pour masquer des statistiques spécifiques, vous pouvez passer un paramètre de ![Les Stats GitHub de Anurag](https://github-readme-stats.vercel.app/api?username=anuraghazra&hide=contribs,prs) ``` -### Ajouter le compte des contributions privées au compte des commits totaux - -Vous pouvez ajouter le compte de toutes vos contributions privées au compte total des engagements en utilisant le paramètre de requête `?count_private=true`. - -_Note: Si vous déployez vous-même ce projet, les contributions privées seront comptées par défaut ; sinon, vous devez choisir de partager les comptes de vos contributions privées._ - -> Options: `&count_private=true` - -```md -![Les Stats GitHub de Anurag](https://github-readme-stats.vercel.app/api?username=anuraghazra&count_private=true) -``` - ### Afficher les icônes Pour activer les icônes, vous pouvez passer `show_icons=true` dans le paramètre de requête, comme ceci : @@ -160,7 +148,7 @@ Vous pouvez fournir plusieurs valeurs (suivie d'une virgule) dans l'option bg_co - `hide_rank` - Masquer le rang _(boolean)_ - `show_icons` - Afficher les icônes _(boolean)_ - `include_all_commits` - Compter le total de commits au lieu de ne compter que les commits de l'année en cours _(boolean)_ -- `count_private` - Compter les commits privés _(boolean)_ +- `count_private` - Compter les contributions privées _(boolean)_ - `line_height` - Fixer la hauteur de la ligne entre les textes _(number)_ #### Repo Card Exclusive Options: diff --git a/readme.md b/readme.md index 58b8509c09c525..dc60369751a272 100644 --- a/readme.md +++ b/readme.md @@ -120,19 +120,6 @@ You can pass a query parameter `&hide=` to hide any specific stats with comma-se ![Anurag's GitHub stats](https://github-readme-stats.vercel.app/api?username=anuraghazra&hide=contribs,prs) ``` -### Adding private contributions count to total commits count - -You can add the count of all your private contributions to the total commits count by using the query parameter `&count_private=true`. - -> **Note** -> If you are deploying this project yourself, the private contributions will be counted by default. If you are using the public Vercel instance, you need to choose to [share your private contributions](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-profile/managing-contribution-settings-on-your-profile/showing-your-private-contributions-and-achievements-on-your-profile). - -> Options: `&count_private=true` - -```md -![Anurag's GitHub stats](https://github-readme-stats.vercel.app/api?username=anuraghazra&count_private=true) -``` - ### Showing icons To enable icons, you can pass `&show_icons=true` in the query param, like so: @@ -283,7 +270,7 @@ You can provide multiple comma-separated values in the bg_color option to render - `rank_icon` - Shows alternative rank icon (i.e. `github` or `default`). Default: `default`. - `show_icons` - _(boolean)_. Default: `false`. - `include_all_commits` - Count total commits instead of just the current year commits _(boolean)_. Default: `false`. -- `count_private` - Count private commits _(boolean)_. Default: `false`. +- `count_private` - Count private contributions _(boolean)_. Default: `false`. - `line_height` - Sets the line height between text _(number)_. Default: `25`. - `exclude_repo` - Exclude stars from specified repositories _(Comma-separated values)_. Default: `[] (blank array)`. - `custom_title` - Sets a custom title for the card. Default: ` GitHub Stats`. diff --git a/src/calculateRank.js b/src/calculateRank.js index 215c24d848c346..7648ad412ed67f 100644 --- a/src/calculateRank.js +++ b/src/calculateRank.js @@ -1,105 +1,72 @@ -/** - * Calculates the probability of x taking on x or a value less than x in a normal distribution - * with mean and standard deviation. - * - * @see https://stackoverflow.com/a/5263759/10629172 - * - * @param {string} mean The mean of the normal distribution. - * @param {number} sigma The standard deviation of the normal distribution. - * @param {number} to The value to calculate the probability for. - * @returns {number} Probability. - */ -const normalcdf = (mean, sigma, to) => { - var z = (to - mean) / Math.sqrt(2 * sigma * sigma); - var t = 1 / (1 + 0.3275911 * Math.abs(z)); - var a1 = 0.254829592; - var a2 = -0.284496736; - var a3 = 1.421413741; - var a4 = -1.453152027; - var a5 = 1.061405429; - var erf = - 1 - ((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t * Math.exp(-z * z); - var sign = 1; - if (z < 0) { - sign = -1; - } - return (1 / 2) * (1 + sign * erf); -}; +function expsf(x, lambda = 1) { + return 2 ** (-lambda * x); +} /** * Calculates the users rank. * * @param {object} params Parameters on which the user's rank depends. - * @param {number} params.totalRepos Total number of repos. - * @param {number} params.totalCommits Total number of commits. - * @param {number} params.contributions The number of contributions. - * @param {number} params.followers The number of followers. + * @param {boolean} params.all_commits Whether `include_all_commits` was used. + * @param {number} params.commits Number of commits. * @param {number} params.prs The number of pull requests. * @param {number} params.issues The number of issues. - * @param {number} params.stargazers The number of stars. + * @param {number} params.repos Total number of repos. + * @param {number} params.stars The number of stars. + * @param {number} params.followers The number of followers. * @returns {{level: string, score: number}}} The users rank. */ -const calculateRank = ({ - totalRepos, - totalCommits, - contributions, - followers, +function calculateRank({ + all_commits, + commits, prs, issues, - stargazers, -}) => { - const COMMITS_OFFSET = 1.65; - const CONTRIBS_OFFSET = 1.65; - const ISSUES_OFFSET = 1; - const STARS_OFFSET = 0.75; - const PRS_OFFSET = 0.5; - const FOLLOWERS_OFFSET = 0.45; - const REPO_OFFSET = 1; - - const ALL_OFFSETS = - CONTRIBS_OFFSET + - ISSUES_OFFSET + - STARS_OFFSET + - PRS_OFFSET + - FOLLOWERS_OFFSET + - REPO_OFFSET; - - const RANK_S_VALUE = 1; - const RANK_DOUBLE_A_VALUE = 25; - const RANK_A2_VALUE = 45; - const RANK_A3_VALUE = 60; - const RANK_B_VALUE = 100; + repos, // unused + stars, + followers, +}) { + const COMMITS_MEAN = all_commits ? 1000 : 250, + COMMITS_WEIGHT = 2; + const PRS_MEAN = 50, + PRS_WEIGHT = 3; + const ISSUES_MEAN = 25, + ISSUES_WEIGHT = 1; + const STARS_MEAN = 250, + STARS_WEIGHT = 4; + const FOLLOWERS_MEAN = 25, + FOLLOWERS_WEIGHT = 1; - const TOTAL_VALUES = - RANK_S_VALUE + - RANK_DOUBLE_A_VALUE + - RANK_A2_VALUE + - RANK_A3_VALUE + - RANK_B_VALUE; + const TOTAL_WEIGHT = + COMMITS_WEIGHT + + PRS_WEIGHT + + ISSUES_WEIGHT + + STARS_WEIGHT + + FOLLOWERS_WEIGHT; - // prettier-ignore - const score = ( - totalCommits * COMMITS_OFFSET + - contributions * CONTRIBS_OFFSET + - issues * ISSUES_OFFSET + - stargazers * STARS_OFFSET + - prs * PRS_OFFSET + - followers * FOLLOWERS_OFFSET + - totalRepos * REPO_OFFSET - ) / 100; + const rank = + (COMMITS_WEIGHT * expsf(commits, 1 / COMMITS_MEAN) + + PRS_WEIGHT * expsf(prs, 1 / PRS_MEAN) + + ISSUES_WEIGHT * expsf(issues, 1 / ISSUES_MEAN) + + STARS_WEIGHT * expsf(stars, 1 / STARS_MEAN) + + FOLLOWERS_WEIGHT * expsf(followers, 1 / FOLLOWERS_MEAN)) / + TOTAL_WEIGHT; - const normalizedScore = normalcdf(score, TOTAL_VALUES, ALL_OFFSETS) * 100; + const RANK_S_PLUS = 0.025; + const RANK_S = 0.1; + const RANK_A_PLUS = 0.25; + const RANK_A = 0.5; + const RANK_B_PLUS = 0.75; const level = (() => { - if (normalizedScore < RANK_S_VALUE) return "S+"; - if (normalizedScore < RANK_DOUBLE_A_VALUE) return "S"; - if (normalizedScore < RANK_A2_VALUE) return "A++"; - if (normalizedScore < RANK_A3_VALUE) return "A+"; - return "B+"; + if (rank <= RANK_S_PLUS) return "S+"; + if (rank <= RANK_S) return "S"; + if (rank <= RANK_A_PLUS) return "A+"; + if (rank <= RANK_A) return "A"; + if (rank <= RANK_B_PLUS) return "B+"; + return "B"; })(); - return { level, score: normalizedScore }; -}; + return { level, score: rank * 100 }; +} export { calculateRank }; export default calculateRank; diff --git a/src/fetchers/stats-fetcher.js b/src/fetchers/stats-fetcher.js index 8603e38bbf59d6..8fecffa466f8d7 100644 --- a/src/fetchers/stats-fetcher.js +++ b/src/fetchers/stats-fetcher.js @@ -192,7 +192,7 @@ const fetchStats = async ( totalIssues: 0, totalStars: 0, contributedTo: 0, - rank: { level: "C", score: 0 }, + rank: { level: "B", score: 0 }, }; let res = await statsFetcher(username); @@ -220,53 +220,44 @@ const fetchStats = async ( const user = res.data.data.user; - // populate repoToHide map for quick lookup - // while filtering out - let repoToHide = {}; - if (exclude_repo) { - exclude_repo.forEach((repoName) => { - repoToHide[repoName] = true; - }); - } - stats.name = user.name || user.login; - stats.totalIssues = user.openIssues.totalCount + user.closedIssues.totalCount; - - // normal commits - stats.totalCommits = user.contributionsCollection.totalCommitContributions; - // if include_all_commits then just get that, - // since totalCommitsFetcher already sends totalCommits no need to += + // if include_all_commits, fetch all commits using the REST API. if (include_all_commits) { stats.totalCommits = await totalCommitsFetcher(username); + } else { + stats.totalCommits = user.contributionsCollection.totalCommitContributions; } - // if count_private then add private commits to totalCommits so far. + // if count_private, add private contributions to totalCommits. if (count_private) { stats.totalCommits += user.contributionsCollection.restrictedContributionsCount; } stats.totalPRs = user.pullRequests.totalCount; + stats.totalIssues = user.openIssues.totalCount + user.closedIssues.totalCount; stats.contributedTo = user.repositoriesContributedTo.totalCount; - // Retrieve stars while filtering out repositories to be hidden + // Retrieve stars while filtering out repositories to be hidden. + let repoToHide = new Set(exclude_repo); + stats.totalStars = user.repositories.nodes .filter((data) => { - return !repoToHide[data.name]; + return !repoToHide.has(data.name); }) .reduce((prev, curr) => { return prev + curr.stargazers.totalCount; }, 0); stats.rank = calculateRank({ - totalCommits: stats.totalCommits, - totalRepos: user.repositories.totalCount, - followers: user.followers.totalCount, - contributions: stats.contributedTo, - stargazers: stats.totalStars, + all_commits: include_all_commits, + commits: stats.totalCommits, prs: stats.totalPRs, issues: stats.totalIssues, + repos: user.repositories.totalCount, + stars: stats.totalStars, + followers: user.followers.totalCount, }); return stats; diff --git a/tests/api.test.js b/tests/api.test.js index f11832ef9141c9..0f14378312435a 100644 --- a/tests/api.test.js +++ b/tests/api.test.js @@ -12,17 +12,18 @@ const stats = { totalCommits: 200, totalIssues: 300, totalPRs: 400, - contributedTo: 500, + contributedTo: 50, rank: null, }; + stats.rank = calculateRank({ - totalCommits: stats.totalCommits, - totalRepos: 1, - followers: 0, - contributions: stats.contributedTo, - stargazers: stats.totalStars, + all_commits: false, + commits: stats.totalCommits, prs: stats.totalPRs, issues: stats.totalIssues, + repos: 1, + stars: stats.totalStars, + followers: 0, }); const data_stats = { @@ -229,38 +230,6 @@ describe("Test /api/", () => { } }); - it("should add private contributions", async () => { - const { req, res } = faker( - { - username: "anuraghazra", - count_private: true, - }, - data_stats, - ); - - await api(req, res); - - expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml"); - expect(res.send).toBeCalledWith( - renderStatsCard( - { - ...stats, - totalCommits: stats.totalCommits + 100, - rank: calculateRank({ - totalCommits: stats.totalCommits + 100, - totalRepos: 1, - followers: 0, - contributions: stats.contributedTo, - stargazers: stats.totalStars, - prs: stats.totalPRs, - issues: stats.totalIssues, - }), - }, - {}, - ), - ); - }); - it("should allow changing ring_color", async () => { const { req, res } = faker( { diff --git a/tests/calculateRank.test.js b/tests/calculateRank.test.js index 235b1b5f20b045..3bfd7f43762481 100644 --- a/tests/calculateRank.test.js +++ b/tests/calculateRank.test.js @@ -2,17 +2,87 @@ import "@testing-library/jest-dom"; import { calculateRank } from "../src/calculateRank.js"; describe("Test calculateRank", () => { - it("should calculate rank correctly", () => { + it("new user gets B rank", () => { expect( calculateRank({ - totalCommits: 100, - totalRepos: 5, + all_commits: false, + commits: 0, + prs: 0, + issues: 0, + repos: 0, + stars: 0, + followers: 0, + }), + ).toStrictEqual({ level: "B", score: 100 }); + }); + + it("average user gets A rank", () => { + expect( + calculateRank({ + all_commits: false, + commits: 250, + prs: 50, + issues: 25, + repos: 0, + stars: 250, + followers: 25, + }), + ).toStrictEqual({ level: "A", score: 50 }); + }); + + it("average user gets A rank (include_all_commits)", () => { + expect( + calculateRank({ + all_commits: true, + commits: 1000, + prs: 50, + issues: 25, + repos: 0, + stars: 250, + followers: 25, + }), + ).toStrictEqual({ level: "A", score: 50 }); + }); + + it("more than average user gets A+ rank", () => { + expect( + calculateRank({ + all_commits: false, + commits: 500, + prs: 100, + issues: 50, + repos: 0, + stars: 500, + followers: 50, + }), + ).toStrictEqual({ level: "A+", score: 25 }); + }); + + it("expert user gets S rank", () => { + expect( + calculateRank({ + all_commits: false, + commits: 1000, + prs: 200, + issues: 100, + repos: 0, + stars: 1000, followers: 100, - contributions: 61, - stargazers: 400, - prs: 300, - issues: 200, }), - ).toStrictEqual({ level: "A+", score: 49.25629684876535 }); + ).toStrictEqual({ level: "S", score: 6.25 }); + }); + + it("ezyang gets S+ rank", () => { + expect( + calculateRank({ + all_commits: false, + commits: 1000, + prs: 4000, + issues: 2000, + repos: 0, + stars: 5000, + followers: 2000, + }), + ).toStrictEqual({ level: "S+", score: 1.1363983154296875 }); }); }); diff --git a/tests/fetchStats.test.js b/tests/fetchStats.test.js index 08523f3362e0e4..3b27e8a6bc3569 100644 --- a/tests/fetchStats.test.js +++ b/tests/fetchStats.test.js @@ -102,13 +102,13 @@ describe("Test fetchStats", () => { it("should fetch correct stats", async () => { let stats = await fetchStats("anuraghazra"); const rank = calculateRank({ - totalCommits: 100, - totalRepos: 5, - followers: 100, - contributions: 61, - stargazers: 300, + all_commits: false, + commits: 100, prs: 300, issues: 200, + repos: 5, + stars: 300, + followers: 100, }); expect(stats).toStrictEqual({ @@ -132,13 +132,13 @@ describe("Test fetchStats", () => { let stats = await fetchStats("anuraghazra"); const rank = calculateRank({ - totalCommits: 100, - totalRepos: 5, - followers: 100, - contributions: 61, - stargazers: 300, + all_commits: false, + commits: 100, prs: 300, issues: 200, + repos: 5, + stars: 300, + followers: 100, }); expect(stats).toStrictEqual({ @@ -161,49 +161,26 @@ describe("Test fetchStats", () => { ); }); - it("should fetch and add private contributions", async () => { - let stats = await fetchStats("anuraghazra", true); - const rank = calculateRank({ - totalCommits: 150, - totalRepos: 5, - followers: 100, - contributions: 61, - stargazers: 300, - prs: 300, - issues: 200, - }); - - expect(stats).toStrictEqual({ - contributedTo: 61, - name: "Anurag Hazra", - totalCommits: 150, - totalIssues: 200, - totalPRs: 300, - totalStars: 300, - rank, - }); - }); - it("should fetch total commits", async () => { mock .onGet("https://api.github.com/search/commits?q=author:anuraghazra") .reply(200, { total_count: 1000 }); - let stats = await fetchStats("anuraghazra", true, true); + let stats = await fetchStats("anuraghazra", false, true); const rank = calculateRank({ - totalCommits: 1050, - totalRepos: 5, - followers: 100, - contributions: 61, - stargazers: 300, + all_commits: true, + commits: 1000, prs: 300, issues: 200, + repos: 5, + stars: 300, + followers: 100, }); expect(stats).toStrictEqual({ contributedTo: 61, name: "Anurag Hazra", - totalCommits: 1050, + totalCommits: 1000, totalIssues: 200, totalPRs: 300, totalStars: 300, @@ -216,21 +193,21 @@ describe("Test fetchStats", () => { .onGet("https://api.github.com/search/commits?q=author:anuraghazra") .reply(200, { total_count: 1000 }); - let stats = await fetchStats("anuraghazra", true, true, ["test-repo-1"]); + let stats = await fetchStats("anuraghazra", false, true, ["test-repo-1"]); const rank = calculateRank({ - totalCommits: 1050, - totalRepos: 5, - followers: 100, - contributions: 61, - stargazers: 200, + all_commits: true, + commits: 1000, prs: 300, issues: 200, + repos: 5, + stars: 200, + followers: 100, }); expect(stats).toStrictEqual({ contributedTo: 61, name: "Anurag Hazra", - totalCommits: 1050, + totalCommits: 1000, totalIssues: 200, totalPRs: 300, totalStars: 200, @@ -243,13 +220,13 @@ describe("Test fetchStats", () => { let stats = await fetchStats("anuraghazra"); const rank = calculateRank({ - totalCommits: 100, - totalRepos: 5, - followers: 100, - contributions: 61, - stargazers: 400, + all_commits: false, + commits: 100, prs: 300, issues: 200, + repos: 5, + stars: 400, + followers: 100, }); expect(stats).toStrictEqual({ @@ -268,13 +245,13 @@ describe("Test fetchStats", () => { let stats = await fetchStats("anuraghazra"); const rank = calculateRank({ - totalCommits: 100, - totalRepos: 5, - followers: 100, - contributions: 61, - stargazers: 300, + all_commits: false, + commits: 100, prs: 300, issues: 200, + repos: 5, + stars: 300, + followers: 100, }); expect(stats).toStrictEqual({ @@ -293,13 +270,13 @@ describe("Test fetchStats", () => { let stats = await fetchStats("anuraghazra"); const rank = calculateRank({ - totalCommits: 100, - totalRepos: 5, - followers: 100, - contributions: 61, - stargazers: 300, + all_commits: false, + commits: 100, prs: 300, issues: 200, + repos: 5, + stars: 300, + followers: 100, }); expect(stats).toStrictEqual({ From 73b0a91b7b07d10f8a28c569a10f80fc949114af Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov Date: Mon, 29 May 2023 09:43:37 +0300 Subject: [PATCH 78/82] Improve ukrainian translation of langcard.title (#2756) --- src/translations.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/translations.js b/src/translations.js index 45c8295e024de6..92a6fc5b90cb9b 100644 --- a/src/translations.js +++ b/src/translations.js @@ -283,7 +283,7 @@ const langCardLocales = { np: "अधिक प्रयोग गरिएको भाषाहरू", el: "Οι περισσότερο χρησιμοποιούμενες γλώσσες", ru: "Наиболее часто используемые языки", - "uk-ua": "Найбільш часто використовувані мови", + "uk-ua": "Найчастіше використовувані мови", id: "Bahasa Yang Paling Banyak Digunakan", ml: "കൂടുതൽ ഉപയോഗിച്ച ഭാഷകൾ", my: "Bahasa Paling Digunakan", From 275c1fc1c7dbb78f0d4201a3034c18d62a04b5d7 Mon Sep 17 00:00:00 2001 From: Alexandr Garbuzov Date: Mon, 29 May 2023 09:45:19 +0300 Subject: [PATCH 79/82] Show notice about no languages data instead of empty card (#2755) * Show message about no languages data instead of empty card * dev * dev * dev * dev * dev * dev --- src/cards/top-languages-card.js | 51 ++++++++++++++++++++++++++------- src/translations.js | 30 +++++++++++++++++++ 2 files changed, 70 insertions(+), 11 deletions(-) diff --git a/src/cards/top-languages-card.js b/src/cards/top-languages-card.js index c6cedb1fb077c1..92f5b9d92d9e18 100644 --- a/src/cards/top-languages-card.js +++ b/src/cards/top-languages-card.js @@ -17,6 +17,7 @@ const MIN_CARD_WIDTH = 280; const DEFAULT_LANGS_COUNT = 5; const DEFAULT_LANG_COLOR = "#858585"; const CARD_PADDING = 25; +const COMPACT_LAYOUT_BASE_HEIGHT = 90; /** * @typedef {import("../fetchers/types").Lang} Lang @@ -101,7 +102,7 @@ const getCircleLength = (radius) => { * @returns {number} Card height. */ const calculateCompactLayoutHeight = (totalLangs) => { - return 90 + Math.round(totalLangs / 2) * 25; + return COMPACT_LAYOUT_BASE_HEIGHT + Math.round(totalLangs / 2) * 25; }; /** @@ -654,6 +655,19 @@ const renderDonutLayout = (langs, width, totalLanguageSize) => { `; }; +/** + * Creates the no coding activity SVG node. + * + * @param {{color: string, text: string, layout: import("./types").TopLangOptions["layout"]}} The function prop + */ +const noLanguagesDataNode = ({ color, text, layout }) => { + return ` + ${text} + `; +}; + /** * Renders card that display user's most frequently used programming languages. * @@ -699,8 +713,24 @@ const renderTopLanguages = (topLangs, options = {}) => { : card_width; let height = calculateNormalLayoutHeight(langs.length); + // returns theme based colors with proper overrides and defaults + const colors = getCardColors({ + title_color, + text_color, + bg_color, + border_color, + theme, + }); + let finalLayout = ""; - if (layout === "pie") { + if (langs.length === 0) { + height = COMPACT_LAYOUT_BASE_HEIGHT; + finalLayout = noLanguagesDataNode({ + color: colors.textColor, + text: i18n.t("langcard.nodata"), + layout, + }); + } else if (layout === "pie") { height = calculatePieLayoutHeight(langs.length); finalLayout = renderPieLayout(langs, totalLanguageSize); } else if (layout === "donut-vertical") { @@ -724,15 +754,6 @@ const renderTopLanguages = (topLangs, options = {}) => { finalLayout = renderNormalLayout(langs, width, totalLanguageSize); } - // returns theme based colors with proper overrides and defaults - const colors = getCardColors({ - title_color, - text_color, - bg_color, - border_color, - theme, - }); - const card = new Card({ customTitle: custom_title, defaultTitle: i18n.t("langcard.title"), @@ -764,6 +785,14 @@ const renderTopLanguages = (topLangs, options = {}) => { width: 100%; } } + .stat { + font: 600 14px 'Segoe UI', Ubuntu, "Helvetica Neue", Sans-Serif; fill: ${colors.textColor}; + } + @supports(-moz-appearance: auto) { + /* Selector detects Firefox */ + .stat { font-size:12px; } + } + .bold { font-weight: 700 } .lang-name { font: 400 11px "Segoe UI", Ubuntu, Sans-Serif; fill: ${colors.textColor}; diff --git a/src/translations.js b/src/translations.js index 92a6fc5b90cb9b..51fc6aaa4cb924 100644 --- a/src/translations.js +++ b/src/translations.js @@ -293,6 +293,36 @@ const langCardLocales = { vi: "Ngôn Ngữ Thường Sử Dụng", se: "Mest använda språken", }, + "langcard.nodata": { + ar: "لا توجد بيانات لغات.", + cn: "沒有語言數據。", + "zh-tw": "沒有語言數據。", + cs: "Žádné jazykové údaje.", + de: "Keine Sprachdaten.", + bn: "কোন ভাষার ডেটা নেই।", + en: "No languages data.", + es: "Sin datos de idiomas.", + fr: "Aucune donnée sur les langues.", + hu: "Nincsenek nyelvi adatok.", + it: "Nessun dato sulle lingue.", + ja: "言語データがありません。", + kr: "언어 데이터가 없습니다.", + nl: "Ingen sprogdata.", + "pt-pt": "Sem dados de idiomas.", + "pt-br": "Sem dados de idiomas.", + np: "कुनै भाषा डाटा छैन।", + el: "Δεν υπάρχουν δεδομένα γλωσσών.", + ru: "Нет данных о языках.", + "uk-ua": "Немає даних про мови.", + id: "Tidak ada data bahasa.", + ml: "ഭാഷാ ഡാറ്റയില്ല.", + my: "Tiada data bahasa.", + sk: "Žiadne údaje o jazykoch.", + tr: "Dil verisi yok.", + pl: "Brak danych dotyczących języków.", + vi: "Không có dữ liệu ngôn ngữ.", + se: "Inga språkdata.", + }, }; const wakatimeCardLocales = { From e0b3d833b04ae434a825d60a1f6f328bfdbdacdd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 31 May 2023 13:24:11 +0200 Subject: [PATCH 80/82] refactor: update languages JSON (#2760) Co-authored-by: rickstaa --- src/common/languageColors.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/common/languageColors.json b/src/common/languageColors.json index 3937eec5a2bf5b..b670823cfd8635 100644 --- a/src/common/languageColors.json +++ b/src/common/languageColors.json @@ -106,6 +106,7 @@ "Cypher": "#34c0eb", "Cython": "#fedf5b", "D": "#ba595e", + "D2": "#526ee8", "DM": "#447265", "Dafny": "#FFEC25", "Darcs Patch": "#8eff23", @@ -185,6 +186,7 @@ "Go": "#00ADD8", "Go Checksums": "#00ADD8", "Go Module": "#00ADD8", + "Go Workspace": "#00ADD8", "Godot Resource": "#355570", "Golo": "#88562A", "Gosu": "#82937f", @@ -284,6 +286,7 @@ "Lua": "#000080", "MATLAB": "#e16737", "MAXScript": "#00a6a6", + "MDX": "#fcb32c", "MLIR": "#5EC8DB", "MQL4": "#62A8D6", "MQL5": "#4A76B8", @@ -330,11 +333,12 @@ "Nu": "#c9df40", "NumPy": "#9C8AF9", "Nunjucks": "#3d8137", + "Nushell": "#4E9906", "OASv2-json": "#85ea2d", "OASv2-yaml": "#85ea2d", "OASv3-json": "#85ea2d", "OASv3-yaml": "#85ea2d", - "OCaml": "#3be133", + "OCaml": "#ef7a08", "ObjectScript": "#424893", "Objective-C": "#438eff", "Objective-C++": "#6866fb", @@ -360,6 +364,7 @@ "PLSQL": "#dad8d8", "PLpgSQL": "#336790", "POV-Ray SDL": "#6bac65", + "Pact": "#F7A8B8", "Pan": "#cc0000", "Papyrus": "#6600cc", "Parrot": "#f3ca0a", @@ -398,6 +403,7 @@ "Quake": "#882233", "R": "#198CE7", "RAML": "#77d9fb", + "RBS": "#701516", "RDoc": "#701516", "REXX": "#d90e09", "RMarkdown": "#198ce7", @@ -472,6 +478,7 @@ "Swift": "#F05138", "SystemVerilog": "#DAE1C2", "TI Program": "#A0AA87", + "TL-Verilog": "#C40023", "TLA": "#4b0079", "TOML": "#9c4221", "TSQL": "#e38c00", @@ -512,6 +519,7 @@ "Vyper": "#2980b9", "Web Ontology Language": "#5b70bd", "WebAssembly": "#04133b", + "WebAssembly Interface Type": "#6250e7", "Whiley": "#d5c397", "Wikitext": "#fc5757", "Windows Registry Entries": "#52d5ff", From c301289f7deba0aa804da2e72e8b33029ffaa0b4 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Fri, 2 Jun 2023 11:07:59 +0200 Subject: [PATCH 81/82] fix: Make WakaTime card compatible with new API (#2707) * fix: Make WakaTime card compatible with new API This commit makes sure that the WakaTime card works with the new WakaTime API. See https://github.com/anuraghazra/github-readme-stats/issues/2698 for more information. * fix: fix chinese simplified translations * fix: improve WakaTime range order * test: fix WakaTime tests * refactor: remove WakaTime range loop * refactor: remove redundant WakaTime call * test: fix e2e tests Co-authored-by: Hakula Chen --------- Co-authored-by: Hakula Chen --- api/wakatime.js | 3 +-- readme.md | 7 +++--- src/cards/wakatime-card.js | 25 ++++++++++++++++--- src/common/utils.js | 2 ++ src/fetchers/wakatime-fetcher.js | 16 ++++++------ src/translations.js | 16 ++++++++++++ .../renderWakatimeCard.test.js.snap | 4 +-- tests/e2e/e2e.test.js | 2 +- tests/fetchWakatime.test.js | 2 +- tests/renderWakatimeCard.test.js | 2 +- 10 files changed, 57 insertions(+), 22 deletions(-) diff --git a/api/wakatime.js b/api/wakatime.js index d439c5b7ac8c67..7ae93b57993212 100644 --- a/api/wakatime.js +++ b/api/wakatime.js @@ -28,7 +28,6 @@ export default async (req, res) => { langs_count, hide, api_domain, - range, border_radius, border_color, } = req.query; @@ -40,7 +39,7 @@ export default async (req, res) => { } try { - const stats = await fetchWakatimeStats({ username, api_domain, range }); + const stats = await fetchWakatimeStats({ username, api_domain }); let cacheSeconds = clampValue( parseInt(cache_seconds || CONSTANTS.FOUR_HOURS, 10), diff --git a/readme.md b/readme.md index dc60369751a272..e7ef2b7096b5c3 100644 --- a/readme.md +++ b/readme.md @@ -315,7 +315,6 @@ You can provide multiple comma-separated values in the bg_color option to render - `layout` - Switch between two available layouts `default` & `compact`. Default `default`. - `langs_count` - Limit the number of languages on the card, defaults to all reported languages _(number)_. - `api_domain` - Set a custom API domain for the card, e.g. to use services like [Hakatime](https://github.com/mujx/hakatime) or [Wakapi](https://github.com/muety/wakapi) _(string)_. Default `Waka API`. -- `range` – Request a range different from your WakaTime default, e.g. `last_7_days`. See [WakaTime API docs](https://wakatime.com/developers#stats) for a list of available options. _(YYYY-MM, last_7_days, last_30_days, last_6_months, last_year, or all_time)_. Default `all_time`. * * * @@ -468,15 +467,15 @@ You can use the `&hide_progress=true` option to hide the percentages and the pro # Wakatime Week Stats +> **Warning** +> Please be aware that we currently only show data from Wakatime profiles that are public. You therefore have to make sure that **BOTH** `Display code time publicly` and `Display languages, editors, os, categories publicly` are enabled. + Change the `?username=` value to your [Wakatime](https://wakatime.com) username. ```md [![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) ``` -> **Note**: -> Please be aware that we currently only show data from Wakatime profiles that are public. - ### Demo [![Harlok's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=Harlok)](https://github.com/anuraghazra/github-readme-stats) diff --git a/src/cards/wakatime-card.js b/src/cards/wakatime-card.js index dacc4227dc2ad6..9c17a4c063db7b 100644 --- a/src/cards/wakatime-card.js +++ b/src/cards/wakatime-card.js @@ -279,7 +279,11 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => { : noCodingActivityNode({ // @ts-ignore color: textColor, - text: i18n.t("wakatimecard.nocodingactivity"), + text: !stats.is_coding_activity_visible + ? i18n.t("wakatimecard.notpublic") + : stats.is_other_usage_visible + ? i18n.t("wakatimecard.nocodingactivity") + : i18n.t("wakatimecard.nocodedetails"), }) } `; @@ -304,7 +308,11 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => { noCodingActivityNode({ // @ts-ignore color: textColor, - text: i18n.t("wakatimecard.nocodingactivity"), + text: !stats.is_coding_activity_visible + ? i18n.t("wakatimecard.notpublic") + : stats.is_other_usage_visible + ? i18n.t("wakatimecard.nocodingactivity") + : i18n.t("wakatimecard.nocodedetails"), }), ], gap: lheight, @@ -312,9 +320,20 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => { }).join(""); } + // Get title range text + let titleText = i18n.t("wakatimecard.title"); + switch (stats.range) { + case "last_7_days": + titleText += ` (${i18n.t("wakatimecard.last7days")})`; + break; + case "last_year": + titleText += ` (${i18n.t("wakatimecard.lastyear")})`; + break; + } + const card = new Card({ customTitle: custom_title, - defaultTitle: i18n.t("wakatimecard.title"), + defaultTitle: titleText, width: 495, height, border_radius, diff --git a/src/common/utils.js b/src/common/utils.js index 47e106140b6d05..d58203be69508d 100644 --- a/src/common/utils.js +++ b/src/common/utils.js @@ -305,6 +305,7 @@ const SECONDARY_ERROR_MESSAGES = { "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", + WAKATIME_USER_NOT_FOUND: "Make sure you have a public WakaTime profile", }; /** @@ -324,6 +325,7 @@ class CustomError extends Error { static MAX_RETRY = "MAX_RETRY"; static USER_NOT_FOUND = "USER_NOT_FOUND"; static GRAPHQL_ERROR = "GRAPHQL_ERROR"; + static WAKATIME_ERROR = "WAKATIME_ERROR"; } /** diff --git a/src/fetchers/wakatime-fetcher.js b/src/fetchers/wakatime-fetcher.js index 2af8fa9998fb1f..4578b9eb0ddda1 100644 --- a/src/fetchers/wakatime-fetcher.js +++ b/src/fetchers/wakatime-fetcher.js @@ -1,29 +1,29 @@ import axios from "axios"; -import { MissingParamError } from "../common/utils.js"; +import { CustomError, MissingParamError } from "../common/utils.js"; +import { I18n } from "../common/I18n.js"; /** * WakaTime data fetcher. * - * @param {{username: string, api_domain: string, range: string}} props Fetcher props. + * @param {{username: string, api_domain: string }} props Fetcher props. * @returns {Promise} WakaTime data response. */ -const fetchWakatimeStats = async ({ username, api_domain, range }) => { +const fetchWakatimeStats = async ({ username, api_domain }) => { if (!username) throw new MissingParamError(["username"]); try { const { data } = await axios.get( `https://${ api_domain ? api_domain.replace(/\/$/gi, "") : "wakatime.com" - }/api/v1/users/${username}/stats/${ - range || "all_time" - }?is_including_today=true`, + }/api/v1/users/${username}/stats?is_including_today=true`, ); return data.data; } catch (err) { if (err.response.status < 200 || err.response.status > 299) { - throw new Error( - "Wakatime user not found, make sure you have a wakatime profile", + throw new CustomError( + `Could not resolve to a User with the login of '${username}'`, + "WAKATIME_USER_NOT_FOUND", ); } throw err; diff --git a/src/translations.js b/src/translations.js index 51fc6aaa4cb924..c123eb346f0bd2 100644 --- a/src/translations.js +++ b/src/translations.js @@ -356,6 +356,22 @@ const wakatimeCardLocales = { vi: "Thống Kê Wakatime", se: "Wakatime statistik", }, + "wakatimecard.lastyear": { + en: "last year", + cn: "去年", + }, + "wakatimecard.last7days": { + en: "last 7 days", + cn: "最近 7 天", + }, + "wakatimecard.notpublic": { + en: "Wakatime user profile not public", + cn: "Wakatime 用户个人资料未公开", + }, + "wakatimecard.nocodedetails": { + en: "User doesn't publicly share detailed code statistics", + cn: "用户不公开分享详细的代码统计信息", + }, "wakatimecard.nocodingactivity": { ar: "لا يوجد نشاط برمجي لهذا الأسبوع", cn: "本周没有编程活动", diff --git a/tests/__snapshots__/renderWakatimeCard.test.js.snap b/tests/__snapshots__/renderWakatimeCard.test.js.snap index 6dfaf98e9742a1..055c0f6642a7a4 100644 --- a/tests/__snapshots__/renderWakatimeCard.test.js.snap +++ b/tests/__snapshots__/renderWakatimeCard.test.js.snap @@ -123,7 +123,7 @@ exports[`Test Render Wakatime Card should render correctly with compact layout 1 y="0" class="header" data-testid="header" - >Wakatime Stats + >Wakatime Stats (last 7 days) @@ -303,7 +303,7 @@ exports[`Test Render Wakatime Card should render correctly with compact layout w y="0" class="header" data-testid="header" - >Wakatime Stats + >Wakatime Stats (last 7 days) diff --git a/tests/e2e/e2e.test.js b/tests/e2e/e2e.test.js index f34859d4c8be08..91399a2a29b85b 100644 --- a/tests/e2e/e2e.test.js +++ b/tests/e2e/e2e.test.js @@ -54,7 +54,7 @@ const WAKATIME_DATA = { is_up_to_date: false, is_up_to_date_pending_future: false, percent_calculated: 0, - range: "last_7_days", + range: "all_time", status: "pending_update", timeout: 15, username: USER, diff --git a/tests/fetchWakatime.test.js b/tests/fetchWakatime.test.js index 04c01ba42c6f04..964b37dcd10922 100644 --- a/tests/fetchWakatime.test.js +++ b/tests/fetchWakatime.test.js @@ -105,7 +105,7 @@ describe("Wakatime fetcher", () => { const username = "anuraghazra"; mock .onGet( - `https://wakatime.com/api/v1/users/${username}/stats/all_time?is_including_today=true`, + `https://wakatime.com/api/v1/users/${username}/stats?is_including_today=true`, ) .reply(200, wakaTimeData); diff --git a/tests/renderWakatimeCard.test.js b/tests/renderWakatimeCard.test.js index 630f259643c78c..7553277f60bac0 100644 --- a/tests/renderWakatimeCard.test.js +++ b/tests/renderWakatimeCard.test.js @@ -43,7 +43,7 @@ describe("Test Render Wakatime Card", () => { expect( document.querySelector('g[transform="translate(0, 0)"]>text.stat.bold') .textContent, - ).toBe("本周没有编程活动"); + ).toBe("Wakatime 用户个人资料未公开"); }); it("should render without rounding", () => { From 1a2a82c3bed1153b24253b1bf1dab43e49721ea1 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Fri, 2 Jun 2023 11:14:12 +0200 Subject: [PATCH 82/82] test: add new ranking to e2e test (#2750) --- tests/e2e/e2e.test.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/e2e/e2e.test.js b/tests/e2e/e2e.test.js index 91399a2a29b85b..e7785975420771 100644 --- a/tests/e2e/e2e.test.js +++ b/tests/e2e/e2e.test.js @@ -20,10 +20,7 @@ const STATS_DATA = { totalIssues: 1, totalStars: 1, contributedTo: 1, - rank: { - level: "A+", - score: 50.88831151384285, - }, + rank: { level: "B", score: 98.50610674501908 }, }; const LANGS_DATA = {