本文へスキップ

【LookerStudio】レポートをSlackに日次投稿する方法【GAS】

目次

Looker Studio にはメール配信はあるが、Slack に送る機能が無い。有料の Looker には標準で付いているが、別契約になる。ここでは GAS を挟んで、日次のレポートを Slack に投稿する形を作る。

(LookerはデフォルトでSlack通知機能がありますが、有料ツールで別途契約が必要です)

Google Cloud Documentation

基本的にメールよりSlackのほうがスマホだと見やすいので、Slackのチャンネル通知がしたいと思い試行錯誤した結果、GASを使って簡単に通知機能が作れたのでこちらにやり方を残しておきます。

完成イメージはこちらです。該当チャンネルにLooker Studioの該当レポートのPDFが送られてきます。PDFのほうがスマホで見やすいのですが、画像にすることもできます。このあたりはお好みで。

Slack の #inf_marketing_report チャンネル。Looker Studio から届いたレポートの PDF が添付ファイルとして投稿され、赤枠で囲まれている。レポートの中身はぼかしている

通知の仕組み

SlackAppの初期設定とGASのコード作成だけ必要ですが、基本的には各ツールの基本機能で対応が可能です。なにより無料で設定できるのは大きい。作業内容は以下のとおりです。

  • Looker Studioの「配信スケジュール」でメール配信を毎日するように設定
  • SlackのAppsを新規作成
  • GoogleAppScript(以下、GAS)でGmailを見に行く
  • 該当のメールが見つかったら、整形してSlackに送信
データフロー図。Looker Studio がレポートをメール配信し、Google Apps Script が Gmail から該当メールを探して投稿内容を作り、Slack に投稿する流れを矢印で示している

Looker Studioの「配信スケジュール」でメール配信を毎日するように設定。

作成したレポートの右上の共有から「配信のスケジュール」を選択。

Looker Studio の共有メニュー。他のユーザーを招待・配信のスケジュール・レポートへのリンクを取得・レポートをダウンロードが並び、配信のスケジュールが選択されている

メールの配信の細かい設定ができますが、開始時刻やリピートの間隔は必要に応じて変えてください。件名とメッセージはデフォルトでもいいのですが、GASで取得しやすいようにカスタマイズで固定の件名にしています。今回は日次のレポートなので「Daily Report」と命名。

Looker Studio のメール配信設定。件名に Daily Report、ページに Summary、開始時刻5:00、リピートに毎日を指定した部分が赤枠で囲まれている

隣のタブの「フィルタ」を選択すると、ReportやPageごとにどの期間やディメンションを選択した状態でレポートをメールで送るかを選択できます。今回は毎日前日の数字を送りたいので「昨日」に設定することが大切です。

もし週次であれば「過去7日間」、月次であれば「前月」などにするのが良いかなと思いますがここは目的に応じて変更しましょう。ディメンションもチャネル別やエリア別など設定した項目で絞り込めるのでここも必要なら選択。

Looker Studio のメール配信のフィルタタブ。日付範囲の指定欄で「昨日」を選ぶプルダウンが赤枠で囲まれている

設定ができたら左下の「今すぐ送信」を押してメールを確認。内容が問題なければ右下の「保存」を押します。

設定が完了するとスケジュール設定が確認できますが、今回は毎日05:00に前日の数字を集計したレポートをメール送信したいので、設定内容問題なさそうです。

Looker Studio のメール配信の確認画面。件名 Daily Report、ページ Summary、開始時刻、リピート毎日が赤枠で囲まれている

SlackのAppsを新規作成

Slack通知を実装する方法はいろいろとありますが、今回は「GAS×SlackApp」を採用しています。

SlackAppの実装方法はいろんな記事があるのでここでは説明を割愛しますが、このあたりの記事を参考に設定しました。(SlackAppの仕様がちょこちょこ変わるのでまとめるのは他の人にお任せします。。。!)

Zenn

Qiita

SlackAppが作成されており、「Install App」から「Bot User OAuth Token」が確認できれば問題ありません。
このkeyを使ってSlackに投稿ができるようになります。

Slack API の Your Apps 画面。作成済みのアプリが1件一覧表示され、右上に Create New App ボタンがある
Slack API の Install App 画面。OAuth Tokens のうち Bot User OAuth Token の欄が赤枠で囲まれている

「OAuth & Permissions」についても下記がBot Token Scopesについていれば問題ありません。

  • chat:write
  • files:write
  • remote_files:write
Slack API の Scopes 画面。Bot Token Scopes に chat:write、files:write、remote_files:write の3つが追加され赤枠で囲まれている

