@thoughtspot/visual-embed-sdk 1.52.2-beta.0 → 1.52.2-beta.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 (36) hide show
  1. package/cjs/package.json +1 -1
  2. package/cjs/src/embed/liveboard.d.ts +13 -15
  3. package/cjs/src/embed/liveboard.d.ts.map +1 -1
  4. package/cjs/src/embed/liveboard.js +31 -3
  5. package/cjs/src/embed/liveboard.js.map +1 -1
  6. package/cjs/src/embed/liveboard.spec.js +79 -0
  7. package/cjs/src/embed/liveboard.spec.js.map +1 -1
  8. package/cjs/src/embed/ts-embed.spec.d.ts.map +1 -1
  9. package/cjs/src/embed/ts-embed.spec.js +10 -15
  10. package/cjs/src/embed/ts-embed.spec.js.map +1 -1
  11. package/dist/{index-BrJMbt8r.js → index-CnHREfvN.js} +1 -1
  12. package/dist/src/embed/liveboard.d.ts +13 -15
  13. package/dist/src/embed/liveboard.d.ts.map +1 -1
  14. package/dist/src/embed/ts-embed.spec.d.ts.map +1 -1
  15. package/dist/tsembed-react.es.js +72 -6
  16. package/dist/tsembed-react.js +71 -5
  17. package/dist/tsembed.es.js +72 -6
  18. package/dist/tsembed.js +71 -5
  19. package/dist/visual-embed-sdk-react-full.d.ts +13 -0
  20. package/dist/visual-embed-sdk-react.d.ts +13 -0
  21. package/dist/visual-embed-sdk.d.ts +13 -15
  22. package/lib/package.json +1 -1
  23. package/lib/src/embed/liveboard.d.ts +13 -15
  24. package/lib/src/embed/liveboard.d.ts.map +1 -1
  25. package/lib/src/embed/liveboard.js +30 -3
  26. package/lib/src/embed/liveboard.js.map +1 -1
  27. package/lib/src/embed/liveboard.spec.js +79 -0
  28. package/lib/src/embed/liveboard.spec.js.map +1 -1
  29. package/lib/src/embed/ts-embed.spec.d.ts.map +1 -1
  30. package/lib/src/embed/ts-embed.spec.js +10 -15
  31. package/lib/src/embed/ts-embed.spec.js.map +1 -1
  32. package/lib/src/visual-embed-sdk.d.ts +13 -15
  33. package/package.json +1 -1
  34. package/src/embed/liveboard.spec.ts +92 -0
  35. package/src/embed/liveboard.ts +36 -3
  36. package/src/embed/ts-embed.spec.ts +11 -18
@@ -15672,21 +15672,6 @@ export interface LiveboardViewConfig extends BaseViewConfig, FullHeightViewConfi
15672
15672
  */
15673
15673
  updatedSpotterExperience?: boolean;
15674
15674
  }
