Typetalk ニュースレターを登録

Typetalk の最新情報、使い方や事例紹介などを毎月配信します。

ニュースレターの登録が完了しました。
ご登録ありがとうございます。

Backlogのプルリクエストのレビュー待ちをTypetalkボットでお知らせしよう!

仕様や画面は現行バージョンと異なる可能性があります。
Typetalkの最新版についてはこちらからご確認ください。

TypetalkプロダクトオーナーのYoshiです。今日もBacklogで楽しく開発していますか?本記事では、Backlogのプルリクエストのレビュー待ちをTypetalkに通知する方法をご紹介します。

プロジェクト管理ツール「Backlog」のGit機能の一部に、プルリクエストがあります。チームメンバーに自分が書いたコードをレビューしてもらいながら開発を進めるときに役立つ機能です。

しかし、プルリクエストをもらっても、自分の仕事が立て込んでうっかりレビューをし忘れてしまうことはないでしょうか?チームで開発を迅速に進めるうえでレビューもマージもされないプルリクエストが増えるのは、好ましいことではありません。

本記事では、Google Apps ScriptとBacklog APIを使ってTypetalkにプルリクエストのレビュー待ちを通知する方法を紹介します。Typetalkボットを活用して、プルリクエストのレビューを素早く効率化しましょう。

セットアップ手順

手順の解説に入る前に、完成イメージをお伝えします。
文末に記載されているスクリプトを実行することで以下のような内容をTypetalkに投稿できます。
プルリクエスト通知

Backlog APIキーの取得

1. Backlogの個人設定ページの「API」からAPI キーを生成します。以下の画像にある「新しいAPIキーを発行」の“登録”をクリック

Backlog API キー設定画面

Typetalkボットの作成方法

2. Typetalkを開き投稿したいトピックにボットを作成します

ⅰ.トピックを選択後、トピック名の右隣の歯車アイコン(トピック設定)をクリック
ⅱ.「ボット」タブを選択後、「新規追加」ボタンをクリック
ⅲ. 以下のような情報を入力してボットを「作成」するとTypetalk Tokenが発行されます

・ID: backlog-pullrequest-reminder (任意)
・名前: Pull Request Reminder (任意)

Typetalk ボット設定

Google Apps Scriptの作成方法

3. Google Apps Scriptを新規作成します

ⅰ.文末にある「ソースコード」を貼り付けます
ⅱ.先頭行に以下のような必要な情報を入力します

backlogApiKey 1の手順で生成したAPIキー
backlogSpaceUrl 利用したいBacklogスペースURL
backlogProjectName 利用したいBacklogプロジェクト名
backlogGitProjectName 利用したいBacklog Gitリポジトリ名
typetalkTopicId Typetalkに投稿したいトピックID
typetalkToken 2の手順で生成したTypetalk Token

4. 実行して動作確認をします
5. トリガーを設定して定期実行させます(※週タイマーを指定して平日のみ対象にすることをおすすめします
Google Apps Script トリガー

以上で完了です!また、今回のスクリプトではプルリクエスト名に[WIP]と入れておけば対象から外すこともできるようにもなっています。

みなさんに便利と感じてご使用いただければ、Typetalk/Backlogに標準機能として組み込んでいきたいなとも思っています。感想をぜひTypetalkの公式ツイッターアカウントからお聞かせくださいね!

それでは今日もBacklogとTypetalkで楽しい開発を:)

プルリクレビュー待ち通知のソースコード

var backlogApiKey = '';
var backlogSpaceUrl = ''; // e.g. https://example.backlog.com
var backlogProjectName = ''; // e.g. TYPETALK
var backlogGitProjectName = ''; // e.g. typetalk
var typetalkTopicId = 0;
var typetalkToken = ''

var postMessage = 'プルリク来てるよ';
var limitHours = 18;

function main() {
  var pullRequests = JSON.parse(requestPullRequestInfo());
  var pendingPullRequests = filterPendingPullRequests(pullRequests);
  if (pendingPullRequests.length > 0) {
    var message = buildMessage(pendingPullRequests);
    postToTypetalk(message);
  }
}

function requestPullRequestInfo() {
  var response = UrlFetchApp.fetch(backlogSpaceUrl + "/api/v2/projects/" + backlogProjectName + "/git/repositories/" + backlogGitProjectName + "/pullRequests?apiKey=" + backlogApiKey + "&statusId[]=1");
  return response.getContentText();
}

function filterPendingPullRequests(pullRequests) {
  var l = [];
  for (var i = 0; i < pullRequests.length; i++) {
    if (!pullRequests[i].assignee) {
      continue;
    }
    
    if (pullRequests[i].summary.indexOf('[WIP]') === 0) {
      continue;
    }

    var now = Date.now();
    var updated = Date.parse(pullRequests[i].updated);
    var diffByMilliSeconds = now - updated;
    var diffBySeconds = diffByMilliSeconds / 1000;
    var diffByMinutes = diffBySeconds / 60;
    var diffByHours = diffByMinutes / 60;

    if (diffByHours < limitHours) {
      continue;
    }

    l.push(pullRequests[i]);
  }

  return l;
}

function buildMessage(pendingPullRequests) {
  var message = postMessage;
  for (var i = 0; i < pendingPullRequests.length; i++) {
    var pullRequestSummary = pendingPullRequests[i].summary;
    var pullRequestNumber = pendingPullRequests[i].number;
    var pullRequestUrl = backlogSpaceUrl + "/git/" + backlogProjectName + "/" + backlogGitProjectName + "/pullRequests/" + pullRequestNumber;
    var nulabAccountUniqueId = pendingPullRequests[i].assignee.nulabAccount.uniqueId;
    
    message = message + "\n@" + nulabAccountUniqueId + " [#" + pullRequestNumber + " " + pullRequestSummary +"](" + pullRequestUrl + ")";
  }

  return message;
}

function postToTypetalk(message) {
  var formData = {
    "typetalkToken": typetalkToken,
    "message": message
  };     

  var options = {
    'method' : 'post',
    'payload' : formData
  };

  UrlFetchApp.fetch("https://typetalk.com/api/v1/topics/" + typetalkTopicId + "?typetalkToken=" + typetalkToken, options);  
}

 

他のTypetalkボットもみる