Google Apps Scriptでデータを組織に流通させる：②Looker StudioのダッシュボードキャプチャをSlackに投稿する

# はじめに
せっかくダッシュボードを作ったものの見に行くのが面倒で、だんだん使わなくなるというのは意外とよくあることです。これを防ぐための一つの施策として、ダッシュボード化している情報の一部をコミュニケーションツールに流し込むというのがあります。データの方にいくのが面倒なのであれば、データに来てもらえばよいのです。

今回は、そのための方法として、

- [①BigQueryにクエリを叩いてSlackに投稿する（前回の記事）](https://qiita.com/SoySoySoyB/items/ac5ea008caef64dbe91c)
- **②Looker StudioのダッシュボードキャプチャをSlackに投稿する（今回の記事）**

の2種類を2回に分けて説明します。

これができると、

- シンプルなKPIの把握や、リンクに飛んで詳しく見たいものの導線設計はそれがしやすい①で
- パッと全体像を把握したいものは視覚的に理解しやすい②で

情報を取得できるので、簡易に状況を理解しつつ、より深く調べたければダッシュボード等に移動することもできますし、またその投稿を起点にしてスレッドで議論を深めることもできます。データを組織に流通させる効果的な方法として、ぜひ参考にしてもらえればと思います。


# こんな方におすすめ

- 整備したデータがより組織に流通するようにしたい
- Google Apps ScriptでBigQueryのデータを活用したい
- Google Apps ScriptでLooker Studioのダッシュボードを活用したい


# 取り上げる題材の背景

現在、私はデータ分析基盤／ダッシュボード構築のクライアントワークを行っています。その際、並行して動く複数プロジェクトのエフォートを調整するために、個々のプロジェクトにかけている工数をタイムリーに把握する必要があります。

そこで、私はGoogleカレンダーに記入したスケジュールをGoogle Apps Scriptで取得し集計しています（この話はここではしません）。そして取得したデータをわかりやすくするために、Looker Studioのダッシュボードにしています。

ダッシュボードは便利ではありますが、やはりいちいち見に行くのは面倒なので、Slackに投稿されてくれれば楽だなーというところからこの仕組みを作りました。


# Looker StudioのダッシュボードをGoogle Apps Scriptで取得して、Slackに投稿する

先に完成形をお見せしましょう。下記のコードを実行すると、Slackに次のような投稿がなされます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492462/f3e21288-7576-774f-98b8-35a88ff7a5fb.png)
ダッシュボードの部分はマスキングをしていますが、ダッシュボードのキャプチャがSlackに投稿されることで、データの概要を瞬時に理解することができます。さらに詳しく知りたければ、リンクから詳細を確認することができます。

これを実行するためのコードは以下の通りです。

```javascript
function postDashboardImage() {
  const report_url = 'https://lookerstudio.google.com/reporting/********/page/********'  // 投稿したいレポートのページのURLを指定
  const report_title = 'ReportTitle'  // 投稿したいレポートの名称を記入

  const report_id = report_url.split('/reporting/')[1].split('/page/')[0]
  const page_id = report_url.split('/page/')[1].split('/')[0]
  const target_file = 'Report_' + report_id + '_page_' + page_id + '.jpg'

  const looker_studio_image = getLookerStudioImage(report_title, target_file)

  const token = PropertiesService.getScriptProperties().getProperty('SLACK_BOT_TOKEN')
  const channel = PropertiesService.getScriptProperties().getProperty('SLACK_CHANNEL_ID')
  const image_title = 'Image Title'
  postSlackImage(token, channel, report_url, image_title, looker_studio_image)
}


function getLookerStudioImage(report_title, target_file){
  const query = 'subject:' + report_title + ' from:looker-studio-noreply@google.com'
  const thread = GmailApp.search(query)[0]
  const message = GmailApp.getMessagesForThread(thread)[0]
  const attachments = message.getAttachments()
  for (const attachment of attachments){
    if (attachment.getName() == target_file){
      return attachment
    }
  }
}


function postSlackImage(token, channel, report_url, image_title, looker_studio_image){
  const url = 'https://slack.com/api/files.upload'
  const headers = {
    'contentType': 'application/json',
    'authorization': `Bearer ${token}`
  }
  const payload = {
    'channels': channel,
    'initial_comment': report_url,
    'title': image_title,
    'file': looker_studio_image,
    'filetype': 'jpg'
  }
  if (thread_ts){
    payload['thread_ts'] = thread_ts
  }
  const options = {
    'method': 'post',
    'headers': headers,
    'payload': payload
  }
  const res = UrlFetchApp.fetch(url, options)
  Logger.log(res)
  return res
}
```


# コードの解説

と進めていきたいところですが、その前に事前準備が必要です。というのも、Looker Studioのダッシュボードはそのままでは画像として抽出することができないので、一度メールで送信して、その画像を検索して拾ってくるという仕組みになっているからです。


