ThoughtSpot and Salesforce Integration

ThoughtSpot and Salesforce Integration

Integrating ThoughtSpot with Salesforce enables users to access analytics directly within their Salesforce environment. By embedding ThoughtSpot content, organizations can deliver data-driven insights where your end users work, enhancing decision-making and efficiency. This guide explores the various embedding options, including the Visual Embed SDK and non-SDK approaches, and discusses key recommendations for authentication, customization/branding, and embedding in mobile apps.

Embeddable objects🔗

ThoughtSpot allows users to create several types of objects. Before we get into the details, it’s important to understand that any of the following objects can be easily embed into Salesforce:

  • Search - A self-service interface that allows users to perform searches on data and build visualizations

  • Answers - Individual visualizations generated and saved from user search queries

  • Liveboards - Interactive dashboards that contain one or more visualizations (Answers)

  • Spotter - AI-powered assistant that helps users analyze data using natural language

  • Full Application - Allows you to embed the full ThoughtSpot application or the individual application pages

Embedding methods🔗

There are two development options for this integration:

  • Embedding with Visual Embed SDK

  • iFrame embedding without the SDK

Visual Embed SDK🔗

If you are using the Salesforce Lightning platform, we highly recommend using ThoughtSpot’s Visual Embed SDK as it provides maximum flexibility and allows you to create highly customized solutions across all Salesforce Cloud products.

Key considerations🔗

  • Simplified integration using Lightning Web Components (LWC)

  • Enhanced customization and interactivity

  • Support for authentication mechanisms like SAML and Trusted Authentication

iFrame embedding🔗

For organizations using the Salesforce Classic platform, or that prefer not to use the SDK, embedding ThoughtSpot can be achieved via iframes. This method is simpler, but considered a legacy approach for embedding.

Key considerations🔗

  • Fewer customization options

  • Potential styling limitations

  • SAML authentication only

Mobile considerations🔗

If you require ThoughtSpot content to be available in the Salesforce mobile app, we recommend leveraging the Visual Embed SDK with Single Sign-On (SSO) using Cookieless Trusted Authentication (AuthType.TrustedAuthTokenCookieless). This combination will provide a seamless embedding experience.

Now that we understand our options, let’s walk through the implementation steps for each method.

High-level implementation steps🔗

To embed ThoughtSpot content, complete the steps described in the following sections:

  • Embed with Visual Embed SDK

  • Use iFrame to embed

Embed with Visual Embed SDK🔗

The Salesforce lightning platform moved developers away from Visualforce to Lightning Web Components (LWC). If you are considering using the SDK, we will assume your Salesforce instance is running on Lightning.

Note
To simplify development, we recommend using the Salesforce extensions pack in Visual Studio Code.

You have a couple of options:

LWC from scratch
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);
    }
}
LWC Git repository

We can provide all the code needed to get you started. Contact your ThoughtSpot Sales representative for access to our Git repositories.

Use iFrame to embed🔗

If you have configured ThoughtSpot to use the same SAML provider as your Salesforce instance, you can create a simple Visualforce page that can seamlessly embed a ThoughtSpot Object.

To create a new Visualforce page in Salesforce, go to Setup > Visualforce Pages > New.

The following code example can be used for the new page. It defines the iFrame, with the ThoughtSpot Liveboard URL using a runtime filter to personalize the results to the Salesforce user:

Note
Use this code only if embedding into Salesforce Classic.
<apex:page standardController="Account" tabStyle="Account">
  <apex:pageBlock title="ThoughtSpot">
    <apex:iframe src="https://{thoughtspot-server}/?embedApp=true&p&col1={field_name}&op1=EQ&val1={!Account.Id}&OrgID={org_id}#/embed/viz/{liveboard_guid}
" scrolling="true" height="800">
    </apex:iframe>
  </apex:pageBlock>
</apex:page>
Note
Variable substitution required
  • {thoughtspot-server}. Your ThoughtSpot host URL.

  • {field_name} represents the column from your ThoughtSpot model to be filtered.

  • {!account.Id} is a Salesforce APEX variable, the value is automatically known based on the record page you are embedding into. The filter values you can pass are based on the standardController=<object> you set when configuring the apex page.

  • {org_id}. If using Orgs in ThoughtSpot, provide your Org identifier. If not using Orgs, set the ID to 0.

  • {liveboard_guid}. Your Liveboard identifier.

SSO Options🔗

Authentication is a critical component of embedding ThoughtSpot in Salesforce. The two primary options for this integration are:

SAML-based SSO
  • Allows users to authenticate via Salesforce’s Identity Provider (IdP).

  • Provides a seamless login experience without requiring additional credentials.

  • Requires ThoughtSpot to be configured as a service provider (SP).

Trusted authentication
  • Uses a secure token-based approach for authentication.

  • Provides more control over user access and permissions.

  • Ideal for embedding within customized Salesforce experiences.

  • Seamless embedding within the Salesforce mobile app.

  • Is supported in ThoughtSpot SDK embed only.

Customization and branding with the SDK🔗

The Visual Embed SDK allows extensive customization, including the following:

  • Styling the embedded Liveboards to match Salesforce’s look and feel.

  • Implementing filters and interactive elements.

  • Controlling user experience via ThoughtSpot’s developer-friendly APIs.

Conclusion🔗

Embedding ThoughtSpot into Salesforce enhances analytics accessibility, enabling users to gain insights without leaving their CRM. Whether using the ThoughtSpot SDK or iframe-based approaches, choosing the right authentication and embedding method is essential. By leveraging LWC and customizing ThoughtSpot’s appearance, organizations can create a seamless and powerful analytics experience within Salesforce.