1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Githubの特定リポジトリからPRのレビューコメントを収集/mdファイル出力するツールを作った話 (Python)

Posted at

1. はじめに

コードレビューをするようになって数ヶ月。まともにメインで使ってる言語のコードレビューをした時に、想像以上に色々拾えず、「これはまずい」となった。
そこで、レビュー観点を整理するために、過去PRでのレビュー指摘が財産となるはずなので、振り返ろうと思ったが、

GithubのWebページ上で一々PRを一つずつを開いていって、コメントを一つずつ見ていく、欲しくない情報も目に入る、それだと手間も時間もかかって面倒だと思いツールを作って収集&ファイル出力しようと思い、そのためのコードを書いた。

せっかく作ったため、その過程でGithub APIのレスポンスデータで押さえた点と、「やろうと思えばこんなことできます」という参考として作成物を簡単に紹介する(何番煎じか分からないが)。

2. Github API

Github アクセストークンが必要ですが、発行方法はググれば出てくるので割愛。Github APIについても同様に割愛。

APIで取得できるPR及びPRのレビューコメントのレスポンスデータが少々分かりにくい部分あるため、
レスポンスデータの一部項目と、それがGUI上のどの部分に対応するかを重点的に記す。

2.1. Pull Request 取得

リクエスト

GET /repos/{owner}/{repository}/pulls

以下で全PRのデータ取得可能。

curl -s -k -H "Accept: application/vnd.github.v3+json" -u :<Github access token> https://api.github.com/repos/<owner>/<repository>/pulls?state=all

レスポンス

レスポンス形式は以下参照。だが
Github Docs - Pull Request

レスポンスデータのどの項目が何のデータか特に説明がないため、(個人的に欲しかった)一部項目だけに絞って紹介する。

# 項目 説明
html_url PRのwebページのURL
review_comments_url PR上の全レビューコメントを取得するREST APIのURL (*1)
comments_url PR上の全コメントを取得するREST APIのURL (*1)
number PR番号
title PRのタイトル
user.login PRの作成者
body PRの説明

image.png
(*1) ②、③で取れる情報は以下の通り
以降、②をレビューコメント、③をコメントと記す。
image.png

2.2. PRのレビューコメント取得

レスポンス

レスポンス形式は以下参照。
Github Docs - PR review comments

こちらも同様に、レスポンスデータのうち、個人的に欲しかった項目だけに絞って紹介する

# 項目 説明
pull_request_review_id ※一度に複数コメントした場合、このidは重複する
id ユニークキー
diff_hunk レビューコメントされたコードの周辺差分()
path レビューコメントされたファイルのパス
user.login レビューコメントした人
body レビューコメント内容
_links.html.href GitHubのWebページ(当該レビューコメント)へのリンクURL

※ body に入っているデータは、レビューコメントのスレッドの先頭の内容のみのようです。

image.png

3. PR上のレビューコメントを収集/出力するために作成した物

pythonで実装

3.1. 出力

csvファイル(念の為の一覧出力) と mdファイル(各PRのレビューコメント1件を1ファイル)に出力

上記のPRから取得した結果の出力は以下の通り(レビューコメントが5つあるため、mdファイルも5つ出力)
image.png
mdファイルの中身は以下の通り
image.png
やろうと思えば、このように出力も可能というご紹介。

mdファイルへの出力は、jinja2 を利用。
簡単に紹介すると、以下のようなテンプレートファイルを用意して

# PR Review Comment Info

- PR title

{{title}}

- PR create user

{{create_user}}

  : (略)

テンプレートに当てはめるためのデータを用意し、

{
  'title': 'pr title',
  'create_user': 'user name'
    : (略)
}

以下のようにするだけで、テンプレートに基づいて出力内容作成、ファイル出力できる

from jinja2 import Environment, FileSystemLoader

TEMPLATES_DIR_PATH = './templates/'
PR_REVIEW_COMMENT_TEMPLATE_FILE_NAME = 'pr_review_comment.j2'

env = Environment(loader=FileSystemLoader(searchpath=TEMPLATES_DIR_PATH, encoding='utf8'))
template = env.get_template(PR_REVIEW_COMMENT_TEMPLATE_FILE_NAME)
md_file_data = template.render(json_data)
with open(OUTPUT_DIR_PATH + md_file_path , mode='w', encoding='CP932', errors='ignore') as f:
    f.write(md_file_data)

※jinja2の詳細はググれば分かるため割愛。

3.2. API実行周り

Github APIには、ページネーション機能があり、1回のAPI実行で最大100件までしか取得できない。
それ以上のデータがある場合は、ページ番号をカウントアップしてAPIを再実行する必要がある(PRもPR上のレビューコメントもその他色々も)。

先にページ数を取得する方法がパッと見、見当たらなかったため、申し訳程度のリトライ機構を用意して以下の通りに実装。

※今回取得したいのは、レビューコメントなので、コメントは現状取得しない
※レビューコメント以外も取得したくなったら、用意したリトライ機構に乗っけて取れば良い算段で実装

# Callback-only function used by retry_execute_github_api()
def collect_and_write_pull_requests(page_count: int, non_arg) -> int:
    api_url = build_get_pull_requests_api_url(page_count)
    response_json_pr_array = execute_github_api(api_url)
    pr_data_list = PullRequestDataList(response_json_pr_array)
    pr_data_list.write_csv(PULL_REQUEST_LIST_FILE_NAME)
    # collect review comment in PR
    for pr_data in pr_data_list.values:
        retry_execute_github_api(collect_and_write_pr_review_comments, pr_data)
    return len(response_json_pr_array)

# Callback-only function used by retry_execute_github_api()
def collect_and_write_pr_review_comments(page_count: int, pr_data) -> int:
    api_url = build_get_pr_review_comments_api_url(pr_data.review_comments_api_url, page_count)
    response_json_review_comments_array = execute_github_api(api_url)
    pr_review_comment_list = PullRequestReviewCommentList(response_json_review_comments_array, pr_data)
    pr_name = pr_data.build_pr_name()
    pr_review_comment_list.write_csv(pr_name)
    pr_review_comment_list.write_md(pr_name)
    return len(response_json_review_comments_array)

def retry_execute_github_api(api_execute_func: Callable[[int, any], int], *args):
    is_retry = True
    page_count = 1
    while is_retry:
        count = api_execute_func(page_count, args[0] if len(args) != 0 else None)
        page_count += 1
        is_retry = False if count < 100 else True

retry_execute_github_api(collect_and_write_pull_requests)

上記は、一部抜粋のため、ソース全文は以下リンク先をご参照ください。

3.3. ソースコード

※MIT License にしているため、コードはパクって頂いて問題ありません。可読性/変更容易性は高くしているつもりのため、欲しい方はご活用ください。

4. おわりに

現状は、レビューコメントのスレッドのうち先頭の内容しか取ってない。
先頭以外は回答/議論が主になると思っているため、先頭だけで十分ではないかと推測している。
足りないと思えば追加実装すればよいというスタンスで、追加実装しやすいように実装もした。
これで少しでも捗ることを期待。

以上。

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?