updated autoassign

This commit is contained in:
Prince Chaddha
2025-09-23 11:57:46 +08:00
parent ad2321a818
commit 6664fbe61c

View File

@@ -1,26 +1,156 @@
name: 🤖 issue/pr assignment
on:
# pull_request:
# types: [opened]
# branches:
# - main
issues:
types: [opened]
name: Auto-assign on open
env:
ASSIGN_TASK_TOKEN: ${{ secrets.PDTEAMX_PAT }} # github personal token
on:
issues:
types: [opened, reopened]
# Use pull_request_target so the job has permissions on PRs from forks.
pull_request_target:
types: [opened, ready_for_review, reopened]
permissions:
contents: read
issues: write
pull-requests: write
concurrency:
group: auto-assign-on-open
cancel-in-progress: false
jobs:
build:
permissions: write-all
assign:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5 # checkout the repository content
- uses: actions/setup-python@v6
- uses: actions/github-script@v7
with:
python-version: '3.10' # install the python version needed
- run: pip install requests
# - if: github.event_name == 'pull_request'
# run: python .github/scripts/assign_tasks.py ${{ github.event.pull_request.number }} pr ${{ secrets.GITHUB_TOKEN }}
- if: github.event_name == 'issues'
run: python .github/scripts/assign_tasks.py ${{ github.event.issue.number }} issue ${{ secrets.GITHUB_TOKEN }}
script: |
const ORG = context.repo.owner;
const REPO = context.repo.repo;
// ======= CONFIG: put your user pools here =======
// Usernames must be GitHub logins and collaborators on this repo.
const ISSUE_ASSIGNEES = [
"princechaddha","pussycat0x","ritikchaddha","DhiyaneshGeek"
];
const REVIEW_POOL = [
"pussycat0x","ritikchaddha","DhiyaneshGeek"
];
const LOOKBACK_DAYS = 30; // only used as a tie-break fairness metric
// ================================================
function toLowerAll(xs){ return xs.map(x => x.toLowerCase()); }
const issuePool = new Set(toLowerAll(ISSUE_ASSIGNEES));
const reviewPool = new Set(toLowerAll(REVIEW_POOL));
const sinceISO = new Date(Date.now() - LOOKBACK_DAYS*24*60*60*1000).toISOString();
// Simple "least recent then fewest in lookback" picker
function initMap(arr){ const m=new Map(); for(const a of arr) m.set(a,0); return m; }
const issueCounts = initMap(issuePool);
const prCounts = initMap(reviewPool);
const lastIssueAssign = new Map();
const lastPrAssign = new Map();
function pick(countsMap, lastMap, exclude=new Set()){
const cands = [...countsMap.keys()].filter(x => !exclude.has(x));
if (!cands.length) return null;
cands.sort((a,b)=>{
const ca=countsMap.get(a)||0, cb=countsMap.get(b)||0;
if (ca!==cb) return ca-cb;
const ta=lastMap.get(a)?.getTime()||0, tb=lastMap.get(b)?.getTime()||0;
if (ta!==tb) return ta-tb;
return a.localeCompare(b);
});
return cands[0];
}
async function buildIssueStats(){
for await (const page of github.paginate.iterator(
github.rest.issues.listForRepo,
{ owner: ORG, repo: REPO, state: 'all', since: sinceISO, per_page: 100 }
)){
for (const it of page.data){
if (it.pull_request) continue;
const ts = new Date(it.updated_at);
for (const a of (it.assignees||[])){
const l=a.login.toLowerCase();
if (issuePool.has(l)){
issueCounts.set(l,(issueCounts.get(l)||0)+1);
if (!lastIssueAssign.has(l)) lastIssueAssign.set(l, ts);
}
}
}
}
}
async function buildPrStats(){
for await (const page of github.paginate.iterator(
github.rest.pulls.list,
{ owner: ORG, repo: REPO, state: 'all', per_page: 100 }
)){
for (const pr of page.data){
const ts = new Date(pr.updated_at);
if (ts < new Date(sinceISO)) continue;
if (pr.assignee){
const l = pr.assignee.login.toLowerCase();
if (reviewPool.has(l)){
prCounts.set(l,(prCounts.get(l)||0)+1);
if (!lastPrAssign.has(l)) lastPrAssign.set(l, ts);
}
}
for (const r of (pr.requested_reviewers||[])){
const l=r.login.toLowerCase();
if (reviewPool.has(l)){
prCounts.set(l,(prCounts.get(l)||0)+1);
if (!lastPrAssign.has(l)) lastPrAssign.set(l, ts);
}
}
}
}
}
// Build stats once per run (cheap enough for small repos)
await Promise.all([buildIssueStats(), buildPrStats()]);
// Determine context
if (context.eventName === 'issues') {
const issue = context.payload.issue;
if (issue.pull_request) return; // guard
if ((issue.assignees||[]).length>0) return;
const pickee = pick(issueCounts, lastIssueAssign);
if (!pickee) return;
await github.rest.issues.addAssignees({
owner: ORG, repo: REPO, issue_number: issue.number, assignees: [pickee]
});
} else if (context.eventName === 'pull_request_target') {
const pr = context.payload.pull_request;
const prNum = pr.number;
const author = (pr.user?.login||"").toLowerCase();
const assignee = pr.assignee?.login?.toLowerCase();
// Ensure one assignee
let finalAssignee = assignee;
if (!finalAssignee) {
const a = pick(prCounts, lastPrAssign, new Set([author]));
if (a) {
await github.rest.issues.addAssignees({
owner: ORG, repo: REPO, issue_number: prNum, assignees: [a]
});
finalAssignee = a;
}
}
// One reviewer, not the author, not the assignee
const already = new Set((pr.requested_reviewers||[]).map(x=>x.login.toLowerCase()));
if (already.size === 0 && !pr.draft) {
const exclude = new Set([author, finalAssignee].filter(Boolean));
const reviewer = pick(prCounts, lastPrAssign, exclude);
if (reviewer) {
await github.rest.pulls.requestReviewers({
owner: ORG, repo: REPO, pull_number: prNum, reviewers: [reviewer]
});
}
}
}