Browser Javascript

Browser Javascript

Table of Contents

REST API v2.0 uses JSON for the request and the response format, so it is easy to implement any v2.0 call in JavaScript.

Every REST API v2.0 endpoint uses either an HTTP GET or POST request, with or without a JSON request, so this simple async wrapper function can be used generically to build out any specific endpoint request. Before making an API request to a REST API v2.0 endpoint, make sure you obtain a bearer token or set session cookies.

/*
* Generic function to make a call to the V2.0 REST API
*
*/
let tsHost = 'https://{yourdomain}.thoughtspot.cloud';
async function restApiCallV2(endpoint, httpVerb, apiRequestObject){
    const publicApiUrl = 'api/rest/2.0/';
    console.log("hitting endpoint " + endpoint + " with verb " + httpVerb + " and request:");
    console.log(apiRequestObject);
    const apiFullEndpoint = tsHost + "/" + publicApiUrl + endpoint;
    return await fetch(
        apiFullEndpoint,
        {
            method: httpVerb.toUpperCase(),
            headers: {
                "Accept": "application/json",
                "X-Requested-By": "ThoughtSpot",
                "Content-Type": "application/json"
            },
            credentials: "include",
            body: JSON.stringify(apiRequestObject)
        })
    // is there always response JSON?
    .then(response =>  response.json())
    .catch(error => {
        console.error("Unable to get the" + endpoint + "response: " + error);
    });
}

You can use the restApiCallV2 function directly in a code block, or wrap it in another function.

Here’s a direct example:

let tmlExportEndpoint = 'metadata/tml/export';
let apiRequestForTML = {
    "metadata" : [{
        "type": "LIVEBOARD",
        "identifier": liveboardId
    }],
    "export_associated": false,
    "export_fqn": true

}

// Place call to export the TML for the Liveboard, to get the details of the Viz
return restApiCallV2(tmlExportEndpoint, 'POST', apiRequestForTML).then(
//tmlExportRestApiCallV2(tmlRequestOptions).then(
    response => {
        // console.log(response);
        let tmlObject = JSON.parse(response[0].edoc);
        // console.log(tmlObject);
        return tmlObject;
    }
).then(...)

Here’s a function to call a specific endpoint:

async function callSearchDataApi(tmlSearchString, datasourceId, recordOffset, recordSize){
    console.log("Using following Search String for Search Data API: ", tmlSearchString);
    let searchDataEndpoint = 'searchdata';
    let apiRequestForSearchData = {
          "query_string": tmlSearchString
        , "logical_table_identifier": datasourceId
        , data_format: "COMPACT"
        , record_offset: recordOffset
        , record_size: recordSize
    }

    return restApiCallV2(searchDataEndpoint, 'POST', apiRequestForSearchData);
}

let vizTmlSearchString = '[Product] [Region]';
let dsId = '80c9b38f-1b2a-4ff4-a759-378259130f58';

let recordSize = 10000;
let offset = 0;

// The function above is async, so you can assign this variable and the next steps won't occur until Promise is fulfilled
let searchResult = await callSearchDataApi(vizTmlSearchString, dsId, offset, recordSize)
console.log("Search Data response:");
console.log(searchResult);

Pagination and offsets🔗

The data APIs have limits to how much data can be returned in a single call. These APIs have record_offset and record_size arguments that can be used in multiple calls to paginate through and retrieve all of the data.

There must be a sort clause in the search or saved viz to guarantee that you are getting the full set of unique results, because each API call results in an indepedent SQL query to the data warehouse, and databases typically do not maintain any sort order unless there are specified sort clauses.

The following function implements an algorithm for paging through all results and storing the results into a single allResults array that can then be processed for later:

 async function getAllSearch(){
    let allResults = [];
    let resultCount = 0;
    let recordSize = 300; // Set this to 10000 in all production cases, it is set LOW to see the iteration working
    let offset = 0;
    let searchResult = await callSearchDataApi(vizTmlSearchString, tsAppState.currentDatasources[0], offset, recordSize);
    console.log("Got the searchResult: ", searchResult);
    allResults.push(searchResult);
    resultCount = searchResult['contents'][0]['returned_data_row_count'];
    console.log("This many records returned " + resultCount);
    while (resultCount == recordSize) {
        console.log('Need another batch');
        offset += recordSize;
        searchResult = await callSearchDataApi(vizTmlSearchString, tsAppState.currentDatasources[0], offset, recordSize);
        allResults.push(searchResult);
        resultCount = searchResult['contents'][0]['returned_data_row_count'];
    }

    console.log(allResults);
}
// Call the async function from directly above to do the full search
getAllSearch();