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

thoughtspot / visual-embed-sdk / #1435

14 Jan 2025 09:42PM UTC coverage: 41.895% (-52.0%) from 93.86%
#1435

Pull #65

Prashant.patil
SCAL-233454-exp checking-mixpanel-error
Pull Request #65: test-exported memb

273 of 1106 branches covered (24.68%)

Branch coverage included in aggregate %.

84 of 356 new or added lines in 14 files covered. (23.6%)

1011 existing lines in 23 files now uncovered.

1301 of 2651 relevant lines covered (49.08%)

9.11 hits per line

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

17.29
/src/embed/base.ts
1
/* eslint-disable camelcase */
2
/* eslint-disable import/no-mutable-exports */
3
/**
4
 * Copyright (c) 2022
5
 *
6
 * Base classes
7
 * @summary Base classes
8
 * @author Ayon Ghosh <ayon.ghosh@thoughtspot.com>
9
 */
10
import EventEmitter from 'eventemitter3';
11
import { registerReportingObserver } from '../utils/reporting';
12
import { resetCachedAuthToken } from '../authToken';
13
import { logger, setGlobalLogLevelOverride } from '../utils/logger';
16✔
14
import { tokenizedFetch } from '../tokenizedFetch';
16✔
15
import { EndPoints } from '../utils/authService/authService';
16✔
16
import { getThoughtSpotHost } from '../config';
17
import {
16✔
18
    AuthType, EmbedConfig, LogLevel, Param, PrefetchFeatures,
19
} from '../types';
20
import {
16✔
21
    authenticate,
22
    logout as _logout,
23
    AuthFailureType,
24
    AuthStatus,
25
    AuthEvent,
26
    notifyAuthFailure,
27
    notifyAuthSDKSuccess,
28
    notifyAuthSuccess,
29
    notifyLogout,
30
    setAuthEE,
31
    AuthEventEmitter,
32
    postLoginService,
33
} from '../auth';
34
import { uploadMixpanelEvent, MIXPANEL_EVENT } from '../mixpanel-service';
35
import { getEmbedConfig, setEmbedConfig } from './embedConfig';
16✔
36
import { getQueryParamString } from '../utils';
16✔
37

38
const CONFIG_DEFAULTS: Partial<EmbedConfig> = {
16✔
39
    loginFailedMessage: 'Not logged in',
40
    authTriggerText: 'Authorize',
41
    authType: AuthType.None,
42
    logLevel: LogLevel.ERROR,
43
};
44

45
export interface executeTMLInput {
46
    metadata_tmls: string[];
47
    import_policy?: 'PARTIAL' | 'ALL_OR_NONE' | 'VALIDATE_ONLY';
48
    create_new?: boolean;
49
}
50

51
export interface exportTMLInput {
52
    metadata: {
53
        identifier: string;
54
        type?: 'LIVEBOARD' | 'ANSWER' | 'LOGICAL_TABLE' | 'CONNECTION';
55
    }[];
56
    export_associated?: boolean;
57
    export_fqn?: boolean;
58
    edoc_format?: 'YAML' | 'JSON';
59
}
60

61
export let authPromise: Promise<boolean>;
62

63
export const getAuthPromise = (): Promise<boolean> => authPromise;
16✔
64

65
export {
66
    notifyAuthFailure, notifyAuthSDKSuccess, notifyAuthSuccess, notifyLogout,
16✔
67
};
68

69
/**
70
 * Perform authentication on the ThoughtSpot app as applicable.
71
 */
72
export const handleAuth = (): Promise<boolean> => {
16✔
UNCOV
73
    authPromise = authenticate(getEmbedConfig());
×
UNCOV
74
    authPromise.then(
×
75
        (isLoggedIn) => {
UNCOV
76
            if (!isLoggedIn) {
×
UNCOV
77
                notifyAuthFailure(AuthFailureType.SDK);
×
78
            } else {
79
                // Post login service is called after successful login.
80
                // postLoginService();
UNCOV
81
                notifyAuthSDKSuccess();
×
82
            }
83
        },
84
        () => {
UNCOV
85
            notifyAuthFailure(AuthFailureType.SDK);
×
86
        },
87
    );
UNCOV
88
    return authPromise;
×
89
};
90

91
const hostUrlToFeatureUrl = {
16✔
UNCOV
92
    [PrefetchFeatures.SearchEmbed]: (url: string, flags: string) => `${url}v2/?${flags}#/embed/answer`,
×
UNCOV
93
    [PrefetchFeatures.LiveboardEmbed]: (url: string, flags: string) => `${url}?${flags}`,
×
UNCOV
94
    [PrefetchFeatures.FullApp]: (url: string, flags: string) => `${url}?${flags}`,
×
95
    [PrefetchFeatures.VizEmbed]: (url: string, flags: string) => `${url}?${flags}`,
×
96
};
97