## 事前準備：Looker Studioのダッシュボードをメール配信する

Looker Studioでは、右上の「共有」＞「配信のスケジュール」から作ったダッシュボードを特定のメールアドレスに対して定期配信することができます。このとき、指定したページに対して、5つまでを埋め込みのサムネイル画像として、全てをPDFファイルとして送付することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492462/92fd6de1-8ed3-e7d2-8f46-280159e93249.png)
なお、ここでの注意点としては、サムネイルで埋め込める画像は5つまでなので、Slackに投稿したい画像がそのなかに含まれるようにすることです（気づかずに超過すると、急にメールが届かなくなります）。

また、今回はメールではなく、添付ファイルを利用する形になるので、メール自体はフィルタ処理してしまって問題ありません。私は下記のような設定で受信フォルダから除外しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492462/b3ae3882-7e43-9eb9-4eb7-5592668cf7d7.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492462/7f6ba4cd-a94f-12c6-a08b-13c852a19ea5.png)


## メールから添付画像を取得する

関連するのは下記のコードになります。

```javascript
function postDashboardImage() {
  const report_url = 'https://lookerstudio.google.com/reporting/********/page/********'  // 投稿したいレポートのページのURLを指定
  const report_title = 'ReportTitle'  // 投稿したいレポートの名称を記入

  const report_id = report_url.split('/reporting/')[1].split('/page/')[0]
  const page_id = report_url.split('/page/')[1].split('/')[0]
  const target_file = 'Report_' + report_id + '_page_' + page_id + '.jpg'

  const looker_studio_image = getLookerStudioImage(report_title, target_file)
```

```javascript
function getLookerStudioImage(report_title, target_file){
  const query = 'subject:' + report_title + ' from:looker-studio-noreply@google.com'
  const thread = GmailApp.search(query)[0]
  const message = GmailApp.getMessagesForThread(thread)[0]
  const attachments = message.getAttachments()
  for (const attachment of attachments){
    if (attachment.getName() == target_file){
      return attachment
    }
  }
}
```


### ポイント

- レポートのURL`https://lookerstudio.google.com/reporting/********/page/********`に対して、reportingとpageに挟まれたものがレポートID、page以降がページIDになる
- メールに埋め込まれているサムネイル画像は、`Report_{report_id}_page_{page_id}.jpg`という名称になっている
- `GmailApp.search(query)`でメールを検索し、その後はスレッド、メッセージ、が複数ヒットするので、`[0]`で最新のスレッドに限定している
- サムネイル画像の名称を検証することで、欲しいダッシュボードの画面を抽出する


## Slackに投稿する

関連するのは下記のコードになります。

```javascript
  const token = PropertiesService.getScriptProperties().getProperty('SLACK_BOT_TOKEN')
  const channel = PropertiesService.getScriptProperties().getProperty('SLACK_CHANNEL_ID')
  const image_title = 'Image Title'
  postSlackImage(token, channel, report_url, image_title, looker_studio_image)
```

```javascript
function postSlackImage(token, channel, report_url, image_title, looker_studio_image){
  const url = 'https://slack.com/api/files.upload'
  const headers = {
    'contentType': 'application/json',
    'authorization': `Bearer ${token}`
  }
  const payload = {
    'channels': channel,
    'initial_comment': report_url,
    'title': image_title,
    'file': looker_studio_image,
    'filetype': 'jpg'
  }
  if (thread_ts){
    payload['thread_ts'] = thread_ts
  }
  const options = {
    'method': 'post',
    'headers': headers,
    'payload': payload
  }
  const res = UrlFetchApp.fetch(url, options)
  Logger.log(res)
  return res
}
```


### ポイント

- クレデンシャルの情報はべた書きするのが望ましくないため、スクリプトプロパティに記載する
- Slackではhttps://api.slack.com/apps で事前にアプリを作成し、Bot Token Scopeに`files:write`の権限を付与した上で、投稿したいチャンネルにインストールしておく必要がある
- チャンネルIDは該当チャンネルの設定画面の最下部から取得できる
- 今回は使っていないが、thread_tsを指定するとスレッドへの返信として投稿できる
- SlackのAPIは色々間違っていても一応レスポンスは返ってきてエラーとして処理してくれないので、その点には注意が必要（この点には地味に苦労した）
- ちなみにUserとして投稿は可能であるし、この場合は投稿を削除できるので初期はそれで調整した方がよいが、Userの形では自動投稿の際に自分に通知が来ないので、最終的にはBotにする方がよい


# さいごに

この方法は、自分のデータ活用では大変役立っており、この件以外にも利用しています。設定はやや手間なのでどれもというわけにはいかないですが、よろしければ使ってみてください。


# 参考

https://qiita.com/mikene_koko/items/3861df2b2a529f03a5ef