Clone wiki

ac-koa-hipchat / FAQ

FAQ

How do I run my add-on on Heroku?

See Paas Deployment.

How can I add analytics to my add-on?

Try ac-koa-hipchat-keenio -- simple Keen.io analytics integration for ac-koa-hipchat based add-on projects. If you're using Heroku to host your add-ons, integration with the Keen.io add-on for Heroku is particularly easy.

How can I easily render Handlebar templates?

Take a look at the ac-koa-hipchat-notifier library.

How do I send room notifications from a third party webhook?

// This is a simplified example; to see a fully functional example add-on that achieves this, see:
// https://bitbucket.org/atlassianlabs/ac-koa-hipchat-mailroom/src/master/lib/mailroom.js

var MemoryStore = require('ac-node').MemoryStore;
var RestClient = require('ac-node-hipchat').RestClient;

// Share this with all rest clients in your app to avoid generating tokens for every notification
var tokenCache = MemoryStore();

// This assumes that you have a 'tenant' object somewhere equivalent to this.tenant in a normal HC webhook callback;
// see the following for an example of how to save the tenant object at installation time:
// https://bitbucket.org/atlassianlabs/ac-koa-hipchat-mailroom/src/master/web.js#cl-35
var tenantClient = RestClient().forTenant(tenant, tokenCache, ['send_notification']);

// You need a room id to send a notification with the tenant client.  If the add-on is installed in a room,
// you can use `tenant.room`, otherwise you need to determine the target room id somehow here as well...
yield tenantClient.sendNotification(tenant.room, 'Hello!');

I'm new to ES6 generators -- how do I combine them with vanilla JavaScript callbacks?

// How do I send a notification to a room when not in inside a generator function?  For example...

addon.webhook('room_message', /^\/sup$/, function *() {
  var query = { 'q' : '#Atlassian', 'result_type' : 'recent', 'count' : 1 };
  twitter.search(query, twitter.apiToken, twitter.apiTokenSecret, function(error, jsonObject, response, url) {
    if (error) {
      console.log('Error: ' + JSON.stringify(error));
    } else {
      var statuses = jsonObject['statuses'];
      var status = statuses[0];
      var username = status['user']['name'];
      var text = status['text'];
      console.log(username + ' said: ' + text);
      // TODO: Now, how do I send to the room?  'yield' does not work in a non-generator
      // yield this.roomClient.sendNotification('Looking...');
    }
  });
  yield this.roomClient.sendNotification('Looking...');
});

// Solution #1a: Don't worry about suspending the current function, since you probably don't really
// care if the send succeeded or not.

addon.webhook('room_message', /^\/sup$/, function *() {
  var query = { 'q' : '#Atlassian', 'result_type' : 'recent', 'count' : 1 };
  twitter.search(query, twitter.apiToken, twitter.apiTokenSecret, function(error, jsonObject, response, url) {
    if (error) {
      console.log('Error: ' + JSON.stringify(error));
    } else {
      var statuses = jsonObject['statuses'];
      var status = statuses[0];
      var username = status['user']['name'];
      var text = status['text'];
      // Note that the lack of a yield (or anything else) simply fires off the notification, but
      // doesn't bother to check the result.  If you care, see solution #1b.
      this.roomClient.sendNotification(username + ' said: ' + text);
    }
  // Note the added 'bind' here propagates the original 'this' to the callback. You could instead capture
  // the 'this' in the enclosing generator in a variable such as self, then use that inside the callback --
  // which you use is largely a matter of style and circumstance.
  }.bind(this));
  yield this.roomClient.sendNotification('Looking...');
});

// Solution #1b: But what if I want to know if it succeeded or not?  Maybe I needed a result from
// another roomClient or tenantClient call?

addon.webhook('room_message', /^\/sup$/, function *() {
  var query = { 'q' : '#Atlassian', 'result_type' : 'recent', 'count' : 1 };
  // Demonstrating the style of capturing the 'this' ref rather than binding the callback.
  var self = this;
  twitter.search(query, twitter.apiToken, twitter.apiTokenSecret, function(error, jsonObject, response, url) {
    if (error) {
      console.log('Error: ' + JSON.stringify(error));
    } else {
      var statuses = jsonObject['statuses'];
      var status = statuses[0];
      var username = status['user']['name'];
      var text = status['text'];
      // The roomClient and tenantClient APIs return promises. When in a Koa generator, you can conveniently
      // yield on promises (among other things). When in a normal function, you can use Promises/A+-compatible
      // callbacks. (Actually, the underlying ac-node-hipchat module uses RSVP for promises, which provides
      // a richer API than pure Promises/A+.)
      self.roomClient.sendNotification(username + ' said: ' + text).then(function () {
        console.log('Woot!');
      }, function (err) {
        console.error('Doh!', err.stack || err);
      });
    }
  });
  yield this.roomClient.sendNotification('Looking...');
});

// Solution #2a: Ok, but solutions #1a and #1b lose the power of generators by resorting to normal
// callbacks. That sucks; can we do anything to stay in generator-land? Well, yes, there are several
// viable approaches as it turns out. This case is ripe for the use of a thunk.

var thunkify = require('thunkify'); // https://github.com/tj/node-thunkify
var search = thunkify(twitter.search);
addon.webhook('room_message', /^\/sup$/, function *() {
  var query = { 'q' : '#Atlassian', 'result_type' : 'recent', 'count' : 1 };
  yield this.roomClient.sendNotification('Looking...');
  var jsonObject = yield search(query);
  var statuses = jsonObject['statuses'];
  var status = statuses[0];
  var username = status['user']['name'];
  var text = status['text'];
  yield this.roomClient.sendNotification(username + ' said: ' + text);
});

// Solution #2b: Ok, #2a was tight... but what if I need to use an API with callbacks that don't
// follow the typical Node.js callback pattern that thunkify can work with?

// You can resort to using promises again, but this time, manually create your own adapter.
var Promise = require('rsvp').Promise;
function search(query) {
  return new Promise(function (resolve, reject) {
    twitter.search(query, twitter.apiToken, twitter.apiTokenSecret, function(error, jsonObject, response, url) {
      if (error) {
        return reject(error);
      }
      resolve(jsonObject, response, url);
    });
  });
}
// Then use the custom adapter the same way we used the thunk in #2a.
addon.webhook('room_message', /^\/sup$/, function *() {
  var query = { 'q' : '#Atlassian', 'result_type' : 'recent', 'count' : 1 };
  yield this.roomClient.sendNotification('Looking...');
  var jsonObject = yield search(query);
  var statuses = jsonObject['statuses'];
  var status = statuses[0];
  var username = status['user']['name'];
  var text = status['text'];
  yield this.roomClient.sendNotification(username + ' said: ' + text);
});

// There are other solutions as well, but this should be enough tools to satisfy most needs.

Updated