Skip to content

Instantly share code, notes, and snippets.

@copyleftdev
Created April 22, 2024 16:00
Show Gist options
  • Save copyleftdev/b1f9ad1ef65a4fce42ff8685c94b9839 to your computer and use it in GitHub Desktop.
Save copyleftdev/b1f9ad1ef65a4fce42ff8685c94b9839 to your computer and use it in GitHub Desktop.
Advanced GitHub Action Script for Stale Pull Request Management
name: Mark stale pull requests in Shepherd repo
on:
schedule:
- cron: '0 0 * * 1' # Runs every Monday at midnight
jobs:
stale:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout code
uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5
- name: Check PRs and compile report
id: compile_report
uses: actions/github-script@211cb3fefb35a799baa5156f9321bb774fe56294
with:
script: |
const FIVE_DAYS_AGO = new Date(new Date().setDate(new Date().getDate() - 5));
const THREE_DAYS_AGO = new Date(new Date().setDate(new Date().getDate() - 3));
const STATES_COMPARE = ["COMMENTED", "CHANGES_REQUESTED"];
// Function to fetch pull requests
async function fetchPullRequests() {
return await github.paginate(github.rest.pulls.list, {
owner: 'rancher',
repo: 'shepherd',
state: 'open',
per_page: 100,
});
}
// Function to fetch reviews for a pull request
async function fetchReviews(owner, repo, pull_number) {
return await github.rest.pulls.listReviews({
owner,
repo,
pull_number,
per_page: 100,
});
}
// Process pull requests and compile report
async function processPullRequests(prs) {
let report = {
noReviewsReport: '',
needsReviews: '',
needsChanges: '',
needsMerge: '',
hasErrors: ''
};
for (const pr of prs) {
const createdDate = new Date(pr.created_at);
let lastReviewDate = null;
let lastReviewStateOne = "";
let lastReviewStateTwo = "";
let numberOfApprovedState = 0;
try {
const reviews = await fetchReviews('rancher', 'shepherd', pr.number);
if (reviews.data.length == 0 && createdDate < FIVE_DAYS_AGO) {
report.noReviewsReport += `${pr.title} #${pr.number} ${pr.html_url} ${pr.user.login}\n`;
} else {
processReviews(reviews.data, &lastReviewDate, &lastReviewStateOne, &lastReviewStateTwo, &numberOfApprovedState);
}
} catch (error) {
report.hasErrors += `${pr.title} (#${pr.number}) ${pr.html_url} - Error: ${error} \n`;
}
classifyPR(pr, lastReviewDate, lastReviewStateOne, lastReviewStateTwo, numberOfApprovedState, report);
}
return compileFullReport(report);
}
// Process reviews to determine the latest review states
function processReviews(reviews, lastReviewDate, lastReviewStateOne, lastReviewStateTwo, numberOfApprovedState) {
for (const review of reviews) {
const reviewDate = new Date(review.submitted_at);
if (!lastReviewDate || reviewDate > lastReviewDate) {
lastReviewDate = reviewDate;
lastReviewStateTwo = lastReviewStateOne;
lastReviewStateOne = review.state;
if (review.state === "APPROVED") {
numberOfApprovedState++;
}
}
}
}
// Classify pull requests based on review states and times
function classifyPR(pr, lastReviewDate, lastReviewStateOne, lastReviewStateTwo, numberOfApprovedState, report) {
const updatedDate = new Date(pr.updated_at);
if (lastReviewDate) {
if (lastReviewDate < THREE_DAYS_AGO && updatedDate <= lastReviewDate && STATES_COMPARE.includes(lastReviewStateOne) && STATES_COMPARE.includes(lastReviewStateTwo)) {
report.needsChanges += `${pr.title} (#${pr.number}) ${pr.html_url} ${pr.user.login} \n`;
} else if (updatedDate > lastReviewDate && updatedDate < THREE_DAYS_AGO) {
report.needsReviews += `${pr.title} (#${pr.number}) ${pr.html_url} ${pr.user.login} \n`;
} else if (numberOfApprovedState >= 2 && !STATES_COMPARE.includes(lastReviewStateOne)) {
report.needsMerge += `${pr.title} (#${pr.number}) ${pr.html_url} ${pr.user.login} \n`;
}
}
}
// Compile the full report from segments
function compileFullReport(report) {
let fullReport = '';
if (report.noReviewsReport) {
fullReport += `PRs with no reviews yet: \n` + report.noReviewsReport +`\n\n`;
}
if (report.needsReviews) {
fullReport += `PRs that need reviews: \n` + report.needsReviews +`\n\n`;
}
if (report.needsChanges) {
fullReport += `PRs with changes requested: \n` + report.needsChanges +`\n\n`;
}
if (report.needsMerge) {
fullReport += `PRs that need to be merged: \n` + report.needsMerge;
}
if (report.hasErrors) {
fullReport += `PRs that have errors when fetching: \n` + report.hasErrors;
}
return fullReport;
}
const prs = await fetchPullRequests();
return processPullRequests(prs);
result-encoding: string
- name: Notify on Slack
run: |
result="${{ steps.compile_report.outputs.result }}"
if [[ -n "$result" ]]; then
curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"PR Status Report:\n$result\"}" ${{ secrets.SLACK_WEBHOOK_URL }}
else
curl -X POST -H 'Content-type: application/json' --data '{"text":"PR Status Report: There are errors please check the GH actions logs"}' ${{ secrets.SLACK_WEBHOOK_URL }}
fi
shell: bash
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment