CUEBiC TEC BLOG

キュービックTECチームの技術ネタを投稿しております。

Tableau REST APIを使ってスプシ連携ツールを作ってみた

どうもキュービックのテックリードをやっている尾﨑です。2月10日にデブサミに登壇してきました。

アーキテクチャの中のサービスに関して1つ1つ詳細をお話しすることができなかったので、利用技術やハマった点などを交えてご紹介していきたいと思います。 今回はTableau REST APIを使ってスプレッドシート連携を自動化した事例をご紹介します。 ボリュームがあるので、手っ取り早くTableau APIの良し悪しと用法を把握したい方は目次の5以降を参照ください。

1.なぜTableau REST APIでスプシ連携ツールを作ろうと思ったか

キュービックではBIツールとしてTableauを使用しており、収集したデータを加工・集計してビジネスサイドがモニタリングできるようにアウトプットをTableauのワークブック上にビジュアライゼーションしています。TableauにはエクセルやCSVダウンロード機能はあるものの、標準でスプレッドシート連携機能がなかったので、こちらを最小工数で自動化できないか?と考えたのがきっかけでした。

2.スプシ連携ツールの構成

構成1

構成2

3.スプシ連携ツール作成時に工夫したところ

  • 1.ちゃんと使ってもらえるツールを考えたところ

    • 敢えてWEBアプリケーション化しないことで運用上の不要なアクション数を減らした
  • 2.運用者FB/トライアル期間を挟んでリリースしたこと

  • 3.GASでデータ取得と整形に関するロジックはライブラリー化したこと

    • 別途テンプレートファイルを作成し、メインロジックが汚染されないようにした

4.スプシ連携ツールの改善点

  • 問題
    • スプシのテンプレートに更新があった際にバージョン追随ができない
    • 運用者に最新テンプレートに乗り換えてもらう必要がある
  • 課題(解決策)
    • 移行コストを如何に削減するか?を考慮する方向で検討 (WEBアプリ化はしない)

★5.Tableau REST APIの主要な機能

作成したスプシ連携ツールでは、TableauのView情報を取得(参照)することを目的としているため、 Tableauに対してAPIで登録/更新/削除を実行するようなものは触れません。

★6.スプシ連携ツールの実装サンプル

