-
Notifications
You must be signed in to change notification settings - Fork 467
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
#28718: Adding GH workflow to fetch open PRs and notify about them t…
…o assigned dev (#29931)
- Loading branch information
1 parent
6f66f1b
commit 9dea667
Showing
4 changed files
with
209 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
name: 'Notify about seated PRs' | ||
on: | ||
schedule: | ||
- cron: '0 10 * * *' | ||
workflow_dispatch: | ||
env: | ||
PR_DAY_THRESHOLD: 3 | ||
DRAFT_PR_DAY_THRESHOLD: 5 | ||
REPO: core | ||
|
||
jobs: | ||
resolve-seated-prs: | ||
runs-on: ubuntu-22.04 | ||
outputs: | ||
seated_prs: ${{ steps.fetch-seated-prs.outputs.seated_prs }} | ||
members: ${{ steps.fetch-seated-prs.outputs.members }} | ||
members_json: ${{ steps.fetch-seated-prs.outputs.members_json }} | ||
steps: | ||
- run: echo 'GitHub context' | ||
env: | ||
GITHUB_CONTEXT: ${{ toJson(github) }} | ||
- name: Filter execution | ||
uses: actions/github-script@v7 | ||
with: | ||
result-encoding: string | ||
script: | | ||
const day = new Date().getDay(); | ||
console.log(new Date()); | ||
if (day === 0 || day === 6) { | ||
console.log('It\'s (happy) weekend, not sending any notifications'); | ||
process.exit(1); | ||
} | ||
- id: fetch-seated-prs | ||
name: Fetch Seated PRs | ||
if: success() | ||
uses: actions/github-script@v7 | ||
with: | ||
result-encoding: string | ||
retries: 3 | ||
retry-exempt-status-codes: 400,401 | ||
script: | | ||
const prDayThreshold = ${{ env.PR_DAY_THRESHOLD }}; | ||
const draftPrDayThreshold = ${{ env.DRAFT_PR_DAY_THRESHOLD }}; | ||
const now = new Date(); | ||
const seatedPrs = []; | ||
const excludedUsers = ['dependabot[bot]'] | ||
const fetchOpenPrs = async () => { | ||
const opts = github.rest.pulls.list.endpoint.merge({ | ||
...{ | ||
owner: '${{ github.repository_owner }}', | ||
repo: '${{ env.REPO }}', | ||
per_page: 100 | ||
} | ||
}); | ||
return await github.paginate(opts); | ||
}; | ||
const isPrSeated = (pr) => { | ||
const createdAt = new Date(Date.parse(pr.created_at)); | ||
console.log(`Now: ${now} / CreatedAt: ${createdAt}`); | ||
let weekdaysCount = 0; | ||
for (let date = new Date(createdAt); date <= now; date.setDate(date.getDate() + 1)) { | ||
const dayOfWeek = date.getDay(); | ||
if (dayOfWeek !== 0 && dayOfWeek !== 6) { | ||
weekdaysCount++; | ||
} | ||
} | ||
const threshold = pr.draft ? draftPrDayThreshold : prDayThreshold; | ||
return weekdaysCount >= threshold; | ||
}; | ||
const addPr = (pr, login) => { | ||
if (!isPrSeated(pr)) { | ||
return; | ||
} | ||
let userPrs = seatedPrs.find(pr => pr.login === login); | ||
if (!userPrs) { | ||
userPrs = { | ||
login, | ||
prs: [] | ||
}; | ||
seatedPrs.push(userPrs); | ||
} | ||
userPrs.prs.push({ | ||
pr_number: pr.number, | ||
url: pr.html_url, | ||
draft: pr.draft, | ||
created_at: pr.created_at, | ||
updated_at: pr.updated_at | ||
}); | ||
}; | ||
const handlePr = (pr) => { | ||
const login = pr.user.login; | ||
if (excludedUsers.includes(login)) { | ||
return; | ||
} | ||
addPr(pr, login); | ||
}; | ||
const prs = await fetchOpenPrs(); | ||
console.log(`PRs size: [${prs.length}]`); | ||
prs.forEach(handlePr); | ||
const members = seatedPrs.map(pr => pr.login); | ||
console.log(`Seated PRs size: [${seatedPrs.length}]`); | ||
console.log(JSON.stringify(seatedPrs, null, 2)); | ||
console.log(`Users: ${JSON.stringify(members)}`); | ||
core.setOutput('seated_prs', JSON.stringify(seatedPrs)); | ||
core.setOutput('members', members.join(' ')); | ||
core.setOutput('members_json', JSON.stringify(members)); | ||
slack-channel-resolver: | ||
name: Resolve Slack Channel | ||
needs: resolve-seated-prs | ||
if: success() && needs.resolve-seated-prs.outputs.members | ||
uses: ./.github/workflows/utility_slack-channel-resolver.yml | ||
with: | ||
github_users: ${{ needs.resolve-seated-prs.outputs.members }} | ||
secrets: | ||
CI_MACHINE_USER: ${{ secrets.CI_MACHINE_USER }} | ||
CI_MACHINE_TOKEN: ${{ secrets.CI_MACHINE_TOKEN }} | ||
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} | ||
|
||
notify-seated-prs: | ||
runs-on: ubuntu-22.04 | ||
needs: [resolve-seated-prs, slack-channel-resolver] | ||
if: success() | ||
name: Notifying team member ${{ matrix.member }} | ||
strategy: | ||
fail-fast: false | ||
matrix: | ||
member: ${{ fromJSON(needs.slack-channel-resolver.outputs.channel_ids) }} | ||
steps: | ||
- name: Build Message | ||
id: build-message | ||
uses: actions/github-script@v7 | ||
with: | ||
result-encoding: string | ||
script: | | ||
const urlMapper = (pr) => `- ${pr.url}`; | ||
const prDayThreshold = ${{ env.PR_DAY_THRESHOLD }}; | ||
const draftPrDayThreshold = ${{ env.DRAFT_PR_DAY_THRESHOLD }}; | ||
const seatedPrs = ${{ needs.resolve-seated-prs.outputs.seated_prs }} | ||
const members = ${{ needs.resolve-seated-prs.outputs.members_json }} | ||
const channels = ${{ needs.slack-channel-resolver.outputs.channel_ids }} | ||
console.log(JSON.stringify(members, null, 2)); | ||
console.log(JSON.stringify(channels, null, 2)); | ||
const idx = channels.findIndex(channel => channel === '${{ matrix.member }}'); | ||
if (idx === -1) { | ||
console.log('Could not find channel [${{ matrix.member }}], skipping this'); | ||
process.exit(2); | ||
} | ||
const login = members[idx]; | ||
const userPrs = seatedPrs.find(pr => pr.login === login); | ||
const prs = userPrs.prs.filter(pr => !pr.draft).map(urlMapper); | ||
const draftPrs = userPrs.prs.filter(pr => pr.draft).map(urlMapper); | ||
const prStatement = `The following PRs have at least *${prDayThreshold}* days since created: | ||
${prs.join('\n')}`; | ||
const draftPrStatement = `The following *draft* PRs have at least *${draftPrDayThreshold}* days since created: | ||
${draftPrs.join('\n')}`; | ||
let message = `:hurtrealbad: Attention dev *${login}*! You have PRs seated for a while.`; | ||
if (prs.length > 0) { | ||
message += `\n${prStatement}` | ||
} | ||
if (draftPrs.length > 0) { | ||
message += `\n${draftPrStatement}` | ||
} | ||
message += `\n\nYou can always check your PRs at: https://github.com/${{ github.repository_owner }}/${{ env.REPO }}/pulls/${login}` | ||
core.setOutput('message', message); | ||
- name: Notify member | ||
if: success() | ||
shell: bash | ||
run: | | ||
channel=${{ matrix.member }} | ||
curl -X POST \ | ||
-H "Content-type: application/json" \ | ||
-H "Authorization: Bearer ${{ secrets.SLACK_BOT_TOKEN }}" \ | ||
-d "{ \"channel\":\"${channel}\",\"text\":\"${{ steps.build-message.outputs.message }}\"}" \ | ||
-s \ | ||
https://slack.com/api/chat.postMessage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters