JIRA Cloudのアドオン開発入門


※サーバー版はこの手順の対象外。サーバー版は[こちら](http://qiita.com/itagakishintaro/items/b30d0b29cb7ef397c06d)を参照。
[Atlassianのデベロッパーサイト](https://developer.atlassian.com/static/connect/docs/latest/index.html)に情報はある。
しかし、基本部分だけがまとまっている情報源があまりないので、最低限の簡単なアドオンをつくる流れをメモします。

Node.js + Expressでのアプリ開発知識が前提。

# 1. 環境準備
## テスト環境
公式サイト: [Development Setup](https://developer.atlassian.com/static/connect/docs/latest/guides/development-setup.html)

簡単には、次のとおり。

テスト環境の取得：http://go.atlassian.com/cloud-dev
開発モードの設定："Manage add-ons"の"Setting"で"Enable development mode"を有効に
アドオンのインストール確認："Manage add-ons"の"Upload add-on"で"https://ac-static-tutorial.aerobatic.io/atlassian-connect.json" を入力

## ローカルデプロイ環境
公式サイト: [Developing locally](https://developer.atlassian.com/static/connect/docs/latest/developing/developing-locally.html)

## Node.jsのインストール

Mac

```bash
brew install node
```

Windows
http://nodejs.org/download/

## ngrokのインストール

```bash
npm install -g ngrok
```

# 2. スケルトン作成
公式サイト: [Tutorial: Display your projects in JIRA](https://developer.atlassian.com/static/connect/docs/latest/tutorials/project-activity-tutorial.html)

## ACE(Atlassian-Connect-Express)のインストール

```bash
npm install -g atlas-connect
```

## スケルトン作成

```bash
atlas-connect new -t [name]
cd [name]
npm install
```

# 3. Hello World作成
公式サイト: [Tutorial: Display your projects in JIRA](https://developer.atlassian.com/static/connect/docs/latest/tutorials/project-activity-tutorial.html)

## 設定ファイル(atlassian-connect.json)の変更
仕様: [Add-On Descriptor](https://developer.atlassian.com/static/connect/docs/latest/modules/)


[xxx]の部分は入力。
`"system.top.navigation.bar"`は画面上部のメニューバーにメニューがでる設定。
`"condition": "user_is_logged_in"`はログインしたときだけアクセスできる設定。
`"authentication"`はアドオンとJIRAの連携の際の認証。例えば、JIRAのIssueをAPIで取得して利用するアドオンの場合は認証が必要（この記事では対象外。使う場合は[Tutorial: Display your projects in JIRA](https://developer.atlassian.com/static/connect/docs/latest/tutorials/project-activity-tutorial.html)参照）。


```json:atlassian-connect.json
{
     "key": "[key]",
     "name": "[name]",
     "description": "[description]",
     "vendor": {
         "name": "[vendor name]",
         "url": "[vendor url]"
     },
     "baseUrl": "{{localBaseUrl}}",
     "links": {
         "self": "{{localBaseUrl}}/atlassian-connect.json",
         "homepage": "{{localBaseUrl}}/atlassian-connect.json"
     },
     "authentication": {
         "type": "none"
     },
      "modules": {
          "generalPages": [

              {
                  "key": "[page key]",
                  "location": "system.top.navigation.bar",
                  "name": {
                      "value": "[page name]"
                  },
                  "url": "/[ページの相対パス]",
                  "conditions": [{
                      "condition": "user_is_logged_in"
                  }]
              }
          ]
      }
 }
```
 
メニューをドロップダウンして表示する場合は、`modules`部分を次のようにするとよい。

```json
"modules":{
  "webItems":[
    {
      "name":{
        "value":"Main"
      },
      "location":"system.top.navigation.bar",
      "url":"/abc",
      "key":"jira-dropdown"
    }
  ],
  "generalPages":[
    {
      "name":{
        "value":"Action A"
      },
      "url":"/actiona",
      "key":"jira-actiona",
      "location":"jira-dropdown/jira-section",
      "weight":1
    },
    {
      "name":{
        "value":"Action B"
      },
      "url":"/actionb",
      "key":"jira-help",
      "location":"jira-dropdown/jira-section",
      "weight":2
    }
  ],
  "webSections":[
    {
    "name":{
      "value":"Main Section"
    },
    "location":"jira-dropdown",
    "key":"jira-section"
    }
  ]
}
```
 
## node.jsのルーティング設定(index.js)
1つめの`app.get`はルートにアクセスすると`atlassian-connect.json`を返す設定。
2つめの`app.get`は`/hello-world`にアクセスすると`hello-world.hbs`を返す設定(`atlas-connect new`はhtmlテンプレートにhbsが設定される)。
 
```js:index.js
module.exports = function (app, addon) {

    app.get('/', function (req, res) {
        res.format({
            'text/html': function () {
                res.redirect('/atlassian-connect.json');
             },
            'application/json': function () {
                res.redirect('/atlassian-connect.json');
            }
        });
    });

    app.get('/hello-world', addon.authenticate(), function (req, res) {
            res.render('hello-world');
        }
    );
    
};
```
 

## hbsの作成

`layout.hbs`をみるとわかるが、[AUI(Atlassian User Interface)](https://docs.atlassian.com/aui/latest/index.html)が読み込まれているので、画面の装飾はAUIを使うのが基本。
もちろん、Bootstrapなどを入れてもよいが、AUIの方がAtlassian製品に組み込んだときに違和感がない。

```hbs:views/hello-world.hbs
{{!< layout}}
<h1>{{title}}</h1>
```

# 4. 動作確認
## ローカル

```bash
node app.js
```

次のURLで動作確認
http://localhost:3000
http://localhost:3000/hello-world

## テスト環境で確認

```bash
ngrok http 3000
```

このときに出力されるhttpsのForwardingのURLをメモする。
```
https://xxxxxxxx.ngrok.io -> localhost:3000
```

メモしたURLをconfig.jsonの"localBaseUrl"に設定する

```json:config.json
"localBaseUrl": "https://xxxxxxxx.ngrok.io"
```

はじめに作成した「テスト環境」で"Manage add-ons"の"Upload add-on"で"https://xxxxxxxx.ngrok.io" を入力

# 5. Herokuへのデプロイ
ローカルで動作確認ができたら、Herokuなどにデプロイする。
Herokuの場合はProcfileが作成されているので、後は`atlassian-connect.json`の`baseUrl`をherokuのデプロイ先URLにして、herokuにデプロイすればよい。

```bash
heroku login
heroku create [name]
git checkout -b heroku
```

`atlassian-connect.json`の`baseUrl`を`http://[name].herokuapp.com`に変更。

```bash
git add .
git commit -m "Change baseUrl for heroku"
git remote add heroku https://git.heroku.com/[name].git
git push heroku heroku:master
```

# 6. DBの利用
Atlassian-connectでは、次の制限があるが、少量のデータは自前でDBを用意しなくても使うことができる。自前でDBを用意する場合は、セキュリティ設定などが必要で手間がかかるので、簡単なアプリを作る場合には便利だ。

[Hosted data storage](https://developer.atlassian.com/static/connect/docs/latest/concepts/hosted-data-storage.html)

* 他のアドオンとデータの共有はできない
* データは100個までで、各データ32KBまで
* データ形式はJSONのみ
* ログインされている場合だけ、AP.requestによってAPIアクセスできる
* 同時更新時の対応はなく、その場合は後勝ち

## API

まず、アドオンがホストされるAtlassian CloudのURL(baseUrl)を取得する。

```js:addon.js
var baseUrl;

$( function() {
    // 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;
    }

    var params = getQueryParams( document.location.search );
    baseUrl = params.xdm_e + params.cp;
    console.log( 'baseUrl', baseUrl );
} );
```

次のこのbaseUrlに`AP.request`を使ってREST APIをたたく。

[API仕様](https://developer.atlassian.com/static/connect/docs/latest/rest-apis/index.html)

* [baseUrl]/rest/atlassian-connect/1/addons/{addonKey} [GET]
* [baseUrl]/rest/atlassian-connect/1/addons/{addonKey}/properties [GET]
* [baseUrl]/rest/atlassian-connect/1/addons/{addonKey}/properties/{propertyKey} [GET, PUT, DELETE]


```js:example
var getX = function() {
    AP.request( baseUrl + '/rest/atlassian-connect/1/addons/T4T/properties/x', {
        success: function( d ) {
            console.log( 'get done', d );
        },
        error: function( d ) {
            console.log( 'get error', d );
        }
    } );
}
```