workflow: Add status label to PRs (#778)

This commit is contained in:
MonsterDruide1 2025-10-31 01:13:53 +01:00 committed by GitHub
parent 847b983680
commit 9d8c2c3759
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

169
.github/workflows/pr-status-label.yml vendored Normal file
View file

@ -0,0 +1,169 @@
name: Add "status:"-label to PRs based on current state
on:
pull_request_review:
types: [submitted]
pull_request:
types: [opened]
schedule:
- cron: '0 * * * *' # every hour, on the hour
permissions:
pull-requests: write
jobs:
new_pr:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' && github.event.action == 'opened'
steps:
- name: Label new PR as "status:waiting for review"
uses: actions/github-script@v7
with:
script: |
github.rest.issues.addLabels({
...context.repo,
issue_number: context.payload.pull_request.number,
labels: ['status:waiting for review']
})
review_update:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request_review' && github.event.action == 'submitted'
steps:
- name: Update status labels based on review
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request
const review = context.payload.review
const owner = context.repo.owner
const repo = context.repo.repo
const R = review.user.login // reviewer
const P = pr.user.login // PR author
const O = owner // repo owner (by repo name)
// 1. Remove all "status:[...]" labels
const { data: existingLabels } = await github.rest.issues.listLabelsOnIssue({
owner,
repo,
issue_number: pr.number
})
const statusLabels = existingLabels.filter(l => l.name.startsWith('status:'))
for (const label of statusLabels) {
await github.rest.issues.removeLabel({
owner,
repo,
issue_number: pr.number,
name: label.name
}).catch(() => {}) // ignore if already removed
}
// 2. Determine new label
let newLabel = null
// https://github.com/Reviewable/Reviewable/issues/1163
// other users cannot approve reviewers, requires permissions to actually say `review.state == 'approved'`
const is_approved = review.state === 'approved' || review.body.includes("complete! all files reviewed, all discussions resolved")
if (is_approved && R === O) {
newLabel = 'status:approved' // standard way of approval: owner appoves PR
} else if (is_approved && P === O && R !== O) {
newLabel = 'status:approved' // if owner is author, someone else must approve
} else if (P === R) {
newLabel = 'status:waiting for review'
} else if (P !== R) {
newLabel = 'status:waiting for author'
} else {
core.info('No matching condition for new label.')
core.info(`Review state: ${review.state}, PR author: ${P}, Reviewer: ${R}, Repo owner: ${O}`)
}
if (newLabel) {
await github.rest.issues.addLabels({
owner,
repo,
issue_number: pr.number,
labels: [newLabel]
})
core.info(`Added label: ${newLabel}`)
} else {
core.info('No new status label added.')
}
promote_ready_to_merge:
runs-on: ubuntu-latest
if: github.event_name == 'schedule'
steps:
- name: Promote approved PRs to "ready to merge"
uses: actions/github-script@v7
with:
script: |
const owner = context.repo.owner
const repo = context.repo.repo
const now = new Date()
// 1. Search for open PRs with "status:approved"
const searchQuery = `repo:${owner}/${repo} is:pr is:open label:"status:approved"`
const { data: searchResults } = await github.rest.search.issuesAndPullRequests({ q: searchQuery })
for (const pr of searchResults.items) {
const prNumber = pr.number
// Get full PR info (for timestamps)
const { data: prData } = await github.rest.pulls.get({
owner,
repo,
pull_number: prNumber
})
const createdAt = new Date(prData.created_at)
const hoursSinceCreation = (now - createdAt) / (1000 * 60 * 60)
if (hoursSinceCreation < 24) {
core.info(`Skipping #${prNumber} (created ${hoursSinceCreation.toFixed(1)}h ago)`)
continue
}
// Find the last "status:" label change (via timeline events)
const { data: events } = await github.rest.issues.listEvents({
owner,
repo,
issue_number: prNumber,
per_page: 100
})
const lastStatusChange = events
.filter(e => e.event === 'labeled' || e.event === 'unlabeled')
.filter(e => e.label && e.label.name.startsWith('status:'))
.sort((a, b) => new Date(b.created_at) - new Date(a.created_at))[0]
if (lastStatusChange) {
const hoursSinceStatusChange = (now - new Date(lastStatusChange.created_at)) / (1000 * 60 * 60)
if (hoursSinceStatusChange < 12) {
core.info(`Skipping #${prNumber} (status changed ${hoursSinceStatusChange.toFixed(1)}h ago)`)
continue
}
}
// 2. Conditions met → remove "status:approved", add "status:ready to merge"
try {
await github.rest.issues.removeLabel({
owner,
repo,
issue_number: prNumber,
name: 'status:approved'
}).catch(() => {})
await github.rest.issues.addLabels({
owner,
repo,
issue_number: prNumber,
labels: ['status:ready to merge']
})
core.info(`Promoted #${prNumber} to status:ready to merge`)
} catch (err) {
core.warning(`Failed to update #${prNumber}: ${err.message}`)
}
}