サーバースクリプトのHttpClientでプリザンターとGitHub Issuesを連携する

本記事は、[OSSのノーコード・ローコード開発ツール「プリザンター」 Advent Calendar 2023](https://qiita.com/advent-calendar/2023/pleasanter) の11日目の記事です。

## サーバースクリプトで外部サービスと連携する
プリザンターのサーバースクリプトには、任意のHTTPリクエストを発行するHttpClientの機能があります。
これにより、REST APIなどを利用した外部サービスとの連携を簡単に行うことができます。

下記記事の「他システムのAPIをサーバスクリプトで操作」のパターンですね。

https://qiita.com/Implem/items/23e50880533a70077f8f

サーバースクリプトのHttpClientに関する詳細は下記マニュアルをご確認ください。

https://pleasanter.org/manual/server-script-http-client

## サンプル
という事で今回は、HttpClientの機能を使って下記の機能を実現するサンプルを紹介いたします。
### 1. GitHub IssesでOpen中のIssueをプリザンターのテーブルに取り込む
GitHub APIで取得したIssueをプリザンターの「課題管理」テーブルに取り込みます。

### 2. 取りこんだプリザンター側のレコードからGitHub Issueのclose/reopenを行う
プリザンター側に取り込んだレコードを開き、「状況」を「完了」または「保留」にして更新したタイミングでGitHub側の対応するIssueがCloseされるようにします。また逆に、上記以外の「状況」に変更した場合に再Openされるようにします。

## 準備

本サンプルではGitHub APIのアクセストークンが必要になりますので、事前に作成・取得しておきます（Issueの読み書き権限だけあればOKです）。
  >[個人用アクセストークンを管理する](https://docs.github.com/ja/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens)


## 1. GitHub IssesでOpen中のIssueをプリザンターのテーブルに取り込む

### 1.1 取得したIssueを取りこむテーブルを用意する
Issue登録用に下記のようなテーブルを作成しておきます。

- テーブル名「課題管理」

|項目名|項目種別|用途|
|:-|:-|:-|
|タイトル|Title|取得したIssueのタイトルを格納|
|内容|Body|取得したIssueの内容を格納|
|状況|Status|Issueのステータス(open/close)との連携用|
|GitHub Issue No.|NumA|取得したIssueの番号を格納|
|GitHub Issue URL|ClassA|取得したIssueへのリンクURLを格納|

### 1.2 API実行用のレコードを作成する
API実行用にテーブルを１つ用意し、レコードを１つ作成しておきます。

- テーブル名 「GitHub連携」

|項目名|項目種別|用途|
|:-|:-|:-|
|タイトル|Title|"GitHub Issue取得"|
|実行結果|Body|APIの実行結果表示用|

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/efbc6bba-782b-5777-574e-2a33e0b7ff5d.png)

### 1.3 GitHub APIを実行するサーバースクリプトを作成する
「GitHub連携」の「テーブルの管理」画面を開き「サーバースクリプト」タブで下記サーバースクリプトを登録します。

:::note
Issue一覧の取得には [GitHub API:List repository issuesd](https://docs.github.com/ja/rest/issues/issues?apiVersion=2022-11-28#list-repository-issues) を使います。
また、取得先のリポジトリはサンプル用にImplem.Pleasanterリポジトリをforkしたものを利用しています。
:::

```javascript
//ID=1のプロセスボタンが押されたときのみ処理する。
if (context.ControlId === 'Process_1') {
    const targetSiteId = 61838;　//取得したIssueを登録するサイトのID
    const accessToken = {your accessd token}; 　//あらかじめ作成しておいたアクセストークン
    //リクエストURL。Open中のIssueのみを取得するためにstate=openのパラメータ追加しています。
    const url = "https://api.github.com/repos/pierre3/Implem.Pleasanter/issues?state=open";
    
    //HttpClientの設定
    httpClient.RequestHeaders.Add('User-Agent', 'Implem'); //User-Agentが無いとエラーになります。
    httpClient.RequestHeaders.Add('Authorization', 'Bearer ' + {your access token});
    httpClient.RequestUri = url;
    
    //GET: https://api.github.com/repos/pierre3/Implem.Pleasanter/issues?state=open 
    let response = httpClient.Get();
    
    if (httpClient.IsSuccess) {
        model.Body = 'Success!';
        var githubIssues = JSON.parse(response);
        //取得したIssue一覧をループ処理
        githubIssues.forEach(item => {
            //「GitHub Issue No.（NumA）」をキーにUpsert
            let data = {
                Keys: ["NumA"],
                Title: item.title,
                Body: item.body,
                NumHash: {
                    NumA: item.number
                },
                ClassHash: {
                    ClassA: item.html_url,
                }
            };
            let ret = items.Upsert(targetSiteId, JSON.stringify(data));
            if (ret === false) {
                model.Body = model.Body + '\nレコードの登録に失敗しました！　IssueNum:' + item.number + '\n';
            }
        });

    } else {
        //エラーの内容を「実行結果」に設定
        model.Body = 'Error: (' + httpClient.StatusCode + ')' + response;
    }
}
```
:::note warn
GitHub APIにリクエストを投げる際には、リクエストヘッダにUser-Agentが含まれている必要があります。（値は任意の文字列で問題なさそうです）
:::

「スクリプト」欄に上記コードを設定します。条件は「更新前」のみチェックしておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/42fad624-984b-1447-c0b4-baf68d07c777.png)

### 1.4 「プロセス」機能を使ってAPI実行用のボタンを追加する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/aee4ffc3-cb84-676d-8eca-b990086fa949.png)

- ID：1(自動割り当て) 
- 名称：GitHub Issue取得
- 画面種別：編集
- 現在の状況：*
- 変更後の状況：*
- 実行種別：追加したボタン
- アクション種別：保存

上記設定でプロセスを作成すると、下記の様に「GitHub連携」テーブルのレコード編集画面に「GitHub Issue取得」のボタンが作成されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/683e9e34-3c58-c865-b279-1cbb3e781272.png)


