// Daily PR Review Reminder Script // Fetches open PRs from GreptimeDB repository and sends Slack notifications // to PR owners and assigned reviewers to keep review process moving. (async () => { const { Octokit } = await import("@octokit/rest"); const { default: axios } = await import('axios'); // Configuration const GITHUB_TOKEN = process.env.GITHUB_TOKEN; const SLACK_WEBHOOK_URL = process.env.SLACK_PR_REVIEW_WEBHOOK_URL; const REPO_OWNER = "GreptimeTeam"; const REPO_NAME = "greptimedb"; const GITHUB_TO_SLACK = JSON.parse(process.env.GITHUBID_SLACKID_MAPPING || '{}'); // Debug: Print environment variable status console.log("=== Environment Variables Debug ==="); console.log(`GITHUB_TOKEN: ${GITHUB_TOKEN ? 'Set ✓' : 'NOT SET ✗'}`); console.log(`SLACK_PR_REVIEW_WEBHOOK_URL: ${SLACK_WEBHOOK_URL ? 'Set ✓' : 'NOT SET ✗'}`); console.log(`GITHUBID_SLACKID_MAPPING: ${process.env.GITHUBID_SLACKID_MAPPING ? `Set ✓ (${Object.keys(GITHUB_TO_SLACK).length} mappings)` : 'NOT SET ✗'}`); console.log("===================================\n"); const octokit = new Octokit({ auth: GITHUB_TOKEN }); // Fetch all open PRs from the repository async function fetchOpenPRs() { try { const prs = await octokit.pulls.list({ owner: REPO_OWNER, repo: REPO_NAME, state: "open", per_page: 100, sort: "created", direction: "asc" }); return prs.data.filter((pr) => !pr.draft); } catch (error) { console.error("Error fetching PRs:", error); return []; } } // Convert GitHub username to Slack mention or fallback to GitHub username function toSlackMention(githubUser) { const slackUserId = GITHUB_TO_SLACK[githubUser]; return slackUserId ? `<@${slackUserId}>` : `@${githubUser}`; } // Calculate days since PR was opened function getDaysOpen(createdAt) { const created = new Date(createdAt); const now = new Date(); const diffMs = now - created; const days = Math.floor(diffMs / (1000 * 60 * 60 * 24)); return days; } // Build Slack notification message from PR list function buildSlackMessage(prs) { if (prs.length === 0) { return "*🎉 Great job! No pending PRs for review.*"; } // Separate PRs by age threshold (14 days) const criticalPRs = []; const recentPRs = []; prs.forEach(pr => { const daysOpen = getDaysOpen(pr.created_at); if (daysOpen >= 14) { criticalPRs.push(pr); } else { recentPRs.push(pr); } }); const lines = [ `*🔍 Daily PR Review Reminder 🔍*`, `Found *${criticalPRs.length}* critical PR(s) (14+ days old)\n` ]; // Show critical PRs (14+ days) in detail if (criticalPRs.length > 0) { criticalPRs.forEach((pr, index) => { const owner = toSlackMention(pr.user.login); const reviewers = pr.requested_reviewers || []; const reviewerMentions = reviewers.map(r => toSlackMention(r.login)).join(", "); const daysOpen = getDaysOpen(pr.created_at); const prInfo = `${index + 1}. <${pr.html_url}|#${pr.number}: ${pr.title}>`; const ageInfo = ` 🔴 Opened *${daysOpen}* day(s) ago`; const ownerInfo = ` 👤 Owner: ${owner}`; const reviewerInfo = reviewers.length > 0 ? ` 👁️ Reviewers: ${reviewerMentions}` : ` 👁️ Reviewers: _Not assigned yet_`; lines.push(prInfo); lines.push(ageInfo); lines.push(ownerInfo); lines.push(reviewerInfo); lines.push(""); // Empty line between PRs }); } lines.push("_Let's keep the code review process moving! 🚀_"); return lines.join("\n"); } // Send notification to Slack webhook async function sendSlackNotification(message) { if (!SLACK_WEBHOOK_URL) { console.log("⚠️ SLACK_PR_REVIEW_WEBHOOK_URL not configured. Message preview:"); console.log("=".repeat(60)); console.log(message); console.log("=".repeat(60)); return; } try { const response = await axios.post(SLACK_WEBHOOK_URL, { text: message }); if (response.status !== 200) { throw new Error(`Slack API returned status ${response.status}`); } console.log("Slack notification sent successfully."); } catch (error) { console.error("Error sending Slack notification:", error); throw error; } } // Main execution flow async function run() { console.log(`Fetching open PRs from ${REPO_OWNER}/${REPO_NAME}...`); const prs = await fetchOpenPRs(); console.log(`Found ${prs.length} open PR(s).`); const message = buildSlackMessage(prs); console.log("Sending Slack notification..."); await sendSlackNotification(message); } run().catch(error => { console.error("Script execution failed:", error); process.exit(1); }); })();