Notify TSC Members about Voting Status #325
This file contains hidden or 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 TSC Members about Voting Status | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| notification_method: | |
| description: 'Which channel(s) to notify on manual dispatch' | |
| required: true | |
| default: 'both' | |
| type: choice | |
| options: | |
| - slack | |
| - both | |
| schedule: | |
| # Daily at 9:00 UTC | |
| - cron: '0 9 * * *' | |
| jobs: | |
| notify-tsc-members: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # To store the state of the votes and the last time the TSC members were notified | |
| # The format of the file is: | |
| # { | |
| # "issue_number": { | |
| # "status": "open" | "closed", | |
| # "last_notified": "2021-09-01T00:00:00Z" | |
| # } | |
| # } | |
| # Example: https://github.com/ash17290/asyncapi-community/blob/vote_state/vote_status.json | |
| - uses: jorgebg/stateful-action@bd279992190b64c6a5906c3b75a6f2835823ab46 | |
| id: state | |
| with: | |
| branch: vote_state | |
| # List all the open issues with the label "vote open" | |
| - name: List current open issues | |
| uses: actions/github-script@v7 | |
| id: list | |
| with: | |
| script: | | |
| const { data: issues } = await github.rest.issues.listForRepo({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| labels: 'gitvote/open' | |
| }); | |
| return issues; | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| # Fetch the current state from the vote_status.json file | |
| - name: Fetch Current State | |
| id: fetch | |
| run: | | |
| # This file comes from the branch vote_state which is created by the stateful-action | |
| # Eg: https://github.com/ash17290/asyncapi-community/tree/vote_state | |
| cd .vote_state | |
| # Check if the file exists, not empty and readable and create if not | |
| if [ ! -f vote_status.json ] || [ ! -s vote_status.json ] || [ ! -r vote_status.json ]; then | |
| echo "Initializing vote_status.json" | |
| echo "{}" > vote_status.json | |
| fi | |
| # Read the file and export it as JSON | |
| export json=$(cat vote_status.json | jq -c) | |
| echo "::debug::vote_status=$json" | |
| # Store in GitHub Output | |
| echo "vote_status=$json" >> $GITHUB_OUTPUT | |
| # Needed as axios and js-yaml are not available in the default environment | |
| - name: Install the dependencies | |
| run: npm install js-yaml@4.1.0 axios@1.7.4 node-mailjet@6.0.9 | |
| shell: bash | |
| - name: Notify TSC Members | |
| uses: actions/github-script@v7 | |
| id: notify | |
| env: | |
| MAILJET_API_KEY: ${{ secrets.MAILJET_PUBLIC_KEY}} | |
| MAILJET_API_SECRET: ${{ secrets.MAILJET_PRIVATE_KEY }} | |
| # default to 'both' if inputs.notification_method is undefined (schedule runs) | |
| NOTIFY_METHOD: ${{ github.event.inputs.notification_method || 'both' }} | |
| SLACK_DM: ${{ secrets.SLACK_DM_TSC }} | |
| with: | |
| script: | | |
| const yaml = require('js-yaml'); | |
| const fs = require('fs'); | |
| const { | |
| filterIssues, | |
| getTSCLeftToVote, | |
| sendSlackNotification, | |
| sendMailNotification | |
| } = require('./.github/workflows/vote-notify-helpers/index.js'); | |
| const { sendMail } = require('./.github/workflows/mailing/index.js'); | |
| // Creates list of issues for which the TSC members need to be notified | |
| // The number of buffer days is easily configurable by changing the value of the days variable | |
| const issues = ${{ steps.list.outputs.result }}; | |
| const initialState = ${{ steps.fetch.outputs.vote_status }}; | |
| // On manual dispatch, override to 0; on schedule keep 5 | |
| // If workflow_dispatch is used (manual workflow trigger) then we want to set 0 to force notification sending, even if it was just sent or the other day | |
| const daysBuffer = context.eventName === 'workflow_dispatch' ? 0 : 5; | |
| const config = { days: daysBuffer }; | |
| const { issuesToNotify, state } = filterIssues(issues, initialState, config); | |
| // Read TSC members from MAINTAINERS.yaml | |
| const tscMembers = yaml.load(fs.readFileSync('MAINTAINERS.yaml', 'utf8')).filter(member => member.isTscMember); | |
| const failingSlackIds = new Set(); | |
| const failingEmails = new Set(); | |
| const method = process.env.NOTIFY_METHOD; // 'email' | 'slack' | 'both' | |
| console.log(`Notification method: ${method}`); | |
| console.log(`Issues to notify: ${JSON.stringify(issuesToNotify.map(issue => issue.number))}`); | |
| for (const issue of issuesToNotify) { | |
| const { leftToVote, daysSinceStart, voteCommentURL } = await getTSCLeftToVote(issue, tscMembers, github, context); | |
| for (const member of leftToVote) { | |
| console.log(`Notifying ${member.name} about issue #${issue.number}`); | |
| // Only send Slack if requested | |
| if (method === 'slack' || method === 'both') { | |
| if (!await sendSlackNotification(member, issue, daysSinceStart, process.env.SLACK_DM, voteCommentURL)) { | |
| failingSlackIds.add(member.slack); | |
| } | |
| } | |
| // Only send Email if requested | |
| if (method === 'email' || method === 'both') { | |
| if (!await sendMailNotification(member, issue, daysSinceStart, process.env.SLACK_DM, sendMail, voteCommentURL)) { | |
| failingEmails.add(member.email); | |
| } | |
| } | |
| } | |
| // Update last_notified timestamp | |
| state[issue.number].last_notified = new Date().toISOString(); | |
| } | |
| // Store the failing IDs in GITHUB_ENV | |
| const failingIds = [...failingSlackIds, ...failingEmails]; | |
| if (failingIds.length > 0) { | |
| process.env.FAILED_IDS = failingIds.join(','); | |
| core.exportVariable('FAILED_IDS', JSON.stringify(failingIds)); | |
| } | |
| // Store the state back | |
| return JSON.stringify(state); | |
| - name: Update State | |
| if: ${{ steps.notify.outputs.result }} | |
| run: | | |
| echo ${{ steps.notify.outputs.result }} | jq > ./.vote_state/vote_status.json | |
| # Always alert on any failures, regardless of method | |
| - name: Notify about failing Slack DMs | |
| if: ${{ env.FAILED_IDS }} | |
| uses: rtCamp/action-slack-notify@v2 | |
| env: | |
| SLACK_WEBHOOK: ${{ secrets.SLACK_CI_FAIL_NOTIFY }} | |
| SLACK_TITLE: 🚨 Vote notifications couldn’t be sent 🚨 | |
| SLACK_MESSAGE: ${{ env.FAILED_IDS }} | |
| MSG_MINIMAL: true |