構成としては以下に分けています。

  • 1.REST APIで取得する部分
  • 2.スプシからパラメータ情報の読み込みと取得結果の出力
  • 今回は1を中心に解説します。
    以下のような方向けにできるだけ分かりやすく解説していきます

    • これからTableauのREST APIを「使ってみよう」と思っている方
    • TableauのREST APIで「疎通」しているけどうまくいかない
    • TableauのREST APIで「エラーの解消方法が分からない」
    • TableauのREST APIで「参考」になる情報を探している
    • TableauのREST APIで「簡単」なツールを作りたい

    アクセストークン取得

    まずは、個人用アクセストークンを発行し、アクセストークンとsite-idを取得します。

    リクエスト情報

    大項目 小項目
    エンドポイント https://10az.online.tableau.com/api/3.4/auth/signin
    HTTPメソッド POST


    ヘッダー 設定値
    Content-Type application/json
    Accept application/json


    パラメータ 概要 /設定値
    personalAccessTokenName 2要素認証対応のアクセストークンを生成時に設定した名前 個人用アクセストークンを参照
    personalAccessTokenSecret 2要素認証対応のアクセストークンを生成時に生成されたキー個人用アクセストークンを参照
    contentUrl tableauのURLのsite/の後の文字列https://10az.online.tableau.com/#/site/{contentUrl}/

    ソースコードサンプル

    1.リクエスト情報のパラメータの設定値に指定した以下の情報を設定してください。

    • personalAccessTokenName
    • personalAccessTokenSecret
    • contentUrl
    function getTableauAcccessToken() {
      // POSTの値
      var requestPayload = {
        "credentials": {
          "personalAccessTokenName": "your Access Key Name",//personalAccessTokenNameを設定
          "personalAccessTokenSecret": "your Access Key",//personalAccessTokenSecretを設定
          "site": {
            "contentUrl": "your content URL"//contentUrlを設定
          }
        }
      }
      // リクエストヘッダー
      var requestHeaders = {
          'Content-Type': 'application/json',
          'Accept' : 'application/json'
      }
      // リクエストオプション
      var requestOptions = {
          'method' : 'post',
          'payload' : JSON.stringify(requestPayload),
          'headers' : requestHeaders,
      }
      var requestUrl = 'https://10az.online.tableau.com/api/3.4/auth/signin';
      var response = UrlFetchApp.fetch(requestUrl, requestOptions);
      var responseCode = response.getResponseCode();
      var responseText = response.getContentText();
      var result = JSON.parse(responseText)['credentials']//アクセストークンなどの認証情報を取得
      return result;
    }
    

    2.APIのresponse結果としてcredentialsを取得します

    • リクエストが成功するとsiteのid(site-id)とtoken(アクセストークン)が取得できます
    {
        "credentials": {
            "site": {
                "id": "sample",
                "contentUrl": "sample"
            },
            "user": {
                "id": "sample"
            },
            "token": "sample",
            "estimatedTimeToExpiration": "270:11:00"
        }
    }
    

    ワークブック情報取得

    次に、Tableauのワークブックの情報を取得します。

    リクエスト情報

    大項目 小項目
    エンドポイント https://10az.online.tableau.com/api/3.17/sites/{site-id}/workbooks
    HTTPメソッド GET
    • ※site-idに関しては「アクセストークン取得」にて取得


    ヘッダー 設定値
    X-Tableau-Auth 「アクセストークン取得」で取得したアクセストーク
    Accept application/json


    パラメータ 概要 /設定値
    pageSize 取得データ件数(defaultは100件)

    1.アクセストークン取得で取得した以下の情報を使用します

    • siteのid(siteId)
    • token(アクセストークン)

    ソースコードサンプル

    function getWorkBookInfo() {
      tokenInfo = getTableauAcccessToken();
      var pageNumber = 1;
      // リクエストヘッダー
      var requestHeaders = {
          'X-Tableau-Auth' : tokenInfo['token'],//アクセストークンをリクエストヘッダーに指定
          'Accept' : 'application/json'
      }
    
      // リクエストオプション
      var requestOptions = {
          "method" : "get",
          "headers" : requestHeaders
      }
      var siteId = tokenInfo['site']['id'];//siteのidを設定
      var requestUrl = `https://10az.online.tableau.com/api/3.17/sites/${siteId}/workbooks?pageSize=1000`;
      var response = UrlFetchApp.fetch(requestUrl, requestOptions);
      var responseCode = response.getResponseCode();
      var responseText = response.getContentText();
      var result = JSON.parse(responseText)['workbooks'];//ワークブックの情報を取得
      return  result;
    }
    

    2.APIのresponse結果としてworkbooksを取得します

    • リクエストが成功すると以下のような情報が取得できます
    {
        "pagination": {
            "pageNumber": "1",
            "pageSize": "100",
            "totalAvailable": "304"
        },
        "workbooks": {
            "workbook": [
                {
                    "project": {
                        "id": "0c5b75e8-8895-4d17-a9fb-f3047a",
                        "name": "転職"
                    },
                    "location": {
                        "id": "0c5b75e8-8895-4d17-a9fb-f3047a",
                        "type": "Project",
                        "name": "転職"
                    },
                    "owner": {
                        "id": "3f5dfad5-1f00-41ed-9eb8-7",
                        "name": "sample@cuebic.co.jp"//オーナーのメールアドレス
                    },
                    "tags": {
                    },
                    "dataAccelerationConfig": {
                        "accelerationEnabled": false
                    },
                    "id": "e25e09f0-a2db-49dc-999e-eb6f6",
                    "name": "分析用",
                    "description": "",
                    "contentUrl": "_0",
                    "webpageUrl": "https://10az.online.tableau.com/#/site/contentUrl/workbooks/1521246",//ワークブックのURL
                    "showTabs": "true",
                    "size": "99",
                    "createdAt": "2019-05-04T04:04:37Z",
                    "updatedAt": "2021-07-13T05:21:00Z",
                    "encryptExtracts": "false",
                    "defaultViewId": "45776cde-f128-442f-92b"
                },
      ]
        }
    }
    

    3.もし必須情報を取得したい場合は以下をresultの後続処理に追加すると良い感じにデータが取得できます

      var kaigyo ="\n";
      var workBookInfoArray = [];
      workBookInfoArray[0] =[
          `ワークブック名${kaigyo}name`,
          `ワークブックID${kaigyo}id`,
          `ワークブックURL${kaigyo}webpageUrl`,
          `プロジェクトID${kaigyo}project:id`,
          `プロジェクト名${kaigyo}project:name`,
          `オーナーID${kaigyo}owner:id`,
          `オーナーメールアドレス${kaigyo}owner:name`
      ]
      var j= 0;
      var cnt = 1;
      for(i=0; i<result['workbook'].length; i++){
        if(!result['workbook'][j]['project']){
          j++
          continue;
        }
        workBookInfoArray[cnt]= [
          result['workbook'][j]['name'],
          result['workbook'][j]['id'],
          result['workbook'][j]['webpageUrl'],
          result['workbook'][j]['project']['id'],
          result['workbook'][j]['project']['name'],
          result['workbook'][j]['owner']['id'],
          result['workbook'][j]['owner']['name']
        ]
        //console.log( workBookInfoArray[i]);
        j++
       cnt ++
      }
    

    View一覧取得

    次に、TableauのワークブックのViewのリストを取得します。

    リクエスト情報

    大項目 小項目
    エンドポイント https://10az.online.tableau.com/api/3.17/sites/{site-id}/workbooks/{workbook-id}/views
    HTTPメソッド GET
    • ※site-idに関しては「アクセストークン取得」にて取得
    • ※workbook-idに関しては「ワークブック情報取得」にて取得


    ヘッダー 設定値
    X-Tableau-Auth 「アクセストークン取得」で取得したアクセストーク
    Accept application/json


    パラメータ 概要 /設定値
    pageSize 取得データ件数(defaultは100件)

    1.ワークブック情報取得で取得したworkbookのid(workBooksId)を使用してViewの一覧を取得します

    ソースコードサンプル

    function getViewID(name,workBooksId) {
      tokenInfo = getTableauAcccessToken();
      // リクエストヘッダー
      var requestHeaders = {
          'X-Tableau-Auth' : tokenInfo['token'],
          'Accept' : 'application/json',
          'pageSize' : 1000
      }
    
      // リクエストオプション
      var requestOptions = {
          "method" : "get",
          "headers" : requestHeaders
      }
      var siteId = tokenInfo['site']['id']
      var requestUrl = `https://10az.online.tableau.com/api/3.17/sites/${siteId}/workbooks/${workBooksId}/views`;//siteIdとワークブック情報取得で取得したworkBooksIdを設定してください
      var response = UrlFetchApp.fetch(requestUrl, requestOptions)
      var responseText = response.getContentText()
      var result = JSON.parse(responseText)['views'];
      return result;
    }
    

    2.APIのresponse結果としてviewsを取得します

    • リクエストが成功すると以下のような情報が取得できます
    • これで取得したいワークブックのIDとView(シート)のIDが取得できました。
    {
        "views": {
            "view": [
                {
                    "tags": {
                    },
                    "id": "bb9aeee4-b144-49e3-9e66-2749c9",//ViewのID
                    "name": "広告アカウント_抽出",//View(ワークブックのシート名)
                    "contentUrl": "AD/sheets/PBID_",
                    "createdAt": "2023-02-15T13:09:57Z",
                    "updatedAt": "2023-02-21T02:41:15Z",
                    "viewUrlName": "PBID_"
                },
      ]
        }
    }
    

    View情報取得

    これでView情報を取得するのに必要な情報が集まりました。 今度は、TableauのワークブックのViewの詳細情報を取得します。

    リクエスト情報

    大項目 小項目
    エンドポイント https://10az.online.tableau.com/api/3.17/sites/{site-id}/views/{view-id}/data
    HTTPメソッド GET
    • ※site-idに関しては「アクセストークン取得」にて取得
    • ※workbook-idに関しては「ワークブック情報取得」にて取得
    • ※view-idに関してはViewsリスト取にて取得


    ヘッダー 設定値
    X-Tableau-Auth 「アクセストークン取得」で取得したアクセストーク
    Accept application/json


    パラメータ 概要 /設定値
    vf_ {fieldname} vf_{filter_name}の表記でパラムを設定してTableauのView上のフィルターと同等の絞り込みを実現できる

    1.View一覧取得で取得したviewIdを指定してViewの詳細情報を取得します

    ソースコードサンプル

    function getViewinfo() {
      var tokenInfo = getTableauAcccessToken();
      
     // リクエストヘッダー
      var requestHeaders = {
          'X-Tableau-Auth' : tokenInfo['token'],
          'Accept' : 'application/json'
      }
    
      // リクエストオプション
      var requestOptions = {
          "method" : "get",
          "headers" : requestHeaders,
          "muteHttpExceptions" : true
      }
      var siteId = tokenInfo['site']['id']
      //filterを適用したい場合は末尾に{vf_フィルター名=フィルタ内容}を設定
      var requestUrl = `https://10az.online.tableau.com/api/3.17/sites/${siteId}/views/${viewId}/data`;//siteIdとView一覧取得で取得したviewIdを設定する
      var response = UrlFetchApp.fetch(requestUrl, requestOptions)
      var responseText = response.getContentText();
      result = Utilities.parseCsv(responseText);
      return result;
    }
    

    2.APIのresponse結果としてviewの詳細情報を取得します - リクエストが成功すると以下のようなCSVデータが取得できます

    ad_media,Measure Names,Month of day,Year of day,粗利,Measure Values
    FB2,imp,August,FY 2020,"115,343","94,140"
    FB2,click,August,FY 2020,"115,343","1,480"
    FB2,ctr,August,FY 2020,"115,343",0.015721266
    FB2,cpc,August,FY 2020,"115,343",85.376351351
    FB2,cost,August,FY 2020,"115,343","126,357"
    FB2,avg.position,August,FY 2020,"115,343",
    FB2,preCV,August,FY 2020,"115,343",219

    ★7.Tableau REST APIの良かったところ

    • 1. ワークブックの一覧が取れる

      • ワークブックのURLやワークブックのオーナーのメールアドレスが取得できる
      • 運用調整時に誰に相談すれば良いのか?が分からないシーンで助かりました
      • 結果的に調査工数削減にもつながりました
    • 2.Viewの一覧が取れる

      • ワークブックと同じくあのViewの所在確認で困らなくなりました
      • Viewの詳細が取得できないものはこちらの時点で一覧に存在しないパターンが多いです(編集中/未publishなど)
    • 3.Viewの詳細が取れる

      • 本命です。運用者が普段使用してるスプレッドシートにシームレスで情報連携が可能になりました
    • 4.Viewのフィルターを指定できる

      • ワークブックを再保存しなくてもフィルター適用状態で取得が可能でした
      • 特定期間のデータや特定組織/メディアの情報を取得したい際に重宝しました
      • APIでデフォルトで取得されるのはワークブック保存時のフィルタ情報のようです
    • 5.Tableauのアカウント保持者以外もデータが擬似的に参照できるようになった

      • ワークブックの設計者以外も参照可能になりました
      • 結果として運用スキルとコストの削減につながりまし

    ★8.Tableau REST APIの改善して欲しいところ

    • Viewで定義されているフィルター情報の一覧をView毎に取得することができない 
      • 現在はスプレッドシート上に最大公約数的にフィルターに相当するパラメータを用意しておき、運用者に埋めてもらっている状態
      • Tableauで運用者がviewのフォーマットを意図せずに更新するとエラーが起きてしまう可能性があります
    • API keyが最大で1年しか持たないところ
      • 連続したアクセスが2週間以上ない場合は非有効化されるのでGASでトリガーを設定しています
      • アラートを設定したとしてもツール作成者居なくなったら負債化する可能性があります
    • CSVでエクスポートしたものとAPIで取得したデータの出力フォーマットが異なる
      • CSVでダウンロードしたものはtableau側でよしなにピボットしてくれておりViewに近い形で出力されます
      • APIで取得したデータは列が行として認識されるものがあり、個別に整形が必要になります
    • エラー内容が不親切
      • 「フォーマットが未対応」のような抽象的なエラーのみがエラーメッセージとして返ってきます
      • ワークブック上で誰かが編集モードで掴んでいるViewや正式にpublishされていないViewは取得できないことがありました