PR Review Reminder #89
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |