forked from JhaSourav07/commitpulse
-
Notifications
You must be signed in to change notification settings - Fork 0
290 lines (250 loc) · 11.1 KB
/
Copy pathci-failure-notifier.yml
File metadata and controls
290 lines (250 loc) · 11.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
name: CI Failure Notifier
on:
workflow_run:
workflows: ['CI Pipeline']
types: [completed]
workflow_dispatch: # Manually scan ALL open PRs from the Actions tab
permissions:
issues: write
pull-requests: write
jobs:
notify-ci-failure:
# Act when CI completes and was triggered by a pull_request or push event
if: github.event.workflow_run.event == 'pull_request' || github.event.workflow_run.event == 'push'
runs-on: ubuntu-latest
steps:
- name: Notify CI Failure/Success
uses: actions/github-script@v7
with:
script: |
const label = 'status:blocked';
const run = context.payload.workflow_run;
const conclusion = run.conclusion;
let prNumber = null;
let pr = null;
// 1. Try retrieving from run.pull_requests
const prs = run.pull_requests;
if (prs && prs.length > 0) {
pr = prs[0];
prNumber = pr.number;
core.info(`Found PR #${prNumber} directly in workflow run payload.`);
} else {
core.info(`workflow_run.pull_requests is empty. Falling back to search open PRs...`);
// 2. Fallback: search open PRs for matching head SHA or head branch
const { data: openPRs } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
per_page: 100,
});
// Try finding by exact SHA match
let matchingPR = openPRs.find(p => p.head.sha === run.head_sha);
// If not found by SHA, try matching by head branch name
if (!matchingPR && run.head_branch) {
matchingPR = openPRs.find(p => p.head.ref === run.head_branch);
}
if (matchingPR) {
pr = matchingPR;
prNumber = matchingPR.number;
core.info(`Resolved PR #${prNumber} using open PR search fallback (SHA/branch match).`);
}
}
if (!prNumber || !pr) {
core.info('No open pull request associated with this workflow run. Skipping.');
return;
}
// Fetch full PR data to get author and current labels
const { data: fullPR } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
});
const author = fullPR.user.login;
const currentLabels = fullPR.labels.map(l => l.name);
if (conclusion === 'failure') {
core.info(`PR #${prNumber}: CI failed. Ensuring label '${label}' and comment are present.`);
// Ensure the label exists in the repo
try {
await github.rest.issues.getLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: label,
});
} catch {
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: label,
color: 'dc2626',
description: 'This PR is blocked due to a failing CI check.',
});
}
// Apply the label if not already present
if (!currentLabels.includes(label)) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
labels: [label],
});
}
// Check for an existing failure comment to avoid spamming
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
per_page: 100,
});
const alreadyCommented = comments.some(c =>
c.user?.login === 'github-actions[bot]' &&
c.body?.includes('CI Pipeline is failing')
);
if (!alreadyCommented) {
const runUrl = run.html_url;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: `🚨 Hey @${author}, the **CI Pipeline is failing** on this PR and it has been marked as \`status:blocked\`.
Please fix the issues before this can be reviewed. Here's how:
**1. Run checks locally before pushing:**
\`\`\`bash
npm run format:check # Check Prettier formatting
npm run lint # Run ESLint
npm run typecheck # TypeScript type check
npm run test # Run unit tests (Vitest)
npm run build # Verify production build passes
\`\`\`
**2. Auto-fix common issues:**
\`\`\`bash
npm run format # Auto-fix formatting with Prettier
npm run lint -- --fix # Auto-fix lint errors where possible
\`\`\`
**3. Check the full failure log here:**
👉 [View CI Run](${runUrl})
Once you push a fix and the CI passes, the \`status:blocked\` label will be removed automatically. 💪`,
});
}
} else if (conclusion === 'success') {
core.info(`PR #${prNumber}: CI succeeded. Removing '${label}' label if present.`);
const hasLabel = currentLabels.includes(label);
if (hasLabel) {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
name: label,
});
core.info(`Removed '${label}' from PR #${prNumber} — CI is now passing.`);
}
}
# ── Manual scan: runs when triggered from the Actions tab ─────────────────
scan-all-prs:
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- name: Scan all open PRs for CI failures
uses: actions/github-script@v7
with:
script: |
const label = 'status:blocked';
// Ensure the label exists
try {
await github.rest.issues.getLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: label,
});
} catch {
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: label,
color: 'dc2626',
description: 'This PR is blocked due to a failing CI check.',
});
}
// Fetch all open PRs
const { data: openPRs } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
per_page: 100,
});
core.info(`Scanning ${openPRs.length} open PRs...`);
for (const pr of openPRs) {
// Get the latest CI Pipeline run for this PR's HEAD commit
const { data: runsData } = await github.rest.actions.listWorkflowRunsForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
head_sha: pr.head.sha,
per_page: 20,
});
// Find the most recent completed run of our CI pipeline
const ciRun = runsData.workflow_runs
.filter(r => r.name === 'CI Pipeline' && r.status === 'completed')
.sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at))[0];
const currentLabels = pr.labels.map(l => l.name);
const hasBlockedLabel = currentLabels.includes(label);
if (!ciRun) {
core.info(`PR #${pr.number}: No completed CI run found. Skipping.`);
continue;
}
core.info(`PR #${pr.number} (@${pr.user.login}): CI conclusion = ${ciRun.conclusion}`);
if (ciRun.conclusion === 'failure') {
// Apply label
if (!hasBlockedLabel) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels: [label],
});
}
// Avoid duplicate comments
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
per_page: 100,
});
const alreadyCommented = comments.some(c =>
c.user?.login === 'github-actions[bot]' &&
c.body?.includes('CI Pipeline is failing')
);
if (!alreadyCommented) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: `🚨 Hey @${pr.user.login}, the **CI Pipeline is failing** on this PR and it has been marked as \`status:blocked\`.
Please fix the issues before this can be reviewed. Here's how:
**1. Run checks locally before pushing:**
\`\`\`bash
npm run format:check # Check Prettier formatting
npm run lint # Run ESLint
npm run typecheck # TypeScript type check
npm run test # Run unit tests (Vitest)
npm run build # Verify production build passes
\`\`\`
**2. Auto-fix common issues:**
\`\`\`bash
npm run format # Auto-fix formatting with Prettier
npm run lint -- --fix # Auto-fix lint errors where possible
\`\`\`
**3. Check the full failure log here:**
👉 [View CI Run](${ciRun.html_url})
Once you push a fix and the CI passes, the \`status:blocked\` label will be removed automatically. 💪`,
});
}
} else if (ciRun.conclusion === 'success' && hasBlockedLabel) {
// CI is now passing — remove the stale blocked label
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
name: label,
});
core.info(`PR #${pr.number}: CI passing — removed '${label}'.`);
}
}