はじめに

この記事では、LookerのスケジューラをGoogle Apps Scriptで設定する方法を紹介します。

Lookerスケジューラとは

まずはLookerスケジューラとは何かについて公式ドキュメントを見ていきましょう。公式では以下のように説明されています。

Looker には、ダッシュボード、Look のデータテーブルやビジュアリゼーション、Explore のクエリのいずれかのコンテンツを、すでにワークフローの一部となっているアプリケーションやサービスに共有する方法がいくつかあります。
Looker のスケジューリング機能を使用すると、一度だけ実行する簡単なコンテンツ、または定期的に繰り返すコンテンツ(「スケジュール」と呼ばれます)をLookerのネイティブの配信先、またはLooker と統合されたサードパーティ サービスに配信することができます。
ネイティブの配信先には、メールアドレス、ウェブフック、Amazon S3バケット、およびSFTPサーバーがあります。

引用:Lookerスケジューラを使用したコンテンツの配信

つまり、LookerスケジューラはLookerで実行したクエリの結果を定期的に特定の場所に対して配信する機能です。
Lookerスケジューラを使用することで、定期的にデータを配信することができます。GUIで設定することもできますが、LookerのAPIを使ってスケジューラを設定することも可能です。

今回はこのLookerスケジューラをGoogle Apps Scriptで設定する方法を紹介します。
なお、GUIによるスケジューラの設定方法はLookerスケジューラを利用してMSPの運用状況をレポートとして配信する – iret.mediaを参照してください。

Looker スケジューラをGoogle Apps Scriptで設定するには

結論から先に説明すると、LookerのAPIを使うことでGoogle Apps Script(以降、本文ではGASと表記)でLooker スケジューラを設定できます。
必要となるものとしては、以下のようなものがあります。

  • Lookerのアクセスキー
  • Lookerのシークレットキー
  • Looker APIのエンドポイント

Looker APIのエンドポイントはLooker API Getting Started – iret.mediaで説明していますので参考にしていただければと思います。

なお、Looker APIを実行する具体例としてはGoogle Apps Script(GAS)でLooker APIの実行結果をSlackに通知してみた – iret.mediaがあります。

Looker APIについて知る

Looker APIでLooker スケジューラを設定できることがわかりましたが
ではLooker APIでスケジューラを設定するためにはどんなAPIを実行したら良いのでしょうか。

Looker APIのリファレンスを参照するとCreate Scheduled Planを使うことでスケジューラを設定できることがわかります。

Create a Scheduled Plan
Create a scheduled plan to render a Look or Dashboard on a recurring schedule.

引用:Create Scheduled Plan

APIの実行においてはリクエストのBodyに以下の値を指定する必要があります。

  • name:配信する際に使う名前
  • look_id:配信するLookのID
  • dashboard_id:配信するダッシュボードのID
  • lookml_dashboard_id:配信するLookMLダッシュボードのID
  • query_id:配信するクエリのID
  • cron_tabまたはdatagroup:配信するスケジュール
  • scheduled_plan_destination:配信先の設定

なお、リクエストする際の形式はJSONになりますので最終的なリクエストの形式は以下のようになります。

{
    "method": "post",
    "contentType": "application/json",
    "headers": {
    "Authorization": "token {access_token}"
    },
    "payload":
    {
        "user_id": "{user_id}",
        "name": "scheduled_name",
        "dashboard_id": "{dashboard_id}",
        "filters_string":"{filters_string}",
        "crontab": "{crontab}",
        "scheduled_plan_destination": "{scheduled_plan_destination_list}",
        "long_tables": true,
        "enabled": true
    }
}
パラメータ 説明
{access_token} Lookerのアクセストークン(アクセスキーとシークレットキーで取得した一時的な認証トークン)
{user_id} ユーザーID、Lookerが管理するユーザーのID(Looker APIのGet Current Userで取得可能)
{dashboard_id} ダッシュボードID、Lookerで作成したダッシュボードがもつID
{filters_string} フィルターの設定、検索条件に日本語を含む場合はURLエンコードが必要
{crontab} スケジュールの設定、cron形式で指定(どのように指定するかはのちほど説明します)
{scheduled_plan_destination} 配信先の設定

上記のとおり、Looker APIのCreate a Scheduled Planではさまざまなパラメータを指定する必要があります。
順番に解説します。

access_token

access_tokenはLookerのAPIキーを使って取得します。APIキーがない場合はLookerの管理者に発行してもらいましょう。

APIキーはclient_idclient_secretで構成されており、access_tokenを取得が可能です。
GASでは`UrlFetchApp`を使ってAPIを実行することで取得ができます。取得方法については下記のとおりです。

変数BASE_URL: Looker APIのエンドポイント
リクエストする際のパス:/login
メソッド:POST
content-type: “application/x-www-form-urlencoded”
payload: APIキーのclient_idclient_secret

function login() {
 try{
  var post = {
    method: 'post',
    contentType: 'application/x-www-form-urlencoded',
    payload: "client_id="+CLIENT_ID+"&client_secret="+CLIENT_SECRET
  };
  var response = UrlFetchApp.fetch(BASE_URL + "/login", post);
  return JSON.parse(response.getContentText()).access_token;
 } catch(err) {
  return "Could not login to Looker. " + err
 }
}

