Add Open External Contributor PRs and Issues to PyTorch Org Project 136 #102
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Add Open External Contributor PRs and Issues to PyTorch Org Project 136 | |
| on: | |
| workflow_dispatch: | |
| schedule: | |
| # GitHub Actions cron uses UTC. These run at: | |
| # - 14:00 UTC -> 08:00 CST (UTC-6) | |
| # - 19:00 UTC -> 13:00 CST (UTC-6) | |
| - cron: "0 14 * * *" | |
| - cron: "0 19 * * *" | |
| pull_request: | |
| paths: | |
| - .github/workflows/add-unanswered-to-project.yml | |
| jobs: | |
| add_to_project: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Add open issues and open, non-draft PRs to org project (excluding certain authors and bots) | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.ET_EXT_CONTRIB }} | |
| script: | | |
| const projectId = "PVT_kwDOAUB9vs4A_PUL"; // PyTorch org project 136 | |
| const owner = 'pytorch'; | |
| const repo = 'executorch'; | |
| // List of authors to exclude | |
| const excludedAuthors = new Set([ | |
| "nil-is-all", "tanvirislam-meta", "cbilgin", "kimishpatel", "psiddh", "digantdesai", "SS-JIA", "ahmtox", "mcr229", | |
| "shoumikhin", "manuelcandales", "metascroy", "cccclai", "rohansjoshi", "kirklandsign", "abhinaykukkadapu", | |
| "JacobSzwejbka", "Conarnar", "rascani", "lucylq", "larryliu0820", "BujSet", "Gasoonjia", "Juntian777", "guangy10", | |
| "jackzhxng", "GregoryComer", "leafs1", "swolchok", "mergennachin", "tarun292", "byjlw", "jathu", "Jack-Khuu", "georgehong", | |
| "zhenyan-zhang-meta", "silverguo", "harishs88ss", "AlannaBurke", "dbort", "huydhn", "mcremon-meta", "trivedivivek", | |
| "angelayi", "helunwencser", "hsharma35", "zhxchen17", "iseeyuan", "svekars", "nathanaelsee", "dulinriley", | |
| "jerryzh168", "cmodi-meta", "bigfootjon", "sxu", "ydwu4", "Riandy", "tugsbayasgalan", "bsoyluoglu", "yangw-dev", | |
| "YIWENX14", "namanahuja", "yushangdi", "limintang", "pianpwk", "viveknayakatmeta", "andreanicastro", "JakeStevens", | |
| "gmagogsfm", "zonglinpeng", "eigen-k", "derekxu", "salilsdesai", "skrtskrtfb", "pssrawat", "r-barnes", | |
| "kalpit-meta-1", "Will-MingLun-Li", "KapJI", "piyengar", "j-bahr", "BoyuanFeng", "fgasperij", "DariusHolmgren", | |
| "sammarden-meta", "kushrast", "meta-emilian", "Rittzz", "jeanschmidt", "copyrightly", "mikekgfb", "vmpuri", | |
| "zonglinpengmeta", "maggiemoss", "aorenste", "hoangminhle98", "Solumin", "meyering", "rchen152", "AishwaryaSivaraman", | |
| "migeed-z", "ebgraham", "Esteb37", "nausicaasnow", "Camyll", "ezyang", "huiyujie", "dltn", "cjhopman", "blackm00n", | |
| "agunapal", "SamGondelman", "Ninja91", "ivayloen", "DrJessop", "rodrigos01meta", "akrieger", "cmt0", "yiming0416", | |
| "ethansfng", "ThomasJannaud", "nirvanagth", "marcinkwiatkowski", "3l1", "omerjerk", "nitish2112", "yipjustin", | |
| "ejnguyen", "andrewor14", "phaiting", "mgiordy", "LeeOHzzZ", "adicatana", "Polyomino", "ezrilow", "navsud", | |
| "michaelmaitland", "RahulC7", "seyeong-han", "thdusdl1219", "jaejunku", "felixweilbach", "apullin", "trviv", "junluan01", | |
| "YifanShenSZ", "RdoubleA", "Olivia-liu", "Abhi-hpp", "Vysarat", "azad-meta", "junpi", "pytorchbot", "pytorchmergebot", | |
| "pytorchupdatebot", "facebook-github-bot", "app/dependabot", "Erik-Lundell", "zingo", "AdrianLundell", "oscarandersson8218", | |
| "per", "Sebastian-Larsson", "SaoirseARM", "robell", "mansnils", "martinlsm", "freddan80", "YufengShi-dudu", "tom-arm", | |
| "perheld", "Jerry-Ge", "gggekov", "fumchin", "wwwind", "benkli01", "Tessil", "maddun01", "Michiel-Olieslagers", "armwaheed", | |
| "agrima1304", "emmakujala", "annietllnd", "MatthiasHertel80", "AlexTawseArm", "jmahbs", "morgolock", "Christoffer-JL", | |
| "ArmRyan", "xingguo01", "tgonzalezorlandoarm", "chizkiyahu", "sarah-blades", "haowhsu-quic", "shewu-quic", "winskuo-quic", | |
| "chunit-quic", "DannyYuyang-quic", "chuntl", "thchenqti", "jethroqti", "chenweng-quic", "cymbalrush", "DenisVieriu97", | |
| "billmguo", "StrycekSimon", "jirioc", "robert-kalmar", "skywall", "MartinPavella", "roman-janik-nxp", "novak-vaclav", | |
| "neuropilot-captain", "dijopaul", "cad-rlc", "cad-audio", "ynimmaga", "daniil-lyakhov", "emmanuel-ferdman", "cavusmustafa", | |
| "anzr299", "Jiseong-oh", "alexdean08", | |
| // explicitly include the dependabot bot login seen in PRs | |
| "dependabot[bot]" | |
| ]); | |
| // List of organization logins (lowercased) to exclude members of | |
| const excludedOrgs = new Set([ | |
| "meta", "facebook", "pytorch", "arm", "apple", "qualcomm", "nxp", "mediatek", "cadence", "intel", "samsung" | |
| ]); | |
| // Labels on PRs to exclude from being added to the project | |
| const excludedPrLabels = new Set(["fb-exported", "meta-exported"]); | |
| // Simple cache for user -> boolean (member of excluded org) | |
| const orgsCache = new Map(); | |
| async function isMemberOfExcludedOrg(user) { | |
| if (!user || !user.login) return false; | |
| const login = user.login; | |
| if (orgsCache.has(login)) return orgsCache.get(login); | |
| try { | |
| const response = await github.rest.orgs.listForUser({ username: login }); | |
| const orgs = response && response.data ? response.data : []; | |
| const isMember = orgs.some(o => o && o.login && excludedOrgs.has(o.login.toLowerCase())); | |
| orgsCache.set(login, isMember); | |
| return isMember; | |
| } catch (error) { | |
| // If checking orgs fails (rate limit, permissions etc.), assume not a member to avoid false positives. | |
| console.log(`Error checking orgs for ${login}: ${error.message}`); | |
| orgsCache.set(login, false); | |
| return false; | |
| } | |
| } | |
| function isBotOrExcluded(user) { | |
| if (!user) return false; | |
| // GitHub sometimes marks bots with user.type === "Bot" | |
| if (user.type && user.type.toLowerCase() === "bot") return true; | |
| // Some bots use logins that end with [bot], e.g. dependabot[bot] | |
| if (user.login && user.login.endsWith("[bot]")) return true; | |
| // Explicit excluded list | |
| if (excludedAuthors.has(user.login)) return true; | |
| return false; | |
| } | |
| function hasExcludedLabel(item) { | |
| if (!item || !item.labels) return false; | |
| return item.labels.some(l => l && l.name && excludedPrLabels.has(l.name.toLowerCase())); | |
| } | |
| async function addItem(contentId, type, number) { | |
| try { | |
| await github.graphql(` | |
| mutation { | |
| addProjectV2ItemById(input: {projectId: "${projectId}", contentId: "${contentId}"}) { | |
| item { id } | |
| } | |
| } | |
| `); | |
| console.log(`Added ${type} #${number} to project`); | |
| } catch (error) { | |
| if (error.message && error.message.includes("A project item already exists for this content")) { | |
| // Ignore if already exists | |
| console.log(`${type} #${number} already in project`); | |
| } else { | |
| console.log(`Error adding ${type} #${number}: ${error.message}`); | |
| } | |
| } | |
| } | |
| try { | |
| // Add open issues (not PRs) and exclude by author/bots | |
| const issues = await github.paginate( | |
| github.rest.issues.listForRepo, | |
| { | |
| owner, | |
| repo, | |
| state: 'open', | |
| filter: 'all' | |
| } | |
| ); | |
| for (const issue of issues) { | |
| if (!issue.pull_request && !isBotOrExcluded(issue.user)) { | |
| await addItem(issue.node_id, 'issue', issue.number); | |
| } else { | |
| console.log(`Skipping issue #${issue.number} by ${issue.user && issue.user.login}`); | |
| } | |
| } | |
| // Add open, non-draft PRs (regardless of review state), exclude by author/bots | |
| const prs = await github.paginate( | |
| github.rest.pulls.list, | |
| { | |
| owner, | |
| repo, | |
| state: 'open', | |
| } | |
| ); | |
| for (const pr of prs) { | |
| if (!pr.draft && !isBotOrExcluded(pr.user)) { | |
| await addItem(pr.node_id, 'pr', pr.number); | |
| } else { | |
| console.log(`Skipping PR #${pr.number} by ${pr.user && pr.user.login}`); | |
| } | |
| } | |
| } catch (error) { | |
| core.setFailed(`Workflow failed: ${error.message}`); | |
| } |