https://{}.thoughtspot.cloud/#/pinboard/*ddd982ea-c7bc-4c00-9b49-c53fab949b34*
Customize navigation components
To create navigation components such as buttons or menus to link to or load ThoughtSpot embedded components, you can use the ThoughtSpot REST APIs within the browser to retrieve filter lists of available content for the logged-in user.
All REST API requests from the browser are scoped to the signed-in user and show only those objects that a user can access.
If you are using the ThoughtSpot AppEmbed component to embed the full ThoughtSpot application, please see the documentation for customizing navigation within the full app embed.
ThoughtSpot IDs and URLs🔗
Every object in ThoughtSpot is referenced by a globally unique identifier (GUID), which appears as the id:
or guid:
property in API responses and TML files.
Visual Embed SDK components require object IDs to load the correct content.
The object ID is part of the URLs to load objects in ThoughtSpot. For example, the GUID of the Liveboard appears at the end of the URL:
To provide the ability to return to a given embedded ThoughtSpot object in a web app, you will want to build out a dynamic URL pattern that includes the ThoughtSpot object type and object ID, to load the correct embed component with the desired object.
Example web app URL patterns for various component types:
-
LiveboardEmbed component for a Liveboard: {web-app-name}.{your-domain}.com/analytics/dashboard/{liveboardId}
-
LiveboardEmbed component for a Viz on a Liveboard: {web-app-name}.{your-domain}.com/analytics/dashboard/{liveboardId}/{vizId}
-
SearchEmbed component to a datasource: {web-app-name}.{your-domain}.com/analytics/data/{datasourceId}
-
SearchEmbed component to an existing Answer: {web-app-name}.{your-domain}.com/analytics/report/{answerId}
The web app could have multiple distinct pages for each component type, or might instead have a single underlying page that is able to load the appropriate component based on the URL components.
REST API request for content listings🔗
The ThoughtSpot web application UI uses the metadata/list REST API endpoint in rendering the Answers, Liveboards, and Data pages.
The endpoint can only request one object type at a time:
-
PINBOARD_ANSWER_BOOK
for Liveboards -
QUESTION_ANSWER_BOOK
for answers -
LOGICAL_TABLE
for data objects
Data objects can be filtered using an additional subtype
parameter to limit the query specifically to ThoughtSpot tables, worksheets, or views.
There are additional parameters for sorting and a category
parameter that can filter the response to show only the objects created or marked as favorites by the logged-in user.
REST API calls are asynchronous. The following is an example function returning the response as a JSON object using fetch():
async function metadataListRestApiCall(args){
// args = { 'type', 'category', 'sortOn', 'sortAsc', 'tagnames' }
let type = args['type'].toLowerCase();
const publicApiUrl = 'callosum/v1/tspublic/v1/';
let endpoint = 'metadata/list';
// Easy type names match ThoughtSpot UI names for objects
const typesToApiType = {
'liveboard': 'PINBOARD_ANSWER_BOOK',
'answer': 'QUESTION_ANSWER_BOOK',
'datasource' : 'LOGICAL_TABLE', // datasource doesn't distinguish sub-types
'table' : 'ONE_TO_ONE_LOGICAL',
'view' : 'AGGR_WORKSHEET',
'worksheet' : 'WORKSHEET'
}
// batchsize = -1 gives all results
let apiParams = { 'batchsize' : '-1'};
console.log(type);
// The three datasource types can be specified using 'subtype'
if (type == 'table' || type == 'view' || type == 'worksheet'){
let subtype = [typesToApiType[type]];
apiParams['type'] = 'LOGICAL_TABLE';
apiParams['subtypes'] = `["${subtype}"]`;
}
else {
apiParams['type'] = typesToApiType[type];
}
// Category arguments
let category = 'ALL';
if ('category' in args){
if ( args['category'] == 'MY' || args['category'] == 'ALL' || args['category'] == 'FAVORITE'){
category = args['category'];
apiParams['category'] = category;
}
}
// Sort arguments
if ('sortOn' in args){
if (args['sortOn'] !== null){
apiParams['sort'] = args['sortOn'];
}
}
if ('sortAsc' in args){
if (args['sortAsc'] === true){
apiParams['sortascending'] = 'true';
}
if (args['sortAsc'] === false){
apiParams['sortascending'] = 'false';
}
}
console.log(apiParams);
const searchParams = new URLSearchParams(apiParams);
const apiFullEndpoint = tsURL + publicApiUrl + endpoint + "?" + searchParams.toString();
console.log(apiFullEndpoint);
return await fetch(
apiFullEndpoint, {
method: 'GET',
headers: {
"Accept": "application/json",
"X-Requested-By": "ThoughtSpot"
},
credentials: "include"
})
.then(response => response.json())
.then(data => data['headers']) // metadata/list info is really in the 'headers' property returned
.catch(error => {
console.error("Unable to get the metadata/list response: " + error)
});
}
The results of this REST API request can be directed into a rendering function using .then()
:
metadataListRestApiCall(
{
'type': 'liveboard',
'sortOn': 'NAME',
'sortAsc' : true,
'category': 'ALL'
})
.then(
(listResponse) => renderNavigationFromResponse(listResponse) // Use your own rendering function here
);
Render navigation elements🔗
The response from the function in the preceding example is an array of header objects, which can be parsed to render navigation.
The name
and id
property are used in almost all the navigation you build (id
is the GUID necessary to load any ThoughtSpot object). Additional properties include:
-
description
Text description added to content by creator
-
authorDisplayName
Display name of the object creator or current owner
-
authorName
Username of the object creator or current owner
-
created
Object creation timestamp (to milliseconds)
-
modified
Last edit timestamp (to milliseconds)
-
tags
Array of tag objects, each with a
name
property among other details
Replicate ThoughtSpot application listing pages🔗
If you want to render something very close to the 'Answers' or 'Liveboards' pages within the ThoughtSpot UI, your rendering function will grab the name
, id
, tags
, modified
and authorDisplayName
properties and make a table in that order (feel free to leave out any undesired elements):
function tableFromList(listResponse){
console.log(listResponse);
let t = document.createElement('table');
// Make table headers
let thead = document.createElement('thead');
t.append(thead);
let thr = document.createElement('tr');
thead.append(thr);
let headers = ['Name', 'Tags', 'Modified', 'Author'];
for (let i=0, len=headers.length; i < len; i++){
let th = document.createElement('th');
th.innerText = headers[i];
thr.append(th);
}
// Go through response and build rows
for (let i=0, len=listResponse.length; i < len; i++){
let tr = document.createElement('tr');
// Name Column
let name_td = document.createElement('td');
name_td.innerHTML = '<a href="#" onclick="loadContent("' + listResponse[i]['id'] + '")>' + listResponse[i]['name'] + '</a>';
//name_td.append(name_text);
console.log(name_td);
tr.append(name_td);
// Tags column
let tags_td = document.createElement('td');
console.log(listResponse[i]['tags']);
// Tags is an Array of Tag objects, with properties ('name' being the important one)
if (listResponse[i]['tags'].length > 0){
let tagNames = [];
for(let k = 0, len = listResponse[i]['tags'].length; k<len; k++){
let tagName = listResponse[i]['tags'][k]['name'];
tagNames.push(tagName);
}
tags_td.innerText = tagNames.join(', ');
}
tr.append(tags_td);
// Modified Date column
let modified_td = document.createElement('td');
modified_td.innerText = listResponse[i]['modified'];
tr.append(modified_td);
let author_td = document.createElement('td');
author_td.innerText = listResponse[i]['authorDisplayName'];
tr.append(author_td);
t.append(tr);
}
return t;
}
The function in the preceding example merely creates the table, it does not place it on the page. You can continue chaining using .then()
to place the table in the appropriate place on your web application page :
metadataListRestApiCall(
{
'type': 'liveboard',
'sortOn': 'NAME',
'sortAsc' : true,
'category': 'ALL'
})
.then(
(response) => tableFromList(response)
).then(
(table) => document.getElementById('main-content-div').append(table)
);
Note that the loadContent()
function referenced in the anchor tag created for the name column in the function above is a placeholder representing whatever is necessary to load that type of ThoughtSpot content in the web application. The actual design you choose for your application will determine the code you need to go from the navigation component to loading the ThoughtSpot content.
Retrieve individual visualizations from a Liveboard🔗
You can load individual visualizations on a Liveboard using the LiveboardEmbed
component by supplying both liveboardId
and vizId
.
The display of a visualization from a Liveboard differs from a saved Answer object, which is loaded via the SearchEmbed
component. The saved answer object always displays the ThoughtSpot search bar and UI actions for editing an Answer, whereas the visualizations display fewer UI elements and show the menu items in the More menu .
To retrieve a list of visualizations from a Liveboard, you can use the get visualization headers REST API endpoint.
async function metadataGetVizHeadersRestApiCall(liveboardGuid){
// args = { 'type', 'category', 'sortOn', 'sortAsc', 'tagnames' }
let type = args['type'].toLowerCase();
const publicApiUrl = 'callosum/v1/tspublic/v1/';
let endpoint = 'metadata/listvizheaders';
// batchsize = -1 gives all results
let apiParams = { 'id' : liveboardGuid};
const searchParams = new URLSearchParams(apiParams);
const apiFullEndpoint = tsURL + publicApiUrl + endpoint + "?" + searchParams.toString();
console.log(apiFullEndpoint);
return await fetch(
apiFullEndpoint, {
method: 'GET',
headers: {
"Accept": "application/json",
"X-Requested-By": "ThoughtSpot"
},
credentials: "include"
})
.then(response => response.json())
//
.then(data => data) // metadata/list info is really in the 'headers' property returned
.catch(error => {
console.error("Unable to get the metadata/listvizheaders response: " + error)
});
}