送りたいチャンネルに作成したAppを招待しておきます。招待されていないと投稿ができません。

該当のメールが見つかったら、整形してSlackに送信

新規のScriptファイルを作成したら、まずは下記のコードをコピーペーストで貼り付けます。chatGPTに作らせながら修正したので、チグハグ感は拭えないですが動けば良しの精神です。

このGASでは「Gmailを検索→該当があれば添付ファイルを取得→SlackAPIに送信→GmailでStarをつける」という処理をしています。 Star をつけることで処理済みという意味を持たせて次回からはスター付きのメールはスキップするようにしています。

一部、設定や変更が必要な箇所があるので解説していきます。

function postAttachmentToSlack() {
  // Slack Bot TokenとチャンネルIDを環境変数から取得
  var slackBotToken =
    PropertiesService.getScriptProperties().getProperty("SLACK_BOT_TOKEN");
  var slackChannelId =
    PropertiesService.getScriptProperties().getProperty("SLACK_CHANNEL_ID");

  // Gmailで未処理のメール(スターが付いていないメール)を検索
  var threads = GmailApp.search(
    'from:looker-studio-noreply@google.com subject:"Daily Report" -is:starred'
  );

  if (threads.length > 0) {
    for (var t = 0; t < threads.length; t++) {
      var messages = threads[t].getMessages();

      for (var m = 0; m < messages.length; m++) {
        var currentMessage = messages[m];
        var attachments = currentMessage.getAttachments();

        if (attachments.length > 0) {
          for (var i = 0; i < attachments.length; i++) {
            var attachment = attachments[i];

            // PDFファイルのみを処理
            if (attachment.getContentType() === "application/pdf") {
              var fileBlob = Utilities.newBlob(
                attachment.getBytes(),
                attachment.getContentType()
              );

              // 本日の日付をファイル名にする (例: 2024-10-07.pdf)
              var today = new Date();
              var formattedDate = Utilities.formatDate(
                today,
                Session.getScriptTimeZone(),
                "yyyy-MM-dd"
              );
              var fileName = formattedDate + ".pdf";

              // ファイルをSlackにアップロード
              uploadFileToSlack(
                fileName,
                fileBlob,
                slackBotToken,
                slackChannelId
              );

              // Looker StudioのURLを投稿
              var message =
                "Looker Studio Report URL:\nhttps://lookerstudio.google.com/u/0/reporting/085e7a69-2827-4e25-8530-cf468e87e26b/page/p_3nkl0kaold";
              UrlFetchApp.fetch("https://slack.com/api/chat.postMessage", {
                method: "post",
                headers: {
                  Authorization: "Bearer " + slackBotToken,
                  "Content-Type": "application/json",
                },
                payload: JSON.stringify({
                  channel: slackChannelId,
                  text: message,
                }),
              });
            }

            // メールにスターを付ける(次回以降処理しないように)
            currentMessage.star();
          }
        } else {
          Logger.log("添付ファイルが見つかりませんでした。");
        }
      }
    }
  } else {
    Logger.log("処理すべきメールが見つかりませんでした。");
  }
}

/**
 * Slackにファイルをアップロードする関数
 * @param {string} name - ファイル名
 * @param {Blob} fileBlob - アップロードするファイルのBlob
 * @param {string} token - SlackのAPIトークン
 * @param {string} channel - ファイルを投稿するチャンネルID
 */
