ContractS開発者ブログ

契約マネジメントシステム「ContractS CLM」の開発者ブログです。株式会社HolmesはContractS株式会社に社名変更しました。

スクラムチームに振り返りbot導入してみた

こんにちは。 株式会社Holmesでスクラムマスターしてます、id:k_kubouchi です。

Holmesの開発はアジャイルでのスクラム開発で行っています。 スクラム開発のイベントには「スプリントレトロスペクティブ」というスプリントを振り返るイベントが存在します。 振り返りの種類は色々とあり、チームによってやり方は様々かと思います。 振り返りたい内容をレトロ内で出し切れたらいいのですが、スプリントを振り返る際に「何かあったんだけど思い出せない」みたいなシチュエーションも中にはあるのではないでしょうか。 そこの一助になればと思い、都度振り返りを投稿できる SlackBot を導入することにしました。 それでは、以下の項目に沿って導入手順を解説していきたいと思います。

※前提として、振り返りは KPT と呼ばれる振り返りのフレームワークを想定しています。

※2020年から SlackBot の設定手順が変更されたようなので、そちらをベースに解説していきます。

1. Slack App の設定(SlackBot)

まず、SlackBot を導入したいワークスペースに管理者権限アカウントでサインインします。 URLはこちらになります。 サインイン後、画面右上にある Create New App をクリックしてください。

f:id:k_kubouchi:20201110093623p:plain

設定ダイアログが表示されるので、 App Name に好きな bot 名を、 Development Slack Workspace に導入先の Slack ワークスペースを設定してください。

f:id:k_kubouchi:20201110093807p:plain

次に、権限の設定をする必要があるので、最初の画面の Basic Information クリックで表示されるメニューから、Add features and functionalityPermissions と遷移してください。

f:id:k_kubouchi:20201110093818p:plain

Scopes の真ん中ぐらいにある Add an OAuth Scope をクリックし、以下2つの権限を追加してください。(選択するだけで選んだことになってます)

  • chat:write
  • chat:write.public

f:id:k_kubouchi:20201110093834p:plain

続いて、 Basic Information から App Display Name に行き、 Slack 上で表示する App の内容を設定していきます。

f:id:k_kubouchi:20201110093848p:plain

設定項目はそれぞれ以下の通りです。

  • App name:Slack App の名前
  • Short description:Slack 上での bot の説明
  • App icon & Preview:Slack 上での bot アイコン
  • Background color:Slack 上での bot 背景

最後に Incoming Webhooks から、以降に登場する Google Apps Script 上で使用する URL を取得します。 左メニューの Incoming Webhooks をクリックし、 Webhook URLs for Your Workspace に表示されている URL をコピーしておいてください。

f:id:k_kubouchi:20201110093902p:plain

以上で Slac App の設定は完了です。

2. GASの設定(Google Apps Script)

Slack に投稿した内容を自動的に Googleスプレッドシートに溜めていくような仕組みにしたいです。 そのためには Google Apps Script を使う必要あるので、まずはそちらを準備していきます。

Google Drive+ NewGoogle Sheets を選択します。

f:id:k_kubouchi:20201110093913p:plain

新規スプレッドシートが表示されたら ToolsScript editor を選択します。

f:id:k_kubouchi:20201110093926p:plain

編集画面が表示されるので、適当なプロジェクト名を左上のタイトルをクリックし設定します。 編集エディタ上に今回は以下コードを挿入することとします。

var KEEP = 'KEEP';
var PROBLEM = 'PROBLEM';
var TRY = 'TRY';

function doPost(e) { // SlackからのPOSTリクエスト時に発火する
  switch(e.parameter.text) { // start、end、K: P: T: で場合分け
    case 'start':
      start();
      break;
    case 'end':
      end();
      break;
    default:
      registerKpt(e.parameter.text, e.parameter.user_name);
      break;
  }
}

function start() { // 開始宣言
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0]; // 先頭のシートを取得
  if ((sheet.getName().match(/^[\d]{4}\/[\d]{2}\/[\d]{2} [\d]{2}:[\d]{2}$/) !== null) || (sheet.getLastRow() !== 0)) { // 重複した開始宣言は排除
    postSlack('すでに今スプリントの `KPT` は始まってるなっしー!');
    return;    
  }
  var date = new Date(); 
  sheet.setName(Utilities.formatDate( date, 'Asia/Tokyo', 'yyyy/MM/dd hh:mm')); // シート名を現在時刻に変更
  postSlack('今スプリントの `KPT` 開始なっしー!');
}

function end() { // 終了宣言
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getSheets()[0];
  var lastRowNum = sheet.getLastRow();
  var keepArray = [];
  var problemArray = [];
  var tryArray = [];
  var reviewArray = [];
  if (lastRowNum === 0) { // 0行の場合は終了できない
    postSlack('登録された `KPT` がないなっしー...');
    return;
  }
  var rows = sheet.getRange(1, 1, lastRowNum, 3).getValues();
  rows.forEach(function(row) { // 行ごとにKPT4要素でまとめる
    switch(row[0]) {
      case KEEP:
        keepArray.push(row[1] + ': @' + row[2]);
        break;
      case PROBLEM:
        problemArray.push(row[1] + ': @' + row[2]);
        break;
      case TRY:
        tryArray.push(row[1] + ': @' + row[2]);
        break;
      default:
        break;
    }
  });
  
  var date = new Date();
  var now = Utilities.formatDate( date, 'Asia/Tokyo', 'yyyy/MM/dd hh:mm'); // 終了時点の時刻を取得
  postSlack( // まとめて投稿
    '今スプリントの KPT なっしー!みんなお疲れなっしー!\n' +
    '```\n' + 
    sheet.getName() + ' ~ ' + now + '\n\n' + 
    '# KEEP\n' + keepArray.join('\n') + '\n\n' +
    '# PROBLEM\n' + problemArray.join('\n') + '\n\n' +
    '# TRY\n' + tryArray.join('\n') +
    '\n```'
  );
  ss.insertSheet(0); // 新たな空シートを先頭に追加
}