98
/**
99
 * Prefetches static resources from the specified URL. Web browsers can then cache the
100
 * prefetched resources and serve them from the user's local disk to provide faster access
101
 * to your app.
102
 * @param url The URL provided for prefetch
103
 * @param prefetchFeatures Specify features which needs to be prefetched.
104
 * @param additionalFlags This can be used to add any URL flag.
105
 * @version SDK: 1.4.0 | ThoughtSpot: ts7.sep.cl, 7.2.1
106
 * @group Global methods
107
 */
108
export const prefetch = (
16✔
109
    url?: string,
110
    prefetchFeatures?: PrefetchFeatures[],
111
    additionalFlags?: { [key: string]: string | number | boolean },
112
): void => {
UNCOV
113
    if (url === '') {
×
114
        // eslint-disable-next-line no-console
UNCOV
115
        logger.warn('The prefetch method does not have a valid URL');
×
116
    } else {
UNCOV
117
        const features = prefetchFeatures || [PrefetchFeatures.FullApp];
×
UNCOV
118
        let hostUrl = url || getEmbedConfig().thoughtSpotHost;
×
UNCOV
119
        const prefetchFlags = {
×
120
            [Param.EmbedApp]: true,
121
            ...getEmbedConfig()?.additionalFlags,
×
122
            ...additionalFlags,
123
        };
UNCOV
124
        hostUrl = hostUrl[hostUrl.length - 1] === '/' ? hostUrl : `${hostUrl}/`;
×
UNCOV
125
        Array.from(
×
126
            new Set(features
UNCOV
127
                .map((feature) => hostUrlToFeatureUrl[feature](
×
128
                    hostUrl,
129
                    getQueryParamString(prefetchFlags),
130
                ))),
131
        )
132
            .forEach(
133
                (prefetchUrl, index) => {
UNCOV
134
                    const iFrame = document.createElement('iframe');
×
UNCOV
135
                    iFrame.src = prefetchUrl;
×
UNCOV
136
                    iFrame.style.width = '0';
×
UNCOV
137
                    iFrame.style.height = '0';
×
UNCOV
138
                    iFrame.style.border = '0';
×
UNCOV
139
                    iFrame.classList.add('prefetchIframe');
×
UNCOV
140
                    iFrame.classList.add(`prefetchIframeNum-${index}`);
×
UNCOV
141
                    document.body.appendChild(iFrame);
×
142
                },
143
            );
144
    }
145
};
146

147
/**
148
 *
149
 * @param embedConfig
150
 */
151
function sanity(embedConfig: EmbedConfig) {
UNCOV
152
    if (embedConfig.thoughtSpotHost === undefined) {
×
UNCOV
153
        throw new Error('ThoughtSpot host not provided');
×
154
    }
UNCOV
155
    if (embedConfig.authType === AuthType.TrustedAuthToken) {
×
UNCOV
156
        if (!embedConfig.authEndpoint && typeof embedConfig.getAuthToken !== 'function') {
×
UNCOV
157
            throw new Error('Trusted auth should provide either authEndpoint or getAuthToken');
×
158
        }
159
    }
160
}
161

162
/**
163
 *
164
 * @param embedConfig
165
 */
166
function backwardCompat(embedConfig: EmbedConfig): EmbedConfig {
UNCOV
167
    const newConfig = { ...embedConfig };
×
UNCOV
168
    if (embedConfig.noRedirect !== undefined && embedConfig.inPopup === undefined) {
×
UNCOV
169
        newConfig.inPopup = embedConfig.noRedirect;
×
170
    }
UNCOV
171
    return newConfig;
×
172
}
173

174
/**
175
 * Initializes the Visual Embed SDK globally and perform
176
 * authentication if applicable. This function needs to be called before any ThoughtSpot
177
 * component like Liveboard etc can be embedded. But need not wait for AuthEvent.SUCCESS
178
 * to actually embed. That is handled internally.
179
 * @param embedConfig The configuration object containing ThoughtSpot host,
180
 * authentication mechanism and so on.
181
 * @example
182
 * ```js
183
 *   const authStatus = init({
184
 *     thoughtSpotHost: 'https://my.thoughtspot.cloud',
185
 *     authType: AuthType.None,
186
 *   });
187
 *   authStatus.on(AuthStatus.FAILURE, (reason) => { // do something here });
188
 * ```
189
 * @returns {@link AuthEventEmitter} event emitter which emits events on authentication success,
190
 *      failure and logout. See {@link AuthStatus}
191
 * @version SDK: 1.0.0 | ThoughtSpot ts7.april.cl, 7.2.1
192
 * @group Authentication / Init
193
 */
