JIRA Automation + Confluence APIで業務効率化

# 導入

普段私のチームではAtlassianのJIRAを使いタスクを管理し、Confluenceを使ってナレッジを管理しています。しかしJIRAを日頃使うなかで、チケットの説明欄やコメント欄にタスクに関する重要な情報や知見が記載され、それらがConfluenceに集約されないことが多々ありました。
今回はこの「**JIRAとConfluenceでナレッジが散在してしまう**」という課題についてJIRA AutomationとConfluence APIを活用した解決策を紹介したいと思います。（なお課題を解決するにあたってそもそもの運用を見直す、なども考えられますが一旦無視します）

:::note
JIRAを使ってConfluenceのAPIを叩く記事についてはあまり見受けられず、公式のドキュメントも比較的わかりづらかったため、今回の利用用途限らずAtlassianスーツの自動化を試みている方のためになればと思います。
:::

## この記事で実現するフロー

1. ユーザーが特定のJIRAチケットの説明欄やコメント欄に知見を記載
2. 任意のタイミングでチケット上にある「ナレッジに追加」ボタンを押すと、チケットのタイトルや説明文を取得しConfluence APIを自動で実行
3. Confluence上の「ナレッジページ」にある特定テーブルに、新しい行として該当するチケットのタイトルやリンク、説明文を追加

# 前準備

## Confluenceページ作成
まずは自身のConfluenceで適当なページを作り、IDを控えてください。IDは作成したコンフルページのURLを確認した際の`https://mydomain/wiki/spaces/pj_id/pages/{page_id}`とある、一番最後の数字の羅列`{page_id}`に該当します。
なお今回は「Confluence内にあるテーブルをアップデートしたい」ため、事前に必要な項目が揃ったテーブルを作っておきます。

