Skip to content

Commit 71afd86

Browse files
committed
chore(workflows): add codeowners slack action
1 parent e322c87 commit 71afd86

3 files changed

Lines changed: 240 additions & 0 deletions

File tree

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
name: CODEOWNERS Approved - Slack Notification
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
skip-draft:
7+
description: "Skip notification for draft PRs"
8+
required: false
9+
type: boolean
10+
default: true
11+
slack-message-prefix:
12+
description: "Custom prefix for Slack message (emoji + text)"
13+
required: false
14+
type: string
15+
default: ":white_check_mark: CODEOWNERS gate satisfied"
16+
secrets:
17+
SLACK_RELEASE_CHANGELOG_WEBHOOK:
18+
description: "Slack incoming webhook URL"
19+
required: true
20+
GH_TOKEN:
21+
description: "GitHub token with PR read access (uses GITHUB_TOKEN if not provided)"
22+
required: false
23+
24+
jobs:
25+
notify:
26+
name: Notify Slack on CODEOWNERS Approval
27+
# Only run when the submitted review is an approval
28+
if: github.event.review.state == 'approved'
29+
runs-on: ubuntu-latest
30+
31+
steps:
32+
- name: Fetch PR decision and check for duplicate notification
33+
id: pr
34+
uses: actions/github-script@v7
35+
with:
36+
github-token: ${{ secrets.GH_TOKEN || github.token }}
37+
script: |
38+
const { owner, repo } = context.repo;
39+
const prNumber = context.payload.pull_request.number;
40+
41+
const query = `query($owner:String!, $repo:String!, $number:Int!) {
42+
repository(owner:$owner, name:$repo) {
43+
pullRequest(number:$number) {
44+
number
45+
title
46+
url
47+
isDraft
48+
headRefOid
49+
baseRefName
50+
reviewDecision
51+
}
52+
}
53+
}`;
54+
55+
const data = await github.graphql(query, { owner, repo, number: prNumber });
56+
const pr = data.repository.pullRequest;
57+
58+
core.setOutput("number", String(pr.number));
59+
core.setOutput("title", pr.title);
60+
core.setOutput("url", pr.url);
61+
core.setOutput("is_draft", pr.isDraft ? "true" : "false");
62+
core.setOutput("head_sha", pr.headRefOid);
63+
core.setOutput("base", pr.baseRefName);
64+
core.setOutput("review_decision", pr.reviewDecision ?? "");
65+
66+
// Dedupe marker per head SHA (so new commits can re-trigger notification)
67+
const marker = `<!-- codeowners-approved:${pr.headRefOid} -->`;
68+
core.setOutput("marker", marker);
69+
70+
// Check if we've already notified for this SHA
71+
const comments = await github.paginate(github.rest.issues.listComments, {
72+
owner,
73+
repo,
74+
issue_number: prNumber,
75+
per_page: 100,
76+
});
77+
78+
const alreadyNotified = comments.some(c => (c.body || "").includes(marker));
79+
core.setOutput("already_notified", alreadyNotified ? "true" : "false");
80+
81+
// Log for debugging
82+
console.log(`PR #${pr.number}: draft=${pr.isDraft}, decision=${pr.reviewDecision}, notified=${alreadyNotified}`);
83+
84+
- name: Skip notification (conditions not met)
85+
if: |
86+
(inputs.skip-draft && steps.pr.outputs.is_draft == 'true') ||
87+
steps.pr.outputs.review_decision != 'APPROVED' ||
88+
steps.pr.outputs.already_notified == 'true'
89+
run: |
90+
echo "### Notification Skipped" >> $GITHUB_STEP_SUMMARY
91+
echo "" >> $GITHUB_STEP_SUMMARY
92+
echo "| Condition | Value |" >> $GITHUB_STEP_SUMMARY
93+
echo "|-----------|-------|" >> $GITHUB_STEP_SUMMARY
94+
echo "| Is Draft | ${{ steps.pr.outputs.is_draft }} |" >> $GITHUB_STEP_SUMMARY
95+
echo "| Review Decision | ${{ steps.pr.outputs.review_decision }} |" >> $GITHUB_STEP_SUMMARY
96+
echo "| Already Notified | ${{ steps.pr.outputs.already_notified }} |" >> $GITHUB_STEP_SUMMARY
97+
98+
- name: Post to Slack
99+
if: |
100+
(inputs.skip-draft == false || steps.pr.outputs.is_draft != 'true') &&
101+
steps.pr.outputs.review_decision == 'APPROVED' &&
102+
steps.pr.outputs.already_notified != 'true'
103+
uses: slackapi/slack-github-action@v2
104+
with:
105+
payload: |
106+
{
107+
"text": "${{ inputs.slack-message-prefix }}\n<${{ steps.pr.outputs.url }}|PR #${{ steps.pr.outputs.number }} — ${{ steps.pr.outputs.title }}>\nBase: `${{ steps.pr.outputs.base }}` Head: `${{ steps.pr.outputs.head_sha }}`\nApproved by: `${{ github.event.review.user.login }}`"
108+
}
109+
env:
110+
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_RELEASE_CHANGELOG_WEBHOOK }}
111+
112+
- name: Add marker comment (dedupe for this SHA)
113+
if: |
114+
(inputs.skip-draft == false || steps.pr.outputs.is_draft != 'true') &&
115+
steps.pr.outputs.review_decision == 'APPROVED' &&
116+
steps.pr.outputs.already_notified != 'true'
117+
uses: actions/github-script@v7
118+
with:
119+
github-token: ${{ secrets.GH_TOKEN || github.token }}
120+
script: |
121+
const { owner, repo } = context.repo;
122+
const prNumber = context.payload.pull_request.number;
123+
const marker = `${{ steps.pr.outputs.marker }}`;
124+
const headSha = `${{ steps.pr.outputs.head_sha }}`;
125+
126+
await github.rest.issues.createComment({
127+
owner,
128+
repo,
129+
issue_number: prNumber,
130+
body: `${marker}\n:bell: Slack notified for head SHA \`${headSha}\`.`,
131+
});
132+
133+
- name: Summary (notification sent)
134+
if: |
135+
(inputs.skip-draft == false || steps.pr.outputs.is_draft != 'true') &&
136+
steps.pr.outputs.review_decision == 'APPROVED' &&
137+
steps.pr.outputs.already_notified != 'true'
138+
run: |
139+
echo "### Slack Notification Sent" >> $GITHUB_STEP_SUMMARY
140+
echo "" >> $GITHUB_STEP_SUMMARY
141+
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
142+
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
143+
echo "| PR | [#${{ steps.pr.outputs.number }}](${{ steps.pr.outputs.url }}) |" >> $GITHUB_STEP_SUMMARY
144+
echo "| Title | ${{ steps.pr.outputs.title }} |" >> $GITHUB_STEP_SUMMARY
145+
echo "| Base | \`${{ steps.pr.outputs.base }}\` |" >> $GITHUB_STEP_SUMMARY
146+
echo "| Head SHA | \`${{ steps.pr.outputs.head_sha }}\` |" >> $GITHUB_STEP_SUMMARY
147+
echo "| Approved By | @${{ github.event.review.user.login }} |" >> $GITHUB_STEP_SUMMARY