:::note info
追加したボタンのControlIDについて：
プロセス機能で追加したボタンには、ControlIDとして`Process_{n}`が割り当てられます。({n}はプロセスのID)
サーバースクリプトでは、このControlIDを利用してどのボタンが押されたかを確認できます。

例えば、今回のサーバースクリプトでは条件を「更新前」としていますので、通常の「更新」ボタン押下時やAPIでレコードが更新されたタイミングなどでもスクリプトが実行されてしまいます。しかし、以下のようなコードを追加することで「GitHub Issue取得」ボタンが押された場合のみ実行されるように設定できます。
```javascript
if(context.ControlId === 'Process_1'){
  //ID=1のプロセスボタンが押された場合だけ実行する
}
```
:::

### 1.5 実行してみる
これでIssueを取りこむ準備ができました。さっそく実行してみましょう。

<details><summary>実行結果</summary>

今、対象のリポジトリにはOpen中のIssueが５件あります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/7a94c4a6-eb9c-a653-6dd9-fd1ee5e31ee4.png)

「GitHub取得」てコードを開き、「GitHub Issue取得」ボタンをクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/94b2e55d-e726-c274-f3e6-adc73f102778.png)

実行結果欄に「Success！」と表示されたのを確認し、レコードが取りこまれたはずの「課題管理」テーブルを開いてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/db1762a5-ec0a-7d58-39c8-921d7d0cb526.png)

無事、Issueの内容が取りこまれているのが確認できます。

</details>


## 2. 取りこんだプリザンター側のレコードからGitHub Issueのclose/reopenを行う

下記のような仕様で、プリザンターからGitHub IssueのClose/Reopen処理を行うよう実装します。
Issue Close時にはCloseの理由(statr_reason: 'compleded' or 'not_planned')を指定するようにします。

