こんにちは。 マネーフォワードでインターンをしております赤羽です。 最近、「営業戦略部」から「CIO室 コーポレートエンジニアリンググループ」に異動しました。
社内イベントの開催者向けに、イベント参加希望者を自動でカレンダー招待する仕組みを作ってみたので紹介します。
↓↓ 完成品 使用例 ↓↓
[video width="960" height="540" mp4="https://moneyforward.com/engineers_blog/wp-content/uploads/2021/07/googleform-to-googlecalendar1.mp4"][/video]
ツール作成の背景
社内でイベントを開催する際の手順として、
- カレンダーで社内イベントの予定を設定
- イベント告知
- 参加希望者を一人ひとり手作業でカレンダーに招待
が一般的な流れでした。 ですがこの場合、「3. 参加希望者を一人ひとり手作業でカレンダーに招待」が地味に手間が掛かります。
Slackで告知して、参加希望者はスレッドにリアクションするという方法などがありますが、100人200人の参加者をポチポチやるのは大変ですし、何より時間のロスです。 マネーフォワードでは、社内イベントや勉強会が日々開催されていますので、効率化のため申込からカレンダー招待を自動化するGoogleフォームを作成しました。
手法
ツールは「Googleカレンダー」と「Googleフォーム」を使用します。 Googleカレンダーで作成する予定名と、Googleフォームのタイトルを一致させることで、GASが自動で予定を識別する仕組みです。
全てGoogle Workspaceでまとまっているため、GASを使用することで簡単にカレンダーやフォームなど他のツールと連携できます。 また一度コードを記述したフォームを作成し、テンプレートとして用意すれば、フォームをコピーするだけで利用できます。
手順:
詳細な手順はこちら
- Googleカレンダーでイベントの予定を作成、予定名をコピーしておく
- Googleフォームを作成。フォームのタイトルにコピーしておいた予定名をペースト。
- Googleフォームからスクリプトエディタを開き、コードをコピペ。
- コード(main関数)を実行し、スクリプトエディタを閉じる。
- カレンダーの日付がフォームの設問の選択肢に反映されていることを確認
- フォームを公開。(イベント告知の際に一緒にリンクを配布)
- 希望者がフォームに回答すると自動で該当の日時のイベントに招待される
コード (GAS)
「3. スクリプトエディタを開き、後述するコードをコピペ」で使用するコードです。 関数ごとに分けて掲載していますが、全て一つの GASファイルにまとめていたものです。
カレンダーから予定取得・フォームの設問設定 (main)
// 手動で実行する関数 function main(){ var form = FormApp.getActiveForm(); var title = form.getTitle(); // Googleフォームのタイトル(カレンダー予定名)を取得 var info = get_my_schedule(title); // カレンダー予定名から予定のIDと日取りを取得 if(info[1]=="") { // カレンダーから予定が取得できなかった場合 console.log("予定が見つかりませんでした。\nGoogleフォームのタイトルと、カレンダーの予定名が一致しているか確認してください。") return; } // フォームの設問と選択肢を作成 form.deleteItem(0) form.addMultipleChoiceItem() .setTitle("参加日を選択してください") .setChoiceValues(info[1]) .setRequired(true) ScriptProperties.deleteAllProperties(); //念の為プロパティをリセット ScriptProperties.setProperty("date-id", JSON.stringify(info[0])); //配列を文字化してカレンダー情報を保存 // フォームが回答された時に実行するトリガーを’作成 deleteTrigger(); // 念の為トリガーをリセット ScriptApp.newTrigger('onForm') .forForm(form) .onFormSubmit() .create(); }
// カレンダーから予定取得用の関数 function get_my_schedule(event_title_name) { var now = new Date(); var fiveWeeksFromNow = new Date(now.getTime() + (35 * 24 * 60 * 60 * 1000)); //5週間分の予定を取得 var events = CalendarApp.getDefaultCalendar().getEvents(now, fiveWeeksFromNow); var array = []; var date_array = []; for(var i=0;i<events.length;i++){ var cal_title = events[i].getTitle() regexp = new RegExp((".*" + event_title_name + ".*"), 'g') if ( cal_title.match(regexp) ){ var cal_start = events[i].getStartTime() var youbi = ['日','月','火','水','木','金','土'][cal_start.getDay()] var cal_atart_Md = Utilities.formatDate(cal_start, "Asia/Tokyo", "M/d"); var cal_atart_HHmm = Utilities.formatDate(cal_start, "Asia/Tokyo", "HH:mm"); var cal_end = events[i].getEndTime() var cal_end_HHmm = Utilities.formatDate(cal_end, "Asia/Tokyo", "HH:mm"); var cal_icalid = events[i].getId() var date = cal_atart_Md + "(" + youbi + ") " + cal_atart_HHmm + "-" + cal_end_HHmm; var id = cal_icalid.toString() array.push([date, id]); // [予定の日付, カレンダーID] date_array.push(date); // 予定の日付のみの配列 } } return [array, date_array]; }
// 全てのトリガーを削除する関数 function deleteTrigger(){ var triggers = ScriptApp.getProjectTriggers(); for (var i = 0; i < triggers.length; i++) { ScriptApp.deleteTrigger(triggers[i]); } }
main関数は詳細な手順4で手動実行する関数です。 フォームのタイトルを取得し、get_my_schedule関数に渡してカレンダーと照合します。
照合で見つかった予定の日付と予定のIDが入った配列を、JSON.stringify()で強制的に文字列に変換してプロパティに保存しています(次のonForm関数実行時にカレンダーに招待するために使用するため)。 また取得した予定の日付からフォームの選択肢を設定しています。最後にフォームが回答された時に自動でonForm関数が実行する様にするトリガーをセットして終了です。
main関数内で使用されているget_my_schedule関数は、受け取った引数(フォームのタイトル)とカレンダーの予定名が一致する予定全てを取ってきてくれます。 現在から5週間分の予定を照合しているため、5週間以上先の予定は取得できません。該当の予定の日付とIDを配列型にして返しています。
main関数内でトリガー発行前に使われているdeleteTrigger関数は、単純にスクリプトに保存されているトリガー全てを削除する関数です。(特別関数に分ける必要もありませんが、見やすくまとめるために別関数に分けています。)
フォームが送信された際に、回答者を自動でカレンダーに招待 (onForm)
// フォームが回答された時に実行される関数 function onForm(e) { var array = ScriptProperties.getProperty("date-id"); array = JSON.parse(array); console.log("array: \n" + array); //入力者のemailを取得 var email = e.response.getRespondentEmail() //入力結果を取得(日付) var entry_day = e.response.getItemResponses()[0].getResponse(); console.log("入力された日付: " + entry_day); //カレンダーに招待 for(i in array){ var date = array[i][0]; var id = array[i][1]; if ( entry_day == date ) { var calendarApp_event = CalendarApp.getDefaultCalendar().getEventById(id); var response = calendarApp_event.addGuest(email) console.log(response.getGuestList()) } } }
onForm関数は参加希望者が、参加希望日を選択して送信した際に実行される関数です。引数eに送信されたGoogleフォームの情報が入ります。まずmain実行時にプロパティに保存されていた予定の日付とカレンダーIDを取り出します(文字列で保存されているので、JSON.parse()で配列型に戻します)。フォームで選択された日付と照合し、該当のカレンダーIDの予定に回答者をメールアドレスで招待します。
詰まったところ
配列のプロパティ保存
setProperty()を使ってプロパティに値を保存しておけば実行終了後もいつでも値を取り出せるわけですが、プロパティ保存は配列に対応していません。あくまで一つの値(文字列か数値)の保存にしか対応しておらず、配列を保存しようとすると文字化けしてしまいます。当時どうしても日付やIDなど複数の値を対応させた形で保存したかったので配列か辞書型で保存する必要がありました。
そこでJSON.stringify()を利用して配列全体を一つの文字列に変換し、保存します。こうするとgetProperty()でプロパティを取得した時もただの文字列として認識されますが、JSON.parse()を行うことにより配列型に戻せます。(これで配列の保存もOK!やったね!)
トリガー設定
少し手間取ったのがトリガー設定。作業工数を減らしたかったのと、普段GASを使わない人にとってトリガーを手動で設定するのは複雑になるので、コード内で完結させたいと思っていました。当初onEditやonOpenのように、フォーム回答時に実行される関数がGASに備え付けであると思っていましたが、無い...。
ですがGASのドキュメントを眺めていたところ、newTrigger()でトリガー作成の際にonFormSubmit()という関数が使えるようでした。main関数の中に組み込めが、使用者が手動でトリガーを設定する手間は無くなるので効率化に役立ちます。
工夫・意識したところ
「使用者がコードを触らずに実行できるように」「工数を少なくシンプルに」ということを毎回意識してツールを作成しています。
理想はコードを見ずに使用できることですが、今回は使用しているのがGoogleフォームということもあり、工数を増やさないためにスクリプトエディタを開いてもらうことは避けられませんでした...。 Googleフォームにも、スプレッドシートのようにボタンやタブが設置できてほしかった。
とりあえず、コードに精通していない人でも簡単にできることを目標にしてました。 そのためメインを実行するだけで、カレンダー取得もフォームの選択肢の書き換えもトリガーの設定も全て自動で行う様に作りました。 これにより、工数をだいぶ抑えられたと思います。
結果・反響
さっそく、こちらのツールを全社に周知しました。 (いつも私をフォローしてくださるコーポレートエンジニアの下村さんより全社周知)
予想以上に大きな反響をいただき、各所で需要があるのだと実感しました。 やっぱり効率化・自動化は色んな人の作業時間に直接影響を与えられるのでやりがいがあります。(楽しい。)
なお、マネーフォワードのCIO室は効率化・自動化を推進する仲間を募集中です! もし興味があればお気軽にご応募ください!
コーポレートエンジニアの募集 ←私の現在の所属はこちら コーポレートインフラの募集 ITサポートの募集
マネーフォワードでは、エンジニアを募集しています。 ご応募お待ちしています。
【サイトのご案内】 ■マネーフォワード採用サイト ■Wantedly ■京都開発拠点
【プロダクトのご紹介】 ■お金の見える化サービス 『マネーフォワード ME』 iPhone,iPad Android
■ビジネス向けバックオフィス向け業務効率化ソリューション 『マネーフォワード クラウド』
■だれでも貯まって増える お金の体質改善サービス 『マネーフォワード おかねせんせい』