Skip to content

Commit

Permalink
PR Review Reminder created
Browse files Browse the repository at this point in the history
  • Loading branch information
csehatt741 committed Feb 19, 2025
1 parent f7ef02a commit 730b23b
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 17 deletions.
35 changes: 18 additions & 17 deletions .github/workflows/issue-pr-reminder.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@ jobs:
steps:
- uses: actions/github-script@v7
env:
CREATE_PR_REMINDER_ENABLED: ${{ vars.CREATE_PR_REMINDER_ENABLED }}
CREATE_PR_REMINDER_AFTER_ISSUE_ASSIGNED_DAYS: ${{ vars.CREATE_PR_REMINDER_AFTER_ISSUE_ASSIGNED_DAYS }}
CREATE_PR_REMINDER_BEFORE_ISSUE_UNASSIGNED_DAYS: ${{ vars.CREATE_PR_REMINDER_BEFORE_ISSUE_UNASSIGNED_DAYS }}
CREATE_ISSUE_PR_REMINDER_ENABLED: ${{ vars.CREATE_ISSUE_PR_REMINDER_ENABLED }}
CREATE_ISSUE_PR_REMINDER_AFTER_ISSUE_ASSIGNED_DAYS: ${{ vars.CREATE_ISSUE_PR_REMINDER_AFTER_ISSUE_ASSIGNED_DAYS }}
CREATE_ISSUE_PR_REMINDER_BEFORE_ISSUE_UNASSIGNED_DAYS: ${{ vars.CREATE_ISSUE_PR_REMINDER_BEFORE_ISSUE_UNASSIGNED_DAYS }}
UNASSIGN_ISSUE_AFTER_DAYS: ${{ vars.UNASSIGN_ISSUE_AFTER_DAYS }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const owner = 'keyshade-xyz';
const repo = 'keyshade';
const createPrReminderEnabled = process.env.CREATE_PR_REMINDER_ENABLED || false;
const createPrReminderAfterIssueAssignedDays = process.env.CREATE_PR_REMINDER_AFTER_ISSUE_ASSIGNED_DAYS || 2;
const createPrReminderBeforeIssueUnassignedDays = process.env.CREATE_PR_REMINDER_BEFORE_ISSUE_UNASSIGNED_DAYS || 2;
const createPrReminderEnabled = process.env.CREATE_ISSUE_PR_REMINDER_ENABLED || false;
const createPrReminderAfterIssueAssignedDays = process.env.CREATE_ISSUE_PR_REMINDER_AFTER_ISSUE_ASSIGNED_DAYS || 2;
const createPrReminderBeforeIssueUnassignedDays = process.env.CREATE_ISSUE_PR_REMINDER_BEFORE_ISSUE_UNASSIGNED_DAYS || 2;
const unassignIssueAfterDays = process.env.UNASSIGN_ISSUE_AFTER_DAYS || 14;
const createPrReminderAfterIssueAssignedMilliseconds = createPrReminderAfterIssueAssignedDays * 24 * 60 * 60 * 1000;
const createPrReminderBeforeIssueUnassignedMilliseconds = createPrReminderBeforeIssueUnassignedDays * 24 * 60 * 60 * 1000;
Expand Down Expand Up @@ -57,49 +57,49 @@ jobs:
}
async function listIssueEvents(issue) {
const allEvents = [];
const events = [];
let page = 1;
let hasNextPage = true;
while (hasNextPage) {
const issuesResponse = await github.rest.issues.listEventsForTimeline({
const eventsResponse = await github.rest.issues.listEventsForTimeline({
owner,
repo,
issue_number: issue.number,
per_page: 100,
page
});
allEvents.push(...issuesResponse.data);
events.push(...eventsResponse.data);
hasNextPage = issuesResponse.headers.link && issuesResponse.headers.link.includes('rel="next"');
hasNextPage = eventsResponse.headers.link && eventsResponse.headers.link.includes('rel="next"');
page++;
}
return allEvents.sort((a, b) => a.id > b.id);
return events.sort((a, b) => a.id > b.id);
}
async function listIssueComments(issue) {
const allComments = [];
const comments = [];
let page = 1;
let hasNextPage = true;
while (hasNextPage) {
const issuesResponse = await github.rest.issues.listComments({
const commentsResponse = await github.rest.issues.listComments({
owner,
repo,
issue_number: issue.number,
per_page: 100,
page
});
allComments.push(...issuesResponse.data);
comments.push(...commentsResponse.data);
hasNextPage = issuesResponse.headers.link && issuesResponse.headers.link.includes('rel="next"');
hasNextPage = commentsResponse.headers.link && commentsResponse.headers.link.includes('rel="next"');
page++;
}
return allComments;
return comments;
}
async function createPrReminder(assignee, issueNumber, comments, createPrReminderAfter, prReminder) {
Expand Down Expand Up @@ -161,8 +161,9 @@ jobs:
}
// Create final PR reminder
const unassignIssueAfter = new Date(issueAssignedAt.getTime() + unassignIssueAfterMilliseconds);
const createFinalPrReminderAfter = new Date(issueAssignedAt.getTime() + unassignIssueAfterMilliseconds - createPrReminderBeforeIssueUnassignedMilliseconds);
const finalPrReminder = `@${assignee.login}, please open a draft PR linking this issue!`;
const finalPrReminder = `@${assignee.login}, please open a draft PR linking this issue; otherwise you will be unassigned from this issue after ${unassignIssueAfter}!`;
await createPrReminder(assignee, issue.number, comments, createFinalPrReminderAfter, finalPrReminder);
}
Expand Down
164 changes: 164 additions & 0 deletions .github/workflows/pr-review-reminder.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
name: PR Review Reminder

on:
schedule:
- cron: '0 * * * *' # Runs every hour
workflow_dispatch:

jobs:
pr-review-reminder:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7
env:
CREATE_PR_REVIEW_REMINDER_ENABLED: ${{ vars.CREATE_PR_REVIEW_REMINDER_ENABLED }}
CREATE_PR_REVIEW_REMINDER_AFTER_PR_OPENED_DAYS: ${{ vars.CREATE_PR_REVIEW_REMINDER_AFTER_PR_OPENED_DAYS }}
CREATE_PR_REVIEW_REMINDER_BEFORE_PR_CLOSED_DAYS: ${{ vars.CREATE_PR_REVIEW_REMINDER_BEFORE_PR_CLOSED_DAYS }}
CLOSE_PR_AFTER_DAYS: ${{ vars.CLOSE_PR_AFTER_DAYS }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const owner = 'keyshade-xyz';
const repo = 'keyshade';
const createReviewReminderEnabled = process.env.CREATE_PR_REVIEW_REMINDER_ENABLED || false;
const createReviewReminderAfterPrOpenedDays = process.env.CREATE_PR_REVIEW_REMINDER_AFTER_PR_OPENED_DAYS || 2;
const createReviewReminderBeforePrClosedDays = process.env.CREATE_PR_REVIEW_REMINDER_BEFORE_PR_CLOSED_DAYS || 2;
const closePrAfterDays = process.env.CLOSE_PR_AFTER_DAYS || 14;
const createReviewReminderAfterPrOpenedMilliseconds = createReviewReminderAfterPrOpenedDays * 24 * 60 * 60 * 1000;
const createReviewReminderBeforePrClosedMilliseconds = createReviewReminderBeforePrClosedDays * 24 * 60 * 60 * 1000;
const closePrAfterMilliseconds = closePrAfterDays * 24 * 60 * 60 * 1000;
const now = Date.now();
if(!createReviewReminderEnabled) {
console.log('!!! Dry run, there are no changes made !!!');
}
async function listOpenPrs() {
const pullRequests = [];
let page = 1;
let hasNextPage = true;
while (hasNextPage) {
const pullRequestsResponse = await github.rest.pulls.list({
owner,
repo,
state: 'open',
per_page: 100,
page
});
pullRequests.push(...pullRequestsResponse.data);
hasNextPage = pullRequestsResponse.headers.link && pullRequestsResponse.headers.link.includes('rel="next"');
page++;
}
return pullRequests;
}
async function listPrReviews(pullRequest) {
const reviews = [];
let page = 1;
let hasNextPage = true;
while (hasNextPage) {
const reviewsResponse = await github.rest.pulls.listReviews({
owner,
repo,
pull_number: pullRequest.number,
per_page: 100,
page
});
reviews.push(...reviewsResponse.data);
hasNextPage = reviewsResponse.headers.link && reviewsResponse.headers.link.includes('rel="next"');
page++;
}
return reviews;
}
async function listPrComments(pullRequest) {
const comments = [];
let page = 1;
let hasNextPage = true;
while (hasNextPage) {
const issuesResponse = await github.rest.issues.listComments({
owner,
repo,
issue_number: pullRequest.number,
per_page: 100,
page
});
comments.push(...issuesResponse.data);
hasNextPage = issuesResponse.headers.link && issuesResponse.headers.link.includes('rel="next"');
page++;
}
return comments;
}
async function createReviewReminder(user, pullRequestNumber, comments, createReviewReminderAfter, reviewReminder) {
// Check if it is time to create review reminder
if (now < createReviewReminderAfter) {
return false;
}
// Check if review reminder has already been created
if (comments.some(comment => comment.body === reviewReminder)) {
return false;
}
// Create review reminder
if(createReviewReminderEnabled) {
await github.rest.issues.createComment({
owner,
repo,
issue_number: pullRequestNumber,
body: reviewReminder
});
}
console.log(`PR '${pullRequestNumber}' review reminder created for: ${user.login}`);
return true;
}
async function createReviewReminders() {
const pullRequests = await listOpenPrs();
for (const pullRequest of pullRequests) {
const reviews = await listPrReviews(pullRequest);
const unapprovedReviews = reviews.filter(review => review.state !== 'APPROVED');
const comments = await listPrComments(pullRequest);
const pullRequestCreatedAt = new Date(pullRequest.created_at);
// Check if PR has unapproved reviews
if(unapprovedReviews.length === 0) {
continue;
}
// Create first review reminder
const createFirstReviewReminderAfter = new Date(pullRequestCreatedAt.getTime() + createReviewReminderAfterPrOpenedMilliseconds);
const firstReviewReminder = `@${pullRequest.user.login}, please resolve all open reviews!`;
const isFirstReviewReminderCreated = await createReviewReminder(pullRequest.user, pullRequest.number, comments, createFirstReviewReminderAfter, firstReviewReminder);
if(isFirstReviewReminderCreated) {
continue;
}
// Create final review reminder
const closePrAfter = new Date(issueAssignedAt.getTime() + closePrAfterMilliseconds);
const createFinalReviewReminderAfter = new Date(issueAssignedAt.getTime() + closePrAfterMilliseconds - createReviewReminderBeforePrClosedMilliseconds);
const finalReviewReminder = `@${pullRequest.user.login}, please resolve all open reviews; otherwise you will be unassigned from this issue after ${unassignIssueAfter}!`;
await createReviewReminder(pullRequest.user, pullRequest.number, comments, createFinalReviewReminderAfter, finalReviewReminder);
}
}
await createReviewReminders();

0 comments on commit 730b23b

Please sign in to comment.