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

thoughtspot / visual-embed-sdk / #2918

02 Dec 2025 05:02AM UTC coverage: 94.302% (+0.009%) from 94.293%
#2918

push

web-flow
SCAL-283098 - Aether- Grouping - new actions (#356)

Co-authored-by: goutham.sidhardha <goutham.sidhardha@thoughtspot.com>

1347 of 1515 branches covered (88.91%)

Branch coverage included in aggregate %.

4 of 4 new or added lines in 1 file covered. (100.0%)

13 existing lines in 1 file now uncovered.

3171 of 3276 relevant lines covered (96.79%)

102.75 hits per line

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

90.46
/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
} from '../types';
24
import { calculateVisibleElementData, getQueryParamString, isUndefined } from '../utils';
14✔
25
import { getAuthPromise } from './base';
26
import { TsEmbed, V1Embed } from './ts-embed';
14✔
27
import { addPreviewStylesIfNotPresent } from '../utils/global-styles';
14✔
28
import { TriggerPayload, TriggerResponse } from './hostEventClient/contracts';
29
import { logger } from '../utils/logger';
14✔
30

31

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

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

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

416
/**
417
 * Embed a ThoughtSpot Liveboard or visualization. When rendered it already
418
 * waits for the authentication to complete, so you need not wait for
419
 * `AuthStatus.SUCCESS`.
420
 * @example
421
 * ```js
422
 * import { .. } from '@thoughtspot/visual-embed-sdk';
423
 * init({ ... });
424
 * const embed = new LiveboardEmbed("#container", {
425
 *   liveboardId: <your-id-here>,
426
 * // .. other params here.
427
 * })
428
 * ```
429
 * @group Embed components
430
 */
