From 512030574ad5a1c7ae17149677bf80cbcb3e95a7 Mon Sep 17 00:00:00 2001 From: John Lago <750845+Lagoja@users.noreply.github.com> Date: Wed, 2 Jul 2025 14:12:21 -0700 Subject: [PATCH 1/6] add random reviewer assignment --- .../workflows/random-reviewer-assignment.yml | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/workflows/random-reviewer-assignment.yml diff --git a/.github/workflows/random-reviewer-assignment.yml b/.github/workflows/random-reviewer-assignment.yml new file mode 100644 index 00000000000..1afb5fdcf01 --- /dev/null +++ b/.github/workflows/random-reviewer-assignment.yml @@ -0,0 +1,46 @@ +name: Random Reviewer Assignment +on: + pull_request: + types: [opened] + +jobs: + assign-reviewer: + runs-on: ubuntu-latest + steps: + - name: Randomly assign reviewer from team + uses: actions/github-script@v6 + with: + script: | + try { + // Get team members + const teamMembers = await github.rest.teams.listMembersInOrg({ + org: 'jetify-com', + team_slug: 'eng' + }); + + // Exclude PR author from potential reviewers + const prAuthor = context.payload.pull_request.user.login; + const eligibleReviewers = teamMembers.data + .map(member => member.login) + .filter(login => login !== prAuthor) + .filter(login => login !== "lagoja"); + + if (eligibleReviewers.length === 0) { + console.log('No eligible reviewers found'); + return; + } + + // Randomly select a reviewer + const randomReviewer = eligibleReviewers[Math.floor(Math.random() * eligibleReviewers.length)]; + console.log(`Assigning reviewer: ${randomReviewer}`); + + await github.rest.pulls.requestReviewers({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + reviewers: [randomReviewer, "lagoja"] + }); + + } catch (error) { + console.error('Error assigning reviewer:', error); + } From 72004d9874f1604a677caf677ccf023007e278bd Mon Sep 17 00:00:00 2001 From: John Lago <750845+Lagoja@users.noreply.github.com> Date: Wed, 2 Jul 2025 14:37:46 -0700 Subject: [PATCH 2/6] Add secret to env --- .github/workflows/random-reviewer-assignment.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/random-reviewer-assignment.yml b/.github/workflows/random-reviewer-assignment.yml index 1afb5fdcf01..543e0cc86cd 100644 --- a/.github/workflows/random-reviewer-assignment.yml +++ b/.github/workflows/random-reviewer-assignment.yml @@ -3,6 +3,9 @@ on: pull_request: types: [opened] +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + jobs: assign-reviewer: runs-on: ubuntu-latest From 304990561b8ad494980d687c761b8b567b6f4b04 Mon Sep 17 00:00:00 2001 From: John Lago <750845+Lagoja@users.noreply.github.com> Date: Wed, 2 Jul 2025 14:41:26 -0700 Subject: [PATCH 3/6] Potential fix for code scanning alert no. 4: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: John Lago <750845+Lagoja@users.noreply.github.com> --- .github/workflows/random-reviewer-assignment.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/random-reviewer-assignment.yml b/.github/workflows/random-reviewer-assignment.yml index 543e0cc86cd..303cff8983a 100644 --- a/.github/workflows/random-reviewer-assignment.yml +++ b/.github/workflows/random-reviewer-assignment.yml @@ -3,6 +3,10 @@ on: pull_request: types: [opened] +permissions: + contents: read + pull-requests: write + env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From dbc675b13ef94d0d7cd51bda737710d9dd46b84d Mon Sep 17 00:00:00 2001 From: John Lago <750845+Lagoja@users.noreply.github.com> Date: Wed, 2 Jul 2025 15:52:11 -0700 Subject: [PATCH 4/6] cleanup workflow --- .../workflows/random-reviewer-assignment.yml | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/.github/workflows/random-reviewer-assignment.yml b/.github/workflows/random-reviewer-assignment.yml index 303cff8983a..7b9bfb60666 100644 --- a/.github/workflows/random-reviewer-assignment.yml +++ b/.github/workflows/random-reviewer-assignment.yml @@ -8,7 +8,7 @@ permissions: pull-requests: write env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GH_TOKEN_FOR_PR_ASSIGNMENT }} jobs: assign-reviewer: @@ -18,34 +18,46 @@ jobs: uses: actions/github-script@v6 with: script: | + const TRIAGE_USERNAME = 'Lagoja'; + try { - // Get team members const teamMembers = await github.rest.teams.listMembersInOrg({ org: 'jetify-com', team_slug: 'eng' }); - // Exclude PR author from potential reviewers - const prAuthor = context.payload.pull_request.user.login; + const prAuthor = context.payload.pull_request.user.login.toLowerCase(); + console.log(`PR Author: ${prAuthor}`); + + // Get eligible reviewers (excluding PR author and lagoja) + const eligibleReviewers = teamMembers.data .map(member => member.login) - .filter(login => login !== prAuthor) - .filter(login => login !== "lagoja"); + .filter(login => { + const loginLower = login.toLowerCase(); + return loginLower !== prAuthor && loginLower !== TRIAGE_USERNAME; + }); if (eligibleReviewers.length === 0) { console.log('No eligible reviewers found'); return; } - // Randomly select a reviewer + // Build reviewers list: random reviewer + lagoja (if not PR author) const randomReviewer = eligibleReviewers[Math.floor(Math.random() * eligibleReviewers.length)]; - console.log(`Assigning reviewer: ${randomReviewer}`); + const reviewers = [randomReviewer]; + + if (prAuthor !== TRIAGE_USERNAME) { + reviewers.push(TRIAGE_USERNAME); + } + + console.log(`Assigning reviewers: ${reviewers.join(', ')}`); await github.rest.pulls.requestReviewers({ owner: context.repo.owner, repo: context.repo.repo, pull_number: context.payload.pull_request.number, - reviewers: [randomReviewer, "lagoja"] + reviewers }); } catch (error) { From 06a6d7f8e61865da75e02e4a792c95db96aed757 Mon Sep 17 00:00:00 2001 From: John Lago <750845+Lagoja@users.noreply.github.com> Date: Wed, 2 Jul 2025 16:38:54 -0700 Subject: [PATCH 5/6] Auth'd user can't be a reviewer, so filter them --- .../workflows/random-reviewer-assignment.yml | 38 +++++++++++++------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/.github/workflows/random-reviewer-assignment.yml b/.github/workflows/random-reviewer-assignment.yml index 7b9bfb60666..d9d0a0dce23 100644 --- a/.github/workflows/random-reviewer-assignment.yml +++ b/.github/workflows/random-reviewer-assignment.yml @@ -19,38 +19,52 @@ jobs: with: script: | const TRIAGE_USERNAME = 'Lagoja'; - + try { + const authenticatedUser = await github.rest.users.getAuthenticated(); + const teamMembers = await github.rest.teams.listMembersInOrg({ org: 'jetify-com', team_slug: 'eng' }); const prAuthor = context.payload.pull_request.user.login.toLowerCase(); - console.log(`PR Author: ${prAuthor}`); + const prAuthorId = context.payload.pull_request.user.id; + const authenticatedUserLower = authenticatedUser.data.login.toLowerCase(); - // Get eligible reviewers (excluding PR author and lagoja) - + // Get eligible reviewers (excluding PR author, authenticated user, and lagoja) const eligibleReviewers = teamMembers.data - .map(member => member.login) - .filter(login => { - const loginLower = login.toLowerCase(); - return loginLower !== prAuthor && loginLower !== TRIAGE_USERNAME; - }); + .filter(member => { + const loginLower = member.login.toLowerCase(); + + // Exclude PR author by both ID and username + const isNotAuthor = (prAuthorId && member.id !== prAuthorId) && loginLower !== prAuthor; + // Exclude authenticated user + const isNotAuthenticatedUser = member.id !== authenticatedUser.data.id; + const isNotTriage = loginLower !== TRIAGE_USERNAME.toLowerCase(); + + return isNotAuthor && isNotAuthenticatedUser && isNotTriage; + }) + .map(member => member.login); + + console.log(`Eligible reviewers: ${eligibleReviewers.join(', ')}`); if (eligibleReviewers.length === 0) { console.log('No eligible reviewers found'); return; } - // Build reviewers list: random reviewer + lagoja (if not PR author) const randomReviewer = eligibleReviewers[Math.floor(Math.random() * eligibleReviewers.length)]; const reviewers = [randomReviewer]; - - if (prAuthor !== TRIAGE_USERNAME) { + + // Only add TRIAGE_USERNAME if they're not the PR author and not the authenticated user + if (prAuthor !== TRIAGE_USERNAME.toLowerCase() && + authenticatedUserLower !== TRIAGE_USERNAME.toLowerCase()) { reviewers.push(TRIAGE_USERNAME); } + console.log(`Final reviewers: ${reviewers.join(', ')}`); + console.log(`Assigning reviewers: ${reviewers.join(', ')}`); await github.rest.pulls.requestReviewers({ From 5815d6126888445bf007ef21aa16f505e7352ee0 Mon Sep 17 00:00:00 2001 From: John Lago <750845+Lagoja@users.noreply.github.com> Date: Wed, 2 Jul 2025 16:59:20 -0700 Subject: [PATCH 6/6] exclude jetify bot, skip if it's an internally created pr --- .../workflows/random-reviewer-assignment.yml | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/.github/workflows/random-reviewer-assignment.yml b/.github/workflows/random-reviewer-assignment.yml index d9d0a0dce23..d4e505194bf 100644 --- a/.github/workflows/random-reviewer-assignment.yml +++ b/.github/workflows/random-reviewer-assignment.yml @@ -19,6 +19,7 @@ jobs: with: script: | const TRIAGE_USERNAME = 'Lagoja'; + const EXCLUDE_USERNAMES = ['jetpack-io-bot']; try { const authenticatedUser = await github.rest.users.getAuthenticated(); @@ -31,19 +32,28 @@ jobs: const prAuthor = context.payload.pull_request.user.login.toLowerCase(); const prAuthorId = context.payload.pull_request.user.id; const authenticatedUserLower = authenticatedUser.data.login.toLowerCase(); + + // If the PR author is already a member of the team, we can skip random assignment + const isPrAuthorInTeam = teamMembers.data.some(member => + member.login.toLowerCase() === prAuthor && member.id === prAuthorId + ); + + if (isPrAuthorInTeam) { + console.log(`PR author ${prAuthor} is already a team member, skipping random assignment.`); + return; + } // Get eligible reviewers (excluding PR author, authenticated user, and lagoja) const eligibleReviewers = teamMembers.data .filter(member => { const loginLower = member.login.toLowerCase(); - // Exclude PR author by both ID and username - const isNotAuthor = (prAuthorId && member.id !== prAuthorId) && loginLower !== prAuthor; // Exclude authenticated user const isNotAuthenticatedUser = member.id !== authenticatedUser.data.id; const isNotTriage = loginLower !== TRIAGE_USERNAME.toLowerCase(); - - return isNotAuthor && isNotAuthenticatedUser && isNotTriage; + const isNotExcludedUsername = !EXCLUDE_USERNAMES.includes(loginLower); + + return isNotAuthenticatedUser && isNotTriage && isNotExcludedUsername; }) .map(member => member.login);