[FIX] 프론트엔드 코드 리뷰 리마인더 및 Discord 알림 수정 #28
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: FE Review Reminder for Discord | |
on: | |
pull_request: | |
branches: | |
- develop | |
schedule: | |
- cron: '10 1 * * 1-5' # 매주 월요일부터 금요일까지, 한국 시간 오전 10시 10분에 실행 | |
workflow_dispatch: | |
jobs: | |
review-reminder: | |
runs-on: ubuntu-latest | |
steps: | |
- name: Send Reminder to Discord | |
uses: actions/github-script@v7 | |
env: | |
DISCORD_WEBHOOK: ${{ secrets.FE_REVIEW_NOTIFICATION_WEBHOOK_URL }} | |
DISCORD_MENTION: ${{ secrets.FE_GITHUB_DISCORD_ID }} | |
with: | |
script: | | |
const owner = context.repo.owner; | |
const repo = context.repo.repo; | |
// GitHub 사용자명과 Discord 멘션 정보 매핑 | |
const discordMentions = JSON.parse(process.env.DISCORD_MENTION); | |
const discordNotMentions = { | |
'useon': '썬데이', | |
'novice0840': '포메', | |
'rbgksqkr': '마루', | |
}; | |
async function getReviews(owner, repo, prNumber) { | |
// 특정 PR의 리뷰 상태 가져오기 | |
const reviews = await github.rest.pulls.listReviews({ | |
owner, | |
repo, | |
pull_number: prNumber, | |
}); | |
return reviews.data; | |
} | |
try { | |
// 열려 있는 PR 목록 가져오기 | |
const pullRequests = await github.rest.pulls.list({ | |
owner, | |
repo, | |
state: 'open', | |
}); | |
// FE 라벨이 달린 PR을 D-DAY가 임박한 순서로 정렬 | |
const fePrs = pullRequests.data | |
.filter(pr => pr.labels.some(label => label.name.includes('FE'))) | |
.map(pr => { | |
const dLabel = pr.labels.find(label => label.name.startsWith('D-')); | |
const urgency = dLabel ? parseInt(dLabel.name.split('-')[1], 10) : Number.MAX_SAFE_INTEGER; | |
return { | |
...pr, | |
urgency, | |
dLabelName: dLabel?.name || 'D-3', | |
updatedAt: pr.updated_at, | |
createdAt: pr.created_at, | |
}; | |
}) | |
.sort((a, b) => a.urgency - b.urgency); | |
// 열린 PR 중 FE PR이 없는 경우 실행 종료 | |
if (fePrs.length === 0) { | |
console.log('No FE PRs to remind.'); | |
return; | |
} | |
const messages = await Promise.all( | |
fePrs.map(async pr => { | |
const reviews = await getReviews(owner, repo, pr.number); | |
const requestedReviewers = pr.requested_reviewers.map(r => r.login); | |
// 리뷰 상태를 관리하는 Map 객체 생성 | |
const reviewStates = new Map(); | |
reviews.forEach(review => { | |
const reviewer = review.user.login; | |
const state = review.state; | |
if (reviewer !== pr.user.login) { // PR 작성자는 제외 | |
reviewStates.set(reviewer, state); | |
} | |
}); | |
// 리뷰 상태 메시지 생성 | |
const reviewStatuses = Array.from(reviewStates.entries()).map(([reviewer, state]) => { | |
const discordUsername = discordMentions[reviewer] || `${reviewer}`; | |
const stateAbbreviations = { | |
APPROVED: 'A', | |
CHANGES_REQUESTED: 'RC', | |
COMMENTED: 'C', | |
}; | |
const reviewState = stateAbbreviations[state] || state.toLowerCase(); | |
return state === 'APPROVED' | |
? `${discordNotMentions[reviewer]}(${reviewState})` // APPROVED인 경우 멘션 없이 이름만 표시 | |
: `<@${discordMentions[reviewer]}>(${reviewState})`; // 나머지 상태인 경우 멘션 | |
}); | |
// 리뷰를 시작하지 않은 리뷰어 추가 | |
const notStartedReviewers = requestedReviewers.filter( | |
reviewer => !reviewStates.has(reviewer) && reviewer !== pr.user.login // PR 작성자 제외 | |
); | |
console.log(notStartedReviewers, '리뷰를 시작하지 않은 리뷰어') | |
const notStartedMentions = notStartedReviewers.map(reviewer => { | |
return `<@${discordMentions[reviewer]}>(X)`; | |
}); | |
const reviewStatusMessage = [...reviewStatuses, ...notStartedMentions]; | |
console.log(reviewStates, '모든 리뷰 상태 확인'); | |
console.log(requestedReviewers, '요청된 리뷰어 확인'); | |
const allReviewersApproved = requestedReviewers.every( | |
reviewer => reviewStates.get(reviewer) === 'APPROVED' | |
); | |
console.log(allReviewersApproved, '모든 리뷰어가 어프루브 상태인가?') | |
const noPendingReviews = notStartedReviewers.length === 0; | |
// 모든 리뷰어가 APPROVED 상태이고 리뷰를 시작하지 않은 리뷰어가 없는 경우 | |
if (allReviewersApproved && noPendingReviews) { | |
const authorMention = discordMentions[pr.user.login] || `${pr.user.login}`; | |
return `[[${pr.dLabelName}] ${pr.title}](<${pr.html_url}>)\n리뷰어: ${reviewStatusMessage.join(', ')}\n<@${authorMention}>, 모든 리뷰어의 승인 완료! 코멘트를 확인 후 머지해 주세요 🚀`; | |
} | |
// 일반적인 리마인드 메시지 | |
return `[[${pr.dLabelName}] ${pr.title}](<${pr.html_url}>)\n리뷰어: ${reviewStatusMessage.join(', ')}`; | |
}) | |
); | |
// 최종 메시지 Discord에 전송 | |
# const response = await fetch(process.env.DISCORD_WEBHOOK, { | |
# method: 'POST', | |
# headers: { 'Content-Type': 'application/json' }, | |
# body: JSON.stringify({ | |
# content: `🍀 [FE] 리뷰가 필요한 PR 목록 🍀\n\n${messages.join('\n\n')}\n\n`, | |
# allowed_mentions: { | |
# parse: ["users"], // 멘션 가능한 사용자만 허용 | |
# }, | |
# }), | |
# }); | |
# console.log('Response status:', response.status); | |
# } catch (error) { | |
# console.error('Error processing FE PR reminders:', error.message); | |
# throw error; // 워크플로우 실패 상태 반환 | |
# } |