• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

thoughtspot / visual-embed-sdk / #2818

20 Nov 2025 02:08PM UTC coverage: 94.381% (+0.1%) from 94.277%
#2818

Pull #329

shivam-kumar-ts
SCAL-256912  Introduce EmbedErrorCodes enum
Pull Request #329: SCAL-256912 Enhance error handling by introducing EmbedErrorEvent interface

1350 of 1517 branches covered (88.99%)

Branch coverage included in aggregate %.

41 of 47 new or added lines in 4 files covered. (87.23%)

2 existing lines in 1 file now uncovered.

3185 of 3288 relevant lines covered (96.87%)

101.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.2
/src/embed/liveboard.ts
1
/**
2
 * Copyright (c) 2022
3
 *
4
 * Embed a ThoughtSpot Liveboard or visualization
5
 * https://developers.thoughtspot.com/docs/?pageid=embed-pinboard
6
 * https://developers.thoughtspot.com/docs/?pageid=embed-a-viz
7
 * @summary Liveboard & visualization embed
8
 * @author Ayon Ghosh <ayon.ghosh@thoughtspot.com>
9
 */
10

11
import { getPreview } from '../utils/graphql/preview-service';
14✔
12
import { ERROR_MESSAGE } from '../errors';
14✔
13
import {
14✔
14
    EmbedEvent,
15
    MessagePayload,
16
    Param,
17
    RuntimeFilter,
18
    DOMSelector,
19
    HostEvent,
20
    SearchLiveboardCommonViewConfig as LiveboardOtherViewConfig,
21
    BaseViewConfig,
22
    LiveboardAppEmbedViewConfig,
23
    ErrorDetailsTypes,
24
    EmbedErrorCodes,
25
} from '../types';
26
import { calculateVisibleElementData, getQueryParamString, isUndefined } from '../utils';
14✔
27
import { getAuthPromise } from './base';
28
import { TsEmbed, V1Embed } from './ts-embed';
14✔
29
import { addPreviewStylesIfNotPresent } from '../utils/global-styles';
14✔
30
import { TriggerPayload, TriggerResponse } from './hostEventClient/contracts';
31
import { logger } from '../utils/logger';
14✔
32

33

34
/**
35
 * The configuration for the embedded Liveboard or visualization page view.
36
 * @group Embed components
37
 */
