yiskw note

機械学習やプログラミングについて気まぐれで書きます

pythonで毎週レビュワーのローテーションを通知するslack botを作成した


概要

自分が参加しているプロジェクトでは,みんなができるだけコード全体をキャッチアップできるように, PRの内容に詳しい人に加えて,ローテーションで1人レビュワーを追加するようにしています. 毎週どの人のPRをどの人がみるかを変えていたのですが,今どの組み合わせかをいちいち覚えておくのが面倒なので, 毎週自動でslackに通知してくれるbotを作成しました.こちらはその際の備忘録となります.

開発環境

- MacOS Catalina
- python 3.8.7
  - crontier==1.0.6
  - python-crontab==2.5.1
  - slacker==0.14.0
- heroku

slack botslackerで作成し, 特定の時刻に処理を行うためにpython-crontabを用いてタスクのスケジューリングをしています.

ディレクトリ構造

以下のようなディレクトリ構造を想定しています. (GitHubのレポジトリにコードを上げるため,.gitignoreを追加しています.)

.
├─ .gitignore
├─ Procfile
├─ README.md
├─ requirements.txt
├─ run.py
├─ runtime.txt
├─ send_rotation.py
└─ setting.py

slack botの作成

API Tokenの取得

https://my.slack.com/services/new/botから,slack botを作成します. botを作成したら,API tokenをコピーし,setting.pyに記述します.

API_TOKEN = "xxxxx"  # コピーしたAPI token
CHANNEL = "#general"  # botに投稿させたいチャンネルの名前

CHANNELには投稿したいチャンネル名を記入します.

slackerを用いて今週のローテーションを投稿するbotの作成(send_rotation.py)

このファイルの流れは以下です. 本日のISO準拠の週番号を取得 -> get_rotationでその週のレビュワーローテーションを取得 -> その結果を整形してslackへ投稿

  
from datetime import datetime

from slacker import Slacker

from setting import API_TOKEN, CHANNEL

NAME2GITHUB = {
    "Aさん": "githubA",
    "Bさん": "githubB",
    "Cさん": "githubC",
    "Dさん": "githubD",
    "Eさん": "githubE",
    "Fさん": "githubF",
}

MEMBER_LIST = [k for k in NAME2GITHUB.keys()]


def get_rotation(nth_week):
    n_members = len(MEMBER_LIST)
    res = {}
    for i, m in enumerate(MEMBER_LIST):
        reviwer_idx = (i + 1 + (nth_week - 1) % (n_members - 1)) % n_members

        reviwer_name = MEMBER_LIST[reviwer_idx]
        res[m] = f"{reviwer_name} ({NAME2GITHUB[reviwer_name]})"

    return res


def send_rotation():
    today = datetime.now()
    _, nth_week, _ = today.isocalendar()
    res = get_rotation(nth_week)

    message = "今週のreviewer対応表\nYou\t-\tReviewer\n"
    for k, v in res.items():
        message += f"{k:<3}\t-\t{v}\n"

    slack = Slacker(API_TOKEN)
    slack.chat.post_message(CHANNEL, message, as_user=True)


if __name__ == "__main__":
    send_rotation()

まず,datetimeというライブラリを用いて,ISO準拠の週番号を取得します.

today = datetime.now()
_, nth_week, _ = today.isocalendar()

