How we built a HipChat Connect add-on in a week and a day

December 11th 2015 Jake Furler in Hipchat, Atlassian Connect, NPS

Last month the HipChat Connect platform was announced at Summit. HipChat Connect brings the power of Atlassian Connect to HipChat, allowing you to create add-ons that run on the platform of your choice and add functionality and custom user interfaces to HipChat.

During the most recent ShipIt at Atlassian, a small team of us created an add-on using HipChat Connect. The HipChat Connect platform was super simple to use, and we want to share how exactly we went about creating the add-on so that you can give it a go yourself!

NPS for HipChat

Net Promoter Score (NPS) is a measure of how customers feel about your product and what you can do about it. NPS for HipChat, our add-on, integrates with SurveyMonkey to bring NPS feedback from customers into your product team's HipChat room. Once configured, the add-on allows you to view a list of all responses to your NPS survey in the HipChat sidebar. You can filter responses, view each response in detail, and bring them into the chat for discussion with your team.

NPS for HipChat is listed publicly on the Atlassian Marketplace, so you can install it and try it out for yourself. We've also made it open source on Bitbucket.

Check out this quick demo to see how it works:


So how did we make it?

Atlassian Connect Express

Atlassian Connect is language agnostic - you can build add-ons using whatever web development languages and frameworks you want. However, to make things a bit easier Atlassian provides a couple of frameworks for developing HipChat Connect add-ons - one for Python (flask) and one for Node.js (Atlassian Connect Express). We chose to use Atlassian Connect Express (ACE). ACE can take care of authentication of requests using JSON Web Token (JWT), data persistence, and the install/uninstall callbacks for you. It’s really simple, and it allows you to focus on developing the add-on itself. To get started, after installing ACE on your machine, you just need to run the following command to set up your project structure and environment:

atlas-connect new -t hipchat <project_name>

The descriptor

The first thing we want to look at is atlassian-connect.json, the descriptor file. This is the core of any Atlassian Connect add-on. It declares everything the add-on intends to do and points HipChat to the right places on our add-on server. In our descriptor, we're declaring that we want to use three main extension points: a glance, a web panel, and a configure page.

A web panel is used to display your own content in the sidebar using ordinary HTML, CSS, and JavaScript. Here's how we define ours in the descriptor, and how it ends up being displayed:


"webPanel": [
  {
    "key": "nps.webpanel.nps",
    "location": "hipchat.sidebar.right",
    "name": {
      "value": "NPS"
    },
    "url": "{{localBaseUrl}}/sidebar",
  }
],
    

We're providing HipChat with a unique key and a location, which tells HipChat to display the web panel in the sidebar. The url parameter provides HipChat with an endpoint on our add-on server from which it can fetch the content we want to display. Everything inside the orange box in the image above is contained within an iframe, served from the endpoint we defined. We can display whatever we want.

To link to the web panel, we're also defining a glance in our descriptor. A glance is a button that appears in the sidebar alongside all the other add-ons.


"glance": [
  {
    "key": "nps.glance.nps",
    "name": {
      "value": "NPS"
    },
    "queryUrl": "{{localBaseUrl}}/glance",
    "target": "nps.webpanel.nps"
  }
],
    

Again, we're providing HipChat with a unique key and an endpoint on our server where it can fetch dynamic data. In our case, the dynamic data we return is a sunny icon if the NPS is on track and a cloudy icon if not. The value of the target parameter is the key of the web panel we defined above. This tells HipChat to open the web panel when the glance gets clicked.

Finally, we're defining a configure page. This one's pretty simple.


"configurable": {
  "url": "{{localBaseUrl}}/config"
},
    

Like the web panel, this will end up inside an iframe as well, displayed when we install the add-on. In the descriptor, we're just providing an endpoint that we're calling config from which our content will be served. Let's look at how we're implementing that endpoint as an example.

Implementing an endpoint on your server

