デイリー・スクラムのSlack投稿をGASで半自動化してみた

#きっかけ
弊社ではスクラム開発を導入しており、毎朝11時にデイリー・スクラムを行っています。
デイリー・スクラムとはいわゆる朝会で、「昨日やったこと」「今日やること」「障害となっていること」の3点を口頭でチーム内に共有をするのですが、Slackにも同様のことを投稿しています。
Slack投稿の際、以下の2点がめんどくさいと思っていたので自動化を考えました。
+ 毎回、前日の「今日やること」の内容を、今日の「昨日やったこと」にコピペするのが地味に面倒
+ 11時のデイリー・スクラムの直前にSlack投稿しようと思うが、MTGや作業に集中していて投稿し忘れることがある

# スプレッドシートで入力欄を作る
シンプルにスプレッドシートのA列に「今日やること」、B列に「障害となっていること」、C列に「昨日やったこと」を書くことにしました。イメージはこんな感じ。
<img width="716" alt="スクリーンショット 2020-05-09 22.22.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/361666/883b2fe5-a7bc-65ff-922d-ef5aa6e35a9e.png">
運用としてはA列の「今日やること」とB列の「障害となっていること」を、毎朝デイリー・スクラム前（もしくは前日の退勤後）に手動入力して、C列の「昨日やったこと」の入力とSlackへの投稿を自動で行うようにします。

#Slackに投稿するためにスプレッドシートの内容を取得する
スプレッドシートの各列の入力値をGASで読み込みます。

```js
const sheet=SpreadsheetApp.getActiveSheet();

function getMessages() {
  // A列の今日やること
  // A列の最終入力行を取得する
  const todayEndRow=sheet.getRange(1,1).getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow()-1;
  // A列の入力値をまとめて取得する
  const todayTask=sheet.getRange(2,1,todayEndRow,1).getValues();
  // スプレッドシートからまとめて取得した入力値を入れる用の変数を用意して
  let todayTaskList='';
  // 各行の先頭に「・」を付ける (「・Qiitaに投稿する」みたいな箇条書きにするため)
  for (i in todayTask) {
    todayTaskList+='・'+todayTask[i][0];
    // 最終入力値以外の各行の末尾に改行を追加する (じゃないと「・Qiitaに投稿する・未読マンg・・技術書の消化」みたいに繋がってしまうから)
    if (null != todayTask[Number(i)+1]) {
      todayTaskList+='\n';
    }
  }

  // B列の障害となっていること
  // A列と同じ処理を書いて、troubleListにスプレッドシートの入力値を入れた

  // C列の昨日やったこと
  // A列と同じ処理を書いて、yesterdayTaskListにスプレッドシートの入力値を入れた

  // [今日やること, 障害となっていること, 昨日やったこと]
  return [todayTaskList, troubleList, yesterdayTaskList]
}
```

#前日の「今日やること」の内容を、今日の「昨日やったこと」にコピペする
自動化目的その1のコピペです。
処理としては
1. A列の今日やることの内容をコピー
2. A列の今日やることの内容を削除
3. B列の障害となっていることの内容を削除
4. B列の障害となっていることに「なし」と入力
　 「障害となっていること」は共有することがない場合が多いので、初期値を「なし」にする
5. C列の昨日やったことの内容を削除
6. C列の昨日やったことに、コピーしたA列の内容を貼り付ける

```js
function moveTask() {
  // A列の今日やることをコピーして削除
  // A列の最終入力行を取得する
  const todayEndRow=sheet.getRange(1,1).getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow()-1
  // A列の入力値をまとめて取得する
  const todayTask=sheet.getRange(2,1,todayEndRow,1).getValues();
  // A列の入力値を全て削除する
  sheet.getRange(2,1,todayEndRow,1).clear();
  // B列の障害となっていることを削除して、「なし」を入力
  // B列の最終入力行を取得する
  let shareEndRow=sheet.getRange(1,2).getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow()-1;
  // B列の入力値を全て削除する
  sheet.getRange(2,2,shareEndRow,1).clear();
  // B2セルに「なし」と入力する
  sheet.getRange(2,2).setValue('なし');
  // C列の昨日やったことを削除
  // C列の最終入力行を取得する
  let yesterdayEndRow=sheet.getRange(1,3).getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow()-1;
  // C列の入力値を全て削除する
  sheet.getRange(2,3,yesterdayEndRow,1).clear();
  // コピーした今日やることを昨日やったことに貼り付け
  // C列の入力値を全て削除したので、C列の最終入力行は2
  yesterdayEndRow=2;
  for (let i in todayTask) {
    // コピーしたA列の今日やることをC列の昨日やったことに順番に貼り付ける
    sheet.getRange(yesterdayEndRow,3).setValue(todayTask[i]);
    // C列の最終入力行を一つ進める
    yesterdayEndRow=sheet.getRange(1,3).getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow()+1;
  }
}
```
この処理をSlackに投稿した後に実行します。