38
export interface LiveboardViewConfig extends BaseViewConfig, LiveboardOtherViewConfig, LiveboardAppEmbedViewConfig {
39
    /**
40
     * If set to true, the embedded object container dynamically resizes
41
     * according to the height of the Liveboard.
42
     *
43
     * **Note**:  Using fullHeight loads all visualizations on the
44
     * Liveboard simultaneously, which results in multiple warehouse
45
     * queries and potentially a longer wait for the topmost
46
     * visualizations to display on the screen.
47
     * Setting `fullHeight` to `false` fetches visualizations
48
     * incrementally as users scroll the page to view the charts and tables.
49
     *
50
     * @version SDK: 1.1.0 | ThoughtSpot: ts7.may.cl, 7.2.1
51
     *
52
     * Supported embed types: `LiveboardEmbed`
53
     * @example
54
     * ```js
55
     * const embed = new LiveboardEmbed('#embed', {
56
     *   ... // other liveboard view config
57
     *  fullHeight: true,
58
     * });
59
     * ```
60
     */
61
    fullHeight?: boolean;
62
    /**
63
     * This is the minimum height(in pixels) for a full-height Liveboard.
64
     * Setting this height helps resolve issues with empty Liveboards and
65
     * other screens navigable from a Liveboard.
66
     *
67
     * Supported embed types: `LiveboardEmbed`
68
     * @version SDK: 1.5.0 | ThoughtSpot: ts7.oct.cl, 7.2.1
69
     * @default 500
70
     * @example
71
     * ```js
72
     * const embed = new LiveboardEmbed('#embed', {
73
     *   ... // other liveboard view config
74
     *   fullHeight: true,
75
     *   defaultHeight: 600,
76
     * });
77
     * ```
78
     */
79
    defaultHeight?: number;
80
    /**
81
     * @Deprecated If set to true, the context menu in visualizations will be enabled.
82
     * @example
83
     * ```js
84
     * const embed = new LiveboardEmbed('#tsEmbed', {
85
     *    ... //other embed view config
86
     *    enableVizTransformations:true,
87
     * })
88
     * ```
89
     * @version: SDK: 1.1.0 | ThoughtSpot: 8.1.0.sw
90
     */
91
    enableVizTransformations?: boolean;
92
    /**
93
     * The Liveboard to display in the embedded view.
94
     * Use either liveboardId or pinboardId to reference the Liveboard to embed.
95
     *
96
     * Supported embed types: `LiveboardEmbed`
97
     * @version SDK: 1.3.0 | ThoughtSpot ts7.aug.cl, 7.2.1
98
     * @example
99
     * ```js
100
     * const embed = new LiveboardEmbed('#tsEmbed', {
101
     *    ... //other embed view config
102
     *    liveboardId:id of liveboard,
103
     * })
104
     */
105
    liveboardId?: string;
106
    /**
107
     * To support backward compatibility
108
     * @hidden
109
     */
110
    pinboardId?: string;
111
    /**
112
     * The visualization within the Liveboard to display.
113
     *
114
     * Supported embed types: `LiveboardEmbed`
115
     * @version SDK: 1.9.1 | ThoughtSpot: 8.1.0.cl, 8.4.1-sw
116
     * @example
117
     * ```js
118
     * const embed = new LiveboardEmbed('#tsEmbed', {
119
     *    ... //other embed view config
120
     *    vizId:'430496d6-6903-4601-937e-2c691821af3c',
121
     * })
122
     * ```
123
     */
124
    vizId?: string;
125
    /**
126
     * If set to true, all filter chips from a
127
     * Liveboard page will be read-only (no X buttons)
128
     *
129
     * Supported embed types: `LiveboardEmbed`
130
     * @version SDK: 1.3.0 | ThoughtSpot ts7.aug.cl, 7.2.1.sw
131
     * @example
132
     * ```js
133
     * const embed = new LiveboardEmbed('#tsEmbed', {
134
     *    ... //other embed view config
135
     *    preventLiveboardFilterRemoval:true,
136
     * })
137
     * ```
138
     */
139
    preventLiveboardFilterRemoval?: boolean;
140
    /**
141
     * Array of visualization IDs which should be visible when the Liveboard
142
     * renders. This can be changed by triggering the `SetVisibleVizs`
143
     * event.
144
     *
145
     * Supported embed types: `LiveboardEmbed`
146
     * @version SDK: 1.9.1 | ThoughtSpot: 8.1.0.cl, 8.4.1-sw
147
     * @example
148
     * ```js
149
     * const embed = new LiveboardEmbed('#tsEmbed', {
150
     *    ... //other embed view config
151
     *    visibleVizs: [
152
     *       '430496d6-6903-4601-937e-2c691821af3c',
153
     *       'f547ec54-2a37-4516-a222-2b06719af726'
154
     *     ]
155
     * })
156
     */
157
    visibleVizs?: string[];
158
    /**
159
     * To support backward compatibility
160
     * @hidden
161
     */
162
    preventPinboardFilterRemoval?: boolean;
163
    /**
164
     * Render embedded Liveboards and visualizations in the
165
     * new Liveboard experience mode.
166
     *
167
     * Supported embed types: `LiveboardEmbed`
168
     * @version SDK: 1.14.0 | ThoughtSpot: 8.6.0.cl, 8.8.1-sw
169
     * @example
170
     * ```js
171
     * const embed = new LiveboardEmbed('#tsEmbed', {
172
     *    ... //other embed view config
173
     *    liveboardV2:true,
174
     * })
175
     * ```
176
     */
177
    liveboardV2?: boolean;
178
    /**
179
     * Set a Liveboard tab as an active tab.
180
     * Specify the tab ID.
181
     *
182
     * Supported embed types: `LiveboardEmbed`
183
     * @example
184
     * ```js
185
     * const embed = new LiveboardEmbed('#tsEmbed', {
186
     *    ... //other embed view config
187
     *    activeTabId:'id-1234',
188
     * })
189
     * ```
190
     * @version SDK: 1.15.0 | ThoughtSpot: 8.7.0.cl, 8.8.1-sw
191
     */
192
    activeTabId?: string;
193
    /**
194
     * Show or hide the tab panel of the embedded Liveboard.
195
     *
196
     * Supported embed types: `LiveboardEmbed`
197
     * @version SDK: 1.25.0 | ThoughtSpot: 9.6.0.cl, 9.8.0.sw
198
     * @example
199
     * ```js
200
     * const embed = new LiveboardEmbed('#tsEmbed', {
201
     *    ... //other embed view config
202
     *    hideTabPanel:true,
203
     * })
204
     * ```
205
     */
206
    hideTabPanel?: boolean;
207
    /**
208
     * Show a preview image of the visualization before the visualization loads.
209
     * Only works for visualizations embeds with a viz id.
210
     *
211
     * Also, viz snashot should be enabled in the ThoughtSpot instance.
212
     * Contact ThoughtSpot support to enable this feature.
213
     *
214
     * Since, this will show preview images, be careful that it may show
215
     * undesired data to the user when using row level security.
216
     *
217
     * Supported embed types: `LiveboardEmbed`
218
     * @example
219
     * ```js
220
     * const embed = new LiveboardEmbed('#tsEmbed', {
221
     *   liveboardId: 'liveboard-id',
222
     *   vizId: 'viz-id',
223
     *   showPreviewLoader: true,
224
     * });
225
     * embed.render();
226
     * ```
227
     * @version SDK: 1.32.0 | ThoughtSpot: 10.0.0.cl
228
     */
229
    showPreviewLoader?: boolean;
230
    /**
231
     * The Liveboard to run on regular intervals to fetch the cdw token.
232
     *
233
     * Supported embed types: `LiveboardEmbed`
234
     * @hidden
235
     * @version SDK: 1.35.0 | ThoughtSpot:10.6.0.cl
236
     * @example
237
     * ```js
238
     * const embed = new LiveboardEmbed('#tsEmbed', {
239
     *    ... //other embed view config
240
     *    oAuthPollingInterval: value in milliseconds,
241
     * })
242
     */
243
    oAuthPollingInterval?: number;
244

245
    /**
246
     * The Liveboard is set to force a token fetch during the initial load.
247
     *
248
     * Supported embed types: `LiveboardEmbed`
249
     * @hidden
250
     * @version SDK: 1.35.0 | ThoughtSpot:10.6.0.cl
251
     * @example
252
     * ```js
253
     * const embed = new LiveboardEmbed('#tsEmbed', {
254
     *    ... //other embed view config
255
     *    isForceRedirect: false,
256
     * })
257
     */
258
    isForceRedirect?: boolean;
259

260
    /**
261
     * The source connection ID for authentication.
262
     * @hidden
263
     * @version SDK: 1.35.0 | ThoughtSpot:10.6.0.cl
264
     *
265
     * Supported embed types: `LiveboardEmbed`
266
     * @example
267
     * ```js
268
     * const embed = new LiveboardEmbed('#tsEmbed', {
269
     *    ... //other embed view config
270
     *    dataSourceId: '',
271
     * })
272
     */
273
    dataSourceId?: string;
274
    /**
275
     * The list of tab IDs to hide from the embedded.
276
     * This Tabs will be hidden from their respective LBs.
277
     * Use this to hide an tabID.
278
     *
279
     * Supported embed types: `LiveboardEmbed`
280
     * @example
281
     * ```js
282
     * const embed = new LiveboardEmbed('#tsEmbed', {
283
     *    ... // other embed view config
284
     *   hiddenTabs: [
285
     *    '430496d6-6903-4601-937e-2c691821af3c',
286
     *    'f547ec54-2a37-4516-a222-2b06719af726'
287
     *   ]
288
     * });
289
     * ```
290
     * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 10.1.0.sw
291
     */
292
    hiddenTabs?: string[];
293
    /**
294
     * The list of tab IDs to show in the embedded Liveboard.
295
     * Only the tabs specified in the array will be shown in the Liveboard.
296
     *
297
     * Use either `visibleTabs` or `hiddenTabs`.
298
     *
299
     * Supported embed types: `LiveboardEmbed`
300
     * @version SDK: 1.26.0 | ThoughtSpot: 9.7.0.cl, 10.1.0.sw
301
     * @example
302
     * ```js
303
     * const embed = new LiveboardEmbed('#tsEmbed', {
304
     *    ... // other embed view config
305
     *    visibleTabs: [
306
     *       '430496d6-6903-4601-937e-2c691821af3c',
307
     *       'f547ec54-2a37-4516-a222-2b06719af726'
308
     *     ]
309
     * })
310
     * ```
311
     */
312
    visibleTabs?: string[];
313
    /**
314
     * This flag is used to enable/disable the styling and grouping in a Liveboard
315
     *
316
     * Supported embed types: `LiveboardEmbed`, `AppEmbed`
317
     * @type {boolean}
318
     * @version SDK: 1.40.0 | ThoughtSpot: 10.11.0.cl
319
     * @example
320
     * ```js
321
     * // Replace <EmbedComponent> with embed component name. For example, AppEmbed or LiveboardEmbed
322
     * const embed = new <EmbedComponent>('#tsEmbed', {
323
     *    ... // other embed view config
324
     *    isLiveboardStylingAndGroupingEnabled: true,
325
     * })
326
     * ```
327
     */
328
    isLiveboardStylingAndGroupingEnabled?: boolean;
329
    /**
330
     * This flag is used to enable/disable the png embedding of liveboard in scheduled mails
331
     *
332
     * Supported embed types: `AppEmbed`, `LiveboardEmbed`
333
     * @type {boolean}
334
     * @version SDK: 1.42.0 | ThoughtSpot: 10.14.0.cl
335
     * @example
336
     * ```js
337
     * // Replace <EmbedComponent> with embed component name. For example, AppEmbed or LiveboardEmbed
338
     * const embed = new <EmbedComponent>('#tsEmbed', {
339
     *    ... // other embed view config
340
     *    isPNGInScheduledEmailsEnabled: true,
341
     * })
342
     * ```
343
     */
344
    isPNGInScheduledEmailsEnabled?: boolean;
345
    /**
346
     * This flag is used to enable the full height lazy load data.
347
     *
348
     * @example
349
     * ```js
350
     * const embed = new LiveboardEmbed('#embed-container', {
351
     *    // ...other options
352
     *    fullHeight: true,
353
     *    lazyLoadingForFullHeight: true,
354
     * })
355
     * ```
356
     *
357
     * @type {boolean}
358
     * @default false
359
     * @version SDK: 1.40.0 | ThoughtSpot:10.12.0.cl
360
     */
361
    lazyLoadingForFullHeight?: boolean;
362
    /**
363
     * The margin to be used for lazy loading.
364
     *
365
     * For example, if the margin is set to '10px',
366
     * the visualization will be loaded 10px before the its top edge is visible in the
367
     * viewport.
368
     *
369
     * The format is similar to CSS margin.
370
     *
371
     * @example
372
     * ```js
373
     * const embed = new LiveboardEmbed('#embed-container', {
374
     *    // ...other options
375
     *    fullHeight: true,
376
     *    lazyLoadingForFullHeight: true,
377
     *   // Using 0px, the visualization will be only loaded when its visible in the viewport.
378
     *    lazyLoadingMargin: '0px',
379
     * })
380
     * ```
381
     * @type {string}
382
     * @version SDK: 1.40.0 | ThoughtSpot:10.12.0.cl
383
     */
384
    lazyLoadingMargin?: string;
385
    /**
386
     * showSpotterLimitations : show limitation text
387
     * of the spotter underneath the chat input.
388
     * default is false.
389
     *
390
     * @example
391
     * ```js
392
     * const embed = new LiveboardEmbed('#embed-container', {
393
     *    // ...other options
394
     *    showSpotterLimitations: true,
395
     * })
396
     * ```
397
     * @type {boolean}
398
     * @version SDK: 1.41.1 | ThoughtSpot: 10.5.0.cl
399
     */
400
    showSpotterLimitations?: boolean;
401
}
402