194
export const init = (embedConfig: EmbedConfig) => {
16✔
195
    // sanity(embedConfig);
196
    // resetCachedAuthToken();
197
    // embedConfig = setEmbedConfig(
198
    //     backwardCompat({
199
    //         ...CONFIG_DEFAULTS,
200
    //         ...embedConfig,
201
    //         thoughtSpotHost: getThoughtSpotHost(embedConfig),
202
    //     }),
203
    // );
204
    const x = 'shillooe-olio';
1✔
205
    // setGlobalLogLevelOverride(embedConfig.logLevel);
206
    // registerReportingObserver();
207

208
    // const authEE = new EventEmitter<AuthStatus | AuthEvent>();
209
    // setAuthEE(authEE);
210
    // handleAuth();
211

212
    // const { password, ...configToTrack } = getEmbedConfig();
213
    // uploadMixpanelEvent(MIXPANEL_EVENT.VISUAL_SDK_CALLED_INIT, {
214
    //     ...configToTrack,
215
    //     usedCustomizationSheet: embedConfig.customizations?.style?.customCSSUrl != null,
216
    //     usedCustomizationVariables:
217
    //          embedConfig.customizations?.style?.customCSS?.variables != null,
218
    //     usedCustomizationRules:
219
    //         embedConfig.customizations?.style?.customCSS?.rules_UNSTABLE != null,
220
    //     usedCustomizationStrings: !!embedConfig.customizations?.content?.strings,
221
    //     usedCustomizationIconSprite: !!embedConfig.customizations?.iconSpriteUrl,
222
    // });
223

224
    // if (getEmbedConfig().callPrefetch) {
225
    //     prefetch(getEmbedConfig().thoughtSpotHost);
226
    // }
227
    return x;
1✔
228
};
229

230
/**
231
 *
232
 */
233
export function disableAutoLogin(): void {
16✔
UNCOV
234
    getEmbedConfig().autoLogin = false;
×
235
}
236

237
/**
238
 * Logs out from ThoughtSpot. This also sets the autoLogin flag to false, to
239
 * prevent the SDK from automatically logging in again.
240
 *
241
 * You can call the `init` method again to re login, if autoLogin is set to
242
 * true in this second call it will be honored.
243
 * @param doNotDisableAutoLogin This flag when passed will not disable autoLogin
244
 * @returns Promise which resolves when logout completes.
245
 * @version SDK: 1.10.1 | ThoughtSpot: 8.2.0.cl, 8.4.1-sw
246
 * @group Global methods
247
 */
248
export const logout = (doNotDisableAutoLogin = false): Promise<boolean> => {
16!
UNCOV
249
    if (!doNotDisableAutoLogin) {
×
UNCOV
250
        disableAutoLogin();
×
251
    }
UNCOV
252
    return _logout(getEmbedConfig()).then((isLoggedIn) => {
×
UNCOV
253
        notifyLogout();
×
UNCOV
254
        return isLoggedIn;
×
255
    });
256
};
257

258
let renderQueue: Promise<any> = Promise.resolve();
16✔
259

260
/**
261
 * Renders functions in a queue, resolves to next function only after the callback next
262
 * is called
263
 * @param fn The function being registered
264
 */
265
export const renderInQueue = (fn: (next?: (val?: any) => void) => Promise<any>): Promise<any> => {
16✔
UNCOV
266
    const { queueMultiRenders = false } = getEmbedConfig();
×
UNCOV
267
    if (queueMultiRenders) {
×
268
        renderQueue = renderQueue.then(() => new Promise((res) => fn(res)));
×
269
        return renderQueue;
×
270
    }
271
    // Sending an empty function to keep it consistent with the above usage.
UNCOV
272
    return fn(() => {}); // eslint-disable-line @typescript-eslint/no-empty-function
×
273
};
274

275
/**
276
 * Imports TML representation of the metadata objects into ThoughtSpot.
277
 * @param data
278
 * @returns imports TML data into ThoughtSpot
279
 * @example
280
 * ```js
281
 *  executeTML({
282
 * //Array of metadata Tmls in string format
283
 *      metadata_tmls: [
284
 *          "'\''{\"guid\":\"9bd202f5-d431-44bf-9a07-b4f7be372125\",
285
 *          \"liveboard\":{\"name\":\"Parameters Liveboard\"}}'\''"
286
 *      ],
287
 *      import_policy: 'PARTIAL', // Specifies the import policy for the TML import.
288
 *      create_new: false, // If selected, creates TML objects with new GUIDs.
289
 *  }).then(result => {
290
 *      console.log(result);
291
 *  }).catch(error => {
292
 *      console.error(error);
293
 *  });
294
 *```
295
 * @version SDK: 1.23.0 | ThoughtSpot: 9.4.0.cl
296
 * @group Global methods
297
 */
