Skip to content

Commit 5be4987

Browse files
committed
Merge remote-tracking branch 'origin/main' into zhongzc/repartition-procedure-scaffold
2 parents db11022 + 41ce100 commit 5be4987

File tree

255 files changed

+12197
-2416
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

255 files changed

+12197
-2416
lines changed

.github/scripts/package-lock.json

Lines changed: 507 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.github/scripts/package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "greptimedb-github-scripts",
3+
"version": "1.0.0",
4+
"type": "module",
5+
"description": "GitHub automation scripts for GreptimeDB",
6+
"dependencies": {
7+
"@octokit/rest": "^21.0.0",
8+
"axios": "^1.7.0"
9+
}
10+
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
// Daily PR Review Reminder Script
2+
// Fetches open PRs from GreptimeDB repository and sends Slack notifications
3+
// to PR owners and assigned reviewers to keep review process moving.
4+
5+
(async () => {
6+
const { Octokit } = await import("@octokit/rest");
7+
const { default: axios } = await import('axios');
8+
9+
// Configuration
10+
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
11+
const SLACK_WEBHOOK_URL = process.env.SLACK_PR_REVIEW_WEBHOOK_URL;
12+
const REPO_OWNER = "GreptimeTeam";
13+
const REPO_NAME = "greptimedb";
14+
const GITHUB_TO_SLACK = JSON.parse(process.env.GITHUBID_SLACKID_MAPPING || '{}');
15+
16+
// Debug: Print environment variable status
17+
console.log("=== Environment Variables Debug ===");
18+
console.log(`GITHUB_TOKEN: ${GITHUB_TOKEN ? 'Set ✓' : 'NOT SET ✗'}`);
19+
console.log(`SLACK_PR_REVIEW_WEBHOOK_URL: ${SLACK_WEBHOOK_URL ? 'Set ✓' : 'NOT SET ✗'}`);
20+
console.log(`GITHUBID_SLACKID_MAPPING: ${process.env.GITHUBID_SLACKID_MAPPING ? `Set ✓ (${Object.keys(GITHUB_TO_SLACK).length} mappings)` : 'NOT SET ✗'}`);
21+
console.log("===================================\n");
22+
23+
const octokit = new Octokit({
24+
auth: GITHUB_TOKEN
25+
});
26+
27+
// Fetch all open PRs from the repository
28+
async function fetchOpenPRs() {
29+
try {
30+
const prs = await octokit.pulls.list({
31+
owner: REPO_OWNER,
32+
repo: REPO_NAME,
33+
state: "open",
34+
per_page: 100,
35+
sort: "created",
36+
direction: "asc"
37+
});
38+
return prs.data.filter((pr) => !pr.draft);
39+
} catch (error) {
40+
console.error("Error fetching PRs:", error);
41+
return [];
42+
}
43+
}
44+
45+
// Convert GitHub username to Slack mention or fallback to GitHub username
46+
function toSlackMention(githubUser) {
47+
const slackUserId = GITHUB_TO_SLACK[githubUser];
48+
return slackUserId ? `<@${slackUserId}>` : `@${githubUser}`;
49+
}
50+
51+
// Calculate days since PR was opened
52+
function getDaysOpen(createdAt) {
53+
const created = new Date(createdAt);
54+
const now = new Date();
55+
const diffMs = now - created;
56+
const days = Math.floor(diffMs / (1000 * 60 * 60 * 24));
57+
return days;
58+
}
59+
60+
// Get urgency emoji based on PR age
61+
function getAgeEmoji(days) {
62+
if (days >= 14) return "🔴"; // 14+ days - critical
63+
if (days >= 7) return "🟠"; // 7+ days - urgent
64+
if (days >= 3) return "🟡"; // 3+ days - needs attention
65+
return "🟢"; // < 3 days - fresh
66+
}
67+
68+
// Build Slack notification message from PR list
69+
function buildSlackMessage(prs) {
70+
if (prs.length === 0) {
71+
return "*🎉 Great job! No pending PRs for review.*";
72+
}
73+
74+
// Separate PRs by age threshold (14 days)
75+
const criticalPRs = [];
76+
const recentPRs = [];
77+
78+
prs.forEach(pr => {
79+
const daysOpen = getDaysOpen(pr.created_at);
80+
if (daysOpen >= 14) {
81+
criticalPRs.push(pr);
82+
} else {
83+
recentPRs.push(pr);
84+
}
85+
});
86+
87+
const lines = [
88+
`*🔍 Daily PR Review Reminder 🔍*`,
89+
`Found *${criticalPRs.length}* critical PR(s) (14+ days old)\n`
90+
];
91+
92+
// Show critical PRs (14+ days) in detail
93+
if (criticalPRs.length > 0) {
94+
criticalPRs.forEach((pr, index) => {
95+
const owner = toSlackMention(pr.user.login);
96+
const reviewers = pr.requested_reviewers || [];
97+
const reviewerMentions = reviewers.map(r => toSlackMention(r.login)).join(", ");
98+
const daysOpen = getDaysOpen(pr.created_at);
99+
100+
const prInfo = `${index + 1}. <${pr.html_url}|#${pr.number}: ${pr.title}>`;
101+
const ageInfo = ` 🔴 Opened *${daysOpen}* day(s) ago`;
102+
const ownerInfo = ` 👤 Owner: ${owner}`;
103+
const reviewerInfo = reviewers.length > 0
104+
? ` 👁️ Reviewers: ${reviewerMentions}`
105+
: ` 👁️ Reviewers: _Not assigned yet_`;
106+
107+
lines.push(prInfo);
108+
lines.push(ageInfo);
109+
lines.push(ownerInfo);
110+
lines.push(reviewerInfo);
111+
lines.push(""); // Empty line between PRs
112+
});
113+
}
114+
115+
lines.push("_Let's keep the code review process moving! 🚀_");
116+
117+
return lines.join("\n");
118+
}
119+
120+
// Send notification to Slack webhook
121+
async function sendSlackNotification(message) {
122+
if (!SLACK_WEBHOOK_URL) {
123+
console.log("⚠️ SLACK_PR_REVIEW_WEBHOOK_URL not configured. Message preview:");
124+
console.log("=".repeat(60));
125+
console.log(message);
126+
console.log("=".repeat(60));
127+
return;
128+
}
129+
130+
try {
131+
const response = await axios.post(SLACK_WEBHOOK_URL, {
132+
text: message
133+
});
134+
135+
if (response.status !== 200) {
136+
throw new Error(`Slack API returned status ${response.status}`);
137+
}
138+
console.log("Slack notification sent successfully.");
139+
} catch (error) {
140+
console.error("Error sending Slack notification:", error);
141+
throw error;
142+
}
143+
}
144+
145+
// Main execution flow
146+
async function run() {
147+
console.log(`Fetching open PRs from ${REPO_OWNER}/${REPO_NAME}...`);
148+
const prs = await fetchOpenPRs();
149+
console.log(`Found ${prs.length} open PR(s).`);
150+
151+
const message = buildSlackMessage(prs);
152+
console.log("Sending Slack notification...");
153+
await sendSlackNotification(message);
154+
}
155+
156+
run().catch(error => {
157+
console.error("Script execution failed:", error);
158+
process.exit(1);
159+
});
160+
})();
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: PR Review Reminder
2+
3+
on:
4+
schedule:
5+
# Run at 9:00 AM UTC+8 (01:00 AM UTC) every day
6+
- cron: '0 1 * * *'
7+
workflow_dispatch:
8+
9+
jobs:
10+
pr-review-reminder:
11+
name: Send PR Review Reminders
12+
runs-on: ubuntu-latest
13+
permissions:
14+
contents: read
15+
pull-requests: read
16+
if: ${{ github.repository == 'GreptimeTeam/greptimedb' }}
17+
steps:
18+
- name: Checkout repository
19+
uses: actions/checkout@v4
20+
21+
- name: Setup Node.js
22+
uses: actions/setup-node@v4
23+
with:
24+
node-version: '20'
25+
26+
- name: Install dependencies
27+
working-directory: .github/scripts
28+
run: npm ci
29+
30+
- name: Run PR review reminder
31+
working-directory: .github/scripts
32+
run: node pr-review-reminder.js
33+
env:
34+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
35+
SLACK_PR_REVIEW_WEBHOOK_URL: ${{ vars.SLACK_PR_REVIEW_WEBHOOK_URL }}
36+
GITHUBID_SLACKID_MAPPING: ${{ vars.GITHUBID_SLACKID_MAPPING }}

0 commit comments

Comments
 (0)