@thoughtspot/visual-embed-sdk 1.21.0-alpha.1 → 1.21.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +1 -1
  2. package/dist/src/auth.d.ts +10 -6
  3. package/dist/src/auth.d.ts.map +1 -1
  4. package/dist/src/auth.spec.d.ts +3 -0
  5. package/dist/src/auth.spec.d.ts.map +1 -1
  6. package/dist/src/embed/base.d.ts +3 -1
  7. package/dist/src/embed/base.d.ts.map +1 -1
  8. package/dist/src/embed/liveboard.d.ts +11 -1
  9. package/dist/src/embed/liveboard.d.ts.map +1 -1
  10. package/dist/src/types.d.ts +67 -19
  11. package/dist/src/types.d.ts.map +1 -1
  12. package/dist/tsembed.es.js +17530 -257
  13. package/dist/tsembed.js +17530 -257
  14. package/lib/package.json +1 -1
  15. package/lib/src/auth.d.ts +10 -6
  16. package/lib/src/auth.d.ts.map +1 -1
  17. package/lib/src/auth.js +25 -10
  18. package/lib/src/auth.js.map +1 -1
  19. package/lib/src/auth.spec.d.ts +3 -0
  20. package/lib/src/auth.spec.d.ts.map +1 -1
  21. package/lib/src/auth.spec.js +23 -1
  22. package/lib/src/auth.spec.js.map +1 -1
  23. package/lib/src/embed/base.d.ts +3 -1
  24. package/lib/src/embed/base.d.ts.map +1 -1
  25. package/lib/src/embed/base.js +3 -1
  26. package/lib/src/embed/base.js.map +1 -1
  27. package/lib/src/embed/liveboard.d.ts +11 -1
  28. package/lib/src/embed/liveboard.d.ts.map +1 -1
  29. package/lib/src/embed/liveboard.js +11 -1
  30. package/lib/src/embed/liveboard.js.map +1 -1
  31. package/lib/src/types.d.ts +67 -19
  32. package/lib/src/types.d.ts.map +1 -1
  33. package/lib/src/types.js +67 -19
  34. package/lib/src/types.js.map +1 -1
  35. package/lib/src/visual-embed-sdk.d.ts +91 -27
  36. package/package.json +1 -1
  37. package/src/auth.spec.ts +40 -8
  38. package/src/auth.ts +43 -16
  39. package/src/embed/base.ts +4 -2
  40. package/src/embed/liveboard.ts +11 -1
  41. package/src/types.ts +69 -21
package/src/auth.spec.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as authInstance from './auth';
2
2
  import * as authService from './utils/authService';
3
3
  import * as checkReleaseVersionInBetaInstance from './utils';
4
+ import * as mixPanelService from './mixpanel-service';
4
5
  import { AuthType, EmbedEvent } from './types';
5
6
  import { executeAfterWait } from './test/test-utils';
6
7
 
