-
Notifications
You must be signed in to change notification settings - Fork 602
feat: add community activity score badges and filters #2008
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
57e855c
b117d67
c90740c
5661fcd
b71c066
3b2fea3
d6f2053
592becc
ea9d77a
b9ac12e
a5bf07b
a98546b
857d105
8303254
86260a1
a723c90
3ff83c1
4064ccc
3dc8d60
2987bf4
37324f4
d1a993c
0ad46eb
de55ca3
385afba
e3a2c71
96103e0
0033b18
7b7692e
f125f66
dc116d5
cb6063f
ff1e033
bbd3ae4
dba796a
3c45e75
b59eb52
364ffb5
059bcdb
c169b41
fabaa23
ce054a4
c6e96c0
bc13aa7
a567f98
dbc8582
01a6ce7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| name: Compute Community Activity Scores | ||
| on: | ||
| schedule: | ||
| - cron: '0 0 * * *' # Daily at midnight | ||
| workflow_dispatch: # Manual trigger | ||
| jobs: | ||
| compute-scores: | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: write | ||
| pull-requests: write | ||
| steps: | ||
| - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | ||
| - name: Setup Node.js | ||
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.2.0 | ||
| with: | ||
| node-version: '18' | ||
| - name: Run activity score computation | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| run: node agent/scripts/compute-activity-scores.js | ||
| - name: Commit updated scores | ||
| uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b2b3e36ac28eb95 # v7.0.8 | ||
| with: | ||
| token: ${{ secrets.GITHUB_TOKEN }} | ||
| commit-message: "chore: update community activity scores" | ||
| title: "chore: update community activity scores" | ||
| body: "Automated daily update of community activity scores." | ||
| branch: "auto/update-community-activity-scores" | ||
| delete-branch: true | ||
| add-paths: data/community_activity.json |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,159 @@ | ||
| // @ts-check | ||
| /* eslint-env node */ | ||
| // Computes community activity scores for all GSoC orgs | ||
| // Run via GitHub Actions daily | ||
|
|
||
| const fs = require('node:fs'); | ||
| const path = require('node:path'); | ||
| const GITHUB_TOKEN = process.env.GITHUB_TOKEN; | ||
| const OUTPUT_FILE = path.join(__dirname, '../../data/community_activity.json'); | ||
|
|
||
| if (!GITHUB_TOKEN) { | ||
| console.error('β GITHUB_TOKEN is required'); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| const headers = { | ||
| 'Authorization': `token ${GITHUB_TOKEN}`, | ||
| 'Accept': 'application/vnd.github.v3+json', | ||
| 'User-Agent': 'gsoc-org-finder' | ||
| }; | ||
|
|
||
| // Load orgs from org.js | ||
| const orgDataRaw = fs.readFileSync( | ||
| path.join(__dirname, '../../src/js/org.js'), 'utf8' | ||
| ); | ||
|
|
||
| // Extract github field from each org | ||
| const githubRepos = []; | ||
| const orgMatches = orgDataRaw.matchAll(/name:\s*"([^"]+)"[^}]+github:\s*"([^"]+)"/g); | ||
| for (const m of orgMatches) { | ||
| const name = m[1]; | ||
| const repo = m[2]; | ||
| if (repo && repo.includes('/')) { | ||
|
Check warning on line 33 in agent/scripts/compute-activity-scores.js
|
||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| githubRepos.push({ name, repo }); | ||
| } else { | ||
| console.warn(`Skipping bare owner (no repo path): ${name} β "${repo}"`); | ||
| } | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| async function fetchJSON(url) { | ||
| const res = await fetch(url, { headers }); | ||
| if (!res.ok) return null; | ||
|
cubic-dev-ai[bot] marked this conversation as resolved.
|
||
| return res.json(); | ||
| } | ||
|
|
||
| function computeScore({ issueResponseDays, commitFrequency, prMergeRate, ideasFreshnessDays, starsGrowth }) { | ||
| const issueScore = Math.max(0, 100 - (issueResponseDays * 5)); | ||
| const commitScore = Math.min(100, commitFrequency * 10); | ||
| const prScore = Math.min(100, prMergeRate * 100); | ||
| const ideasScore = Math.max(0, 100 - (ideasFreshnessDays * 0.5)); | ||
| const growthScore = Math.min(100, starsGrowth * 2); | ||
|
|
||
| return Math.round( | ||
| issueScore * 0.3 + | ||
| commitScore * 0.2 + | ||
| prScore * 0.15 + | ||
| ideasScore * 0.2 + | ||
| growthScore * 0.15 | ||
| ); | ||
| } | ||
|
|
||
| function getTier(score) { | ||
| if (score >= 80) return { tier: 'very-active', label: 'π’ Very Active' }; | ||
| if (score >= 60) return { tier: 'active', label: 'π΅ Active' }; | ||
| if (score >= 40) return { tier: 'moderate', label: 'π‘ Moderate' }; | ||
| return { tier: 'low', label: 'π΄ Low Activity' }; | ||
| } | ||
|
|
||
| async function analyzeOrg({ name, repo }) { | ||
| try { | ||
| const since = new Date(Date.now() - 90 * 86400000).toISOString(); | ||
|
|
||
| const commits = await fetchJSON( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: The GitHub API calls use Prompt for AI agents |
||
| `https://api.github.com/repos/${repo}/commits?since=${since}&per_page=100` | ||
| ); | ||
| const commitFrequency = Array.isArray(commits) ? commits.length / 90 : 0; | ||
|
Comment on lines
+73
to
+76
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ποΈ Data Integrity & Integration | π‘ Minor | β‘ Quick win π§© Analysis chainπ Script executed: #!/bin/bash
set -euo pipefail
# Inspect the target file and nearby helpers.
git ls-files agent/scripts/compute-activity-scores.js
wc -l agent/scripts/compute-activity-scores.js
sed -n '1,220p' agent/scripts/compute-activity-scores.js
# Search for any pagination/counting helpers used elsewhere.
rg -n "Link:|rel=\"next\"|per_page=100|fetchJSON|countItemsSince|commits\\?since" agent/scriptsRepository: S3DFX-CYBER/GSoC-Org-Finder- Length of output: 6102 Commit-frequency signal undercounts active repos This only reads the first 100 commits from the 90-day window, so repos with more activity get the same π€ Prompt for AI Agents |
||
|
|
||
| const issues = await fetchJSON( | ||
| `https://api.github.com/repos/${repo}/issues?state=closed&since=${since}&per_page=100` | ||
| ); | ||
| let issueResponseDays = null; | ||
| if (Array.isArray(issues) && issues.length > 0) { | ||
| const responseTimes = issues | ||
| .filter(i => i.created_at && i.closed_at && !i.pull_request) | ||
| .map(i => (+new Date(i.closed_at) - +new Date(i.created_at)) / 86400000); | ||
| if (responseTimes.length > 0) { | ||
| issueResponseDays = responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length; | ||
| } | ||
| } | ||
|
|
||
| // Fetch PRs | ||
| const prs = await fetchJSON( | ||
| `https://api.github.com/repos/${repo}/pulls?state=closed&per_page=100` | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: PR Merge Rate becomes a partial, creation-ordered sample for busy repositories, not the stated 90-day rate. Fetch all relevant pages or query/sort so every PR closed in the window contributes before calculating the ratio. Prompt for AI agents |
||
| ); | ||
| let prMergeRate = null; | ||
| if (Array.isArray(prs) && prs.length > 0) { | ||
| const recentPrs = prs.filter(p => p.closed_at && new Date(p.closed_at) >= new Date(since)); | ||
| if (recentPrs.length > 0) { | ||
| const merged = recentPrs.filter(p => p.merged_at).length; | ||
| prMergeRate = merged / recentPrs.length; | ||
| } | ||
| } | ||
|
|
||
| const repoData = await fetchJSON(`https://api.github.com/repos/${repo}`); | ||
| const starsGrowth = repoData ? Math.min(50, repoData.stargazers_count / 1000) : 0; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: The score rewards accumulated repository popularity, not star growth: an old, inactive project retains this score contribution even with zero new stars. Persist a prior star count/timestamp and calculate a period delta, or rename/remove this signal if total stars is intended. Prompt for AI agents |
||
|
|
||
| const ideasFreshnessDays = repoData?.pushed_at | ||
| ? (Date.now() - new Date(repoData.pushed_at)) / 86400000 | ||
| : 60; | ||
|
|
||
| if (issueResponseDays === null || prMergeRate === null) return null; | ||
|
|
||
| const score = computeScore({ | ||
| issueResponseDays: issueResponseDays, | ||
| commitFrequency, | ||
| prMergeRate: prMergeRate, | ||
| ideasFreshnessDays, | ||
| starsGrowth | ||
| }); | ||
| const { tier, label } = getTier(score); | ||
|
|
||
| return { | ||
| score, | ||
| tier, | ||
| label, | ||
| signals: { | ||
| issueResponseDays: issueResponseDays === null ? null : Math.round(issueResponseDays), | ||
| commitFrequency: Math.round(commitFrequency * 10) / 10, | ||
| prMergeRate: prMergeRate === null ? null : Math.round(prMergeRate * 100), | ||
| ideasFreshnessDays: Math.round(ideasFreshnessDays), | ||
| starsGrowth: Math.round(starsGrowth * 10) / 10 | ||
| }, | ||
| lastUpdated: new Date().toISOString() | ||
| }; | ||
| } catch (e) { | ||
| console.error(`Error analyzing ${name}:`, e.message); | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| async function main() { | ||
| console.log(`Analyzing ${githubRepos.length} orgs...`); | ||
| const results = {}; | ||
|
|
||
| for (const org of githubRepos) { | ||
| console.log(`Processing: ${org.name}`); | ||
| const data = await analyzeOrg(org); | ||
| if (data) results[org.name] = data; | ||
| await new Promise(r => setTimeout(r, 500)); | ||
| } | ||
|
|
||
| fs.writeFileSync(OUTPUT_FILE, JSON.stringify(results, null, 2)); | ||
| console.log(`β Done! Wrote ${Object.keys(results).length} orgs to community_activity.json`); | ||
| } | ||
|
|
||
| main().catch((err) => { | ||
| console.error(err); | ||
| process.exit(1); | ||
| }); | ||
Uh oh!
There was an error while loading. Please reload this page.