プリザンターとRedmineでAPIを使用してチケットを連携する

プリザンターは素晴らしいソフトウェアですが、(他のあらゆるソフトウェアがそうであるように)どのような場合でも最も適した選択肢というわけではありません。 システムエンジニアが作業タスクを管理することを考えると非常に優れたツールですが、エンジニアが使う他のツールとの連携にはまだ大きな伸びしろがあります。

無理に一つのツールに統一するよりも、システムエンジニアシステムエンジニアの、エンジニアはエンジニアの使い勝手のいいツールをそれぞれ使うのが最も生産性が高まる(かもしれません)。

今回実現するもの

システムエンジニアはプリザンターを、エンジニアはRedmineを使うものとし、両ツール間のデータ連携を実現します。
どちらのツールもクラウドサービスだけでなくオンプレミスで構築可能である点が大きな特徴です。この特徴を生かすため、クラウドサービスを使用しない連携を実現します。

実現したもの

今回はシンプルに要点だけを説明するため次の単機能の連携ツールを作りました。

・ プリザンターに登録したレコードを定期的にRedmineにも自動登録する

動作イメージ

Redmine初期状態 - チケットが空

f:id:imageinformationsystem:20190819105354j:plain

プリザンターにレコードを新規作成

f:id:imageinformationsystem:20190809115819j:plain

Redmineへ連携される

f:id:imageinformationsystem:20190809115828j:plain

プリザンターにレコードをさらに新規作成

f:id:imageinformationsystem:20190809115842j:plain

Redmineへ連携される

f:id:imageinformationsystem:20190809115853j:plain

概要

プリザンターとRedmineAPIを叩くPowerShellスクリプトを組みました。
このスクリプトをプリザンターやRedmineを動かしているサーバなどで定時実行すればデータが連携されます。

スクリプト

組んだスクリプトです。

# プリザンター設定
#   プリザンターのURL
#   プリザンター上の連携するテーブルのID
#   プリザンターAPIを使用する際のAPIキー
$pleasanter_url = "http://localhost:1759/"
$pleasanter_tableId = "{プリザンター上の連携するテーブルのID}"
$pleasanter_apikey = "{プリザンターのAPIキー}"

# Redmine設定
#   RedmineのURL
#   RedmineAPIを使用する際のAPIキー
$redmine_url = "http://localhost:1760/"
$redmine_apikey = "{RedmineのAPIキー}"

# データを取得
#   プリザンターから連携するテーブルのレコードを取得
#   Redmineから連携するプロジェクトのIssueを取得
$pleasanter_data = ((Invoke-WebRequest ($pleasanter_url + "api/Items/" + $pleasanter_tableId + "/Get") -Method POST -Body ('{ "ApiKey": "' + $pleasanter_apikey + '", }')).content | ConvertFrom-Json).Response.Data
$redmine_issues  = ((Invoke-WebRequest ($redmine_url + "issues.json?key=" + $redmine_apikey) -Method GET).content | ConvertFrom-Json).issues

# 連携処理で追加された場合にRedmineのIssueに設定されるプリザンター側のレコードIDのリスト(1)を作成
$redmine_pids = 
    foreach($issue in $redmine_issues) {
        foreach($custom_field in $issue.custom_fields.Where({ $_.name -eq "pid" })) {
            $custom_field.value
        }
    }
$redmine_pids = $redmine_pids | Sort-Object | Get-Unique | Where-Object {$_}
if(!$redmine_pids) { $redmine_pids = @(-1) }

# (1)にIDの存在しないプリザンター上のレコードのリスト(2)を作成
$pleasanter_to_redmine_data = $pleasanter_data | Where-Object {!$redmine_pids.Contains($_.ResultId)}

# (2)のレコードをRedmineに登録
$pleasanter_to_redmine_data | ForEach-Object {
    $body = '{ "issue": {"project_id": 1, "subject": "' + $_.Title + '", "description": "' + ($_.Body -replace "`n","\n") + '", "priority_id": 4, "custom_fields":[{"id":1,"value":' + $_.ResultId + '}]}}'
    $bytes = [Text.Encoding]::UTF8.GetBytes($body);
    Invoke-RestMethod ($redmine_url + "issues.json?key=" + $redmine_apikey) -Method POST -Body $bytes -ContentType application/json > Out-Null
}

少し長いですが、このスクリプト1本だけでデータ連携が実現できました。

解説

プリザンター設定 Redmine設定

まず最初に、環境によって変わる情報を設定しています。

データを取得

それぞれのツールのAPIを使用して現在登録されているレコード/チケットを取得しています。
APIはHTTPへのアクセスなので、Invoke-WebRequestコマンドを使用しています。POSTで送信するデータは -Body でJSON形式の文字列を与えています。今回は条件なしで取得しているので

{
     "ApiKey": "apikey",
} 

というシンプルなデータです。

その後、帰ってきたレスポンスから.contentプロパティで結果のJSON文字列を取り出し、パイプラインでConvertFrom-Jsonコマンドへつなぎオブジェクトにしています。

RedmineはRestAPIなので、単純なGETリクエストになっています。ConvertFrom-Jsonコマンドでオブジェクトにするのは同様です。

レコードIDのリスト(1)を作成

今回、Redmine上のカスタムフィールドとしてpidという名前でプリザンター上のレコードのIDを登録しています。このカスタムフィールドに未登録のIDを持つレコードだけをRedmineへチケット追加することで、同じデータが何個も生まれることを防いでいます。
そのカスタムフィールドpidの値のリストを作っています。

ここは少し面倒な実装をしています。
issueのリストの中のissue一件ずつの中にあるカスタムフィールドのリストの中のpidフィールドをリストにする、必要があります。日本語で書いても面倒ですね。
今回は、ifやforeachなどが値を返すPowerShellの仕様を利用して、二段階foreachからの返却値を変数に取っています。

その後、結果から重複したIDを一つにまとめ、空白(Redmine上で直接入力したもの)を取り除いています。
重複を削除するには、結果の配列をSort-ObjectでソートしさらにパイプラインでGet-Uniqueで重複を取り除きます。そして最後に空白を取り除くためにパイプラインでWhere-Objectへ繋いで {$_} を条件に絞り込んでいます。
PowerShellでは、空の変数をifなどの条件にするとfalseとなり空でないとtrueとなるので、{$_} と書くだけで空の場合はfalseとなりWhere-Objectでの絞り込みで除外されてくれます。

if(!$redmine_pids) { $redmine_pids = @(-1) }

この部分はこれで良いのか実は自信がありませんが、空の配列は.Containsメソッドを持たないそうです。後続のコードでのエラー回避のために空の配列の場合は、-1というあり得ない値を一つだけ持った配列に置き換えています。

プリザンター上のレコードのリスト(2)を作成

こちらは単純な処理です。
プリザンターのレコードのリストから先に作成したRedmineへ登録済みのIDに含まれないIDをもつレコードのリストを作っています。
レコードのリストをWhere-Objectコマンドで絞っているだけの簡単な一行です。

(2)のレコードをRedmineに登録

最後、先のリスト(2)を繰り返し処理でRedmineAPIで登録しています。
今回はデータの登録になるので、RestAPI的にPOSTメソッドになっています。POSTメソッドでのHTTPリクエストは最初にプリザンターのデータを取得したときと変わりません。

注意点としては、JSON文字列に生の改行が入るとJSONとして解釈されないので、-replace "`n","\n" で改行を置換しています。
また、何もしないと日本語が化けるのでJSON文字列を $bytes = [Text.Encoding]::UTF8.GetBytes($body) とバイト配列にしています。こうすることで日本語も正しく登録することができました。
最後に、これは必須ではないですが、Invoke-RestMethodコマンドの結果を > Out-Null で捨てています。こうしないと登録の結果一件ずつが表示されてしまいます(何が登録されたか表示されるのも悪くないので、これは敢えてやらないのも良い選択です)。

実際のご使用では

今回はシンプルな例にするため、更新やタイトルと明細しか連携していません。実際にご使用する際にはそれらもいい感じになるように書き足してご使用ください。

「+読者になる」のお願い

プリザンターの他、C#によるWebアプリ開発IISの得意とする領域です。今後もプリザンターの機能、拡張スクリプト、ページ追加の研究を進めて行くつもりです。関心のある方、よかったらブログ上部の「+読者になる」をクリックをお願いします!

最後に

IISはプリザンターのスクリプトによるカスタマイズの経験が豊富です。
プリザンター導入に際してカスタマイズをご検討されている方は是非ご相談ください!
またご不明点やご質問などございましたら弊社までお問い合わせください。