From 94aef8cf88c3ae5ce9c4964f26ba761df16fa22e Mon Sep 17 00:00:00 2001 From: Umbert Date: Mon, 10 Jan 2022 23:59:55 +0100 Subject: [PATCH] [#43] Add payload file path to enhance the github action. (#44) * Add payload file path to enhance the github action. Increase code coverage. Updated Readme to have the new input type. Added example for the new input type usage. * Add E2E test * Minor OPR changes to align with repo naming conventions. * Minor OPR changes to align with repo naming conventions. --- .github/resources/payload-notification.json | 13 ++++ .github/workflows/main.yml | 20 +++++- README.md | 13 ++++ .../JSON_payload_from_file.yml | 14 +++++ example-workflows/payloads/example.json | 42 +++++++++++++ src/slack-send.js | 28 +++++++-- test/resources/invalid-payload.json | 1 + test/resources/valid-payload.json | 4 ++ test/slack-send-test.js | 61 +++++++++++++++++++ 9 files changed, 191 insertions(+), 5 deletions(-) create mode 100644 .github/resources/payload-notification.json create mode 100644 example-workflows/Workflow_Builder/JSON_payload_from_file.yml create mode 100644 example-workflows/payloads/example.json create mode 100644 test/resources/invalid-payload.json create mode 100644 test/resources/valid-payload.json diff --git a/.github/resources/payload-notification.json b/.github/resources/payload-notification.json new file mode 100644 index 0000000..13fdef4 --- /dev/null +++ b/.github/resources/payload-notification.json @@ -0,0 +1,13 @@ +{ + "text": "Incoming Webhook test for slack send", + "blocks": [ + { + "type": "section", + "text": { + "type": "plain_text", + "text": "Content of the event name: {{ github.event_name }}", + "emoji": true + } + } + ] +} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 92353ee..2b22a9d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -80,8 +80,26 @@ jobs: env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_INCOMING_WEBHOOK_URL }} SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK - # Use the output from the `slackIncoming` step - name: Check Action output is not empty run: test -n "${{ steps.slackIncoming.outputs.time }}" + + integration_test_file_payload: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: npm ci && npm run build + - run: echo "${{ github.event_name }}" + - name: Post message to Slack with Payload path + id: slackPayloadFile + uses: ./ + with: + payload-file-path: ./.github/resources/payload-notification.json + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_INCOMING_WEBHOOK_URL }} + SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK + + # Use the output from the `slackIncoming` step + - name: Check Action output is not empty + run: test -n "${{ steps.slackPayloadFile.outputs.time }}" diff --git a/README.md b/README.md index e19f27c..65d1725 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,19 @@ or env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} ``` +or + +> If the `payload` is provided it will take preference over `payload-file-path` + +``` +- name: Send custom JSON data to Slack workflow + id: slack + uses: slackapi/slack-github-action@v1.16.0 + with: + payload-file-path: "./payload-slack-content.json" + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} +``` ## Technique 2: Slack App diff --git a/example-workflows/Workflow_Builder/JSON_payload_from_file.yml b/example-workflows/Workflow_Builder/JSON_payload_from_file.yml new file mode 100644 index 0000000..9b8136d --- /dev/null +++ b/example-workflows/Workflow_Builder/JSON_payload_from_file.yml @@ -0,0 +1,14 @@ +on: [push] + +jobs: + new_push_job: + runs-on: ubuntu-latest + name: New push to repo + steps: + - name: Send GitHub trigger payload to Slack Workflow Builder + id: slack + uses: slackapi/slack-github-action@v1.16.0 + with: + payload-file-path: "./example-workflows/payloads/example.json" + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/example-workflows/payloads/example.json b/example-workflows/payloads/example.json new file mode 100644 index 0000000..9a1ec39 --- /dev/null +++ b/example-workflows/payloads/example.json @@ -0,0 +1,42 @@ +{ + "attachments": [ + { + "color": "#d90000", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "${{ github.workflow }} workflow failed on ${{ env.DEFAULT_BRANCH }} branch in the ${{ github.event.repository.name }} repository!" + } + }, + { + "type": "divider" + }, + { + "type": "actions", + "elements": [ + { + "type": "button", + "text": { + "type": "plain_text", + "text": "Build", + "emoji": true + }, + "url": "${{ env.REPO_URL }}/actions/runs/${{ github.run_id }}" + }, + { + "type": "button", + "text": { + "type": "plain_text", + "text": "Commit", + "emoji": true + }, + "url": "${{ env.REPO_URL }}/commit/${{ github.sha }}" + } + ] + } + ] + } + ] +} diff --git a/src/slack-send.js b/src/slack-send.js index ba76314..15402aa 100644 --- a/src/slack-send.js +++ b/src/slack-send.js @@ -2,6 +2,8 @@ const github = require('@actions/github'); const { WebClient } = require('@slack/web-api'); const flatten = require('flat'); const axios = require('axios'); +const { promises: fs } = require('fs'); +const path = require('path'); const SLACK_WEBHOOK_TYPES = { WORKFLOW_TRIGGER: 'WORKFLOW_TRIGGER', @@ -26,6 +28,18 @@ module.exports = async function slackSend(core) { let payload = core.getInput('payload'); + const payloadFilePath = core.getInput('payload-file-path'); + + if (payloadFilePath && !payload) { + try { + payload = await fs.readFile(path.resolve(payloadFilePath), 'utf-8'); + } catch (error) { + // passed in payload file path was invalid + console.error(error); + throw new Error(`The payload-file-path may be incorrect. Failed to load the file: ${payloadFilePath}`); + } + } + if (payload) { try { // confirm it is valid json @@ -38,15 +52,21 @@ module.exports = async function slackSend(core) { } if (typeof botToken !== 'undefined' && botToken.length > 0) { - const message = core.getInput('slack-message'); - const channelId = core.getInput('channel-id'); + const message = core.getInput('slack-message') || ''; + const channelId = core.getInput('channel-id') || ''; const web = new WebClient(botToken); - if (channelId.length > 0 && (message.length > 0 || payload)) { + if (channelId.length <= 0) { + console.log('Channel ID is required to run this action. An empty one has been provided'); + throw new Error('Channel ID is required to run this action. An empty one has been provided'); + } + + if (message.length > 0 || payload) { // post message await web.chat.postMessage({ channel: channelId, text: message, ...(payload || {}) }); } else { - console.log('missing either channel-id, slack-message or payload! Did not send a message via chat.postMessage with botToken'); + console.log('Missing slack-message or payload! Did not send a message via chat.postMessage with botToken', { channel: channelId, text: message, ...(payload) }); + throw new Error('Missing message content, please input a valid payload or message to send. No Message has been send.'); } } diff --git a/test/resources/invalid-payload.json b/test/resources/invalid-payload.json new file mode 100644 index 0000000..c51046e --- /dev/null +++ b/test/resources/invalid-payload.json @@ -0,0 +1 @@ +{not-valid-json diff --git a/test/resources/valid-payload.json b/test/resources/valid-payload.json new file mode 100644 index 0000000..b3b562b --- /dev/null +++ b/test/resources/valid-payload.json @@ -0,0 +1,4 @@ +{ + "oliver": "benji", + "bonny": "clyde" +} diff --git a/test/slack-send-test.js b/test/slack-send-test.js index bfab36a..a441138 100644 --- a/test/slack-send-test.js +++ b/test/slack-send-test.js @@ -61,6 +61,24 @@ describe('slack-send', () => { assert.equal(chatArgs.channel, 'C123456', 'Correct channel provided to postMessage'); assert.equal(chatArgs.text, 'who let the dogs out?', 'Correct message provided to postMessage'); }); + + it('should accept a payload-file-path and use it\'s content in the message', async () => { + // Prepare + fakeCore.getInput.withArgs('channel-id').returns('C123456'); + fakeCore.getInput.withArgs('payload-file-path').returns('./test/resources/valid-payload.json'); + + // Run + await slackSend(fakeCore); + + // Assert + assert.equal(fakeCore.setOutput.lastCall.firstArg, 'time', 'Output name set to time'); + assert(fakeCore.setOutput.lastCall.lastArg.length > 0, 'Time output a non-zero-length string'); + const chatArgs = ChatStub.postMessage.lastCall.firstArg; + assert.equal(chatArgs.channel, 'C123456', 'Correct channel provided to postMessage'); + assert.equal(chatArgs.text, '', 'Correct message provided to postMessage'); + assert.equal(chatArgs.bonny, 'clyde', 'Correct message provided to postMessage'); + assert.equal(chatArgs.oliver, 'benji', 'Correct message provided to postMessage'); + }); }); describe('sad path', () => { it('should set an error if payload cannot be JSON parsed', async () => { @@ -68,6 +86,49 @@ describe('slack-send', () => { await slackSend(fakeCore); assert.include(fakeCore.setFailed.lastCall.firstArg.message, 'Need to provide valid JSON', 'Error set specifying JSON was invalid.'); }); + + it('should fail if an invalid payload-file-path is provided', async () => { + // Prepare + fakeCore.getInput.withArgs('channel-id').returns('C123456'); + fakeCore.getInput.withArgs('payload-file-path').returns('non-existing-path.json'); + + // Run + await slackSend(fakeCore); + + // Assert + assert.include(fakeCore.setFailed.lastCall.firstArg.message, 'The payload-file-path may be incorrect. Failed to load the file: non-existing-path.json', 'Error set specifying JSON was invalid.'); + }); + + it('should fail if a valid payload-file-path with an invalid JSON is provided', async () => { + // Prepare + fakeCore.getInput.withArgs('channel-id').returns('C123456'); + fakeCore.getInput.withArgs('payload-file-path').returns('./test/resources/invalid-payload.json'); + + // Run + await slackSend(fakeCore); + + // Assert + assert.include(fakeCore.setFailed.lastCall.firstArg.message, 'Need to provide valid JSON payload', 'Error set specifying JSON was invalid.'); + }); + + it('should fail if Channel ID is missing', async () => { + // Run + await slackSend(fakeCore); + + // Assert + assert.include(fakeCore.setFailed.lastCall.firstArg.message, 'Channel ID is required to run this action. An empty one has been provided', 'Error set specifying JSON was invalid.'); + }); + + it('should fail if payload is missing or empty', async () => { + // Prepare + fakeCore.getInput.withArgs('channel-id').returns('C123456'); + + // Run + await slackSend(fakeCore); + + // Assert + assert.include(fakeCore.setFailed.lastCall.firstArg.message, 'Missing message content, please input a valid payload or message to send. No Message has been send.', 'Error set specifying JSON was invalid.'); + }); }); });