Atlassian Connect Express is based on the Express framework, so if you’ve used that before, or if you’ve used ACE in any of our other products, some of this may look familiar to you. The first thing we want to do is define the config endpoint using express, and use ACE to authenticate any incoming requests.

app.get('/config',
    addon.authenticate(),
    function(req, res) {

The addon object is provided by ACE and all we have to do is call authenticate() on it. ACE handles the installation callback for you. When we call authenticate, behind the scenes ACE is matching the JWT token of the incoming request against the information established during installation. We can use this to make sure any incoming request is coming from a known HipChat room. From there we can perform whatever backend logic we need to and pass data to a handlebars template using express by calling res.render. Here's our entire endpoint implementation:

app.get('/config',
  addon.authenticate(),
  function(req, res) {
    var key = generateIdentifier(req);
    addon.settings.get('accessToken', key).then(function(token) {
      var surveyMonkeyURI = surveymonkey.getSurveyMonkeyURI(key);
      surveymonkey.getSurveyList(key).then(function(surveyList) {
        addon.settings.get('surveyName', key).then(function (surveyName) {
          addon.settings.get('goalNPS', key).then(function (goal) {
            res.render('config', {
              surveyMonkeyURI: surveyMonkeyURI,
              accessToken: token,
              surveyName: surveyName,
              goalNPS: goal,
              surveyList: surveyList.data.surveys,
              badGoal: req.query.badGoal,
              goodGoal: req.query.goodGoal,
              badSurvey: req.query.badSurvey
            });
          });
        });
      });
    });
  }
);

We're simply performing some SurveyMonkey-specific logic, and we're also using ACE to fetch some stored client data. ACE handles data persistence for you. Once you've set up your database through a few lines in a config file, you can achieve simple key-value data persistence by calling addon.settings.set and addon.settings.get. In our case here, we're storing the survey name and the goal NPS for every room.

And that's it! That's our endpoint. Everything else is ordinary web development.

Communication between components

The framework provides total flexibility where it’s useful, but also simple methods for providing data where a consistent design is needed. We can post cards into a room's chat through HipChat's REST API. When a user clicks on the "discuss" button in our sidebar web panel, we post to the HipChat notification endpoint. We include a JSON object containing the information that we want to display, and HipChat will format it into a card design and post it into the room.

{
  "style":"application",
  "description": {
      "format": "html",
      "value": "It's really useful, but the performance could be improved"
  },
  "title":"NPS",
  "attributes":[
      {
          "value":{
              "label":data.score,
              "style":scoreLozengeStyle
          },
          "label":"Score"
      }
  ],
  "id":data.comment,
  "icon":{
      "url":logo
  }
}

You can also define the description of a card using basic HTML. We're using this to display the description as a link, which we can tell HipChat to open in the sidebar using the data-target parameter. We can even tell HipChat to pass a JSON object of parameters when it hits that link using the data-target-options parameter. The description value becomes:

"description": {
    "format": "html",
    "value": "<a href='#' data-target='hipchat-nps-add-on:nps.webpanel.nps' " +
                "data-target-options='" + optionsObject + "'\><i>" +
                '\"' + data.comment + '\"' + "</i></a>"
},

So we're able to put all of our detailed data from the sidebar into the link of a chat card without displaying it. And then when that link gets clicked, the data gets passed back into the sidebar to be displayed in detail.

Deploying

Atlassian Connect is deployment agnostic too. We chose to deploy on Heroku just because it was simple to use with Node.js and Redis. But you can deploy your add-on service however and wherever you want.

Try it yourself!

We made a functional version of this add-on within 24 hours at ShipIt. We developed it into the current version we just showed you during a second week-long hackathon. It’s not difficult or complicated - you can have a look at our [source code on Bitbucket] (https://bitbucket.org/atlassianlabs/nps-for-hipchat). So have a test of our add-on on Marketplace and have a go at making one yourself!