ConfluenceでJiraを操作するページを作る

JiraとConfluenceを連携させて使っていると、Jiraでかゆいところに手が届かないのをREST APIで何とかしたいと思うことがある。個人的に作業するだけなら`curl`でも良いが、慣れていない人も含め、複数の人が同じことをしやすいようにしたいので、UIが欲しい。

定型的なチケット作成や、テンプレートチケットのクローン程度であれば簡単にできるので、Confluenceページ上のHTMLマクロ内にスクリプトを少し書くだけで実現できる。

# HTML部分
AUIでは色々なコンポーネントが用意されているが、まずはフォームがあればそれらしい画面が作れそうだった。[AUIのフォームの説明ページ](https://docs.atlassian.com/aui/8.3.3/docs/forms.html)では基本的なフォームの形が紹介されている。

例えば、ボタンを押したら何かするようにしたければ、以下のようにする。

```html
<form ... class="aui">
    <div class="field-group">...</div>
    <div class="field-group">...</div>
    <div class="buttons-container">
        <div class="buttons" onclick="onClick()">...</div>
    </div>
</form>

<script type="text/javascript">
(function () {
const API_BASE="<JIRAサーバ>/rest/api/2"

async function onClick() {
    ...
}

})();
</script>
```

> テキストボックスでEnterキーを押したり、Submitボタンをクリックしたりすると、意図しないSubmitアクションが実行されてしまった。ボタンは`type="submit"`の代わりに`type="button"`にすることでSubmitアクションを止められたが、テキストボックスは一工夫いるらしい。「[Enterキーを無効にする方法](https://qiita.com/mimoe/items/89b317be5e38bfbc44ee)」がうまく使えるか試してみる。

# Jiraチケットを取得する
チケットを取得するときは、`/rest/api/2/issue/<KEY or ID>`を`GET`する。HTTPリクエストを出すには`AJS.$.ajax()`(jQueryの機能)を使う。

```javascript
async function getIssue( key ) {
    var issue;
    await AJS.$.ajax( {
        url: API_BASE + '/issue/' + key,
        type: 'get',
        successs: function( data ) {
            issue = data;
        }
    } );
    return issue;
}
```
> JiraとConfluenceを連動して使っているのであれば多分Originが同じになるのでCORSを気にすることはないが、同一PCで動かしていてポートが違うような場合、Jiraサーバ側に特定のOriginを許可する設定が必要になる。

# Jiraチケットを作成する
チケットを作成するときは、`{ fields: { field: value } }`という形のJSONを`/rest/api/2/issue`に`POST`する。以下では、戻り値(issue)の`fields`だけ取り出して返している。

```javascript
async function postIssue( fields ) {
    var resultFields;
    await AJS.$.ajax( {
        url: API_BASE + '/issue',
        type: 'post',
        data: JSON.stringify( { 'fields': fields } ),
        headers: {
            'Content-Type': 'application/json'
        },
        success: function( data ) {
            resultFields = data.fields;
        }
    } );
    return resultFields;
}
```

# Jiraチケットを複製する
チケットを作る際のルール・テンプレートが柔らかく、暫定的にチーム内で統一して作りたい場合があると思う。そんなとき、ベースとなるチケットを複製するという手段がある。

以下は、`srcKey`(string)で指定したチケットを`parentKey`(string)以下にコピーする関数。コピーする対象のフィールドは`fields`(string[])で指定する。

```javascript
function extractFields( src, fieldNames ) {
    let fields = {};

    for( let fieldName of fieldNames ) {
        let tmp = src[ fieldName ];

        // null, undefined を受け付けない場合があるので除外しておく
        if( tmp ) {
            fields[ fieldName ] = tmp;
        }
    }
}

async function cloneIssue( srcKey, parentKey, fieldNames ) {
    const src = await getIssue( srcKey );
    const parent = await getIssue( parentKey );

    if( !src || !parent ) {
        throw new Error( 'Invalid Key.' ); // 適当   
    }

    const projectKey = parent.fields.project.key;

    let fields = extractFields( src.fields, fieldNames );
    fields[ 'project' ] = { key: projectKey };
    fields[ 'parent' ] = { key: parentKey };

    await postIssue( fields );
}
```

# UIと繋ぐ
先ほどの`cloneIssue`関数をUIと繋ぐ場合、ボタン操作などをトリガとして実行する関数(ここでは`onClick`)で、設定値を持つinput要素等から`val()`関数で設定値を取得し、`cloneIssue`関数をコールする。成功・失敗を通知する機能もあると良いかもしれないが、基本は以下のようなイメージ。

```javascript
const API_BASE="<JIRAサーバ>/rest/api/2"
const src = AJS.$('#src');         // コピーしたいチケットのキーを入力するinput要素のID
const parent = AJS.$('#parent');   // コピー先の親チケットのキーを入力するinput要素のID
const FIELDNAMES = [
    'custom_field_xxxxx', 
    'custom_field_yyyyy', 
    'custom_field_zzzzz', 
    'description',
    'issuetype',
    'summary'
];  // コピーしたいフィールド

// イベント処理の関数…UIのボタンから呼ぶようにする。
async function onClick() {
    try {
        const srcKey = src.val();
        const parentKey = parent.val();
        await cloneIssue( srcKey, parentKey, FIELDNAMES );
    } catch( err ) {
        console.log( err ); // 適当
    }
}
```

# 参考情報
## Confluence
ConfluenceのHTMLマクロでは、[AUI(Atlassian User Interface)](https://docs.atlassian.com/aui/)で提供されるUI部品やヘルパ関数`AJS`を使うことができる。AJSには`jQuery`が含まれていて、`AJS.$`で利用できる。また、ページのメタ情報を`AJS.params`で取得することもできる。

AUIのコードサンプルには「Edit in codepen」や「Edit in jsfiddle」と書かれたリンクがあり、AJSも含めどんな結果になるかを確認しながらコードを書くことができるようになっている。

## Jira
### REST API資料
[JiraのREST API](https://developer.atlassian.com/server/jira/platform/rest-apis/)については、サーバ版とCloud版で異なるらしい。どちらが使えるか調べて、リンク先からREST APIの資料を参照する。

### 400 Bad Requestが返ってくる
REST APIでチケットを作ろうとすると、fieldsメンバ内に必ず設定しなくてはならない項目が不足するか、設定してはいけない項目があることで、400応答が返ってくることがある。そんな時は応答に含まれるエラーメッセージ(`error.responseText`)から、何がダメだったかが分かるようになっている。例えば`issuetype`が無い、親チケットが無い、エピックにエピック名が無いなどがある。

### 401 Unauthorizedが返ってくる
ConfluenceとJiraでユーザの情報を共有して連携している場合(大抵はこれ)、ブラウザでJiraにログインすることで解消する。

### 415 Unsupported Media Typeが返ってくる
HTTPリクエスト時に、`data`の中身を`JSON.stringify`し忘れるか、`Content-Type`ヘッダを忘れるかのどちらかで起きる。

### カスタムフィールドと設定値を調べる
チケットのカスタムフィールドは`custom_field1234`のような名前なので、何を示すか分からない。また、`priority`のような選択式のメンバについても、設定値が`id:10500`のようになっていて、何を示すか分からない。これらは、以下APIで調べることができる。

* カスタムフィールド: /rest/api/2/customFields
* フィールドの選択肢: /rest/api/2/customFieldOption/{id}

> フィールドの選択肢`customFieldOption`は、idが分かってからでないと呼び出せないので、用途としてはidに対応する文字列を取得する程度にしか使えない。