Skip to content

Commit 07510f6

Browse files
heiskrCopilot
andauthored
🚰 Add missing failure-issue alerts to scheduled workflows (#60920)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent c3ff656 commit 07510f6

5 files changed

Lines changed: 78 additions & 27 deletions

File tree

‎.github/workflows/needs-sme-stale-check.yaml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,8 @@ jobs:
4141
with:
4242
slack_channel_id: ${{ secrets.DOCS_ALERTS_SLACK_CHANNEL_ID }}
4343
slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }}
44+
45+
- uses: ./.github/actions/create-workflow-failure-issue
46+
if: ${{ failure() }}
47+
with:
48+
token: ${{ secrets.DOCS_BOT_PAT_BASE }}

‎.github/workflows/no-response.yaml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,8 @@ jobs:
6363
with:
6464
slack_channel_id: ${{ secrets.DOCS_ALERTS_SLACK_CHANNEL_ID }}
6565
slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }}
66+
67+
- uses: ./.github/actions/create-workflow-failure-issue
68+
if: ${{ failure() }}
69+
with:
70+
token: ${{ secrets.DOCS_BOT_PAT_BASE }}

‎.github/workflows/notify-about-deployment.yml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,8 @@ jobs:
7575
with:
7676
slack_channel_id: ${{ secrets.DOCS_ALERTS_SLACK_CHANNEL_ID }}
7777
slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }}
78+
79+
- uses: ./.github/actions/create-workflow-failure-issue
80+
if: ${{ failure() }}
81+
with:
82+
token: ${{ secrets.DOCS_BOT_PAT_BASE }}

‎.github/workflows/triage-stale-check.yml‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ jobs:
5252
slack_channel_id: ${{ secrets.DOCS_ALERTS_SLACK_CHANNEL_ID }}
5353
slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }}
5454

55+
- uses: ./.github/actions/create-workflow-failure-issue
56+
if: ${{ failure() }}
57+
with:
58+
token: ${{ secrets.DOCS_BOT_PAT_BASE }}
59+
5560
stale_staff:
5661
name: Remind staff about PRs waiting for review
5762
if: github.repository == 'github/docs'
@@ -82,3 +87,8 @@ jobs:
8287
with:
8388
slack_channel_id: ${{ secrets.DOCS_ALERTS_SLACK_CHANNEL_ID }}
8489
slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }}
90+
91+
- uses: ./.github/actions/create-workflow-failure-issue
92+
if: ${{ failure() }}
93+
with:
94+
token: ${{ secrets.DOCS_BOT_PAT_BASE }}

‎src/workflows/tests/actions-workflows.ts‎

Lines changed: 53 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -68,19 +68,44 @@ const allUsedActions = chain(workflows)
6868

6969
const scheduledWorkflows = workflows.filter(({ data }) => data.on.schedule)
7070