403
/**
404
 * Embed a ThoughtSpot Liveboard or visualization. When rendered it already
405
 * waits for the authentication to complete, so you need not wait for
406
 * `AuthStatus.SUCCESS`.
407
 * @example
408
 * ```js
409
 * import { .. } from '@thoughtspot/visual-embed-sdk';
410
 * init({ ... });
411
 * const embed = new LiveboardEmbed("#container", {
412
 *   liveboardId: <your-id-here>,
413
 * // .. other params here.
414
 * })
415
 * ```
416
 * @group Embed components
417
 */
418
export class LiveboardEmbed extends V1Embed {
14✔
419
    protected viewConfig: LiveboardViewConfig;
420

421
    private defaultHeight = 500;
142✔
422

423

424
    constructor(domSelector: DOMSelector, viewConfig: LiveboardViewConfig) {
425
        viewConfig.embedComponentType = 'LiveboardEmbed';
142✔
426
        super(domSelector, viewConfig);
142✔
427
        if (this.viewConfig.fullHeight === true) {
142✔
428
            if (this.viewConfig.vizId) {
18✔
429
                logger.warn('Full height is currently only supported for Liveboard embeds.' +
2✔
430
                    'Using full height with vizId might lead to unexpected behavior.');
431
            }
432

433
            this.on(EmbedEvent.RouteChange, this.setIframeHeightForNonEmbedLiveboard);
18✔
434
            this.on(EmbedEvent.EmbedHeight, this.updateIFrameHeight);
18✔
435
            this.on(EmbedEvent.EmbedIframeCenter, this.embedIframeCenter);
18✔
436
            this.on(EmbedEvent.RequestVisibleEmbedCoordinates, this.requestVisibleEmbedCoordinatesHandler);
18✔
437
        }
438
    }
439

440
    /**
441
     * Construct a map of params to be passed on to the
442
     * embedded Liveboard or visualization.
443
     */
444
    protected getEmbedParams() {
445
        const params = this.getEmbedParamsObject();
128✔
446
        const queryParams = getQueryParamString(params, true);
128✔
447
        return queryParams;
128✔
448
    }
449

450
    protected getEmbedParamsObject() {
451
        let params: any = {};
130✔
452
        params = this.getBaseQueryParams(params);
130✔
453
        const {
454
            enableVizTransformations,
455
            fullHeight,
456
            defaultHeight,
457
            visibleVizs,
458
            liveboardV2,
459
            vizId,
460
            hideTabPanel,
461
            activeTabId,
462
            hideLiveboardHeader,
463
            showLiveboardDescription,
464
            showLiveboardTitle,
465
            isLiveboardHeaderSticky = true,
129✔
466
            isLiveboardCompactHeaderEnabled = false,
129✔
467
            showLiveboardVerifiedBadge = true,
129✔
468
            showLiveboardReverifyBanner = true,
129✔
469
            hideIrrelevantChipsInLiveboardTabs = false,
129✔
470
            isEnhancedFilterInteractivityEnabled = false,
129✔
471
            enableAskSage,
472
            enable2ColumnLayout,
473
            dataPanelV2 = true,
130✔
474
            enableCustomColumnGroups = false,
130✔
475
            oAuthPollingInterval,
476
            isForceRedirect,
477
            dataSourceId,
478
            coverAndFilterOptionInPDF = false,
128✔
479
            liveboardXLSXCSVDownload = false,
128✔
480
            isLiveboardStylingAndGroupingEnabled,
481
            isPNGInScheduledEmailsEnabled = false,
129✔
482
            showSpotterLimitations,
483
            isCentralizedLiveboardFilterUXEnabled = false,
128✔
484
            isLinkParametersEnabled,
485
        } = this.viewConfig;
130✔
486

487
        const preventLiveboardFilterRemoval = this.viewConfig.preventLiveboardFilterRemoval
130✔
488
            || this.viewConfig.preventPinboardFilterRemoval;
489

490
        if (fullHeight === true) {
130✔
491
            params[Param.fullHeight] = true;
18✔
492
            if (this.viewConfig.lazyLoadingForFullHeight) {
18✔
493
                params[Param.IsLazyLoadingForEmbedEnabled] = true;
8✔
494
                params[Param.RootMarginForLazyLoad] = this.viewConfig.lazyLoadingMargin;
8✔
495
            }
496
        }
497
        if (defaultHeight) {
130✔
498
            this.defaultHeight = defaultHeight;
1✔
499
        }
500
        if (enableVizTransformations !== undefined) {
130✔
501
            params[Param.EnableVizTransformations] = enableVizTransformations.toString();
7✔
502
        }
503
        if (preventLiveboardFilterRemoval) {
130✔
504
            params[Param.preventLiveboardFilterRemoval] = true;
1✔
505
        }
506
        if (visibleVizs) {
130✔
507
            params[Param.visibleVizs] = visibleVizs;
1✔
508
        }
509
        params[Param.livedBoardEmbed] = true;
130✔
510
        if (vizId) {
130✔
511
            params[Param.vizEmbed] = true;
17✔
512
        }
513
        if (liveboardV2 !== undefined) {
130✔
514
            params[Param.LiveboardV2Enabled] = liveboardV2;
4✔
515
        }
516
        if (enable2ColumnLayout !== undefined) {
130✔
517
            params[Param.Enable2ColumnLayout] = enable2ColumnLayout;
1✔
518
        }
519
        if (hideTabPanel) {
130✔
520
            params[Param.HideTabPanel] = hideTabPanel;
1✔
521
        }
522
        if (hideLiveboardHeader) {
130!
523
            params[Param.HideLiveboardHeader] = hideLiveboardHeader;
×
524
        }
525
        if (showLiveboardDescription) {
130!
526
            params[Param.ShowLiveboardDescription] = showLiveboardDescription;
×
527
        }
528
        if (showLiveboardTitle) {
130!
529
            params[Param.ShowLiveboardTitle] = showLiveboardTitle;
×
530
        }
531
        if (enableAskSage) {
130✔
532
            params[Param.enableAskSage] = enableAskSage;
1✔
533
        }
534

535
        if (oAuthPollingInterval !== undefined) {
130✔
536
            params[Param.OauthPollingInterval] = oAuthPollingInterval;
1✔
537
        }
538

539
        if (isForceRedirect) {
130✔
540
            params[Param.IsForceRedirect] = isForceRedirect;
1✔
541
        }
542

543
        if (dataSourceId !== undefined) {
130✔
544
            params[Param.DataSourceId] = dataSourceId;
1✔
545
        }
546

547

548
        if (isLiveboardStylingAndGroupingEnabled !== undefined) {
130✔
549
            params[Param.IsLiveboardStylingAndGroupingEnabled] = isLiveboardStylingAndGroupingEnabled;
1✔
550
        }
551

552
        if (isPNGInScheduledEmailsEnabled !== undefined) {
130✔
553
            params[Param.isPNGInScheduledEmailsEnabled] = isPNGInScheduledEmailsEnabled;
130✔
554
        }
555

556
        if (showSpotterLimitations !== undefined) {
130✔
557
            params[Param.ShowSpotterLimitations] = showSpotterLimitations;
1✔
558
        }
559

560
        if (isLinkParametersEnabled !== undefined) {
130✔
561
            params[Param.isLinkParametersEnabled] = isLinkParametersEnabled;
2✔
562
        }
563

564
        if (isCentralizedLiveboardFilterUXEnabled !== undefined) {
130✔
565
            params[
130✔
566
                Param.isCentralizedLiveboardFilterUXEnabled
567
            ] = isCentralizedLiveboardFilterUXEnabled;
568
        }
569

570
        params[Param.LiveboardHeaderSticky] = isLiveboardHeaderSticky;
130✔
571
        params[Param.LiveboardHeaderV2] = isLiveboardCompactHeaderEnabled;
130✔
572
        params[Param.ShowLiveboardVerifiedBadge] = showLiveboardVerifiedBadge;
130✔
573
        params[Param.ShowLiveboardReverifyBanner] = showLiveboardReverifyBanner;
130✔
574
        params[Param.HideIrrelevantFiltersInTab] = hideIrrelevantChipsInLiveboardTabs;
130✔
575
        params[Param.IsEnhancedFilterInteractivityEnabled] = isEnhancedFilterInteractivityEnabled;
130✔
576
        params[Param.DataPanelV2Enabled] = dataPanelV2;
130✔
577
        params[Param.EnableCustomColumnGroups] = enableCustomColumnGroups;
130✔
578
        params[Param.CoverAndFilterOptionInPDF] = coverAndFilterOptionInPDF;
130✔
579
        params[Param.LiveboardXLSXCSVDownload] = !!liveboardXLSXCSVDownload;
130✔
580
        const queryParams = getQueryParamString(params, true);
130✔
581

582
        return params;
130✔
583
    }
584

585
    private getIframeSuffixSrc(liveboardId: string, vizId: string, activeTabId: string) {
586
        let suffix = `/embed/viz/${liveboardId}`;
124✔
587
        if (activeTabId) {
124✔
588
            suffix = `${suffix}/tab/${activeTabId} `;
6✔
589
        }
590
        if (vizId) {
124✔
591
            suffix = `${suffix}/${vizId}`;
19✔
592
        }
593
        const tsPostHashParams = this.getThoughtSpotPostUrlParams();
124✔
594
        suffix = `${suffix}${tsPostHashParams}`;
124✔
595
        return suffix;
124✔
596
    }
597

598
    private sendFullHeightLazyLoadData = () => {
142✔
599
        const data = calculateVisibleElementData(this.iFrame);
2✔
600
        this.trigger(HostEvent.VisibleEmbedCoordinates, data);
2✔
601
    }
602

603
    /**
604
     * This is a handler for the RequestVisibleEmbedCoordinates event.
605
     * It is used to send the visible coordinates data to the host application.
606
     * @param data The event payload
607
     * @param responder The responder function
608
     */
609
    private requestVisibleEmbedCoordinatesHandler = (data: MessagePayload, responder: any) => {
142✔
610
        logger.info('Sending RequestVisibleEmbedCoordinates', data);
1✔
611
        const visibleCoordinatesData = calculateVisibleElementData(this.iFrame);
1✔
612
        responder({ type: EmbedEvent.RequestVisibleEmbedCoordinates, data: visibleCoordinatesData });
1✔
613
    }
614

615
    /**
616
     * Construct the URL of the embedded ThoughtSpot Liveboard or visualization
617
     * to be loaded within the iFrame.
618
     */
619
    private getIFrameSrc(): string {
620
        const { vizId, activeTabId } = this.viewConfig;
120✔
621
        const liveboardId = this.viewConfig.liveboardId ?? this.viewConfig.pinboardId;
120✔
622

623
        if (!liveboardId) {
120!
NEW
624
            this.handleError({
×
625
                errorType: ErrorDetailsTypes.VALIDATION_ERROR,
626
                message: ERROR_MESSAGE.LIVEBOARD_VIZ_ID_VALIDATION,
627
                code: EmbedErrorCodes.LIVEBOARD_ID_MISSING,
628
                error: ERROR_MESSAGE.LIVEBOARD_VIZ_ID_VALIDATION,
629
            });
630
        }
631
        return `${this.getRootIframeSrc()}${this.getIframeSuffixSrc(
120✔
632
            liveboardId,
633
            vizId,
634
            activeTabId,
635
        )}`;
636
    }
637

638
    /**
639
     * Set the iframe height as per the computed height received
640
     * from the ThoughtSpot app.
641
     * @param data The event payload
642
     */
643
    private updateIFrameHeight = (data: MessagePayload) => {
142✔
644
        this.setIFrameHeight(Math.max(data.data, this.defaultHeight));
×
645
        this.sendFullHeightLazyLoadData();
×
646
    };
647

648
    private embedIframeCenter = (data: MessagePayload, responder: any) => {
142✔
649
        const obj = this.getIframeCenter();
4✔
650
        responder({ type: EmbedEvent.EmbedIframeCenter, data: obj });
4✔
651
    };
652

653
    private setIframeHeightForNonEmbedLiveboard = (data: MessagePayload) => {
142✔
654
        const { height: frameHeight } = this.viewConfig.frameParams || {};
3!
655

656
        const liveboardRelatedRoutes = [
3✔
657
            '/pinboard/',
658
            '/insights/pinboard/',
659
            '/schedules/',
660
            '/embed/viz/',
661
            '/embed/insights/viz/',
662
            '/liveboard/',
663
            '/insights/liveboard/',
664
            '/tsl-editor/PINBOARD_ANSWER_BOOK/',
665
            '/import-tsl/PINBOARD_ANSWER_BOOK/',
666
        ];
667

668
        if (liveboardRelatedRoutes.some((path) => data.data.currentPath.startsWith(path))) {
18✔
669
            // Ignore the height reset of the frame, if the navigation is
670
            // only within the liveboard page.
671
            return;
2✔
672
        }
673
        this.setIFrameHeight(frameHeight || this.defaultHeight);
1!
674
    };
675

676
    private setActiveTab(data: { tabId: string }) {
677
        if (!this.viewConfig.vizId) {
2✔
678
            const prefixPath = this.iFrame.src.split('#/')[1].split('/tab')[0];
1✔
679
            const path = `${prefixPath}/tab/${data.tabId}`;
1✔
680
            super.trigger(HostEvent.Navigate, path);
1✔
681
        }
682
    }
683

684
    private async showPreviewLoader() {
685
        if (!this.viewConfig.showPreviewLoader || !this.viewConfig.vizId) {
120✔
686
            return;
119✔
687
        }
688

689
        try {
1✔
690
            const preview = await getPreview(
1✔
691
                this.thoughtSpotHost,
692
                this.viewConfig.vizId,
693
                this.viewConfig.liveboardId,
694
            );
695

696
            if (!preview.vizContent) {
1!
697
                return;
×
698
            }
699
            addPreviewStylesIfNotPresent();
1✔
700

701
            const div = document.createElement('div');
1✔
702
            div.innerHTML = `
1✔
703
                <div class=ts-viz-preview-loader>
704
                    ${preview.vizContent}
705
                </div>
706
                `;
707
            const previewDiv = div.firstElementChild as HTMLElement;
1✔
708
            this.el.appendChild(previewDiv);
1✔
709
            this.el.style.position = 'relative';
1✔
710
            this.on(EmbedEvent.Data, () => {
1✔
711
                previewDiv.remove();
1✔
712
            });
713
        } catch (error) {
714
            console.error('Error fetching preview', error);
×
715
        }
716
    }
717

718
    /**
719
     * @hidden
720
     * Internal state to track the current liveboard id.
721
     * This is used to navigate to the correct liveboard when the prerender is visible.
722
     */
723
    public currentLiveboardState = {
142✔
724
        liveboardId: this.viewConfig.liveboardId,
725
        vizId: this.viewConfig.vizId,
726
        activeTabId: this.viewConfig.activeTabId,
727
    };
728

729
    protected beforePrerenderVisible(): void {
730
        const embedObj = this.getPreRenderObj<LiveboardEmbed>();
12✔
731

732
        this.executeAfterEmbedContainerLoaded(() => {
12✔
733
            this.navigateToLiveboard(this.viewConfig.liveboardId, this.viewConfig.vizId, this.viewConfig.activeTabId);
8✔
734
            if (embedObj) {
8✔
735
                embedObj.currentLiveboardState = {
3✔
736
                    liveboardId: this.viewConfig.liveboardId,
737
                    vizId: this.viewConfig.vizId,
738
                    activeTabId: this.viewConfig.activeTabId,
739
                };
740
            }
741
        });
742
    }
743

744
    protected async handleRenderForPrerender(): Promise<TsEmbed> {
745
        if (isUndefined(this.viewConfig.liveboardId)) {
12✔
746
            return this.prerenderGeneric();
5✔
747
        }
748
        return super.handleRenderForPrerender();
7✔
749
    }
750

751
    /**
752
     * Triggers an event to the embedded app
753
     * @param {HostEvent} messageType The event type
754
     * @param {any} data The payload to send with the message
755
     * @returns A promise that resolves with the response from the embedded app
756
     */
757
    public trigger<HostEventT extends HostEvent, PayloadT>(
758
        messageType: HostEventT,
759
        data: TriggerPayload<PayloadT, HostEventT> = ({} as any),
71✔
760
    ): Promise<TriggerResponse<PayloadT, HostEventT>> {
761
        const dataWithVizId: any = data;
84✔
762
        if (messageType === HostEvent.SetActiveTab) {
84✔
763
            this.setActiveTab(data as { tabId: string });
2✔
764
            return Promise.resolve(null);
2✔
765
        }
766
        if (typeof dataWithVizId === 'object' && this.viewConfig.vizId) {
82✔
767
            dataWithVizId.vizId = this.viewConfig.vizId;
4✔
768
        }
769
        return super.trigger(messageType, dataWithVizId);
82✔
770
    }
771
    /**
772
     * Destroys the ThoughtSpot embed, and remove any nodes from the DOM.
773
     * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl
774
     */
775
    public destroy() {
776
        super.destroy();
11✔
777
        this.unregisterLazyLoadEvents();
11✔
778
    }
779

780
    private postRender() {
781
        this.registerLazyLoadEvents();
120✔
782
    }
783

784
    private registerLazyLoadEvents() {
785
        if (this.viewConfig.fullHeight && this.viewConfig.lazyLoadingForFullHeight) {
120✔
786
            // TODO: Use passive: true, install modernizr to check for passive
787
            window.addEventListener('resize', this.sendFullHeightLazyLoadData);
8✔
788
            window.addEventListener('scroll', this.sendFullHeightLazyLoadData, true);
8✔
789
        }
790
    }
791

792
    private unregisterLazyLoadEvents() {
793
        if (this.viewConfig.fullHeight && this.viewConfig.lazyLoadingForFullHeight) {
11✔
794
            window.removeEventListener('resize', this.sendFullHeightLazyLoadData);
1✔
795
            window.removeEventListener('scroll', this.sendFullHeightLazyLoadData);
1✔
796
        }
797
    }
798

799
    /**
800
     * Render an embedded ThoughtSpot Liveboard or visualization
801
     * @param renderOptions An object specifying the Liveboard ID,
802
     * visualization ID and the runtime filters.
803
     */
804
    public async render(): Promise<LiveboardEmbed> {
805
        await super.render();
120✔
806

807
        const src = this.getIFrameSrc();
120✔
808
        await this.renderV1Embed(src);
120✔
809
        this.showPreviewLoader();
120✔
810

811
        this.postRender();
120✔
812
        return this;
120✔
813
    }
814

815
    public navigateToLiveboard(liveboardId: string, vizId?: string, activeTabId?: string) {
816
        const path = this.getIframeSuffixSrc(liveboardId, vizId, activeTabId);
4✔
817
        this.viewConfig.liveboardId = liveboardId;
4✔
818
        this.viewConfig.activeTabId = activeTabId;
4✔
819
        this.viewConfig.vizId = vizId;
4✔
820
        if (this.isRendered) {
4!
821
            this.trigger(HostEvent.Navigate, path.substring(1));
4✔
822
        } else if (this.viewConfig.preRenderId) {
×
823
            this.preRender(true);
×
824
        } else {
825
            this.render();
×
826
        }
827
    }
828

829
    /**
830
     * Returns the full url of the Liveboard/visualization which can be used to open
831
     * this Liveboard inside the full ThoughtSpot application in a new tab.
832
     * @returns url string
833
     */
834
    public getLiveboardUrl(): string {
835
        let url = `${this.thoughtSpotHost}/#/pinboard/${this.viewConfig.liveboardId}`;
1✔
836
        if (this.viewConfig.activeTabId) {
1!
837
            url = `${url}/tab/${this.viewConfig.activeTabId}`;
×
838
        }
839

840
        if (this.viewConfig.vizId) {
1!
841
            url = `${url}/${this.viewConfig.vizId}`;
×
842
        }
843

844
        return url;
1✔
845
    }
846
}
847

848
/**
849
 * @hidden
850
 */
851
export class PinboardEmbed extends LiveboardEmbed { }
14✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc