diff --git a/README.md b/README.md index 9d7cd10..236d715 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ For the moment, harvest communication may be only set up using an account. To ha ###SLACK configuration -The Slack part uses a simple **incoming WebHook** that need to be created within the Slack application itself (See [https://api.slack.com/incoming-webhooks](https://api.slack.com/incoming-webhooks)). The only mandatory parameter that need to be set is `endpoint` which is the webhook endpoint. The configuration below contains additional params which are overriding the default settings for the webhook. All [params available for the webhook](https://api.slack.com/incoming-webhooks) can be used except `channel`, which will always be overridden by **slack username** of the user that receives the notifications. +The Slack part uses a simple **incoming WebHook** that needs to be created within the Slack application itself (See [https://api.slack.com/incoming-webhooks](https://api.slack.com/incoming-webhooks)). The only mandatory parameter that need to be set is `endpoint` which is the webhook endpoint. The configuration below contains additional params which are overriding the default settings for the webhook. All [params available for the webhook](https://api.slack.com/incoming-webhooks) can be used except `channel`, which will always be overridden by **slack username** of the user that receives the notifications. ``` "slack" : { @@ -68,6 +68,8 @@ For the moment the application is able to: - send periodical report notifications on Slack management channel defined in `cron.report.channel` setting according to cron time provided in `cron.report.cronTime` section +- send reminder messages via Slack to people who have no timers running according to `cron.remind.cronTime` setting. + ``` "cron" : { "notify" : { @@ -93,11 +95,13 @@ If any of the section for `cron` settings are not provided, the cron job will no The API provides given endpoints: -- `http|https://your.domain.you/notify-all` Notifies all users present in the users config +- `http|https://your.domain.you/notify-all` Notifies all users present in the users config providing information about their started tasks. + +- `http|https://your.domain.you/notify-user/user_id` Notifies user given by `user_id` which represents **either Harvest user ID or Slack username** about her/his started tasks. -- `http|https://your.domain.you/notify-user/user_id` Notifies user given by `user_id` which represents **either Harvest user ID or Slack username** +- `http|https://your.domain.you/notify-management` Notifies management channel provided in the `channel` property of the `POST` request. -- `http|https://your.domain.you/notify-management` Notifies management channel provided in the `channel` property of the `POST` request +- `http|https://your.domain.you/remind-all` Triggers notifications to users wo have no tasks started on Harvest. The `notify-all` and `notify-user` are **actions names**; this will be useful when creating authorization token. diff --git a/app/api/controllers/actions/notify_management.js b/app/api/controllers/actions/notify_management.js index dbebab1..3b44ce2 100644 --- a/app/api/controllers/actions/notify_management.js +++ b/app/api/controllers/actions/notify_management.js @@ -36,7 +36,7 @@ function validateCreateDate (dateString) * @param {Function} next The next callback to apply * @returns {undefined} */ -function notifyManagementController(req, res, next) +function notifyManagementController (req, res, next) { var from = req.body.from || null, to = req.body.to || null, diff --git a/app/api/controllers/actions/reminder_all.js b/app/api/controllers/actions/reminder_all.js index 1585888..57bb6ce 100644 --- a/app/api/controllers/actions/reminder_all.js +++ b/app/api/controllers/actions/reminder_all.js @@ -35,10 +35,10 @@ function doNotify (harvestResponse, userId) */ function remindAllController (req, res, next) { - reminder.remind(harvest.users, function () { + reminder.remind(harvest.users, function () { res.success = true; next(); - }); + }); } module.exports = function (app, config) diff --git a/app/api/controllers/actions/reminder_user.js b/app/api/controllers/actions/reminder_user.js new file mode 100644 index 0000000..222216e --- /dev/null +++ b/app/api/controllers/actions/reminder_user.js @@ -0,0 +1,59 @@ +/*jshint node: true*/ +'use strict'; + +var harvest = require('./../../../services/harvest')('default'), + notifier = require('./../../../services/notifier'), + _ = require('lodash'), + logger = require('./../../../services/logger.js')('default'), + reminder = require('./../../../services/reminder/index'), + tools = require('./../../../services/tools.js') +; + + +/** + * Notifies the users on Slack + * + * @param {Object} harvestResponse The harvest API response + * @param {Number} userId The harvest user Id + * @returns {undefined} + */ +function doNotify (harvestResponse, userId) +{ + notifier.notify('reminder', { + harvestUserId : userId, + harvestResponse : harvestResponse + }); +} + + +/** + * Notifies all mapped slack users to activate their Harvest if that's not done. + * + * @param {Object} req The request object + * @param {Object} res The response object + * @param {Function} next The next callback to apply + * @returns {undefined} + */ +function remindUserController (req, res, next) +{ + var users; + try { + users = tools.validateGetUser(harvest.users, req.params.user); + } catch (err) { + res.success = false; + res.errors = [ + err.message + ]; + next(); + return; + } + reminder.remind(users, function () { + res.success = true; + next(); + }); +} + +module.exports = function (app, config) +{ + app.use('/api/remind-user/:user', remindUserController); +}; \ No newline at end of file diff --git a/app/services/cronjobs/lib/jobs.js b/app/services/cronjobs/lib/jobs.js index e377d7a..b722114 100644 --- a/app/services/cronjobs/lib/jobs.js +++ b/app/services/cronjobs/lib/jobs.js @@ -56,7 +56,8 @@ JobsHolder.prototype = { cronTime = job.getCronTime(config), handler = job.getJob(config), description = job.getDescription(), - autoRun = job.shouldRunNow(); + autoRun = job.shouldRunNow() + ; logger.info('Setting up cron job for: "' + description + '" with cron time: ', cronTime, {}); diff --git a/app/services/interactive_session/index.js b/app/services/interactive_session/index.js index 3ff2a62..13087f5 100644 --- a/app/services/interactive_session/index.js +++ b/app/services/interactive_session/index.js @@ -145,16 +145,39 @@ if (resolver === null) { remind : { execute : function (params, callback) { + var userId = params.name, + users, + error = null + ; + if (!!userId) { + try { + users = tools.validateGetUser(harvest.users, userId); + } catch (err) { + users = null; + error = err; + } + } else { + users = harvest.users; + } var step = interactiveSession .getDefault() .createStep(params.userId, {}, params.action) + .addParam('users', users) + .addParam('error', error) ; callback(null, step); }, postExecute : function (step, callback) { - reminder.remind(harvest.users, null, function (results) { + var users = step.getParam('users'), + error = step.getParam('error') + ; + if (error !== null) { + callback(); + return; + } + reminder.remind(users, null, function (results) { step.addParam('results', results); var userId = step.getParam('userId'); @@ -175,7 +198,12 @@ if (resolver === null) { getView : function (step) { var results = step.getParam('results'), - view = []; + error = step.getParam('error'), + view = [] + ; + if (error) { + return error.message; + } if (Object.size(results.notified) > 0) { view.push('Notified given users:'); diff --git a/app/services/tools.js b/app/services/tools.js index 6e1c870..1f9a4f6 100644 --- a/app/services/tools.js +++ b/app/services/tools.js @@ -145,6 +145,36 @@ module.exports = { } return obj[param]; + }, + + + /** + * Validates if correct user has been sent + * + * @param {Object} users A map of harvest id -> slack name of all + * configured users + * @param {String} userId Either harvest id or slack name + * @returns {Object} A hashmap of harvestId -> slackName + * @throws {Error} If invalid user provided + */ + validateGetUser : function (users, userId) + { + var userMap = {}, + found = false + ; + + _.each(users, function (slackName, harvestId) { + if ((String(harvestId) === String(userId)) || (String(slackName) === String(userId))) { + userMap[harvestId] = slackName; + found = true; + } + }); + + if (!found) { + throw new Error('Invalid user provided.'); + } else { + return userMap; + } } }; \ No newline at end of file diff --git a/config.dist.json b/config.dist.json index 148c1b7..9413972 100644 --- a/config.dist.json +++ b/config.dist.json @@ -40,6 +40,9 @@ "report" : { "reportTitle" : "Weekly activity report", "channel" : "#channel_name" + }, + "remind" : { + "cronTime" : "00 00 12 * * 1-5" } }, "logger" : { diff --git a/test/functional/commands.js b/test/functional/commands.js index 9acab8c..b4a6bfa 100644 --- a/test/functional/commands.js +++ b/test/functional/commands.js @@ -189,6 +189,80 @@ describe('Functional: Non-dialogue commands', function () { }); }); + it ([ + 'Should call harvest API and grab given user current timeline ', + 'Send remind messages to this user with empty timeline. ', + 'Returns message that says which user received the notification.' + ].join('\n'), function (done) { + + var sendMessage = slack.sendMessage; + harvestModule.client.get = function (url, data, cb) { + cb(null, []); + }; + + slack.sendMessage = function (text, config, callback) + { + callback(null, true, ''); + }; + + + harvest.users = { + 23456 : 'some_user' + }; + + request.post({ + url : 'http://localhost:3333/api/timer', + form : { + token : 'thisissomeauthtoken', + user_name : 'some_user', + text : 'remind 23456' + } + }, function (err, res, body) { + slack.sendMessage = sendMessage; + var view = [ + 'Notified given users:', + '', + 'some_user' + ]; + expect(body).to.be.equal(view.join('\n')); + done(); + }); + }); + + + it ('Should return an error response if invalid user was provided.', function (done) { + + var sendMessage = slack.sendMessage; + harvestModule.client.get = function (url, data, cb) { + cb(null, []); + }; + + slack.sendMessage = function (text, config, callback) + { + callback(null, true, ''); + }; + + + harvest.users = { + 23456 : 'some_user' + }; + + request.post({ + url : 'http://localhost:3333/api/timer', + form : { + token : 'thisissomeauthtoken', + user_name : 'some_user', + text : 'remind some_non_existing_user' + } + }, function (err, res, body) { + slack.sendMessage = sendMessage; + + expect(body).to.be.equal('Invalid user provided.'); + + done(); + }); + }); + it ([ 'Should call harvest API and grab all users current timelines. ', diff --git a/test/functional/notifications.js b/test/functional/notifications.js index fab7601..849823e 100644 --- a/test/functional/notifications.js +++ b/test/functional/notifications.js @@ -39,7 +39,7 @@ describe('Functional: Notifications', function () { var userId = 2345; harvestMock.setUsers({ - '2345' : 'some_test_user' + '23456' : 'some_test_user' }); diff --git a/test/unit/services/reminder/index.js b/test/unit/services/reminder/index.js index 7f446eb..5982669 100644 --- a/test/unit/services/reminder/index.js +++ b/test/unit/services/reminder/index.js @@ -56,10 +56,10 @@ describe('reminder', function () { describe('remind', function () { it('Should send reminder messages to all people whose harvest day entries reports are empty.', function (done) { - var users = { - '1234' : 'some_user1', - '2345' : 'some_user2', - '3456' : 'some_user3' + var users = { + '12345' : 'some_user1', + '23456' : 'some_user2', + '34567' : 'some_user3' }, counter = 0, sendMessage = slack.sendMessage @@ -99,14 +99,14 @@ describe('reminder', function () { expect(results.successes).to.be.deep.equal({ - '1234' : 'some_user1', - '2345' : 'some_user2', - '3456' : 'some_user3' + '12345' : 'some_user1', + '23456' : 'some_user2', + '34567' : 'some_user3' }); expect(results.notified).to.be.deep.equal({ - '1234' : 'some_user1', - '3456' : 'some_user3' + '12345' : 'some_user1', + '34567' : 'some_user3' }); expect(results.errors).to.be.deep.equal({}); diff --git a/test/unit/services/tools.js b/test/unit/services/tools.js index f2ffe2f..bea6510 100644 --- a/test/unit/services/tools.js +++ b/test/unit/services/tools.js @@ -277,4 +277,57 @@ describe('tools', function () { }).to.throw(Error, errorMessage) }); }); + + describe('tools.validateGetUser', function () { + var users = { + 12345 : 'some_user1', + 23456 : 'some_user2', + 34567 : 'some_user3' + }; + + var given = [ + { + given : 'some_user1', + expected : { + 12345 : 'some_user1' + } + }, + { + given : 'some_user2', + expected : { + 23456 : 'some_user2' + } + }, + { + given : 'some_user3', + expected : { + 34567 : 'some_user3' + } + }, + { + given : 12345, + expected : { + 12345 : 'some_user1' + } + }, + { + given : 23456, + expected : { + 23456 : 'some_user2' + } + }, + { + given : 34567, + expected : { + 34567 : 'some_user3' + } + } + ]; + + it('Should return proper harvest id -> slack name pair object for given user id.', function () { + _.each(given, function (givenObject) { + expect(tools.validateGetUser(users, givenObject.given)).to.be.deep.equal(givenObject.expected); + }); + }); + }); }); \ No newline at end of file