function registerKpt(text, userName) { // KPT登録処理
  postSlack('登録してるなっしー....');
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
  var categoryCell = sheet.getRange(sheet.getLastRow() + 1, 1); // 挿入する行の1列目を取得
  var contentCell = sheet.getRange(sheet.getLastRow() + 1, 2); // 挿入する行の2列目を取得
  var userCell = sheet.getRange(sheet.getLastRow() + 1, 3); // 挿入する行の3列目を取得
  var message = processMessage(text); // 投稿を要素と内容に分ける
  if (message === null) { // 形式違いは排除
    postSlack('形式が違うなっしー。 `K:`、 `P:`、 `T:` から始めるなっしー!');
    return;
  }
  categoryCell.setValue(message.category); // 書き込み
  contentCell.setValue(message.content);
  userCell.setValue(userName);
  postSlack('スプシに書いといたなっしー!');
}

function processMessage(text) {  // 投稿を要素と内容に分ける
  var match = text.match(/^([K|P|T]):(.*)$/m);
  if (match === null) {
    return null;
  }
  
  var getText = text;
  switch(match[1]) {
    case 'K':
      getText = getText.replace('K:', '');
    case 'P':
      getText = getText.replace('P:', '');
    case 'T':
      getText = getText.replace('T:', '');
    default:
  }
  
  return {
    category: convertCategory(match[1]),
    content: getText,
  };
}

function convertCategory(category) { // プレフィックスから4要素を判断する
  switch(category) {
    case 'K':
      return KEEP;
    case 'P':
      return PROBLEM;
    case 'T':
      return TRY;
    default:
      return null;
  }
}

function postSlack(text){ // Slackへの投稿
  // ここに先程コピーしていた Slack Incoming WebHook の URL を指定してください
  var url = "https://hooks.slack.com/services/~~~~~~";
  var options = {
    "method" : "POST",
    "headers": {"Content-type": "application/json"},
    "payload" : '{"text":"' + text + '"}'
  };
  UrlFetchApp.fetch(url, options);
}

コードを設定できたら保存し、メニューの Publish を選択します。 設定ダイアログの中身を入力していきますが、この時以下のことに注意してください。

  • Project version: は毎回保存時は必ず New を選択してください
  • Who has access to the app: ですが、必ず Anyone, even anonymous で設定してください

以上で Google Apps Script の設定は完了です。

3. Slack App と GAS の連携(Slack Outgoing WebHook)

Slack の機能である Slack Outgoing WebHook を設定することにより、 GAS を発火させることができます。 その手順を説明していきます。

まず、 https://SlackのWorkspace名.slack.com/apps にアクセスし、検索フォームから Outgoing WebHooks を検索し、選択してください。

f:id:k_kubouchi:20201110105029p:plain

遷移したら Slack に追加 を選択し、 Outgoing Webhook インテグレーションの追加 をクリックしてください。 そうすると設定画面になるので、追加していきます。 今回は KPT 振り返りの内容を投稿できるようにしたいので、以下のようにそれぞれ設定していきます。

  • チャンネル : 投稿で使用するチャンネルを設定
  • 引き金となる言葉 : 投稿内容に含まれるどの文字を検知して発火させるか指定(今回だと KPT
    • K: : Keep 内容投稿用
    • P: : Problem 内容投稿用
    • T: : Try 内容投稿用
    • start : スプリント単位で振り返り内容をスプレッドシートにまとめたいので、その始まり用のトリガー
    • end : スプリント単位で振り返り内容をスプレッドシートにまとめたいので、その終わり用のトリガー
  • URL : GAS の URL を指定

f:id:k_kubouchi:20201110093958p:plain

以上で、 GAS と Slack の連携設定は完了です。

4. チームでの運用ルール

以下のルールを元に、チーム内で運用しています。

  • スプリントの始まりと終わりにスプレッドシートのシートをスプリント単位で分けたいので、スクラムマスターが start end を投稿
  • KEEP の内容は K: を頭につけて投稿
  • PROBLEM の内容は P: を頭につけて投稿
  • TRY の内容は T: を頭につけて投稿

上記ルールに沿って投稿すると、bot が返信してくれるので、地味に面白かったりします(笑)。 (参考サイトがふなっしーを使って運用されてたので、そこに引きずられる形でボットの画像や口調も合わせて設定してみてます)

f:id:k_kubouchi:20201110094011p:plain

5. まとめ

SlackBot での振り返りを導入したことにより、以下のような感想をチームメンバーからもらうことができました。

- 思いついたタイミングで気軽に投稿できる
- 気づきを忘れなくなった
- スプリント単位で手軽に管理できる

短い期間のスプリントだと、効率良く少しずつ改善を繰り返していく仕組みが大事なので 少しの自動化も開発の一助になるのならと思い、今回導入してみましたがそれなりの価値があったのではないかと思います。

今後もメンバーの手助けになるような仕組みをどんどん導入していき、 円滑にスプリントを回していけるよう精進したいと思います!

ここまで読んでいただきありがとうございました!!

参考記事

Holmesはエンジニア・デザイナーを募集しています 興味がある方はぜひこちらからご連絡ください!

lab.holmescloud.com

lab.holmescloud.com