From 521249dce98376d0321f772a1dc5781b2e41b8bf Mon Sep 17 00:00:00 2001 From: Mark Silverberg Date: Fri, 19 Jun 2020 16:29:36 -0700 Subject: [PATCH 01/20] Genericize/rename shortcut callback from `triage_stats` to `channel_stats` --- app.js | 2 +- docs/SETUP.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app.js b/app.js index 236114d..f3afb82 100644 --- a/app.js +++ b/app.js @@ -72,7 +72,7 @@ const app = new App({ // ========================================================================= // Handle the shortcut we configured in the Slack App Config -app.shortcut('triage_stats', async ({ ack, context, body }) => { +app.shortcut('channel_stats', async ({ ack, context, body }) => { // Acknowledge right away await ack() diff --git a/docs/SETUP.md b/docs/SETUP.md index 1473ab8..064dfb3 100644 --- a/docs/SETUP.md +++ b/docs/SETUP.md @@ -27,7 +27,7 @@ In your preferred web browser: - Choose a descriptive name and description for your shortcut, for example: - Name: Show triage stats - Description: Calculate stats for a triage channel - - For the Callback ID, it is important you set it to `triage_stats` + - For the Callback ID, it is important you set it to `channel_stats` - Enter your Select Menus Options Load URL `https://your-host/slack/events` From 75bf444343468e37763d958ed2f80fa901692452 Mon Sep 17 00:00:00 2001 From: Mark Silverberg Date: Fri, 19 Jun 2020 16:31:39 -0700 Subject: [PATCH 02/20] Genericize/rename shortcut callback from `triage_stats` to `channel_stats` --- app.js | 2 +- docs/DEPLOY_Heroku.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app.js b/app.js index f3afb82..708727e 100644 --- a/app.js +++ b/app.js @@ -84,7 +84,7 @@ app.shortcut('channel_stats', async ({ ack, context, body }) => { }) }) -// Handle `view_submision` of modal we opened as a result of the `triage_stats` shortcut +// Handle `view_submision` of modal we opened as a result of the `channel_stats` shortcut app.view('channel_selected', async ({ body, view, ack, client, logger, context }) => { // Acknowledge right away await ack() diff --git a/docs/DEPLOY_Heroku.md b/docs/DEPLOY_Heroku.md index f3409b2..22be093 100644 --- a/docs/DEPLOY_Heroku.md +++ b/docs/DEPLOY_Heroku.md @@ -32,7 +32,7 @@ In a new tab, do the following. Be sure to replace `awesome-app-name-you-entered - Choose a descriptive name and description for your shortcut, for example: - Name: Show triage stats - Description: Calculate stats for a triage channel - - For the Callback ID, it is important you set it to `triage_stats` + - For the Callback ID, it is important you set it to `channel_stats` - Enter your Select Menus Options Load URL `https://awesome-app-name-you-entered.herokuapp.com/slack/events` - Click Save Changes From a9c5d6a76e9110893cdebb87ae81a2ab4c6cd9b9 Mon Sep 17 00:00:00 2001 From: Mark Silverberg Date: Fri, 19 Jun 2020 18:10:24 -0700 Subject: [PATCH 03/20] Add space after channel name --- helpers/scheduled_jobs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers/scheduled_jobs.js b/helpers/scheduled_jobs.js index 96681bc..9bb79d2 100644 --- a/helpers/scheduled_jobs.js +++ b/helpers/scheduled_jobs.js @@ -87,7 +87,7 @@ const onCronTick = async function (reminderConfig) { if (messagesFilteredForConfig.length === 0) { await client.chat.postMessage({ channel: channel.id, - text: `:tada: Nice job, <#${channel.id}>!` + + text: `:tada: Nice job, <#${channel.id}>! ` + `There are ${messagesFilteredForConfig.length} messages from the past ${reminderConfig.hours_to_look_back} hours that are ` + `either ${levelEmojis.join( '/' From 6072bb39a8ba75f55fa2be9d8f94def0adea98c7 Mon Sep 17 00:00:00 2001 From: Mark Silverberg Date: Fri, 19 Jun 2020 18:17:08 -0700 Subject: [PATCH 04/20] Rename, simplify, and add stats type to modal --- app.js | 2 +- views/modals.blockkit.js | 74 ++++++++++++++++++++++++++++++++-------- 2 files changed, 60 insertions(+), 16 deletions(-) diff --git a/app.js b/app.js index 708727e..05eabea 100644 --- a/app.js +++ b/app.js @@ -80,7 +80,7 @@ app.shortcut('channel_stats', async ({ ack, context, body }) => { await app.client.views.open({ token: context.botToken, trigger_id: body.trigger_id, - view: modalViews.select_triage_channel + view: modalViews.select_channel_and_config }) }) diff --git a/views/modals.blockkit.js b/views/modals.blockkit.js index cc6a7fa..628ff99 100644 --- a/views/modals.blockkit.js +++ b/views/modals.blockkit.js @@ -1,10 +1,9 @@ module.exports = { - select_triage_channel: { + select_channel_and_config: { callback_id: 'channel_selected', - type: 'modal', title: { type: 'plain_text', - text: 'Triage stats', + text: 'Channel Stats', emoji: true }, submit: { @@ -12,6 +11,7 @@ module.exports = { text: 'Submit', emoji: true }, + type: 'modal', close: { type: 'plain_text', text: 'Cancel', @@ -22,8 +22,7 @@ module.exports = { type: 'section', text: { type: 'mrkdwn', - text: - ':wave: Please select a channel to retrieve triage stats for.\n\n:warning: Note that a bot :robot_face: will be added to the selected public channel.' + text: ':wave: Please select a channel to retrieve triage stats for.' } }, { @@ -32,6 +31,11 @@ module.exports = { { block_id: 'channel', type: 'input', + label: { + type: 'plain_text', + text: 'Select a channel', + emoji: true + }, element: { action_id: 'channel', type: 'conversations_select', @@ -42,18 +46,29 @@ module.exports = { }, default_to_current_conversation: true, filter: { - include: ['public'] + include: [ + 'public' + ] } - }, - label: { - type: 'plain_text', - text: 'Select a channel', - emoji: true } }, + { + type: 'context', + elements: [ + { + type: 'mrkdwn', + text: 'A bot :robot_face: will be added to the channel' + } + ] + }, { block_id: 'n_hours', type: 'input', + label: { + type: 'plain_text', + text: 'How far back should we look?', + emoji: true + }, element: { action_id: 'n_hours', type: 'static_select', @@ -69,7 +84,6 @@ module.exports = { text: '7 days' } }, - options: [ { value: '12', @@ -107,11 +121,41 @@ module.exports = { } } ] - }, + } + }, + { + block_id: 'stats_type', + type: 'input', label: { type: 'plain_text', - text: ':1234: How far should we look back?', - emoji: true + text: 'What type of stats would you like to generate?' + }, + element: { + action_id: 'stats_type', + type: 'static_select', + initial_option: { + value: 'triage', + text: { + type: 'plain_text', + text: 'Triage stats (look for specific emojis)' + } + }, + options: [ + { + value: 'triage', + text: { + type: 'plain_text', + text: 'Triage stats (look for specific emojis)' + } + }, + { + value: 'generic', + text: { + type: 'plain_text', + text: 'Generic stats' + } + } + ] } } ] From 0fa4e0c348f91bb994785d89cd6711d765e23db1 Mon Sep 17 00:00:00 2001 From: Mark Silverberg Date: Sat, 20 Jun 2020 09:32:24 -0700 Subject: [PATCH 05/20] Further update code to support generic stats type --- app.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app.js b/app.js index 05eabea..0957384 100644 --- a/app.js +++ b/app.js @@ -92,8 +92,10 @@ app.view('channel_selected', async ({ body, view, ack, client, logger, context } const submittedByUserId = body.user.id const selectedChannelId = view.state.values.channel.channel.selected_conversation - const nHoursToGoBack = + const nHoursToGoBack = parseInt(view.state.values.n_hours.n_hours.selected_option.value) || 7 + const statsType = + view.state.values.stats_type.stats_type.selected_option.value try { // Get converstion info; this will throw an error if the bot does not have access to it @@ -110,7 +112,7 @@ app.view('channel_selected', async ({ body, view, ack, client, logger, context } // Let the user know, in a DM from the bot, that we're working on their request const msgWorkingOnIt = await client.chat.postMessage({ channel: submittedByUserId, - text: `*You asked for triage stats for <#${selectedChannelId}>*.\n` + + text: `*You asked for _${statsType} stats_ for <#${selectedChannelId}>*.\n` + `I'll work on the stats for the past ${nHoursToGoBack} hours right away!` }) @@ -118,10 +120,10 @@ app.view('channel_selected', async ({ body, view, ack, client, logger, context } await client.chat.postMessage({ channel: msgWorkingOnIt.channel, thread_ts: msgWorkingOnIt.ts, - text: `A number for you while you wait.. the channel has ${conversationInfo.channel.num_members} members (including apps) currently` + text: `A number for you while you wait.. <#${selectedChannelId}> has ${conversationInfo.channel.num_members} members (including apps) currently` }) - // Get all messages from the beginning of time (probably not a good idea) + // Get all messages for the time period specified const allMessages = await getAllMessagesForPastHours( selectedChannelId, nHoursToGoBack, From b73b5999e32a6a71932a34085f8866eb78d65684 Mon Sep 17 00:00:00 2001 From: Mark Silverberg Date: Sat, 20 Jun 2020 09:32:43 -0700 Subject: [PATCH 06/20] Fix linter warnings --- app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app.js b/app.js index 0957384..257325f 100644 --- a/app.js +++ b/app.js @@ -92,7 +92,7 @@ app.view('channel_selected', async ({ body, view, ack, client, logger, context } const submittedByUserId = body.user.id const selectedChannelId = view.state.values.channel.channel.selected_conversation - const nHoursToGoBack = + const nHoursToGoBack = parseInt(view.state.values.n_hours.n_hours.selected_option.value) || 7 const statsType = view.state.values.stats_type.stats_type.selected_option.value @@ -123,7 +123,7 @@ app.view('channel_selected', async ({ body, view, ack, client, logger, context } text: `A number for you while you wait.. <#${selectedChannelId}> has ${conversationInfo.channel.num_members} members (including apps) currently` }) - // Get all messages for the time period specified + // Get all messages for the time period specified const allMessages = await getAllMessagesForPastHours( selectedChannelId, nHoursToGoBack, From 8291933a10f62e04340df21d23cf2b9e51f784f1 Mon Sep 17 00:00:00 2001 From: Mark Silverberg Date: Sat, 20 Jun 2020 09:38:03 -0700 Subject: [PATCH 07/20] Add eslint rc --- .eslintrc | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .eslintrc diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..c2c8e44 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,13 @@ +{ + "extends": [ + "standard" + ], + "rules": { + "no-multi-spaces": [ + "error", + { + "ignoreEOLComments": true + } + ] + } +} From 41b568d19762061f986b14567c1f7489339e4d85 Mon Sep 17 00:00:00 2001 From: Mark Silverberg Date: Sat, 20 Jun 2020 09:39:39 -0700 Subject: [PATCH 08/20] Begin branching logic for triage versus generic stats --- app.js | 72 ++++++++++++++++++++++++++++++---------------------------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/app.js b/app.js index 257325f..b3bf6a7 100644 --- a/app.js +++ b/app.js @@ -131,49 +131,51 @@ app.view('channel_selected', async ({ body, view, ack, client, logger, context } ) // Use a helper method to enrich the messages we have - const allMessagesEnriched = filterAndEnrichMessages(allMessages, selectedChannelId, context.botId) - - // For each level, let's do some analysis! - const levelDetailBlocks = [] - for (const i in triageConfig._.levels) { - const level = triageConfig._.levels[i] - const allMessagesForLevel = allMessagesEnriched.filter( - m => m[`_level_${level}`] === true - ) - - // Formulate strings for each status - const countsStrings = triageConfig._.statuses.map(status => { - const messagesForLevelAndStatus = allMessagesForLevel.filter( - m => m[`_status_${status}`] === true + const allMessagesEnriched = filterAndEnrichMessages(allMessages, selectedChannelId, context.botId, statsType) + + if (statsType === 'triage') { + // For each level, let's do some analysis! + const levelDetailBlocks = [] + for (const i in triageConfig._.levels) { + const level = triageConfig._.levels[i] + const allMessagesForLevel = allMessagesEnriched.filter( + m => m[`_level_${level}`] === true ) - return `\tMessages ${status} ${triageConfig._.statusToEmoji[status]}: ${messagesForLevelAndStatus.length}` - }) - // Add level block to array - levelDetailBlocks.push( - { + // Formulate strings for each status + const countsStrings = triageConfig._.statuses.map(status => { + const messagesForLevelAndStatus = allMessagesForLevel.filter( + m => m[`_status_${status}`] === true + ) + return `\tMessages ${status} ${triageConfig._.statusToEmoji[status]}: ${messagesForLevelAndStatus.length}` + }) + + // Add level block to array + levelDetailBlocks.push( + { + type: 'section', + text: { + type: 'mrkdwn', + text: `${triageConfig._.levelToEmoji[level]} *${level}* (${allMessagesForLevel.length} total)\n${countsStrings.join('\n')}` + } + } + ) + } + + // Send a single message to the thread with all of the stats by level + await client.chat.postMessage({ + channel: msgWorkingOnIt.channel, + thread_ts: msgWorkingOnIt.ts, + blocks: [{ type: 'section', text: { type: 'mrkdwn', - text: `${triageConfig._.levelToEmoji[level]} *${level}* (${allMessagesForLevel.length} total)\n${countsStrings.join('\n')}` + text: "Here's a summary of the messages needing attention by urgency level and status:" } - } - ) + }].concat(levelDetailBlocks) + }) } - // Send a single message to the thread with all of the stats by level - await client.chat.postMessage({ - channel: msgWorkingOnIt.channel, - thread_ts: msgWorkingOnIt.ts, - blocks: [{ - type: 'section', - text: { - type: 'mrkdwn', - text: "Here's a summary of the messages needing attention by urgency level and status:" - } - }].concat(levelDetailBlocks) - }) - // Try to parse our object to CSV and upload it as an attachment try { // Convert object to CSV From 0819cc6314d93daaa4e9f6647ba0f2ccb5d81ad1 Mon Sep 17 00:00:00 2001 From: Mark Silverberg Date: Sat, 20 Jun 2020 11:52:43 -0700 Subject: [PATCH 09/20] Rename scheduled *reminders* to scheduled *jobs* Scheduled jobs will only work on triage statsTypes, for now Also fixed misc. typos --- README.md | 2 +- app.js | 6 ++++-- config.js | 3 ++- docs/DEPLOY_Heroku.md | 2 +- docs/SETUP.md | 2 +- ...eminders.png => func_1_scheduled_jobs.png} | Bin helpers/scheduled_jobs.js | 16 +++++++-------- views/app_home.blockkit.js | 19 +++++++++--------- 8 files changed, 27 insertions(+), 23 deletions(-) rename docs/assets/{func_1_scheduled_reminders.png => func_1_scheduled_jobs.png} (100%) diff --git a/README.md b/README.md index 5eee56a..2974c57 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ This application has a few entry points and features to help you keep track of r | Name and Description | Visual | |------------------------ |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| **1. Scheduled Reminders**
You can invite the bot to public channels and it will monitor for and remind the channel about messages that fit specific criteria on a scheduled basis.

All messages, except those posted by the app are counted, so this works great with [Slack's Workflow Builder](https://slack.com/slack-tips/quickly-field-requests-for-your-team) and any other monitoring integration that you use that uses the emojis you configure. | [![](docs/assets/func_1_scheduled_reminders.png)](docs/assets/func_1_scheduled_reminders.png) | +| **1. Scheduled Jobs (aka Reminders)**
You can invite the bot to public channels and it will monitor for and remind the channel about messages that fit specific criteria on a scheduled basis.

All messages, except those posted by the app are counted, so this works great with [Slack's Workflow Builder](https://slack.com/slack-tips/quickly-field-requests-for-your-team) and any other monitoring integration that you use that uses the emojis you configure. | [![](docs/assets/func_1_scheduled_jobs.png)](docs/assets/func_1_scheduled_jobs.png) | | **2. Ad-hoc Reporting**
You can use the global shortcut :zap: to create ad-hoc reports on any public channel. It'll give you top-line message counts by urgency and status and provide a CSV for offline analysis too. 
  1. Trigger the modal with a [global shortcut](https://slackhq.com/speed-up-work-with-apps-for-slack) and configure your report in the resulting modal
  2. Triage stats bot will be added to the specified channel and run its analysis
  3. Triage stats will be delivered to you in a DM from the bot
| [![](docs/assets/func_2_ad_hoc_reports.gif)](docs/assets/func_2_ad_hoc_reports.gif) | | **3. View Configuration**
The app's [Slack App Home](https://api.slack.com/surfaces/tabs) offers users a view into the configuration of the application | [![](docs/assets/func_3_app_home.png)](docs/assets/func_3_app_home.png) | diff --git a/app.js b/app.js index b3bf6a7..cf6a0d8 100644 --- a/app.js +++ b/app.js @@ -18,7 +18,7 @@ const triageConfig = require('./config') const modalViews = require('./views/modals.blockkit') const appHomeView = require('./views/app_home.blockkit') const { getAllMessagesForPastHours, filterAndEnrichMessages, messagesToCsv } = require('./helpers/messages') -const { scheduleReminders, manuallyTriggerScheduledJobs } = require('./helpers/scheduled_jobs') +const { scheduleJobs, manuallyTriggerScheduledJobs } = require('./helpers/scheduled_jobs') // ==================================== // === Initialization/Configuration === @@ -174,6 +174,8 @@ app.view('channel_selected', async ({ body, view, ack, client, logger, context } } }].concat(levelDetailBlocks) }) + } else { + // do generic stuff, statsType should only ever be 'generic' but its a good default/fall back too } // Try to parse our object to CSV and upload it as an attachment @@ -252,7 +254,7 @@ app.error(error => { (async () => { // Schedule our dynamic cron jobs - scheduleReminders() + scheduleJobs() // Actually start thhe Bolt app. Let's go! await app.start(process.env.PORT || 3000) diff --git a/config.js b/config.js index 1b66775..702e493 100644 --- a/config.js +++ b/config.js @@ -21,9 +21,10 @@ const triageConfig = { emoji: ':white_circle:' } }, - scheduled_reminders: [ + scheduled_jobs: [ { expression: '0 * * * *', + type: 'triage', hours_to_look_back: 24, report_on_levels: ['Urgent', 'Medium'], // only report on messages with one of these levels ("OR" logic) report_on_does_not_have_status: ['Acknowledged', 'Done'] // only report on messages that do not have either of these statuses ("OR") diff --git a/docs/DEPLOY_Heroku.md b/docs/DEPLOY_Heroku.md index 22be093..4e9b379 100644 --- a/docs/DEPLOY_Heroku.md +++ b/docs/DEPLOY_Heroku.md @@ -83,5 +83,5 @@ In a new tab, do the following. Be sure to replace `awesome-app-name-you-entered - You should see that MongoLabs has some data in it - Consider adding other addons to help you manage your app such as Logentries for ingesting your logs and NewRelic for monitoring performance characteristics. -4. Lastly, note that in the default configuration of this app, you should have one and only one web dyno running at a time as the scheduled reminder functionality runs _within_ the web application code courtesy of `node-cron`. +4. Lastly, note that in the default configuration of this app, you should have one and only one web dyno running at a time as the scheduled job functionality runs _within_ the web application code courtesy of `node-cron`. - In production, you may want to disable this and outsource the scheduling to [Heroku Scheduler](https://devcenter.heroku.com/articles/scheduler) or another service/add-on. \ No newline at end of file diff --git a/docs/SETUP.md b/docs/SETUP.md index 064dfb3..0107b5c 100644 --- a/docs/SETUP.md +++ b/docs/SETUP.md @@ -97,5 +97,5 @@ Back in your preferred web browser... 2. Execute your shortcut by entering "Show triage stats" in the quick switcher (CMD+k) or by using the lightning bolt ⚡️ symbol right below the message input field in Slack and filling out the form. You should receive a DM from the bot. 3. Wait for the (by default) top-of-the-hour hourly update in any channel the bot has been invited to. -3. Lastly, note that in the default configuration of this app, you should have one and only one Node process running at a time as the scheduled reminder functionality runs _within_ the web application code courtesy of `node-cron`. +3. Lastly, note that in the default configuration of this app, you should have one and only one Node process running at a time as the scheduled job functionality runs _within_ the web application code courtesy of `node-cron`. - In production, you may want to disable this and outsource the scheduling to `crontab` or another schedule service/daemon. \ No newline at end of file diff --git a/docs/assets/func_1_scheduled_reminders.png b/docs/assets/func_1_scheduled_jobs.png similarity index 100% rename from docs/assets/func_1_scheduled_reminders.png rename to docs/assets/func_1_scheduled_jobs.png diff --git a/helpers/scheduled_jobs.js b/helpers/scheduled_jobs.js index 9bb79d2..23fabf1 100644 --- a/helpers/scheduled_jobs.js +++ b/helpers/scheduled_jobs.js @@ -123,11 +123,11 @@ const onCronTick = async function (reminderConfig) { } // This exported function is loaded and executed toward the bottom of app.js -// when run, this function looks at all defined scheduled_reminders in the config js file +// when run, this function looks at all defined scheduled_jobs in the config js file // and schedules them! Upon triggering (handled by the node-cron) -const scheduleReminders = function () { - // get the scheduled_reminders array from the config file - const scheduledReminders = triageConfig.scheduled_reminders +const scheduleJobs = function () { + // get the scheduled_jobs array from the config file + const scheduledReminders = triageConfig.scheduled_jobs // check to make sure it is neither undefined nor blank if (typeof (scheduledReminders) !== 'undefined' && scheduledReminders.length > 0) { // For each reminder, schedule it based off of the expression value @@ -139,19 +139,19 @@ const scheduleReminders = function () { }) } else { console.error('Sorry but there are no scheduled reminders to schedule.') - console.error('Please add some to config_triage.js and restart yoru app') + console.error('Please add some to config_triage.js and restart your app') } } const manuallyTriggerScheduledJobs = function () { console.debug('Manually triggering scheduled jobs') - triageConfig.scheduled_reminders.forEach(reminderConfig => { - onCronTick(reminderConfig) + triageConfig.scheduled_jobs.forEach(jobConfig => { + onCronTick(jobConfig) }) } module.exports = { - scheduleReminders, + scheduleJobs, onCronTick, manuallyTriggerScheduledJobs } diff --git a/views/app_home.blockkit.js b/views/app_home.blockkit.js index 8f6133b..c374e78 100644 --- a/views/app_home.blockkit.js +++ b/views/app_home.blockkit.js @@ -1,5 +1,6 @@ // External dependencies -// Crontstrue will help us convert cron expressions to human readable language +// Cronstrue will help us convert cron expressions to human readable language + const cronstrue = require('cronstrue') module.exports = function (userId, triageConfig) { @@ -22,19 +23,19 @@ module.exports = function (userId, triageConfig) { }) .join('\n') - const scheduledJobsDisplay = triageConfig.scheduled_reminders - .map(reminderConfig => { - const scheduleString = cronstrue.toString(reminderConfig.expression) - const levelEmojis = reminderConfig.report_on_levels.map( + const scheduledJobsDisplay = triageConfig.scheduled_jobs + .map(jobConfig => { + const scheduleString = cronstrue.toString(jobConfig.expression) + const levelEmojis = jobConfig.report_on_levels.map( l => triageConfig._.levelToEmoji[l] ) - const statusEmojis = reminderConfig.report_on_does_not_have_status.map( + const statusEmojis = jobConfig.report_on_does_not_have_status.map( s => triageConfig._.statusToEmoji[s] ) return `${indentationUsingWhitespaceHack.repeat( 2 )} ${scheduleString}, look for messages from the past ${ - reminderConfig.hours_to_look_back + jobConfig.hours_to_look_back } hours.. \n\t\t\tthat contain any of the following emoji: ${levelEmojis.join( ' / ' )}\n\t\t\tand do not have any of the following reactions: ${statusEmojis.join( @@ -63,7 +64,7 @@ module.exports = function (userId, triageConfig) { type: 'section', text: { type: 'mrkdwn', - text: `${newLineInSectionBlockHack}:question: *What is this?* :question:\n\nThis is the App Home for me, Triage Bot. I have two main jobs around here:\n\n:one: You can invite me to public channels and I will monitor for and remind the channel about messages that fit the criteria below (see _Scheduled Reminders_).\n\n:two: You can use my message shortcut :zap: to create ad-hoc reports on any public channel. I'll give you top-line stats and provide a CSV for offline analysis too.${newLineInSectionBlockHack.repeat( + text: `${newLineInSectionBlockHack}:question: *What is this?* :question:\n\nThis is the App Home for me, Triage Bot. I have two main jobs around here:\n\n:one: You can invite me to public channels and I will monitor for and remind the channel about messages that fit the criteria below (see _Scheduled Jobs_).\n\n:two: You can use my message shortcut :zap: to create ad-hoc reports on any public channel. I'll give you top-line stats and provide a CSV for offline analysis too.${newLineInSectionBlockHack.repeat( 2 )}` } @@ -107,7 +108,7 @@ module.exports = function (userId, triageConfig) { type: 'section', text: { type: 'mrkdwn', - text: `_Scheduled Reminders_\n\n${scheduledJobsDisplay}` + text: `_Scheduled Jobs_\n\n${scheduledJobsDisplay}` } }, { From e87f0b50fefce533bf75a26f263395e12036875c Mon Sep 17 00:00:00 2001 From: Mark Silverberg Date: Sat, 20 Jun 2020 11:56:26 -0700 Subject: [PATCH 10/20] More renaming of 'reminder' to 'job' --- helpers/messages.js | 2 +- helpers/scheduled_jobs.js | 43 ++++++++++++++++++++------------------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/helpers/messages.js b/helpers/messages.js index 451c4fc..ffccdbb 100644 --- a/helpers/messages.js +++ b/helpers/messages.js @@ -51,7 +51,7 @@ const getAllMessagesForPastHours = async function (channelId, nHoursToGoBack, cl return allMessages } -const filterAndEnrichMessages = function (messages, fromChannel, teamBotId) { +const filterAndEnrichMessages = function (messages, fromChannel, teamBotId, statsType) { // First, filter out messages from the team's bot const filteredMessages = messages.filter(m => { if (m.bot_id !== teamBotId) return true diff --git a/helpers/scheduled_jobs.js b/helpers/scheduled_jobs.js index 23fabf1..606b3f0 100644 --- a/helpers/scheduled_jobs.js +++ b/helpers/scheduled_jobs.js @@ -14,10 +14,10 @@ const { getAllMessagesForPastHours, filterAndEnrichMessages } = require('./messa // Initialize a single instance of Slack Web API WebClient, without a token const client = new WebClient() -const onCronTick = async function (reminderConfig) { +const onCronTick = async function (jobConfig) { const now = new Date() console.log('[node-cron] [onCronTick] ran ' + now.toLocaleString()) - console.log('Reminder config is as follows', reminderConfig) + console.log('Job config is as follows', jobConfig) // Get all teams const teams = await AuthedTeam.find({}) @@ -48,25 +48,26 @@ const onCronTick = async function (reminderConfig) { // Get all messages from the beginning of time (probably not a good idea) const allMessages = await getAllMessagesForPastHours( channel.id, - reminderConfig.hours_to_look_back, + jobConfig.hours_to_look_back, client ) console.log( - `\tFound ${allMessages.length} total messages in past ${reminderConfig.hours_to_look_back} hours` + `\tFound ${allMessages.length} total messages in past ${jobConfig.hours_to_look_back} hours` ) - // Use the enricMessages helper to enrich the messages we have - const allMessagesEnriched = filterAndEnrichMessages(allMessages, channel, team.bot.id) + // Use the filterAndEnrichMessages helper to enrich the messages we have + // and always use 'triage' stats type for scheduled jobs + const allMessagesEnriched = filterAndEnrichMessages(allMessages, channel, team.bot.id, 'triage') // Filter all messages to ones we care about based off of the config const messagesFilteredForConfig = allMessagesEnriched.filter(m => { - // Look to see if the levels and statuses are any of the ones we care about (per reminderConfig) + // Look to see if the levels and statuses are any of the ones we care about (per jobConfig) const containsAnySpecifiedLevels = m._levels.some(l => - reminderConfig.report_on_levels.includes(l) + jobConfig.report_on_levels.includes(l) ) const containsAnySpecifiedStatuses = m._statuses.some(s => - reminderConfig.report_on_does_not_have_status.includes(s) + jobConfig.report_on_does_not_have_status.includes(s) ) // Return the boolean of if they contain any of the levels and do NOT contain any of the statuses @@ -74,13 +75,13 @@ const onCronTick = async function (reminderConfig) { }) console.log( - `\t${messagesFilteredForConfig.length} messages match the reminderConfig criteria` + `\t${messagesFilteredForConfig.length} messages match the jobConfig criteria` ) - const statusEmojis = reminderConfig.report_on_does_not_have_status.map( + const statusEmojis = jobConfig.report_on_does_not_have_status.map( s => triageConfig._.statusToEmoji[s] ) - const levelEmojis = reminderConfig.report_on_levels.map( + const levelEmojis = jobConfig.report_on_levels.map( l => triageConfig._.levelToEmoji[l] ) @@ -88,7 +89,7 @@ const onCronTick = async function (reminderConfig) { await client.chat.postMessage({ channel: channel.id, text: `:tada: Nice job, <#${channel.id}>! ` + - `There are ${messagesFilteredForConfig.length} messages from the past ${reminderConfig.hours_to_look_back} hours that are ` + + `There are ${messagesFilteredForConfig.length} messages from the past ${jobConfig.hours_to_look_back} hours that are ` + `either ${levelEmojis.join( '/' )} and don't have either ${statusEmojis.join('/')}` @@ -104,7 +105,7 @@ const onCronTick = async function (reminderConfig) { await client.chat.postMessage({ channel: channel.id, text: `:wave: Hi there, <#${channel.id}>. ` + - `${numMessagesString} from the past ${reminderConfig.hours_to_look_back} hours that are ` + + `${numMessagesString} from the past ${jobConfig.hours_to_look_back} hours that are ` + `either ${levelEmojis.join( '/' )} and don't have either ${statusEmojis.join( @@ -127,18 +128,18 @@ const onCronTick = async function (reminderConfig) { // and schedules them! Upon triggering (handled by the node-cron) const scheduleJobs = function () { // get the scheduled_jobs array from the config file - const scheduledReminders = triageConfig.scheduled_jobs + const scheduledJobs = triageConfig.scheduled_jobs // check to make sure it is neither undefined nor blank - if (typeof (scheduledReminders) !== 'undefined' && scheduledReminders.length > 0) { - // For each reminder, schedule it based off of the expression value + if (typeof (scheduledJobs) !== 'undefined' && scheduledJobs.length > 0) { + // For each job, schedule it based off of the expression value // and send the rest of the config to the function (onCronTick) so it knows what to do - scheduledReminders.forEach(reminderConfig => { - cron.schedule(reminderConfig.expression, () => { - onCronTick(reminderConfig) + scheduledJobs.forEach(jobConfig => { + cron.schedule(jobConfig.expression, () => { + onCronTick(jobConfig) }) }) } else { - console.error('Sorry but there are no scheduled reminders to schedule.') + console.error('Sorry but there are no scheduled jobs to schedule.') console.error('Please add some to config_triage.js and restart your app') } } From 558d38763a1dff208229953b9dbb48d6a4a71357 Mon Sep 17 00:00:00 2001 From: Mark Silverberg Date: Sat, 20 Jun 2020 12:50:58 -0700 Subject: [PATCH 11/20] Add vscode launch config that goes with `npm run debug` --- .vscode/launch.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..371aa87 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,13 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "attach", + "name": "Node: Nodemon", + "processId": "${command:PickProcess}", + "restart": true, + "protocol": "inspector", + }, + ] +} \ No newline at end of file From 9e90cf6c51a7f3bb08ca2f438af92ee3baea1213 Mon Sep 17 00:00:00 2001 From: Mark Silverberg Date: Sat, 20 Jun 2020 12:51:51 -0700 Subject: [PATCH 12/20] Fix typos --- app.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/app.js b/app.js index cf6a0d8..8ec7b14 100644 --- a/app.js +++ b/app.js @@ -52,7 +52,7 @@ const app = new App({ let teamData = installation.team teamData = Object.assign(teamData, installation) delete teamData.team // we already have this information from the assign above - delete teamData.user.token // we dont want a user token, if the scopes are requested + delete teamData.user.token // we don't want a user token, if the scopes are requested // Do an upsert so that we always have just one document per team ID await AuthedTeam.findOneAndUpdate({ id: teamData.id }, teamData, { upsert: true }) @@ -98,7 +98,7 @@ app.view('channel_selected', async ({ body, view, ack, client, logger, context } view.state.values.stats_type.stats_type.selected_option.value try { - // Get converstion info; this will throw an error if the bot does not have access to it + // Get conversation info; this will throw an error if the bot does not have access to it const conversationInfo = await client.conversations.info({ channel: selectedChannelId, include_num_members: true @@ -174,8 +174,6 @@ app.view('channel_selected', async ({ body, view, ack, client, logger, context } } }].concat(levelDetailBlocks) }) - } else { - // do generic stuff, statsType should only ever be 'generic' but its a good default/fall back too } // Try to parse our object to CSV and upload it as an attachment @@ -238,7 +236,7 @@ app.event('app_home_opened', async ({ payload, context, logger }) => { }) // Handle the shortcut for triggering manually scheduled jobs; -// this should only be used for debugging (so we dont have to wait until a triggered job would normally fire) +// this should only be used for debugging (so we don't have to wait until a triggered job would normally fire) app.shortcut('debug_manually_trigger_scheduled_jobs', async ({ ack, context, body }) => { // Acknowledge right away await ack() @@ -256,7 +254,7 @@ app.error(error => { // Schedule our dynamic cron jobs scheduleJobs() - // Actually start thhe Bolt app. Let's go! + // Actually start the Bolt app. Let's go! await app.start(process.env.PORT || 3000) console.log('⚡️ Bolt app is running!') })() From bf1ee968c3494a5dbb5307f8f164fc3d4cc9bcb2 Mon Sep 17 00:00:00 2001 From: Mark Silverberg Date: Sat, 20 Jun 2020 12:52:27 -0700 Subject: [PATCH 13/20] Beginning of generic analysis options Also add fields to denote is message was posted by a workflow builder bot Also fix misc typos --- helpers/messages.js | 84 ++++++++++++++++++++++++++++++--------------- 1 file changed, 56 insertions(+), 28 deletions(-) diff --git a/helpers/messages.js b/helpers/messages.js index ffccdbb..f06d969 100644 --- a/helpers/messages.js +++ b/helpers/messages.js @@ -6,13 +6,13 @@ // We use json2csv to convert our messages JS object to a CSV file const { parse: parseJsonToCsv } = require('json2csv') -// Internal depencies +// Internal dependencies // Load our triage config const triageConfig = require('./../config') // The helper functions related to messages follow -// Recursive function to paginate through all history of a conversatoin +// Recursive function to paginate through all history of a conversation const getFullConvoHistory = async function (client, params, data = []) { const apiMethod = 'conversations.history' console.log(`Querying ${apiMethod} with ${JSON.stringify(params)}; already have ${data.length} in array`) @@ -51,7 +51,7 @@ const getAllMessagesForPastHours = async function (channelId, nHoursToGoBack, cl return allMessages } -const filterAndEnrichMessages = function (messages, fromChannel, teamBotId, statsType) { +const filterAndEnrichMessages = function (messages, fromChannel, teamBotId, statsType = 'triage') { // First, filter out messages from the team's bot const filteredMessages = messages.filter(m => { if (m.bot_id !== teamBotId) return true @@ -62,38 +62,56 @@ const filterAndEnrichMessages = function (messages, fromChannel, teamBotId, stat // Loop through all messages and enrich them additional attributes so we can do filters on them later enrichedMessages.forEach(message => { + // Regardless of statsType, we want to populate the channel ID, _statsType, and array of _reactions // Add `channel` attribute with the channel we retrieved the message from message.channel = fromChannel + message._statsType = statsType - // Add array attributes we will populate later - message._statuses = [] - message._levels = [] - // Create a new `_reactions` attribute with an array of reacitons - message._reactions = message.reactions + // Create a new `_all_reactions` attribute with an array of all reactions (regardless of if they are relevant to triage analysis) + message._all_reactions = message.reactions ? message.reactions.map(r => `:${r.name}:`) : [] - // Populate `_level_XXX` attribute with boolean and add to array of _levels - triageConfig._.levels.forEach(level => { - if (message.text.includes(triageConfig._.levelToEmoji[level])) { - message[`_level_${level}`] = true - message._levels.push(level) - } - }) + // Add `_threadedReplyCount` and `_threadedReplyUsersCount` with the # of replies and # users who wrote those replies + message._threadedReplyCount = message.reply_count || 0 + message._threadedReplyUsersCount = message.reply_users_count || 0 - // Populate `_status_XXX` attribute with boolean and add to array of _statuses - triageConfig._.statuses.forEach(status => { - if (message._reactions.includes(triageConfig._.statusToEmoji[status])) { - message[`_status_${status}`] = true - message._statuses.push(status) + // If message is by a bot (such as workflow builder), put the bot ID in the user field + if (message.subtype === 'bot_message') { + message.user = message.bot_id + if (message.bot_profile.is_workflow_bot === true) { + message._postedByWorkflowBuilder = true } - }) + } + + // Do additional status and level analysis for triage stats requests + if (statsType === 'triage') { + // Add array attributes we will populate later + message._statuses = [] + message._levels = [] + + // Populate `_level_XXX` attribute with boolean and add to array of _levels + triageConfig._.levels.forEach(level => { + if (message.text.includes(triageConfig._.levelToEmoji[level])) { + message[`_level_${level}`] = true + message._levels.push(level) + } + }) + + // Populate `_status_XXX` attribute with boolean and add to array of _statuses + triageConfig._.statuses.forEach(status => { + if (message._all_reactions.includes(triageConfig._.statusToEmoji[status])) { + message[`_status_${status}`] = true + message._statuses.push(status) + } + }) + } }) return enrichedMessages } -const messagesToCsv = function (messages) { +const messagesToCsv = function (messages, statsType = 'triage') { try { // Create CSV header row const csvFields = [ @@ -106,13 +124,23 @@ const messagesToCsv = function (messages) { 'text', 'blocks', 'attachments', - '_reactions', - '_levels', - '_statuses' + '_postedByWorkflowBuilder', + '_statsType', + '_all_reactions', + '_threadedReplyCount', + '_threadedReplyUsersCount' ] - const statusFields = triageConfig._.statuses.map(s => `_status_${s}`) - const levelFields = triageConfig._.levels.map(l => `_level_${l}`) - const csvOpts = { fields: csvFields.concat(levelFields, statusFields) } + + // add specific columns related to triage stats if relevant + if (statsType === 'triage') { + csvFields.push('_levels') + csvFields.push('_statuses') + const statusFields = triageConfig._.statuses.map(s => `_status_${s}`) + const levelFields = triageConfig._.levels.map(l => `_level_${l}`) + csvFields.concat(levelFields, statusFields) + } + + const csvOpts = { fields: csvFields } const csvString = parseJsonToCsv(messages, csvOpts) return csvString } catch (e) { From 04cd023cb819581db3ad2386354f791af065d165 Mon Sep 17 00:00:00 2001 From: Mark Silverberg Date: Sat, 20 Jun 2020 13:12:53 -0700 Subject: [PATCH 14/20] Add full reactions object to output Also properly string statsType to csv generator --- app.js | 2 +- helpers/messages.js | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app.js b/app.js index 8ec7b14..416be41 100644 --- a/app.js +++ b/app.js @@ -179,7 +179,7 @@ app.view('channel_selected', async ({ body, view, ack, client, logger, context } // Try to parse our object to CSV and upload it as an attachment try { // Convert object to CSV - const csvString = messagesToCsv(allMessagesEnriched) + const csvString = messagesToCsv(allMessagesEnriched, statsType) // Upload CSV File await client.files.upload({ diff --git a/helpers/messages.js b/helpers/messages.js index f06d969..9f56217 100644 --- a/helpers/messages.js +++ b/helpers/messages.js @@ -51,7 +51,7 @@ const getAllMessagesForPastHours = async function (channelId, nHoursToGoBack, cl return allMessages } -const filterAndEnrichMessages = function (messages, fromChannel, teamBotId, statsType = 'triage') { +const filterAndEnrichMessages = function (messages, fromChannel, teamBotId, statsType) { // First, filter out messages from the team's bot const filteredMessages = messages.filter(m => { if (m.bot_id !== teamBotId) return true @@ -84,8 +84,8 @@ const filterAndEnrichMessages = function (messages, fromChannel, teamBotId, stat } } - // Do additional status and level analysis for triage stats requests if (statsType === 'triage') { + // Do additional status and level analysis for triage stats requests // Add array attributes we will populate later message._statuses = [] message._levels = [] @@ -105,13 +105,16 @@ const filterAndEnrichMessages = function (messages, fromChannel, teamBotId, stat message._statuses.push(status) } }) + } else if (statsType === 'generic') { + // If generic analysis, let's dive deeper into the count of each emoji + // nothing currently added for generic analysis } }) return enrichedMessages } -const messagesToCsv = function (messages, statsType = 'triage') { +const messagesToCsv = function (messages, statsType) { try { // Create CSV header row const csvFields = [ @@ -124,6 +127,7 @@ const messagesToCsv = function (messages, statsType = 'triage') { 'text', 'blocks', 'attachments', + 'reactions', '_postedByWorkflowBuilder', '_statsType', '_all_reactions', From 1f74be3fb72280a2aaa6a36b61e9b5bca089fa4c Mon Sep 17 00:00:00 2001 From: Mark Silverberg Date: Sat, 20 Jun 2020 14:56:44 -0700 Subject: [PATCH 15/20] For generic statsType, add a count column for each reaction --- helpers/messages.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/helpers/messages.js b/helpers/messages.js index 9f56217..5250a12 100644 --- a/helpers/messages.js +++ b/helpers/messages.js @@ -108,6 +108,10 @@ const filterAndEnrichMessages = function (messages, fromChannel, teamBotId, stat } else if (statsType === 'generic') { // If generic analysis, let's dive deeper into the count of each emoji // nothing currently added for generic analysis + message.reactions.forEach((reaction) => { + const key = `_nUsersReactedWith_${reaction.name}` + message[key] = reaction.count + }) } }) @@ -137,11 +141,21 @@ const messagesToCsv = function (messages, statsType) { // add specific columns related to triage stats if relevant if (statsType === 'triage') { - csvFields.push('_levels') - csvFields.push('_statuses') + csvFields.push('_levels', '_levels') + const statusFields = triageConfig._.statuses.map(s => `_status_${s}`) const levelFields = triageConfig._.levels.map(l => `_level_${l}`) + csvFields.concat(levelFields, statusFields) + } else if (statsType === 'generic') { + // Get a unique list of `nUsersReactedWith_` keys so we can use them as columns in the csv + const allReactionKeys = new Set() + messages.forEach((m) => { + Object.keys(m) + .filter((k) => k.includes('nUsersReactedWith_')) + .forEach((k) => allReactionKeys.add(k)) + }) + csvFields.concat(Array.from(allReactionKeys)) } const csvOpts = { fields: csvFields } From 07b8cefed7074a65fa5336f6f8ec746b838b9fc3 Mon Sep 17 00:00:00 2001 From: Mark Silverberg Date: Sat, 20 Jun 2020 14:57:25 -0700 Subject: [PATCH 16/20] remove statsType column --- helpers/messages.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/helpers/messages.js b/helpers/messages.js index 5250a12..dbe1367 100644 --- a/helpers/messages.js +++ b/helpers/messages.js @@ -62,10 +62,9 @@ const filterAndEnrichMessages = function (messages, fromChannel, teamBotId, stat // Loop through all messages and enrich them additional attributes so we can do filters on them later enrichedMessages.forEach(message => { - // Regardless of statsType, we want to populate the channel ID, _statsType, and array of _reactions + // Regardless of statsType, we want to populate a few key/values // Add `channel` attribute with the channel we retrieved the message from message.channel = fromChannel - message._statsType = statsType // Create a new `_all_reactions` attribute with an array of all reactions (regardless of if they are relevant to triage analysis) message._all_reactions = message.reactions @@ -133,7 +132,6 @@ const messagesToCsv = function (messages, statsType) { 'attachments', 'reactions', '_postedByWorkflowBuilder', - '_statsType', '_all_reactions', '_threadedReplyCount', '_threadedReplyUsersCount' From dc607ea5ec4876a9e7d27f8ff01289439929afad Mon Sep 17 00:00:00 2001 From: Mark Silverberg Date: Sat, 20 Jun 2020 15:04:06 -0700 Subject: [PATCH 17/20] Update file name --- app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.js b/app.js index 416be41..611ab3f 100644 --- a/app.js +++ b/app.js @@ -186,7 +186,7 @@ app.view('channel_selected', async ({ body, view, ack, client, logger, context } channels: msgWorkingOnIt.channel, content: csvString, title: `All messages from the past ${nHoursToGoBack} hours`, - filename: 'allMessages.csv', + filename: `${selectedChannelId}_last${nHoursToGoBack}hours_allMessages_${statsType}.csv`, filetype: 'csv', thread_ts: msgWorkingOnIt.ts }) From 6d8c1647683f7a8a7eec68080eda53dbda8ea5ca Mon Sep 17 00:00:00 2001 From: Mark Silverberg Date: Sat, 20 Jun 2020 15:04:10 -0700 Subject: [PATCH 18/20] Fix columns --- helpers/messages.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/helpers/messages.js b/helpers/messages.js index dbe1367..b2af09c 100644 --- a/helpers/messages.js +++ b/helpers/messages.js @@ -139,12 +139,12 @@ const messagesToCsv = function (messages, statsType) { // add specific columns related to triage stats if relevant if (statsType === 'triage') { - csvFields.push('_levels', '_levels') + csvFields.push('_levels', '_stats') const statusFields = triageConfig._.statuses.map(s => `_status_${s}`) const levelFields = triageConfig._.levels.map(l => `_level_${l}`) - csvFields.concat(levelFields, statusFields) + csvFields.push(...levelFields, ...statusFields) } else if (statsType === 'generic') { // Get a unique list of `nUsersReactedWith_` keys so we can use them as columns in the csv const allReactionKeys = new Set() @@ -153,9 +153,11 @@ const messagesToCsv = function (messages, statsType) { .filter((k) => k.includes('nUsersReactedWith_')) .forEach((k) => allReactionKeys.add(k)) }) - csvFields.concat(Array.from(allReactionKeys)) + console.log(allReactionKeys) + csvFields.push(...Array.from(allReactionKeys)) } + console.log(csvFields) const csvOpts = { fields: csvFields } const csvString = parseJsonToCsv(messages, csvOpts) return csvString From 7ed5a4a3bd9b91f56fc412c31c60021196e2e502 Mon Sep 17 00:00:00 2001 From: Mark Silverberg Date: Sat, 20 Jun 2020 16:11:33 -0700 Subject: [PATCH 19/20] Rename shortcut and update docs --- docs/DEPLOY_Heroku.md | 4 ++-- docs/SETUP.md | 4 ++-- views/modals.blockkit.js | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/DEPLOY_Heroku.md b/docs/DEPLOY_Heroku.md index 4e9b379..6cef23f 100644 --- a/docs/DEPLOY_Heroku.md +++ b/docs/DEPLOY_Heroku.md @@ -30,7 +30,7 @@ In a new tab, do the following. Be sure to replace `awesome-app-name-you-entered - Create a new Shortcut - Select **Global** shortcut - Choose a descriptive name and description for your shortcut, for example: - - Name: Show triage stats + - Name: Show channel stats - Description: Calculate stats for a triage channel - For the Callback ID, it is important you set it to `channel_stats` - Enter your Select Menus Options Load URL `https://awesome-app-name-you-entered.herokuapp.com/slack/events` @@ -76,7 +76,7 @@ In a new tab, do the following. Be sure to replace `awesome-app-name-you-entered 2. Try out your freshly deployed app! 1. Visit your app's App Home tab to see the current configuration (you can edit `config.js` and restart the application to make changes) - 2. Execute your shortcut by entering "Show triage stats" in the quick switcher (CMD+k) or by using the lightning bolt ⚡️ symbol right below the message input field in Slack and filling out the form. You should receive a DM from the bot. + 2. Execute your shortcut by entering "Show channel stats" in the quick switcher (CMD+k) or by using the lightning bolt ⚡️ symbol right below the message input field in Slack and filling out the form. You should receive a DM from the bot. 3. Wait for the (by default) top-of-the-hour hourly update in any channel the bot has been invited to. 3. Take a moment to check out your Heroku addon. diff --git a/docs/SETUP.md b/docs/SETUP.md index 0107b5c..fdd2121 100644 --- a/docs/SETUP.md +++ b/docs/SETUP.md @@ -25,7 +25,7 @@ In your preferred web browser: - Create a new Shortcut - Select **Global** shortcut - Choose a descriptive name and description for your shortcut, for example: - - Name: Show triage stats + - Name: Show channel stats - Description: Calculate stats for a triage channel - For the Callback ID, it is important you set it to `channel_stats` - Enter your Select Menus Options Load URL `https://your-host/slack/events` @@ -94,7 +94,7 @@ Back in your preferred web browser... 2. Try out your app! 1. Visit your app's App Home tab to see the current configuration (you can edit `config.js` and restart the application to make changes) - 2. Execute your shortcut by entering "Show triage stats" in the quick switcher (CMD+k) or by using the lightning bolt ⚡️ symbol right below the message input field in Slack and filling out the form. You should receive a DM from the bot. + 2. Execute your shortcut by entering "Show channel stats" in the quick switcher (CMD+k) or by using the lightning bolt ⚡️ symbol right below the message input field in Slack and filling out the form. You should receive a DM from the bot. 3. Wait for the (by default) top-of-the-hour hourly update in any channel the bot has been invited to. 3. Lastly, note that in the default configuration of this app, you should have one and only one Node process running at a time as the scheduled job functionality runs _within_ the web application code courtesy of `node-cron`. diff --git a/views/modals.blockkit.js b/views/modals.blockkit.js index 628ff99..70643e9 100644 --- a/views/modals.blockkit.js +++ b/views/modals.blockkit.js @@ -137,7 +137,7 @@ module.exports = { value: 'triage', text: { type: 'plain_text', - text: 'Triage stats (look for specific emojis)' + text: 'Triage (report on specific emojis)' } }, options: [ @@ -145,14 +145,14 @@ module.exports = { value: 'triage', text: { type: 'plain_text', - text: 'Triage stats (look for specific emojis)' + text: 'Triage (report on specific emojis)' } }, { value: 'generic', text: { type: 'plain_text', - text: 'Generic stats' + text: 'Generic (report on all emoji reactions)' } } ] From ec2286625ca46fd94f3af01e844af0fcd1191281 Mon Sep 17 00:00:00 2001 From: Mark Silverberg Date: Sat, 20 Jun 2020 16:34:30 -0700 Subject: [PATCH 20/20] Update modal intro --- views/modals.blockkit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/modals.blockkit.js b/views/modals.blockkit.js index 70643e9..d1be69b 100644 --- a/views/modals.blockkit.js +++ b/views/modals.blockkit.js @@ -22,7 +22,7 @@ module.exports = { type: 'section', text: { type: 'mrkdwn', - text: ':wave: Please select a channel to retrieve triage stats for.' + text: ':wave: Please select a channel to retrieve stats for.' } }, {