Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 93 additions & 32 deletions .github/workflows/pr_approval_check.yml
Original file line number Diff line number Diff line change
@@ -1,45 +1,106 @@
name: PR Approval Check
name: Review Checks

on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
types: [opened, synchronize, reopened]
pull_request_review:
types: [submitted]
types: [submitted, dismissed]
merge_group:

permissions:
contents: read
pull-requests: read
statuses: write

concurrency:
group: pr-approval-check-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true

jobs:
check-approvals:
publish-approval-status:
name: Set approval status
runs-on: ubuntu-latest
# Skip this check if PR is in draft state
if: github.event.pull_request.draft == false

steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Evaluate and publish approval status
uses: actions/github-script@v7
with:
fetch-depth: 0

- name: Check PR approvals
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Check if PR author is clockwork-labs-bot
if [[ "${{ github.event.pull_request.user.login }}" != "clockwork-labs-bot" ]]; then
echo "PR opened by ${{ github.event.pull_request.user.login }}, not clockwork-labs-bot. Skipping check."
exit 0
fi

PR_NUMBER="${{ github.event.pull_request.number }}"
# Get approval count
APPROVALS=$(gh pr view $PR_NUMBER --json reviews -q '.reviews | map(select(.state == "APPROVED")) | length')

echo "PR has $APPROVALS approvals"

if [[ $APPROVALS -lt 2 ]]; then
echo "Error: PRs from clockwork-labs-bot require at least 2 approvals"
exit 1
else
echo "PR has the required number of approvals"
fi
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const contextName = "PR approval check";

let targetSha;
let state;
let description;

if (context.eventName === "merge_group") {
targetSha = process.env.GITHUB_SHA;
state = "success";
description = "Merge group entry; approvals already satisfied";
} else {
const pr = context.payload.pull_request;
targetSha = pr.head.sha;

if (pr.user.login !== "clockwork-labs-bot") {
state = "success";
description = "PR author is not clockwork-labs-bot";
} else {
const result = await github.graphql(
`
query($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $number) {
latestOpinionatedReviews(first: 100, writersOnly: true) {
nodes {
state
author {
login
}
}
}
}
}
}
`,
{
owner: context.repo.owner,
repo: context.repo.repo,
number: pr.number,
}
);

const effectiveApprovers =
result.repository.pullRequest.latestOpinionatedReviews.nodes
.filter((review) => review.state === "APPROVED")
.map((review) => review.author?.login)
.filter(Boolean);

core.info(
`Latest effective approvers (${effectiveApprovers.length}): ${effectiveApprovers.join(", ")}`
);

if (effectiveApprovers.length < 2) {
state = "failure";
description = "PRs from clockwork-labs-bot require at least 2 approvals";
} else {
state = "success";
description = "PR has the required number of approvals";
}
}
}

core.info(`Publishing status ${state} for ${targetSha}: ${description}`);

// We need to set a separate commit status for this, because it runs on both
// pull_request and pull_request_review events. If we don't set an explicit context,
// what happens is that there are sometimes two separate statuses on the same commit -
// one from each event type. This leads to weird cases where one copy of the check is failed,
// and the other is successful, and the failed one blocks the PR from merging.
await github.rest.repos.createCommitStatus({
owner: context.repo.owner,
repo: context.repo.repo,
sha: targetSha,
state,
context: contextName,
description,
});
Loading