Documentation

Tutorial: Display your projects in JIRA

In this tutorial, you'll learn about:

This tutorial shows you how to build a static Connect add-on that displays your JIRA projects in a table, accessible via an Activity link in the header.

Your add-on will use the JIRA REST API to get information about projects in your instance. You'll use the Node.js framework and Atlassian Connect Express (ACE) to interface with JIRA. Finally, you'll create a table of your projects using D3.js.

When you're finished, your add-on will look similar to this:

Configuring your development environment

In this step, you'll confirm you have Node.js installed, and install the Atlassian Connect Express (ACE) toolkit. ACE helps you create Connect add-ons using Node.js. It also detects changes made to your atlassian-connect.json descriptor file, so you don't need to continually restart your add-on as you develop. Importantly, ACE also handles JSON web token (JWT), so that requests between your add-on and the JIRA application are signed and authenticated.

  1. Install Node.js 4.5.0 or later. If you use Homebrew, you can use the following command:
    $ brew install node
    If you use Windows or don't use Homebrew, you can download and install Node.js directly. You might need to enter sudo.
  2. Install ACE using the npm install command.
    $ npm install -g atlas-connect
  3. Create a new ACE project called jira-activity.
    $ atlas-connect new -t jira jira-activity
  4. Change to your new jira-activity directory.
    $ cd jira-activity/
  5. Install Node.js dependencies for your jira-activity project.
    $ npm install
  6. Get a JIRA development environment by following the Development Setup

Install your add-on and add an Activity link

Now you've got the basic architecture for your add-on. If you open your new jira-activity directory, you'll see essentials like the atlassian-connect.json descriptor in the root. You'll also see an app.js file.

