209 lines
7.3 KiB
YAML
209 lines
7.3 KiB
YAML
name: Triage
|
|
|
|
on:
|
|
pull_request_target:
|
|
types:
|
|
- opened
|
|
- synchronize
|
|
- reopened
|
|
- closed
|
|
- labeled
|
|
- unlabeled
|
|
schedule:
|
|
- cron: '0 */3 * * *' # every 3 hours
|
|
|
|
jobs:
|
|
review:
|
|
runs-on: ubuntu-latest
|
|
if: startsWith(github.repository, 'Homebrew/')
|
|
steps:
|
|
- name: Re-run this workflow
|
|
if: github.event_name == 'schedule' || github.event.action == 'closed'
|
|
uses: reitermarkus/rerun-workflow@e2647e8885422412d5acbd61f9add5b1e77ad3ab
|
|
with:
|
|
token: ${{ secrets.HOMEBREW_GITHUB_API_TOKEN }}
|
|
continuous-label: waiting for feedback
|
|
trigger-labels: critical
|
|
workflow: triage.yml
|
|
- name: Review pull request
|
|
if: >
|
|
(github.event_name == 'pull_request' || github.event_name == 'pull_request_target') &&
|
|
github.event.action != 'closed'
|
|
uses: actions/github-script@v3
|
|
with:
|
|
github-token: ${{ secrets.HOMEBREW_GITHUB_API_TOKEN }}
|
|
script: |
|
|
async function approvePullRequest(pullRequestNumber) {
|
|
const reviews = await approvalsByAuthenticatedUser(pullRequestNumber)
|
|
|
|
if (reviews.length > 0) {
|
|
return
|
|
}
|
|
|
|
await github.pulls.createReview({
|
|
...context.repo,
|
|
pull_number: pullRequestNumber,
|
|
event: 'APPROVE',
|
|
})
|
|
}
|
|
|
|
async function findComment(pullRequestNumber, id) {
|
|
const { data: comments } = await github.issues.listComments({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: pullRequestNumber,
|
|
})
|
|
|
|
const regex = new RegExp(`<!--\\s*#${id}\\s*-->`)
|
|
return comments.filter(comment => comment.body.match(regex))[0]
|
|
}
|
|
|
|
async function createOrUpdateComment(pullRequestNumber, id, message) {
|
|
const beginComment = await findComment(pullRequestNumber, id)
|
|
|
|
const body = `<!-- #${id} -->\n\n${message}`
|
|
if (beginComment) {
|
|
await github.issues.updateComment({
|
|
...context.repo,
|
|
comment_id: beginComment.id,
|
|
body,
|
|
})
|
|
} else {
|
|
await github.issues.createComment({
|
|
...context.repo,
|
|
issue_number: pullRequestNumber,
|
|
body,
|
|
})
|
|
}
|
|
}
|
|
|
|
async function approvalsByAuthenticatedUser(pullRequestNumber) {
|
|
const { data: user } = await github.users.getAuthenticated()
|
|
|
|
const { data: reviews } = await github.pulls.listReviews({
|
|
...context.repo,
|
|
pull_number: pullRequestNumber,
|
|
})
|
|
|
|
const approvals = reviews.filter(review => review.state == 'APPROVED')
|
|
return approvals.filter(review => review.user.login == user.login)
|
|
}
|
|
|
|
async function dismissApprovals(pullRequestNumber, message) {
|
|
const reviews = await approvalsByAuthenticatedUser(pullRequestNumber)
|
|
for (const review of reviews) {
|
|
await github.pulls.dismissReview({
|
|
...context.repo,
|
|
pull_number: pullRequestNumber,
|
|
review_id: review.id,
|
|
message: message
|
|
});
|
|
}
|
|
}
|
|
|
|
function offsetDate(start, offsetHours, skippedDays) {
|
|
let end = new Date(start)
|
|
|
|
end.setUTCHours(end.getUTCHours() + (offsetHours % 24))
|
|
|
|
while (skippedDays.includes(end.getUTCDay()) || offsetHours >= 24) {
|
|
if (!skippedDays.includes(end.getUTCDay())) {
|
|
offsetHours -= 24
|
|
}
|
|
|
|
end.setUTCDate(end.getUTCDate() + 1)
|
|
}
|
|
|
|
if (skippedDays.includes(start.getUTCDay())) {
|
|
end.setUTCHours(offsetHours, 0, 0)
|
|
}
|
|
|
|
return end
|
|
}
|
|
|
|
function formatDate(date) {
|
|
return date.toISOString().replace(/\.\d+Z$/, ' UTC').replace('T', ' at ')
|
|
}
|
|
|
|
async function reviewPullRequest(pullRequestNumber) {
|
|
const { data: pullRequest } = await github.pulls.get({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
pull_number: pullRequestNumber,
|
|
})
|
|
|
|
const { data: user } = await github.users.getAuthenticated()
|
|
if (pullRequest.user.login == user.login) {
|
|
core.warning('Pull request author is a bot.')
|
|
return
|
|
}
|
|
|
|
if (pullRequest.author_association != 'MEMBER') {
|
|
core.warning('Pull request author is not a member.')
|
|
return
|
|
}
|
|
|
|
const reviewLabel = 'waiting for feedback'
|
|
const criticalLabel = 'critical'
|
|
|
|
const labels = pullRequest.labels.map(label => label.name)
|
|
const hasReviewLabel = labels.includes(reviewLabel)
|
|
const hasCriticalLabel = labels.includes(criticalLabel)
|
|
|
|
const offsetHours = 24
|
|
const skippedDays = [
|
|
6, // Saturday
|
|
0, // Sunday
|
|
]
|
|
|
|
const currentDate = new Date()
|
|
const reviewStartDate = new Date(pullRequest.created_at)
|
|
const reviewEndDate = offsetDate(reviewStartDate, offsetHours, skippedDays)
|
|
const reviewEnded = currentDate > reviewEndDate
|
|
|
|
if (reviewEnded || hasCriticalLabel) {
|
|
let message
|
|
if (hasCriticalLabel && !reviewEnded) {
|
|
message = `Review period skipped due to \`${criticalLabel}\` label.`
|
|
} else {
|
|
message = 'Review period ended.'
|
|
}
|
|
|
|
if (hasReviewLabel) {
|
|
await github.issues.removeLabel({
|
|
...context.repo,
|
|
issue_number: pullRequestNumber,
|
|
name: reviewLabel,
|
|
})
|
|
}
|
|
|
|
core.info(message)
|
|
await createOrUpdateComment(pullRequestNumber, 'review-period-end', message)
|
|
await approvePullRequest(pullRequestNumber)
|
|
} else {
|
|
const message = `Review period will end on ${formatDate(reviewEndDate)}.`
|
|
core.info(message)
|
|
|
|
await dismissApprovals(pullRequestNumber, 'Review period has not ended yet.')
|
|
await createOrUpdateComment(pullRequestNumber, 'review-period-begin', message)
|
|
|
|
const endComment = await findComment(pullRequestNumber, 'review-period-end')
|
|
if (endComment) {
|
|
await github.issues.deleteComment({
|
|
...context.repo,
|
|
comment_id: endComment.id,
|
|
})
|
|
}
|
|
|
|
await github.issues.addLabels({
|
|
...context.repo,
|
|
issue_number: pullRequestNumber,
|
|
labels: [reviewLabel],
|
|
})
|
|
|
|
core.setFailed('Review period has not ended yet.')
|
|
}
|
|
}
|
|
|
|
await reviewPullRequest(context.issue.number)
|