#Slackに投稿する
Slack投稿については過去記事の[やり忘れのBacklog課題を定期的にSlackに通知してみる](https://qiita.com/takeh/items/61bb4ad4ffd7a18302cf#slack%E3%81%AB%E9%80%81%E4%BF%A1%E3%81%99%E3%82%8B)に書いたのでそちらで。

#Slackに投稿するメッセージを整形する
Slackにはこのような形で投稿することにします。
<img width="214" alt="スクリーンショット 2020-05-10 16.34.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/361666/81a7b8d7-4aec-ad8c-be90-848a5031bd61.png">

```js
// getMessages()でスプレッドシートから取得した値を引数に渡す
function slackMessage(todayMessage,troubleMessage,yesterdayMessage) {
  message+='■昨日やったこと\n';
  message+=yesterdayMessage + '\n';
  message+='■今日やること\n';
  message+=todayMessage + '\n';
  message+='■障害となっていること\n';
  message+=troubleMessage;

  // 作成したmessageをsendSlack()に渡す
  sendSlack(message);
}
```

#完成
全部合わせるとこのようになります。

```js
const sheet=SpreadsheetApp.getActiveSheet();

function myFunction() {
  const todayMessage=getMessage()[0];
  const troubleMessage=getMessage()[1];
  const yesterdayMessage=getMessage()[2];
  slackMessage(todayMessage,troubleMessage,yesterdayMessage)
}

// Slackに投稿するためにスプレッドシートの内容を取得する
function getMessages() {
  // A列の今日やること
  // A列の最終入力行を取得する
  const todayEndRow=sheet.getRange(1,1).getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow()-1;
  // A列の入力値をまとめて取得する
  const todayTask=sheet.getRange(2,1,todayEndRow,1).getValues();
  // スプレッドシートからまとめて取得した入力値を入れる用の変数を用意して
  let todayTaskList='';
  // 各行の先頭に「・」を付ける (「・Qiitaに投稿する」みたいな箇条書きにするため)
  for (i in todayTask) {
    todayTaskList+='・'+todayTask[i][0];
    // 最終入力値以外の各行の末尾に改行を追加する (じゃないと「・Qiitaに投稿する・未読マンg・・技術書の消化」みたいに繋がってしまうから)
    if (null != todayTask[Number(i)+1]) {
      todayTaskList+='\n';
    }
  }

  // B列の障害となっていること
  // A列と同じ処理を書いて、troubleListにスプレッドシートの入力値を入れた

  // C列の昨日やったこと
  // A列と同じ処理を書いて、yesterdayTaskListにスプレッドシートの入力値を入れた

  // [今日やること, 障害となっていること, 昨日やったこと]
  return [todayTaskList, troubleList, yesterdayTaskList]
}

// デイリースクラムメッセージ作成
function slackMessage(todayMessage,troubleMessage,yesterdayMessage) {
  message+='■昨日やったこと\n';
  message+=yesterdayMessage + '\n';
  message+='■今日やること\n';
  message+=todayMessage + '\n';
  message+='■障害となっていること\n';
  message+=troubleMessage;
  
  // 作成したmessageをsendSlack()に渡す
  sendSlack(message);
}

// Slackに送信
function sendSlack(message) {
  const postUrl='取得したWebhook URL';
  
  const json={
    'text': message
  };
  const payload=JSON.stringify(json);
  
  const options={
    'method': 'post',
    'contentType': 'application/json',
    'payload': payload
  };
  
  UrlFetchApp.fetch(postUrl,options);
}
```

これで完成と思いましたが、この`myFunction()`を毎日実行すると休みの日も無駄にSlack投稿をしてしまうので、スプレッドシートの今日やることが未入力（＝更新がなかった）場合は処理をスキップすることにします。

```js
function myFunction() {
  // スプレッドシートのA2セルが空欄ではない時に実行（＝休みの日は実行しない）
  if ('' != sheet.getRange(2,1).getValue()) {
    const todayMessage=getMessage()[0];
    const shareMessage=getMessage()[1];
    const yesterdayMessage=getMessage()[2];
    messageDS(todayMessage,shareMessage,yesterdayMessage)
  }
}
```

#Slackに自動投稿する
準備が整ったので、あとは自動化目的その2のSlack自動投稿です。
GASのトリガーで以下のように設定します。
+ 実行する関数を選択: myFunction
+ イベントのソースを選択: 時間主導型
+ 時間ベースのトリガーのタイプを選択: 日付ベースのタイマー
+ 時刻を選択: 午前10時〜11時
GASのトリガーの実行時間指定は1時間範囲での指定しかできません。
そのため11時ぴったりに実行したいというような指定はできません。
上記指定の場合だと10〜11時のどこかで実行されます。（本番では毎回10時31分に実行されています）

#感想
この記事を書いたことでバグ（不要な処理）に気づけたので良かった..

#We're hiring!
AIチャットボットを開発しています。
ご興味ある方は [Wantedlyページ](https://www.wantedly.com/projects/266837) からお気軽にご連絡ください！