From 05b6701fe82512f7ed75d2ba136c5c909e9c771c Mon Sep 17 00:00:00 2001 From: willie Date: Tue, 14 Jan 2020 16:16:08 -0500 Subject: [PATCH 1/7] remove deprecated slack endpoints and add Engineering team members --- incidentSlackBot.js | 271 ++++++++++++++------------------ package-lock.json | 369 +++++--------------------------------------- package.json | 5 +- 3 files changed, 159 insertions(+), 486 deletions(-) diff --git a/incidentSlackBot.js b/incidentSlackBot.js index c964f24..c8b7d0e 100644 --- a/incidentSlackBot.js +++ b/incidentSlackBot.js @@ -1,4 +1,4 @@ -const rp = require('request-promise-native'); +const axios = require('axios'); const qs = require('querystring'); const API_URL = 'https://www.slack.com/api'; @@ -28,14 +28,14 @@ function verifyWebhook(body) { */ function sendMessageToSlack(responseURL, message) { const postOptions = { - uri: responseURL, + url: responseURL, method: 'POST', headers: { 'Content-type': 'application/x-www-form-urlencoded', }, - form: qs.stringify(message), + data: qs.stringify(message), }; - return rp(postOptions); + return axios(postOptions); } /** @@ -56,136 +56,25 @@ function getCurrentDate() { } /** - * Invite Product to the channel - * - * @param {String} channelID - channel ID for the channel we want to add the users to + * This function sends private messages to the user specified */ -function inviteProductToChannel(channelID) { - // Add all product members to the channel - const responseURL = `${API_URL}/channels.invite`; - const addToChannel = []; - addToChannel.push(process.env.USER_ID_1); - addToChannel.push(process.env.USER_ID_2); - addToChannel.push(process.env.USER_ID_3); - - const inviteUserPayload = { - token: process.env.USER_SLACK_TOKEN, - channel: channelID, +function sendDirectMessage(channelID, user, role) { + const responseURL = `${API_URL}/chat.postMessage`; + const notificationMessagePayload = { + token: process.env.INCIDENT_BOT_TOKEN, + channel: user, + text: `You have been declared the incident ${role} for <#${channelID}>. I've already invited you to the channel, but you should get involved ASAP.`, }; - const promiseList = []; - - addToChannel.forEach((user) => { - inviteUserPayload.user = user; - promiseList.push(sendMessageToSlack(responseURL, inviteUserPayload)); - }); - return Promise.all(promiseList).then((data) => { - data.forEach(() => { - console.log('Product member added to channel'); + return sendMessageToSlack(responseURL, notificationMessagePayload) + .then((response) => { + const chatResponseBody = response.data; + // if notification message succeeds + if (chatResponseBody.ok) { + return `Notification message was successfully delivered to ${chatResponseBody.message.username}`; + } + return `Notification message could not be delivered to ${chatResponseBody.message.username}. Reason: ${chatResponseBody.error}`; }); - return 'function adding product members has completed.'; - }); -} - -/** - * Invite the commander to the channel and notify them in a private message - * - * @param {string} channelID - channel to add the commander to - * @param {string} commander - user id for the user that was designated as commander - */ -function inviteCommanderToChannel(channelID, commander) { - let responseURL = `${API_URL}/channels.invite`; - const inviteCommanderPayload = { - token: process.env.USER_SLACK_TOKEN, - channel: channelID, - user: commander, - }; - return sendMessageToSlack(responseURL, inviteCommanderPayload).then((invitationBody) => { - const invitationResponseBody = JSON.parse(invitationBody); - responseURL = `${API_URL}/chat.postMessage`; - // if commander is successfully invited to the channel then we send notification message - if (invitationResponseBody.ok) { - const notificationMessage = { - token: process.env.INCIDENT_BOT_TOKEN, - channel: commander, - text: `You have been declared the incident commander for <#${invitationResponseBody.channel.id}>. I've already invited you to the channel, but you should get involved ASAP.`, - }; - return sendMessageToSlack(responseURL, notificationMessage).then((chatBody) => { - const chatResponseBody = JSON.parse(chatBody); - // if notification message succeeds - if (chatResponseBody.ok) { - return `incident commander: ${chatResponseBody.message.username} was successfully added and notification message was delivered.`; - } - return `incident commander was added to channel but notification message could not be delivered. Reason: ${chatResponseBody.error}`; - }); - } - return `failed to invite incident commander to channel. Reason: ${invitationResponseBody.error}`; - }); -} - -/** - * Invite the communications to the channel and notify them in a private message - * - * @param {string} channelID - channel to add the comms to - * @param {string} comms - user id that was designated as incident comms - */ -function inviteCommsToChannel(channelID, comms) { - let responseURL = `${API_URL}/channels.invite`; - const inviteCommanderPayload = { - token: process.env.USER_SLACK_TOKEN, - channel: channelID, - user: comms, - }; - return sendMessageToSlack(responseURL, inviteCommanderPayload).then((invitationBody) => { - const invitationResponseBody = JSON.parse(invitationBody); - responseURL = `${API_URL}/chat.postMessage`; - // if comms is successfully invited to the channel then we send notification message - if (invitationResponseBody.ok) { - const notificationMessage = { - token: process.env.INCIDENT_BOT_TOKEN, - channel: comms, - text: `You have been declared the incident communications for <#${invitationResponseBody.channel.id}>. I've already invited you to the channel, but you should get involved ASAP.`, - }; - return sendMessageToSlack(responseURL, notificationMessage).then((chatBody) => { - const chatResponseBody = JSON.parse(chatBody); - // if notification message succeeds - if (chatResponseBody.ok) { - return `incident communications: ${chatResponseBody.message.username} was successfully added and notification message was delivered.`; - } - return `incident communications was added to channel but notification message could not be delivered. Reason: ${chatResponseBody.error}`; - }); - } - return `failed to invite incident communications to channel. Reason: ${invitationResponseBody.error}`; - }); -} - -/** - * Set the topic for the new incident channel to be the jira instructions for an incident - * - * @param {string} channelID - id for the channel which we would like to modify - * @param {string} commander - user id that was designated as incident commander - * @param {string} comms - user id that was designated as incident communications - */ -function setChannelTopic(channelID, commander, comms) { - // Set the topic for the channel - const responseURL = `${API_URL}/channels.setTopic`; - let topic = `Commander: <@${commander}>`; - if (comms) { - topic += ` Comms: <@${comms}>`; - } - topic += ` Incident Doc: ${process.env.INCIDENT_DOC_URL}`; - const channelTopicBody = { - token: process.env.USER_SLACK_TOKEN, - channel: channelID, - topic, - }; - return sendMessageToSlack(responseURL, channelTopicBody).then((topicBody) => { - const topicResponseBody = JSON.parse(topicBody); - if (topicResponseBody.ok) { - return `The channel topic was set to ${topicResponseBody.topic}`; - } - return `Setting the channel topic failed. Reason: ${topicResponseBody.error}`; - }); } /** @@ -276,32 +165,100 @@ function sendIncidentDetailsMessage(payload, channelName, channelID) { text, }; - return sendMessageToSlack(responseURL, incidentDetailMessage).then((chatBody) => { - const chatResponseBody = JSON.parse(chatBody); - if (chatResponseBody.ok) { - return 'incident details message sent successfully to the incident channel'; - } - return `incident details message failed to send. Reason: ${chatResponseBody.error}`; - }); + return sendMessageToSlack(responseURL, incidentDetailMessage) + .then((response) => { + const chatResponseBody = response.data; + if (chatResponseBody.ok) { + return 'incident details message sent successfully to the incident channel'; + } + return `incident details message failed to send. Reason: ${chatResponseBody.error}`; + }); +} + +/** + * Format the list of users to add to the incident channel. + * We are doing this by getting all the members of the 'Engineering Team' Slack Group. + */ +function getIncidentChannelMembers(commander, comms) { + const responseURL = `${API_URL}/usergroups.list`; + const requestMembersBody = { + token: process.env.INCIDENT_BOT_TOKEN, + include_disabled: false, + include_users: true, + }; + let channelMembers = []; + sendMessageToSlack(responseURL, requestMembersBody) + .then((response) => { + const responseBody = response.data; + if (responseBody.ok) { + // eslint-disable-next-line consistent-return + responseBody.usergroups.forEach((group) => { + if (group.name === process.env.INCIDENT_GROUP_NAME) { + channelMembers = group.user_ids; + } + }); + } else { + console.log('Formatting list of users for channel creation failed.'); + } + // Only add the incident commander and comms to this channel if they are not already in it. + if (!channelMembers.includes(commander)) { + channelMembers.push(commander); + } + if (comms && !channelMembers.includes(comms)) { + channelMembers.push(comms); + } + return channelMembers; + }); } /** * Create the new slack channel for the incident */ -function createIncidentChannel() { - const responseURL = `${API_URL}/channels.create`; +function createIncidentChannel(channelMembers) { + const responseURL = `${API_URL}/conversations.create`; const incidentDate = getCurrentDate(); // need this random 4 digit string in case multiple incidents are declared in a day. const incidentIdentifier = Math.floor(1000 + Math.random() * 9000); const channelCreationBody = { - token: process.env.USER_SLACK_TOKEN, + token: process.env.INCIDENT_BOT_TOKEN, name: `Incident-${incidentDate}__${incidentIdentifier}`, - validate: false, + is_private: false, + user_ids: channelMembers, }; return sendMessageToSlack(responseURL, channelCreationBody); } +/** + * Set the topic for the new incident channel to be the jira instructions for an incident + * + * @param {string} channelID - id for the channel which we would like to modify + * @param {string} commander - user id that was designated as incident commander + * @param {string} comms - user id that was designated as incident communications + */ +function setChannelTopic(channelID, commander, comms) { + // Set the topic for the channel + const responseURL = `${API_URL}/conversations.setTopic`; + let topic = `Commander: <@${commander}>`; + if (comms) { + topic += ` Comms: <@${comms}>`; + } + topic += ` Incident Doc: ${process.env.INCIDENT_DOC_URL}`; + const channelTopicBody = { + token: process.env.INCIDENT_BOT_TOKEN, + channel: channelID, + topic, + }; + return sendMessageToSlack(responseURL, channelTopicBody) + .then((response) => { + const topicResponseBody = response.data; + if (topicResponseBody.ok) { + return `The channel topic was set to ${topicResponseBody.topic}`; + } + return `Setting the channel topic failed. Reason: ${topicResponseBody.error}`; + }); +} + /* * * * * * * * * * * * * * * * * */ /* CLOUD FUNCTIONS */ @@ -358,8 +315,9 @@ exports.incidentSlashCommand = (req, res) => { }; // Create a dialog with the user that executed the slash command sendMessageToSlack(responseURL, dialog) - .then((body) => { - const responseBody = JSON.parse(body); + .then((response) => { + const responseBody = response.data; + console.log(responseBody); if (responseBody.error) { console.error(responseBody.error); } else { @@ -382,31 +340,34 @@ exports.handleIncidentForm = (req, res) => { let channelID = ''; verifyWebhook(payload); // Make sure that the request is coming from Slack + // get the members that we would like to add to the channel + const channelMembers = getIncidentChannelMembers(submission.commander, submission.comms); + // If the channel gets created successfully then we proceed - createIncidentChannel() - .then((body) => { - const channelInfo = JSON.parse(body).channel; + createIncidentChannel(channelMembers) + .then((response) => { + const body = response.data; + const channelInfo = body.channel; channelID = channelInfo.id; channelName = channelInfo.name; const setTopicPromise = setChannelTopic(channelID, submission.commander, submission.comms); - const inviteUsersPromise = inviteProductToChannel(channelID); - const promiseList = [setTopicPromise, inviteUsersPromise]; - // Only want to invite commander and send message notification if the commander + const promiseList = [setTopicPromise]; + // Only want to send message notification if the commander // is not the one who declared the incident if (submission.commander !== user.id) { - const commanderPromise = inviteCommanderToChannel(channelID, submission.commander); + const commanderPromise = sendDirectMessage(channelID, submission.commander, 'commander'); promiseList.push(commanderPromise); } - // Only want to invite and send the message notification to comms if comms - // was assigned during incident creation and is not the one who declared the incident. + // Only want to send the message notification to comms if comms + // is not the one who declared the incident. if (submission.comms && submission.comms !== user.id) { - const commsPromise = inviteCommsToChannel(channelID, submission.comms); + const commsPromise = sendDirectMessage(channelID, submission.comms, 'communications'); promiseList.push(commsPromise); } return Promise.all(promiseList); }) - .then((responseLogs) => { - console.log(responseLogs); + .then((responses) => { + console.log(responses); console.log('Channel Creation Phase Completed'); return sendIncidentDetailsMessage(payload, channelName, channelID); }) diff --git a/package-lock.json b/package-lock.json index 18afc31..2516ccd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,7 @@ "version": "6.10.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "dev": true, "requires": { "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", @@ -87,39 +88,19 @@ "es-abstract": "^1.7.0" } }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, "astral-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" - }, - "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + "axios": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.1.tgz", + "integrity": "sha512-Yl+7nfreYKaLRvAvjNPkvfjnQHJM1yLBY3zhqAwcJSwR/6ETkanUgylgtIvkvz0xJ+p/vZuNw8X7Hnb7Whsbpw==", + "requires": { + "follow-redirects": "1.5.10" + } }, "balanced-match": { "version": "1.0.0", @@ -127,14 +108,6 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "requires": { - "tweetnacl": "^0.14.3" - } - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -151,11 +124,6 @@ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -203,14 +171,6 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -229,11 +189,6 @@ "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", "dev": true }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -255,14 +210,6 @@ } } }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { - "assert-plus": "^1.0.0" - } - }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -287,11 +234,6 @@ "object-keys": "^1.0.12" } }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -301,15 +243,6 @@ "esutils": "^2.0.2" } }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, "emoji-regex": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", @@ -588,11 +521,6 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, "external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -604,20 +532,17 @@ "tmp": "^0.0.33" } }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - }, "fast-deep-equal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true }, "fast-levenshtein": { "version": "2.0.6", @@ -669,19 +594,27 @@ "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", "dev": true }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" + "debug": "=3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } } }, "fs.realpath": { @@ -702,14 +635,6 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "^1.0.0" - } - }, "glob": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", @@ -745,20 +670,6 @@ "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", "dev": true }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" - }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" - } - }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -786,16 +697,6 @@ "integrity": "sha512-pzXIvANXEFrc5oFFXRMkbLPQ2rXRoDERwDLyrcUxGhaZhgP54BBSl9Oheh7Vv0T090cszWBxPjkQQ5Sq1PbBRQ==", "dev": true }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -927,11 +828,6 @@ "has-symbols": "^1.0.0" } }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -944,11 +840,6 @@ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -965,20 +856,11 @@ "esprima": "^4.0.0" } }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" - }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -986,22 +868,6 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -1037,20 +903,8 @@ "lodash": { "version": "4.17.15", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" - }, - "mime-db": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" - }, - "mime-types": { - "version": "2.1.24", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", - "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", - "requires": { - "mime-db": "1.40.0" - } + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true }, "mimic-fn": { "version": "1.2.0", @@ -1126,11 +980,6 @@ } } }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" - }, "object-inspect": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", @@ -1292,11 +1141,6 @@ "pify": "^2.0.0" } }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -1324,20 +1168,11 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, - "psl": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz", - "integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==" - }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true }, "querystring": { "version": "0.2.0", @@ -1371,67 +1206,6 @@ "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", "dev": true }, - "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - }, - "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - } - } - } - }, - "request-promise-core": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", - "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", - "requires": { - "lodash": "^4.17.11" - } - }, - "request-promise-native": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", - "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", - "requires": { - "request-promise-core": "1.1.2", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - } - }, "resolve": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", @@ -1484,15 +1258,11 @@ "tslib": "^1.9.0" } }, - "safe-buffer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" - }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true }, "semver": { "version": "6.3.0", @@ -1570,27 +1340,6 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" - }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -1716,34 +1465,12 @@ "os-tmpdir": "~1.0.2" } }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, "tslib": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", "dev": true }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" - }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", @@ -1757,15 +1484,11 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, "requires": { "punycode": "^2.1.0" } }, - "uuid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", - "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" - }, "v8-compile-cache": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", @@ -1782,16 +1505,6 @@ "spdx-expression-parse": "^3.0.0" } }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", diff --git a/package.json b/package.json index 462e6c3..1716d86 100644 --- a/package.json +++ b/package.json @@ -17,9 +17,8 @@ }, "homepage": "https://github.com/industrydive/incident_response#readme", "dependencies": { - "querystring": "^0.2.0", - "request": "^2.88.0", - "request-promise-native": "^1.0.7" + "axios": "^0.19.1", + "querystring": "^0.2.0" }, "devDependencies": { "eslint": "^6.5.1", From 271ab50a4e7fcd1fb697db3a1ccbd64770c17c43 Mon Sep 17 00:00:00 2001 From: willie Date: Thu, 16 Jan 2020 11:49:12 -0500 Subject: [PATCH 2/7] changes to add all engineering people --- incidentSlackBot.js | 97 +++++++++++++++++++++++++++++---------------- 1 file changed, 63 insertions(+), 34 deletions(-) diff --git a/incidentSlackBot.js b/incidentSlackBot.js index c8b7d0e..c85bc21 100644 --- a/incidentSlackBot.js +++ b/incidentSlackBot.js @@ -71,9 +71,9 @@ function sendDirectMessage(channelID, user, role) { const chatResponseBody = response.data; // if notification message succeeds if (chatResponseBody.ok) { - return `Notification message was successfully delivered to ${chatResponseBody.message.username}`; + return 'Notification message was successfully delivered.'; } - return `Notification message could not be delivered to ${chatResponseBody.message.username}. Reason: ${chatResponseBody.error}`; + return 'Notification message could not be delivered.'; }); } @@ -186,15 +186,15 @@ function getIncidentChannelMembers(commander, comms) { include_disabled: false, include_users: true, }; - let channelMembers = []; - sendMessageToSlack(responseURL, requestMembersBody) + return sendMessageToSlack(responseURL, requestMembersBody) .then((response) => { + let channelMembers = []; const responseBody = response.data; if (responseBody.ok) { // eslint-disable-next-line consistent-return responseBody.usergroups.forEach((group) => { if (group.name === process.env.INCIDENT_GROUP_NAME) { - channelMembers = group.user_ids; + channelMembers = group.users; } }); } else { @@ -207,14 +207,43 @@ function getIncidentChannelMembers(commander, comms) { if (comms && !channelMembers.includes(comms)) { channelMembers.push(comms); } - return channelMembers; + // We need to convert this to a string because the Slack API + // is stupid and doesn't work with a list. + return channelMembers.join(); + }); +} + +/** + * Invite the channel members that we want to the incident channel + */ +function inviteUsersToChannel(channel, commander, comms) { + console.log('inviting users to channel'); + const responseURL = `${API_URL}/conversations.invite`; + return getIncidentChannelMembers(commander, comms) + .then((channelMembers) => { + console.log(channelMembers); + const invitiationBody = { + token: process.env.INCIDENT_BOT_TOKEN, + channel, + users: channelMembers, + }; + console.log(invitiationBody); + return sendMessageToSlack(responseURL, invitiationBody); + }) + .then((response) => { + const responseBody = response.data; + if (responseBody.ok) { + return 'Users were successfully invited to the channel'; + } + console.log(responseBody.error); + return 'There was an error when inviting the Users to the channel'; }); } /** * Create the new slack channel for the incident */ -function createIncidentChannel(channelMembers) { +function createIncidentChannel() { const responseURL = `${API_URL}/conversations.create`; const incidentDate = getCurrentDate(); // need this random 4 digit string in case multiple incidents are declared in a day. @@ -222,9 +251,8 @@ function createIncidentChannel(channelMembers) { const channelCreationBody = { token: process.env.INCIDENT_BOT_TOKEN, - name: `Incident-${incidentDate}__${incidentIdentifier}`, + name: `incident-${incidentDate}__${incidentIdentifier}`, is_private: false, - user_ids: channelMembers, }; return sendMessageToSlack(responseURL, channelCreationBody); } @@ -253,9 +281,9 @@ function setChannelTopic(channelID, commander, comms) { .then((response) => { const topicResponseBody = response.data; if (topicResponseBody.ok) { - return `The channel topic was set to ${topicResponseBody.topic}`; + return 'The channel topic was set successfully'; } - return `Setting the channel topic failed. Reason: ${topicResponseBody.error}`; + return 'Setting the channel topic failed.'; }); } @@ -317,7 +345,6 @@ exports.incidentSlashCommand = (req, res) => { sendMessageToSlack(responseURL, dialog) .then((response) => { const responseBody = response.data; - console.log(responseBody); if (responseBody.error) { console.error(responseBody.error); } else { @@ -340,31 +367,33 @@ exports.handleIncidentForm = (req, res) => { let channelID = ''; verifyWebhook(payload); // Make sure that the request is coming from Slack - // get the members that we would like to add to the channel - const channelMembers = getIncidentChannelMembers(submission.commander, submission.comms); - - // If the channel gets created successfully then we proceed - createIncidentChannel(channelMembers) + // attempt to create the new incident channel + createIncidentChannel() + // If the channel gets created successfully then we proceed .then((response) => { const body = response.data; - const channelInfo = body.channel; - channelID = channelInfo.id; - channelName = channelInfo.name; - const setTopicPromise = setChannelTopic(channelID, submission.commander, submission.comms); - const promiseList = [setTopicPromise]; - // Only want to send message notification if the commander - // is not the one who declared the incident - if (submission.commander !== user.id) { - const commanderPromise = sendDirectMessage(channelID, submission.commander, 'commander'); - promiseList.push(commanderPromise); - } - // Only want to send the message notification to comms if comms - // is not the one who declared the incident. - if (submission.comms && submission.comms !== user.id) { - const commsPromise = sendDirectMessage(channelID, submission.comms, 'communications'); - promiseList.push(commsPromise); + if (body.ok) { + const channelInfo = body.channel; + channelID = channelInfo.id; + channelName = channelInfo.name; + const setTopicPromise = setChannelTopic(channelID, submission.commander, submission.comms); + const inviteUsersPromise = inviteUsersToChannel(channelID, submission.commander, submission.comms); + const promiseList = [setTopicPromise, inviteUsersPromise]; + // Only want to send message notification if the commander + // is not the one who declared the incident + if (submission.commander !== user.id) { + const commanderPromise = sendDirectMessage(channelID, submission.commander, 'commander'); + promiseList.push(commanderPromise); + } + // Only want to send the message notification to comms if comms + // is not the one who declared the incident. + if (submission.comms && submission.comms !== user.id) { + const commsPromise = sendDirectMessage(channelID, submission.comms, 'communications'); + promiseList.push(commsPromise); + } + return Promise.all(promiseList); } - return Promise.all(promiseList); + return `Creating the incident channel failed: ${body.error}`; }) .then((responses) => { console.log(responses); From b066a787f8a0db218c98330025d3924a06276014 Mon Sep 17 00:00:00 2001 From: willie Date: Thu, 16 Jan 2020 17:02:32 -0500 Subject: [PATCH 3/7] refactor code into multiple files --- package.json | 2 +- .../handleIncidentForm.js | 136 ++---------------- src/incidentSlashCommand.js | 70 +++++++++ src/main.js | 5 + src/slackHelperMethods.js | 56 ++++++++ 5 files changed, 142 insertions(+), 127 deletions(-) rename incidentSlackBot.js => src/handleIncidentForm.js (73%) create mode 100644 src/incidentSlashCommand.js create mode 100644 src/main.js create mode 100644 src/slackHelperMethods.js diff --git a/package.json b/package.json index 1716d86..7eb1827 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "y", "version": "1.0.0", "description": "Incident Response Tools", - "main": "incidentSlackBot.js", + "main": "./src/main.js", "scripts": { "test": "test" }, diff --git a/incidentSlackBot.js b/src/handleIncidentForm.js similarity index 73% rename from incidentSlackBot.js rename to src/handleIncidentForm.js index c85bc21..d5c2964 100644 --- a/incidentSlackBot.js +++ b/src/handleIncidentForm.js @@ -1,59 +1,9 @@ -const axios = require('axios'); -const qs = require('querystring'); - -const API_URL = 'https://www.slack.com/api'; - -/* * * * * * * * * * * * * * * * * */ -/* HELPER FUNCTIONS */ -/* * * * * * * * * * * * * * * * * */ - -/** - * Verify that the webhook request came from Slack. - * - * @param {object} body The body of the request. - */ -function verifyWebhook(body) { - if (!body || body.token !== process.env.SLACK_TOKEN) { - const error = new Error('Invalid credentials'); - error.code = 401; - throw error; - } -} - -/** - * Send a response to slack. - * - * @param {string} responseURL The url to send response to. - * @param {object} message The payload that we are sending to the url. - */ -function sendMessageToSlack(responseURL, message) { - const postOptions = { - url: responseURL, - method: 'POST', - headers: { - 'Content-type': 'application/x-www-form-urlencoded', - }, - data: qs.stringify(message), - }; - return axios(postOptions); -} - -/** - * Get the current date for use in the new channel name - */ -function getCurrentDate() { - const dateObject = new Date(); - // adjust 0 before single digit date - const day = `0${dateObject.getDate()}`.slice(-2); - - // current month - const month = `0${dateObject.getMonth() + 1}`.slice(-2); - - // current year - const year = dateObject.getFullYear(); - - return `${year}-${month}-${day}`; -} +const { + verifyWebhook, + sendMessageToSlack, + getCurrentDate, + API_URL, +} = require('./slackHelperMethods'); /** * This function sends private messages to the user specified @@ -180,23 +130,18 @@ function sendIncidentDetailsMessage(payload, channelName, channelID) { * We are doing this by getting all the members of the 'Engineering Team' Slack Group. */ function getIncidentChannelMembers(commander, comms) { - const responseURL = `${API_URL}/usergroups.list`; + const responseURL = `${API_URL}/usergroups.users.list`; const requestMembersBody = { token: process.env.INCIDENT_BOT_TOKEN, + usergroup: process.env.INCIDENT_GROUP_ID, include_disabled: false, - include_users: true, }; return sendMessageToSlack(responseURL, requestMembersBody) .then((response) => { let channelMembers = []; const responseBody = response.data; if (responseBody.ok) { - // eslint-disable-next-line consistent-return - responseBody.usergroups.forEach((group) => { - if (group.name === process.env.INCIDENT_GROUP_NAME) { - channelMembers = group.users; - } - }); + channelMembers = responseBody.users; } else { console.log('Formatting list of users for channel creation failed.'); } @@ -289,70 +234,9 @@ function setChannelTopic(channelID, commander, comms) { /* * * * * * * * * * * * * * * * * */ -/* CLOUD FUNCTIONS */ +/* CLOUD FUNCTION */ /* * * * * * * * * * * * * * * * * */ -/** - * Handle the incoming slash command. In this case the command is '/incident'. - * - * @param {object} req the request object. - * @param {object} res the response object. - */ -exports.incidentSlashCommand = (req, res) => { - res.status(200).send(); // best practice to respond with empty 200 status code - const reqBody = req.body; - const responseURL = `${API_URL}/dialog.open`; - verifyWebhook(reqBody); // Make sure that the request is coming from Slack - - const dialog = { - token: process.env.INCIDENT_BOT_TOKEN, - trigger_id: reqBody.trigger_id, - dialog: JSON.stringify({ - title: 'Create an Incident', - callback_id: 'submit-incident', - submit_label: 'Create', - elements: [ - { - label: 'Incident Title', - type: 'text', - name: 'title', - placeholder: 'Enter a title for this incident', - }, - { - label: 'Description', - type: 'textarea', - name: 'description', - optional: true, - }, - { - label: 'Incident Commander', - name: 'commander', - type: 'select', - value: reqBody.user_id, - data_source: 'users', - }, - { - label: 'Incident Communications', - name: 'comms', - type: 'select', - optional: true, - data_source: 'users', - }, - ], - }), - }; - // Create a dialog with the user that executed the slash command - sendMessageToSlack(responseURL, dialog) - .then((response) => { - const responseBody = response.data; - if (responseBody.error) { - console.error(responseBody.error); - } else { - console.log(responseBody); - } - }); -}; - /** * Handle the response from the form submission. * diff --git a/src/incidentSlashCommand.js b/src/incidentSlashCommand.js new file mode 100644 index 0000000..e6ec997 --- /dev/null +++ b/src/incidentSlashCommand.js @@ -0,0 +1,70 @@ +const { + verifyWebhook, + sendMessageToSlack, + API_URL, +} = require('./slackHelperMethods'); + +/* * * * * * * * * * * * * * * * * */ +/* CLOUD FUNCTION */ +/* * * * * * * * * * * * * * * * * */ + +/** + * Handle the incoming slash command. In this case the command is '/incident'. + * + * @param {object} req the request object. + * @param {object} res the response object. + */ +exports.incidentSlashCommand = (req, res) => { + res.status(200).send(); // best practice to respond with empty 200 status code + const reqBody = req.body; + const responseURL = `${API_URL}/dialog.open`; + verifyWebhook(reqBody); // Make sure that the request is coming from Slack + + const dialog = { + token: process.env.INCIDENT_BOT_TOKEN, + trigger_id: reqBody.trigger_id, + dialog: JSON.stringify({ + title: 'Create an Incident', + callback_id: 'submit-incident', + submit_label: 'Create', + elements: [ + { + label: 'Incident Title', + type: 'text', + name: 'title', + placeholder: 'Enter a title for this incident', + }, + { + label: 'Description', + type: 'textarea', + name: 'description', + optional: true, + }, + { + label: 'Incident Commander', + name: 'commander', + type: 'select', + value: reqBody.user_id, + data_source: 'users', + }, + { + label: 'Incident Communications', + name: 'comms', + type: 'select', + optional: true, + data_source: 'users', + }, + ], + }), + }; + // Create a dialog with the user that executed the slash command + sendMessageToSlack(responseURL, dialog) + .then((response) => { + const responseBody = response.data; + if (responseBody.error) { + console.error(responseBody.error); + } else { + console.log(responseBody); + } + }); +}; diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..8ba6aac --- /dev/null +++ b/src/main.js @@ -0,0 +1,5 @@ +const { incidentSlashCommand } = require('./incidentSlashCommand.js'); +const { handleIncidentForm } = require('./handleIncidentForm.js'); + +exports.incidentSlashCommand = incidentSlashCommand; +exports.handleIncidentForm = handleIncidentForm; diff --git a/src/slackHelperMethods.js b/src/slackHelperMethods.js new file mode 100644 index 0000000..2bad75f --- /dev/null +++ b/src/slackHelperMethods.js @@ -0,0 +1,56 @@ +const axios = require('axios'); +const qs = require('querystring'); + +exports.API_URL = 'https://www.slack.com/api'; + +/* * * * * * * * * * * * * * * * * */ +/* HELPER FUNCTIONS */ +/* * * * * * * * * * * * * * * * * */ + +/** + * Get the current date for use in the new channel name + */ +exports.getCurrentDate = () => { + const dateObject = new Date(); + // adjust 0 before single digit date + const day = `0${dateObject.getDate()}`.slice(-2); + + // current month + const month = `0${dateObject.getMonth() + 1}`.slice(-2); + + // current year + const year = dateObject.getFullYear(); + + return `${year}-${month}-${day}`; +}; + +/** + * Verify that the webhook request came from Slack. + * + * @param {object} body The body of the request. + */ +exports.verifyWebhook = (body) => { + if (!body || body.token !== process.env.SLACK_TOKEN) { + const error = new Error('Invalid credentials'); + error.code = 401; + throw error; + } +}; + +/** + * Send a response to slack. + * + * @param {string} responseURL The url to send response to. + * @param {object} message The payload that we are sending to the url. + */ +exports.sendMessageToSlack = (responseURL, message) => { + const postOptions = { + url: responseURL, + method: 'POST', + headers: { + 'Content-type': 'application/x-www-form-urlencoded', + }, + data: qs.stringify(message), + }; + return axios(postOptions); +}; From 43baac5f722e4b23de1ba4e1678539e98556c3ac Mon Sep 17 00:00:00 2001 From: Willie Brown <42161628+wbrown22@users.noreply.github.com> Date: Fri, 17 Jan 2020 11:34:44 -0500 Subject: [PATCH 4/7] Update README.md --- README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 50df889..0f7ad94 100644 --- a/README.md +++ b/README.md @@ -32,22 +32,18 @@ Below is a step by step series of examples that tell you how to get a developmen * You can follow the instructions [here](https://cloud.google.com/resource-manager/docs/creating-managing-projects) to set up a Google Cloud project if you do not already have one. * In the Google Cloud Console navigate to APIs & Services -> Dashboard and enable the Cloud Functions API if you haven't already done so. -2. You must clone this repo. +2. Clone this repo. 3. Create your Slack app: * Navigate to https://api.slack.com/apps and create your new Slack app. * In your new app, navigate to `Slash Commands` and create a new Slash Command. You may call this whatever you like. Place `https://placeholder.com` in the Request URL section for now. Remember where this is because you will have to replace this with the HTTP trigger endpoint for the `incidentSlashCommand` function once you have completed step 4. * In your new app, navigate to and enable `Interactive Components`. Place `https://placeholder.com` in the Request URL section for now. Remember where this is because you will have to replace this with the HTTP trigger endpoint for the `handleIncidentForm` function once you have completed step 4. * In your new app, navigate to `Bots` and add a new bot user. You may call it whatever you would like. - * Navigate to `OAuth & Permission -> Scopes` and add the `channels:write` and the `chat:write:bot` permissions. + * Navigate to `OAuth & Permission -> Scopes` and under the Bot Token scopes, add the `channels:manage`, `chat:write`, `im:write`, and `usergroups:read` permissions. 4. You must set your environment variables and deploy your Cloud Functions. * Rename `example.env.yaml` to `.env.yaml` and input your environment variables. - NOTE: For us, the three users that are always added to the incident channel upon creation are the members of our product - team. You can follow this [link](https://help.workast.com/hc/en-us/articles/360027461274-How-to-find-a-Slack-user-ID) to - easily find the user IDs for the members of your team that you would like to include. - * Now you can deploy each of your cloud functions by following the instructions in the [deployment section](#deploying-the-cloud-functions) NOTE: You may want to save the HTTP trigger endpoint that is part of the output from the deployment command for use in step 5, although you can find this information for each cloud function in the Google Cloud Console later. From 5f1dceadf13a5c1ba3f76145a950518b004f5f37 Mon Sep 17 00:00:00 2001 From: willie Date: Fri, 17 Jan 2020 11:37:56 -0500 Subject: [PATCH 5/7] update example env file --- example.env.yaml | 6 ++---- src/handleIncidentForm.js | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/example.env.yaml b/example.env.yaml index 8cd934b..69f2748 100644 --- a/example.env.yaml +++ b/example.env.yaml @@ -1,7 +1,5 @@ SLACK_TOKEN: This can be found under Basic Information -> App Credentials -> Verification Token in your app INCIDENT_BOT_TOKEN: This can be found under OAuth & Permissions -> Bot User OAuth Access Token and begins with 'xobp' -USER_SLACK_TOKEN: This can be found under OAuth & Permissions -> OAuth Access Token and begins with 'xoxp' INCIDENT_DOC_URL: This is a link to additional guidelines for how to handle the incident. Will be included in channel description. -USER_ID_1: User ID of first person that will be added to incident channel -USER_ID_2: User ID of second person that will be added to incident channel -USER_ID_3: User ID of third person that will be added to incident channel \ No newline at end of file +INCIDENT_GROUP_ID: This is the ID for the usergroup that you would like to add to the channel on creation. The easiest way to get this +is to go the the usergroup in the web version of slack and take the last string of the url. \ No newline at end of file diff --git a/src/handleIncidentForm.js b/src/handleIncidentForm.js index d5c2964..777c880 100644 --- a/src/handleIncidentForm.js +++ b/src/handleIncidentForm.js @@ -260,9 +260,9 @@ exports.handleIncidentForm = (req, res) => { const channelInfo = body.channel; channelID = channelInfo.id; channelName = channelInfo.name; - const setTopicPromise = setChannelTopic(channelID, submission.commander, submission.comms); const inviteUsersPromise = inviteUsersToChannel(channelID, submission.commander, submission.comms); - const promiseList = [setTopicPromise, inviteUsersPromise]; + const setTopicPromise = setChannelTopic(channelID, submission.commander, submission.comms); + const promiseList = [inviteUsersPromise, setTopicPromise]; // Only want to send message notification if the commander // is not the one who declared the incident if (submission.commander !== user.id) { From 78859c98623e7bbd09c7f4954f03e213fb36db1c Mon Sep 17 00:00:00 2001 From: Willie Brown <42161628+wbrown22@users.noreply.github.com> Date: Fri, 17 Jan 2020 11:42:50 -0500 Subject: [PATCH 6/7] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 0f7ad94..8f1be36 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,8 @@ Below is a step by step series of examples that tell you how to get a developmen 4. You must set your environment variables and deploy your Cloud Functions. * Rename `example.env.yaml` to `.env.yaml` and input your environment variables. + + NOTE: When the incident channel gets created, it invites all of the members of the usergroup that is specified in the env.yml file. For more info on usergroups and how to manage them see the [following](https://slack.com/help/articles/212906697-Create-a-user-group) and the example.env.yml file that is included in this repo. * Now you can deploy each of your cloud functions by following the instructions in the [deployment section](#deploying-the-cloud-functions) From 069838813c3b8567a0f6a5c81fe3e8a8d531affb Mon Sep 17 00:00:00 2001 From: willie Date: Mon, 27 Jan 2020 16:24:03 -0500 Subject: [PATCH 7/7] qa changes --- package.json | 3 +-- src/handleIncidentForm.js | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 7eb1827..966249a 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,7 @@ }, "homepage": "https://github.com/industrydive/incident_response#readme", "dependencies": { - "axios": "^0.19.1", - "querystring": "^0.2.0" + "axios": "^0.19.1" }, "devDependencies": { "eslint": "^6.5.1", diff --git a/src/handleIncidentForm.js b/src/handleIncidentForm.js index 777c880..6d60305 100644 --- a/src/handleIncidentForm.js +++ b/src/handleIncidentForm.js @@ -46,7 +46,6 @@ function sendIncidentDetailsMessage(payload, channelName, channelID) { type: 'section', text: { type: 'mrkdwn', - // tslint:disable-next-line:max-line-length text: `*[${channelName}] An Incident has been opened by <@${payload.user.id}>*`, }, }, @@ -88,7 +87,6 @@ function sendIncidentDetailsMessage(payload, channelName, channelID) { fields: [ { type: 'mrkdwn', - // tslint:disable-next-line:max-line-length text: `*Incident started*\n`, }, ], @@ -277,7 +275,7 @@ exports.handleIncidentForm = (req, res) => { } return Promise.all(promiseList); } - return `Creating the incident channel failed: ${body.error}`; + return Promise.reject(body.error); }) .then((responses) => { console.log(responses);