README.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,42 @@ jobs:
8282
SLACK_RELEASE_CHANGELOG_WEBHOOK: ${{ secrets.SLACK_RELEASE_CHANGELOG_WEBHOOK }}
8383
```
8484

85+
#### For CODEOWNERS Approval Events
86+
87+
Create `.github/workflows/codeowners-approved.yml`:
88+
89+
```yaml
90+
name: CODEOWNERS Approved - Slack
91+
92+
on:
93+
pull_request_review:
94+
types: [submitted]
95+
96+
permissions:
97+
pull-requests: read
98+
issues: write
99+
contents: read
100+
101+
jobs:
102+
notify:
103+
uses: dualentry/github-actions/.github/workflows/codeowners-approved-slack.yml@main
104+
# with:
105+
# skip-draft: true # Skip draft PRs (default: true)
106+
# slack-message-prefix: ":rocket: Ready for merge" # Custom message prefix
107+
secrets:
108+
SLACK_RELEASE_CHANGELOG_WEBHOOK: ${{ secrets.SLACK_RELEASE_CHANGELOG_WEBHOOK }}
109+
```
110+
111+
**Prerequisites for CODEOWNERS approval notifications:**
112+
113+
1. Enable branch protection on your target branch with:
114+
- Require a pull request before merging
115+
- Require approvals (>= 1)
116+
- **Require review from Code Owners** (this is key!)
117+
2. Create a `CODEOWNERS` file in your repo
118+
3. Add `SLACK_RELEASE_CHANGELOG_WEBHOOK` secret (Slack incoming webhook)
119+
4. **Important**: This workflow file must exist on the default branch for the `pull_request_review` trigger to work
120+
85121
### 2. Workflow Inputs
86122

87123
#### linear-slack-pr-opened.yml
@@ -99,6 +135,18 @@ jobs:
99135
| `environment` | Environment name for Slack message | No | `production` |
100136
| `update-linear-status` | Whether to update Linear ticket status to Done | No | `true` |
101137

138+
#### codeowners-approved-slack.yml
139+
140+
| Input | Description | Required | Default |
141+
|-------|-------------|----------|---------|
142+
| `skip-draft` | Skip notification for draft PRs | No | `true` |
143+
| `slack-message-prefix` | Custom prefix for Slack message (emoji + text) | No | `:white_check_mark: CODEOWNERS gate satisfied` |
144+
145+
| Secret | Description | Required |
146+
|--------|-------------|----------|
147+
| `SLACK_RELEASE_CHANGELOG_WEBHOOK` | Slack incoming webhook URL | Yes |
148+
| `GH_TOKEN` | GitHub token with PR read access | No (uses `GITHUB_TOKEN`) |
149+
102150
### 3. Example Workflow for Dev Branch
103151

104152
If you want to trigger notifications for `dev` branch as well:
@@ -158,6 +206,21 @@ Environment: `production`
158206
• Add onboarding checklist visibility endpoints (DEV-8282)
159207
```
160208
209+
### CODEOWNERS Approved
210+
211+
```
212+
✅ CODEOWNERS gate satisfied
213+
PR #123 — feat: add new feature
214+
Base: `main` Head: `abc1234...`
215+
Approved by: `reviewer-username`
216+
```
217+
218+
**Behavior notes:**
219+
220+
- Only triggers when the `reviewDecision` becomes `APPROVED` (i.e., all required CODEOWNERS have approved)
221+
- De-duplicates per head SHA: if you push new commits and get re-approved, it will notify again
222+
- Adds a hidden marker comment to track which SHA was notified
223+
161224
## Scripts
162225
163226
The repository includes three main scripts:

examples/codeowners-approved.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Copy this file to .github/workflows/codeowners-approved.yml in your repository
2+
#
3+
# IMPORTANT: This workflow file must exist on the default branch for the
4+
# pull_request_review trigger to work. Merge this to main/master first.
5+
#
6+
# Prerequisites:
7+
# 1. Enable branch protection on your target branch with:
8+
# - Require a pull request before merging
9+
# - Require approvals (>= 1)
10+
# - Require review from Code Owners
11+
# 2. Create a CODEOWNERS file in your repo
12+
# 3. Add SLACK_RELEASE_CHANGELOG_WEBHOOK secret (Slack incoming webhook)
13+
14+
name: CODEOWNERS Verification Step
15+
16+
on:
17+
pull_request_review:
18+
types: [submitted]
19+
20+
# Minimum permissions required
21+
permissions:
22+
pull-requests: read
23+
issues: write
24+
contents: read
25+
26+
jobs:
27+
notify:
28+
uses: dualentry/github-actions/.github/workflows/codeowners-approved-slack.yml@main
29+
secrets:
30+
SLACK_RELEASE_CHANGELOG_WEBHOOK: ${{ secrets.SLACK_RELEASE_CHANGELOG_WEBHOOK }}

0 commit comments

Comments
 (0)