431
export class LiveboardEmbed extends V1Embed {
432
    protected viewConfig: LiveboardViewConfig;
433

434
    private defaultHeight = 500;
435

436

437
    constructor(domSelector: DOMSelector, viewConfig: LiveboardViewConfig) {
438
        viewConfig.embedComponentType = 'LiveboardEmbed';
439
        super(domSelector, viewConfig);
440
        if (this.viewConfig.fullHeight === true) {
441
            if (this.viewConfig.vizId) {
442
                logger.warn('Full height is currently only supported for Liveboard embeds.' +
443
                    'Using full height with vizId might lead to unexpected behavior.');
444
            }
445

446
            this.on(EmbedEvent.RouteChange, this.setIframeHeightForNonEmbedLiveboard);
447
            this.on(EmbedEvent.EmbedHeight, this.updateIFrameHeight);
448
            this.on(EmbedEvent.EmbedIframeCenter, this.embedIframeCenter);
449
            this.on(EmbedEvent.RequestVisibleEmbedCoordinates, this.requestVisibleEmbedCoordinatesHandler);
14✔
450
        }
451
    }
452

147✔
453
    /**
454
     * Construct a map of params to be passed on to the
455
     * embedded Liveboard or visualization.
456
     */
147✔
457
    protected getEmbedParams() {
147✔
458
        const params = this.getEmbedParamsObject();
147✔
459
        const queryParams = getQueryParamString(params, true);
22✔
460
        return queryParams;
2✔
461
    }
462

463
    protected getEmbedParamsObject() {
464
        let params: any = {};
22✔
465
        params = this.getBaseQueryParams(params);
22✔
466
        const {
22✔
467
            enableVizTransformations,
22✔
468
            fullHeight,
469
            defaultHeight,
470
            visibleVizs,
471
            liveboardV2,
472
            vizId,
473
            hideTabPanel,
474
            activeTabId,
475
            hideLiveboardHeader,
476
            showLiveboardDescription,
133✔
477
            showLiveboardTitle,
133✔
478
            isLiveboardHeaderSticky = true,
133✔
479
            isLiveboardCompactHeaderEnabled = false,
480
            showLiveboardVerifiedBadge = true,
481
            showLiveboardReverifyBanner = true,
482
            hideIrrelevantChipsInLiveboardTabs = false,
135✔
483
            isEnhancedFilterInteractivityEnabled = false,
135✔
484
            enableAskSage,
485
            enable2ColumnLayout,
486
            dataPanelV2 = true,
487
            enableCustomColumnGroups = false,
488
            oAuthPollingInterval,
489
            isForceRedirect,
490
            dataSourceId,
491
            coverAndFilterOptionInPDF = false,
492
            liveboardXLSXCSVDownload,
493
            isLiveboardStylingAndGroupingEnabled,
494
            isPNGInScheduledEmailsEnabled = false,
495
            showSpotterLimitations,
496
            isCentralizedLiveboardFilterUXEnabled = false,
497
            isLinkParametersEnabled,
134✔
498
            updatedSpotterChatPrompt,
134✔
499
        } = this.viewConfig;
134✔
500

134✔
501
        const preventLiveboardFilterRemoval = this.viewConfig.preventLiveboardFilterRemoval
134✔
502
            || this.viewConfig.preventPinboardFilterRemoval;
134✔
503

504
        if (fullHeight === true) {
505
            params[Param.fullHeight] = true;
135✔
506
            if (this.viewConfig.lazyLoadingForFullHeight) {
135✔
507
                params[Param.IsLazyLoadingForEmbedEnabled] = true;
508
                params[Param.RootMarginForLazyLoad] = this.viewConfig.lazyLoadingMargin;
509
            }
510
        }
133✔
511
        if (defaultHeight) {
512
            this.defaultHeight = defaultHeight;
513
        }
134✔
514
        if (enableVizTransformations !== undefined) {
515
            params[Param.EnableVizTransformations] = enableVizTransformations.toString();
133✔
516
        }
517
        if (preventLiveboardFilterRemoval) {
518
            params[Param.preventLiveboardFilterRemoval] = true;
135✔
519
        }
520
        if (!isUndefined(updatedSpotterChatPrompt)) {
135✔
521
            params[Param.UpdatedSpotterChatPrompt] = !!updatedSpotterChatPrompt;
522
        }
523
        if (visibleVizs) {
135✔
524
            params[Param.visibleVizs] = visibleVizs;
22✔
525
        }
22✔
526
        params[Param.livedBoardEmbed] = true;
8✔
527
        if (vizId) {
8✔
528
            params[Param.vizEmbed] = true;
529
        }
530
        if (liveboardV2 !== undefined) {
135✔
531
            params[Param.LiveboardV2Enabled] = liveboardV2;
3✔
532
        }
533
        if (enable2ColumnLayout !== undefined) {
135✔
534
            params[Param.Enable2ColumnLayout] = enable2ColumnLayout;
2✔
535
        }
536
        if (hideTabPanel) {
135✔
537
            params[Param.HideTabPanel] = hideTabPanel;
7✔
538
        }
539
        if (hideLiveboardHeader) {
135✔
540
            params[Param.HideLiveboardHeader] = hideLiveboardHeader;
1✔
541
        }
542
        if (showLiveboardDescription) {
135✔
543
            params[Param.ShowLiveboardDescription] = showLiveboardDescription;
2✔
544
        }
545
        if (showLiveboardTitle) {
135✔
546
            params[Param.ShowLiveboardTitle] = showLiveboardTitle;
1✔
547
        }
548
        if (enableAskSage) {
135✔
549
            params[Param.enableAskSage] = enableAskSage;
135✔
550
        }
17✔
551

552
        if (oAuthPollingInterval !== undefined) {
135✔
553
            params[Param.OauthPollingInterval] = oAuthPollingInterval;
4✔
554
        }
555

135✔
556
        if (isForceRedirect) {
1✔
557
            params[Param.IsForceRedirect] = isForceRedirect;
558
        }
135✔
559

1✔
560
        if (dataSourceId !== undefined) {
561
            params[Param.DataSourceId] = dataSourceId;
135!
UNCOV
562
        }
×
563

564

135!
UNCOV
565
        if (isLiveboardStylingAndGroupingEnabled !== undefined) {
×
566
            params[Param.IsLiveboardStylingAndGroupingEnabled] = isLiveboardStylingAndGroupingEnabled;
567
        }
135!
UNCOV
568

×
569
        if (isPNGInScheduledEmailsEnabled !== undefined) {
570
            params[Param.isPNGInScheduledEmailsEnabled] = isPNGInScheduledEmailsEnabled;
135✔
571
        }
1✔
572

573
        if (showSpotterLimitations !== undefined) {
574
            params[Param.ShowSpotterLimitations] = showSpotterLimitations;
135✔
575
        }
1✔
576

577
        if (isLinkParametersEnabled !== undefined) {
578
            params[Param.isLinkParametersEnabled] = isLinkParametersEnabled;
135✔
579
        }
1✔
580

581
        if (isCentralizedLiveboardFilterUXEnabled !== undefined) {
582
            params[
135✔
583
                Param.isCentralizedLiveboardFilterUXEnabled
1✔
584
            ] = isCentralizedLiveboardFilterUXEnabled;
585
        }
586

587
        params[Param.LiveboardHeaderSticky] = isLiveboardHeaderSticky;
135✔
588
        params[Param.LiveboardHeaderV2] = isLiveboardCompactHeaderEnabled;
1✔
589
        params[Param.ShowLiveboardVerifiedBadge] = showLiveboardVerifiedBadge;
590
        params[Param.ShowLiveboardReverifyBanner] = showLiveboardReverifyBanner;
591
        params[Param.HideIrrelevantFiltersInTab] = hideIrrelevantChipsInLiveboardTabs;
135✔
592
        params[Param.IsEnhancedFilterInteractivityEnabled] = isEnhancedFilterInteractivityEnabled;
135✔
593
        params[Param.DataPanelV2Enabled] = dataPanelV2;
594
        params[Param.EnableCustomColumnGroups] = enableCustomColumnGroups;
595
        params[Param.CoverAndFilterOptionInPDF] = coverAndFilterOptionInPDF;
135✔
596

1✔
597
        if (liveboardXLSXCSVDownload !== undefined) {
598
            params[Param.LiveboardXLSXCSVDownload] = !!liveboardXLSXCSVDownload;
599
        }
135✔
600

2✔
601
        const queryParams = getQueryParamString(params, true);
602

603
        return params;
135✔
604
    }
135✔
605

606
    private getIframeSuffixSrc(liveboardId: string, vizId: string, activeTabId: string) {
607
        let suffix = `/embed/viz/${liveboardId}`;
608
        if (activeTabId) {
609
            suffix = `${suffix}/tab/${activeTabId} `;
135✔
610
        }
135✔
611
        if (vizId) {
135✔
612
            suffix = `${suffix}/${vizId}`;
135✔
613
        }
135✔
614
        const tsPostHashParams = this.getThoughtSpotPostUrlParams();
135✔
615
        suffix = `${suffix}${tsPostHashParams}`;
135✔
616
        return suffix;
135✔
617
    }
135✔
618

619
    private sendFullHeightLazyLoadData = () => {
135✔
620
        const data = calculateVisibleElementData(this.iFrame);
2✔
621
        this.trigger(HostEvent.VisibleEmbedCoordinates, data);
622
    }
623

135✔
624
    /**
625
     * This is a handler for the RequestVisibleEmbedCoordinates event.
135✔
626
     * It is used to send the visible coordinates data to the host application.
627
     * @param data The event payload
628
     * @param responder The responder function
629
     */
129✔
630
    private requestVisibleEmbedCoordinatesHandler = (data: MessagePayload, responder: any) => {
129✔
631
        logger.info('Sending RequestVisibleEmbedCoordinates', data);
6✔
632
        const visibleCoordinatesData = calculateVisibleElementData(this.iFrame);
633
        responder({ type: EmbedEvent.RequestVisibleEmbedCoordinates, data: visibleCoordinatesData });
129✔
634
    }
19✔
635

636
    /**
129✔
637
     * Construct the URL of the embedded ThoughtSpot Liveboard or visualization
129✔
638
     * to be loaded within the iFrame.
129✔
639
     */
640
    private getIFrameSrc(): string {
641
        const { vizId, activeTabId } = this.viewConfig;
147✔
642
        const liveboardId = this.viewConfig.liveboardId ?? this.viewConfig.pinboardId;
2✔
643

2✔
644
        if (!liveboardId) {
645
            this.handleError(ERROR_MESSAGE.LIVEBOARD_VIZ_ID_VALIDATION);
646
        }
647
        return `${this.getRootIframeSrc()}${this.getIframeSuffixSrc(
648
            liveboardId,
649
            vizId,
650
            activeTabId,
651
        )}`;
652
    }
147✔
653

1✔
654
    /**
1✔
655
     * Set the iframe height as per the computed height received
1✔
656
     * from the ThoughtSpot app.
657
     * @param data The event payload
658
     */
659
    private updateIFrameHeight = (data: MessagePayload) => {
660
        this.setIFrameHeight(Math.max(data.data, this.defaultHeight));
661
        this.sendFullHeightLazyLoadData();
662
    };
663

125✔
664
    private embedIframeCenter = (data: MessagePayload, responder: any) => {
125✔
665
        const obj = this.getIframeCenter();
666
        responder({ type: EmbedEvent.EmbedIframeCenter, data: obj });
125!
UNCOV
667
    };
×
668

669
    private setIframeHeightForNonEmbedLiveboard = (data: MessagePayload) => {
125✔
670
        const { height: frameHeight } = this.viewConfig.frameParams || {};
671

672
        const liveboardRelatedRoutes = [
673
            '/pinboard/',
674
            '/insights/pinboard/',
675
            '/schedules/',
676
            '/embed/viz/',
677
            '/embed/insights/viz/',
678
            '/liveboard/',
679
            '/insights/liveboard/',
680
            '/tsl-editor/PINBOARD_ANSWER_BOOK/',
681
            '/import-tsl/PINBOARD_ANSWER_BOOK/',
147✔
UNCOV
682
        ];
×
UNCOV
683

×
684
        if (liveboardRelatedRoutes.some((path) => data.data.currentPath.startsWith(path))) {
685
            // Ignore the height reset of the frame, if the navigation is
686
            // only within the liveboard page.
147✔
687
            return;
4✔
688
        }
4✔
689
        this.setIFrameHeight(frameHeight || this.defaultHeight);
690
    };
691

147✔
692
    private setActiveTab(data: { tabId: string }) {
3!
693
        if (!this.viewConfig.vizId) {
694
            const prefixPath = this.iFrame.src.split('#/')[1].split('/tab')[0];
3✔
695
            const path = `${prefixPath}/tab/${data.tabId}`;
696
            super.trigger(HostEvent.Navigate, path);
697
        }
698
    }
699

700
    private async showPreviewLoader() {
701
        if (!this.viewConfig.showPreviewLoader || !this.viewConfig.vizId) {
702
            return;
703
        }
704

705
        try {
706
            const preview = await getPreview(
18✔
707
                this.thoughtSpotHost,
708
                this.viewConfig.vizId,
709
                this.viewConfig.liveboardId,
2✔
710
            );
711

1!
712
            if (!preview.vizContent) {
713
                return;
714
            }
715
            addPreviewStylesIfNotPresent();
2✔
716

1✔
717
            const div = document.createElement('div');
1✔
718
            div.innerHTML = `
1✔
719
                <div class=ts-viz-preview-loader>
720
                    ${preview.vizContent}
721
                </div>
722
                `;
723
            const previewDiv = div.firstElementChild as HTMLElement;
125✔
724
            this.el.appendChild(previewDiv);
124✔
725
            this.el.style.position = 'relative';
726
            this.on(EmbedEvent.Data, () => {
727
                previewDiv.remove();
1✔
728
            });
1✔
729
        } catch (error) {
730
            console.error('Error fetching preview', error);
731
        }
732
    }
733

734
    /**
1!
UNCOV
735
     * @hidden
×
736
     * Internal state to track the current liveboard id.
737
     * This is used to navigate to the correct liveboard when the prerender is visible.
1✔
738
     */
739
    public currentLiveboardState = {
1✔
740
        liveboardId: this.viewConfig.liveboardId,
1✔
741
        vizId: this.viewConfig.vizId,
742
        activeTabId: this.viewConfig.activeTabId,
743
    };
744

745
    protected beforePrerenderVisible(): void {
1✔
746
        const embedObj = this.getPreRenderObj<LiveboardEmbed>();
1✔
747

1✔
748
        this.executeAfterEmbedContainerLoaded(() => {
1✔
749
            this.navigateToLiveboard(this.viewConfig.liveboardId, this.viewConfig.vizId, this.viewConfig.activeTabId);
1✔
750
            if (embedObj) {
751
                embedObj.currentLiveboardState = {
UNCOV
752
                    liveboardId: this.viewConfig.liveboardId,
×
753
                    vizId: this.viewConfig.vizId,
754
                    activeTabId: this.viewConfig.activeTabId,
755
                };
756
            }
757
        });
758
    }
759

760
    protected async handleRenderForPrerender(): Promise<TsEmbed> {
761
        if (isUndefined(this.viewConfig.liveboardId)) {
147✔
762
            return this.prerenderGeneric();
763
        }
764
        return super.handleRenderForPrerender();
765
    }
766

767
    /**
768
     * Triggers an event to the embedded app
12✔
769
     * @param {HostEvent} messageType The event type
770
     * @param {any} data The payload to send with the message
12✔
771
     * @returns A promise that resolves with the response from the embedded app
8✔
772
     */
8✔
773
    public trigger<HostEventT extends HostEvent, PayloadT>(
3✔
774
        messageType: HostEventT,
775
        data: TriggerPayload<PayloadT, HostEventT> = ({} as any),
776
    ): Promise<TriggerResponse<PayloadT, HostEventT>> {
777
        const dataWithVizId: any = data;
778
        if (messageType === HostEvent.SetActiveTab) {
779
            this.setActiveTab(data as { tabId: string });
780
            return Promise.resolve(null);
781
        }
782
        if (typeof dataWithVizId === 'object' && this.viewConfig.vizId) {
783
            dataWithVizId.vizId = this.viewConfig.vizId;
10✔
784
        }
5✔
785
        return super.trigger(messageType, dataWithVizId);
786
    }
5✔
787
    /**
788
     * Destroys the ThoughtSpot embed, and remove any nodes from the DOM.
789
     * @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl
790
     */
791
    public destroy() {
792
        super.destroy();
793
        this.unregisterLazyLoadEvents();
794
    }
795

796
    private postRender() {
797
        this.registerLazyLoadEvents();
71✔
798
    }
799

84✔
800
    private registerLazyLoadEvents() {
84✔
801
        if (this.viewConfig.fullHeight && this.viewConfig.lazyLoadingForFullHeight) {
2✔
802
            // TODO: Use passive: true, install modernizr to check for passive
2✔
803
            window.addEventListener('resize', this.sendFullHeightLazyLoadData);
804
            window.addEventListener('scroll', this.sendFullHeightLazyLoadData, true);
82✔
805
        }
4✔
806
    }
807

82✔
808
    private unregisterLazyLoadEvents() {
809
        if (this.viewConfig.fullHeight && this.viewConfig.lazyLoadingForFullHeight) {
810
            window.removeEventListener('resize', this.sendFullHeightLazyLoadData);
811
            window.removeEventListener('scroll', this.sendFullHeightLazyLoadData);
812
        }
813
    }
814

11✔
815
    /**
11✔
816
     * Render an embedded ThoughtSpot Liveboard or visualization
817
     * @param renderOptions An object specifying the Liveboard ID,
818
     * visualization ID and the runtime filters.
819
     */
125✔
820
    public async render(): Promise<LiveboardEmbed> {
821
        await super.render();
822

823
        const src = this.getIFrameSrc();
125✔
824
        await this.renderV1Embed(src);
825
        this.showPreviewLoader();
8✔
826

8✔
827
        this.postRender();
828
        return this;
829
    }
830

831
    public navigateToLiveboard(liveboardId: string, vizId?: string, activeTabId?: string) {
11✔
832
        const path = this.getIframeSuffixSrc(liveboardId, vizId, activeTabId);
1✔
833
        this.viewConfig.liveboardId = liveboardId;
1✔
834
        this.viewConfig.activeTabId = activeTabId;
835
        this.viewConfig.vizId = vizId;
836
        if (this.isRendered) {
837
            this.trigger(HostEvent.Navigate, path.substring(1));
838
        } else if (this.viewConfig.preRenderId) {
839
            this.preRender(true);
840
        } else {
841
            this.render();
842
        }
843
    }
125✔
844

845
    /**
125✔
846
     * Returns the full url of the Liveboard/visualization which can be used to open
125✔
847
     * this Liveboard inside the full ThoughtSpot application in a new tab.
125✔
848
     * @returns url string
849
     */
125✔
850
    public getLiveboardUrl(): string {
125✔
851
        let url = `${this.thoughtSpotHost}/#/pinboard/${this.viewConfig.liveboardId}`;
852
        if (this.viewConfig.activeTabId) {
853
            url = `${url}/tab/${this.viewConfig.activeTabId}`;
854
        }
4✔
855

4✔
856
        if (this.viewConfig.vizId) {
4✔
857
            url = `${url}/${this.viewConfig.vizId}`;
4✔
858
        }
4!
859

4✔
UNCOV
860
        return url;
×
UNCOV
861
    }
×
862
}
UNCOV
863

×
864
/**
865
 * @hidden
866
 */
867
export class PinboardEmbed extends LiveboardEmbed { }
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