Backlogの課題をPythonでAPI使ってええ感じに出力する方法【添付ファイル・コメントも対応】
こんちには。
データアナリティクス事業本部 インテグレーション部 機械学習チームの中村です。
今日はBacklogの課題をPythonでええ感じに出力(エクスポート)する方法を紹介します。
本記事の方法でできること
本記事の方法でできることは以下です。
- 各課題をテキストファイルに落とす
- 上記テキストファイルには、コメントと日時・作成者を含める
- 添付ファイルもダウンロードする
結果としては以下のような形式で落とすようになっています。
{プロジェクト名}-001-{チケットのタイトル}/ ├ body.backlog └ attachment/ ├ hoge.png └ fuga.csv {プロジェクト名}-002-{チケットのタイトル}/ ├ body.backlog └ attachment/ ├ hogehoge.png └ fugafuga.csv ...
body.backlogの中身は以下のように、課題自体とコメントをまとめたものになります。
以下のような仕様でクラスを作成する。 - クラス名:SampleClass - メソッド -- __init__() -- hogehoge : hogehogeな処理をする -- fugafuga : fugafugaな処理をする --------------------------------------- comment created: 2023-02-16T02:42:27Z author: 田中<[email protected]> --------------------------------------- SAMPLE_PRJ-1 クラスを実装 --------------------------------------- comment created: 2023-02-16T02:42:27Z author: 田中<[email protected]> --------------------------------------- SAMPLE_PRJ-1 レビュー指摘事項を修正 --------------------------------------- comment created: 2023-02-16T03:09:14Z author: 田中<[email protected]> --------------------------------------- Merge branch 'SAMPLE_PRJ-1 ' --------------------------------------- comment created: 2023-03-06T06:27:58Z author: sato<[email protected]> --------------------------------------- 報告済みのため完了とします。
課題一覧をエクスポートする先行事例
Backlog公式でも以下の方法が使用可能です。
ただしこの方法では、コメントが横方向に広がった形式の可変長テーブルとなったり、添付ファイルが取得できなかったりするため、今回はAPIを使って実装をしました。
また、その他弊社のブログでも添付ファイルを含めてダウンロードする例が、Go言語で紹介されていました。
こちらは私のやりたいことに近いですが、Go言語の経験がないため今回Pythonで書いてみました。
実装説明
準備
使用するライブラリは以下です。pipなどでインストールしてください。
(私はpoetry環境でやっていますが環境に合わせてお好みで実施ください)
requests==2.28.2 python-dotenv==1.0.0
あらかじめ以下の環境変数を.env
に記述しておきます。
(環境変数の設定方法もお好みでOKです)
BACKLOG_API_KEY={APIキー} BACKLOG_PROJECT_ID={プロジェクトID} BACKLOG_BASE_URL={ベースURL} BACKLOG_PROJECT_NAME={プロジェクト名}
APIキーは、「個人設定」の「API」からAPIキーを発行すればOKです。
プロジェクトIDは左メニューから以下のような「プロジェクト設定」をクリックし、
その際にブラウザのURLに表示される以下のような末尾の数字からわかります。
https://example.backlog.jp/ViewPermission.action?projectId={プロジェクトID}
ベースURLはhttps://example.backlog.jpのような部分です。
プロジェクト名は、課題番号に必ず付くプレフィックスと同じです。
コード
コードは以下のようになっています。
import os import json import pathlib import re from dotenv import load_dotenv import requests load_dotenv(verbose=True) def main(output_dir="./output"): # 環境変数からの取得 BACKLOG_API_KEY = os.environ.get("BACKLOG_API_KEY") BACKLOG_PROJECT_ID = os.environ.get("BACKLOG_PROJECT_ID") BACKLOG_PROJECT_NAME = os.environ.get("BACKLOG_PROJECT_NAME") BACKLOG_BASE_URL = os.environ.get("BACKLOG_BASE_URL") # クエリパラメータにプロジェクトIDとAPIキーを含める payload = { 'projectId[]': f'{BACKLOG_PROJECT_ID}' , 'apiKey': f'{BACKLOG_API_KEY}' } # 課題一覧を取得 response = requests.get(BACKLOG_BASE_URL + "/api/v2/issues", params=payload) issues = json.loads(response.text) # 課題番号の昇順にソート issues = sorted(issues, key=lambda v: v["keyId"]) # 課題のループ for issue in issues: keyId = issue['keyId'] # 課題番号(プロジェクト名なし) summary: str = issue['summary'] # タイトル description = issue['description'] # 記載内容 # ファイル名に使えないものを置換する summary = re.sub(r'[\\|/|:|?|.|"|<|>|\|]', '-', summary) print(f"{BACKLOG_PROJECT_NAME}-{keyId:03d}-{summary}") # 課題毎に出力フォルダを作成 output_path = pathlib.Path(output_dir)\ .joinpath(f"{BACKLOG_PROJECT_NAME}-{keyId:03d}-{summary}") output_path.mkdir(parents=True, exist_ok=True) # 課題本文のファイル名 output_body_path = output_path.joinpath(f"body.backlog") # 課題本文への出力 with open(output_body_path, "wt") as f: # 課題自体の内容はそのまま出力 f.writelines(description) # 課題のコメントをすべて取得 issue_id = issue['id'] response = requests\ .get(BACKLOG_BASE_URL + f"/api/v2/issues/{issue_id}/comments", params=payload) comments = json.loads(response.text) # コメントを古い順(昇順)となるようソート(そのままだと新しいものが先頭にきていた) comments = sorted(comments, key=lambda v: v["created"]) # 見やすさのためのpadding f.writelines(["\n", "\n"]) # コメントのループ for comment in comments: # コメント内容 content = comment['content'] # コメント作成者の名前 createdUserName = comment['createdUser']['name'] # コメント作成者のメールアドレス createdUserMailAddress = comment['createdUser']['mailAddress'] # コメント作成日 created = comment['created'] # Noneの場合があったためケア if content is None: continue # コメントに関するメタデータを出力 f.writelines([ "\n---------------------------------------" , f"\ncomment created: {created}" , f"\nauthor: {createdUserName}<{createdUserMailAddress}>" , "\n---------------------------------------" ]) # コメントそのものを出力 f.writelines(["\n"]) f.writelines(content) f.writelines(["\n"]) # 添付ファイルの情報を取得 response = requests\ .get(BACKLOG_BASE_URL + f"/api/v2/issues/{issue_id}/attachments", params=payload) attachments = json.loads(response.text) # 添付ファイル情報のループ for attachment in attachments: attachment_id = attachment['id'] attachment_name = attachment['name'] # 出力先のファイル名を合成 output_attachment_path = output_path.joinpath("attachment", f"{attachment_name}") output_attachment_path.parent.mkdir(parents=True, exist_ok=True) # 添付ファイルのデータを取得 response = requests.get( BACKLOG_BASE_URL + f"/api/v2/issues/{issue_id}/attachments/{attachment_id}" , params=payload) # 添付ファイルはバイナリとして出力 with open(output_attachment_path, "wb") as f: f.write(response.content) return if __name__ == "__main__": main()
コメントに説明を書いておきました。ほぼ難しい部分はなく、requestsライブラリに慣れていればすぐに理解できると思います。
敢えてポイントをいくつかあげると、以下でファイル名に使えない文字列をハイフンに置き換えます。
# ファイル名に使えないものを置換する summary = re.sub(r'[\\|/|:|?|.|"|<|>|\|]', '-', summary)
またコメントはそのままでは新しいものが先頭に来るため、古い順に並べなおしました。
# コメントを古い順(昇順)となるようソート(そのままだと新しいものが先頭にきていた) comments = sorted(comments, key=lambda v: v["created"])
こちらのコードを実行すると、./output/
にエクスポートした結果が格納されます。
まとめ
いかがでしたでしょうか。
本記事が、Backlogをお使いになられている方の参考になれば幸いです。