次にget_rotationという関数で,指定された週で,誰が誰のレビューを見るのかを辞書型で取得しています.(keyがPRを作成する人で,valueがそのレビューをする人)
具体的にはISO準拠の週番号nth_weekから,自分が何個先の人にレビューしてもらうかを計算しています.<br> 例えば,Aさん(0番目の人)は,週番号2週目に(0 + 1 + (2 - 1) % 5) % 5 = 2`で,Cさん(2番目)の人にレビューをしてもらいます.
つまり自分から見て2個先の人のレビューをしてもらうことになります.
これは他の人でも同様で,例えばBさん(1番目の人)は,2つ先のDさん(3番目の人)にレビューを行ってもらうようになっています.
このように週番号から,それぞれの人が何個先に人に見てもらうかを計算し,その結果を返す関数になっています.

for i, m in enumerate(MEMBER_LIST):
    reviwer_idx = (i + 1 + (nth_week - 1) % (n_members - 1)) % n_members

    reviwer_name = MEMBER_LIST[reviwer_idx]

最後に,get_rotationで得られた結果を,stringに変形し,slackerを用いてメッセージを送っています.

message = "今週のreviewer対応表\nYou\t-\tReviewer\n"
for k, v in res.items():
    message += f"{k}\t-\t{v}\n"

slack = Slacker(API_TOKEN)
slack.chat.post_message(CHANNEL, message, as_user=True)

試しに,作成したbotをチャンネルに招待して,python send_rotation.pyを実行してみると,メッセージが送信されることが確認できるかと思います.

定期的にslackにメッセージを送る

ここまででslackのチャンネルにレビュワーの対応を送ることができました. しかし,現状ではpython send_rotation.pyを実行した時にメッセージを送信するだけで, 定期的な通知はしてくれません. そこで,今回はpython-crontabというライブラリを用いて,タスクスケジューリングを行いました. 実装にあたって,以下の実装を参考にさせていただきました.詳しい説明はこちらを参照していただけたらと思います.

miyabikno-jobs.com

run.pyを以下のようにします. 注意点としては,herokuサーバー内の時刻がPSTになっているため, sheduleを指定する際には,PSTで記述する必要があります.

from crontab import CronTab


class CrontabControl(object):
    """
    Reference: https://miyabikno-jobs.com/slack-regularly-weather/
    """

    def __init__(self):
        self.cron = CronTab()
        self.job = None
        self.all_job = None

    # write jobs to file
    def write_job(self, command, schedule, file_name):
        self.job = self.cron.new(command=command)
        self.job.setall(schedule)
        self.cron.write(file_name)

    # read all jobs from file
    def read_jobs(self, file_name):
        self.all_job = CronTab(tabfile=file_name)

    # monitoring job
    def monitor_start(self, file):
        self.read_jobs(file)
        for result in self.all_job.run_scheduler():
            continue


def main():
    command = "python3 send_rotation.py"

    # 3 p.m. every Sunday (PST) -> 8 a.m. every Monday (JST)
    schedule = "* 15 * * 0"

    output_file = "output.tab"

    c = CrontabControl()
    c.write_job(command, schedule, output_file)
    c.monitor_start(output_file)


if __name__ == "__main__":
    main()

GitHubへpush

ここまで完成したら,あとは他の設定ファイルを追加して,GitHubへpushします.

  • .gitignore
__pycache__
.DS_Store
output.tab
pbot: python run.py
  • requirements.txt ... 必要なパッケージを記述するファイル
croniter==1.0.6
python-crontab==2.5.1
slackbot==1.0.0
slacker==0.14.0
  • runtime.txt ... herokuでpythonのバージョンを指定するファイル
python-3.8.7

上記のファイルを作成し,GitHubに適当なレポジトリを作成して,これまでの内容をpushします.

git init
git add .
git commit -m "first commit"
git branch -M main

# 自分のレポジトリのURLに変更してください
git remote add origin https://github.com/XXXXXX/XXXXXX

git push -u origin main

herokuでデプロイ

herokuにログイン(アカウントを持っていない場合は作成)し, https://dashboard.heroku.com/apps から新しいアプリを作成します. 作成したアプリから,Deploy -> Deployment methodでGitHubを選択し, 先ほど作成したレポジトリと接続します. こんな感じで接続されればオーケーです.

f:id:yiskw713:20210213125136p:plain:w700

そのページの下のManual Deployから,mainブランチを選択して,Deploy Branchを実行します.

f:id:yiskw713:20210213125320p:plain:w700

最後にOverviewから,pbot python run.pyをオンにすれば完了です!

f:id:yiskw713:20210213125603p:plain:w700

まとめ

slackerpython-crontab,herokuを用いて,簡単にslack botを作成することができました. このような定期配信ではなく,特定のキーワードに反応するようなbotを作成したい場合は,pythonslackbotというライブラリを使うと簡単にできるみたいです.

参考