Google Spreadsheet + Google Apps Script で Slack KPT Botを作ってみた

前回、Google Apps Scriptで作るSlack BotHello, World!!を紹介しました。

Google Apps Scriptを利用したSlack Bot作成の" Hello, World!! " - zuckey blog

今回は、応用としてSlack KPT Bot を作ってみたのでそれについて解説したいと思います。

KPT

よくある振り返りの手法にKPTがあります。

K:keep = 良かったこと(今後も続けること)
P:problem= 悪かったこと(今後はやめること)
T:try = 次に挑戦すること

上記の3つの要素を上げていくことで現状の分析をし、組織やプロジェクトを改善していく手法です。

KPTのやり方として、ホワイトボードに付箋を使ってKPTに分けて貼り付けていき項目ごとに深掘っていくというのが1つの一般的なやり方です。
しかし、人数が多めのチームでやろうとすると、ごちゃごちゃして時間もかかるので少し現実的ではありません。*1
そこで、KPT Botを使って、3つの要素をあげていくというプロセスをSlack上で完結させるようにしました。

どういうものを作ったのか

あるチャンネルで、

  1. 開始宣言する( start と投稿する)
  2. K:P:T:をプレフィックスとしてそれぞれの項目を投稿する
  3. 終了宣言する( end と投稿する)
  4. 開始 ~ 終了までの時間と、2で上げた項目が要素ごとにまとまって表示される

というものを作成しました。

f:id:zuckey_17:20180506180318p:plain:w300

裏側

本エントリのタイトルの通り、このSlack KPT BotGoogle Spreadsheet と Google Apps Scriptで作っています。
どういう仕組みになっているかというと以下のようになっています。

  1. 開始宣言(start)を受けて、先頭にある空のシートの名前を現在時刻にする
  2. K:P:T:をプレフィックスで判断して、1投稿1行ずつ、1列目に要素の種類、2列目に内容、3列目に投稿者名を書き込む
  3. 終了宣言( end )を受けて、その時刻と、2で登録された情報を要素に分別して表示する
  4. 終了処理として新しいシートを先頭に追加する

Spreadsheetを扱えるGoogle Apps Scriptの作成

Spreadsheetを扱うGoogle Apps Scriptのプロジェクトは、Spreadsheetの画面から作成します。
以下のように、ツール > スクリプトエディタをクリックすると、新しいタブでGoogle Apps Scriptの画面が表示されます。

f:id:zuckey_17:20180506180229p:plain f:id:zuckey_17:20180506180239p:plain

後は前回のエントリ同様の手順と変わりません。

スクリプト

var KEEP = 'KEEP';
var PLOBLEM = 'PLOBLEM';
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('すでに始まっています。');
    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 ploblemArray = [];
  var tryArray = [];
  if (lastRowNum === 0) { // 0行の場合は終了できない
    postSlack('登録された `KPT`がありません');
    return;
  }
  var rows = sheet.getRange(1, 1, lastRowNum, 3).getValues();
  rows.forEach(function(row) { // 行ごとにKPT3要素でまとめる
    switch(row[0]) {
      case KEEP:
        keepArray.push(row[1] + ': @' + row[2]);
        break;
      case PLOBLEM:
        ploblemArray.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( // まとめて投稿
    '```\n' +
    sheet.getName() + ' ~ ' + now + '\n\n' + 
    '# KEEP\n' + keepArray.join('\n') + '\n\n' +
    '# PROBLEM\n' + ploblemArray.join('\n') + '\n\n' +
    '# TRY\n' + tryArray.join('\n') +
    '\n```'
  );
  ss.insertSheet(0); // 新たな空シートを戦闘に追加
}

function registerKpt(text, userName) { // KPT登録処理
  postSlack('registering....');
  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]):(.*)$/);
  if (match === null) {
    return null;
  }
  return {
    category: convertCategory(match[1]),
    content: match[2],
  };
}

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

function postSlack(text){ // Slackへの投稿処理
  var url = "https://hooks.slack.com/services/~~~";
  var options = {
    "method" : "POST",
    "headers": {"Content-type": "application/json"},
    "payload" : '{"text":"' + text + '"}'
  };
  UrlFetchApp.fetch(url, options);
}

設定

基本的には、前回のエントリの踏襲になります。
そのため、詳細ははぶきますが、以下の項目だけ異なります。

Slack Outgoing WebHookの設定で、トリガーとなる単語と監視チャンネルの設定トリガーとなる単語に
K,P,T,start,endという文字列を入力します。

まとめ

Google SpreadsheetをデータソースにしてSlack Botを作成しました。
表示をもう少し見やすくわかりやすくするなどの改善はできそうだなと思っています。
これを使ったKPTがどうだったかについてもどこかで書きたいと思います。

感想、ご指摘などあれば、コメントもしくはTwitter @zuckey17まで連絡いただけますと嬉しいです!!

*1:そのくらいの人数が1チームなのはどうなのかというツッコミはここではスルーしますmm