15675
- /**
15676
- * Embed a ThoughtSpot Liveboard or visualization. When rendered it already
15677
- * waits for the authentication to complete, so you need not wait for
15678
- * `AuthStatus.SUCCESS`.
15679
- * @group Embed components
15680
- * @example
15681
- * ```js
15682
- * import { .. } from '@thoughtspot/visual-embed-sdk';
15683
- * init({ ... });
15684
- * const embed = new LiveboardEmbed("#container", {
15685
- * liveboardId: <your-id-here>,
15686
- * // .. other params here.
15687
- * })
15688
- * ```
15689
- */
15690
15675
  export class LiveboardEmbed extends V1Embed {
15691
15676
  protected viewConfig: LiveboardViewConfig;
15692
15677
  private readonly fullHeightController?;
@@ -15718,6 +15703,19 @@ export class LiveboardEmbed extends V1Embed {
15718
15703
  personalizedViewId: string;
15719
15704
  };
15720
15705
  protected beforePrerenderVisible(): void;
15706
+ /**
15707
+ * Whether `previous` was showing what this instance is about to show.
15708
+ *
15709
+ * Undefined-valued keys are dropped before comparing. `navigateToLiveboard`
15710
+ * assigns `vizId`, `activeTabId` and `personalizedViewId` unconditionally,
15711
+ * so an instance that has been shown once carries those keys as `undefined`
15712
+ * while a freshly constructed one does not have them at all. `isEqual`
15713
+ * counts own keys, so without this the comparison can never be true after
15714
+ * the first show — which is the whole case this exists for.
15715
+ *
15716
+ * Callbacks compare as equal: React passes a fresh closure per render.
15717
+ */
15718
+ private isSameEmbedConfig;
15721
15719
  private isShowingLiveboardRoute;
15722
15720
  protected handleRenderForPrerender(): Promise<TsEmbed>;
15723
15721
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thoughtspot/visual-embed-sdk",
3
- "version": "1.52.2-beta.0",
3
+ "version": "1.52.2-beta.2",
4
4
  "description": "ThoughtSpot Embed SDK",
5
5
  "module": "lib/src/index.js",
6
6
  "main": "dist/tsembed.js",
@@ -2748,6 +2748,98 @@ describe('Liveboard/viz embed tests', () => {
2748
2748
  expect(navigateToLiveboardSpy).toHaveBeenCalled();
2749
2749
  });
2750
2750
 
2751
+ test('should not route via home when a new instance shows an equal config', async () => {
2752
+ const viewConfig = {
2753
+ liveboardId,
2754
+ vizId,
2755
+ activeTabId,
2756
+ ...defaultViewConfig,
2757
+ };
2758
+ const previous = new LiveboardEmbed(getRootEl(), { ...viewConfig, beforeRender: () => {} } as any);
2759
+ const liveboardEmbed = new LiveboardEmbed(getRootEl(), { ...viewConfig, beforeRender: () => {} } as any);
2760
+
2761
+ // React constructs a new embed on every mount.
2762
+ jest.spyOn(liveboardEmbed as any, 'getPreRenderObj').mockReturnValue(previous);
2763
+ const triggerSpy = jest
2764
+ .spyOn(liveboardEmbed, 'trigger')
2765
+ .mockImplementation(() => Promise.resolve(undefined as any));
2766
+ const navigateToLiveboardSpy = jest
2767
+ .spyOn(liveboardEmbed, 'navigateToLiveboard')
2768
+ .mockImplementation(() => Promise.resolve(undefined));
2769
+
2770
+ liveboardEmbed.isEmbedContainerLoaded = false;
2771
+ liveboardEmbed['beforePrerenderVisible']();
2772
+ liveboardEmbed.isEmbedContainerLoaded = true;
2773
+ liveboardEmbed['executeEmbedContainerReadyCallbacks']();
2774
+ await waitForPreRenderNavigate();
2775
+
2776
+ expect(triggerSpy).not.toHaveBeenCalledWith(HostEvent.Navigate, 'home');
2777
+ expect(navigateToLiveboardSpy).toHaveBeenCalled();
2778
+ });
2779
+
2780
+ test('should not route via home when the previous instance has been shown once', async () => {
2781
+ // navigateToLiveboard assigns vizId/activeTabId/personalizedViewId
2782
+ // unconditionally, so a shown instance carries them as undefined
2783
+ // while a fresh one lacks the keys entirely. Comparing raw objects
2784
+ // counts own keys, so it never matches again.
2785
+ const viewConfig = { liveboardId, ...defaultViewConfig };
2786
+ const previous = new LiveboardEmbed(getRootEl(), { ...viewConfig } as any);
2787
+ (previous as any).viewConfig.vizId = undefined;
2788
+ (previous as any).viewConfig.activeTabId = undefined;
2789
+ (previous as any).viewConfig.personalizedViewId = undefined;
2790
+
2791
+ const liveboardEmbed = new LiveboardEmbed(getRootEl(), { ...viewConfig } as any);
2792
+
2793
+ jest.spyOn(liveboardEmbed as any, 'getPreRenderObj').mockReturnValue(previous);
2794
+ const triggerSpy = jest
2795
+ .spyOn(liveboardEmbed, 'trigger')
2796
+ .mockImplementation(() => Promise.resolve(undefined as any));
2797
+ jest
2798
+ .spyOn(liveboardEmbed, 'navigateToLiveboard')
2799
+ .mockImplementation(() => Promise.resolve(undefined));
2800
+
2801
+ liveboardEmbed.isEmbedContainerLoaded = false;
2802
+ liveboardEmbed['beforePrerenderVisible']();
2803
+ liveboardEmbed.isEmbedContainerLoaded = true;
2804
+ liveboardEmbed['executeEmbedContainerReadyCallbacks']();
2805
+ await waitForPreRenderNavigate();
2806
+
2807
+ expect(triggerSpy).not.toHaveBeenCalledWith(HostEvent.Navigate, 'home');
2808
+ });
2809
+
2810
+ test('should route via home when a new instance shows the same route with other filters', async () => {
2811
+ const viewConfig = {
2812
+ liveboardId,
2813
+ vizId,
2814
+ activeTabId,
2815
+ ...defaultViewConfig,
2816
+ };
2817
+ const previous = new LiveboardEmbed(getRootEl(), {
2818
+ ...viewConfig,
2819
+ runtimeFilters: [{ columnName: 'region', operator: RuntimeFilterOp.EQ, values: ['east'] }],
2820
+ });
2821
+ const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
2822
+ ...viewConfig,
2823
+ runtimeFilters: [{ columnName: 'region', operator: RuntimeFilterOp.EQ, values: ['west'] }],
2824
+ });
2825
+
2826
+ jest.spyOn(liveboardEmbed as any, 'getPreRenderObj').mockReturnValue(previous);
2827
+ const triggerSpy = jest
2828
+ .spyOn(liveboardEmbed, 'trigger')
2829
+ .mockImplementation(() => Promise.resolve(undefined as any));
2830
+ jest
2831
+ .spyOn(liveboardEmbed, 'navigateToLiveboard')
2832
+ .mockImplementation(() => Promise.resolve(undefined));
2833
+
2834
+ liveboardEmbed.isEmbedContainerLoaded = false;
2835
+ liveboardEmbed['beforePrerenderVisible']();
2836
+ liveboardEmbed.isEmbedContainerLoaded = true;
2837
+ liveboardEmbed['executeEmbedContainerReadyCallbacks']();
2838
+ await waitForHomeHopNavigate();
2839
+
2840
+ expect(triggerSpy).toHaveBeenCalledWith(HostEvent.Navigate, 'home');
2841
+ });
2842
+
2751
2843
  test('should not route via home when the pre-render is on another liveboard', async () => {
2752
2844
  const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
2753
2845
  liveboardId,
@@ -8,6 +8,7 @@
8
8
  * @author Ayon Ghosh <ayon.ghosh@thoughtspot.com>
9
9
  */
10
10
 
11
+ import isEqualWith from 'lodash/isEqualWith';
11
12
  import { getPreview } from '../utils/graphql/preview-service';
12
13
  import { ERROR_MESSAGE } from '../errors';
13
14
  import {
@@ -562,6 +563,14 @@ export interface LiveboardViewConfig
562
563
  * })
563
564
  * ```
564
565
  */
566
+ /**
567
+ * A shallow copy without the keys whose value is `undefined`, so a key that was
568
+ * assigned `undefined` compares equal to one that was never set.
569
+ */
570
+ const definedEntries = (config: Record<string, any> = {}): Record<string, any> => Object.fromEntries(
571
+ Object.entries(config).filter(([, value]) => value !== undefined),
572
+ );
573
+
565
574
  export class LiveboardEmbed extends V1Embed {
566
575
  protected viewConfig: LiveboardViewConfig;
567
576
 
@@ -955,10 +964,12 @@ export class LiveboardEmbed extends V1Embed {
955
964
  super.beforePrerenderVisible();
956
965
 
957
966
  // Captured before showPreRender() hands the wrapper over to this
958
- // instance. Itself means a hide/show, not a hand-over, so there is
959
- // nothing stale to clear.
967
+ // instance. React builds a new instance per mount, so an equal config
968
+ // is a hide/show, not a hand-over: there is nothing stale to clear.
960
969
  const previous = this.getPreRenderObj<LiveboardEmbed>();
961
- const showing = previous === this ? undefined : previous?.currentLiveboardState;
970
+ const showing = this.isSameEmbedConfig(previous)
971
+ ? undefined
972
+ : previous?.currentLiveboardState;
962
973
 
963
974
  this.executeAfterEmbedContainerLoaded(async () => {
964
975
  // Without this the params callback suspends on its await and
@@ -992,6 +1003,28 @@ export class LiveboardEmbed extends V1Embed {
992
1003
  });
993
1004
  }
994
1005
 
1006
+ /**
1007
+ * Whether `previous` was showing what this instance is about to show.
1008
+ *
1009
+ * Undefined-valued keys are dropped before comparing. `navigateToLiveboard`
1010
+ * assigns `vizId`, `activeTabId` and `personalizedViewId` unconditionally,
1011
+ * so an instance that has been shown once carries those keys as `undefined`
1012
+ * while a freshly constructed one does not have them at all. `isEqual`
1013
+ * counts own keys, so without this the comparison can never be true after
1014
+ * the first show — which is the whole case this exists for.
1015
+ *
1016
+ * Callbacks compare as equal: React passes a fresh closure per render.
1017
+ */
1018
+ private isSameEmbedConfig(previous?: LiveboardEmbed): boolean {
1019
+ if (!previous) return false;
1020
+ if (previous === this) return true;
1021
+ return isEqualWith(
1022
+ definedEntries(previous.viewConfig),
1023
+ definedEntries(this.viewConfig),
1024
+ (a, b) => (typeof a === 'function' && typeof b === 'function' ? true : undefined),
1025
+ );
1026
+ }
1027
+
995
1028
  // The whole route, not just the liveboard id: the path is built from the
996
1029
  // viz, tab and view too, so a same-liveboard show onto another tab is a
997
1030
  // real navigation.
@@ -71,10 +71,6 @@ import * as processData from '../utils/processData';
71
71
  jest.mock('../utils/processTrigger');
72
72
 
73
73
  const mockProcessTrigger = processTrigger as jest.Mock;
74
-
75
- const flushAnimationFrame = () => new Promise<void>((resolve) => {
76
- requestAnimationFrame(() => resolve());
77
- });
78
74
  const mockHandleInterceptEvent = jest.spyOn(apiIntercept, 'handleInterceptEvent');
79
75
  const defaultViewConfig = {
80
76
  frameParams: {
@@ -2624,9 +2620,6 @@ describe('Unit test case for ts embed', () => {
2624
2620
  contentRect: { height: 297, width: 987 },
2625
2621
  },
2626
2622
  ]);
2627
- // Syncs are coalesced to one per frame.
2628
- await flushAnimationFrame();
2629
-
2630
2623
  expect(preRenderWrapper.style.height).toEqual(`${297}px`);
2631
2624
  expect(preRenderWrapper.style.width).toEqual(`${987}px`);
2632
2625
 
@@ -2887,28 +2880,28 @@ describe('Unit test case for ts embed', () => {
2887
2880
  return placeholder;
2888
2881
  };
2889
2882
 
2890
- it('should stop tracking once the frame is hidden', async () => {
2891
- const { scroller, readPlaceholderTop } = mountInNestedScroller();
2883
+ it('should stop observing the placeholder once the frame is hidden', async () => {
2884
+ const { scroller } = mountInNestedScroller();
2892
2885
 
2893
2886
  const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
2894
- preRenderId: 'scroll-stops-on-hide',
2887
+ preRenderId: 'observer-stops-on-hide',
2895
2888
  liveboardId: 'myLiveboardId',
2896
2889
  });
2897
2890
  libEmbed.preRender();
2898
2891
  await waitFor(() => !!getIFrameEl());
2899
2892
  await libEmbed.showPreRender();
2900
2893
 
2901
- stubPlaceholderRect(libEmbed, readPlaceholderTop);
2902
- libEmbed.hidePreRender();
2894
+ // Size is the only thing the browser will not handle for us, so
2895
+ // the observer is the one live resource a hide has to release.
2896
+ const observer = (libEmbed as any).resizeObserver;
2897
+ expect(observer).toBeDefined();
2898
+ const disconnectSpy = jest.spyOn(observer, 'disconnect');
2903
2899
 
2904
- const syncSpy = jest.spyOn(libEmbed, 'syncPreRenderStyle');
2905
- scroller.scrollTop = 250;
2906
- scroller.dispatchEvent(new Event('scroll'));
2907
- await flushAnimationFrame();
2900
+ libEmbed.hidePreRender();
2908
2901
 
2909
- expect(syncSpy).not.toHaveBeenCalled();
2902
+ expect(disconnectSpy).toHaveBeenCalled();
2910
2903
 
2911
- syncSpy.mockRestore();
2904
+ disconnectSpy.mockRestore();
2912
2905
  libEmbed.destroy();
2913
2906
  scroller.remove();
2914
2907
  });