@@ -91,6 +92,9 @@ export const embedConfig: any = {
91
92
 
92
93
  const originalWindow = window;
93
94
  export const mockSessionInfo = {
95
+ userGUID: '1234',
96
+ mixpanelToken: 'abc123',
97
+ isPublicUser: false,
94
98
  sessionId: '6588e7d9-710c-453e-a7b4-535fb3a8cbb2',
95
99
  genNo: 3,
96
100
  acSession: {
@@ -110,6 +114,7 @@ describe('Unit test for auth', () => {
110
114
  });
111
115
 
112
116
  test('when session info giving response', async () => {
117
+ jest.spyOn(mixPanelService, 'initMixpanel').mockImplementation(() => Promise.resolve());
113
118
  authInstance.initSession(mockSessionInfo);
114
119
  const sessionInfo = await authInstance.getSessionInfo();
115
120
  expect(sessionInfo).toStrictEqual(mockSessionInfo);
@@ -138,11 +143,17 @@ describe('Unit test for auth', () => {
138
143
  });
139
144
 
140
145
  test('doTokenAuth: when user is loggedIn', async () => {
141
- jest.spyOn(authService, 'fetchSessionInfoService').mockImplementation(async () => ({
142
- json: () => mockSessionInfo,
143
- status: 200,
144
- }));
145
- await authInstance.doTokenAuth(embedConfig.doTokenAuthSuccess('authToken'));
146
+ jest.spyOn(authService, 'fetchSessionInfoService').mockImplementation(
147
+ async () => ({
148
+ json: () => mockSessionInfo,
149
+ status: 200,
150
+ }),
151
+ );
152
+ jest.spyOn(authInstance, 'getSessionDetails').mockReturnValue(mockSessionInfo);
153
+ jest.spyOn(authInstance, 'initSession').mockReturnValue(null);
154
+ await authInstance.doTokenAuth(
155
+ embedConfig.doTokenAuthSuccess('authToken'),
156
+ );
146
157
  expect(authService.fetchSessionInfoService).toBeCalled();
147
158
  expect(authInstance.loggedInStatus).toBe(true);
148
159
  });
@@ -260,14 +271,24 @@ describe('Unit test for auth', () => {
260
271
  });
261
272
 
262
273
  it('when user is loggedIn', async () => {
263
- spyOn(checkReleaseVersionInBetaInstance, 'checkReleaseVersionInBeta');
264
- jest.spyOn(authService, 'fetchSessionInfoService').mockImplementation(async () => ({
274
+ spyOn(
275
+ checkReleaseVersionInBetaInstance,
276
+ 'checkReleaseVersionInBeta',
277
+ );
278
+ jest.spyOn(authInstance, 'getSessionDetails').mockReturnValue(mockSessionInfo);
279
+ jest.spyOn(authInstance, 'initSession').mockReturnValue(null);
280
+ jest.spyOn(
281
+ authService,
282
+ 'fetchSessionInfoService',
283
+ ).mockImplementation(async () => ({
265
284
  json: () => mockSessionInfo,
266
285
  status: 200,
267
286
  }));
268
287
  await authInstance.doBasicAuth(embedConfig.doBasicAuth);
269
288
  expect(authService.fetchSessionInfoService).toBeCalled();
270
289
  expect(authInstance.loggedInStatus).toBe(true);
290
+ expect(authInstance.getSessionDetails).toBeCalled();
291
+ expect(authInstance.initSession).toBeCalled();
271
292
  });
272
293
 
273
294
  it('when user is not loggedIn', async () => {
@@ -304,6 +325,8 @@ describe('Unit test for auth', () => {
304
325
  json: () => mockSessionInfo,
305
326
  status: 200,
306
327
  }));
328
+ jest.spyOn(authInstance, 'getSessionDetails').mockReturnValue(mockSessionInfo);
329
+ jest.spyOn(authInstance, 'initSession').mockReturnValue(null);
307
330
  await authInstance.doSamlAuth(embedConfig.doSamlAuth);
308
331
  expect(authService.fetchSessionInfoService).toBeCalled();
309
332
  expect(window.location.hash).toBe('');
@@ -340,7 +363,14 @@ describe('Unit test for auth', () => {
340
363
  });
341
364
  spyOn(authInstance, 'samlCompletionPromise');
342
365
  global.window.open = jest.fn();
343
- jest.spyOn(authService, 'fetchSessionInfoService').mockImplementation(() => Promise.reject());
366
+ jest.spyOn(authService, 'fetchSessionInfoService')
367
+ .mockImplementationOnce(() => Promise.reject())
368
+ .mockImplementationOnce(async () => ({
369
+ json: () => mockSessionInfo,
370
+ status: 200,
371
+ }));
372
+ jest.spyOn(authInstance, 'getSessionDetails').mockReturnValue(mockSessionInfo);
373
+ jest.spyOn(authInstance, 'initSession').mockReturnValue(null);
344
374
  expect(await authInstance.samlCompletionPromise).not.toBe(null);
345
375
  expect(
346
376
  await authInstance.doSamlAuth({
@@ -351,6 +381,8 @@ describe('Unit test for auth', () => {
351
381
  window.postMessage({ type: EmbedEvent.SAMLComplete }, '*');
352
382
  await authInstance.samlCompletionPromise;
353
383
  expect(authService.fetchSessionInfoService).toBeCalled();
384
+ expect(authInstance.getSessionDetails).toBeCalled();
385
+ expect(authInstance.initSession).toBeCalled();
354
386
  });
355
387
  });
356
388
 
package/src/auth.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import EventEmitter from 'eventemitter3';
2
+ import _ from 'lodash';
2
3
  import { initMixpanel } from './mixpanel-service';
3
4
  import {
4
5
  AuthType, DOMSelector, EmbedConfig, EmbedEvent, Param,
@@ -20,9 +21,9 @@ export let loggedInStatus = false;
20
21
  export let samlAuthWindow: Window = null;
21
22
  // eslint-disable-next-line import/no-mutable-exports
22
23
  export let samlCompletionPromise: Promise<void> = null;
23
- let sessionInfo: any = null;
24
- let sessionInfoResolver: (value: any) => void = null;
25
- const sessionInfoPromise = new Promise((resolve) => {
24
+ let sessionInfo: sessionInfoInterface = null;
25
+ let sessionInfoResolver: (value: sessionInfoInterface) => void = null;
26
+ const sessionInfoPromise = new Promise((resolve:(value: sessionInfoInterface) => void) => {
26
27
  sessionInfoResolver = resolve;
27
28
  });
28
29
  let releaseVersion = '';
@@ -38,6 +39,13 @@ export const EndPoints = {
38
39
  LOGOUT: '/callosum/v1/session/logout',
39
40
  };
40
41
 
42
+ interface sessionInfoInterface {
43
+ userGUID: any;
44
+ isPublicUser: any;
45
+ mixpanelToken: any;
46
+ [key:string]:any;
47
+ }
48
+
41
49
  /**
42
50
  * Enum for auth failure types. This is the parameter passed to the listner
43
51
  * of {@link AuthStatus.FAILURE}.
@@ -196,6 +204,28 @@ export function notifyLogout(): void {
196
204
  authEE.emit(AuthStatus.LOGOUT);
197
205
  }
198
206
 
207
+ export const initSession = (sessionDetails: sessionInfoInterface) => {
208
+ if (_.isNull(sessionInfo)) {
209
+ sessionInfo = sessionDetails;
210
+ initMixpanel(sessionInfo);
211
+ sessionInfoResolver(sessionInfo);
212
+ }
213
+ };
214
+
215
+ export const getSessionDetails = (sessionInfoResp: any):sessionInfoInterface => {
216
+ const devMixpanelToken = sessionInfoResp.configInfo.mixpanelConfig.devSdkKey;
217
+ const prodMixpanelToken = sessionInfoResp.configInfo.mixpanelConfig.prodSdkKey;
218
+ const mixpanelToken = sessionInfoResp.configInfo.mixpanelConfig.production
219
+ ? prodMixpanelToken
220
+ : devMixpanelToken;
221
+ return {
222
+ userGUID: sessionInfoResp.userGUID,
223
+ mixpanelToken,
224
+ isPublicUser: sessionInfoResp.configInfo.isPublicUser,
225
+ ...sessionInfoResp,
226
+ };
227
+ };
228
+
199
229
  /**
200
230
  * Check if we are logged into the ThoughtSpot cluster
201
231
  *
@@ -207,6 +237,9 @@ async function isLoggedIn(thoughtSpotHost: string): Promise<boolean> {
207
237
  try {
208
238
  response = await fetchSessionInfoService(authVerificationUrl);
209
239
  const sessionInfoResp = await response.json();
240
+ const sessionDetails = getSessionDetails(sessionInfoResp);
241
+ // Store user session details from session info
242
+ initSession(sessionDetails);
210
243
  releaseVersion = sessionInfoResp.releaseVersion;
211
244
  } catch (e) {
212
245
  return false;
@@ -227,20 +260,10 @@ export function getReleaseVersion() {
227
260
  *
228
261
  * @group Global methods
229
262
  */
230
- export function getSessionInfo(): Promise<any> {
263
+ export function getSessionInfo(): Promise<sessionInfoInterface> {
231
264
  return sessionInfoPromise;
232
265
  }
233
266
 
234
- /**
235
- *
236
- * @param sessionDetails
237
- */
238
- export function initSession(sessionDetails: any) {
239
- sessionInfo = sessionDetails;
240
- initMixpanel(sessionInfo);
241
- sessionInfoResolver(sessionInfo);
242
- }
243
-
244
267
  const DUPLICATE_TOKEN_ERR = 'Duplicate token, please issue a new token every time getAuthToken callback is called.'
245
268
  + 'See https://developers.thoughtspot.com/docs/?pageid=embed-auth#trusted-auth-embed for more details.';
246
269
  let prevAuthToken: string = null;
@@ -425,8 +448,12 @@ const doSSOAuth = async (embedConfig: EmbedConfig, ssoEndPoint: string): Promise
425
448
 
426
449
  const ssoURL = `${thoughtSpotHost}${ssoEndPoint}`;
427
450
  if (embedConfig.inPopup) {
428
- await samlPopupFlow(ssoURL, embedConfig.authTriggerContainer, embedConfig.authTriggerText);
429
- loggedInStatus = true;
451
+ await samlPopupFlow(
452
+ ssoURL,
453
+ embedConfig.authTriggerContainer,
454
+ embedConfig.authTriggerText,
455
+ );
456
+ loggedInStatus = await isLoggedIn(thoughtSpotHost);
430
457
  return;
431
458
  }
432
459
 
package/src/embed/base.ts CHANGED
@@ -142,7 +142,9 @@ function backwardCompat(embedConfig: EmbedConfig): EmbedConfig {
142
142
 
143
143
  /**
144
144
  * Initializes the Visual Embed SDK globally and perform
145
- * authentication if applicable.
145
+ * authentication if applicable. This function needs to be called before any ThoughtSpot
146
+ * component like liveboard etc can be embedded. But need not wait for AuthEvent.SUCCESS
147
+ * to actually embed. That is handled internally.
146
148
  *
147
149
  * @param embedConfig The configuration object containing ThoughtSpot host,
148
150
  * authentication mechanism and so on.
@@ -232,7 +234,7 @@ export const renderInQueue = (fn: (next?: (val?: any) => void) => Promise<any>):
232
234
  return renderQueue;
233
235
  }
234
236
  // Sending an empty function to keep it consistent with the above usage.
235
- return fn(() => {}); // eslint-disable-line @typescript-eslint/no-empty-function
237
+ return fn(() => { }); // eslint-disable-line @typescript-eslint/no-empty-function
236
238
  };
237
239
 
238
240
  // For testing purposes only
@@ -102,8 +102,18 @@ export interface LiveboardViewConfig extends ViewConfig {
102
102
  }
103
103
 
104
104
  /**
105
- * Embed a ThoughtSpot Liveboard or visualization
105
+ * Embed a ThoughtSpot Liveboard or a Thoughtspot visualization. When rendered it already
106
+ * waits for the authentication to complete, so no need to wait for AuthStatus.SUCCESS.
106
107
  *
108
+ * @example
109
+ * ```js
110
+ * import { .. } from '@thoughtspot/visual-embed-sdk';
111
+ * init({ ... });
112
+ * const embed = new LiveboardEmbed("#container", {
113
+ * liveboardId: <your-id-here>,
114
+ * // .. other params here.
115
+ * })
116
+ * ```
107
117
  * @group Embed components
108
118
  */
109
119
  export class LiveboardEmbed extends V1Embed {
package/src/types.ts CHANGED
@@ -44,8 +44,23 @@ export enum AuthType {
44
44
  SAML = 'SSO_SAML',
45
45
  /**
46
46
  * SSO using SAML
47
- * Will make the host application redirect to the SAML Idp.
47
+ * Will make the host application redirect to the SAML Idp. Use this
48
+ * when the idp does not allow itself to be embedded.
48
49
  *
50
+ * This redirects the host application to the SAML Idp. The host application
51
+ * will be redirected back to the ThoughtSpot app after authentication.
52
+ *
53
+ * @example
54
+ * ```js
55
+ * init({
56
+ * // ...
57
+ * authType: AuthType.SAMLRedirect,
58
+ * });
59
+ * ```
60
+ *
61
+ * This opens the SAML Idp in a popup window. The popup is triggered
62
+ * when the user clicks the trigger button. The popup window will be
63
+ * closed automatically after authentication.
49
64
  * @example
50
65
  * ```js
51
66
  * init({
@@ -53,7 +68,23 @@ export enum AuthType {
53
68
  * authType: AuthType.SAMLRedirect,
54
69
  * authTriggerText: 'Login with SAML',
55
70
  * authTriggerContainer: '#embed-container',
56
- * });
71
+ * inPopup: true,
72
+ * });
73
+ * ```
74
+ *
75
+ * Can also use event to trigger the popup flow. Works the same
76
+ * as above example.
77
+ * @example
78
+ * ```js
79
+ * const authEE = init({
80
+ * // ...
81
+ * authType: AuthType.SAMLRedirect,
82
+ * inPopup: true,
83
+ * });
84
+ *
85
+ * someButtonOnYourPage.addEventListener('click', () => {
86
+ * authEE.emit(AuthEvent.TRIGGER_SSO_POPUP);
87
+ * });
57
88
  * ```
58
89
  */
59
90
  SAMLRedirect = 'SSO_SAML',
@@ -67,6 +98,7 @@ export enum AuthType {
67
98
  /**
68
99
  * SSO using OIDC
69
100
  * Will make the host application redirect to the OIDC Idp.
101
+ * See code samples in {@link SAMLRedirect}.
70
102
  */
71
103
  OIDCRedirect = 'SSO_OIDC',
72
104
  /**
@@ -677,18 +709,17 @@ export enum EmbedEvent {
677
709
  Load = 'load',
678
710
  /**
679
711
  * Data pertaining to answer or Liveboard is received
680
- *
681
- * @returns data - The answer or Liveboard data
712
+ * @return data - The answer or Liveboard data
713
+ * @important
682
714
  */
683
715
  Data = 'data',
684
716
  /**
685
- * Search/answer/Liveboard filters have been applied/updated
686
- *
717
+ * Search/answer/Liveboard filters have been applied/updated by the user.
687
718
  * @hidden
688
719
  */
689
720
  FiltersChanged = 'filtersChanged',
690
721
  /**
691
- * Search query has been updated
722
+ * Search query has been updated by the user.
692
723
  */
693
724
  QueryChanged = 'queryChanged',
694
725
  /**
@@ -720,17 +751,29 @@ export enum EmbedEvent {
720
751
  */
721
752
  CustomAction = 'customAction',
722
753
  /**
723
- * A double click has been triggered on table/chart
724
- *
725
- * @returns ContextMenuInputPoints - data point that is double clicked
754
+ * Listen to double clicks on a visualization
755
+ * @return ContextMenuInputPoints - data point that is double clicked
726
756
  * @version SDK: 1.5.0 | ThoughtSpot: ts7.oct.cl, 7.2.1
727
757
  */
728
758
  VizPointDoubleClick = 'vizPointDoubleClick',
729
759
  /**
730
- * A click has been triggered on table/chart
760
+ * Listen to clicks on a visualization in a liveboard or Search result.
731
761
  *
732
- * @returns ContextMenuInputPoints - data point that is clicked
762
+ * @example
763
+ * ```js
764
+ * embed.on(ThoughtSpotEmbed.Event.VizPointClick, (data) => {
765
+ * console.log(
766
+ * data.vizId, // viz id
767
+ * data.clickedPoint.selectedAttributes[0].value,
768
+ * data.clickedPoint.selectedAttributes[0].column.name,
769
+ * data.clickedPoint.selectedMeasures[0].value,
770
+ * data.clickedPoint.selectedMeasures[0].column.name,
771
+ * )
772
+ * });
773
+ * ```
774
+ * @return {vizId, clickedPoint} - metadata about point that is clicked
733
775
  * @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl, 8.4.1-sw
776
+ * @important
734
777
  */
735
778
  VizPointClick = 'vizPointClick',
736
779
  /**
@@ -850,7 +893,7 @@ export enum EmbedEvent {
850
893
  *
851
894
  * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl, 9.4.0-sw
852
895
  */
853
- DownloadAsPng='downloadAsPng',
896
+ DownloadAsPng = 'downloadAsPng',
854
897
  /**
855
898
  * Emitted when the Download as PDF action is triggered on an answer
856
899
  *
@@ -1034,8 +1077,7 @@ export enum EmbedEvent {
1034
1077
  */
1035
1078
  CrossFilterChanged = 'cross-filter-changed',
1036
1079
  /**
1037
- * Emitted when a user right clicks on chart or table
1038
- *
1080
+ * Emitted when a user right clicks on a visualization (chart or table)
1039
1081
  * @version SDK: 1.21.0 | ThoughtSpot: 9.2.0.cl
1040
1082
  */
1041
1083
  VizPointRightClick = 'vizPointRightClick',
@@ -1126,10 +1168,11 @@ export enum HostEvent {
1126
1168
  * @param - {@link RuntimeFilter}[] an array of {@link RuntimeFilter} Types.
1127
1169
  * @example
1128
1170
  * liveboardEmbed.trigger(HostEvent.UpdateRuntimeFilters, [
1129
- * {columnName: "state",operator: RuntimeFilterOp.EQ,values: ["michigan"]},
1130
- * {columnName: "item type",operator: RuntimeFilterOp.EQ,values:
1131
- * ["Jackets"]} ])
1171
+ * {columnName: "state",operator: RuntimeFilterOp.EQ,values: ["michigan"]},
1172
+ * {columnName: "item type",operator: RuntimeFilterOp.EQ,values: ["Jackets"]}
1173
+ * ])
1132
1174
  * @version SDK: 1.9.0 | ThoughtSpot: 8.1.0.cl, 8.4.1-sw
1175
+ * @important
1133
1176
  */
1134
1177
  UpdateRuntimeFilters = 'UpdateRuntimeFilters',
1135
1178
  /**
@@ -1381,9 +1424,14 @@ export enum HostEvent {
1381
1424
  *
1382
1425
  * @example
1383
1426
  * ```js
1384
- * searchEmbed.trigger(HostEvent.GetTML)
1427
+ * searchEmbed.trigger(HostEvent.GetTML).then((tml) => {
1428
+ * console.log(
1429
+ * tml.search_query // TML representation of the search query
1430
+ * );
1431
+ * })
1385
1432
  * ```
1386
- * @version SDK: 1.18.0 | ThoughtSpot: 8.10.0.cl
1433
+ * @version SDK: 1.18.0 | ThoughtSpot: 8.10.0.cl, 9.0.1-sw
1434
+ * @important
1387
1435
  */
1388
1436
  GetTML = 'getTML',
1389
1437
  /**
@@ -1659,7 +1707,7 @@ export enum Action {
1659
1707
  ReplaySearch = 'replaySearch',
1660
1708
  ShowUnderlyingData = 'showUnderlyingData',
1661
1709
  Download = 'download',
1662
- DownloadAsPng='downloadAsPng',
1710
+ DownloadAsPng = 'downloadAsPng',
1663
1711
  DownloadAsPdf = 'downloadAsPdf',
1664
1712
  DownloadAsCsv = 'downloadAsCSV',
1665
1713
  DownloadAsXlsx = 'downloadAsXLSX',