mirror of
https://github.com/Lissy93/awesome-privacy.git
synced 2026-03-11 08:55:33 +00:00
Add workflow to notify maintainer (me) when pr needs reivew
Some checks are pending
📚 Inserts Awesome Privacy into README / build (push) Waiting to run
Some checks are pending
📚 Inserts Awesome Privacy into README / build (push) Waiting to run
This commit is contained in:
parent
259314537a
commit
f95a41c08c
2 changed files with 177 additions and 0 deletions
87
.github/workflows/pr-comment.yml
vendored
87
.github/workflows/pr-comment.yml
vendored
|
|
@ -4,9 +4,12 @@ on:
|
|||
workflow_run:
|
||||
workflows: ["PR Check"]
|
||||
types: [completed]
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
checks: read
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
|
|
@ -132,3 +135,87 @@ jobs:
|
|||
});
|
||||
console.log('Updated bot comment.');
|
||||
}
|
||||
|
||||
review-notify:
|
||||
name: Notify maintainer
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'pull_request_review'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Fetch review and CI context
|
||||
id: context
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.BOT_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const pr = context.payload.pull_request;
|
||||
const { owner, repo } = context.repo;
|
||||
|
||||
// Fetch reviews — extract user login and state
|
||||
const { data: reviews } = await github.rest.pulls.listReviews({
|
||||
owner, repo, pull_number: pr.number, per_page: 100,
|
||||
});
|
||||
const reviewData = reviews.map(r => ({
|
||||
user: r.user.login,
|
||||
state: r.state,
|
||||
}));
|
||||
|
||||
// Fetch check runs for the PR head SHA
|
||||
const { data: { check_runs } } = await github.rest.checks.listForRef({
|
||||
owner, repo, ref: pr.head.sha, per_page: 100,
|
||||
});
|
||||
const checkData = check_runs.map(cr => ({
|
||||
status: cr.status,
|
||||
conclusion: cr.conclusion,
|
||||
}));
|
||||
|
||||
// Check if we already posted the notification
|
||||
const marker = '<!-- pr-review-ready -->';
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner, repo, issue_number: pr.number, per_page: 100,
|
||||
});
|
||||
const alreadyNotified = comments.some(c => c.body.includes(marker));
|
||||
|
||||
// Write data for Python
|
||||
fs.mkdirSync('pr-meta', { recursive: true });
|
||||
fs.writeFileSync('pr-meta/reviews.json', JSON.stringify(reviewData));
|
||||
fs.writeFileSync('pr-meta/check-runs.json', JSON.stringify(checkData));
|
||||
fs.writeFileSync('pr-meta/already-notified.txt', String(alreadyNotified));
|
||||
|
||||
core.setOutput('pr_number', pr.number);
|
||||
|
||||
- name: Check review readiness
|
||||
run: python lib/checks/check-review-ready.py
|
||||
|
||||
- name: Post notification
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.BOT_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const actionFile = 'pr-meta/action.txt';
|
||||
if (!fs.existsSync(actionFile)) return;
|
||||
|
||||
const action = fs.readFileSync(actionFile, 'utf8').trim();
|
||||
if (action !== 'notify') {
|
||||
console.log('Not ready for review — skipping.');
|
||||
return;
|
||||
}
|
||||
|
||||
const body = [
|
||||
'<!-- pr-review-ready -->',
|
||||
'This PR is now ready to be merged, pending maintainer review. All checks are passing and it has been peer-reviewed.',
|
||||
'',
|
||||
'@Lissy93 - Please evaluate, and either merge or leave feedback.',
|
||||
].join('\n');
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: parseInt('${{ steps.context.outputs.pr_number }}'),
|
||||
body,
|
||||
});
|
||||
console.log('Posted maintainer notification.');
|
||||
|
|
|
|||
90
lib/checks/check-review-ready.py
Normal file
90
lib/checks/check-review-ready.py
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
"""Decides whether a PR is ready for maintainer review.
|
||||
|
||||
Conditions: 2+ approvals from external contributors AND all CI checks passing.
|
||||
|
||||
Reads:
|
||||
pr-meta/reviews.json — array of {user, state} from GitHub API
|
||||
pr-meta/check-runs.json — array of {status, conclusion} from GitHub API
|
||||
pr-meta/already-notified.txt — "true" if notification comment already exists
|
||||
|
||||
Writes:
|
||||
pr-meta/action.txt — "notify" or "skip"
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
WORK_DIR = "pr-meta"
|
||||
MAINTAINER = "Lissy93"
|
||||
REQUIRED_APPROVALS = 2
|
||||
PASSING_CONCLUSIONS = {"success", "skipped", "neutral"}
|
||||
|
||||
|
||||
def read_json(filename):
|
||||
"""Load a JSON file from the work directory, or empty list on error."""
|
||||
try:
|
||||
with open(os.path.join(WORK_DIR, filename)) as f:
|
||||
return json.load(f)
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
def count_external_approvals(reviews):
|
||||
"""Count unique non-maintainer users who approved."""
|
||||
approvers = {
|
||||
r["user"]
|
||||
for r in reviews
|
||||
if r.get("state") == "APPROVED"
|
||||
and r.get("user", "").lower() != MAINTAINER.lower()
|
||||
}
|
||||
return len(approvers)
|
||||
|
||||
|
||||
def all_checks_passing(check_runs):
|
||||
"""Return True if every check run completed successfully."""
|
||||
if not check_runs:
|
||||
return False
|
||||
return all(
|
||||
cr.get("status") == "completed"
|
||||
and cr.get("conclusion") in PASSING_CONCLUSIONS
|
||||
for cr in check_runs
|
||||
)
|
||||
|
||||
|
||||
def already_notified():
|
||||
"""Return True if the notification comment already exists on the PR."""
|
||||
try:
|
||||
with open(os.path.join(WORK_DIR, "already-notified.txt")) as f:
|
||||
return f.read().strip().lower() == "true"
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def write_action(action):
|
||||
os.makedirs(WORK_DIR, exist_ok=True)
|
||||
with open(os.path.join(WORK_DIR, "action.txt"), "w") as f:
|
||||
f.write(action)
|
||||
|
||||
|
||||
def main():
|
||||
if already_notified():
|
||||
write_action("skip")
|
||||
return
|
||||
|
||||
reviews = read_json("reviews.json")
|
||||
approvals = count_external_approvals(reviews)
|
||||
|
||||
if approvals < REQUIRED_APPROVALS:
|
||||
write_action("skip")
|
||||
return
|
||||
|
||||
check_runs = read_json("check-runs.json")
|
||||
if not all_checks_passing(check_runs):
|
||||
write_action("skip")
|
||||
return
|
||||
|
||||
write_action("notify")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in a new issue