├── index.md ├── images ├── demo.png ├── main_screenshot.png ├── update_screenshot.png └── CodeCogsEqn.svg ├── .gitignore ├── Gemfile ├── _config.yml ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ └── jekyll.yml ├── LICENSE ├── README.md ├── _layouts ├── home.html └── default.html ├── _includes └── custom.scss └── _plugins └── top_users_tag.rb /index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | title: Inicio 4 | --- 5 | 6 | # hola 7 | 8 | hello -------------------------------------------------------------------------------- /images/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelibaceta/top-coders-peru/HEAD/images/demo.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _site 2 | .sass-cache 3 | .jekyll-metadata 4 | .DS_Store 5 | .jekyll-cache 6 | site 7 | top_users_data.json -------------------------------------------------------------------------------- /images/main_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelibaceta/top-coders-peru/HEAD/images/main_screenshot.png -------------------------------------------------------------------------------- /images/update_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelibaceta/top-coders-peru/HEAD/images/update_screenshot.png -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'jekyll' 4 | gem 'minima' 5 | gem "kramdown", ">= 1.14.0" 6 | gem 'jekyll-seo-tag' 7 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | title: Top Coders Peru 2 | theme: minima 3 | tagline: "Peruvian developers ranking based on github stats" 4 | description: "Peruvian developers ranking based on github stats" 5 | url: "https://joelibaceta.github.io/top-coders-peru/" 6 | author: "Joel Ibaceta" 7 | locale: "es-PE" 8 | plugins: 9 | - jekyll-seo-tag -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/workflows/jekyll.yml: -------------------------------------------------------------------------------- 1 | name: Jekyll site CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | schedule: 9 | - cron: "0 0 * * *" 10 | env: 11 | ENV_LOCATION: "location:lima+location:peru" 12 | 13 | jobs: 14 | build: 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Build the site in the jekyll/builder container 21 | run: | 22 | docker run -e GH_ACCESS_TOKEN="${{ secrets.GH_ACCESS_TOKEN }}" -e LOCATION="$ENV_LOCATION" \ 23 | -v ${{ github.workspace }}:/srv/jekyll -v ${{ github.workspace }}/_site:/srv/jekyll/_site \ 24 | jekyll/builder:latest /bin/bash -c "chmod -R 777 /srv/jekyll && jekyll build --future" 25 | 26 | - name: Deploy to gh-pages 27 | uses: JamesIves/github-pages-deploy-action@v4 28 | with: 29 | branch: gh-pages # The branch the action should deploy to. 30 | folder: ${{ github.workspace }}/_site # The folder the action should deploy. 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Joel Ibaceta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Top Coders Perú 2 | Based on Github Stats 3 | 4 |
5 | 6 | [![CodeCogsEqn.svg](images/update_screenshot.png)](https://topcoders.pe/) 7 | 8 |
9 | 10 | ## ¿Cómo se calcula el Raking? 11 | 12 | Para generar el ranking se realiza un calculo del indice rockstar, considerando las siguientes variables: 13 | 14 | - Popularidad (_Número de seguidores_) 15 | - Impacto (_Número de estrellas en repositorios propios_) 16 | - Actividad (_Número de commits en el ultimo año_) 17 | - OpenSource (_Numero de proyectos personales publicos_) 18 | 19 | El indice de cada una de estas variables se divide entre el maximo general encontrado para cada variable, esto permitira obtener un indice relativo al total de la muestra. 20 | 21 |
22 | 23 | ![CodeCogsEqn.svg](images/CodeCogsEqn.svg) 24 | 25 |
26 | 27 | ## ¿Cómo funciona? 28 | 29 | La pagina esta basada en Jekyll para la generacion de contenido estatico y de Travis CI para la generacion automatica de nuevos deploys cada dia, manteniendo actualizada la información del ranking. 30 | 31 | ## ¿Como Contribuir? 32 | 33 | 1. Crear un issue con la descripcion de la contribucion 34 | 2. Hacer un fork del proyecto 35 | 3. Hacer los cambios y enviar un Pull Request 36 | 37 | ### Para iniciar el proyecto localmente necesitas 38 | 39 | - Ejecutar `bundle install` 40 | - Ejecutar `jekyll build` 41 | - Podras ver el sitio web generado en la carpeta `_site` 42 | - Definir las siguientes variables de entorno: `CLIENT_ID` y `CLIENT_SECRET` puedes obtener tus credenciales de la siguiente manera https://developer.github.com/v3/guides/basics-of-authentication/ 43 | 44 | 45 | -------------------------------------------------------------------------------- /_layouts/home.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 | 6 | 58 | 59 | {% top_users %} 60 | -------------------------------------------------------------------------------- /_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {% seo %} 9 | {% capture maincss %}{% include custom.scss %}{% endcapture %} 10 | 11 | 12 | 13 | 14 | 17 |
18 |
19 |

Perú Top Coders

20 |

based on github stats

21 | 29 |
30 |
31 | {{ content }} 32 |
33 |
34 | 35 | 36 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /_includes/custom.scss: -------------------------------------------------------------------------------- 1 | @import "minima"; 2 | 3 | /** Mixin for responsive design **/ 4 | @mixin breakpoint($class) { 5 | /** Breakpoints based on Bootstrap4 **/ 6 | @if $class == xs { 7 | @media (max-width: 575px) { 8 | @content; 9 | } 10 | } @else if $class == sm { 11 | @media (min-width: 576px) { 12 | @content; 13 | } 14 | } @else if $class == md { 15 | @media (min-width: 768px) { 16 | @content; 17 | } 18 | } @else if $class == lg { 19 | @media (min-width: 992px) { 20 | @content; 21 | } 22 | } @else if $class == xl { 23 | @media (min-width: 1140px) { 24 | @content; 25 | } 26 | } @else if $class == xxl { 27 | @media (min-width: 1180px) { 28 | @content; 29 | } 30 | } @else { 31 | @warn "Breakpoint mixin supports: xs, sm, md, lg and xl"; 32 | } 33 | } 34 | 35 | .UsersTableContainer { 36 | overflow-x: auto; 37 | } 38 | 39 | .Main { 40 | display: flex; 41 | justify-content: space-around; 42 | flex-direction: column; 43 | @include breakpoint(xl) { 44 | flex-direction: row; 45 | } 46 | &__Info { 47 | min-width: 250px; 48 | width: 20%; 49 | } 50 | &__Content { 51 | overflow-x: auto; 52 | } 53 | 54 | } 55 | 56 | .User { 57 | &__image { 58 | width: 60px; 59 | min-width: 30px; 60 | } 61 | } 62 | .column { 63 | padding: 20px; 64 | } 65 | 66 | table { 67 | font-size: 12px; 68 | thead { 69 | font-weight: 600; 70 | } 71 | } 72 | 73 | .contribute-button { 74 | padding-top: 30px; 75 | 76 | a { 77 | display: flex; 78 | align-items: center; 79 | padding-top: 10px; 80 | padding-bottom: 10px; 81 | border-top: 1px solid #ddd; 82 | border-bottom: 1px solid #ddd; 83 | 84 | svg { 85 | margin-right: 10px; 86 | } 87 | } 88 | 89 | 90 | } 91 | 92 | #flags { 93 | display: flex; 94 | justify-content: space-between; 95 | } 96 | 97 | @media (max-width: 900px) { 98 | canvas { 99 | display: none; 100 | } 101 | } 102 | 103 | .score-box { 104 | display: flex; 105 | 106 | } 107 | 108 | .score-title { 109 | display: flex; 110 | justify-content: center; 111 | flex-direction: column; 112 | font-size: 18px; 113 | } 114 | 115 | .score-detail { 116 | white-space: nowrap; 117 | i { 118 | overflow: hidden; 119 | max-width: 18ch; 120 | text-overflow: ellipsis; 121 | } 122 | padding-left: 15px; 123 | small { 124 | font-size: 9px; 125 | } 126 | display: flex; 127 | justify-content: center; 128 | flex-direction: column; 129 | } -------------------------------------------------------------------------------- /_plugins/top_users_tag.rb: -------------------------------------------------------------------------------- 1 | require 'net/http' 2 | require 'uri' 3 | require 'json' 4 | require 'uri' 5 | 6 | module Jekyll 7 | class TopUsersTag < Liquid::Tag 8 | 9 | attr_accessor :technologies 10 | attr_accessor :top_users 11 | attr_accessor :storage 12 | 13 | def make_get_request(uri) 14 | parsed_uri = URI.parse(uri) 15 | http = Net::HTTP.new(parsed_uri.host, parsed_uri.port) 16 | http.use_ssl = true 17 | 18 | request = Net::HTTP::Get.new(parsed_uri) 19 | request["Accept"] = 'application/vnd.github.cloak-preview' 20 | request["Authorization"] = 'Bearer ' + ENV['GH_ACCESS_TOKEN'] 21 | 22 | response = http.request(request) 23 | 24 | if response.code == "403" or response.body.include? "rate limit" 25 | p "Rate limit exceeded. Sleeping for 10 seconds..." 26 | sleep(10) 27 | make_get_request(uri) 28 | end 29 | 30 | return response.body 31 | end 32 | 33 | def percentile(values, percentile) 34 | values_sorted = values.sort 35 | k = (percentile*(values_sorted.length-1)+1).floor - 1 36 | f = (percentile*(values_sorted.length-1)+1).modulo(1) 37 | return values_sorted[k] + (f * (values_sorted[k+1] - values_sorted[k])) 38 | end 39 | 40 | 41 | def countCommits(user) 42 | awaitRateLimitReset() 43 | now = Time.new 44 | date_one_year_ago = "#{(now.year - 1).to_s}-#{now.month.to_s.rjust(2, '0')}-#{now.day.to_s.rjust(2, '0')}" 45 | uri = "https://api.github.com/search/commits?q=author:#{user} committer-date:>#{date_one_year_ago}&per_page=1" 46 | raw_response = make_get_request(uri) 47 | commits = JSON.parse(raw_response) 48 | return commits["total_count"] 49 | end 50 | 51 | def countContributions(user) 52 | uri = "https://github.com/users/#{user}/contributions" 53 | raw_response = make_get_request(uri) 54 | begin 55 | counter = raw_response.scan(/\>[\s\n\t]+(?[0-9\,]+)[\s\n\t]+contributions/).flatten.first.gsub(",", "").to_i 56 | rescue 57 | counter = 0 58 | end 59 | return counter 60 | end 61 | 62 | def countPRs(user) 63 | awaitRateLimitReset() 64 | uri = "https://api.github.com/search/issues?q=involves:#{user} type:pr is:merged is:public not #{user} &per_page=1" 65 | raw_response = make_get_request(uri) 66 | commits = JSON.parse(raw_response) 67 | return commits["total_count"] 68 | end 69 | 70 | def countStarts(user) 71 | awaitRateLimitReset() 72 | uri = "https://api.github.com/search/repositories?q=user:#{user}+stars:>0+repo:#{user}" 73 | raw_response = make_get_request(uri) 74 | repos = JSON.parse(raw_response) 75 | if repos["total_count"] == nil 76 | p "Error: #{repos}" 77 | end 78 | counter = 0 79 | if repos["total_count"] > 0 80 | repos["items"].each do |repo| 81 | counter += (repo["stargazers_count"].to_i) 82 | end 83 | end 84 | return counter 85 | end 86 | 87 | def getUserData(user) 88 | uri = "https://api.github.com/users/#{user}" 89 | raw_response = make_get_request(uri) 90 | return JSON.parse(raw_response) 91 | end 92 | 93 | def awaitRateLimitReset(still_sleeping=false) 94 | uri = "https://api.github.com/rate_limit" 95 | raw_response = make_get_request(uri) 96 | rate_limits = JSON.parse(raw_response) 97 | search_limit = rate_limits["resources"]["search"] 98 | if search_limit["remaining"] < 5 99 | if still_sleeping == true 100 | p "Still sleeping, 10 more seconds..." 101 | else 102 | p "Less than 5 request remaining to reach rate limit sleeping for 10 seconds..." 103 | end 104 | sleep(10) 105 | awaitRateLimitReset(still_sleeping=true) 106 | end 107 | end 108 | 109 | def getEachUserData 110 | top_users = [] 111 | 112 | location = ENV['LOCATION'] 113 | uri = "https://api.github.com/search/users?q=#{location}+followers:>10+repos:>10+type:user&per_page=50&page=1&sort=followers&order=desc" 114 | p "getting data from to #{uri}" 115 | 116 | raw_response = make_get_request(uri) 117 | users = JSON.parse(raw_response) 118 | 119 | users["items"].each do |user| 120 | 121 | data = getUserData(user["login"]) 122 | commits = countContributions(user["login"]) 123 | stars = countStarts(user["login"]) 124 | followers = data["followers"] 125 | prs = countPRs(user["login"]) 126 | 127 | # Filtrar organizaciones explícitamente 128 | next if data["type"] == "Organization" 129 | 130 | p data["name"] 131 | 132 | top_users << { 133 | "id" => user["login"], 134 | "pic" => data["avatar_url"], 135 | "name" => data["name"], 136 | "email" => data["email"], 137 | "company" => data["company"], 138 | "followers" => followers, 139 | "repos" => data["public_repos"], 140 | "url" => data["html_url"], 141 | "commits" => commits, 142 | "stars" => stars, 143 | "prs" => prs 144 | } 145 | end 146 | return top_users 147 | end 148 | 149 | def calcMaxs(users) 150 | commits, stars, followers, prs = [], [], [], [] 151 | 152 | users.each do |user| 153 | commits << user["commits"] 154 | stars << user["stars"] 155 | followers << user["followers"] 156 | prs << user["prs"] 157 | end 158 | 159 | p95_commits = percentile(commits, 0.95) 160 | p95_stars = percentile(stars, 0.95) 161 | p95_followers = percentile(followers, 0.95) 162 | p95_prs = percentile(prs, 0.95) 163 | 164 | return p95_commits, p95_stars, p95_followers, p95_prs 165 | end 166 | 167 | def getTopUsersData() 168 | 169 | if File.exist?("top_users_data.json") 170 | data = JSON.parse(open("top_users_data.json").read()) 171 | @top_users = data["users"] 172 | else 173 | @top_users = getEachUserData 174 | end 175 | 176 | max_commits, max_stars, max_followers, max_prs, max_repos = calcMaxs(@top_users) 177 | 178 | @top_users.each do |user| 179 | 180 | contributions_score = ( 181 | #[(user["repos"] / max_repos.to_f), 1.0].min + 182 | [(user["prs"] / max_prs.to_f), 1.0].min + 183 | [(user["stars"] / max_stars.to_f), 1.0].min 184 | ) / 2 185 | 186 | user["score"] = (( 187 | [(user["commits"] / max_commits.to_f), 1.0].min + 188 | contributions_score + 189 | [(user["followers"] / max_followers.to_f), 1.0].min 190 | ) / 3) * 5 191 | 192 | user["commits_pct"] = [(user["commits"] / max_commits.to_f), 1.0].min * 5 193 | user["contribs_pct"] = contributions_score * 5 194 | user["followers_pct"] = [(user["followers"] / max_followers.to_f), 1.0].min * 5 195 | 196 | end 197 | 198 | f = open("top_users_data.json", "w") 199 | f.write({ users: @top_users }.to_json) 200 | 201 | return @top_users.sort_by {|obj| obj["score"]}.reverse 202 | end 203 | 204 | def render(context) 205 | 206 | users = getTopUsersData 207 | 208 | #element = "\n" 209 | element = "
\n" 210 | element += "" 211 | element += "" 212 | element += "" 213 | element += "" 214 | element += "" 215 | element += "" 216 | element += "" 217 | users.each_with_index do |user, i| 218 | element += "" 219 | element += "" 220 | element += "" 221 | element += "" 225 | element += "" 231 | element += "" 238 | element += "" 242 | end 243 | element += "" 244 | element += "
UserInfoScorePopularityContributionsActivity
#{i + 1}
#{user["name"]}#{user["company"]}
" 222 | element += "
#{user["score"].round(1)}
" 223 | element += "
" 224 | element += "
" 226 | element += "
#{user["followers_pct"].round(1)}
" 227 | element += "
" 228 | element += "#{user["followers"].round(1)} followers
" 229 | element += "
" 230 | element += "
" 232 | element += "
#{user["contribs_pct"].round(1)}
" 233 | element += "
" 234 | element += "#{user["stars"].round(1)} stars on public repos" 235 | element += "#{user["prs"].round(1)} PRs merged on public repos
" 236 | element += "
" 237 | element += "
" 239 | element += "
#{user["commits_pct"].round(1)}
" 240 | element += "
" 241 | element += "#{user["commits"].round(1)} commits in the last year
\n" 245 | end 246 | 247 | def initialize(tag_name, text, tokens) 248 | super 249 | @technologies = Hash.new 250 | end 251 | end 252 | end 253 | 254 | Liquid::Template.register_tag('top_users', Jekyll::TopUsersTag) 255 | -------------------------------------------------------------------------------- /images/CodeCogsEqn.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | --------------------------------------------------------------------------------