From cf7bb417dc9786a5a399eb2aba12c5f3f8d09964 Mon Sep 17 00:00:00 2001 From: Armand Fardeau Date: Mon, 17 Apr 2023 17:58:30 +0200 Subject: [PATCH] Add rocket chat support (#103) * Add Rocket (#1) * Remove workflows * Fix offenses --- .../pr-reviews-reminder-msteams-mention.yml | 16 --- .../workflows/pr-reviews-reminder-msteams.yml | 14 -- README.md | 6 +- dist/index.js | 121 ++++++++++++------ functions.js | 23 ++++ index.js | 4 + test/test.js | 40 ++++++ 7 files changed, 154 insertions(+), 70 deletions(-) delete mode 100644 .github/workflows/pr-reviews-reminder-msteams-mention.yml delete mode 100644 .github/workflows/pr-reviews-reminder-msteams.yml diff --git a/.github/workflows/pr-reviews-reminder-msteams-mention.yml b/.github/workflows/pr-reviews-reminder-msteams-mention.yml deleted file mode 100644 index 3534a22..0000000 --- a/.github/workflows/pr-reviews-reminder-msteams-mention.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: PRs reviews reminder (self testing for MS Teams with mention) - -on: ["push", "pull_request"] - -jobs: - pr-reviews-reminder: - runs-on: ubuntu-latest - steps: - - uses: davideviolante/pr-reviews-reminder-action@master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - webhook-url: 'https://davideviolante.webhook.office.com/webhookb2/a8d77ee9-e1d9-44ef-931d-a72a1bd94db4@962d0904-2bf7-4c2c-8845-3e7d68a0a1e7/IncomingWebhook/67be8507a1cd4913a0e3ade58d5d13ef/8fce525b-00ca-4fca-983e-9403f62d14d8' # Required - provider: 'msteams' # Required (slack or msteams) - channel: '' # Optional, eg: #general - github-provider-map: 'DavideViolante:admin@DavideViolante.onmicrosoft.com' # Optional, eg: "DavideViolante:UEABCDEFG,foobar:UAABCDEFG" \ No newline at end of file diff --git a/.github/workflows/pr-reviews-reminder-msteams.yml b/.github/workflows/pr-reviews-reminder-msteams.yml deleted file mode 100644 index 96c9a51..0000000 --- a/.github/workflows/pr-reviews-reminder-msteams.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: PRs reviews reminder (self testing for MS Teams) - -on: ["push", "pull_request"] - -jobs: - pr-reviews-reminder: - runs-on: ubuntu-latest - steps: - - uses: davideviolante/pr-reviews-reminder-action@master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - webhook-url: 'https://davideviolante.webhook.office.com/webhookb2/a8d77ee9-e1d9-44ef-931d-a72a1bd94db4@962d0904-2bf7-4c2c-8845-3e7d68a0a1e7/IncomingWebhook/67be8507a1cd4913a0e3ade58d5d13ef/8fce525b-00ca-4fca-983e-9403f62d14d8' # Required - provider: 'msteams' # Required (slack or msteams) \ No newline at end of file diff --git a/README.md b/README.md index 4772185..1fe3ba3 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Pull Request reviews reminder action [![](https://github.com/davideviolante/pr-reviews-reminder-action/workflows/Node.js%20CI/badge.svg)](https://github.com/DavideViolante/pr-reviews-reminder-action/actions?query=workflow%3A%22Node.js+CI%22) [![Coverage Status](https://coveralls.io/repos/github/DavideViolante/pr-reviews-reminder-action/badge.svg?branch=master)](https://coveralls.io/github/DavideViolante/pr-reviews-reminder-action?branch=master) [![Maintainability](https://api.codeclimate.com/v1/badges/60f9b3a6b4177a0bfe77/maintainability)](https://codeclimate.com/github/DavideViolante/pr-reviews-reminder-action/maintainability) [![Donate](https://img.shields.io/badge/paypal-donate-179BD7.svg)](https://www.paypal.me/dviolante) -Action to send Slack/Teams notifications when there are pull requests pending for reviews. +Action to send Slack/Rocket/Teams notifications when there are pull requests pending for reviews. ## Preview ![Preview](https://raw.githubusercontent.com/DavideViolante/pr-reviews-reminder-action/master/preview.png "Preview") @@ -14,7 +14,7 @@ The webhook URL (required). More info [here (Slack)](https://api.slack.com/messa ### provider -Chat provider, `slack` or `msteams` (required). Default `slack`. +Chat provider, `slack`, `Rocket` or `msteams` (required). Default `slack`. ### channel @@ -51,7 +51,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: webhook-url: '' # Required - provider: '' # Required (slack or msteams) + provider: '' # Required (slack, rocket or msteams) channel: '' # Optional, eg: #general github-provider-map: '' # Optional, eg: DavideViolante:UEABCDEFG,foobar:UAABCDEFG ignore-label: '' # Optional, eg: no-reminder,ignore me diff --git a/dist/index.js b/dist/index.js index 1216c52..feebba2 100644 --- a/dist/index.js +++ b/dist/index.js @@ -108,6 +108,13 @@ function prettyMessage(pr2user, github2provider, provider) { message += `Hey ${mention}, the PR "${obj.title}" is waiting for your review: ${obj.url}\n`; break; } + case 'rocket': { + const mention = github2provider[obj.login] ? + `<@${github2provider[obj.login]}>` : + `@${obj.login}`; + message += `Hey ${mention}, the PR "${obj.title}" is waiting for your review: ${obj.url}\n`; + break; + } case 'msteams': { const mention = github2provider[obj.login] ? `${obj.login}` : @@ -163,6 +170,21 @@ function formatSlackMessage(channel, message) { return messageData; } +/** + * Formats channel and rocket message text into a request object + * @param {String} channel channel to send the message to + * @param {String} message rocket message text + * @return {Object} rocket message data object + */ +function formatRocketMessage(channel, message) { + const messageData = { + channel: channel, + username: 'Pull Request reviews reminder', + text: message, + }; + return messageData; +} + /** * Format the MS Teams message request object * Docs: https://bit.ly/3UlOoqo @@ -209,6 +231,7 @@ module.exports = { prettyMessage, getTeamsMentions, formatTeamsMessage, + formatRocketMessage, formatSlackMessage, }; @@ -6470,7 +6493,7 @@ module.exports = require("zlib"); /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { "use strict"; -// Axios v1.3.0 Copyright (c) 2023 Matt Zabriskie and contributors +// Axios v1.3.4 Copyright (c) 2023 Matt Zabriskie and contributors const FormData$1 = __nccwpck_require__(4334); @@ -6478,6 +6501,7 @@ const url = __nccwpck_require__(7310); const proxyFromEnv = __nccwpck_require__(3329); const http = __nccwpck_require__(3685); const https = __nccwpck_require__(5687); +const util = __nccwpck_require__(3837); const followRedirects = __nccwpck_require__(7707); const zlib = __nccwpck_require__(9796); const stream = __nccwpck_require__(2781); @@ -6489,6 +6513,7 @@ const FormData__default = /*#__PURE__*/_interopDefaultLegacy(FormData$1); const url__default = /*#__PURE__*/_interopDefaultLegacy(url); const http__default = /*#__PURE__*/_interopDefaultLegacy(http); const https__default = /*#__PURE__*/_interopDefaultLegacy(https); +const util__default = /*#__PURE__*/_interopDefaultLegacy(util); const followRedirects__default = /*#__PURE__*/_interopDefaultLegacy(followRedirects); const zlib__default = /*#__PURE__*/_interopDefaultLegacy(zlib); const stream__default = /*#__PURE__*/_interopDefaultLegacy(stream); @@ -7450,7 +7475,7 @@ function toFormData(obj, formData, options) { value = JSON.stringify(value); } else if ( (utils.isArray(value) && isFlatArray(value)) || - (utils.isFileList(value) || utils.endsWith(key, '[]') && (arr = utils.toArray(value)) + ((utils.isFileList(value) || utils.endsWith(key, '[]')) && (arr = utils.toArray(value)) )) { // eslint-disable-next-line no-param-reassign key = removeBrackets(key); @@ -8051,11 +8076,15 @@ function isValidHeaderName(str) { return /^[-_a-zA-Z]+$/.test(str.trim()); } -function matchHeaderValue(context, value, header, filter) { +function matchHeaderValue(context, value, header, filter, isHeaderNameFilter) { if (utils.isFunction(filter)) { return filter.call(this, value, header); } + if (isHeaderNameFilter) { + value = header; + } + if (!utils.isString(value)) return; if (utils.isString(filter)) { @@ -8159,7 +8188,7 @@ class AxiosHeaders { if (header) { const key = utils.findKey(this, header); - return !!(key && (!matcher || matchHeaderValue(this, this[key], key, matcher))); + return !!(key && this[key] !== undefined && (!matcher || matchHeaderValue(this, this[key], key, matcher))); } return false; @@ -8199,7 +8228,7 @@ class AxiosHeaders { while (i--) { const key = keys[i]; - if(!matcher || matchHeaderValue(this, this[key], key, matcher)) { + if(!matcher || matchHeaderValue(this, this[key], key, matcher, true)) { delete this[key]; deleted = true; } @@ -8418,7 +8447,7 @@ function buildFullPath(baseURL, requestedURL) { return requestedURL; } -const VERSION = "1.3.0"; +const VERSION = "1.3.4"; function parseProtocol(url) { const match = /^([-+\w]{1,25})(:?\/\/|:)/.exec(url); @@ -8758,7 +8787,7 @@ const readBlob$1 = readBlob; const BOUNDARY_ALPHABET = utils.ALPHABET.ALPHA_DIGIT + '-_'; -const textEncoder = new TextEncoder(); +const textEncoder = new util.TextEncoder(); const CRLF = '\r\n'; const CRLF_BYTES = textEncoder.encode(CRLF); @@ -8980,14 +9009,39 @@ function setProxy(options, configProxy, location) { const isHttpAdapterSupported = typeof process !== 'undefined' && utils.kindOf(process) === 'process'; +// temporary hotfix + +const wrapAsync = (asyncExecutor) => { + return new Promise((resolve, reject) => { + let onDone; + let isDone; + + const done = (value, isRejected) => { + if (isDone) return; + isDone = true; + onDone && onDone(value, isRejected); + }; + + const _resolve = (value) => { + done(value); + resolve(value); + }; + + const _reject = (reason) => { + done(reason, true); + reject(reason); + }; + + asyncExecutor(_resolve, _reject, (onDoneHandler) => (onDone = onDoneHandler)).catch(_reject); + }) +}; + /*eslint consistent-return:0*/ const httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { - return new Promise(function dispatchHttpRequest(resolvePromise, rejectPromise) { - let data = config.data; - const responseType = config.responseType; - const responseEncoding = config.responseEncoding; + return wrapAsync(async function dispatchHttpRequest(resolve, reject, onDone) { + let {data} = config; + const {responseType, responseEncoding} = config; const method = config.method.toUpperCase(); - let isFinished; let isDone; let rejected = false; let req; @@ -8995,10 +9049,7 @@ const httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { // temporary internal emitter until the AxiosRequest class will be implemented const emitter = new EventEmitter__default["default"](); - function onFinished() { - if (isFinished) return; - isFinished = true; - + const onFinished = () => { if (config.cancelToken) { config.cancelToken.unsubscribe(abort); } @@ -9008,28 +9059,15 @@ const httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { } emitter.removeAllListeners(); - } - - function done(value, isRejected) { - if (isDone) return; + }; + onDone((value, isRejected) => { isDone = true; - if (isRejected) { rejected = true; onFinished(); } - - isRejected ? rejectPromise(value) : resolvePromise(value); - } - - const resolve = function resolve(value) { - done(value); - }; - - const reject = function reject(value) { - done(value, true); - }; + }); function abort(reason) { emitter.emit('abort', !reason || reason.type ? new CanceledError(null, config, req) : reason); @@ -9046,7 +9084,7 @@ const httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { // Parse url const fullPath = buildFullPath(config.baseURL, config.url); - const parsed = new URL(fullPath); + const parsed = new URL(fullPath, 'http://localhost'); const protocol = parsed.protocol || supportedProtocols[0]; if (protocol === 'data:') { @@ -9073,7 +9111,7 @@ const httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { convertedData = convertedData.toString(responseEncoding); if (!responseEncoding || responseEncoding === 'utf8') { - data = utils.stripBOM(convertedData); + convertedData = utils.stripBOM(convertedData); } } else if (responseType === 'stream') { convertedData = stream__default["default"].Readable.from(convertedData); @@ -9123,9 +9161,14 @@ const httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { // support for https://www.npmjs.com/package/form-data api } else if (utils.isFormData(data) && utils.isFunction(data.getHeaders)) { headers.set(data.getHeaders()); - if (utils.isFunction(data.getLengthSync)) { // check if the undocumented API exists - const knownLength = data.getLengthSync(); - !utils.isUndefined(knownLength) && headers.setContentLength(knownLength, false); + + if (!headers.hasContentLength()) { + try { + const knownLength = await util__default["default"].promisify(data.getLength).call(data); + Number.isFinite(knownLength) && knownLength >= 0 && headers.setContentLength(knownLength); + /*eslint no-empty:0*/ + } catch (e) { + } } } else if (utils.isBlob(data)) { data.size && headers.setContentType(data.type || 'application/octet-stream'); @@ -10707,6 +10750,7 @@ const { stringToObject, getTeamsMentions, formatSlackMessage, + formatRocketMessage, formatTeamsMessage, } = __nccwpck_require__(3505); @@ -10771,6 +10815,9 @@ async function main() { case 'slack': messageObject = formatSlackMessage(channel, messageText); break; + case 'rocket': + messageObject = formatRocketMessage(channel, messageText); + break; case 'msteams': { const msTeamsMentions = getTeamsMentions(github2provider, pr2user); messageObject = formatTeamsMessage(messageText, msTeamsMentions); diff --git a/functions.js b/functions.js index 17d97ab..12d2b64 100644 --- a/functions.js +++ b/functions.js @@ -102,6 +102,13 @@ function prettyMessage(pr2user, github2provider, provider) { message += `Hey ${mention}, the PR "${obj.title}" is waiting for your review: ${obj.url}\n`; break; } + case 'rocket': { + const mention = github2provider[obj.login] ? + `<@${github2provider[obj.login]}>` : + `@${obj.login}`; + message += `Hey ${mention}, the PR "${obj.title}" is waiting for your review: ${obj.url}\n`; + break; + } case 'msteams': { const mention = github2provider[obj.login] ? `${obj.login}` : @@ -157,6 +164,21 @@ function formatSlackMessage(channel, message) { return messageData; } +/** + * Formats channel and rocket message text into a request object + * @param {String} channel channel to send the message to + * @param {String} message rocket message text + * @return {Object} rocket message data object + */ +function formatRocketMessage(channel, message) { + const messageData = { + channel: channel, + username: 'Pull Request reviews reminder', + text: message, + }; + return messageData; +} + /** * Format the MS Teams message request object * Docs: https://bit.ly/3UlOoqo @@ -203,5 +225,6 @@ module.exports = { prettyMessage, getTeamsMentions, formatTeamsMessage, + formatRocketMessage, formatSlackMessage, }; diff --git a/index.js b/index.js index 3ae09b0..75c99be 100644 --- a/index.js +++ b/index.js @@ -12,6 +12,7 @@ const { stringToObject, getTeamsMentions, formatSlackMessage, + formatRocketMessage, formatTeamsMessage, } = require('./functions'); @@ -76,6 +77,9 @@ async function main() { case 'slack': messageObject = formatSlackMessage(channel, messageText); break; + case 'rocket': + messageObject = formatRocketMessage(channel, messageText); + break; case 'msteams': { const msTeamsMentions = getTeamsMentions(github2provider, pr2user); messageObject = formatTeamsMessage(messageText, msTeamsMentions); diff --git a/test/test.js b/test/test.js index 178865f..cf82e95 100644 --- a/test/test.js +++ b/test/test.js @@ -11,6 +11,7 @@ const { prettyMessage, getTeamsMentions, formatSlackMessage, + formatRocketMessage, formatTeamsMessage, } = require('../functions'); @@ -405,6 +406,33 @@ describe('Pull Request Reviews Reminder Action tests', () => { assert.strictEqual(dRow, 'Hey @User2, the PR "Title5" is waiting for your review: https://example.com/5'); }); + it('Should print the pretty message, one reviewer per row, Rocket (correct map)', () => { + const message = prettyMessage(mockPr2User, mockGithub2provider, 'rocket'); + const [aRow, bRow, cRow, dRow] = message.split('\n'); + assert.strictEqual(aRow, 'Hey <@ID123>, the PR "Title1" is waiting for your review: https://example.com/1'); + assert.strictEqual(bRow, 'Hey <@ID456>, the PR "Title1" is waiting for your review: https://example.com/1'); + assert.strictEqual(cRow, 'Hey <@ID789>, the PR "Title3" is waiting for your review: https://example.com/3'); + assert.strictEqual(dRow, 'Hey <@ID456>, the PR "Title5" is waiting for your review: https://example.com/5'); + }); + + it('Should print the pretty message, one reviewer per row, Rocket (malformed map)', () => { + const message = prettyMessage(mockPr2User, mockGithub2providerMalformed, 'rocket'); + const [aRow, bRow, cRow, dRow] = message.split('\n'); + assert.strictEqual(aRow, 'Hey @User1, the PR "Title1" is waiting for your review: https://example.com/1'); + assert.strictEqual(bRow, 'Hey @User2, the PR "Title1" is waiting for your review: https://example.com/1'); + assert.strictEqual(cRow, 'Hey @User3, the PR "Title3" is waiting for your review: https://example.com/3'); + assert.strictEqual(dRow, 'Hey @User2, the PR "Title5" is waiting for your review: https://example.com/5'); + }); + + it('Should print the pretty message, one reviewer per row, Rocket (no map)', () => { + const message = prettyMessage(mockPr2User, mockGithub2providerNoData, 'rocket'); + const [aRow, bRow, cRow, dRow] = message.split('\n'); + assert.strictEqual(aRow, 'Hey @User1, the PR "Title1" is waiting for your review: https://example.com/1'); + assert.strictEqual(bRow, 'Hey @User2, the PR "Title1" is waiting for your review: https://example.com/1'); + assert.strictEqual(cRow, 'Hey @User3, the PR "Title3" is waiting for your review: https://example.com/3'); + assert.strictEqual(dRow, 'Hey @User2, the PR "Title5" is waiting for your review: https://example.com/5'); + }); + it('Should print the pretty message, one reviewer per row, Teams (correct map)', () => { const message = prettyMessage(mockPr2User, mockGithub2provider, 'msteams'); const [aRow, bRow, cRow, dRow] = message.split(' \n'); @@ -468,6 +496,18 @@ describe('Pull Request Reviews Reminder Action tests', () => { assert.deepEqual(slackMessageRequest, expectedSlackMessageRequest); }); + it('Should format a Rocket message to send the request', () => { + const channel = '#developers'; + const message = 'Hey @User1, the PR "Title1" is waiting for your review: https://example.com/1'; + const rocketMessageRequest = formatRocketMessage(channel, message); + const expectedRocketMessageRequest = { + channel: '#developers', + username: 'Pull Request reviews reminder', + text: 'Hey @User1, the PR "Title1" is waiting for your review: https://example.com/1', + }; + assert.deepEqual(rocketMessageRequest, expectedRocketMessageRequest); + }); + it('Should format a Teams message to send the request', () => { const message = `Hey User1, the PR "Title1" is waiting for your review: [https://example.com/1](https://example.com/1)`; const teamsMessageRequest = formatTeamsMessage(message, mockTeamsMentions);