Looker APIを利用してGoogle Spreadsheetへデータ辞書を作成する

Lookerを使用すると、より多くの人がデータにアクセスしてアドホックにクエリを実行できますが、項目や取得元の意味がエンドユーザに知られていないために誤ったクエリを実行してしまうことがあります。
これを防ぐ1つの方法として、**Looker API**を使用して**Google Spreadsheets**に必要な情報を出力し、データ辞書を作成することが可能です。
このスクリプトは、ビュー内の各フィールドに`Description`定義されている場合に特に役立ちます。

##スクリプト
このスクリプトは、[このリポジトリにリストされている機能](https://github.com/brechtv/looker_google_sheets)を使用して、[Apps Script](https://developers.google.com/apps-script/)とJavascriptの組み合わせで定義されています。

このスクリプトを使用する際には、以下の点について留意する必要があります。
**1. model_nameとexplore_nameは大文字と小文字を区別します
2. Google Sheetで設定されているシート(タブ)名と一致する各ビューの情報をシート内に出力するように設定されています。**（本振る舞いを変更するためには、以下のコードの対象行にあるコメントを確認してください）。

このスクリプトでは、Looker Exploreの各ViewへのGoogle Sheetマッピングを使用しています。各Google SheetにはLooker Viewの名前があり、そのビューに関連するViewのみがシートに表示されます。

また、API呼び出しの出力をキャッシュに入れて、呼び出しがシートを開くたびに実行されるのではなく、シートの最初の実行時に実行されてから6時間キャッシュされるようにします。

```javascript:
// Replace this with your base domain 
var BASE_URL = 'https://your_company.com:19999/api/3.0';
// Replace this with your API credentials
var CLIENT_ID = 'your_api_ID'; 
var CLIENT_SECRET = 'your_api_key'; 

function LOOKER_GET_DATA_DICTIONARY(model_name) {
   //checks for previous cached entry 
   var cache = CacheService.getScriptCache();
   var cached = cache.get("api_results");
   if (cached != null) {
     for (elem in cached) { 
       results.push(elem)
     return results;
     }
  }
  // if nothing in cache, run the call
  try {

  var options = {
        'method': 'get',
        'headers': {
            'Authorization': 'token ' + login()
        }
    };

    // api call to the /lookml_models/{lookml_model_name} endpoint
    var response = UrlFetchApp.fetch(BASE_URL + "/lookml_models/" + model_name, options);
    var explores = JSON.parse(response.getContentText()).explores;
    var result = [];
    
    // defining the fields to retrieve for the Google Sheets
    result.push(["View Name", "Field Type", "Name", "Label", "Type", "Description", "Hidden"]);
                 // additional details if needed:
                 //, "SQL", "Source"]);

    
    for (var i = 0; len = explores.length, i < len; i++) {
        Logger.log(explores);

        var explore = explores[i].name;
        var explore_results = UrlFetchApp.fetch(BASE_URL + "/lookml_models/" + model_name + "/explores/" + explore, options);

        var connection = JSON.parse(explore_results.getContentText()).connection_name;
        var dimensions = JSON.parse(explore_results.getContentText()).fields.dimensions;
        var measures = JSON.parse(explore_results.getContentText()).fields.measures;
        var current_sheet = SpreadsheetApp.getActiveSheet().getName();

        // using this test to retrieve only data relevant to a specific explore
        // change explore_name with your explore
        if (explore == "explore_name") {
        
        // adding the data for the dimensions
          for (var j = 0; j < dimensions.length; j++) {
            // checks that only the fields from the underlying Looker view matching the name of the Google sheet are displayed
            if (dimensions[j].view.replace("_", " ") == current_sheet.toLowerCase()|| dimensions[j].view_label.replace("_", " ").toLowerCase() == current_sheet.toLowerCase()) {     
              result.push([dimensions[j].view, 
                         "Dimension", 
                         (dimensions[j].name.substring((dimensions[j].name.indexOf(".")+1), dimensions[j].name.length)).replace(/_/g, " "), 
                         (dimensions[j].label != null ? dimensions[j].label : (dimensions[j].name.substring((dimensions[j].name.indexOf(".")+1), dimensions[j].name.length).replace(/_/g, " "))), 
                         (dimensions[j].type != null ? (dimensions[j].type).replace("_", " ") : "String"),
                         dimensions[j].description,  
                         dimensions[j].hidden, dimensions[j].view_label
                         //, (dimensions[j].sql != null ? dimensions[j].sql : ""), 
                         //dimensions[j].source_file
                         ]);
          }
          }
          
          // adding the data for the measures
          for (var k = 0; k < measures.length; k++) {
            // checks that only the fields from the view matching the name of the sheet are displayed
            if (measures[k].view.replace("_", " ") == current_sheet.toLowerCase() || measures[k].view_label.replace("_", " ").toLowerCase() == current_sheet.toLowerCase()) {
              result.push([measures[k].view, 
                         "Measure", 
                         (measures[k].name.substring((measures[k].name.indexOf(".")+1), measures[k].name.length).replace(/_/g, " ")), 
                         (measures[k].label != null ? measures[k].label : (measures[k].name.substring((measures[k].name.indexOf(".")+1), measures[k].name.length)).replace(/_/g, " ")), 
                         (measures[k].type != null ? (measures[k].type).replace("_", " ") : "String"), 
                         measures[k].description, 
                         measures[k].hidden
                         //, (measures[k].sql != null ? measures[k].sql : ""), 
                         //measures[k].source_file
                         ]);
        }
        }
        }
    }
    cache.put("api_results", result, 21600); // cache for 6 hours
    //the maximum cache time is 6 hours (21600 seconds) 
    //the default time is 10 minutes (600 seconds).
    return result
  } catch(err) {
    return "Something went wrong. " + err
}
}

function login() {
  try{
    var post = {
        'method': 'post'
    };
    var response = UrlFetchApp.fetch(BASE_URL + "/login?client_id=" + CLIENT_ID + "&client_secret=" + CLIENT_SECRET, post);
    return JSON.parse(response.getContentText()).access_token;
  } catch(err) {
    Logger.log(err);
    return "Could not login to Looker. Check your credentials.";
  }
}
```

上記のコードとともに、特定のモデルを呼び出すようにセルに以下の数式を入力します。
`=LOOKER_GET_DATA_DICTIONARY("model_name")`

##注
APIの認証情報をスクリプトに直接記述する代わりに、[以下のリポジトリにあるコード](https://github.com/brechtv/looker_google_sheets/blob/master/script.gs#L297)を利用してGoogle SheetのUI上で認証情報を入力させるようにすることも可能です。

```javascript
// set credentials via prompt
function setCred() {
    var ui = SpreadsheetApp.getUi();
    var base_url_input = ui.prompt("Set your Looker API credentials", "Base URL (e.g. https://yourdomain.looker.com:19999/api/3.0):", ui.ButtonSet.OK_CANCEL);
    var client_id_input = ui.prompt("Set your Looker API credentials", "Client ID:", ui.ButtonSet.OK_CANCEL);
    var client_id_secret = ui.prompt("Set your Looker API credentials", "Client Secret:", ui.ButtonSet
        .OK_CANCEL);
    var scriptProperties = PropertiesService.getScriptProperties();
    // assign them to scriptProperties so the user doesn't have to enter them over and over again
    scriptProperties.setProperty("BASE_URL", base_url_input.getResponseText());
    scriptProperties.setProperty("CLIENT_ID", client_id_input.getResponseText());
    scriptProperties.setProperty("CLIENT_SECRET", client_id_secret.getResponseText());
    // test the credentials with a /user call
    testCred();
}

// testing the existing creds
function testCred() {
    var ui = SpreadsheetApp.getUi();
        var options = {
            "method": "get",
            "headers": {
                "Authorization": "token " + login()
            }
        };
    try {
        var response = UrlFetchApp.fetch(BASE_URL + "/user", options);
        var success_header = "Successfully set API credentials!";
        var success_content = "Authenticated as " + JSON.parse(response.getContentText()).first_name +
            " " + JSON.parse(response.getContentText()).last_name + " (user " + JSON.parse(response.getContentText()).id +").Keep in mind that API credentials are script/spreadsheet bound. This is needed for the custom formulas to keep on working for other users. Hit 'Test' to test your credentials or 'Delete' to remove the currently set credentials.";
        var result = ui.alert(success_header, success_content, ui.ButtonSet.OK);
    } catch (err) {
        var result = ui.alert("Invalid credentials / Credentials not set!",
            "Doublecheck your base URL and your client ID & secret.", ui.ButtonSet.OK);
    }
}

// delete credentials from scriptProperties
function deleteCred() {
    var scriptProperties = PropertiesService.getScriptProperties();
    scriptProperties.deleteAllProperties();
}
```

## 追加リソース
[Pythonを利用してデータ辞書CSVファイルを作成する方法](https://discourse.looker.com/t/writing-a-simple-data-dictionary-to-csv-using-the-looker-api-and-the-python-requests-library/3920)をご紹介しています。
また、[Rubyを利用した同様の方法](https://discourse.looker.com/t/creating-a-data-dictionary-using-lookers-api/3589)についてもご紹介しています。
* 上記は弊社Discourceの英語記事へのリンクとなっております。