1. Issueを取りこんだ「課題管理」テーブルで、レコードを開く
1. 状況欄を「完了」に変更後、「Issueステータス更新」ボタンをクリックする
1. 対応するGitHub IssueがCloseされる（Close理由 - completed）
1. 状況欄を「実行中」に変更後、「Issueステータス更新」ボタンをクリックする
1. 対応するGitHub IssueがReopenされる
1. 状況欄を「保留」に変更後、「Issueステータス更新」ボタンをクリックする
1. 対応するGitHub IssueがCloseされる（Close理由 - no-planned）

### 2.1 GitHub Issue更新APIを実行するサーバースクリプトを作成する
今度は、「課題管理」テーブル側でサーバースクリプトの設定を行います。
「テーブルの管理」画面の「サーバースクリプト」タブでサーバースクリプトを追加し、下記のスクリプトを設定します。


:::note
Issueの更新は[GitHub API: Update an issue](https://docs.github.com/ja/rest/issues/issues?apiVersion=2022-11-28#update-an-issue) を使います。
また、取得先のリポジトリはサンプル用にImplem.Pleasanterリポジトリをforkしたものを利用します。
:::

```javascript
//ID=2のプロセスのボタンが押された場合のみ実行する
if (context.ControlId === 'Process_2') {
    const token = {your access token};
    const url = "https://api.github.com/repos/pierre3/Implem.Pleasanter/issues/" + model.NumA;
    //HttpClient設定
    httpClient.RequestHeaders.Add('User-Agent', 'Implem');
    httpClient.RequestHeaders.Add('Authorization', 'Bearer ' + token);
    httpClient.RequestUri = url;
    //更新データの設定
    let data = {};
    if (model.Status === 900){
        data = { state: 'closed' }; //状況：完了時
    }else if (model.Status > 900) {
        data = { state: 'closed', state_reason: 'not_planned' }; //状況: 保留時
    }else{
        data = { state: 'open' }; //上記以外
    }
    httpClient.Content = JSON.stringify(data);
    //PATCH: https://api.github.com/repos/pierre3/Implem.Pleasanter/issues/{issueNumber}
    let response = httpClient.Patch();
    //実行結果をClassBに格納
    if (httpClient.IsSuccess) {
        model.ClassB = response;
    } else {
        model.ClassB = 'Error: (' + httpClient.StatusCode + ')' + response;
    }
}
```

条件は先ほどと同様に「更新前」のみにチェックを入れておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/f1f1cc0b-2399-64a7-cce6-2ec5bf581b71.png)

### 2.2 「プロセス」機能を使ってAPI実行用のボタンを追加する
Issue取得処理の時と同じように、下記設定でプロセスを作成します。

- ID：2(自動割り当て) 
- 名称：Issueステータス更新
- 画面種別：編集
- 現在の状況：*
- 変更後の状況：*
- 実行種別：追加したボタン
- アクション種別：保存

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/b2b4dca3-2ab5-5318-4459-c024a588fff0.png)

### 2.4 実行してみる
これで準備OKです。実行してみましょう。

<details><summary>実行結果</summary>

タイトル「テーブルが重くて表示できません」のレコードを開き、状況を「完了」として「Issueステータス更新」ボタンをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/9bd0d8c4-bce7-7faf-b28c-7767c616e9b9.png)

Issueを開いてみると、「pierre3 closed this as completed...」のコメントと共にIssueがCloseされているのが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/a482fe5e-fdcd-045c-2979-a2253aa6cdf6.png)

状況を「実施中」にして更新
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/43bc31d5-9fda-0336-a9c9-55495a0247fb.png)

再びOpenになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/2fe099fa-9c02-15f7-6ea9-890352010344.png)

最後に、状況を「保留」にして更新
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/95ed2c27-26ee-0db5-0e72-e75c0e7c98ec.png)

今度は 「pierre3 closed this as not planned...」のコメントと共にIssueがCloseされました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28805/305b6aab-4289-42ed-3ea1-cf0806dd43b1.png)

</details>

## まとめ
プリザンターのサーバースクリプトで利用できるHttpClient機能について実装例とともに紹介いたしました。
HTTPClientを活用することで、外部サービスとのシームレスな連携が可能になります。
非常に強力な機能だと思いますので、是非一度試してみてください。