Confluence JIRA連携時のリンク表示をカスタマイズする

[Confluenceのユーザーマクロの基本](http://qiita.com/ymd_aaa/items/fd9c6ca15fc5bed22ab2)から始まり、

つぎに[既存マクロを自作マクロに組み込む方法](http://qiita.com/ymd_aaa/items/356627353005a6600c71)を紹介し、

いよいよ今回はその応用編で、JIRA連携時のリンクカスタマイズ方法を紹介します。

今までの記事はこれを書くために作成していた節もあります。

# JIRAとConfluenceの連携について
連携方法については私は詳しくないのでこの記事では触れませんが、連携するとどんなことになるのかについてだけ先に紹介しておきます。

連携すると、ConfluenceのページにJIRAで作成した課題のURLを貼るだけで、

<img width="331" alt="JIRA連携.png" src="https://qiita-image-store.s3.amazonaws.com/0/149859/e6c4b205-12f7-465d-84e2-7da7558e5396.png">

このようにJIRAへのリンクが見やすく変化します。

ステータスや要約が簡単にみれるのがいいですね。
ただ、個人的には「おしい」と思っていました。
というのも表示されている項目が少なすぎます。

優先度と担当者ぐらいはデフォルトで表示してほしかったなと思いました。

そこで、

前回紹介した[既存マクロを組み込む方法](http://qiita.com/ymd_aaa/items/356627353005a6600c71)を利用して、優先度と担当者も表示するように変更するマクロを作成してみました。

<img width="450" alt="JIRA連携カスタム.png" src="https://qiita-image-store.s3.amazonaws.com/0/149859/536a0bf6-fa84-fd0a-3658-9be4df84233c.png">

こんな感じで優先度（色のついた矢印）と担当の名前が追加されます。

これをするために作ったマクロがあるのですが、ちょっと説明が長くなりそうなので先にコード全てを貼ってしまいます。


# JIRA連携リンクの表示をカスタマイズするマクロ
**マクロ本体の処理**
マクロ本文なし

```html:テンプレート
## @noparams
#set($page=$pageManager.getPage($renderContext.spaceKey,$renderContext.pageTitle))

## JIRAにおけるID(key)のリスト
#set($keyList = "")

## JIRAリンクマクロを抽出するため、<p>タグでページのテキストを分割する。
#set($splits=$page.bodyContent.body.split("<p>"))

#set($p='"')

## 分割した<p>タグの中にJIRAリンクマクロがあるか１つずつ探す。
#foreach($s in $splits)
    #if($s.contains("ac:name=${p}jira${p}") && $s.contains("${p}key${p}"))
        ## JIRAリンクマクロを発見したのでJIRAにおけるID(key)を抽出する
        #set($linkPageId = $s.replaceFirst(".*key${p}>","").replaceFirst("</ac:p.*",""))
        #set($keyList = $keyList + "," + $linkPageId)
    #end
#end

## 抽出したIDのリストを使って改めてJIRAから情報を収集する
#if(!$keyList.equals(""))
    #set($trimed = $keyList.replaceFirst(",",""))
    <ac:structured-macro ac:name="jira" ac:schema-version="1" >
        <ac:parameter ac:name="columns">type,key,priority,summary,status,assignee</ac:parameter>
        <ac:parameter ac:name="jqlQuery">key in ($trimed) </ac:parameter>
        <ac:parameter ac:name="maximumIssues">1000</ac:parameter>
        <ac:parameter ac:name="serverId">[JIRAのサーバーのID]</ac:parameter>
    </ac:structured-macro>
#end
<script type="text/javascript">
jQuery(document).ready(function(){
    
    main();
    
    function main(){
        // 既存のJIRAリンクのDOMを収集
        var originJiraLinks = collectOriginalJiraLink();
        
        // 一括収集したJIRAのDOM(tr単位)を収集
        var tableJiraLinks =collectTrJiraLink();
        
        // DOMをデータに変換 
        var dataJiraLinks = transDataFromTr(tableJiraLinks);
        
        // 既存のJIRAリンクを、収集したJIRAの情報で上書き
        updateOriginJiraLink(originJiraLinks,dataJiraLinks);
        
        // 更新イベントハンドリング
        jQuery("[id^='refresh-module']").on('DOMSubtreeModified', function() {
            var oldId = jQuery(this).attr("id");
            startCheckUpdate(oldId);
        });
    }
    
    /*
     更新イベントハンドリング
     */
    function startCheckUpdate(oldId){
        // domが再生成されるまで待機
        if(jQuery("#"+oldId)[0]){
            setTimeout(function(){
                console.log("wait")
                startCheckUpdate(oldId);
            },100);
        }else{
            main();
        }
    }
    
    /*
     既存のJIRAリンクのDOMを収集
     */
    function collectOriginalJiraLink(){
        return jQuery(".jira-issue");
    }
    
    /*
     一括収集したJIRAのDOM(tr単位)を収集
     */
    function collectTrJiraLink(){
        return jQuery("[id^='refresh-module']").find("tr.rowNormal,tr.rowAlternate");
    }
    
    /*
     DOMをデータに変換 
     */
    function transDataFromTr(trs){
        var dataList = [];
        jQuery(trs).each(function(){
            var tds = jQuery(this).find("td");
            var data = {};
            data["type"] = jQuery(tds[0]).html(); 
            data["key"] = jQuery(tds[1]).text().trim();
            data["link"] = jQuery(tds[1]).html();
            data["priority"] = jQuery(tds[2]).html();
            data["summary"] = jQuery(tds[3]).find("a").text().trim();
            data["status"] = jQuery(tds[4]).html();
            data["assignee"] = jQuery(tds[5]).text().trim();

            dataList.push(data);
        });
        return dataList;
    }
    
    /*
     既存のJIRAリンクを、収集したJIRAの情報で上書き
     */
    function updateOriginJiraLink(doms,data){
        jQuery(doms).each(function(){
            // 該当するデータを探す
            var unitData = findConsistentData(this,data);
            var jThisDom = jQuery(this);
            jThisDom.empty();
            // type
            jThisDom.append(unitData.type);
            // key
            jThisDom.append(jQuery(unitData.link).attr("target","_blank"));
            jThisDom.append(" - ");
            // 優先度用のdomを生成
            jThisDom.append(unitData.priority);
            // タイトル
            var summary = jQuery("<span class='summary'>" + unitData.summary + "</span>");
            jThisDom.append(summary);
            // ステータス
            jThisDom.append(jQuery(unitData.status));
            // 担当者
            var tantoDom = jQuery("<span style='font-size:11px;font-weight:bold;margin:0px 5px;'> 担当:" + unitData.assignee + "</span>");
            jThisDom.append(tantoDom);                
        });
    }
    
    /*
     該当するデータを探す
     */
    function findConsistentData(dom,data){
        var key;
        if(jQuery(dom).find("a")[0]){
            key = jQuery(dom).find("a").text().trim();                        
        } else {
            key = jQuery(dom).text().trim();            
        }
        
        for(var i = 0; i < data.length; i++){
            if(key == data[i].key){
                return data[i];
            }
        }
    }
    
})
</script>
```

JavaScriptが多いですね。
いろいろ泥臭いことをやっていますがまずは流れだけ簡潔に書きますと、

1. レンダリング前のテキストを取得。
1. JIRAリンクマクロを抽出。
1. さらにJIRAにおけるIDを抽出。
1. 抽出したIDを利用してJIRAに問い合わせ。
1. 問い合わせた結果をJavaScriptでJIRAリンクに反映。

とやっております。

今回はこの中の「2.JIRAリンクマクロの抽出」 と 「4.抽出したIDを利用してJIRAに問い合わせ」について詳しく解説したいと思います。


# 2.JIRAリンクマクロの抽出
抽出するためには、もともとこのマクロがどんな風に書かれているのかを知る必要があります。
それを調べるため、前回の記事で作成した「レンダリング前のテキストを表示するマクロ」を利用します。※公式ページも見たのですが、同じビジュアルのマクロが見つかりませんでした。

するとJIRAリンクマクロがこのようになっているのが確認できます。

```html:JIRAリンクマクロ
<ac:structured-macro ac:name="jira" ac:schema-version="1" ac:macro-id="ランダム文字列">
  <ac:parameter ac:name="server">Your Company JIRA</ac:parameter>
  <ac:parameter ac:name="serverId">[サーバーID]</ac:parameter>
  <ac:parameter ac:name="key">PJSAMPLE-577</ac:parameter>
</ac:structured-macro>
```
※serverIdは連携しているJIRAのサーバーIDが入っています。

これをページから抽出したら、次のステップの `3.さらにJIRAにおけるIDを抽出` は **ac:name="key"** の値を抽出すればよさそうに見えます。

このような感じでJIRAにおけるID抽出ができそうなことは確認できました。
続いて、そのIDを使ってJIRAから情報を入手する方法をマクロを解析して調査します。

# 4.抽出したIDを利用してJIRAに問い合わせ
どうやってJIRAから値を取得するかなのですが、実はJIRAリンクマクロには設定できるパラメータが用意されていてそれを設定することで任意の項目の値をとることができるんです。
<img width="409" alt="JIRAリンク編集.png" src="https://qiita-image-store.s3.amazonaws.com/0/149859/5e98f4b8-45ac-342d-0663-5dc4fbc51a28.png">
この編集を押すと
<img width="830" alt="JIRAリンク編集２.png" src="https://qiita-image-store.s3.amazonaws.com/0/149859/f237ef07-6f1e-1666-d43e-e2b6d9e28a97.png">

こんな画面が表示され、表示形式を**表**にすると
<img width="830" alt="JIRAリンク別バージョン.png" src="https://qiita-image-store.s3.amazonaws.com/0/149859/66cadd88-10f0-679d-1866-a1459c35fcf7.png">

見た目はだいぶ変わりましたが、担当者や優先度、その他諸々取得できました。

ちなみにこの表形式のマクロについては公式にも情報があります。
[JIRA Issues Macro](https://confluence.atlassian.com/conf58/jira-issues-macro-771892345.html)

もちろん構文も載っています。

```:公式のサンプル
<ac:structured-macro ac:name="jira">
  <ac:parameter ac:name="columns">key,summary,type,created,assignee,status</ac:parameter>
  <ac:parameter ac:name="server">Atlassian JIRA</ac:parameter>
  <ac:parameter ac:name="serverId">144880e9-a1111-333f-9412-ed999a9999fa</ac:parameter>
  <ac:parameter ac:name="jqlQuery">project = CONF AND component = documentation AND resolution = unresolved</ac:parameter>
</ac:structured-macro>
```

これをみると、上で確認したJIRAリンクマクロとほとんど一緒なのがわかります。元が一緒なので当然ですが。
違いは`key`がなくなったことと、`jqlQuery`と`columns`が追加されている点なので、これらを変更すると表形式に変わりそうな気配がします。

`jplQuery`はJIRAで課題を検索するときに使うSQLのようなものです。
取得したIDを使って検索したいので`column名 IN (値,値...)`を使います。

`columns`は表示する項目を羅列します。
とりあえず今回必要なものだけ残しました。

```
<ac:structured-macro ac:name="jira" ac:schema-version="1" >
    <ac:parameter ac:name="columns">type,key,priority,summary,status,assignee</ac:parameter>
    <ac:parameter ac:name="jqlQuery">key in (PJSAMPLE-577,PJSAMPLE-578,PJSAMPLE-579) </ac:parameter>
    <ac:parameter ac:name="maximumIssues">1000</ac:parameter>
    <ac:parameter ac:name="serverId">[JIRAのサーバーのID]</ac:parameter>
</ac:structured-macro>
```

結果
<img width="572" alt="JIRAリンク複数.png" src="https://qiita-image-store.s3.amazonaws.com/0/149859/39894fe2-ee1d-a73a-d621-9003cb33dc68.png">

欲しい情報を一発で取れることが確認できました。


# ページ内に貼られているJIRAリンクの情報をJIRAから取得する方法まとめ
ここで一度JIRAリンクからの情報取得方法をまとめます

1. ページ内のJIRAリンクマクロを解析してそれぞれのkeyを取得する。
1. 取得したkeyを以下のマクロのjqlQueryに指定して情報を取得する。

```html
<ac:structured-macro ac:name="jira" ac:schema-version="1" >
    <ac:parameter ac:name="columns">type,key,priority,summary,status,assignee</ac:parameter>
    <ac:parameter ac:name="jqlQuery">key in ([カンマ区切りでkeyを入れる]) </ac:parameter>
    <ac:parameter ac:name="maximumIssues">1000</ac:parameter>
    <ac:parameter ac:name="serverId">[JIRAのサーバーのID]</ac:parameter>
</ac:structured-macro>
```

情報を取得したら、あとは、Javascriptでごりごりやって通常時のJIRAリンクのHTMLを書き換えます。
このページの最初に書いたマクロのjsはほとんどその書き換えのための処理です。多分jsの部分は色々やり方があると思うので特に解説はしません。

# 実行例
ちなみに冒頭のマクロを使用するとこんな感じになります。
編集画面
<img width="217" alt="実用例編集.png" src="https://qiita-image-store.s3.amazonaws.com/0/149859/4dac4a41-e56f-06f2-b8a6-aa39d080451e.png">
出力
<img width="837" alt="実用例出力.png" src="https://qiita-image-store.s3.amazonaws.com/0/149859/1ae18527-193b-7978-62c0-e5c3546d9f36.png">

※下にでている表を隠したい場合は、`function main`の中に、以下のような記述を追加すると隠せます。

```js
if (jQuery("[id^='jira-issues']").is(':visible')) {
   jQuery("[id^='jira-issues']").hide();
}
```

# 表形式の弱点
ちなみに、JIRAリンクマクロなのですが、単一課題の表示であればページリロードで最新の情報を取りに行くのですが、表形式にしてしまうとページリロードしても最新の情報に更新されません。

その代わりなのでしょうか、表の下に<img width="65" alt="更新ボタン.png" src="https://qiita-image-store.s3.amazonaws.com/0/149859/5a17ee69-a5c8-3ada-e2f6-2ca1faba1425.png">が用意されていて、これを押すときちんと更新されます。

# おわりに
過去２回の記事に比べると今回は一気にコード量が増えました。（ほとんどJavaScriptですが）
ただその分、より実践的な自作マクロの作り方を紹介したつもりです。
これまでの記事を見て、「いろいろできそうだな」と興味を持ってくれる人がでてきたらいいなと思います。
まあでも本当に今回は飛ばしすぎて細かい部分の説明が足りていないかもしれません、特にVelocityとかjavaScriptの部分とか。

そういった部分は参考ページのリンクを用意しましたのでそちらをご確認いただければと思います。

# 参考
JIRA Issues Macro - Atlassian Documentation
https://confluence.atlassian.com/conf58/jira-issues-macro-771892345.html

JIRAのJQLでよく使う構文を色々(適宜追加)
http://qiita.com/takahirono7/items/4052321fe26130957c03

Velocity - Velocity User Guide
http://www.jajakarta.org/velocity/velocity-1.4/docs-ja/user-guide.html

Velocityで文字列リテラル中にダブルクォーテーションを書きたい - idesaku blog
http://d.hatena.ne.jp/idesaku/20090204/1233728141

Qiita 要素の中身が変更されたことを知る
http://qiita.com/Hashibata/items/1113d9cc6a1ad04a9644