user_id

ユーザIDはLooker APIのGet Current Userで取得できます、これもGASで取得可能です。取得方法については下記のとおりです。

function TestGetCurrentUserId(){
  let res = GetCurrentUserId();
  console.log("レスポンスコード:" + res.getResponseCode);
  if(res.getResponseCode == 200){
    console.log("LookerUserId=" + JSON.parse(res.response)["id"]);
  }
}

function GetCurrentUserId() {
    try {
    // 必須パラメータ: dashboard_id

    var options = {
      'method': 'get',
      'contentType': 'application/json',
      'headers': {
        'Authorization': 'token ' + login()
      }
    };
    var response = UrlFetchApp.fetch(BASE_URL + "/user", options);

    return {
        'getResponseCode': Number(response.getResponseCode()),
        'response': response.getContentText()
    };
  } catch (err) {
    console.log(err);
    return "Uh oh! Something went wrong. Check your API credentials and if you're passing the correct parameters and that your Look exists!";
  }
}

引用:Get Current User – Looker APIのリファレンス

dashboard_id

dashboard_idは読んで字のごとくダッシュボードのIDであるため、ダッシュボードをブラウザで開いている人であれば、だれでもすぐに取得できます。
取得方法は簡単でダッシュボードを開くと以下のようなURLで開かれます。末尾の数値がそのままダッシュボードのIDになります。

Lookerのインスタンス名をyamada、dashboard_idを78とした場合は下記のとおりになります。

https://yamada.looker.com/dashboards/78

filters_string

filters_stringはデータを絞り込むもの文字列です。つまり、検索条件ということになります。
検索条件はURLの後ろにつくクエリ文字列を想定しているため、filters_stringはURLエンコードを想定しています。

GASでURL文字列を生成する場合はencode_query_stringsを使ってエンコードします。具体的には以下の内容です。

encode_query_strings("ID=XXXXXXX&指定月=2024-07")

crontab

Looker スケジューラの要、いつ配信するかの設定になります。インフラストラクチャー、とりわけ、Linuxに知見のあるユーザであれば、親しみやすいものだと思います。以下のような形式で指定します。

{min} {hour} {day of month} {month} {day of week}

わかりにくいので日本語に置き換えてみてみます。

{分} {時} {日} {月} {曜日}

たとえば、6:00 AM every Mondayと設定する場合は以下のとおりになります。

0 6 * * 1

{曜日}を指定すると{日} {月}がどんな値であっても無視されます。

火曜日にしたい場合は最後の1を2にします。

0 6 * * 2

最後にもうひとつ例を見ていきましょう。July on the 15th at 6:00 AMは下記のように表現できます。

0 6 15 7 *

法則がわかったのであとで迷わないように表にしておきましょう。(いくつか例を示します。)

日付 cron式
{min} {hour} {day of month} {month} {day of week} {分} {時} {日} {月} {曜日}
January on the 1st at 12:00 AM 0 0 1 1 *
February on the 1st at 12:00 AM 0 0 1 2 *
March on the 1st at 12:00 AM 0 0 1 3 *
April on the 1st at 12:00 AM 0 0 1 4 *
May on the 1st at 12:00 AM 0 0 1 5 *
June on the 1st at 12:00 AM 0 0 1 6 *
July on the 1st at 12:00 AM 0 0 1 7 *
August on the 1st at 12:00 AM 0 0 1 8 *
September on the 1st at 12:00 AM 0 0 1 9 *
October on the 1st at 12:00 AM 0 0 1 10 *
November on the 1st at 12:00 AM 0 0 1 11 *
December on the 1st at 12:00 AM 0 0 1 12 *

曜日を入れた場合

説明 cron式
{min} {hour} {day of month} {month} {day of week} {分} {時} {日} {月} {曜日}
6:00 AM every Sunday 0 6 * * 0
6:00 AM every Monday 0 6 * * 1
6:00 AM every Tuesday 0 6 * * 2
6:00 AM every Wednesday 0 6 * * 3
6:00 AM every Thursday 0 6 * * 4
6:00 AM every Friday 0 6 * * 5
6:00 AM every Saturday 0 6 * * 6

scheduled_plan_destination

scheduled_plan_destinationに指定するデータは配列です。具体的には以下のようになります。

{
    "scheduled_plan_destination":[
        {
            "format":"wysiwyg_pdf",
            "type":"email",
            "address":"{MailTo1}"
        },
        {
            "format":"wysiwyg_pdf",
            "type":"email",
            "address":"{MailTo2}"
        }
    ]
}

formatでは指定された送信先に送信するデータの形式を指定します。
対応しているものは以下のとおりです。

「txt」、「csv」、「inline_json」、「json」、「json_detail」、「xlsx」、「html」、「wysiwyg_pdf」、「wasiwyg_png」

上記の例では、MailTo1MailTo2にメールアドレスを指定することで、指定したメールアドレスにPDFを配信します。

参考:スケジュール設定されたプランを取得する

GASでLookerスケジューラを設定してみよう

