Google Apps Script から Google Cloud Functions のHTTPトリガーを実行してみた

Googleフォームから Google Apps Script を介して Google Cloud Function の関数を呼び出す方法について書きます。

----

なお、この記事はタスク管理にGitHubを使う一連の記事の中の一つです。

- [情シスのタスクをGitHubのissueで管理するようにしたら捗った](https://qiita.com/itkr/items/1ae5578c1ca761dacf88)
- [プロジェクト管理ツールとしてGitHubを"普通に"使う](https://qiita.com/itkr/items/0d4c0015da28827b2bb7)
- [Google Cloud Functions (Python) を使ってみた](https://qiita.com/itkr/items/d3f8b4f8ff9becf101b4)
- [GitHub App を作ってPythonからAPIを叩いてみた](https://qiita.com/itkr/items/db5ed965bf8cac1cdd4e)
- **Google Apps Script から Google Cloud Functions のHTTPトリガーを実行してみた** ←ここ

## Google Cloud Functions の準備

### 使用するトリガー

Google Cloud Functions は実行するためのトリガーに紐づけるイベントには複数の選択肢があります。[ドキュメント](https://cloud.google.com/functions/docs/concepts/events-triggers?hl=ja)から引用すると以下のようなものがあります

>
> - HTTP - HTTP リクエストによって直接関数を起動します
> - Cloud Storage
> - Cloud Pub/Sub
> - Firebase（DB、Storage、アナリティクス、Auth）
>

今回はその中でもHTTPを使います

### HTTPトリガーで実行するときの認証

今回はGoogleフォームに入力されたときに Google Apps Script を通じて実行したいので、HTTPによるトリガーを設定しました。

ただ、このとき実行する関数には認証がかかっていないので、URLさえ分かっていれば誰でも実行できてしまいます。そのために何らかの方法で認証をする必要があります。

Googleは「[HTTP Cloud Function での認証](https://cloud.google.com/solutions/authentication-in-http-cloud-functions?hl=ja)」として、Google Cloud Storage を使う方法を紹介していました。しかし、現在そのページがリダイレクトされて見ることができなくなっているので、同様の方法は[メルカリのブログ](https://tech.mercari.com/entry/2019/03/28/171100)とかが参考になるかもしれません。もしかしたら今後もう少し楽な方法をGoogleが提供してくれるかもしれないですね。

## Google Apps Script の作成

Gooleフォーム用の Google Apps Script の作成のしかたはもうすでに複数の紹介ブログやドキュメントがありますが、軽く説明すると、Googleフォームでフォームを作成し、

```
三つの点のマーク → スクリプトエディタ
```

で作成できます。Java Script がわかれば構文には苦労しないと思います。

![screen1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14918/a34b99a7-a0df-afdd-2691-f897c922c998.png)

### 定数の扱い

今回は Google Cloud Functions を使ったりメールアドレスを扱ったりするので、それらの情報をGitHubのリポジトリになるべく載せたくありません。そこで Google Apps Script のプロパティを指定します。

```
ファイル → プロジェクトのプロパティ → スクリプトのプロパティ
```

と進むとkeyとvalueが設定できます。ただしこれはフォーム自体のオーナー権限を持っている必要があります。

![screen2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14918/f45d36fc-fcf2-083e-9413-2cad9f0391f4.png)

作成したプロパティは以下のように取得できます。

```
PropertiesService.getScriptProperties().getProperty('key_name');
```

### 実装

作ったものは、フォームがSubmitされたら、入力内容をMarkdownに変換して Google Cloud Functions のHTTPトリガーのエンドポイントにPOSTするコードです。実際のコードでは最終的にissueのURLが取得できるので、それをSlackやメールで飛ばしています。

```js
FormApp.getActiveForm()

var GCF_ACCESS_KEY = PropertiesService.getScriptProperties().getProperty('GCF_ACCESS_KEY');
var FUNCTIONS_URL = PropertiesService.getScriptProperties().getProperty('FUNCTIONS_URL');

function objectToMarkdown (items) {
      // key, value のオブジェクトをMarkdownに変換する
      var markdown = ''
      items.forEach(function (item, index) {
              markdown += '## ' + item['title'] + '\n\n'
              markdown += item['value'] + '\n\n'
            })
      return markdown
}

function responseToObject (items) {
      // レスポンスの内容を key, value のオブジェクトにする
      var result = []
      items.forEach(function (item, index) {
              result.push({
                        'title': item.getItem().getTitle(),
                        'value': item.getResponse()
                      })
            })
      return result
}

function sendToCloudFunctions (title, body, assignees) {
      // Google Cloud Functions のエンドポイントにPOSTする
      var options = {
              'method': 'post',
              'contentType': 'application/json',
              'payload': JSON.stringify({
                        'body': body,
                        'title': title,
                        'assignees': assignees,
                        'labels': 'development'
                      }),
              'headers': {
                        'Authorization': 'Bearer ' + GCF_ACCESS_KEY
                      }
            }
      return UrlFetchApp.fetch(FUNCTIONS_URL, options)
}

function onFormSubmit (e) {
      var author = e.response.getRespondentEmail()
      var items = responseToObject(e.response.getItemResponses())
      var title = 'Test Issue'
      var body = objectToMarkdown(items)
      var assignees = ['octocat']
      var response = sendToCloudFunctions(title, body, assignees)
      var issueUrl = JSON.parse(response.getContentText())['html_url']
      // ...
      // メールを飛ばす処理など
}
```

### デプロイ

毎回スクリプトエディタを開いて編集するのもいいですが、できるならローカル環境で開発してgit管理したいです。そのためにローカルからデプロイするツールを導入すると便利です。

#### clasp

ローカル環境からデプロイするために [clasp](https://github.com/google/clasp) というツールを使います。

claspについてはこちらを参考にしました。
https://qiita.com/HeRo/items/4e65dcc82783b2766c03

##### インストール

[claspのREADME](https://github.com/google/clasp#install)に従ってインストールします。

> 
> ```
> sudo npm i @google/clasp -g
> ```
> 

##### ログイン

GoogleのIDでログインします

```
$ clasp login
```

#### スクリプトIDの取得

デプロイするためにはスクリプトIDというのが必要になります。スクリプトIDは Google Form のスクリプトエディタから取得できます。

```
ファイル → プロジェクトのプロパティ → 情報 → スクリプトID
```

![screen3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14918/4d9e76ea-774b-efe4-9a74-a6ea1e043418.png)


#### デプロイ

`.clasp.json` というファイルを作ってスクリプトIDを記載します。デプロイ対象のディレクトリも指定することができます。

```.clasp.json
{
    "scriptId": "[scriptId]",
    "rootDir":"./"
}
```

デプロイするコマンド

```bash
$ clasp push
$ clasp deploy
```

### フォーマット

Google Apps Script は JavaScriptのようなものだと思っているので、ついでに `eslint` を使って整形するとよいかと思います。

ESLintはこちらを参考にしました。
https://qiita.com/munieru_jp/items/ca16cbfa859468137d2e#eslint%E3%81%A7%E4%BD%BF%E3%81%86