Google スプレッドシート＆Google フォーム＆GASだけでサーバーレスなリモート状況報告システム (その3)

この投稿は、
[Google スプレッドシート＆Google フォーム＆GASだけでサーバーレスなリモート状況報告システム](https://qiita.com/rihok/items/ee1e4ba8eb88f2025585)
[Google スプレッドシート＆Google フォーム＆GASだけでサーバーレスなリモート状況報告システム (その2)](https://qiita.com/rihok/items/eb7efed39b5a0261a4c6)
の続きです。

前回までは以下
[1. GASのライブラリ化](https://qiita.com/rihok/items/eb7efed39b5a0261a4c6#1-gas%E3%81%AE%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E5%8C%96)
[2. 全員の状況を一覧表示する](https://qiita.com/rihok/items/eb7efed39b5a0261a4c6#2-%E5%85%A8%E5%93%A1%E3%81%AE%E7%8A%B6%E6%B3%81%E3%82%92%E4%B8%80%E8%A6%A7%E8%A1%A8%E7%A4%BA%E3%81%99%E3%82%8B)
[3. 一覧表示に必要な社員情報を管理](https://qiita.com/rihok/items/eb7efed39b5a0261a4c6#3-%E4%B8%80%E8%A6%A7%E8%A1%A8%E7%A4%BA%E3%81%AB%E5%BF%85%E8%A6%81%E3%81%AA%E7%A4%BE%E5%93%A1%E6%83%85%E5%A0%B1%E3%82%92%E7%AE%A1%E7%90%86)


## 4. 状況を選択して打刻

社員一人一人が自分の状況を送信するための入力ツールとしてはGoogleフォームを使用します。
Googleフォームで普通にフォームを作成します。

- ファイル名、フォームのタイトル、フォームの説明を入力
- 設問はラジオボタンで選択肢をテレワーク開始/テレワーク終了/出社/退社の4つを用意
基本的にはこれだけです。


個人の識別はメールアドレスで行うので、「設定」から「メールアドレスを収集する」をチェックしておきます。
そうすると、
「このフォームでは [G Suite上の組織名] のユーザーのメールアドレスが自動的に収集されます。」
という文言が表示されます。
以下、フォーム設定の参考まで。

![googleform01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73231/8b2cda2f-73fc-9e36-e957-359fe206f4e4.png)

#### GASでスクリプトを記述
ここまでできたら、次にGASでスクリプトを書いていきます。
フォームの入力内容を取得するGASを記述します。

フォームのスクリプトを記述するには、フォームの右上のメニューボタン（点が縦に3つ並んでるやつですね）をクリックして、その中の「スクリプトエディタ」を選択します。
そうするとスクリプトエディタが表示されるので、ここにコードを書いていきます。


```GAS:submitForm.gs
function submitForm(e){ 
  ///----------------------------------------------------------------------------
  //getActiveForm を呼び出す権限がありません」を解決するために実行する
  FormApp.getActiveForm();
  
  // フォームから入力された値を取得する
  var user_mail = e.response.getRespondentEmail(); // フォーム送信者のメールアドレス取得
  var itemResponses = e.response.getItemResponses(); // 回答群

  // フォームの項目を順番にチェック
  for (var i = 0; i < itemResponses.length; i++) { 
    var itemResponse = itemResponses[i];
    var question = itemResponse.getItem().getTitle(); // 質問
    var answer = itemResponse.getResponse(); // 答え
  }
}  
```
まず関数名は当たり障りなくsubmitFormにしておきます。
フォームからの入力内容を受け取るために、引数には`e`を指定しておきます。

次に、`FormApp.getActiveForm()`を実行し、フォームの実行権限を確認するダイアログを表示します。
初回実行時に、権限を許可するかどうかのダイアログが表示されます。

次に、引数eから内容を取得します。
まずはemailを、`e.response.getRespondentEmail()`で取得。
フォームの入力内容は`e.response.getItemResponses()`で取得し、itemResponsesに格納しておきます。
この例ではfor文で回していますが、今回は設問が1つしかないので、itemResponsesの配列の中身は1つだけです。あくまでここでは説明用にこのままにしておきます。

この中身itemResponseから、
`itemResponse.getItem().getTitle()`→質問
`itemResponse.getResponse()`→答え
が取得できます。
先の例で挙げている画像であれば、質問が「申請種類」答えが「テレワーク開始/テレワーク終了/出社/退社」のいずれかになります。



#### フォーム送信時に実行するトリガーにセット
このスクリプトを、フォーム送信時に実行するトリガーにセットします。

スクリプトエディタの画面で、「編集」→「現在のプロジェクトのトリガー」を選択します。
そうするとトリガーの管理画面が表示されるので、右下にある「トリガーを追加」ボタンからトリガーを追加します。

ダイアログが表示されるので

- 実行する関数　　：submitForm（スクリプトで付けた関数名）
- イベントのソース：フォームから
- イベントの種類　：フォーム送信時
- エラー通知　　　：今すぐ通知を受け取る

と設定しておきます。
エラー設定はどの設定でも問題ありませんが、運用してすぐのうちは特に、すぐにメールが飛んできてくれるとすぐ気がつけてよいです。

これでフォームにスクリプトが関連付きました。
フォームが送られる度に、GASスクリプトが実行されます。


## 5. 連携

GASスクリプトができたので、今度はメール / slack / スプレッドシートと連携します。
titleとcontentの2つの変数は、フォームから入力した値が反映されるようにしておきます。

#### (1) 内容をメールで送る
さっきのGASスクリプトの中に、メールを送信するスクリプトを追加します。

```
  var to_address = 'メール送信先のメールアドレス';
  var title = 'メールのタイトル';
  var content = 'メールの内容';
  var from_name = '名前';
  var user_mail = 'フォームを実行した人のメールアドレス';
  var options = {bcc: '', name: from_name, replyTo: user_mail}; // メール送信オプション設定  
  GmailApp.sendEmail(to_address, title, content, options);
```

GmailApp.sendEmailについては以下の公式ドキュメントを確認してください。
https://developers.google.com/apps-script/reference/gmail/gmail-app#sendEmail(String,String,String)

なお、メールの送信元は、このスクリプトの実行者のメールアドレスになります。
options にfrom: 'メールアドレス' を追加すると変更できるので、必要な場合は変更します。
（ただし、実行者のGmailアカウントにエイリアスを設定する必要があります）

#### (2) 内容をslackに通知する
事前にslack側で、Incoming WebHooksを設定し、urlを取得しておいてください。
あとは投稿ユーザー名やチャンネル名を設定しておきます。
投稿ユーザー名は何か決まったものがあるわけではないので好きに設定できます。

```
var webhook_url = "";
webhook_url = 'SLACK側で設定したWEBHOOK用URL';
var post_user_name = "投稿ユーザー名";
var post_to_channel_name = "#report"; // 投稿チャンネル名
var icon_emoji = "";
postToSlack(webhook_url, post_user_name, post_to_channel_name, "" + title + " " + content, icon_emoji);

function postToSlack(webhookUrl, userName, channelName, str, iconEmoji) {
  var payload  = { 
    'username'  : userName,
    'text'      : str,
    'channel'   : channelName,
    'icon_emoji': iconEmoji
  };
  var options = {
    'method'      : 'post',
    'contentType' : 'application/json',
    'payload'     : JSON.stringify(payload),
  };
  UrlFetchApp.fetch(webhookUrl, options);
}
```

#### (3) 内容を社員情報のスプレッドシートに追記する
勤務状況を表示するためのデータなので、フォームからの申請内容に応じたステータスに変更する必要があります。
ステータスは テレワーク中 / 出社中 / 業務外の3つで、

- 入力：テレワーク開始→ステータス：テレワーク中
- 入力：テレワーク終了→ステータス：業務外
- 入力：出社→ステータス：出社中
- 入力：退社→ステータス：業務外

という対応になります。
あとは普通にスプレッドシートに書き込みます。

```
  var spreadsheet = SpreadsheetApp.openById('スプレッドシートのID');
  var sheet = spreadsheet.getSheetByName('記入先のシート名');
  var data = [''];
  switch(applicationType) {
      case 'テレワーク開始':
        data[0] = 'テレワーク中';
        break;
      case 'テレワーク終了':
        data[0] = '業務外';
        break;
      case '出社':
        data[0] = '出社中';
        break;
      case '退社':
        data[0] = '業務外';
        break;
  }
  var startRow = userId; // フォーム入力者の列を指定する
  var starCol = 1; // 入力セルの開始行
  var numRows = data.length;
  var numCols = data[0].length;
  sheet.getRange(startRow, starCol, numRows, numCols).setValues([data]);
```

これらの連携により、フォームの入力をトリガーとして、メール送信、slackに通知、状態一覧に反映、という一連の流れが確立されます。


## 6. デバッグ

デフォルトのGASスクリプトエディタでは、フォームの入力値はフォーム実行時でないと取得できないため、デバッグはしにくいです。用意されている方法としては、フォームのトリガーがスクリプトのエラーをキャッチしているので、トリガー側でのエラーを確認します。

このエラーは、トリガー一覧画面でトリガーを表示したのち、該当トリガーの右端のメニューボタン（また3つの点が縦に並んでいるやつです）からメニューを開き、「失敗した実行」を選択すると表示されます。

トリガー画面でのエラー表示は、エラーの内容と箇所は示してくれます。
Loggerを仕込んでおくとここにログが出力されます。Loggerのログがないと状況を追うのはかなり難しいと思います…。

関数単位での確認などはGASスクリプトエディタ上でブレイクポイントを設定して確認できます。
フォームの入力に関しては、引数に値するオブジェクトを自分で用意しない限り、エディタ上ではデバッグできないはずです。


なお、トリガー設定時に、「エラー通知：今すぐ通知を受け取る」に設定していた場合は、エラーが発生したら即メールが飛んできて、エラー内容が把握できます。
これはもちろん、メールを送信する設定にしていなくても、トリガーの画面から実行の状況を確認できます。



## まとめ
3回に分けて書いてみましたが、いかがでしたでしょうか。
技術的には難しいことは何もないながらも、GASでできる範囲で、GSuite内の割と広い範囲を泳ぎながら有効に活用できてきてます。
細かいところはかなり飛ばしているので、部分的に別記事にまとめていってもいいかなと考えてます。