APIの概要が理解できたところでGASでLookerスケジューラを設定します。

構成図

今回は以下のような構成で検証します。

送信スケジュールを組む人をレポート配信者、受け取る人をレポート購読者とします。
レポート購読者は複数人いることを想定しているため、構成図では3本線で表現しています。

なお、スケジュールを設定する人はLookerの管理者からアクセスキーを発行してもらう必要がありますので忘れずに対応しておきましょう。

GASを書く

今回はロジックとデータを分けて書きます。
つまりはスプレッドシートに送り先の一覧があった場合、その一覧に従ってLookerスケジューラを設定するというものです。

以下のような一覧を用意します。

では書いていきましょう。まずは認証用のスクリプトを書きます。

var BASE_URL = 'https://{インスタンス名}/api/4.0';

// Replace this with your API credentials
var CLIENT_ID = PropertiesService.getScriptProperties().getProperty("LOOKER_CLIENT_ID");
var CLIENT_SECRET = PropertiesService.getScriptProperties().getProperty("LOOKER_CLIENT_SECRET");

function login() {
 try{
  var post = {
    method: 'post',
    contentType: 'application/x-www-form-urlencoded',
    payload: "client_id="+CLIENT_ID+"&client_secret="+CLIENT_SECRET
  };
  var response = UrlFetchApp.fetch(BASE_URL + "/login", post);
  return JSON.parse(response.getContentText()).access_token;
 } catch(err) {
  return "Could not login to Looker. " + err
 }
}

次にスケジューラのAPIを実行するGASを書きます。

// スケジュールを設定する
function PostCreateScheduledPlan(user_id, scheduled_name, dashboard_id,crontab, filters_string, scheduled_plan_destination_list) {
  try {
    // 必須パラメータ: name,dashboard_id, crontab,scheduled_plan_destination
    // 任意のパラメータ: filters_string

    var options = {
      'method': 'post',
      'contentType': 'application/json',
      'headers': {
        'Authorization': 'token ' + login()
      },
      'payload':JSON.stringify(
        {
          'user_id': String(user_id),
          'name': scheduled_name,
          'dashboard_id': String(dashboard_id),
          'filters_string':filters_string,
          'crontab': crontab,
          'scheduled_plan_destination': scheduled_plan_destination_list,
          'include_links': false,
          'long_tables': true,
          'enabled': true
        }
      )
    };

    var response = UrlFetchApp.fetch(BASE_URL + "/scheduled_plans", options);
    let getContentText = JSON.parse(response);
    console.log(getContentText);
    let scheduled_plan_destination = getContentText["scheduled_plan_destination"].shift();

    return {
        'getResponseCode': response.getResponseCode(),
        'scheduled_plan_destination': scheduled_plan_destination["scheduled_plan_id"]
    };
  } catch (err) {
    console.log(err);
    return "Uh oh! Something went wrong. Check your API credentials and if you're passing the correct parameters and that your Look exists!";
  }
}

最後にmain関数を作成して実行します。変数dQueryにはURLエンコードされたダッシュボードのフィルタ文字列を設定します。

ダッシュボードのフィルタ文字列=検索条件

SHEET_ID="XXX";

function main(){
  let wSheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName("main");

  for(Cnt = 2;;Cnt++){
    if (wSheet.getRange(`A${Cnt}`).isBlank()){
      break;
    }
    let ReportName = wSheet.getRange(`A${Cnt}`).getValue();
    let ServiceId = wSheet.getRange(`B${Cnt}`).getValue();
    let FilterMonth = wSheet.getRange(`C${Cnt}`).getValue();
    let UserId = wSheet.getRange(`D${Cnt}`).getValue();
    let DashboadId = wSheet.getRange(`E${Cnt}`).getValue();
    let SendTime = wSheet.getRange(`F${Cnt}`).getValue();
    let MailTo = wSheet.getRange(`G${Cnt}`).getValue();

    let scheduled_plan_destination_list = [
              {
                'format':'wysiwyg_pdf',
                'type':'email',
                'address':MailTo
              }
            ];
    let dQuery = "ID=" + ServiceId + "&指定月=" + FilterMonth
    let res = PostCreateScheduledPlan(UserId, ReportName, DashboadId, SendTime, encode_query_strings(dQuery), scheduled_plan_destination_list);
    console.log(res);
  }
}
function encode_query_strings(dQuery){
  return "?"+encodeURI(dQuery);
}

実行するとシートには12ヶ月分のスケジュールが記載されているので12ヶ月分のスケジュールが設定されます。
※https://{インスタンス名}/account/schedules で確認できます。

まとめ

今回はLooker APIを使ってLookerスケジューラを設定してみました。
Looker APIは覚えるまでが大変ですが、マーケットプライスにあるAPI Explorerなどを活用することでどのような動きをするAPIかチェックできます。
また、今回はGASで実装しましたが、Looker APIには言語ごとにSDKが存在するのでSDKを使ってみるのも良いかもしれません。

なお、今回示したように直接エンドポイントにリクエストを送ってAPIを実行する方法は他の言語でも可能です。
用途に合わせてカスタマイズしていただけたらと思います。

おわり