11name : Discord notifications
22
33on :
4- pull_request :
5- types : [closed]
4+ push :
5+ branches :
6+ - master
7+
8+ permissions :
9+ contents : read
10+ pull-requests : read
611
712jobs :
13+ resolvePush :
14+ runs-on : ubuntu-latest
15+ outputs :
16+ is_pr_merge : ${{ steps.resolve.outputs.is_pr_merge }}
17+ is_external : ${{ steps.resolve.outputs.is_external }}
18+ pr_author : ${{ steps.resolve.outputs.pr_author }}
19+ pr_head_ref : ${{ steps.resolve.outputs.pr_head_ref }}
20+ pr_head_repo : ${{ steps.resolve.outputs.pr_head_repo }}
21+ pr_number : ${{ steps.resolve.outputs.pr_number }}
22+ pr_title : ${{ steps.resolve.outputs.pr_title }}
23+ pr_url : ${{ steps.resolve.outputs.pr_url }}
24+ steps :
25+ - name : Resolve pushed commit
26+ id : resolve
27+ uses : actions/github-script@v7
28+ with :
29+ script : |
30+ const { owner, repo } = context.repo;
31+ const sha = context.sha;
32+
33+ async function findAssociatedPRs() {
34+ // It seems that when we do this too promptly after a PR merge then the data isn't there, so we have a retry loop.
35+ for (let attempt = 1; attempt <= 3; attempt++) {
36+ core.info('Looking for associated PRs..');
37+ const prs = await github.paginate(github.rest.repos.listPullRequestsAssociatedWithCommit, {
38+ owner,
39+ repo,
40+ commit_sha: sha,
41+ });
42+
43+ if (prs.length > 0) {
44+ return prs;
45+ }
46+
47+ core.info('No associated PRs found. Will check again.');
48+ await new Promise((resolve) => setTimeout(resolve, 30000));
49+ }
50+
51+ return [];
52+ }
53+
54+ const prs = await findAssociatedPRs();
55+ core.info(`Associated PR info found:`);
56+ core.info(JSON.stringify(prs, null, 2));
57+
58+ if (prs.length === 0) {
59+ core.info('No associated PRs found. Treating this push as a direct/non-PR push.');
60+ core.setOutput('is_pr_merge', 'false');
61+ core.info('Output is_pr_merge=false');
62+ return;
63+ }
64+ if (prs.length > 1) {
65+ core.info('Multiple associated PRs found. Failing because downstream notification expects exactly one PR.');
66+ core.setFailed(`Expected exactly one PR associated with ${sha}, found ${prs.length}.`);
67+ return;
68+ }
69+ const pr = prs[0];
70+
71+ const headRepo = pr.head?.repo?.full_name || '';
72+ core.setOutput('is_pr_merge', 'true');
73+ core.setOutput('is_external', String(headRepo !== `${owner}/${repo}`));
74+ core.setOutput('pr_author', pr.user?.login || '');
75+ core.setOutput('pr_head_ref', pr.head?.ref || '');
76+ core.setOutput('pr_head_repo', headRepo);
77+ core.setOutput('pr_number', String(pr.number));
78+ core.setOutput('pr_title', pr.title || '');
79+ core.setOutput('pr_url', pr.html_url || '');
80+
881 discordNotification :
82+ needs : resolvePush
983 runs-on : ubuntu-latest
10- if : github.event.pull_request.merged == true &&
11- github.event.pull_request.base.ref == 'master'
84+ if : needs.resolvePush.outputs.is_pr_merge == 'true'
1285 env :
1386 GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
1487 steps :
@@ -24,16 +97,25 @@ jobs:
2497 - name : Send Discord notification
2598 env :
2699 DISCORD_WEBHOOK_URL : ${{ secrets.DISCORD_WEBHOOK_URL }}
27- PR_TITLE : ${{ github.event.pull_request.title }}
28- PR_NUMBER : ${{ github.event.pull_request.number }}
29- PR_URL : ${{ github.event.pull_request.html_url }}
100+ PR_AUTHOR : ${{ needs.resolvePush.outputs.pr_author }}
101+ PR_HEAD_REF : ${{ needs.resolvePush.outputs.pr_head_ref }}
102+ PR_HEAD_REPO : ${{ needs.resolvePush.outputs.pr_head_repo }}
103+ PR_IS_EXTERNAL : ${{ needs.resolvePush.outputs.is_external }}
104+ PR_TITLE : ${{ needs.resolvePush.outputs.pr_title }}
105+ PR_NUMBER : ${{ needs.resolvePush.outputs.pr_number }}
106+ PR_URL : ${{ needs.resolvePush.outputs.pr_url }}
30107 MENTION_ON_FAILURE : ${{ secrets.DEV_OPS_ROLE_ID }}
31108 DISCORD_USER_MAP : ${{ secrets.DISCORD_USER_MAP }}
32109 run : |
33110 message="PR merged: [(#${PR_NUMBER}) ${PR_TITLE}](<${PR_URL}>)"
111+ if [[ "${PR_IS_EXTERNAL}" == "true" ]]; then
112+ message+=$'\n'
113+ message+="External PR source: ${PR_HEAD_REPO}@${PR_HEAD_REF}"
114+ fi
115+
34116 # Note that anything besides success is treated as a failure (e.g. if the check did not run at all, or if it is still pending).
35117 FAILED_CHECKS="$(
36- gh pr checks "${{github.event.pull_request.html_url} }" \
118+ gh pr checks "${PR_URL }" \
37119 --json 'workflow,state,name' |
38120 jq '.[]
39121 | select(.workflow != "Discord notifications")
45127 # Lookup PR author's Discord ID from the provided JSON map (if any)
46128 author_discord_id="$(
47129 jq -r \
48- --arg u "${{ github.event.pull_request.user.login } }" \
130+ --arg u "${PR_AUTHOR }" \
49131 '.[$u] // empty' \
50132 <<<"${DISCORD_USER_MAP}"
51133 )"
@@ -71,10 +153,52 @@ jobs:
71153 data="$(jq --null-input --arg msg "$message" '.content=$msg')"
72154 curl -X POST -H 'Content-Type: application/json' -d "$data" "${DISCORD_WEBHOOK_URL}"
73155
156+ notifyFailure :
157+ needs :
158+ - resolvePush
159+ - discordNotification
160+ runs-on : ubuntu-latest
161+ if : ${{ always() && (needs.resolvePush.result == 'failure' || needs.discordNotification.result == 'failure') }}
162+ steps :
163+ - name : Send failure notification
164+ env :
165+ DISCORD_WEBHOOK_URL : ${{ secrets.DISCORD_WEBHOOK_URL }}
166+ run : |
167+ message="Discord notification workflow failed."
168+ message+=$'\n'
169+ message+="Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
170+ data="$(jq --null-input --arg msg "$message" '.content=$msg')"
171+ curl -X POST -H 'Content-Type: application/json' -d "$data" "${DISCORD_WEBHOOK_URL}"
172+
173+ warnDirectPush :
174+ needs : resolvePush
175+ runs-on : ubuntu-latest
176+ if : needs.resolvePush.outputs.is_pr_merge != 'true'
177+ steps :
178+ - name : Warn about non-PR push
179+ env :
180+ DISCORD_WEBHOOK_URL : ${{ secrets.DISCORD_WEBHOOK_URL }}
181+ COMMIT_MESSAGE : ${{ github.event.head_commit.message }}
182+ COMMIT_URL : ${{ github.event.head_commit.url }}
183+ PUSHER : ${{ github.event.pusher.name }}
184+ SHA : ${{ github.sha }}
185+ run : |
186+ short_sha="${SHA:0:7}"
187+ subject="$(printf '%s\n' "${COMMIT_MESSAGE}" | head -n 1)"
188+ message="Warning: push to master was not associated with a merged PR."
189+ message+=$'\n'
190+ message+="Commit: [${short_sha}](<${COMMIT_URL}>)"
191+ message+=$'\n'
192+ message+="Pusher: ${PUSHER}"
193+ message+=$'\n'
194+ message+="Subject: ${subject}"
195+ data="$(jq --null-input --arg msg "$message" '.content=$msg')"
196+ curl -X POST -H 'Content-Type: application/json' -d "$data" "${DISCORD_WEBHOOK_URL}"
197+
74198 invokePrivate :
199+ needs : resolvePush
75200 runs-on : ubuntu-latest
76- if : github.event.pull_request.merged == true &&
77- github.event.pull_request.base.ref == 'master'
201+ if : needs.resolvePush.outputs.is_pr_merge == 'true'
78202 permissions :
79203 contents : read
80204 steps :
89213 workflow_id: 'public-pr-merge.yml',
90214 ref: 'master',
91215 inputs: {
92- public_pr_number: String(context.payload.pull_request.number) ,
216+ public_pr_number: '${{ needs.resolvePush.outputs.pr_number }}' ,
93217 }
94218 });
0 commit comments