Skip to content

PR Review Reminder

PR Review Reminder #81

name: PR Review Reminder
on:
schedule:
# 한국 시간 12시에 실행 (UTC 기준 03시)
- cron: '0 3 * * *'
# 한국 시간 21시에 실행 (UTC 기준 09시)
- cron: '0 12 * * *'
workflow_dispatch:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
OWNER: ${{ github.repository_owner }}
REPO: ${{ github.event.repository.name }}
REVIEWER_MAP: '[
{"github": "Miaash", "discord": "<@938048520374603817>"},
{"github": "viaunixue", "discord": "<@386917108455309316>"},
{"github": "swwho96", "discord": "<@816298263614455839>"},
{"github": "PracticeKJY", "discord": "<@460816530007916554>"}
]'
jobs:
review-reminder:
runs-on: ubuntu-latest
steps:
- name: Check out the code
uses: actions/checkout@v3
- name: Get open PRs and requested reviewers
id: get-prs
run: |
prs=$(gh api graphql -F owner=$OWNER -F repo=$REPO -f query='
query($owner: String!, $repo: String!) {
repository(owner: $owner, name: $repo) {
pullRequests(states: OPEN, first: 100) {
edges {
node {
number
title
url
reviews(last: 100) {
nodes {
state
author {
login
}
}
}
reviewRequests(first: 100) {
nodes {
requestedReviewer {
... on User {
login
}
}
}
}
}
}
}
}
}
' --jq '.data.repository.pullRequests.edges | map({number: .node.number, title: .node.title, url: .node.url, reviews: .node.reviews.nodes, reviewRequests: .node.reviewRequests.nodes})')
prs_without_completed_reviews="[]"
ready_to_merge_prs="[]"
while read -r pr; do
echo "Debug: PR data"
echo "$pr" | jq '.'
pr_number=$(echo "$pr" | jq -r '.number')
pr_url=$(echo "$pr" | jq -r '.url')
pr_title=$(echo "$pr" | jq -r '.title')
reviews=$(echo "$pr" | jq -r '.reviews // [] | .[] | select(.state == "APPROVED") | .author.login')
review_requests=$(echo "$pr" | jq -r '.reviewRequests // [] | .[].requestedReviewer.login')
all_approved=true
incomplete_reviewers=()
for reviewer in $review_requests; do
if [[ ! " $reviews " =~ " $reviewer " ]]; then
all_approved=false
incomplete_reviewers+=("$reviewer")
fi
done
if [ "$all_approved" = false ]; then
pr=$(echo "$pr" | jq --argjson reviewers "$(printf '%s\n' "${incomplete_reviewers[@]}" | jq -R . | jq -s .)" '. + {incompleteReviewers: $reviewers}')
prs_without_completed_reviews=$(echo "$prs_without_completed_reviews" | jq --argjson pr "$pr" '. += [$pr]')
else
ready_to_merge_prs=$(echo "$ready_to_merge_prs" | jq --arg number "$pr_number" --arg title "$pr_title" --arg url "$pr_url" '. += [{"number": $number, "title": $title, "url": $url}]')
fi
done < <(echo "$prs" | jq -c '.[]')
echo "$prs_without_completed_reviews" > prs.json
echo "$prs_without_completed_reviews" > prs_to_review.json
echo "$ready_to_merge_prs" > prs_ready_to_merge.json
if [ "$(echo "$prs_without_completed_reviews" | jq length)" -gt 0 ]; then
echo "has_prs_to_review=true" >> $GITHUB_OUTPUT
else
echo "has_prs_to_review=false" >> $GITHUB_OUTPUT
fi
if [ "$(echo "$ready_to_merge_prs" | jq length)" -gt 0 ]; then
echo "has_prs_ready_to_merge=true" >> $GITHUB_OUTPUT
else
echo "has_prs_ready_to_merge=false" >> $GITHUB_OUTPUT
fi
- name: Prepare merge ready notification
if: steps.get-prs.outputs.has_prs_ready_to_merge == 'true'
id: prepare-merge-notification
run: |
ready_prs=$(cat prs_ready_to_merge.json)
pr_list=""
mentions=""
while read -r pr; do
pr_number=$(echo "$pr" | jq -r '.number')
pr_title=$(echo "$pr" | jq -r '.title')
pr_url=$(echo "$pr" | jq -r '.url')
pr_author=$(gh api repos/$OWNER/$REPO/pulls/$pr_number --jq '.user.login')
# GitHub 사용자를 Discord 사용자로 매핑
discord_user=$(echo "$REVIEWER_MAP" | jq --arg user "$pr_author" -r '.[] | select(.github == $user) | .discord')
if [ -n "$discord_user" ]; then
mentions+="$discord_user "
fi
pr_list+="- [PR #$pr_number: \"$pr_title\"]($pr_url) (작성자: $pr_author)\n"
done < <(echo "$ready_prs" | jq -c '.[]')
current_time=$(date --utc +%Y-%m-%dT%H:%M:%SZ)
merge_notification=$(jq -n \
--arg title "병합 준비 완료!" \
--arg description "$mentions\\\\n\\\\n다음 PR들이 모든 리뷰를 통과하여 병합 준비가 완료되었습니다:\\n$pr_list" \
--arg thumbnail "https://github.com/user-attachments/assets/5bbf4cfb-67a9-402b-969e-8fb142c37f0f" \
--arg author_name "PR MERGE BOT" \
--arg author_icon "https://avatars.githubusercontent.com/u/9919?s=200&v=4" \
--arg footer_text "NEONADEULI" \
--arg footer_icon "https://github.com/user-attachments/assets/5bbf4cfb-67a9-402b-969e-8fb142c37f0f" \
--arg timestamp "$current_time" \
'{
"embeds": [{
"title": $title,
"description": $description,
"color": 3066993,
"thumbnail": {"url": $thumbnail},
"author": {
"name": $author_name,
"icon_url": $author_icon
},
"footer": {
"text": $footer_text,
"icon_url": $footer_icon
},
"timestamp": $timestamp
}]
}')
echo "$merge_notification" > merge_notification.json
echo "has_merge_notification=true" >> $GITHUB_OUTPUT
- name: Map GitHub users to Discord users and notify
id: map-github-users
run: |
prs=$(cat prs.json)
embed_fields="[]"
reviewers_processed=()
while read -r pr; do
pr_number=$(echo "$pr" | jq -r '.number')
pr_title=$(echo "$pr" | jq -r '.title')
pr_url=$(echo "$pr" | jq -r '.url')
incomplete_reviewers=$(echo "$pr" | jq -r '.incompleteReviewers[]')
for reviewer in $incomplete_reviewers; do
if [[ ! " ${reviewers_processed[@]} " =~ " ${reviewer} " ]]; then
reviewers_processed+=("$reviewer")
discord_user=$(echo "$REVIEWER_MAP" | jq --arg user "$reviewer" -r '.[] | select(.github == $user) | .discord')
if [ -n "$discord_user" ]; then
github_user=$(echo "$REVIEWER_MAP" | jq --arg user "$reviewer" -r '.[] | select(.github == $user) | .github')
pr_list=""
while read -r pr_item; do
pr_num=$(echo "$pr_item" | jq -r '.number')
pr_title=$(echo "$pr_item" | jq -r '.title')
pr_url=$(echo "$pr_item" | jq -r '.url')
pr_list+="- [PR #$pr_num: \"$pr_title\"]($pr_url)\n"
done < <(echo "$prs" | jq -c --arg user "$reviewer" '.[] | select(.incompleteReviewers[] == $user)')
pr_count=$(echo "$prs" | jq --arg user "$reviewer" '[.[] | select(.incompleteReviewers[] == $user)] | length')
fire_count=$(printf ' 🔥%.0s' $(seq 1 $pr_count))
embed_fields=$(echo "$embed_fields" | jq --arg name "$github_user $fire_count ($pr_count건)" --arg value "$discord_user\n$pr_list" '. += [{"name": $name, "value": ($value | gsub("\\\\n"; "\n")), "inline": false}]')
fi
fi
done
done < <(echo "$prs" | jq -c '.[]')
if [ "$(echo "$embed_fields" | jq length)" -eq 0 ]; then
echo "No reviewers to notify."
echo "has_notifications=false" >> $GITHUB_OUTPUT
else
current_time=$(date --utc +%Y-%m-%dT%H:%M:%SZ)
embed=$(jq -n \
--arg title "리뷰를 기다리고 있어요!!" \
--arg description "[리뷰하러 가기](https://github.com/$OWNER/$REPO/pulls)" \
--argjson fields "$embed_fields" \
--arg thumbnail "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRidSZL4BdECVb3sL0ZQ2jZSYIWNDQTiTcJJQ&usqp=CAU" \
--arg author_name "PR REMINDER BOT" \
--arg author_icon "https://avatars.githubusercontent.com/u/9919?s=200&v=4" \
--arg footer_text "NEONADEULI" \
--arg footer_icon "https://github.com/user-attachments/assets/5bbf4cfb-67a9-402b-969e-8fb142c37f0f" \
--arg timestamp "$current_time" \
'{
"embeds": [{
"title": $title,
"description": $description,
"color": 15258703,
"fields": $fields,
"thumbnail": {"url": $thumbnail},
"author": {
"name": $author_name,
"icon_url": $author_icon
},
"footer": {
"text": $footer_text,
"icon_url": $footer_icon
},
"timestamp": $timestamp
}]
}')
echo "$embed" > embed.json
echo "has_notifications=true" >> $GITHUB_OUTPUT
fi
- name: Send Discord Notification
if: steps.map-github-users.outputs.has_notifications == 'true'
run: |
if [ "${{ steps.map-github-users.outputs.has_notifications }}" == "true" ]; then
curl -H "Content-Type: application/json" -d @embed.json ${{ secrets.DISCORD_WEBHOOK_URL }}
fi
if [ "${{ steps.prepare-merge-notification.outputs.has_merge_notification }}" == "true" ]; then
curl -H "Content-Type: application/json" -d @merge_notification.json ${{ secrets.DISCORD_WEBHOOK_URL }}
fi