In this step, you'll prune some of the stub code, and install your add-on in JIRA using ACE.

  1. Open the atlassian-connect.json descriptor file in your favorite editor or IDE.
  2. Replace the key, name, description, and vendor name and URL with these fields:
     {
         "key": "jira-activity",
         "name": "JIRA Project Activity",
         "description": "A Connect add-on that displays JIRA projects in a table",
         "vendor": {
             "name": "Atlassian Developer Relations",
             "url": "https://developer.atlassian.com/"
         },
    
    This names your add-on in in your JIRA instance, and essentially makes it yours.
  3. Replace the content of modules with a generalPages module:

      "generalPages": [
    
         {
             "key": "activity",
             "location": "system.top.navigation.bar",
             "name": {
                 "value": "Activity"
             },
             "url": "/activity",
             "conditions": [{
                 "condition": "user_is_logged_in"
             }]
         }
     ]
    

    This adds an Activity link in the JIRA header (system.top.navigation.bar), and sets a URL for your add-on to use under /activity. It also provides a condition so that the REST API shows private projects visible to a logged in user, not just public projects.

    At this point, your descriptor file should look like this:

     {
         "key": "jira-activity",
         "name": "JIRA Project Activity",
         "description": "A Connect add-on that displays JIRA projects in a table",
         "vendor": {
             "name": "Atlassian Developer Relations",
             "url": "https://developer.atlassian.com/"
         },
         "baseUrl": "{{localBaseUrl}}",
         "links": {
             "self": "{{localBaseUrl}}/atlassian-connect.json",
             "homepage": "{{localBaseUrl}}/atlassian-connect.json"
         },
         "authentication": {
             "type": "jwt"
         },
         "lifecycle": {
             // atlassian-connect-express expects this route to be configured to manage the installation handshake
             "installed": "/installed"
         },
         "scopes": [
             "READ"
         ],
          "modules": {
              "generalPages": [
    
                  {
                      "key": "activity",
                      "location": "system.top.navigation.bar",
                      "name": {
                          "value": "Activity"
                      },
                      "url": "/activity",
                      "conditions": [{
                          "condition": "user_is_logged_in"
                      }]
                  }
              ]
          }
     }
    
  4. Open a new terminal window.

  5. From your jira-activity root, start up a Node.js server:
    $ node app.js
    This starts up your add-on on a server locally.
  6. Make your add-on available on the public internet, see Developing locally
  7. Install the add-on in your development JIRA instance.
  8. Refresh JIRA in your browser.
    You'll see the Activity label in the header:
    This link doesn't go anywhere – yet. You'll fix this in future steps.
  9. Back in your editor, open routes/index.js.
    From here, you'll add the /activity route to your app.
  10. After the /hello-world stub code, add:

     app.get('/activity', addon.authenticate(), function(req, res) {
         res.render('activity', { title: "JIRA activity" });
     });
    

    Your routes/index.js file should resemble this:

     module.exports = function (app, addon) {
    
         // Root route. This route will serve the `atlassian-connect.json` unless the
         // documentation url inside `atlassian-connect.json` is set
         app.get('/', function (req, res) {
             res.format({
                 // If the request content-type is text-html, it will decide which to serve up
                 'text/html': function () {
                     res.redirect('/atlassian-connect.json');
                 },
                 // This logic is here to make sure that the `atlassian-connect.json` is always
                 // served up when requested by the host
                 'application/json': function () {
                     res.redirect('/atlassian-connect.json');
                 }
             });
         });
    
         // The following is stub code for a Hello World app provided by ACE.
         // You can remove this section since it's not used in this tutorial, 
         // or leave it here – it makes no difference to this add-on.
    
         // This is an example route that's used by the default "generalPage" module.
         // Verify that the incoming request is authenticated with Atlassian Connect
         app.get('/hello-world', addon.authenticate(), function (req, res) {
                 // Rendering a template is easy; the `render()` method takes two params: name of template
                 // and a json object to pass the context in
                 res.render('hello-world', {
                     title: 'Atlassian Connect'
                     //issueId: req.query('issueId')
                 });
             }
         );
    
         // Add any additional route handlers you need for views or REST resources here...
         app.get('/activity', addon.authenticate(), function(req, res) {
             res.render('activity', { title: "JIRA activity" });
         });
     };
    

    This route titles your Activity page "JIRA activity", and ensures that your add-on is authenticated.

  11. Close and save your atlassian-connect.json and routes/index.js files.

Build the static Activity page

You've added a link in the JIRA header, and now you'll define how your page should look. In this step, you'll add the capability for your add-on to use D3.js, and style the page using Atlassian User Interface (AUI).

  1. Open views/layout.hbs.
  2. Add the following to the views/layout.hbs file, near the closing </body> tag (following the </section> line):
    <script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
    <script src="{{furl '/js/jira-activity.js'}}"></script>
    
    This lets you to use D3.js for your chart. This file is a shared container of styles (like AUI) and scripts you can use for all pages of your add-on.
  3. Create a new file called views/activity.hbs.
    This file is a template you'll use to render the /activity URL.
  4. Add the following content:

     {{!< layout}}
     <header class="aui-page-header">
         <div class="aui-page-header-inner">
             <div class="aui-page-header-main intro-header">
                 <h1>{{title}}</h1>
             </div>
         </div>
     </header>
    
     <div class="aui-page-panel main-panel">
         <div class="aui-page-panel-inner">
             <section class="aui-page-panel-item">
                 <div class="aui-group">
                     <div class="aui-item">
                         <div class="projects">
                         </div>
                     </div>
                 </div>
             </section>
         </div>
     </div>
    
  5. Create a file called public/js/jira-activity.js.
    This file will take a list of your projects, and generate an HTML table using D3.js.
  6. Add the following content:

     // Canned functionality for JIRA Activity
     $(function() {
         "use strict";
    
         // Get parameters from query string
         // and stick them in an object
         function getQueryParams(qs) {
             qs = qs.split("+").join(" ");
    
             var params = {}, tokens,
                 re = /[?&]?([^=]+)=([^&]*)/g;
    
             while (tokens = re.exec(qs)) {
                 params[decodeURIComponent(tokens[1])] =
                     decodeURIComponent(tokens[2]);
             }
    
             return params;
         }
    
         AP.define('JiraActivity', {
             buildProjectTable: function(projects, selector) {
    
                 var params = getQueryParams(document.location.search);
                 var baseUrl = params.xdm_e + params.cp;
    
                 function buildTableAndReturnTbody(hostElement) {
                     var projTable = hostElement.append('table')
                         .classed({'project': true, 'aui': true});
    
                     // table > thead > tr, as needed below
                     var projHeadRow = projTable.append("thead").append("tr");
                     // Empty header
                     projHeadRow.append("th");
                     // Now for the next column
                     projHeadRow.append("th").text("Key");
                     projHeadRow.append("th").text("Name");
    
                     return projTable.append("tbody");
                 }
    
                 var projectBaseUrl = baseUrl + "/browse/";
    
                 var rootElement = d3.select(selector);
                 var projBody = buildTableAndReturnTbody(rootElement);
    
                 // For each data item in projects
                 var row = projBody.selectAll("tr")
                     .data(projects)
                     .enter()
                     .append("tr");
    
                 // Add a td for the avatar, stick a span in it
                 row.append("td").append('span')
                     // Set the css classes for this element
                     .classed({'aui-avatar': true, 'aui-avatar-xsmall': true})
                     .append('span')
                     .classed({'aui-avatar-inner': true})
                     .append('img')
                     // Set the atribute for the img element inside this td > span > span
                     .attr('src', function(item) { return item.avatarUrls["16x16"] });
    
                 // Add a td for the project key
                 row.append("td").append('span')
                     .classed({'project-key': true, 'aui-label': true})
                     // set the content of the element to be some text
                     .text(function(item) { return item.key; });
    
                 // And finally, a td for the project name & link
                 row.append("td").append('span')
                     .classed({'project-name': true})
                     .append("a")
                     // make the name a link to the project
                     .attr('href', function(item) { return projectBaseUrl + item.key; })
                     // since we're in the iframe, we need to set _top
                     .attr('target', "_top")
                     .text(function(item) { return item.name; });
             }
         });
     });
    
  7. Open public/js/addon.js. This file uses the JIRA REST API to request project information you use to generate the table.
  8. Add the following content:

    /* add-on script */
    // MyAddon functionality
    $(function() {
     // Call REST API via the iframe
     // Bridge functionality
     // JiraActivity is registered by an external script that was included
     AP.require(['request', 'JiraActivity'], function(request, JiraActivity) {
         request({
             url: '/rest/api/2/project',
             success: function(response) {
                 // Convert the string response to JSON
                 response = JSON.parse(response);
    
                 // Call your helper function to build the
                 // table, now that you have the data
                 JiraActivity.buildProjectTable(response, ".projects");
             },
             error: function(response) {
                 console.log("Error loading API (" + uri + ")");
                 console.log(arguments);
             },
             contentType: "application/json"
         });
     });
    });
    

    AP.require calls the Connect API, and url is the REST API you call (/rest/api/2/project). Your JIRA instance returns a string response, and success converts it to JSON.

  9. Save and close all files.

  10. Restart the Node.js app. Shut down the app with CTRL+C and re-run the node app.js command.
  11. Click Activity in the header.
    You'll see an empty page with your "JIRA activity" title:

    Your page is blank since your JIRA instance doesn't yet have any data, but you'll fix that in the next step!

Add some data, and verify your add-on works

Your add-on is essentially done, but you don't have any data to validate that your table works. In this step, you'll manually add a few projects, and validate that your table reflects the changes.

  1. Click Projects > Create Project in the header.
    Run through the prompts and create a project.
  2. Repeat as desired. The more data you create, the more your add-on displays.
  3. Check your add-on between adding data.
    You should see your Activity table update each time you click the link.
    Here's an example what you'll see, using example projects:

Additional resources

Thanks for trying the tutorial! If you'd like to check your work, feel free to check out the repository here:

https://bitbucket.org/atlassianlabs/connect-jira-activity/overview

Additionally, we're always interested in making our docs and developer experiences better. If you have feedback about this tutorial or other Connect documentation, let us know.