mirror of
https://github.com/baz-scm/awesome-reviewers.git
synced 2025-08-20 18:58:52 +03:00
6
.github/workflows/deploy.yml
vendored
6
.github/workflows/deploy.yml
vendored
@@ -25,17 +25,15 @@ jobs:
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install Python dependencies
|
||||
run: pip install pyyaml requests geonamescache
|
||||
run: pip install pyyaml
|
||||
- name: Generate leaderboard
|
||||
run: python generate_leaderboard.py
|
||||
- name: Build profiles and locations
|
||||
run: python build_locations.py
|
||||
- name: Commit leaderboard data
|
||||
run: |
|
||||
if [[ `git status --porcelain` ]]; then
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git add _data/leaderboard.json assets/data/contributors.json assets/data/locations.json
|
||||
git add _data/leaderboard.json assets/data/contributors.json
|
||||
git commit -m "Update leaderboard [skip ci]"
|
||||
git push
|
||||
fi
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,4 +3,3 @@ vendor
|
||||
_site
|
||||
.jekyll-cache
|
||||
.claude
|
||||
assets/data/profiles.json
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
* **🚀 One-Click Deployment:** Deploy reviewer agents automatically on your PRs using **[Baz](https://baz.co)**. The site includes a **“🚀 Deploy to baz”** button that lets you spin up the selected reviewer as an AI code review bot in a single click. Baz is a platform for agentic code reviews, and with this integration it can apply the Awesome Reviewers prompts to your PRs without any manual copy-paste. In other words, you get automated code review comments on your GitHub PRs based on the same proven guidelines (with no configuration needed beyond the click). *This is a nod to how Awesome Reviewers works hand-in-hand with Baz to supercharge developer workflows.*
|
||||
|
||||
* **🏆 Community Leaderboard:** Check out the new **Leaderboard** page to see the top contributors across all reviewer categories. Contributor stats and GitHub profile metadata are automatically regenerated by a GitHub Action on every deployment. The metadata is cached during the build (not committed) so the leaderboard stays fast without exceeding API limits.
|
||||
* **🏆 Community Leaderboard:** Check out the new **Leaderboard** page to see the top contributors across all reviewer categories. Contributor stats are automatically regenerated by a GitHub Action on every deployment.
|
||||
|
||||
## Getting Started
|
||||
|
||||
|
||||
@@ -30,11 +30,6 @@
|
||||
<link rel="stylesheet" href="{{ '/assets/prism/prism.css' | relative_url }}">
|
||||
<link rel="manifest" href="{{ '/manifest.json' | relative_url }}">
|
||||
<link rel="canonical" href="{{ site.url }}{{ page.url }}">
|
||||
{% if page.extra_css %}
|
||||
{% for css in page.extra_css %}
|
||||
<link rel="stylesheet" href="{{ css }}">
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-2S2LLCMG7N"></script>
|
||||
@@ -976,10 +971,5 @@
|
||||
<script src="{{ '/assets/prism/prism.js' | relative_url }}"></script>
|
||||
<script src="{{ '/assets/js/prism-init.js' | relative_url }}"></script>
|
||||
<script src="{{ '/assets/js/main.js' | relative_url }}"></script>
|
||||
{% if page.extra_js %}
|
||||
{% for js in page.extra_js %}
|
||||
<script src="{{ js }}"></script>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1939,29 +1939,3 @@ h2 .stat-value {
|
||||
color: var(--accent);
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
|
||||
/* Leaderboard map */
|
||||
.leaderboard-map {
|
||||
height: 500px;
|
||||
margin-top: 1rem;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
/* Leaderboard tabs */
|
||||
.lb-tabs {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.lb-tabs button {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0.5rem 1rem;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
color: var(--text-light);
|
||||
}
|
||||
.lb-tabs button.active {
|
||||
color: var(--text);
|
||||
border-bottom: 2px solid var(--accent);
|
||||
}
|
||||
.lb-pane { display: none; }
|
||||
.lb-pane.active { display: block; }
|
||||
|
||||
@@ -1,169 +0,0 @@
|
||||
{
|
||||
"kimwnasptd": {
|
||||
"location": "Athens",
|
||||
"lat": 37.98376,
|
||||
"lon": 23.72784
|
||||
},
|
||||
"Darksonn": {
|
||||
"location": "Denmark",
|
||||
"lat": 55.67594,
|
||||
"lon": 12.56553
|
||||
},
|
||||
"louwers": {
|
||||
"location": "Germany, Europe",
|
||||
"lat": 52.52437,
|
||||
"lon": 13.41053
|
||||
},
|
||||
"kamilmysliwiec": {
|
||||
"location": "Poland",
|
||||
"lat": 52.22977,
|
||||
"lon": 21.01178
|
||||
},
|
||||
"jasnell": {
|
||||
"location": "Clovis, California",
|
||||
"lat": 36.82523,
|
||||
"lon": -119.70292
|
||||
},
|
||||
"sapphi-red": {
|
||||
"location": "Japan",
|
||||
"lat": 35.6895,
|
||||
"lon": 139.69171
|
||||
},
|
||||
"tannergooding": {
|
||||
"location": "Lake Stevens, WA",
|
||||
"lat": 48.0151,
|
||||
"lon": -122.06374
|
||||
},
|
||||
"daniel-lxs": {
|
||||
"location": "Colombia",
|
||||
"lat": 20.98812,
|
||||
"lon": -77.42598
|
||||
},
|
||||
"jfagoagas": {
|
||||
"location": "Madrid, Spain",
|
||||
"lat": 40.4165,
|
||||
"lon": -3.70256
|
||||
},
|
||||
"Viicos": {
|
||||
"location": "Amsterdam",
|
||||
"lat": 52.37403,
|
||||
"lon": 4.88969
|
||||
},
|
||||
"wilkinsona": {
|
||||
"location": "UK"
|
||||
},
|
||||
"MikeMcQuaid": {
|
||||
"location": "Edinburgh, Scotland",
|
||||
"lat": 55.95206,
|
||||
"lon": -3.19648
|
||||
},
|
||||
"sydney-runkle": {
|
||||
"location": "Somerville, MA",
|
||||
"lat": 42.3876,
|
||||
"lon": -71.0995
|
||||
},
|
||||
"agaudreault": {
|
||||
"location": "Saguenay",
|
||||
"lat": 48.41675,
|
||||
"lon": -71.06573
|
||||
},
|
||||
"chris-olszewski": {
|
||||
"location": "Cleveland",
|
||||
"lat": 41.4995,
|
||||
"lon": -81.69541
|
||||
},
|
||||
"ololobus": {
|
||||
"location": "Berlin, Germany",
|
||||
"lat": 52.52437,
|
||||
"lon": 13.41053
|
||||
},
|
||||
"treo": {
|
||||
"location": "Mainz, Germany",
|
||||
"lat": 49.98419,
|
||||
"lon": 8.2791
|
||||
},
|
||||
"jmcdo29": {
|
||||
"location": "Concrete, WA"
|
||||
},
|
||||
"micalevisk": {
|
||||
"location": "Manaus, Amazonas (Brazil)",
|
||||
"lat": -3.10194,
|
||||
"lon": -60.025
|
||||
},
|
||||
"VladLazar": {
|
||||
"location": "London, United Kingdom",
|
||||
"lat": 51.50853,
|
||||
"lon": -0.12574
|
||||
},
|
||||
"AlexDBlack": {
|
||||
"location": "Melbourne, Australia",
|
||||
"lat": -37.814,
|
||||
"lon": 144.96332
|
||||
},
|
||||
"ritchie46": {
|
||||
"location": "Utrecht",
|
||||
"lat": 52.09083,
|
||||
"lon": 5.12222
|
||||
},
|
||||
"yottta": {
|
||||
"location": "Brasov, Romania",
|
||||
"lat": 44.43225,
|
||||
"lon": 26.10626
|
||||
},
|
||||
"hiltontj": {
|
||||
"location": "Canada",
|
||||
"lat": 45.41117,
|
||||
"lon": -75.69812
|
||||
},
|
||||
"SomeoneToIgnore": {
|
||||
"location": "Turku, Finland",
|
||||
"lat": 60.45148,
|
||||
"lon": 22.26869
|
||||
},
|
||||
"ZeeshanTamboli": {
|
||||
"location": "Pune, Maharashtra",
|
||||
"lat": 18.51957,
|
||||
"lon": 73.85535
|
||||
},
|
||||
"jacoblee93": {
|
||||
"location": "San Francisco",
|
||||
"lat": 37.77493,
|
||||
"lon": -122.41942
|
||||
},
|
||||
"thaJeztah": {
|
||||
"location": "Netherlands",
|
||||
"lat": 52.37403,
|
||||
"lon": 4.88969
|
||||
},
|
||||
"ndeloof": {
|
||||
"location": "Rennes, France",
|
||||
"lat": 48.11198,
|
||||
"lon": -1.67429
|
||||
},
|
||||
"konstin": {
|
||||
"location": "Munich",
|
||||
"lat": 48.13743,
|
||||
"lon": 11.57549
|
||||
},
|
||||
"apparentlymart": {
|
||||
"location": "Portland, OR",
|
||||
"lat": 45.52345,
|
||||
"lon": -122.67621
|
||||
},
|
||||
"n1t0": {
|
||||
"location": "New York"
|
||||
},
|
||||
"ste93cry": {
|
||||
"location": "Padova, Italy",
|
||||
"lat": 45.40797,
|
||||
"lon": 11.88586
|
||||
},
|
||||
"sbrannen": {
|
||||
"location": "Zurich, Switzerland",
|
||||
"lat": 46.94809,
|
||||
"lon": 7.44744
|
||||
},
|
||||
"pauldix": {
|
||||
"location": "New York, NY"
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
import requests
|
||||
import geonamescache
|
||||
|
||||
gc = geonamescache.GeonamesCache()
|
||||
|
||||
DATA_DIR = Path('_data')
|
||||
ASSETS_DIR = Path('assets/data')
|
||||
|
||||
LEADERBOARD_PATH = DATA_DIR / 'leaderboard.json'
|
||||
LOCATIONS_OUT = ASSETS_DIR / 'locations.json'
|
||||
PROFILES_OUT = ASSETS_DIR / 'profiles.json'
|
||||
|
||||
API_URL = 'https://api.github.com/users/'
|
||||
HEADERS = {'Accept': 'application/vnd.github+json'}
|
||||
|
||||
|
||||
def best_city(name):
|
||||
results = gc.get_cities_by_name(name)
|
||||
if not results:
|
||||
return None
|
||||
result = max(results, key=lambda d: list(d.values())[0]['population'])
|
||||
city = list(result.values())[0]
|
||||
return city['latitude'], city['longitude']
|
||||
|
||||
|
||||
def geocode(location):
|
||||
if not location:
|
||||
return None
|
||||
# split by comma or slash
|
||||
parts = [p.strip() for p in re.split('[,;/]', location)]
|
||||
for part in parts:
|
||||
if not part:
|
||||
continue
|
||||
coords = best_city(part)
|
||||
if coords:
|
||||
return coords
|
||||
coords = best_country(part)
|
||||
if coords:
|
||||
return coords
|
||||
return None
|
||||
|
||||
def best_country(name):
|
||||
match = gc.get_countries_by_names().get(name)
|
||||
if match:
|
||||
code = match['iso']
|
||||
info = gc.get_countries()[code]
|
||||
capital = info['capital']
|
||||
cities = gc.get_cities_by_name(capital)
|
||||
if cities:
|
||||
city = max(cities, key=lambda d: list(d.values())[0]['population'])
|
||||
data = list(city.values())[0]
|
||||
return data['latitude'], data['longitude']
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
if not LEADERBOARD_PATH.exists():
|
||||
print('leaderboard not found')
|
||||
return
|
||||
users = [u['name'] for u in json.load(open(LEADERBOARD_PATH))]
|
||||
locations = {}
|
||||
profiles = {}
|
||||
for user in users:
|
||||
print('Fetching', user)
|
||||
r = requests.get(API_URL + user, headers=HEADERS)
|
||||
if r.status_code != 200:
|
||||
print(' failed', r.status_code)
|
||||
continue
|
||||
info = r.json()
|
||||
loc = info.get('location')
|
||||
coords = geocode(loc)
|
||||
if coords:
|
||||
lat, lon = coords
|
||||
locations[user] = {'location': loc, 'lat': lat, 'lon': lon}
|
||||
elif loc:
|
||||
locations[user] = {'location': loc}
|
||||
profile = {
|
||||
'location': info.get('location'),
|
||||
'company': info.get('company'),
|
||||
'blog': info.get('blog'),
|
||||
'twitter_username': info.get('twitter_username'),
|
||||
'email': info.get('email'),
|
||||
'site_admin': info.get('site_admin'),
|
||||
'followers': info.get('followers'),
|
||||
'following': info.get('following'),
|
||||
}
|
||||
profiles[user] = {k: v for k, v in profile.items() if v is not None}
|
||||
ASSETS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
with open(LOCATIONS_OUT, 'w', encoding='utf-8') as f:
|
||||
json.dump(locations, f, indent=2)
|
||||
print('Wrote', len(locations), 'locations to', LOCATIONS_OUT)
|
||||
with open(PROFILES_OUT, 'w', encoding='utf-8') as f:
|
||||
json.dump(profiles, f, indent=2)
|
||||
print('Wrote', len(profiles), 'profiles to', PROFILES_OUT)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
130
leaderboard.html
130
leaderboard.html
@@ -2,10 +2,6 @@
|
||||
layout: default
|
||||
title: Leaderboard
|
||||
description: Top contributors powering Awesome Reviewers
|
||||
extra_css:
|
||||
- https://unpkg.com/leaflet@1.9.4/dist/leaflet.css
|
||||
extra_js:
|
||||
- https://unpkg.com/leaflet@1.9.4/dist/leaflet.js
|
||||
---
|
||||
|
||||
<section class="hero leaderboard-hero">
|
||||
@@ -29,11 +25,6 @@ extra_js:
|
||||
|
||||
<section class="leaderboard">
|
||||
<div class="container">
|
||||
<div class="lb-tabs">
|
||||
<button class="lb-tab active" data-target="table">Leaderboard</button>
|
||||
<button class="lb-tab" id="map-tab" data-target="map" style="display:none">Map</button>
|
||||
</div>
|
||||
<div id="lb-table-pane" class="lb-pane active">
|
||||
<table id="leaderboard-table" class="leaderboard-table">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -66,10 +57,6 @@ extra_js:
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div id="lb-map-pane" class="lb-pane">
|
||||
<div id="leader-map" class="leaderboard-map"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -84,25 +71,6 @@ extra_js:
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let map;
|
||||
const markers = {};
|
||||
function initMap() {
|
||||
map = L.map('leader-map').setView([20, 0], 2);
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap contributors'
|
||||
}).addTo(map);
|
||||
}
|
||||
|
||||
function addMarker(user, location) {
|
||||
if (markers[user] || !map) return;
|
||||
const info = locationData[user];
|
||||
if (info && info.lat && info.lon) {
|
||||
const m = L.marker([info.lat, info.lon]).addTo(map);
|
||||
m.bindPopup(`<a href="https://github.com/${user}" target="_blank">${user}</a><br>${location}`);
|
||||
markers[user] = m;
|
||||
}
|
||||
}
|
||||
|
||||
function sortTable(col) {
|
||||
const table = document.getElementById('leaderboard-table');
|
||||
const tbody = table.tBodies[0];
|
||||
@@ -127,38 +95,19 @@ document.querySelectorAll('#leaderboard-table th').forEach((th, i) => {
|
||||
});
|
||||
|
||||
let contributors = {};
|
||||
let locationData = {};
|
||||
let profileData = {};
|
||||
Promise.all([
|
||||
fetch('/assets/data/contributors.json').then(r => r.json()),
|
||||
fetch('/assets/data/locations.json').then(r => r.json()),
|
||||
fetch('/assets/data/profiles.json').then(r => r.json()).catch(() => ({}))
|
||||
]).then(([cData, lData, pData]) => {
|
||||
contributors = cData;
|
||||
locationData = lData;
|
||||
profileData = pData;
|
||||
const locUsers = Object.keys(lData);
|
||||
if (locUsers.length) {
|
||||
initMap();
|
||||
locUsers.forEach(u => {
|
||||
const info = lData[u];
|
||||
if (info.lat && info.lon) {
|
||||
const m = L.marker([info.lat, info.lon]).addTo(map);
|
||||
m.bindPopup(`<a href="https://github.com/${u}" target="_blank">${u}</a><br>${info.location}`);
|
||||
markers[u] = m;
|
||||
}
|
||||
});
|
||||
document.getElementById('map-tab').style.display = '';
|
||||
}
|
||||
const hash = location.hash.slice(1);
|
||||
if (hash && contributors[hash]) {
|
||||
openContributorDrawer(hash);
|
||||
}
|
||||
loadProfileData();
|
||||
setupTabs();
|
||||
}).catch(err => {
|
||||
console.error('Failed to load datasets', err);
|
||||
});
|
||||
fetch('/assets/data/contributors.json')
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
contributors = data;
|
||||
const hash = location.hash.slice(1);
|
||||
if (hash && contributors[hash]) {
|
||||
openContributorDrawer(hash);
|
||||
}
|
||||
loadProfileData();
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Failed to load contributors dataset', err);
|
||||
});
|
||||
|
||||
attachContributorEvents();
|
||||
|
||||
@@ -172,30 +121,13 @@ function attachContributorEvents() {
|
||||
});
|
||||
}
|
||||
|
||||
function setupTabs() {
|
||||
document.querySelectorAll('.lb-tab').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
document.querySelectorAll('.lb-tab').forEach(b => b.classList.remove('active'));
|
||||
document.querySelectorAll('.lb-pane').forEach(p => p.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
const pane = document.getElementById(`lb-${btn.dataset.target}-pane`);
|
||||
pane.classList.add('active');
|
||||
if (btn.dataset.target === 'map' && map) {
|
||||
setTimeout(() => map.invalidateSize(), 0);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function loadProfileData() {
|
||||
const rows = document.querySelectorAll('tr[data-contributor]');
|
||||
rows.forEach((row, index) => {
|
||||
const user = row.dataset.contributor;
|
||||
const cached = profileData[user];
|
||||
if (cached) {
|
||||
if (!contributors[user]) contributors[user] = {};
|
||||
contributors[user].profile = cached;
|
||||
applyProfile(row, cached);
|
||||
const stored = contributors[user] && contributors[user].profile;
|
||||
if (stored) {
|
||||
applyProfile(row, stored);
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
@@ -208,18 +140,8 @@ function loadProfileData() {
|
||||
.then(r => (r.ok ? r.json() : null))
|
||||
.then(data => {
|
||||
if (!data) return;
|
||||
profileData[user] = {
|
||||
location: data.location,
|
||||
company: data.company,
|
||||
blog: data.blog,
|
||||
twitter_username: data.twitter_username,
|
||||
email: data.email,
|
||||
site_admin: data.site_admin,
|
||||
followers: data.followers,
|
||||
following: data.following
|
||||
};
|
||||
if (contributors[user]) contributors[user].profile = profileData[user];
|
||||
applyProfile(row, profileData[user]);
|
||||
if (contributors[user]) contributors[user].profile = data;
|
||||
applyProfile(row, data);
|
||||
})
|
||||
.catch(() => {});
|
||||
}, index * 100);
|
||||
@@ -233,7 +155,6 @@ function applyProfile(row, data) {
|
||||
if (data.location && locCell) {
|
||||
locCell.dataset.sort = data.location;
|
||||
locCell.innerHTML = `<svg class="meta-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg> ${data.location}`;
|
||||
addMarker(row.dataset.contributor, data.location);
|
||||
}
|
||||
if (data.company && compCell) {
|
||||
compCell.dataset.sort = data.company;
|
||||
@@ -297,8 +218,7 @@ function openContributorDrawer(user) {
|
||||
header.appendChild(infoBox);
|
||||
content.appendChild(header);
|
||||
|
||||
const existing = info.profile || profileData[user];
|
||||
if (existing) info.profile = existing;
|
||||
const existing = info.profile;
|
||||
const load = existing ? Promise.resolve(existing) : fetch(`https://api.github.com/users/${user}`, {
|
||||
headers: {
|
||||
'Accept': 'application/vnd.github+json',
|
||||
@@ -307,17 +227,7 @@ function openContributorDrawer(user) {
|
||||
}).then(r => r.ok ? r.json() : null);
|
||||
load.then(async data => {
|
||||
if (!data) return;
|
||||
profileData[user] = {
|
||||
location: data.location,
|
||||
company: data.company,
|
||||
blog: data.blog,
|
||||
twitter_username: data.twitter_username,
|
||||
email: data.email,
|
||||
site_admin: data.site_admin,
|
||||
followers: data.followers,
|
||||
following: data.following
|
||||
};
|
||||
if (!existing) info.profile = profileData[user];
|
||||
if (!existing) info.profile = data;
|
||||
if (data.site_admin) {
|
||||
const badge = document.createElement('span');
|
||||
badge.className = 'verified-badge';
|
||||
|
||||
Reference in New Issue
Block a user