@thoughtspot/visual-embed-sdk 1.10.0-alpha.0 → 1.10.0-alpha.3

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 (75) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +50 -20
  3. package/dist/src/embed/app.d.ts +13 -1
  4. package/dist/src/embed/base.d.ts +1 -1
  5. package/dist/src/embed/liveboard.d.ts +15 -0
  6. package/dist/src/embed/pinboard.d.ts +91 -0
  7. package/dist/src/embed/ts-embed.d.ts +33 -4
  8. package/dist/src/react/index.d.ts +7 -5
  9. package/dist/src/react/util.d.ts +1 -1
  10. package/dist/src/types.d.ts +201 -7
  11. package/dist/src/utils/plugin.d.ts +0 -0
  12. package/dist/src/utils.d.ts +7 -0
  13. package/dist/src/v1/api.d.ts +19 -0
  14. package/dist/tsembed.es.js +262 -20
  15. package/dist/tsembed.js +262 -20
  16. package/lib/package.json +2 -2
  17. package/lib/src/embed/app.d.ts +13 -1
  18. package/lib/src/embed/app.js +11 -2
  19. package/lib/src/embed/app.js.map +1 -1
  20. package/lib/src/embed/app.spec.js +11 -10
  21. package/lib/src/embed/app.spec.js.map +1 -1
  22. package/lib/src/embed/base.d.ts +1 -1
  23. package/lib/src/embed/base.js +1 -1
  24. package/lib/src/embed/base.spec.js +13 -1
  25. package/lib/src/embed/base.spec.js.map +1 -1
  26. package/lib/src/embed/liveboard.d.ts +15 -0
  27. package/lib/src/embed/liveboard.js +7 -1
  28. package/lib/src/embed/liveboard.js.map +1 -1
  29. package/lib/src/embed/liveboard.spec.js +12 -1
  30. package/lib/src/embed/liveboard.spec.js.map +1 -1
  31. package/lib/src/embed/pinboard.d.ts +91 -0
  32. package/lib/src/embed/pinboard.js +110 -0
  33. package/lib/src/embed/pinboard.js.map +1 -0
  34. package/lib/src/embed/pinboard.spec.js +1 -1
  35. package/lib/src/embed/pinboard.spec.js.map +1 -1
  36. package/lib/src/embed/search.js +2 -1
  37. package/lib/src/embed/search.js.map +1 -1
  38. package/lib/src/embed/ts-embed.d.ts +33 -4
  39. package/lib/src/embed/ts-embed.js +61 -11
  40. package/lib/src/embed/ts-embed.js.map +1 -1
  41. package/lib/src/embed/ts-embed.spec.js +162 -8
  42. package/lib/src/embed/ts-embed.spec.js.map +1 -1
  43. package/lib/src/react/index.d.ts +7 -5
  44. package/lib/src/react/index.js +10 -3
  45. package/lib/src/react/index.js.map +1 -1
  46. package/lib/src/react/index.spec.js +36 -6
  47. package/lib/src/react/index.spec.js.map +1 -1
  48. package/lib/src/react/util.d.ts +1 -1
  49. package/lib/src/react/util.js.map +1 -1
  50. package/lib/src/types.d.ts +201 -7
  51. package/lib/src/types.js +171 -4
  52. package/lib/src/types.js.map +1 -1
  53. package/lib/src/utils/plugin.d.ts +0 -0
  54. package/lib/src/utils/plugin.js +1 -0
  55. package/lib/src/utils/plugin.js.map +1 -0
  56. package/lib/src/utils.d.ts +7 -0
  57. package/lib/src/utils.js +9 -0
  58. package/lib/src/utils.js.map +1 -1
  59. package/lib/src/visual-embed-sdk.d.ts +263 -13
  60. package/package.json +2 -2
  61. package/src/embed/app.spec.ts +11 -10
  62. package/src/embed/app.ts +20 -2
  63. package/src/embed/base.spec.ts +14 -0
  64. package/src/embed/base.ts +1 -1
  65. package/src/embed/liveboard.spec.ts +14 -1
  66. package/src/embed/liveboard.ts +24 -0
  67. package/src/embed/pinboard.spec.ts +1 -1
  68. package/src/embed/search.ts +4 -1
  69. package/src/embed/ts-embed.spec.ts +228 -8
  70. package/src/embed/ts-embed.ts +97 -13
  71. package/src/react/index.spec.tsx +52 -5
  72. package/src/react/index.tsx +39 -22
  73. package/src/react/util.ts +2 -1
  74. package/src/types.ts +211 -5
  75. package/src/utils.ts +14 -0