71-
const alertWorkflows = workflows
72-
// Only include jobs running on docs-internal
73-
.filter(({ data }) =>
74-
Object.values(data.jobs)
75-
.map((job) => job.if)
76-
.toString()
77-
.includes('docs-internal'),
78-
)
79-
// Require slack alerts on workflows that aren't actively watched at time of run
80-
.filter(({ data }) => data.on.schedule || data.on.push || data.on.issues || data.on.issue_comment)
81-
// Not including
82-
// - premerge workflows: pull_request, pull_request_target, pull_request_review, merge_group
83-
// - adhoc workflows: workflow_dispatch, workflow_run, workflow_call, repository_dispatch
71+
// Triggers where a workflow runs without a human actively watching and
72+
// therefore needs explicit failure reporting (Slack + issue). Attended
73+
// triggers (pull_request*, workflow_dispatch, workflow_call, merge_group)
74+
// are intentionally excluded: the person who triggered the run sees the
75+
// result directly.
76+
//
77+
// `issues` and `issue_comment` are only considered unattended for jobs
78+
// running in docs-internal itself. When a job is scoped to the public
79+
// github/docs fork via `if: github.repository == 'github/docs'`, those
80+
// triggers fire from external reporters/commenters, and the issue or
81+
// comment itself is the natural failure surface — piling on automated
82+
// alert-issues there is duplicative and noisy.
83+
const ALWAYS_UNATTENDED_TRIGGERS = ['schedule', 'workflow_run', 'repository_dispatch', 'push']
84+
const DOCS_INTERNAL_ONLY_UNATTENDED_TRIGGERS = ['issues', 'issue_comment']
85+
86+
function jobIsPublicDocsScoped(job: WorkflowJob): boolean {
87+
return typeof job.if === 'string' && /github\.repository\s*==\s*['"]github\/docs['"]/.test(job.if)
88+
}
89+
90+
function jobRequiresFailureAlerts(workflow: WorkflowMeta, job: WorkflowJob): boolean {
91+
const triggers = workflow.data.on || {}
92+
if (ALWAYS_UNATTENDED_TRIGGERS.some((t) => (triggers as Record<string, unknown>)[t])) {
93+
return true
94+
}
95+
if (
96+
!jobIsPublicDocsScoped(job) &&
97+
DOCS_INTERNAL_ONLY_UNATTENDED_TRIGGERS.some((t) => (triggers as Record<string, unknown>)[t])
98+
) {
99+
return true
100+
}
101+
return false
102+
}
103+
104+
// Workflows where at least one job requires failure alerts — used to drive
105+
// the parameterised tests below. Per-job filtering happens inside each test.
106+
const alertWorkflows = workflows.filter(({ data }) =>
107+
Object.values(data.jobs).some((job) => job.steps),
108+
)
84109
// to generate list, console.log(new Set(workflows.map(({ data }) => Object.keys(data.on)).flat()))
85110

86111
const dailyWorkflows = scheduledWorkflows.filter(({ data }) =>
@@ -151,23 +176,22 @@ describe('GitHub Actions workflows', () => {
151176
}
152177
})
153178

154-
test.each(alertWorkflows)(
155-
'scheduled workflows slack alert on fail $filename',
156-
({ filename, data }) => {
157-
for (const [name, job] of Object.entries(data.jobs)) {
158-
if (
159-
!job.steps.find((step: WorkflowStep) => step.uses === './.github/actions/slack-alert')
160-
) {
161-
throw new Error(`Job ${filename} # ${name} missing slack alert on fail`)
162-
}
179+
test.each(alertWorkflows)('unattended workflows slack alert on fail $filename', (workflow) => {
180+
const { filename, data } = workflow
181+
for (const [name, job] of Object.entries(data.jobs)) {
182+
if (!jobRequiresFailureAlerts(workflow, job)) continue
183+
if (!job.steps.find((step: WorkflowStep) => step.uses === './.github/actions/slack-alert')) {
184+
throw new Error(`Job ${filename} # ${name} missing slack alert on fail`)
163185
}
164-
},
165-
)
186+
}
187+
})
166188

167189
test.each(alertWorkflows)(
168-
'scheduled workflows create failure issue on fail $filename',
169-
({ filename, data }) => {
190+
'unattended workflows create failure issue on fail $filename',
191+
(workflow) => {
192+
const { filename, data } = workflow
170193
for (const [name, job] of Object.entries(data.jobs)) {
194+
if (!jobRequiresFailureAlerts(workflow, job)) continue
171195
if (
172196
!job.steps.find(
173197
(step: WorkflowStep) => step.uses === './.github/actions/create-workflow-failure-issue',
@@ -181,8 +205,10 @@ describe('GitHub Actions workflows', () => {
181205

182206
test.each(alertWorkflows)(
183207
'performs a checkout before calling composite action $filename',
184-
({ filename, data }) => {
208+
(workflow) => {
209+
const { filename, data } = workflow
185210
for (const [name, job] of Object.entries(data.jobs)) {
211+
if (!jobRequiresFailureAlerts(workflow, job)) continue
186212
if (!job.steps.find((step: WorkflowStep) => checkoutRegexp.test(step.uses || ''))) {
187213
throw new Error(
188214
`Job ${filename} # ${name} missing a checkout before calling the composite action`,

0 commit comments

Comments
 (0)