Compare commits

..
1 Commits
Author SHA1 Message Date
rickstaa 14373a0c4d test: add new ranking to e2e test 2023-05-26 16:33:53 +02:00
9 changed files with 73 additions and 152 deletions
+29 -25
View File
@@ -1,10 +1,5 @@
function exponential_cdf(x) { function expsf(x, lambda = 1) {
return 1 - 2 ** -x; return 2 ** (-lambda * x);
}
function log_normal_cdf(x) {
// approximation
return x / (1 + x);
} }
/** /**
@@ -18,7 +13,7 @@ function log_normal_cdf(x) {
* @param {number} params.repos Total number of repos. * @param {number} params.repos Total number of repos.
* @param {number} params.stars The number of stars. * @param {number} params.stars The number of stars.
* @param {number} params.followers The number of followers. * @param {number} params.followers The number of followers.
* @returns {{level: string, percentile: number}}} The users rank. * @returns {{level: string, score: number}}} The users rank.
*/ */
function calculateRank({ function calculateRank({
all_commits, all_commits,
@@ -29,15 +24,15 @@ function calculateRank({
stars, stars,
followers, followers,
}) { }) {
const COMMITS_MEDIAN = all_commits ? 1000 : 250, const COMMITS_MEAN = all_commits ? 1000 : 250,
COMMITS_WEIGHT = 2; COMMITS_WEIGHT = 2;
const PRS_MEDIAN = 50, const PRS_MEAN = 50,
PRS_WEIGHT = 3; PRS_WEIGHT = 3;
const ISSUES_MEDIAN = 25, const ISSUES_MEAN = 25,
ISSUES_WEIGHT = 1; ISSUES_WEIGHT = 1;
const STARS_MEDIAN = 50, const STARS_MEAN = 250,
STARS_WEIGHT = 4; STARS_WEIGHT = 4;
const FOLLOWERS_MEDIAN = 10, const FOLLOWERS_MEAN = 25,
FOLLOWERS_WEIGHT = 1; FOLLOWERS_WEIGHT = 1;
const TOTAL_WEIGHT = const TOTAL_WEIGHT =
@@ -47,21 +42,30 @@ function calculateRank({
STARS_WEIGHT + STARS_WEIGHT +
FOLLOWERS_WEIGHT; FOLLOWERS_WEIGHT;
const THRESHOLDS = [1, 12.5, 25, 37.5, 50, 62.5, 75, 87.5, 100];
const LEVELS = ["S", "A+", "A", "A-", "B+", "B", "B-", "C+", "C"];
const rank = const rank =
1 - (COMMITS_WEIGHT * expsf(commits, 1 / COMMITS_MEAN) +
(COMMITS_WEIGHT * exponential_cdf(commits / COMMITS_MEDIAN) + PRS_WEIGHT * expsf(prs, 1 / PRS_MEAN) +
PRS_WEIGHT * exponential_cdf(prs / PRS_MEDIAN) + ISSUES_WEIGHT * expsf(issues, 1 / ISSUES_MEAN) +
ISSUES_WEIGHT * exponential_cdf(issues / ISSUES_MEDIAN) + STARS_WEIGHT * expsf(stars, 1 / STARS_MEAN) +
STARS_WEIGHT * log_normal_cdf(stars / STARS_MEDIAN) + FOLLOWERS_WEIGHT * expsf(followers, 1 / FOLLOWERS_MEAN)) /
FOLLOWERS_WEIGHT * log_normal_cdf(followers / FOLLOWERS_MEDIAN)) / TOTAL_WEIGHT;
TOTAL_WEIGHT;
const level = LEVELS[THRESHOLDS.findIndex((t) => rank * 100 <= t)]; 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;
return { level: level, percentile: rank * 100 }; const level = (() => {
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: rank * 100 };
} }
export { calculateRank }; export { calculateRank };
+3 -2
View File
@@ -209,8 +209,9 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
hide_rank ? 0 : 150, hide_rank ? 0 : 150,
); );
// the lower the user's percentile the better // the better user's score the the rank will be closer to zero so
const progress = 100 - rank.percentile; // subtracting 100 to get the progress in 100%
const progress = 100 - rank.score;
const cssStyles = getStyles({ const cssStyles = getStyles({
titleColor, titleColor,
ringColor, ringColor,
+11 -40
View File
@@ -17,7 +17,6 @@ const MIN_CARD_WIDTH = 280;
const DEFAULT_LANGS_COUNT = 5; const DEFAULT_LANGS_COUNT = 5;
const DEFAULT_LANG_COLOR = "#858585"; const DEFAULT_LANG_COLOR = "#858585";
const CARD_PADDING = 25; const CARD_PADDING = 25;
const COMPACT_LAYOUT_BASE_HEIGHT = 90;
/** /**
* @typedef {import("../fetchers/types").Lang} Lang * @typedef {import("../fetchers/types").Lang} Lang
@@ -102,7 +101,7 @@ const getCircleLength = (radius) => {
* @returns {number} Card height. * @returns {number} Card height.
*/ */
const calculateCompactLayoutHeight = (totalLangs) => { const calculateCompactLayoutHeight = (totalLangs) => {
return COMPACT_LAYOUT_BASE_HEIGHT + Math.round(totalLangs / 2) * 25; return 90 + Math.round(totalLangs / 2) * 25;
}; };
/** /**
@@ -655,19 +654,6 @@ 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 x="${
layout === "pie" || layout === "donut-vertical" ? CARD_PADDING : 0
}" y="11" class="stat bold" fill="${color}">${text}</text>
`;
};
/** /**
* Renders card that display user's most frequently used programming languages. * Renders card that display user's most frequently used programming languages.
* *
@@ -713,24 +699,8 @@ const renderTopLanguages = (topLangs, options = {}) => {
: card_width; : card_width;
let height = calculateNormalLayoutHeight(langs.length); 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 = ""; let finalLayout = "";
if (langs.length === 0) { if (layout === "pie") {
height = COMPACT_LAYOUT_BASE_HEIGHT;
finalLayout = noLanguagesDataNode({
color: colors.textColor,
text: i18n.t("langcard.nodata"),
layout,
});
} else if (layout === "pie") {
height = calculatePieLayoutHeight(langs.length); height = calculatePieLayoutHeight(langs.length);
finalLayout = renderPieLayout(langs, totalLanguageSize); finalLayout = renderPieLayout(langs, totalLanguageSize);
} else if (layout === "donut-vertical") { } else if (layout === "donut-vertical") {
@@ -754,6 +724,15 @@ const renderTopLanguages = (topLangs, options = {}) => {
finalLayout = renderNormalLayout(langs, width, totalLanguageSize); 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({ const card = new Card({
customTitle: custom_title, customTitle: custom_title,
defaultTitle: i18n.t("langcard.title"), defaultTitle: i18n.t("langcard.title"),
@@ -785,14 +764,6 @@ const renderTopLanguages = (topLangs, options = {}) => {
width: 100%; 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 { .lang-name {
font: 400 11px "Segoe UI", Ubuntu, Sans-Serif; font: 400 11px "Segoe UI", Ubuntu, Sans-Serif;
fill: ${colors.textColor}; fill: ${colors.textColor};
+1 -9
View File
@@ -106,7 +106,6 @@
"Cypher": "#34c0eb", "Cypher": "#34c0eb",
"Cython": "#fedf5b", "Cython": "#fedf5b",
"D": "#ba595e", "D": "#ba595e",
"D2": "#526ee8",
"DM": "#447265", "DM": "#447265",
"Dafny": "#FFEC25", "Dafny": "#FFEC25",
"Darcs Patch": "#8eff23", "Darcs Patch": "#8eff23",
@@ -186,7 +185,6 @@
"Go": "#00ADD8", "Go": "#00ADD8",
"Go Checksums": "#00ADD8", "Go Checksums": "#00ADD8",
"Go Module": "#00ADD8", "Go Module": "#00ADD8",
"Go Workspace": "#00ADD8",
"Godot Resource": "#355570", "Godot Resource": "#355570",
"Golo": "#88562A", "Golo": "#88562A",
"Gosu": "#82937f", "Gosu": "#82937f",
@@ -286,7 +284,6 @@
"Lua": "#000080", "Lua": "#000080",
"MATLAB": "#e16737", "MATLAB": "#e16737",
"MAXScript": "#00a6a6", "MAXScript": "#00a6a6",
"MDX": "#fcb32c",
"MLIR": "#5EC8DB", "MLIR": "#5EC8DB",
"MQL4": "#62A8D6", "MQL4": "#62A8D6",
"MQL5": "#4A76B8", "MQL5": "#4A76B8",
@@ -333,12 +330,11 @@
"Nu": "#c9df40", "Nu": "#c9df40",
"NumPy": "#9C8AF9", "NumPy": "#9C8AF9",
"Nunjucks": "#3d8137", "Nunjucks": "#3d8137",
"Nushell": "#4E9906",
"OASv2-json": "#85ea2d", "OASv2-json": "#85ea2d",
"OASv2-yaml": "#85ea2d", "OASv2-yaml": "#85ea2d",
"OASv3-json": "#85ea2d", "OASv3-json": "#85ea2d",
"OASv3-yaml": "#85ea2d", "OASv3-yaml": "#85ea2d",
"OCaml": "#ef7a08", "OCaml": "#3be133",
"ObjectScript": "#424893", "ObjectScript": "#424893",
"Objective-C": "#438eff", "Objective-C": "#438eff",
"Objective-C++": "#6866fb", "Objective-C++": "#6866fb",
@@ -364,7 +360,6 @@
"PLSQL": "#dad8d8", "PLSQL": "#dad8d8",
"PLpgSQL": "#336790", "PLpgSQL": "#336790",
"POV-Ray SDL": "#6bac65", "POV-Ray SDL": "#6bac65",
"Pact": "#F7A8B8",
"Pan": "#cc0000", "Pan": "#cc0000",
"Papyrus": "#6600cc", "Papyrus": "#6600cc",
"Parrot": "#f3ca0a", "Parrot": "#f3ca0a",
@@ -403,7 +398,6 @@
"Quake": "#882233", "Quake": "#882233",
"R": "#198CE7", "R": "#198CE7",
"RAML": "#77d9fb", "RAML": "#77d9fb",
"RBS": "#701516",
"RDoc": "#701516", "RDoc": "#701516",
"REXX": "#d90e09", "REXX": "#d90e09",
"RMarkdown": "#198ce7", "RMarkdown": "#198ce7",
@@ -478,7 +472,6 @@
"Swift": "#F05138", "Swift": "#F05138",
"SystemVerilog": "#DAE1C2", "SystemVerilog": "#DAE1C2",
"TI Program": "#A0AA87", "TI Program": "#A0AA87",
"TL-Verilog": "#C40023",
"TLA": "#4b0079", "TLA": "#4b0079",
"TOML": "#9c4221", "TOML": "#9c4221",
"TSQL": "#e38c00", "TSQL": "#e38c00",
@@ -519,7 +512,6 @@
"Vyper": "#2980b9", "Vyper": "#2980b9",
"Web Ontology Language": "#5b70bd", "Web Ontology Language": "#5b70bd",
"WebAssembly": "#04133b", "WebAssembly": "#04133b",
"WebAssembly Interface Type": "#6250e7",
"Whiley": "#d5c397", "Whiley": "#d5c397",
"Wikitext": "#fc5757", "Wikitext": "#fc5757",
"Windows Registry Entries": "#52d5ff", "Windows Registry Entries": "#52d5ff",
+1 -1
View File
@@ -192,7 +192,7 @@ const fetchStats = async (
totalIssues: 0, totalIssues: 0,
totalStars: 0, totalStars: 0,
contributedTo: 0, contributedTo: 0,
rank: { level: "C", percentile: 100 }, rank: { level: "B", score: 0 },
}; };
let res = await statsFetcher(username); let res = await statsFetcher(username);
+1 -1
View File
@@ -22,7 +22,7 @@ export type StatsData = {
totalIssues: number; totalIssues: number;
totalStars: number; totalStars: number;
contributedTo: number; contributedTo: number;
rank: { level: string; percentile: number }; rank: { level: string; score: number };
}; };
export type Lang = { export type Lang = {
+1 -31
View File
@@ -283,7 +283,7 @@ const langCardLocales = {
np: "अधिक प्रयोग गरिएको भाषाहरू", np: "अधिक प्रयोग गरिएको भाषाहरू",
el: "Οι περισσότερο χρησιμοποιούμενες γλώσσες", el: "Οι περισσότερο χρησιμοποιούμενες γλώσσες",
ru: "Наиболее часто используемые языки", ru: "Наиболее часто используемые языки",
"uk-ua": "Найчастіше використовувані мови", "uk-ua": "Найбільш часто використовувані мови",
id: "Bahasa Yang Paling Banyak Digunakan", id: "Bahasa Yang Paling Banyak Digunakan",
ml: "കൂടുതൽ ഉപയോഗിച്ച ഭാഷകൾ", ml: "കൂടുതൽ ഉപയോഗിച്ച ഭാഷകൾ",
my: "Bahasa Paling Digunakan", my: "Bahasa Paling Digunakan",
@@ -293,36 +293,6 @@ const langCardLocales = {
vi: "Ngôn Ngữ Thường Sử Dụng", vi: "Ngôn Ngữ Thường Sử Dụng",
se: "Mest använda språken", 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 = { const wakatimeCardLocales = {
+25 -39
View File
@@ -2,7 +2,7 @@ import "@testing-library/jest-dom";
import { calculateRank } from "../src/calculateRank.js"; import { calculateRank } from "../src/calculateRank.js";
describe("Test calculateRank", () => { describe("Test calculateRank", () => {
it("new user gets C rank", () => { it("new user gets B rank", () => {
expect( expect(
calculateRank({ calculateRank({
all_commits: false, all_commits: false,
@@ -13,24 +13,10 @@ describe("Test calculateRank", () => {
stars: 0, stars: 0,
followers: 0, followers: 0,
}), }),
).toStrictEqual({ level: "C", percentile: 100 }); ).toStrictEqual({ level: "B", score: 100 });
}); });
it("beginner user gets B- rank", () => { it("average user gets A rank", () => {
expect(
calculateRank({
all_commits: false,
commits: 125,
prs: 25,
issues: 10,
repos: 0,
stars: 25,
followers: 5,
}),
).toStrictEqual({ level: "B-", percentile: 69.333868386557 });
});
it("median user gets B+ rank", () => {
expect( expect(
calculateRank({ calculateRank({
all_commits: false, all_commits: false,
@@ -38,13 +24,13 @@ describe("Test calculateRank", () => {
prs: 50, prs: 50,
issues: 25, issues: 25,
repos: 0, repos: 0,
stars: 50, stars: 250,
followers: 10, followers: 25,
}), }),
).toStrictEqual({ level: "B+", percentile: 50 }); ).toStrictEqual({ level: "A", score: 50 });
}); });
it("average user gets B+ rank (include_all_commits)", () => { it("average user gets A rank (include_all_commits)", () => {
expect( expect(
calculateRank({ calculateRank({
all_commits: true, all_commits: true,
@@ -52,13 +38,13 @@ describe("Test calculateRank", () => {
prs: 50, prs: 50,
issues: 25, issues: 25,
repos: 0, repos: 0,
stars: 50, stars: 250,
followers: 10, followers: 25,
}), }),
).toStrictEqual({ level: "B+", percentile: 50 }); ).toStrictEqual({ level: "A", score: 50 });
}); });
it("advanced user gets A rank", () => { it("more than average user gets A+ rank", () => {
expect( expect(
calculateRank({ calculateRank({
all_commits: false, all_commits: false,
@@ -66,13 +52,13 @@ describe("Test calculateRank", () => {
prs: 100, prs: 100,
issues: 50, issues: 50,
repos: 0, repos: 0,
stars: 200, stars: 500,
followers: 40, followers: 50,
}), }),
).toStrictEqual({ level: "A", percentile: 22.72727272727273 }); ).toStrictEqual({ level: "A+", score: 25 });
}); });
it("expert user gets A+ rank", () => { it("expert user gets S rank", () => {
expect( expect(
calculateRank({ calculateRank({
all_commits: false, all_commits: false,
@@ -80,23 +66,23 @@ describe("Test calculateRank", () => {
prs: 200, prs: 200,
issues: 100, issues: 100,
repos: 0, repos: 0,
stars: 800, stars: 1000,
followers: 160, followers: 100,
}), }),
).toStrictEqual({ level: "A+", percentile: 6.082887700534744 }); ).toStrictEqual({ level: "S", score: 6.25 });
}); });
it("sindresorhus gets S rank", () => { it("ezyang gets S+ rank", () => {
expect( expect(
calculateRank({ calculateRank({
all_commits: false, all_commits: false,
commits: 1300, commits: 1000,
prs: 1500, prs: 4000,
issues: 4500, issues: 2000,
repos: 0, repos: 0,
stars: 600000, stars: 5000,
followers: 50000, followers: 2000,
}), }),
).toStrictEqual({ level: "S", percentile: 0.49947889605312934 }); ).toStrictEqual({ level: "S+", score: 1.1363983154296875 });
}); });
}); });
+1 -4
View File
@@ -20,10 +20,7 @@ const STATS_DATA = {
totalIssues: 1, totalIssues: 1,
totalStars: 1, totalStars: 1,
contributedTo: 1, contributedTo: 1,
rank: { rank: { level: "B", score: 98.50610674501908 },
level: "A+",
score: 50.88831151384285,
},
}; };
const LANGS_DATA = { const LANGS_DATA = {