![Pasted image 20240807230356.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3031575/0a90bbc4-1a0c-6d4f-aee9-8fed7ee32f6e.png)
## APIキー
Confluence APIを叩くにあたって、まずはAtlassianのAPIキーを発行します。
詳細は[こちら](https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/)のページを参考にいただければと思いますが、基本的にはAtlassianにログインしている状態でプロフィール設定ページ内セキュリティタブにある[APIトークンの作成と管理](https://id.atlassian.com/manage-profile/security/api-tokens)画面で発行可能です。

![Pasted image 20240805214710.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3031575/f4e4c724-cb99-b079-3af5-35648c542240.png)
APIトークンを作成したら内容を大事にメモっておいてください。

## Confluence API
APIトークンが発行されれば、Confluence APIを叩いて、Confluenceのページを取得したり編集したりすることが可能です。
一例としてあるページ内のコンテンツを知りたい場合は[下記のリクエスト](https://developer.atlassian.com/cloud/confluence/rest/v2/api-group-page/#api-pages-id-get)を送信します（ここではターミナルで試せるように`curl`を使っています）
```bash
curl --request GET \  
--url 'https://{mydomain}/wiki/api/v2/pages/{page_id}' \
--user '{hoge@mail.com}:{api_key}' \
--header 'Accept: application/json'
```
この時変数としては下記を設定する必要あります（上記`{}`記号は記載する必要なく、変数を表しているのみです）
- `mydomain` 
	- 自分のコンフルエンスページのドメイン名。大体は `hoge.atlassian.net` という形になっているかと思います（適当なコンフルのページを開いて確認）。
- `page_id`
	- コンテンツを取得したいページのID。
- `hoge@mail.com`
	- Atlassianのアカウントメール。コンフルやJIRAページ右上にある自分のプロフィール情報で確認可能です。
- `api key`
	- 先ほど取得したAPIキーをべた張りしたもの。

まずは上記シンプルなリクエストが通らないと後続は難しくなるので、ご自身の適当なコンフルのページ内容を取得できるかターミナル等で確認してください。

:::note
なおConfluence APIは今回使うページ関連のエンドポイントとは別に、他にもさまざまな機能のAPIが提供されています。[リンク](https://developer.atlassian.com/cloud/confluence/rest/v2)
:::

# JIRA Automation

ここからが本題になります。既存ConfluenceページにJIRAチケットから抽出した情報を書き出す一連の流れを順に説明します。

## 自動化作成
まずは自動化を作成したいJIRAのページにアクセスし、左側のメニューから「**プロジェクト設定**」に移動します。その後再度左のメニューから「**自動化**」を選び、画面右上部にある「**ルールを作成**」を選択します。

![Pasted image 20240805221329.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3031575/89fb3f25-49a1-caa6-8b1c-aa6a3080141b.png)

すると自動化ルール作成UIに移動します。

![Pasted image 20240805221709.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3031575/2cecd39e-ffb0-346d-4cbf-5406e5d30c5a.png)


こちらでは各種「トリガー」（＝発火条件）や「アクション」（＝発火後実施内容）コンポーネントを組み合わせることによって自動化のルールを構築します。詳しくは公式ドキュメントもご確認ください。

## 対象ページ取得

### トリガー作成

続いて最初のトリガーです。ここでは自動化が発火される条件として右側の「トリガーを追加」の検索窓あるいはスクロールダウンして「**課題から手動トリガー**」を選択します。実行可能グループや表示条件は特にこだわりなければ「次へ」を押してください。（いずれにしてもアクションは対象JIRAに限定され、組織全体に反映されることはないのでご安心ください）

![Pasted image 20240805221947.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3031575/806e16b6-74cf-3dd5-6cce-0d2b993be5cf.png)

:::note
トリガーは他にも「タスク完了時」などいろいろとあるので、適切なものを選んでください。ただし最初の開発時にはデバッグが容易な「手動によるトリガー」を使うことをおすすめします。
:::

### GETリクエスト

続いてはアクションです。右側のメニューから「**その場合：アクションを追加する**」　を選択したうえで、まずは「**Webリクエストを送信**」を検索窓あるいはスクロールダウンで選択しましょう。そうすると下記のようにリクエストの詳細を右側で求められます。

![Pasted image 20240805222203.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3031575/8c906951-20b1-8241-53c0-c2d6537189e4.png)

ここでは先ほど `curl` コマンドを使ってたたいたAPIと同じものを入力します。

**WebリクエストURL**
```
https://{mydomain}/wiki/rest/api/content/{page_id}?expand=body.storage,version
```
最後の`expand=body.storage,version` を忘れずにいれてください。これはページ内容（`body.storage`）に加えてページのバージョン（`version`）も返すように明示しています。

**HTTPメソッド**
```
GET
```

**Web リクエスト本文**
**空白**のまま。下にある「この Web リクエストからの応答を受信するまで、後続のルール アクションの実行を遅延します」に**チェックを入れる。**

**ヘッダー**
ヘッダーは二つ必要です。

| キー            | 値                        |
| ------------- | ------------------------ |
| Accept        | application/json         |
| Authorization | Basic {email:api base64} |

ここでのハマりポイントは `Authorization` に格納する {email: api base64} です。こちらは先ほどの例でも使った `{hoge@mail.com}:{api_key}`をbase64でエンコードしたものになります。base64へのエンコードは適当なサイトを使うか、Unixであればターミナルで
```
echo -n "hoge@mail.com:api_key" | base64
```
とたたくと変換可能です。エンコードされた文字列を `Authorization` の値に入れてください。

### 変数作成

続いてGETリクエストを通じて取得した内容をJIRA Automationで扱えるよう変数に格納します。そのためには「コンポーネントを追加」「アクションを追加」から「**変数を作成**」を選択してください。

**変数名**
```
pageContent
```
ここは適当な名前で大丈夫です。

**スマート値**
```
{{webResponse.body.body.storage.value}}
```

ここでは先ほどのGETリクエストで取得したリスポンスからページ内容を抽出します。 

![Pasted image 20240806222506.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3031575/ad2c80a2-46cb-bd5b-90ff-5acfb0ae0c36.png)

### 確認

ここでいったんGETリクエストが問題なく通るか確認しましょう。そのためにはデバッグを目的としたコンポーネントを追加します（最終的に削除可能）。「コンポーネントを追加」後、「**アクションを記録**」を選択してください。コンポーネントの設定フィールドは下記の移転のみです。

**ログメッセージ**
```
デバッグ：{{pageContent}}
```

上記ログメッセージを設定したら、右上の「**ルールをオンにする**」を押してください。これで自動化ルールが設定されたはずです。
ためしにJIRAの適当なチケットを開きましょう。するとチケット左上の「アクション」から先ほど設定したルールを選べるはずです。

![Pasted image 20240806223138.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3031575/48afc642-e4e7-2b8d-85b5-74383f65b3ef.png)

設定したルールを実行してください。現時点ではまだ該当Confluenceは更新されないはずです。ルールが正常に実行されたか確認するためには、JIRA自動化のページに戻り、「**監査ログ**」を開きます。

![Pasted image 20240806223407.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3031575/9f938a5f-6263-b46a-4891-3f32f28c7a8b.png)

先ほど実行したルールのステータスが成功であることを確認して、「**さらに表示**」で詳細を確認します。
すると「アクションを記録」という項目があり、コンフルのページから取得した内容がロギングされているはずです。

![スクリーンショット 2024-08-06 223508.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3031575/fd95bca5-564b-10c1-e518-9bb7ab6bc45c.png)

:::note
今後明示的には記載しませんが、この通り適宜「アクションを記録」コンポーネントを追加することでデバッグが可能です。
:::

### 追加変数作成

先ほどと同じ要領で新しいコンポーネントから追加の変数を作成してください。

**変数名**
```
pageVersion
```

**スマート値**
```
{{webResponse.body.version.number}}
```
## 更新内容編集

続いてJIRAチケットの情報を反映したコンフルコンテンツを作成します。
### テーブル追加項目作成

またしても新しい変数を作ります。

**変数名**
```
newRow
```

**スマート値**
```
<tr><td>{{issue.summary}}</td><td><a href=\"{{issue.url}}\">{{issue.key}}</a></td><td>{{issue.assignee.displayName}}</td></tr>
```
こちらはコンフルページ内のテーブルに新しく追加する項目（html）を指しています。今回はテーブル内に下記3つの情報を出力します。
- `issue.summary` チケットのタイトル
- `issue.url` `issue.key` チケットのIDとURL
- `issue.assignee.displayName` チケットの担当者

なお上記のとおりJIRA Automationで利用できるチケットに紐づいた変数は *JIRA smart value* と呼ばれており、全量は[こちらのページ](https://support.atlassian.com/cloud-automation/docs/smart-values-in-jira-automation/)から確認できます。

### 更新コンフルページ作成

先ほど作成した新規テーブル項目を、最初にGETで取得した現コンフルページコンテンツに追加します。項目の追加はJIRAのStringに関するメソッド（`replace`）を利用することで実施します。
またしても「コンポーネントを追加」から新たな変数を作りましょう。

**変数名**
```
updatedContent
```

**スマート値**
```
{{pageContent.replace("\"", "\\\"").replace("</tbody></table>", newRow.concat("</tbody></table>"))}}
```
ここでのポイントは、最初に作った変数であり現コンフルページの内容がStringとして入っている `pageContent` に対して、`replace` メソッド（二つ目。一つ目については後述）を用いてhtmlテーブルを閉じるタグ（`</tbody></table>`）を見つけ、これを先ほど作成した新規テーブル項目 `newRow` を加えたタグで置き換える（`newRow.concat("</tbody></table><p />")`）ところです。

:::note warn
1番目の `replace` について。後続にてConfluenceに渡すにあたってAPIを叩く際に上記のスマート値をJSONとして渡します。JSONでは文字列は常にダブルクオーテーション（`"`）で囲む必要がありますが、そうするとスマート値内にあるダブルクオーテーションとバッティングしてしまいます。こちらを避けるために最初の `replace` を通じて明示的にエスケープキャラクター（`\`）を挿入しています。
:::

## 書き出し

さていよいよConfluenceへの書き出しです。なおこのタイミングではJIRA自動化のアクションは最初のGETリクエストに加えて、4つの変数を作成しているはずです。

![Pasted image 20240806230621.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3031575/84f81790-74be-9eb8-f6e3-b09cb93451c6.png)

最後のアクションは再度「コンポーネントを追加」から「**Webリクエストを送信**」をなります。フィールドは下記のとおりです。

**WebリクエストURL**
```
https://{mydomain}/wiki/rest/api/content/{page_id}
```
先ほどのGETリクエストと同様です。

**HTTPメソッド**
```
PUT
```

**Web リクエスト本文**
```
カスタム
```

**カスタムデータ**
```
{
  "id": "hogehoge",
  "type": "page",
  "title": "hogehoge",
  "version": {
    "number": "{{#=}}{{pageVersion}}+1{{/}}"
  },
  "body": {
    "storage": {
      "value": "{{updatedContent}}",
      "representation": "storage"
    }
  }
}
```
なお `hogehoge` と記載している箇所は該当のConfluenceページのID（`id`）とタイトル（`title`）を入れてください。ここでのポイントは `body.storage.value` にて先ほど更新したコンフルページの新内容を入れていることと、`version.number` にて冒頭で取得した現行ページバージョンの値を元にインクリメントした、コンフルページの新バージョンの値を格納していることです。（バージョン指定をちゃんとしないとエラーがでます）

下にある「この Web リクエストからの応答を受信するまで、後続のルール アクションの実行を遅延します」に**チェックを入れる。**

**ヘッダー**
ヘッダーはGETと同様です。

| キー            | 値                        |
| ------------- | ------------------------ |
| Accept        | application/json         |
| Authorization | Basic {email:api base64} |

## 最終確認

さて最終確認です！今回はワークフローがうまく機能するか確認できれば十分なので、特にログを加える必要なく、ルールを「更新」したうえで、適当なJIRAチケット内からアクションを実行してみましょう。正常に実行されれば下記のように該当Confluenceページ内のテーブルに新な行が書き込まれたはずです！

![Pasted image 20240807232512.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3031575/26b48901-cb83-7402-6f8b-24335b42010a.png)

# まとめ

今回はJIRA Automationを活用してConfluenceのページを編集する方法について紹介しました。JIRA/Confluence双方について知見を得られたものの、JIRAとConfluenceはAutomationやAPIを使わずともネイティブでシナジーが高いので、よりよい運用方法をこれからも検討したいと思います。