@@ -11,6 +11,8 @@ import {
11
11
  getEncodedQueryParamsString,
12
12
  getCssDimension,
13
13
  getOffsetTop,
14
+ embedEventStatus,
15
+ setAttributes,
14
16
  } from '../utils';
15
17
  import {
16
18
  getThoughtSpotHost,
@@ -28,6 +30,8 @@ import {
28
30
  RuntimeFilter,
29
31
  Param,
30
32
  EmbedConfig,
33
+ MessageOptions,
34
+ MessageCallbackObj,
31
35
  } from '../types';
32
36
  import { uploadMixpanelEvent, MIXPANEL_EVENT } from '../mixpanel-service';
33
37
  import { getProcessData } from '../utils/processData';
@@ -37,6 +41,11 @@ import { getAuthPromise, getEmbedConfig, renderInQueue } from './base';
37
41
 
38
42
  const { version } = pkgInfo;
39
43
 
44
+ /**
45
+ * Global prefix for all Thoughtspot postHash Params.
46
+ */
47
+ export const THOUGHTSPOT_PARAM_PREFIX = 'ts-';
48
+
40
49
  /**
41
50
  * The event id map from v2 event names to v1 event id
42
51
  * v1 events are the classic embed events implemented in Blink v1
@@ -62,6 +71,11 @@ export interface FrameParams {
62
71
  * The height of the iFrame (unit is pixels if numeric).
63
72
  */
64
73
  height?: number | string;
74
+ /**
75
+ * This parameters will be passed on the iframe
76
+ * as is.
77
+ */
78
+ [key: string]: string | number | boolean;
65
79
  }
66
80
 
67
81
  /**
@@ -105,15 +119,28 @@ export interface ViewConfig {
105
119
  * @version 1.6.0 or later
106
120
  */
107
121
  visibleActions?: Action[];
122
+ /**
123
+ * Show alert messages and toast messages in the embedded view.
124
+ * @version 1.11.0 | ThoughtSpot: 8.3.0.cl
125
+ */
126
+ showAlerts?: boolean;
108
127
  /**
109
128
  * The list of runtime filters to apply to a search answer,
110
129
  * visualization, or Liveboard.
111
130
  */
112
131
  runtimeFilters?: RuntimeFilter[];
132
+ /**
133
+ * The locale/language to use for the embedded view.
134
+ * @version 1.9.4 or later
135
+ */
136
+ locale?: string;
113
137
  /**
114
138
  * This is an object (key/val) of override flags which will be applied
115
139
  * to the internal embedded object. This can be used to add any
116
140
  * URL flag.
141
+ * Warning: This option is for advanced use only and is used internally
142
+ * to control embed behavior in non-regular ways. We do not publish the
143
+ * list of supported keys and values associated with each.
117
144
  * @version SDK: 1.9.0 | ThoughtSpot: 8.1.0.cl
118
145
  */
119
146
  additionalFlags?: { [key: string]: string | number | boolean };
