@thoughtspot/visual-embed-sdk 1.52.2-beta → 1.52.2-beta.1

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 (82) hide show
  1. package/cjs/package.json +2 -2
  2. package/cjs/src/embed/app.js +1 -1
  3. package/cjs/src/embed/app.js.map +1 -1
  4. package/cjs/src/embed/auto-frame-renderer.js +1 -1
  5. package/cjs/src/embed/auto-frame-renderer.js.map +1 -1
  6. package/cjs/src/embed/bodyless-conversation.js +1 -1
  7. package/cjs/src/embed/bodyless-conversation.js.map +1 -1
  8. package/cjs/src/embed/conversation.js +1 -1
  9. package/cjs/src/embed/conversation.js.map +1 -1
  10. package/cjs/src/embed/liveboard.d.ts +1 -0
  11. package/cjs/src/embed/liveboard.d.ts.map +1 -1
  12. package/cjs/src/embed/liveboard.js +16 -4
  13. package/cjs/src/embed/liveboard.js.map +1 -1
  14. package/cjs/src/embed/liveboard.spec.js +54 -0
  15. package/cjs/src/embed/liveboard.spec.js.map +1 -1
  16. package/cjs/src/embed/search-bar.js +1 -1
  17. package/cjs/src/embed/search-bar.js.map +1 -1
  18. package/cjs/src/embed/search.js +1 -1
  19. package/cjs/src/embed/search.js.map +1 -1
  20. package/cjs/src/embed/search.spec.js +0 -20
  21. package/cjs/src/embed/search.spec.js.map +1 -1
  22. package/cjs/src/embed/ts-embed.d.ts +34 -12
  23. package/cjs/src/embed/ts-embed.d.ts.map +1 -1
  24. package/cjs/src/embed/ts-embed.js +98 -142
  25. package/cjs/src/embed/ts-embed.js.map +1 -1
  26. package/cjs/src/embed/ts-embed.spec.d.ts.map +1 -1
  27. package/cjs/src/embed/ts-embed.spec.js +171 -138
  28. package/cjs/src/embed/ts-embed.spec.js.map +1 -1
  29. package/dist/{index-CcBYlGo9.js → index-Bt56pC82.js} +1 -1
  30. package/dist/src/embed/liveboard.d.ts +1 -0
  31. package/dist/src/embed/liveboard.d.ts.map +1 -1
  32. package/dist/src/embed/ts-embed.d.ts +34 -12
  33. package/dist/src/embed/ts-embed.d.ts.map +1 -1
  34. package/dist/src/embed/ts-embed.spec.d.ts.map +1 -1
  35. package/dist/tsembed-react.es.js +161 -155
  36. package/dist/tsembed-react.js +160 -154
  37. package/dist/tsembed.es.js +162 -156
  38. package/dist/tsembed.js +161 -155
  39. package/dist/visual-embed-sdk-react-full.d.ts +35 -12
  40. package/dist/visual-embed-sdk-react.d.ts +35 -12
  41. package/dist/visual-embed-sdk.d.ts +35 -12
  42. package/lib/package.json +2 -2
  43. package/lib/src/embed/app.js +1 -1
  44. package/lib/src/embed/app.js.map +1 -1
  45. package/lib/src/embed/auto-frame-renderer.js +1 -1
  46. package/lib/src/embed/auto-frame-renderer.js.map +1 -1
  47. package/lib/src/embed/bodyless-conversation.js +1 -1
  48. package/lib/src/embed/bodyless-conversation.js.map +1 -1
  49. package/lib/src/embed/conversation.js +1 -1
  50. package/lib/src/embed/conversation.js.map +1 -1
  51. package/lib/src/embed/liveboard.d.ts +1 -0
  52. package/lib/src/embed/liveboard.d.ts.map +1 -1
  53. package/lib/src/embed/liveboard.js +15 -4
  54. package/lib/src/embed/liveboard.js.map +1 -1
  55. package/lib/src/embed/liveboard.spec.js +54 -0
  56. package/lib/src/embed/liveboard.spec.js.map +1 -1
  57. package/lib/src/embed/search-bar.js +1 -1
  58. package/lib/src/embed/search-bar.js.map +1 -1
  59. package/lib/src/embed/search.js +1 -1
  60. package/lib/src/embed/search.js.map +1 -1
  61. package/lib/src/embed/search.spec.js +0 -20
  62. package/lib/src/embed/search.spec.js.map +1 -1
  63. package/lib/src/embed/ts-embed.d.ts +34 -12
  64. package/lib/src/embed/ts-embed.d.ts.map +1 -1
  65. package/lib/src/embed/ts-embed.js +99 -143
  66. package/lib/src/embed/ts-embed.js.map +1 -1
  67. package/lib/src/embed/ts-embed.spec.d.ts.map +1 -1
  68. package/lib/src/embed/ts-embed.spec.js +172 -139
  69. package/lib/src/embed/ts-embed.spec.js.map +1 -1
  70. package/lib/src/visual-embed-sdk.d.ts +35 -12
  71. package/package.json +2 -2
  72. package/src/embed/app.ts +1 -1
  73. package/src/embed/auto-frame-renderer.ts +1 -1
  74. package/src/embed/bodyless-conversation.ts +1 -1
  75. package/src/embed/conversation.ts +1 -1
  76. package/src/embed/liveboard.spec.ts +62 -0
  77. package/src/embed/liveboard.ts +18 -4
  78. package/src/embed/search-bar.tsx +1 -1
  79. package/src/embed/search.spec.ts +0 -23
  80. package/src/embed/search.ts +1 -1
  81. package/src/embed/ts-embed.spec.ts +213 -179
  82. package/src/embed/ts-embed.ts +96 -149