function uploadFileToSlack(name, fileBlob, token, channel) {
  // 1. ファイルのサイズを取得
  var fileSize = fileBlob.getBytes().length;

  // 2. files.getUploadURLExternal APIを呼び出してURLとファイルIDを取得
  var uploadUrlResponse = UrlFetchApp.fetch(
    `https://slack.com/api/files.getUploadURLExternal?filename=${name}&length=${fileSize}`,
    {
      method: "get",
      headers: {
        Authorization: "Bearer " + token,
      },
    }
  );

  var uploadUrlJson = JSON.parse(uploadUrlResponse.getContentText());
  if (!uploadUrlJson.ok) {
    throw new Error("Failed to get upload URL: " + uploadUrlJson.error);
  }

  var uploadUrl = uploadUrlJson.upload_url;
  var fileId = uploadUrlJson.file_id;

  Logger.log("Upload URL: " + uploadUrl);
  Logger.log("File ID: " + fileId);

  // 3. 取得したURLにファイルをアップロード
  var uploadResponse = UrlFetchApp.fetch(uploadUrl, {
    method: "post",
    payload: fileBlob.getBytes(), // ファイルのバイトデータを送信
    headers: {
      "Content-Type": fileBlob.getContentType(), // ファイルのMIMEタイプを設定
    },
    muteHttpExceptions: true,
  });

  if (uploadResponse.getResponseCode() !== 200) {
    throw new Error(
      "Failed to upload file: " + uploadResponse.getContentText()
    );
  }

  // 4. files.completeUploadExternal APIにファイルIDとオプション引数を送信してアップロードを完了
  var completeUploadPayload = {
    files: [{ id: fileId, title: name }],
  };

  if (channel) {
    completeUploadPayload.channel_id = channel;
  }

  var completeUploadResponse = UrlFetchApp.fetch(
    "https://slack.com/api/files.completeUploadExternal",
    {
      method: "post",
      headers: {
        Authorization: "Bearer " + token,
        "Content-Type": "application/json",
      },
      payload: JSON.stringify(completeUploadPayload),
    }
  );

  var completeUploadJson = JSON.parse(completeUploadResponse.getContentText());
  if (!completeUploadJson.ok) {
    throw new Error("Failed to complete upload: " + completeUploadJson.error);
  }

  Logger.log(completeUploadJson);
  return completeUploadJson;
}

環境変数の設定

  // Slack Bot TokenとチャンネルIDを環境変数から取得
  var slackBotToken =
    PropertiesService.getScriptProperties().getProperty("SLACK_BOT_TOKEN");
  var slackChannelId =
    PropertiesService.getScriptProperties().getProperty("SLACK_CHANNEL_ID");

slackBotTokenとslackChannelIdはベタ書きですと安全性が低いため、環境変数で取得するようにしています。「スクリプトプロパティ」からそれぞれの値を設定します。

  • SLACK_BOT_TOKEN
  • SLACK_CHANNEL_ID

BOT_TOKEは先程のAppの画面からcopy、チャンネルIDがわからない場合はこちらを参照に取得ができます。

Slack のチャンネル ID の確認方法(Yoom ヘルプ)

Google Apps Script のプロジェクト設定。スクリプトプロパティに SLACK_BOT_TOKEN と SLACK_CHANNEL_ID を登録した部分が赤枠で囲まれている

Gmailの検索

  // Gmailで未処理のメール(スターが付いていないメール)を検索
  var threads = GmailApp.search(
    'from:looker-studio-noreply@google.com subject:"Daily Report" -is:starred'
  );

Gmailを検索する際に件名を見るようにしているのですが、 subject:"Daily Report" の値の部分はLooker Studioでメール配信をする時に設定した件名に変更するようにしてください。

逆に件名がレポートを送るメールごとにユニークでないとここで誤作動が起きてしまいます。

実行してみる

postAttachmentToSlack の変数を実行すると初回は認証が出ると思いますがそのまま承認、もう一度押して問題なく動けばSlackに投稿されると思います。

Logger.logにuploadされたURLなどが出てきたら成功です。「処理すべきメールが見つかりませんでした。」と出てきた場合は、Gmail検索がうまくいっていないので GmailApp.search の部分を色々と変更しながら確認してみてください。

Google Apps Script のエディタと実行ログ。postAttachmentToSlack 関数のコードが表示され、下部のログに Slack へのアップロード URL とファイル ID が出ている

トリガー設定

動くことを確認したら定期実行するために「トリガー」を設定します。

今回は「毎日8時からの出勤中に確認できるようにしたい」ため、日付ベースで午前7~8時の間にSlack通知されるように設定しました。このあたりは状況に応じて通知タイミングが頻度は調整してください。

Google Apps Script のトリガー編集画面。実行する関数に postAttachmentToSlack、イベントのソースに時間主導型、日付ベースのタイマーで午前7〜8時を指定した部分が赤枠で囲まれている

注意事項

今回は2025 年 3 月にSlackがfiles.upload API 廃止をしたため、添付ファイルを送る仕様やAPIが変更されていてそこに苦戦しました。。。Slackの仕様はちょこちょこ変わるのでもしSlackAPIでエラーが頻発する場合は最新のドキュメントを参考に修正してみてください。

まとめ

「Looker Studio×Slack」を実装することで、毎日自分が見るべき数字を通勤中のスマホから確認したり、毎週見るべきKPIをチームメンバーに共有する手間から開放されます。Looker Studioを使って自動化を更に進める一手として、記事が参考になれば幸いです。

RELATED

マーケティングの課題をご相談ください

現状のご説明だけでも構いません。まずはお問い合わせください。

ブログ一覧へ