أتمتة تنبيه Pull Requests القديمة إلى Slack
هتطلع من المقال ده بـ workflow عملي يقلل PRs المنسية قبل ما تتحول لتعطيل يوم كامل في الفريق.
مستوى القارئ: متوسط
المشكلة باختصار
لو فريقك عنده 6 مطورين وبيفتح 20 Pull Request في الأسبوع، غالبًا فيه PR أو اثنين بيتأخروا أكتر من 48 ساعة. التأخير ده مش بس رقم في GitHub. اللي بيحصل فعلاً إن المطور يبدّل context، ويرجع بعد يومين يفتكر ليه كتب التغيير أصلاً.
الطريقة الشائعة الغلط إنك تعتمد على إن كل واحد يفتح GitHub بنفسه كل صباح. الطريقة دي بتفشل لما الفريق يدخل في deploy أو incident أو ضغط دعم. البديل الأفضل: فحص آلي يومي يجيب PRs المفتوحة، يفلتر القديمة، ويرسل Slack message فيها الرابط والمالك والعمر بالساعات.
الفكرة الأساسية
الافتراض إن عندك repository واحد أو عدة repositories على GitHub، وقناة Slack داخلية اسمها مثلًا #engineering-review. هنستخدم GitHub REST API لقائمة Pull Requests المفتوحة، وSlack Incoming Webhook لإرسال الرسالة. حسب توثيق GitHub، endpoint قائمة Pull Requests يدعم state=open وsort=updated وper_page. وحسب توثيق Slack، الـ webhook يستقبل طلب HTTP POST فيه JSON يحتوي على text أو blocks.
ركز في نقطة مهمة: إحنا مش بنعمل bot معقد. إحنا بنعمل guardrail. يعني النظام لا يقرر بدل الفريق، لكنه يقول: “فيه PR بقاله 54 ساعة من غير merge”. المكسب إنك تكتشف التعطيل بدري. التكلفة إنك ممكن تبعت تنبيه زائد لو PR متعمد يفضل مفتوح.
الخطوات العملية
- اعمل Slack Incoming Webhook وخزّن الرابط في secret اسمه
SLACK_WEBHOOK_URL. - اعمل GitHub token بصلاحية قراءة Pull Requests فقط لو المستودع private.
- حدد حد التأخير. في فرق صغيرة، 48 ساعة رقم مناسب كبداية.
- شغّل سكربت يومي من cron أو GitHub Actions.
الملف التالي يشتغل كـ GitHub Actions job يوميًا الساعة 8 صباحًا UTC. لو عايزها تدعم توقيت الرياض، خليه 5 صباحًا UTC تقريبًا لمعظم السنة، أو استخدم scheduling خارجي يدعم timezone صريح.
name: stale-pr-alert
on:
schedule:
- cron: "0 8 * * *"
workflow_dispatch:
jobs:
alert:
runs-on: ubuntu-latest
steps:
- name: Find old open pull requests
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
OWNER: your-org
REPO: your-repo
MAX_AGE_HOURS: "48"
run: |
python - <<'PY'
import os, requests
from datetime import datetime, timezone
owner = os.environ["OWNER"]
repo = os.environ["REPO"]
max_age = int(os.environ.get("MAX_AGE_HOURS", "48"))
headers = {
"Accept": "application/vnd.github+json",
"Authorization": f"Bearer {os.environ['GH_TOKEN']}",
"X-GitHub-Api-Version": "2022-11-28",
}
url = f"https://api.github.com/repos/{owner}/{repo}/pulls"
params = {"state": "open", "sort": "updated", "direction": "asc", "per_page": 100}
pulls = requests.get(url, headers=headers, params=params, timeout=20).json()
now = datetime.now(timezone.utc)
old = []
for pr in pulls:
updated = datetime.fromisoformat(pr["updated_at"].replace("Z", "+00:00"))
age_hours = int((now - updated).total_seconds() // 3600)
if age_hours >= max_age:
old.append(f"• <{pr['html_url']}|#{pr['number']} {pr['title']}> — {age_hours}h — @{pr['user']['login']}")
if not old:
print("No stale PRs")
raise SystemExit(0)
text = "Pull Requests محتاجة مراجعة:\n" + "\n".join(old[:10])
response = requests.post(os.environ["SLACK_WEBHOOK_URL"], json={"text": text}, timeout=20)
response.raise_for_status()
PY