298
export const executeTML = async (data: executeTMLInput): Promise<any> => {
16✔
UNCOV
299
    try {
×
UNCOV
300
        sanity(getEmbedConfig());
×
301
    } catch (err) {
UNCOV
302
        return Promise.reject(err);
×
303
    }
304

UNCOV
305
    const { thoughtSpotHost, authType } = getEmbedConfig();
×
UNCOV
306
    const headers: Record<string, string | undefined> = {
×
307
        'Content-Type': 'application/json',
308
        'x-requested-by': 'ThoughtSpot',
309
    };
310

UNCOV
311
    const payload = {
×
312
        metadata_tmls: data.metadata_tmls,
313
        import_policy: data.import_policy || 'PARTIAL',
×
314
        create_new: data.create_new || false,
×
315
    };
UNCOV
316
    return tokenizedFetch(`${thoughtSpotHost}${EndPoints.EXECUTE_TML}`, {
×
317
        method: 'POST',
318
        headers,
319
        body: JSON.stringify(payload),
320
        credentials: 'include',
321
    })
322
        .then((response) => {
UNCOV
323
            if (!response.ok) {
×
324
                throw new Error(
×
325
                    `Failed to import TML data: ${response.status} - ${response.statusText}`,
326
                );
327
            }
UNCOV
328
            return response.json();
×
329
        })
330
        .catch((error) => {
UNCOV
331
            throw error;
×
332
        });
333
};
334

335
/**
336
 * Exports TML representation of the metadata objects from ThoughtSpot in JSON or YAML
337
 * format.
338
 * @param data
339
 * @returns exports TML data
340
 * @example
341
 * ```js
342
 * exportTML({
343
 *   metadata: [
344
 *     {
345
 *       type: "LIVEBOARD", //Metadata Type
346
 *       identifier: "9bd202f5-d431-44bf-9a07-b4f7be372125" //Metadata Id
347
 *     }
348
 *   ],
349
 *   export_associated: false,//indicates whether to export associated metadata objects
350
 *   export_fqn: false, //Adds FQNs of the referenced objects.For example, if you are
351
 *                      //exporting a Liveboard and its associated objects, the API
352
 *                      //returns the Liveboard TML data with the FQNs of the referenced
353
 *                      //worksheet. If the exported TML data includes FQNs, you don't need
354
 *                      //to manually add FQNs of the referenced objects during TML import.
355
 *   edoc_format: "JSON" //It takes JSON or YAML value
356
 * }).then(result => {
357
 *   console.log(result);
358
 * }).catch(error => {
359
 *   console.error(error);
360
 * });
361
 * ```
362
 * @version SDK: 1.23.0 | ThoughtSpot: 9.4.0.cl
363
 * @group Global methods
364
 */
365
export const exportTML = async (data: exportTMLInput): Promise<any> => {
16✔
UNCOV
366
    const { thoughtSpotHost, authType } = getEmbedConfig();
×
UNCOV
367
    try {
×
UNCOV
368
        sanity(getEmbedConfig());
×
369
    } catch (err) {
370
        return Promise.reject(err);
×
371
    }
UNCOV
372
    const payload = {
×
373
        metadata: data.metadata,
374
        export_associated: data.export_associated || false,
×
375
        export_fqn: data.export_fqn || false,
×
376
        edoc_format: data.edoc_format || 'YAML',
×
377
    };
378

UNCOV
379
    const headers: Record<string, string | undefined> = {
×
380
        'Content-Type': 'application/json',
381
        'x-requested-by': 'ThoughtSpot',
382
    };
383

UNCOV
384
    return tokenizedFetch(`${thoughtSpotHost}${EndPoints.EXPORT_TML}`, {
×
385
        method: 'POST',
386
        headers,
387
        body: JSON.stringify(payload),
388
        credentials: 'include',
389
    })
390
        .then((response) => {
UNCOV
391
            if (!response.ok) {
×
392
                throw new Error(
×
393
                    `Failed to export TML: ${response.status} - ${response.statusText}`,
394
                );
395
            }
UNCOV
396
            return response.json();
×
397
        })
398
        .catch((error) => {
UNCOV
399
            throw error;
×
400
        });
401
};
402

403
// For testing purposes only
404
/**
405
 *
406
 */
407
export function reset(): void {
16✔
UNCOV
408
    setEmbedConfig({} as any);
×
UNCOV
409
    setAuthEE(null);
×
UNCOV
410
    authPromise = null;
×
411
}
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