diff --git a/.github/scripts/blacklisted-contributors.json b/.github/scripts/blacklisted-contributors.json new file mode 100644 index 00000000000..921e4db4ace --- /dev/null +++ b/.github/scripts/blacklisted-contributors.json @@ -0,0 +1,4 @@ +[ + "john_doe", + "jane_smith" +] diff --git a/.github/scripts/leaderboard.js b/.github/scripts/leaderboard.js new file mode 100644 index 00000000000..d777c444fe8 --- /dev/null +++ b/.github/scripts/leaderboard.js @@ -0,0 +1,138 @@ +const axios = require('axios'); +const fs = require('fs'); +const parse = require('csv-parse'); + +const CROWDIN_API_ENDPOINT = 'https://api.crowdin.com/api/v2'; +const CROWDIN_PROJECT_ID = '604593'; +const CROWDIN_PERSONAL_TOKEN = process.env.CROWDIN_PERSONAL_TOKEN; + +function delay(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +async function getProjectMembers() { + try { + const response = await axios.get(`${CROWDIN_API_ENDPOINT}/projects/${CROWDIN_PROJECT_ID}/members`, { + headers: { + 'Authorization': `Bearer ${CROWDIN_PERSONAL_TOKEN}` + } + }); + + let membersMapping = {}; + response.data.data.forEach(member => { + membersMapping[member.data.username] = { + id: member.data.id, + avatarUrl: member.data.avatarUrl + }; + }); + return membersMapping; + } catch (error) { + console.error('Error fetching project members:', error); + throw error; + } +} + +async function generateReport() { + try { + const response = await axios.post(`${CROWDIN_API_ENDPOINT}/projects/${CROWDIN_PROJECT_ID}/reports`, { + name: "top-members", + schema: { + unit: "words", + format: "csv" + } + }, { + headers: { + 'Authorization': `Bearer ${CROWDIN_PERSONAL_TOKEN}`, + 'Content-Type': 'application/json' + } + }); + return response.data.data.identifier; + } catch (error) { + console.error('Error generating report:', error); + throw error; + } +} + +async function downloadReport(identifier) { + try { + const response = await axios.get(`${CROWDIN_API_ENDPOINT}/projects/${CROWDIN_PROJECT_ID}/reports/${identifier}/download`, { + headers: { + 'Authorization': `Bearer ${CROWDIN_PERSONAL_TOKEN}` + } + }); + return response.data.data.url; + } catch (error) { + console.error('Error downloading report:', error); + throw error; + } +} + +function loadBlacklistedContributors() { + return JSON.parse(fs.readFileSync('blacklisted-contributors.json', 'utf8')); +} + + +function processCsvData(csvData, membersMapping) { + return new Promise((resolve, reject) => { + const records = []; + const parser = parse({ columns: true, skip_empty_lines: true }); + + parser.on('readable', function() { + let record; + while ((record = parser.read()) !== null) { + records.push(record); + } + }); + + parser.on('error', function(err) { + console.error('Error during CSV parsing:', err.message); + reject(err); + }); + + parser.on('end', function() { + console.log('CSV parsing completed. Total records:', records.length); + + // Incorporate the user IDs and avatars from the membersMapping + records.forEach(record => { + if (membersMapping[record.Name]) { + record.userId = membersMapping[record.Name].id; + record.avatarUrl = membersMapping[record.Name].avatarUrl; + } + }); + + const blacklistedContributors = loadBlacklistedContributors(); + console.log('Blacklisted contributors:', blacklistedContributors); + + // Filter out blacklisted contributors + const filteredRecords = records.filter(record => !blacklistedContributors.includes(record.Name)); + console.log('Filtered records count (after removing blacklisted contributors):', filteredRecords.length); + + resolve(filteredRecords); + }); + + // Provide the CSV data to the parser + console.log('Starting CSV parsing...'); + parser.write(csvData); + parser.end(); + }); +} + + + +function saveDataToJson(data) { + fs.writeFileSync('leaderboard.json', JSON.stringify(data, null, 2), 'utf8'); +} + +(async function main() { + try { + const membersMapping = await getProjectMembers(); + const reportIdentifier = await generateReport(); + await delay(5000); // Wait for 5 seconds before downloading the report + const downloadUrl = await downloadReport(reportIdentifier); + const csvDataResponse = await axios.get(downloadUrl); // Fetch the CSV data + const leaderboardData = processCsvData(csvDataResponse.data, membersMapping); // Process the CSV data + saveDataToJson(leaderboardData); + } catch (error) { + console.error('Error in main function:', error); + } +})(); diff --git a/leaderboard.json b/leaderboard.json new file mode 100644 index 00000000000..9e26dfeeb6e --- /dev/null +++ b/leaderboard.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/package.json b/package.json index 03f838b0076..a4a9c7634d6 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "write-translations": "docusaurus write-translations", "write-heading-ids": "docusaurus write-heading-ids", "reorganize": "node .github/scripts/reorganize.js", + "generate-leaderboard": "node .github/scripts/leaderboard.js", "typecheck": "tsc" }, "dependencies": { @@ -26,7 +27,9 @@ "@docusaurus/plugin-sitemap": "^2.4.1", "@docusaurus/preset-classic": "^2.4.1", "@mdx-js/react": "^1.6.22", + "axios": "^1.5.0", "clsx": "^1.2.1", + "csv-parse": "^5.5.0", "fs-extra": "^11.1.1", "prism-react-renderer": "^1.3.5", "react": "^17.0.2", diff --git a/yarn.lock b/yarn.lock index 2cac4ad3b3a..72d8fdc9996 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2565,6 +2565,11 @@ asap@~2.0.3: resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + at-least-node@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" @@ -2589,6 +2594,15 @@ axios@^0.25.0: dependencies: follow-redirects "^1.14.7" +axios@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.5.0.tgz#f02e4af823e2e46a9768cfc74691fdd0517ea267" + integrity sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ== + dependencies: + follow-redirects "^1.15.0" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + babel-loader@^8.2.5: version "8.3.0" resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.3.0.tgz#124936e841ba4fe8176786d6ff28add1f134d6a8" @@ -3033,6 +3047,13 @@ combine-promises@^1.1.0: resolved "https://registry.yarnpkg.com/combine-promises/-/combine-promises-1.2.0.tgz#5f2e68451862acf85761ded4d9e2af7769c2ca6a" integrity sha512-VcQB1ziGD0NXrhKxiwyNbCDmRzs/OShMs2GqW2DlU2A/Sd0nQxE1oWDAE5O0ygSx5mgQOn9eIFh7yKPgFRVkPQ== +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + comma-separated-tokens@^1.0.0: version "1.0.8" resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea" @@ -3383,6 +3404,11 @@ csstype@^3.0.2: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== +csv-parse@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-5.5.0.tgz#2313421e69b650dae32a79ac884b20b21ca1d9da" + integrity sha512-RxruSK3M4XgzcD7Trm2wEN+SJ26ChIb903+IWxNOcB5q4jT2Cs+hFr6QP39J05EohshRFEvyzEBoZ/466S2sbw== + debug@2.6.9, debug@^2.6.0: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -3453,6 +3479,11 @@ del@^6.1.1: rimraf "^3.0.2" slash "^3.0.0" +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + depd@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" @@ -3985,7 +4016,7 @@ flux@^4.0.1: fbemitter "^3.0.0" fbjs "^3.0.1" -follow-redirects@^1.0.0, follow-redirects@^1.14.7: +follow-redirects@^1.0.0, follow-redirects@^1.14.7, follow-redirects@^1.15.0: version "1.15.2" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== @@ -4009,6 +4040,15 @@ fork-ts-checker-webpack-plugin@^6.5.0: semver "^7.3.2" tapable "^1.0.0" +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -5199,7 +5239,7 @@ mime-types@2.1.18: dependencies: mime-db "~1.33.0" -mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: +mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -6046,6 +6086,11 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + pump@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"