Note
| This guide does not cover LWC development. We will assume you have experience developing in Salesforce. If not, contact your ThoughtSpot Sales representative for details. |
Any LWC you develop in Salesforce will contain an html, js, and meta.xml file. Let’s walk through a simple Liveboard embed component.
meta.xml
Defines the metadata values for the component. Specifically, where you want to embed in Salesforce (Record Pages, Experience Cloud, Homepage, etc.), and any configurable parameters for your ThoughtSpot objects (type of object to embed, Cluster URL, Org, etc.).
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>63.0</apiVersion>
<isExposed>true</isExposed>
<masterLabel>ThoughtSpot Embed Template</masterLabel>
<targets>
<target>lightning__AppPage</target>
<target>lightning__RecordPage</target>
<target>lightning__HomePage</target>
<target>lightningCommunity__Page</target>
<target>lightningCommunity__Default</target>
</targets>
<targetConfigs>
<targetConfig targets="lightning__RecordPage,lightning__AppPage,lightning__HomePage,lightningCommunity__Default">
<property label="What are you embedding?" name="embedType" type="String" datasource="Liveboard, Spotter" default="Liveboard"/>
<property
name="tsURL"
type="String"
label="ThoughtSpot URL"
required="false"
description="The full URL to your ThoughtSpot host"
default=""
/>
<property
name="tsOrg"
type="String"
label="TS Org ID - leave empty if not using orgs"
required="false"
description="ThoughtSpot Organization Identifier"
default=""
/>
<property
name="tsObjectId"
type="String"
label="Liveboard or Datasource GUID"
required="false"
description="ThoughtSpot Content GUID"
default=""
/>
<property
name="hideLiveboardHeader"
type="Boolean"
default="false"
label="Hide Liveboard Header?"
/>
<property
name="showLiveboardTitle"
type="Boolean"
default="false"
label="Show Liveboard Title?"
/>
<property
name="fullHeight"
type="Boolean"
default="false"
label="Full Height Liveboard?"
/>
</targetConfig>
</targetConfigs>
</LightningComponentBundle>
html
This page defines the div where your ThoughtSpot object will be embedded.
<template>
<div class="container" data-id="myContainer">
<div class="thoughtspotObject" data-id="thoughtspotObject" id="thoughtspotObject" lwc:dom="manual"></div>
</div>
</template>
javascript
-
The js file will communicate with your ThoughtSpot cluster and use the Visual Embed SDK to embed your objects.
-
Variables set in the
meta.xml
will be tracked and applied in the SDK initialization. -
You must import the Visual Embed SDK as a static resource in Salesforce. Get the latest NPM version here.
-
Add Salesforce URL to CORS allowed-domains in ThoughtSpot
-
Update CORS and CSP settings in Salesforce with your ThoughtSpot cluster URL
///////////////////////////////////////
//Prototype for TS Liveboard Embed
//
// High-level steps:
// : Update CCORS whitelisted domains settings in ThoughtSpot (Developer -> Security). Add your Salesforce url(s)
// : Update CORS and CSP settings in Salesforce with your thoughtspot cluster url
// : Upload the ThoughtSpot SDK into SF as Static Resource. Make sure name matches thoughtSpotSDK import below
// : Set values for your ThoughtSpot username & password below.
//
// Notes:
// : Basic Auth used in this LWC, no SSO.
// : Do not use in production
//
///////////////////////////////////////
import { LightningElement, api, track } from 'lwc';
import thoughtSpotSDK from '@salesforce/resourceUrl/thoughtSpotSDK';
import { loadScript } from 'lightning/platformResourceLoader';
export default class TsEmbedTemplate extends LightningElement {
@api objectApiName; /** Object API name - automatically passed when in a record page */
@api recordId; /** Object record ID - automatically passed when in a record page */
//track variables set in meta.xml
@api embedType;
@api tsObjectId;
@api tsURL;
@api tsOrg;
@api hideLiveboardHeader;
@api showLiveboardTitle;
@api fullHeight;
////////////////////////////////////////////////////////////////////////////////////////////////////
// Basic Auth testing - use your ThoughtSpot credentials
////////////////////////////////////////////////////////////////////////////////////////////////////
myTestUser = '';
myTestPW = '';
////////////////////////////////////////////////////////////////////////////////////////////////////
async connectedCallback() {
console.log("### Loading the ThoughtSpotSDK...");
this.loadTSSDK();
}
loadTSSDK() {
loadScript(this, thoughtSpotSDK)
.then(() => {
// ThoughtSpot library loaded successfully
console.log("### SDK successfully loaded...initializing embed");
this.initSDKEmbed();
})
.catch(error => {
// Error occurred while loading the ThoughtSpot library
this.handleError(error);
});
}
async initSDKEmbed() {
const containerDiv = this.template.querySelector(
'div.thoughtspotObject'
);
try {
this.embedInit = tsembed.init({
thoughtSpotHost: this.tsURL,
authType: tsembed.AuthType.Basic,
username: this.myTestUser,
password: this.myTestPW,
org_id: this.tsOrg,
customizations: {
style: {
customCSSUrl: "https://cdn.jsdelivr.net/gh/thoughtspot/custom-css-demo/css-variables.css", // location of your style sheet
// To apply overrides for your style sheet in this init, provide variable values below
customCSS: {
variables: {
"--ts-var-button--secondary-background": "#9da7c2",
"--ts-var-button--secondary--hover-background": "#cacad5",
"--ts-var-button--primary--hover-background":"#cacad5",
"--ts-var-button--primary-background": "#9da7c2",
"ts-var-button--primary-color": "#9da7c2",
"--ts-var-root-background": "#b0c4df",
"--ts-var-viz-border-radius": "22px",
"--ts-var-viz-title-font-family": "Helvetica",
"--ts-var-viz-background": "#ffffff",
"--ts-var-menu--hover-background": "#c9c9c9",
"--ts-var-menu-font-family": "Helvetica",
"--ts-var-chip-border-radius": "8px",
"--ts-var-chip--active-color": "#CF112C",
"--ts-var-chip--active-background": "#57a3fd",
"--ts-var-chip--hover-color": "white",
"--ts-var-chip--hover-background": "#A4A4A3",
"--ts-var-chip-color": "#F9F6EE",
},
},
},
},
});
if( this.embedType === "Liveboard" ) {
console.log('### Configuring ' + this.embedType + ' embed');
console.log("### RECORD ID: ", this.recordId);
this.embedObj = new tsembed.LiveboardEmbed(containerDiv, {
frameParams: {
},
fullHeight: this.fullHeight,
hideLiveboardHeader: this.hideLiveboardHeader,
showLiveboardTitle: this.showLiveboardTitle,
liveboardId: this.tsObjectId,
});
}
else if(this.embedType === "Spotter") {
console.log('### Configuring ' + this.embedType + ' embed');
this.embedObj = new tsembed.ConversationEmbed(containerDiv, {
frameParams: {
height: 800,
},
worksheetId: this.tsObjectId,
});
} else {
console.log("###ERROR: No embed type selected in meta xml");
}
this.embedObj.render();
}
catch (error) {
console.error('Error:', error);
}
}
handleError(error) {
console.error('Error loading TS library:', error.message || error);
}
}