@@ -12335,7 +12335,7 @@ class TsEmbed {
12335
12335
  private defaultHiddenActions;
12336
12336
  private resizeObserver;
12337
12337
  private preRenderContainerEl;
12338
- private containerScrollListener;
12338
+ /** Last height the embedded app reported, once fullHeight has measured one. */
12339
12339
  protected hostEventClient: HostEventClient;
12340
12340
  protected isReadyForRenderPromise: Promise<void>;
12341
12341
  protected shouldWaitForRenderPromise: boolean;
@@ -12550,7 +12550,6 @@ class TsEmbed {
12550
12550
  protected getV1EmbedBasePath(queryString: string): string;
12551
12551
  protected getEmbedParams(): string;
12552
12552
  protected getEmbedParamsObject(): Record<any, any>;
12553
- protected getUrlQueryParamsObject(): Record<any, any>;
12554
12553
  protected getRootIframeSrc(): string;
12555
12554
  protected createIframeEl(frameSrc: string): HTMLIFrameElement;
12556
12555
  /**
@@ -12592,10 +12591,26 @@ class TsEmbed {
12592
12591
  */
12593
12592
  private createPreRenderPlaceholder;
12594
12593
  /**
12595
- * Resolves the configured preRenderContainer to a live element, falling
12596
- * back to `document.body`. A string selector is re-queried on every call so
12597
- * a remounted container (E.g.: React replacing the node) resolves to the
12598
- * fresh element; an element passed directly cannot be re-resolved.
12594
+ * Resolves the configured preRenderContainer to a live element. A string
12595
+ * selector is re-queried on every call so a remounted container (E.g.: React
12596
+ * replacing the node) resolves to the fresh element; an element passed
12597
+ * directly cannot be re-resolved.
12598
+ *
12599
+ * With nothing configured the frame goes into the host element's nearest
12600
+ * scrolling ancestor, falling back to `document.body`.
12601
+ *
12602
+ * The wrapper is absolutely positioned, so it follows the page for free —
12603
+ * but only while it sits inside whatever scrolls. `document.body` is right
12604
+ * only when the document itself is the scroller; an app that scrolls an
12605
+ * inner element instead left the frame pinned to the viewport while the page
12606
+ * moved under it (SCAL-338563). Being inside the scroller also lets wheel
12607
+ * events chain out of the frame into it, which they cannot do from
12608
+ * `document.body`.
12609
+ *
12610
+ * A scrolling ancestor is layout chrome rather than route content, so it
12611
+ * outlives the embed in practice; and if it does go, the host element goes
12612
+ * with it and this instance is remounting anyway.
12613
+ * `reconcilePreRenderContainer` re-runs this on a detached container.
12599
12614
  */
12600
12615
  private resolvePreRenderContainerTarget;
12601
12616
  private inheritPreRenderContainer;
@@ -12612,8 +12627,9 @@ class TsEmbed {
12612
12627
  * Re-attaches the wrapper to a live container when the previously resolved
12613
12628
  * one has been detached or no longer holds the wrapper — e.g. the host app
12614
12629
  * remounted a custom preRenderContainer, which would otherwise leave a stale
12615
- * reference and collapse the wrapper. Only string selectors can be
12616
- * re-resolved; a container passed as an element is left untouched.
12630
+ * reference and collapse the wrapper. Selectors and the auto-resolved
12631
+ * scrolling ancestor are both re-resolved; a container passed as an element
12632
+ * is left untouched, as there is nothing to re-query.
12617
12633
  */
12618
12634
  private reconcilePreRenderContainer;
12619
12635
  protected insertIntoDOMForPreRender(child: string | Node): void;
@@ -12829,9 +12845,17 @@ class TsEmbed {
12829
12845
  */
12830
12846
  private restorePreRenderContainerPosition;
12831
12847
  /**
12832
- * Detaches and clears the container scroll listener, if one is attached.
12848
+ * Re-sizes the wrapper when its placeholder changes size.
12849
+ *
12850
+ * Position deliberately has no equivalent. An absolutely positioned wrapper
12851
+ * follows its containing block for free, so a correctly placed frame is the
12852
+ * browser's job and costs nothing; repositioning it on scroll would mean a
12853
+ * layout read and a style write per frame to compensate for placing it in
12854
+ * the wrong box. Size is the one thing the browser will not do for us: the
12855
+ * placeholder grows when the embedded app reports a new height, and the
12856
+ * wrapper has to be told.
12833
12857
  */
12834
- private removeContainerScrollListener;
12858
+ private observePreRenderSize;
12835
12859
  /**
12836
12860
  * Destroys the ThoughtSpot embed, and remove any nodes from the DOM.
12837
12861
  * @version SDK: 1.19.1 | ThoughtSpot: *
@@ -12847,8 +12871,6 @@ class TsEmbed {
12847
12871
  */
12848
12872
  prerenderGeneric(): Promise<any>;
12849
12873
  protected preRenderParamsApplied: Promise<void>;
12850
- protected isConfigExcludedFromUrl(): boolean;
12851
- protected triggerUpdateEmbedParams(): Promise<void>;
12852
12874
  protected beforePrerenderVisible(): void;
12853
12875
  /**
12854
12876
  * Displays the pre-rendered component inside the host element.
@@ -15696,6 +15718,7 @@ export class LiveboardEmbed extends V1Embed {
15696
15718
  personalizedViewId: string;
15697
15719
  };
15698
15720
  protected beforePrerenderVisible(): void;
15721
+ private isSameEmbedConfig;
15699
15722
  private isShowingLiveboardRoute;
15700
15723
  protected handleRenderForPrerender(): Promise<TsEmbed>;
15701
15724
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thoughtspot/visual-embed-sdk",
3
- "version": "1.52.2-beta",
3
+ "version": "1.52.2-beta.1",
4
4
  "description": "ThoughtSpot Embed SDK",
5
5
  "module": "lib/src/index.js",
6
6
  "main": "dist/tsembed.js",
@@ -65,7 +65,7 @@
65
65
  "test": "npm run test-sdk",
66
66
  "posttest": "cat ./coverage/sdk/lcov.info | npx coveralls-next || echo 'Warning: Coveralls upload failed but not breaking the build.'",
67
67
  "is-publish-allowed": "node scripts/is-publish-allowed.js",
68
- "prepublishOnly": "npm run is-publish-allowed && npm run tsc && npm run bundle-dts-file && npm run bundle-dts && npm run bundle-dts-react && npm run bundle-dts-react-full && npm run build",
68
+ "prepublishOnly": "npm run is-publish-allowed && npm run test && npm run tsc && npm run bundle-dts-file && npm run bundle-dts && npm run bundle-dts-react && npm run bundle-dts-react-full && npm run build",
69
69
  "check-size": "npm run build && size-limit",
70
70
  "publish-dev": "npm publish --tag dev",
71
71
  "publish-prod": "npm publish --tag latest",
package/src/embed/app.ts CHANGED
@@ -991,7 +991,7 @@ export class AppEmbed extends V1Embed {
991
991
  * embedded Liveboard or visualization.
992
992
  */
993
993
  protected getEmbedParams() {
994
- const params = this.getUrlQueryParamsObject();
994
+ const params = this.getEmbedParamsObject();
995
995
  return getQueryParamString(params, true);
996
996
  }
997
997
 
@@ -108,7 +108,7 @@ class AutoFrameRenderer extends TsEmbed {
108
108
  * @returns The constructed URL to use for the ThoughtSpot embed iframe.
109
109
  */
110
110
  private getMCPIframeSrc(sourceSrc: string) {
111
- const queryParams = this.getUrlQueryParamsObject();
111
+ const queryParams = this.getEmbedParamsObject();
112
112
  const sourceURL = new URL(sourceSrc);
113
113
  const existingQueryParams = sourceURL.searchParams;
114
114
  const existingQueryParamsObject = Object.fromEntries(existingQueryParams);
@@ -56,7 +56,7 @@ export class ConversationMessage extends TsEmbed {
56
56
  messageId,
57
57
  } = this.viewConfig;
58
58
  const path = 'conv-assist-answer';
59
- const queryParams = this.getUrlQueryParamsObject();
59
+ const queryParams = this.getEmbedParamsObject();
60
60
 
61
61
  let query = '';
62
62
  const queryParamsString = getQueryParamString(queryParams, true);
@@ -931,7 +931,7 @@ export class SpotterEmbed extends TsEmbed {
931
931
  const path = sharedConversationId
932
932
  ? `insights/conv-assist/s/${encodeURIComponent(sharedConversationId)}`
933
933
  : 'insights/conv-assist';
934
- const queryParams = this.getUrlQueryParamsObject();
934
+ const queryParams = this.getEmbedParamsObject();
935
935
 
936
936
  let query = '';
937
937
  const queryParamsString = getQueryParamString(queryParams, true);
@@ -2748,6 +2748,68 @@ 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 route via home when a new instance shows the same route with other filters', async () => {
2781
+ const viewConfig = {
2782
+ liveboardId,
2783
+ vizId,
2784
+ activeTabId,
2785
+ ...defaultViewConfig,
2786
+ };
2787
+ const previous = new LiveboardEmbed(getRootEl(), {
2788
+ ...viewConfig,
2789
+ runtimeFilters: [{ columnName: 'region', operator: RuntimeFilterOp.EQ, values: ['east'] }],
2790
+ });
2791
+ const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
2792
+ ...viewConfig,
2793
+ runtimeFilters: [{ columnName: 'region', operator: RuntimeFilterOp.EQ, values: ['west'] }],
2794
+ });
2795
+
2796
+ jest.spyOn(liveboardEmbed as any, 'getPreRenderObj').mockReturnValue(previous);
2797
+ const triggerSpy = jest
2798
+ .spyOn(liveboardEmbed, 'trigger')
2799
+ .mockImplementation(() => Promise.resolve(undefined as any));
2800
+ jest
2801
+ .spyOn(liveboardEmbed, 'navigateToLiveboard')
2802
+ .mockImplementation(() => Promise.resolve(undefined));
2803
+
2804
+ liveboardEmbed.isEmbedContainerLoaded = false;
2805
+ liveboardEmbed['beforePrerenderVisible']();
2806
+ liveboardEmbed.isEmbedContainerLoaded = true;
2807
+ liveboardEmbed['executeEmbedContainerReadyCallbacks']();
2808
+ await waitForHomeHopNavigate();
2809
+
2810
+ expect(triggerSpy).toHaveBeenCalledWith(HostEvent.Navigate, 'home');
2811
+ });
2812
+
2751
2813
  test('should not route via home when the pre-render is on another liveboard', async () => {
2752
2814
  const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
2753
2815
  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 {
@@ -599,7 +600,7 @@ export class LiveboardEmbed extends V1Embed {
599
600
  * embedded Liveboard or visualization.
600
601
  */
601
602
  protected getEmbedParams() {
602
- const params = this.getUrlQueryParamsObject();
603
+ const params = this.getEmbedParamsObject();
603
604
  const queryParams = getQueryParamString(params, true);
604
605
  return queryParams;
605
606
  }
@@ -955,10 +956,12 @@ export class LiveboardEmbed extends V1Embed {
955
956
  super.beforePrerenderVisible();
956
957
 
957
958
  // 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.
959
+ // instance. React builds a new instance per mount, so an equal config
960
+ // is a hide/show, not a hand-over: there is nothing stale to clear.
960
961
  const previous = this.getPreRenderObj<LiveboardEmbed>();
961
- const showing = previous === this ? undefined : previous?.currentLiveboardState;
962
+ const showing = this.isSameEmbedConfig(previous)
963
+ ? undefined
964
+ : previous?.currentLiveboardState;
962
965
 
963
966
  this.executeAfterEmbedContainerLoaded(async () => {
964
967
  // Without this the params callback suspends on its await and
@@ -992,6 +995,17 @@ export class LiveboardEmbed extends V1Embed {
992
995
  });
993
996
  }
994
997
 
998
+ // Callbacks are compared as equal: React passes a fresh closure per render.
999
+ private isSameEmbedConfig(previous?: LiveboardEmbed): boolean {
1000
+ if (!previous) return false;
1001
+ if (previous === this) return true;
1002
+ return isEqualWith(
1003
+ previous.viewConfig,
1004
+ this.viewConfig,
1005
+ (a, b) => (typeof a === 'function' && typeof b === 'function' ? true : undefined),
1006
+ );
1007
+ }
1008
+
995
1009
  // The whole route, not just the liveboard id: the path is built from the
996
1010
  // viz, tab and view too, so a same-liveboard show onto another tab is a
997
1011
  // real navigation.
@@ -163,7 +163,7 @@ export class SearchBarEmbed extends TsEmbed {
163
163
  * @param dataSources A list of data source GUIDs
164
164
  */
165
165
  private getIFrameSrc() {
166
- const queryParams = this.getUrlQueryParamsObject();
166
+ const queryParams = this.getEmbedParamsObject();
167
167
  const path = 'search-bar-embed';
168
168
 
169
169
  let query = '';
@@ -854,29 +854,6 @@ test('should pass forceTable parameter when forceTable is true', async () => {
854
854
  });
855
855
 
856
856
  describe('SearchBarEmbed tests', () => {
857
- test('should keep view config off the URL when excludeConfigFromURL is set', async () => {
858
- const searchBarEmbed = new SearchBarEmbed(getRootEl() as any, {
859
- ...defaultViewConfig,
860
- dataSources: ['source-1', 'source-2'],
861
- additionalFlags: { excludeConfigFromURL: true },
862
- } as any);
863
- searchBarEmbed.render();
864
- await executeAfterWait(() => {
865
- const iframeSrc = getIFrameSrc();
866
-
867
- // Bootstrap params stay: the handshake, the embed marker that
868
- // picks the app shell, and the data sources it boots against.
869
- expect(iframeSrc).toContain('hostAppUrl=');
870
- expect(iframeSrc).toContain('isSearchEmbed=true');
871
- expect(iframeSrc).toContain('dataSources');
872
-
873
- // The rest of the view config goes over
874
- // HostEvent.UpdateEmbedParams.
875
- expect(iframeSrc).not.toContain('hideAction');
876
- expect(iframeSrc).not.toContain('useLastSelectedDataSource');
877
- });
878
- });
879
-
880
857
  test('should pass dataSources parameter when dataSources array is provided', async () => {
881
858
  const searchBarEmbed = new SearchBarEmbed(getRootEl() as any, {
882
859
  ...defaultViewConfig,
@@ -546,7 +546,7 @@ export class SearchEmbed extends TsEmbed {
546
546
  excludeRuntimeParametersfromURL,
547
547
  excludeRuntimeFiltersfromURL,
548
548
  } = this.viewConfig;
549
- const queryParams = this.getUrlQueryParamsObject();
549
+ const queryParams = this.getEmbedParamsObject();
550
550
  let query = '';
551
551
  const queryParamsString = getQueryParamString(queryParams, true);
552
552
  if (queryParamsString) {