diff --git a/.github/workflows/slack-notifier.yml b/.github/workflows/slack-notifier.yml new file mode 100644 index 0000000..ffee3fb --- /dev/null +++ b/.github/workflows/slack-notifier.yml @@ -0,0 +1,92 @@ +name: Post comments on slack, with rate limiting. +on: + workflow_call: + secrets: + SLACK_BOT_TOKEN: + description: 'The slack API token, with `chat:write` permissions.' + required: true + inputs: + channel-id: + description: 'The channel ID to send the message to.' + required: true + type: string + slack-message: + description: 'The message to send to slack.' + required: true + type: string + message-label: + description: 'A label identifying the message type. Used to rate limit messages.' + required: true + type: string + timeout-minutes: + description: 'The minimum time to wait before sending the message again, in minutes.' + required: false + type: string + # Default to 24 hours + default: '1440' + outputs: + sent: + description: 'Whether the message was sent. Returns false if we are waiting for a timeout.' + value: ${{ jobs.notify-slack.outputs.sent }} + +jobs: + notify-slack: + runs-on: ubuntu-latest + outputs: + sent: ${{ steps.rate-limit.outputs.send }} + steps: + - name: Check last message timestamp + id: last-sent + uses: dawidd6/action-download-artifact@v6 + with: + # Downloads the artifact from the most recent successful run + workflow: 'slack-notifier.yml' + name: last-sent-${{ inputs.message-label }}.txt + if_no_artifact_found: ignore + + - name: Rate limit + id: rate-limit + run: | + # Check if the last message was sent within the timeout + echo "Preparing to send message:\n" + echo "\"$MESSAGE\"\n" + + if [ -f last-sent-${{ inputs.message-label }}.txt ] + then + LAST_SENT=$( cat last-sent.txt ) + echo "Last sent: $LAST_SENT" + NOW=$( date +%s ) + echo "Now: $NOW" + echo "Timeout: $TIMEOUT mins" + echo "Difference: $(( NOW - LAST_SENT ))" + + if [ $(( NOW - LAST_SENT )) -lt $(( TIMEOUT * 60 )) ] + then + echo "Rate limiting. Not sending the message." + echo "send=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + else + echo "No last-sent file found." + fi + echo "send=true" >> "$GITHUB_OUTPUT" + date +%s > last-sent-${{ inputs.message-label }}.txt + env: + TIMEOUT: ${{ inputs.timeout-minutes }} + MESSAGE: ${{ inputs.slack-message }} + + - name: Send notification + if: ${{ steps.rate-limit.outputs.send == 'true' }} + uses: slackapi/slack-github-action@v1.27.0 + with: + channel-id: ${{ inputs.channel-id }} + slack-message: ${{ inputs.slack-message }} + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + + - name: Upload the new last-sent timestamp + if: ${{ steps.rate-limit.outputs.send == 'true' }} + uses: actions/upload-artifact@v4 + with: + name: last-sent-${{ inputs.message-label }}.txt + path: last-sent-${{ inputs.message-label }}.txt diff --git a/README.md b/README.md index 59fd43f..77769b2 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ The following workflows are available: - [`drop-cache`](#drop-cache): Drops the cache for a branch when a pull request is closed. - [`pr-title`](#pr-title): Checks the title of pull requests to ensure they follow the conventional commits format. - [`rs-semver-checks`](#rs-semver-checks): Runs `cargo-semver-checks` on a PR against the base branch, and reports back if there are breaking changes. +- [`slack-notifier`](#slack-notifier): Post comments on slack, with a rate limit to avoid spamming the channel. ## [`add-to-project`](https://github.com/CQCL/hugrverse-actions/blob/main/.github/workflows/add-to-project.yml) @@ -184,3 +185,50 @@ The fine-grained `GITHUB_PAT` secret must include the following permissions: | --- | --- | | Pull requests | Read and write | + +## [`slack-notifier`](https://github.com/CQCL/hugrverse-actions/blob/main/.github/workflows/slack-notifier.yml) + +Post comments on slack using +[slackapi/slack-github-action](https://github.com/slackapi/slack-github-action), +adding a rate limit to avoid spamming the channel. + +### Usage +```yaml +name: Send a slack message +on: + pull_request: + branches: + - main + +jobs: + message-slack: + uses: CQCL/hugrverse-actions/.github/workflows/slack-notifier.yml@main + with: + channel-id: "SOME CHANNEL ID" + slack-message: "Hello 🌎!" + # An unique identifier for the message type, to use in rate limiting. + message-label: "hello" + # A minimum time in minutes to wait before sending another message. + timeout-minutes: 60 + secrets: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} +``` + +### Inputs + +- `channel-id`: The ID of the channel to post the message to. (**required**) +- `slack-message`: The message to post. (**required**) +- `message-label`: An identifier for the message type, to use in rate limiting. + This label is tied to the workflow name. (**required**) +- `timeout-minutes`: A minimum time in minutes to wait before sending another message. Defaults to 24 hours. + +### Outputs + +- `sent`: A boolean indicating if the message was sent. + +### Token Permissions + +`SLACK_BOT_TOKEN` is a token generated by Slack with `chat:write` access to the +channel. See the +[slackapi/slack-github-action](https://github.com/slackapi/slack-github-action?tab=readme-ov-file#technique-2-slack-app) +documentation for more information.