test #107
Workflow file for this run
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
name: 'Notify about seated PRs' | |
on: | |
schedule: | |
- cron: '0 10 * * *' | |
workflow_dispatch: | |
# TODO: remove on-push event | |
push: | |
branches: | |
- 28718-notify-seated-prs | |
env: | |
# TODO: restore values to 5 and 10 for next env-vars | |
PR_DAY_THRESHOLD: 1 | |
DRAFT_PR_DAY_THRESHOLD: 1 | |
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 }} | |
env: | |
REPO: core | |
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'); | |
//TODO: uncomment the following line | |
//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 millisPerDay = 1 * 60 * 60 * 1000; | |
const prDayThreshold = ${{ env.PR_DAY_THRESHOLD }} * millisPerDay; | |
const draftPrDayThreshold = ${{ env.DRAFT_PR_DAY_THRESHOLD }} * millisPerDay; | |
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}`); | |
const delta = now.getTime() - createdAt.getTime(); | |
const threshold = pr.draft ? draftPrDayThreshold : prDayThreshold; | |
return delta >= 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; | |
// TODO: remove the cond condition in the following if block | |
if (excludedUsers.includes(login) || login !== 'victoralfaro-dotcms') { | |
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}` | |
} | |
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 |