@@ -155,7 +182,7 @@ export class TsEmbed {
155
182
  * by the embedded app; multiple event handlers can be registered
156
183
  * against a particular message type.
157
184
  */
158
- private eventHandlerMap: Map<string, MessageCallback[]>;
185
+ private eventHandlerMap: Map<string, MessageCallbackObj[]>;
159
186
 
160
187
  /**
161
188
  * A flag that is set to true post render.
@@ -324,7 +351,12 @@ export class TsEmbed {
324
351
  queryParams[Param.ViewPortHeight] = window.innerHeight;
325
352
  queryParams[Param.ViewPortWidth] = window.innerWidth;
326
353
  queryParams[Param.Version] = version;
327
-
354
+ if (
355
+ this.embedConfig.disableLoginRedirect === true ||
356
+ this.embedConfig.autoLogin === true
357
+ ) {
358
+ queryParams[Param.DisableLoginRedirect] = true;
359
+ }
328
360
  if (this.embedConfig.customCssUrl) {
329
361
  queryParams[Param.CustomCSSUrl] = this.embedConfig.customCssUrl;
330
362
  }
@@ -334,7 +366,9 @@ export class TsEmbed {
334
366
  disabledActionReason,
335
367
  hiddenActions,
336
368
  visibleActions,
369
+ showAlerts,
337
370
  additionalFlags,
371
+ locale,
338
372
  } = this.viewConfig;
339
373
 
340
374
  if (Array.isArray(visibleActions) && Array.isArray(hiddenActions)) {
@@ -356,6 +390,12 @@ export class TsEmbed {
356
390
  if (Array.isArray(visibleActions)) {
357
391
  queryParams[Param.VisibleActions] = visibleActions;
358
392
  }
393
+ if (showAlerts !== undefined) {
394
+ queryParams[Param.ShowAlerts] = showAlerts;
395
+ }
396
+ if (locale !== undefined) {
397
+ queryParams[Param.Locale] = locale;
398
+ }
359
399
  if (additionalFlags && additionalFlags.constructor.name === 'Object') {
360
400
  Object.assign(queryParams, additionalFlags);
361
401
  }
@@ -399,7 +439,7 @@ export class TsEmbed {
399
439
  * @param url
400
440
  * @param frameOptions
401
441
  */
402
- protected renderIFrame(url: string, frameOptions: FrameParams): void {
442
+ protected renderIFrame(url: string, frameOptions: FrameParams = {}): void {
403
443
  if (this.isError) {
404
444
  return;
405
445
  }
@@ -441,12 +481,19 @@ export class TsEmbed {
441
481
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
442
482
  // @ts-ignore
443
483
  this.iFrame.mozallowfullscreen = true;
484
+ const {
485
+ height: frameHeight,
486
+ width: frameWidth,
487
+ ...restParams
488
+ } = frameOptions;
444
489
  const width = getCssDimension(
445
- frameOptions?.width || DEFAULT_EMBED_WIDTH,
490
+ frameWidth || DEFAULT_EMBED_WIDTH,
446
491
  );
447
492
  const height = getCssDimension(
448
- frameOptions?.height || DEFAULT_EMBED_HEIGHT,
493
+ frameWidth || DEFAULT_EMBED_HEIGHT,
449
494
  );
495
+ setAttributes(this.iFrame, restParams);
496
+
450
497
  this.iFrame.style.width = `${width}`;
451
498
  this.iFrame.style.height = `${height}`;
452
499
  this.iFrame.style.border = '0';
@@ -511,11 +558,19 @@ export class TsEmbed {
511
558
  eventPort?: MessagePort | void,
512
559
  ): void {
513
560
  const callbacks = this.eventHandlerMap.get(eventType) || [];
514
- callbacks.forEach((callback) =>
515
- callback(data, (payload) => {
516
- this.triggerEventOnPort(eventPort, payload);
517
- }),
518
- );
561
+ const dataStatus = data?.status || embedEventStatus.END;
562
+ callbacks.forEach((callbackObj) => {
563
+ if (
564
+ (callbackObj.options.start &&
565
+ dataStatus === embedEventStatus.START) || // When start status is true it trigger only start releated payload
566
+ (!callbackObj.options.start &&
567
+ dataStatus === embedEventStatus.END) // When start status is false it trigger only end releated payload
568
+ ) {
569
+ callbackObj.callback(data, (payload) => {
570
+ this.triggerEventOnPort(eventPort, payload);
571
+ });
572
+ }
573
+ });
519
574
  }
520
575
 
521
576
  /**
@@ -582,20 +637,21 @@ export class TsEmbed {
582
637
  * sends an event of a particular message type to the host application.
583
638
  *
584
639
  * @param messageType The message type
585
- * @param callback A callback function
640
+ * @param callback A callback as a function
641
+ * @param options The message options
586
642
  */
587
643
  public on(
588
644
  messageType: EmbedEvent,
589
645
  callback: MessageCallback,
646
+ options: MessageOptions = { start: false },
590
647
  ): typeof TsEmbed.prototype {
591
648
  if (this.isRendered) {
592
649
  this.handleError(
593
650
  'Please register event handlers before calling render',
594
651
  );
595
652
  }
596
-
597
653
  const callbacks = this.eventHandlerMap.get(messageType) || [];
598
- callbacks.push(callback);
654
+ callbacks.push({ options, callback });
599
655
  this.eventHandlerMap.set(messageType, callbacks);
600
656
  return this;
601
657
  }
@@ -649,6 +705,34 @@ export class TsEmbed {
649
705
 
650
706
  return this;
651
707
  }
708
+
709
+ /**
710
+ * Get the Post Url Params for THOUGHTSPOT from the current
711
+ * host app URL.
712
+ * THOUGHTSPOT URL params starts with a prefix "ts-"
713
+ */
714
+ public getThoughtSpotPostUrlParams(): string {
715
+ const urlHash = window.location.hash;
716
+ const queryParams = window.location.search;
717
+ const postHashParams = urlHash.split('?');
718
+ const postURLParams = postHashParams[postHashParams.length - 1];
719
+ const queryParamsObj = new URLSearchParams(queryParams);
720
+ const postURLParamsObj = new URLSearchParams(postURLParams);
721
+ const params = new URLSearchParams();
722
+
723
+ const addKeyValuePairCb = (value: string, key: string): void => {
724
+ if (key.startsWith(THOUGHTSPOT_PARAM_PREFIX)) {
725
+ params.append(key, value);
726
+ }
727
+ };
728
+ queryParamsObj.forEach(addKeyValuePairCb);
729
+ postURLParamsObj.forEach(addKeyValuePairCb);
730
+
731
+ let tsParams = params.toString();
732
+ tsParams = tsParams ? `?${tsParams}` : '';
733
+
734
+ return tsParams;
735
+ }
652
736
  }
653
737
 
654
738
  /**
@@ -3,13 +3,14 @@ import '@testing-library/jest-dom';
3
3
  import '@testing-library/jest-dom/extend-expect';
4
4
  import { cleanup, fireEvent, render, waitFor } from '@testing-library/react';
5
5
  import {
6
+ executeAfterWait,
6
7
  getIFrameEl,
7
8
  getIFrameSrc,
8
9
  postMessageToParent,
9
10
  } from '../test/test-utils';
10
- import { SearchEmbed, AppEmbed, PinboardEmbed } from './index';
11
+ import { SearchEmbed, AppEmbed, LiveboardEmbed, useEmbedRef } from './index';
11
12
  import { AuthType, init } from '../index';
12
- import { EmbedEvent } from '../types';
13
+ import { EmbedEvent, HostEvent } from '../types';
13
14
  import { version } from '../../package.json';
14
15
 
15
16
  const thoughtSpotHost = 'localhost';
@@ -25,17 +26,22 @@ describe('React Components', () => {
25
26
  describe('SearchEmbed', () => {
26
27
  it('Should Render the Iframe with props', async () => {
27
28
  const { container } = render(
28
- <SearchEmbed hideDataSources={true} />,
29
+ <SearchEmbed hideDataSources={true} className="embedClass" />,
29
30
  );
30
31
 
31
32
  await waitFor(() => getIFrameEl(container));
32
33
 
34
+ expect(
35
+ getIFrameEl(container).parentElement.classList.contains(
36
+ 'embedClass',
37
+ ),
38
+ ).toBe(true);
33
39
  expect(getIFrameSrc(container)).toBe(
34
40
  `http://${thoughtSpotHost}/?hostAppUrl=local-host&viewPortHeight=768&viewPortWidth=1024&sdkVersion=${version}&hideAction=[%22editACopy%22,%22saveAsView%22,%22updateTSL%22,%22editTSL%22,%22onDeleteAnswer%22]&dataSourceMode=hide&useLastSelectedSources=false&isSearchEmbed=true#/embed/answer`,
35
41
  );
36
42
  });
37
43
 
38
- it('Should attach event listeners', async () => {
44
+ it('Should attach event listeners', async (done) => {
39
45
  const userGUID = 'absfdfgd';
40
46
  const { container } = render(
41
47
  <SearchEmbed
@@ -44,6 +50,7 @@ describe('React Components', () => {
44
50
  }}
45
51
  onAuthInit={(e) => {
46
52
  expect(e.data.userGUID).toEqual(userGUID);
53
+ done();
47
54
  }}
48
55
  />,
49
56
  );
@@ -63,7 +70,47 @@ describe('React Components', () => {
63
70
  //
64
71
  });
65
72
 
66
- describe('PinboardEmbed', () => {
73
+ describe('LiveboardEmbed', () => {
67
74
  //
75
+ it('Should be able to trigger events on the embed using refs', async () => {
76
+ const TestComponent = () => {
77
+ const embedRef = useEmbedRef();
78
+ const onLiveboardRendered = () => {
79
+ embedRef.current.trigger(HostEvent.SetVisibleVizs, [
80
+ 'viz1',
81
+ 'viz2',
82
+ ]);
83
+ };
84
+
85
+ return (
86
+ <LiveboardEmbed
87
+ ref={embedRef}
88
+ liveboardId="abcd"
89
+ onLiveboardRendered={onLiveboardRendered}
90
+ />
91
+ );
92
+ };
93
+
94
+ const { container } = render(<TestComponent />);
95
+
96
+ await waitFor(() => getIFrameEl(container));
97
+ const iframe = getIFrameEl(container);
98
+ jest.spyOn(iframe.contentWindow, 'postMessage');
99
+ postMessageToParent(iframe.contentWindow, {
100
+ type: EmbedEvent.LiveboardRendered,
101
+ data: {
102
+ userGUID: 'abcd',
103
+ },
104
+ });
105
+ await executeAfterWait(() => {
106
+ expect(iframe.contentWindow.postMessage).toHaveBeenCalledWith(
107
+ {
108
+ type: HostEvent.SetVisibleVizs,
109
+ data: ['viz1', 'viz2'],
110
+ },
111
+ `http://${thoughtSpotHost}`,
112
+ );
113
+ });
114
+ });
68
115
  });
69
116
  });
@@ -7,7 +7,7 @@ import {
7
7
  } from '../embed/liveboard';
8
8
  import { TsEmbed, ViewConfig } from '../embed/ts-embed';
9
9
 
10
- import { EmbedEvent, MessageCallback } from '../types';
10
+ import { EmbedEvent } from '../types';
11
11
  import { EmbedProps, getViewPropsAndListeners } from './util';
12
12
 
13
13
  const componentFactory = <
@@ -16,28 +16,41 @@ const componentFactory = <
16
16
  V extends ViewConfig
17
17
  >(
18
18
  EmbedConstructor: T,
19
- ) => (props: U) => {
20
- const ref = React.useRef<HTMLDivElement>(null);
21
- const { className, ...embedProps } = props;
22
- const { viewConfig, listeners } = getViewPropsAndListeners<
23
- Omit<U, 'className'>,
24
- V
25
- >(embedProps);
26
- React.useEffect(() => {
27
- const tsEmbed = new EmbedConstructor(ref!.current, {
28
- ...viewConfig,
29
- });
30
- Object.keys(listeners).forEach((eventName) => {
31
- tsEmbed.on(
32
- eventName as EmbedEvent,
33
- listeners[eventName as EmbedEvent],
34
- );
35
- });
36
- tsEmbed.render();
37
- }, [embedProps]);
19
+ ) =>
20
+ React.forwardRef<TsEmbed, U>(
21
+ (props: U, forwardedRef: React.MutableRefObject<TsEmbed>) => {
22
+ const ref = React.useRef<HTMLDivElement>(null);
23
+ const { className, ...embedProps } = props;
24
+ const { viewConfig, listeners } = getViewPropsAndListeners<
25
+ Omit<U, 'className'>,
26
+ V
27
+ >(embedProps);
28
+ React.useEffect(() => {
29
+ const tsEmbed = new EmbedConstructor(ref!.current, {
30
+ ...viewConfig,
31
+ });
32
+ Object.keys(listeners).forEach((eventName) => {
33
+ tsEmbed.on(
34
+ eventName as EmbedEvent,
35
+ listeners[eventName as EmbedEvent],
36
+ );
37
+ });
38
+ tsEmbed.render();
39
+ if (forwardedRef) {
40
+ // eslint-disable-next-line no-param-reassign
41
+ forwardedRef.current = tsEmbed;
42
+ }
43
+ }, [embedProps]);
38
44
 
39
- return <div data-testid="tsEmbed" className={className} ref={ref}></div>;
40
- };
45
+ return (
46
+ <div
47
+ data-testid="tsEmbed"
48
+ ref={ref}
49
+ className={className}
50
+ ></div>
51
+ );
52
+ },
53
+ );
41
54
 
42
55
  interface SearchProps extends EmbedProps, SearchViewConfig {}
43
56
 
@@ -68,3 +81,7 @@ export const PinboardEmbed = componentFactory<
68
81
  LiveboardProps,
69
82
  LiveboardViewConfig
70
83
  >(_LiveboardEmbed);
84
+
85
+ export const useEmbedRef = (): React.MutableRefObject<TsEmbed> => {
86
+ return React.useRef<TsEmbed>(null);
87
+ };
package/src/react/util.ts CHANGED
@@ -2,7 +2,8 @@ import { EmbedEvent, MessageCallback } from '../types';
2
2
  import { ViewConfig } from '../embed/ts-embed';
3
3
 
4
4
  // eslint-disable-next-line prettier/prettier
5
- export type EmbedEventHandlers = { [key in EmbedEvent as `on${Capitalize<key>}`]?: MessageCallback };
5
+ export type EmbedEventHandlers = { [key in keyof typeof EmbedEvent as `on${Capitalize<key>}`]?: MessageCallback };
6
+
6
7
 
7
8
  export interface EmbedProps extends ViewConfig, EmbedEventHandlers {
8
9
  className?: string;