@thoughtspot/visual-embed-sdk 1.46.4 → 1.46.5-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 (172) hide show
  1. package/cjs/package.json +2 -2
  2. package/cjs/src/auth.d.ts +2 -1
  3. package/cjs/src/auth.d.ts.map +1 -1
  4. package/cjs/src/auth.js +2 -1
  5. package/cjs/src/auth.js.map +1 -1
  6. package/cjs/src/authToken.d.ts.map +1 -1
  7. package/cjs/src/authToken.js.map +1 -1
  8. package/cjs/src/css-variables.d.ts +51 -17
  9. package/cjs/src/css-variables.d.ts.map +1 -1
  10. package/cjs/src/embed/app.d.ts +1 -0
  11. package/cjs/src/embed/app.d.ts.map +1 -1
  12. package/cjs/src/embed/app.js +10 -1
  13. package/cjs/src/embed/app.js.map +1 -1
  14. package/cjs/src/embed/app.spec.js +46 -1
  15. package/cjs/src/embed/app.spec.js.map +1 -1
  16. package/cjs/src/embed/base.d.ts.map +1 -1
  17. package/cjs/src/embed/base.js.map +1 -1
  18. package/cjs/src/embed/base.spec.js.map +1 -1
  19. package/cjs/src/embed/events.spec.js +72 -0
  20. package/cjs/src/embed/events.spec.js.map +1 -1
  21. package/cjs/src/embed/hostEventClient/contracts.d.ts +74 -3
  22. package/cjs/src/embed/hostEventClient/contracts.d.ts.map +1 -1
  23. package/cjs/src/embed/hostEventClient/contracts.js +7 -0
  24. package/cjs/src/embed/hostEventClient/contracts.js.map +1 -1
  25. package/cjs/src/embed/hostEventClient/host-event-client.d.ts +10 -0
  26. package/cjs/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
  27. package/cjs/src/embed/hostEventClient/host-event-client.js +46 -9
  28. package/cjs/src/embed/hostEventClient/host-event-client.js.map +1 -1
  29. package/cjs/src/embed/hostEventClient/host-event-client.spec.js +155 -0
  30. package/cjs/src/embed/hostEventClient/host-event-client.spec.js.map +1 -1
  31. package/cjs/src/embed/liveboard.d.ts +1 -0
  32. package/cjs/src/embed/liveboard.d.ts.map +1 -1
  33. package/cjs/src/embed/liveboard.js +11 -2
  34. package/cjs/src/embed/liveboard.js.map +1 -1
  35. package/cjs/src/embed/liveboard.spec.js +96 -2
  36. package/cjs/src/embed/liveboard.spec.js.map +1 -1
  37. package/cjs/src/embed/sage.d.ts.map +1 -1
  38. package/cjs/src/embed/sage.js.map +1 -1
  39. package/cjs/src/embed/search.spec.js.map +1 -1
  40. package/cjs/src/embed/ts-embed.d.ts +41 -2
  41. package/cjs/src/embed/ts-embed.d.ts.map +1 -1
  42. package/cjs/src/embed/ts-embed.js +41 -2
  43. package/cjs/src/embed/ts-embed.js.map +1 -1
  44. package/cjs/src/react/index.d.ts.map +1 -1
  45. package/cjs/src/react/index.js +58 -53
  46. package/cjs/src/react/index.js.map +1 -1
  47. package/cjs/src/types.d.ts +730 -32
  48. package/cjs/src/types.d.ts.map +1 -1
  49. package/cjs/src/types.js +734 -3
  50. package/cjs/src/types.js.map +1 -1
  51. package/cjs/src/utils/graphql/answerService/answerService.d.ts +4 -2
  52. package/cjs/src/utils/graphql/answerService/answerService.d.ts.map +1 -1
  53. package/cjs/src/utils/graphql/answerService/answerService.js +4 -2
  54. package/cjs/src/utils/graphql/answerService/answerService.js.map +1 -1
  55. package/cjs/src/utils/graphql/preview-service.d.ts.map +1 -1
  56. package/cjs/src/utils/graphql/preview-service.js.map +1 -1
  57. package/cjs/src/utils/processData.d.ts.map +1 -1
  58. package/cjs/src/utils/processData.js.map +1 -1
  59. package/dist/{index-DGV_zh53.js → index-DW2wEHqy.js} +1 -1
  60. package/dist/src/auth.d.ts +2 -1
  61. package/dist/src/auth.d.ts.map +1 -1
  62. package/dist/src/authToken.d.ts.map +1 -1
  63. package/dist/src/css-variables.d.ts +51 -17
  64. package/dist/src/css-variables.d.ts.map +1 -1
  65. package/dist/src/embed/app.d.ts +1 -0
  66. package/dist/src/embed/app.d.ts.map +1 -1
  67. package/dist/src/embed/base.d.ts.map +1 -1
  68. package/dist/src/embed/hostEventClient/contracts.d.ts +74 -3
  69. package/dist/src/embed/hostEventClient/contracts.d.ts.map +1 -1
  70. package/dist/src/embed/hostEventClient/host-event-client.d.ts +10 -0
  71. package/dist/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
  72. package/dist/src/embed/liveboard.d.ts +1 -0
  73. package/dist/src/embed/liveboard.d.ts.map +1 -1
  74. package/dist/src/embed/sage.d.ts.map +1 -1
  75. package/dist/src/embed/ts-embed.d.ts +41 -2
  76. package/dist/src/embed/ts-embed.d.ts.map +1 -1
  77. package/dist/src/react/index.d.ts.map +1 -1
  78. package/dist/src/types.d.ts +730 -32
  79. package/dist/src/types.d.ts.map +1 -1
  80. package/dist/src/utils/graphql/answerService/answerService.d.ts +4 -2
  81. package/dist/src/utils/graphql/answerService/answerService.d.ts.map +1 -1
  82. package/dist/src/utils/graphql/preview-service.d.ts.map +1 -1
  83. package/dist/src/utils/processData.d.ts.map +1 -1
  84. package/dist/tsembed-react.es.js +917 -77
  85. package/dist/tsembed-react.js +915 -75
  86. package/dist/tsembed.es.js +859 -24
  87. package/dist/tsembed.js +857 -22
  88. package/dist/visual-embed-sdk-react-full.d.ts +892 -54
  89. package/dist/visual-embed-sdk-react.d.ts +892 -54
  90. package/dist/visual-embed-sdk.d.ts +912 -56
  91. package/lib/package.json +2 -2
  92. package/lib/src/auth.d.ts +2 -1
  93. package/lib/src/auth.d.ts.map +1 -1
  94. package/lib/src/auth.js +2 -1
  95. package/lib/src/auth.js.map +1 -1
  96. package/lib/src/authToken.d.ts.map +1 -1
  97. package/lib/src/authToken.js.map +1 -1
  98. package/lib/src/css-variables.d.ts +51 -17
  99. package/lib/src/css-variables.d.ts.map +1 -1
  100. package/lib/src/embed/app.d.ts +1 -0
  101. package/lib/src/embed/app.d.ts.map +1 -1
  102. package/lib/src/embed/app.js +10 -1
  103. package/lib/src/embed/app.js.map +1 -1
  104. package/lib/src/embed/app.spec.js +46 -1
  105. package/lib/src/embed/app.spec.js.map +1 -1
  106. package/lib/src/embed/base.d.ts.map +1 -1
  107. package/lib/src/embed/base.js.map +1 -1
  108. package/lib/src/embed/base.spec.js.map +1 -1
  109. package/lib/src/embed/events.spec.js +73 -1
  110. package/lib/src/embed/events.spec.js.map +1 -1
  111. package/lib/src/embed/hostEventClient/contracts.d.ts +74 -3
  112. package/lib/src/embed/hostEventClient/contracts.d.ts.map +1 -1
  113. package/lib/src/embed/hostEventClient/contracts.js +7 -0
  114. package/lib/src/embed/hostEventClient/contracts.js.map +1 -1
  115. package/lib/src/embed/hostEventClient/host-event-client.d.ts +10 -0
  116. package/lib/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
  117. package/lib/src/embed/hostEventClient/host-event-client.js +46 -9
  118. package/lib/src/embed/hostEventClient/host-event-client.js.map +1 -1
  119. package/lib/src/embed/hostEventClient/host-event-client.spec.js +155 -0
  120. package/lib/src/embed/hostEventClient/host-event-client.spec.js.map +1 -1
  121. package/lib/src/embed/liveboard.d.ts +1 -0
  122. package/lib/src/embed/liveboard.d.ts.map +1 -1
  123. package/lib/src/embed/liveboard.js +11 -2
  124. package/lib/src/embed/liveboard.js.map +1 -1
  125. package/lib/src/embed/liveboard.spec.js +96 -2
  126. package/lib/src/embed/liveboard.spec.js.map +1 -1
  127. package/lib/src/embed/sage.d.ts.map +1 -1
  128. package/lib/src/embed/sage.js.map +1 -1
  129. package/lib/src/embed/search.spec.js.map +1 -1
  130. package/lib/src/embed/ts-embed.d.ts +41 -2
  131. package/lib/src/embed/ts-embed.d.ts.map +1 -1
  132. package/lib/src/embed/ts-embed.js +42 -3
  133. package/lib/src/embed/ts-embed.js.map +1 -1
  134. package/lib/src/react/index.d.ts.map +1 -1
  135. package/lib/src/react/index.js +58 -53
  136. package/lib/src/react/index.js.map +1 -1
  137. package/lib/src/types.d.ts +730 -32
  138. package/lib/src/types.d.ts.map +1 -1
  139. package/lib/src/types.js +734 -3
  140. package/lib/src/types.js.map +1 -1
  141. package/lib/src/utils/graphql/answerService/answerService.d.ts +4 -2
  142. package/lib/src/utils/graphql/answerService/answerService.d.ts.map +1 -1
  143. package/lib/src/utils/graphql/answerService/answerService.js +4 -2
  144. package/lib/src/utils/graphql/answerService/answerService.js.map +1 -1
  145. package/lib/src/utils/graphql/preview-service.d.ts.map +1 -1
  146. package/lib/src/utils/graphql/preview-service.js.map +1 -1
  147. package/lib/src/utils/processData.d.ts.map +1 -1
  148. package/lib/src/utils/processData.js.map +1 -1
  149. package/lib/src/visual-embed-sdk.d.ts +912 -56
  150. package/package.json +2 -2
  151. package/src/auth.spec.ts +1 -1
  152. package/src/auth.ts +2 -1
  153. package/src/authToken.ts +0 -1
  154. package/src/css-variables.ts +51 -17
  155. package/src/embed/app.spec.ts +62 -3
  156. package/src/embed/app.ts +10 -1
  157. package/src/embed/base.spec.ts +1 -2
  158. package/src/embed/base.ts +1 -4
  159. package/src/embed/events.spec.ts +88 -0
  160. package/src/embed/hostEventClient/contracts.ts +74 -2
  161. package/src/embed/hostEventClient/host-event-client.spec.ts +257 -0
  162. package/src/embed/hostEventClient/host-event-client.ts +70 -15
  163. package/src/embed/liveboard.spec.ts +126 -2
  164. package/src/embed/liveboard.ts +11 -2
  165. package/src/embed/sage.ts +0 -1
  166. package/src/embed/search.spec.ts +0 -2
  167. package/src/embed/ts-embed.ts +43 -3
  168. package/src/react/index.tsx +76 -72
  169. package/src/types.ts +739 -31
  170. package/src/utils/graphql/answerService/answerService.ts +4 -5
  171. package/src/utils/graphql/preview-service.ts +0 -1
  172. package/src/utils/processData.ts +0 -5
@@ -205,6 +205,128 @@ describe('HostEventClient', () => {
205
205
  expect(result).toEqual(mockResponse);
206
206
  });
207
207
 
208
+ it('should route GetAnswerSession through passthrough and return data', async () => {
209
+ const { client, mockIframe } = createHostEventClient();
210
+ const mockResponse = [{ value: { session: 'testSession', embedAnswerData: { id: '1' } } }];
211
+ mockProcessTrigger.mockResolvedValue(mockResponse);
212
+
213
+ const result = await client.triggerHostEvent(HostEvent.GetAnswerSession, { vizId: '123' });
214
+
215
+ expect(mockProcessTrigger).toHaveBeenCalledWith(
216
+ mockIframe,
217
+ HostEvent.UIPassthrough,
218
+ mockThoughtSpotHost,
219
+ { type: UIPassthroughEvent.GetAnswerSession, parameters: { vizId: '123' } },
220
+ undefined,
221
+ );
222
+ expect(result).toEqual({ session: 'testSession', embedAnswerData: { id: '1' } });
223
+ });
224
+
225
+ it('should fall back to legacy host event when passthrough returns no data for GetAnswerSession', async () => {
226
+ const { client } = createHostEventClient();
227
+ mockProcessTrigger
228
+ .mockResolvedValueOnce([])
229
+ .mockResolvedValueOnce({ session: 'fallbackSession' });
230
+
231
+ const result = await client.triggerHostEvent(HostEvent.GetAnswerSession, { vizId: '123' });
232
+
233
+ expect(mockProcessTrigger).toHaveBeenCalledTimes(2);
234
+ expect(mockProcessTrigger).toHaveBeenNthCalledWith(
235
+ 2,
236
+ expect.anything(),
237
+ HostEvent.GetAnswerSession,
238
+ mockThoughtSpotHost,
239
+ { vizId: '123' },
240
+ undefined,
241
+ );
242
+ expect(result).toEqual({ session: 'fallbackSession' });
243
+ });
244
+
245
+ it('should throw real errors from passthrough without falling back', async () => {
246
+ const { client } = createHostEventClient();
247
+ mockProcessTrigger.mockResolvedValue([{ error: 'Permission denied' }]);
248
+
249
+ await expect(client.triggerHostEvent(HostEvent.GetAnswerSession, {}))
250
+ .rejects.toThrow('Permission denied');
251
+ expect(mockProcessTrigger).toHaveBeenCalledTimes(1);
252
+ });
253
+
254
+ it('should route GetFilters through passthrough and return data', async () => {
255
+ const { client, mockIframe } = createHostEventClient();
256
+ const mockResponse = [{ value: { liveboardFilters: [{ id: 'f1' }], runtimeFilters: [] as any[] } }];
257
+ mockProcessTrigger.mockResolvedValue(mockResponse);
258
+
259
+ const result = await client.triggerHostEvent(HostEvent.GetFilters, {});
260
+
261
+ expect(mockProcessTrigger).toHaveBeenCalledWith(
262
+ mockIframe,
263
+ HostEvent.UIPassthrough,
264
+ mockThoughtSpotHost,
265
+ { type: UIPassthroughEvent.GetFilters, parameters: {} },
266
+ undefined,
267
+ );
268
+ expect(result).toEqual({ liveboardFilters: [{ id: 'f1' }], runtimeFilters: [] });
269
+ });
270
+
271
+ it('should route GetTabs through passthrough and return data', async () => {
272
+ const { client } = createHostEventClient();
273
+ const mockResponse = [{ value: { orderedTabIds: ['t1', 't2'], numberOfTabs: 2, Tabs: [] as any[] } }];
274
+ mockProcessTrigger.mockResolvedValue(mockResponse);
275
+
276
+ const result = await client.triggerHostEvent(HostEvent.GetTabs, {});
277
+
278
+ expect(result).toEqual({ orderedTabIds: ['t1', 't2'], numberOfTabs: 2, Tabs: [] });
279
+ });
280
+
281
+ it('should route GetTML through passthrough and return data', async () => {
282
+ const { client } = createHostEventClient();
283
+ const tmlData = { answer: { search_query: 'revenue by region' } };
284
+ mockProcessTrigger.mockResolvedValue([{ value: tmlData }]);
285
+
286
+ const result = await client.triggerHostEvent(HostEvent.GetTML, {});
287
+
288
+ expect(result).toEqual(tmlData);
289
+ });
290
+
291
+ it('should route GetIframeUrl through passthrough and return data', async () => {
292
+ const { client } = createHostEventClient();
293
+ mockProcessTrigger.mockResolvedValue([{ value: { iframeUrl: 'https://ts.example.com/embed' } }]);
294
+
295
+ const result = await client.triggerHostEvent(HostEvent.GetIframeUrl, {});
296
+
297
+ expect(result).toEqual({ iframeUrl: 'https://ts.example.com/embed' });
298
+ });
299
+
300
+ it('should route GetParameters through passthrough and return data', async () => {
301
+ const { client } = createHostEventClient();
302
+ mockProcessTrigger.mockResolvedValue([{ value: { parameters: [{ name: 'p1' }] } }]);
303
+
304
+ const result = await client.triggerHostEvent(HostEvent.GetParameters, {});
305
+
306
+ expect(result).toEqual({ parameters: [{ name: 'p1' }] });
307
+ });
308
+
309
+ it('should route getExportRequestForCurrentPinboard through passthrough and return data', async () => {
310
+ const { client } = createHostEventClient();
311
+ mockProcessTrigger.mockResolvedValue([{ value: { v2Content: 'exportData' } }]);
312
+
313
+ const result = await client.triggerHostEvent(HostEvent.getExportRequestForCurrentPinboard, {});
314
+
315
+ expect(result).toEqual({ v2Content: 'exportData' });
316
+ });
317
+
318
+ it('should fall back to legacy for GetFilters when passthrough returns null', async () => {
319
+ const { client } = createHostEventClient();
320
+ mockProcessTrigger
321
+ .mockResolvedValueOnce(null)
322
+ .mockResolvedValueOnce({ liveboardFilters: [], runtimeFilters: [] });
323
+
324
+ const result = await client.triggerHostEvent(HostEvent.GetFilters, {});
325
+
326
+ expect(mockProcessTrigger).toHaveBeenCalledTimes(2);
327
+ expect(result).toEqual({ liveboardFilters: [], runtimeFilters: [] });
328
+ });
329
+
208
330
  it('should call fallback for Pin event', async () => {
209
331
  const { client, mockIframe } = createHostEventClient();
210
332
  const hostEvent = HostEvent.Pin;
@@ -318,4 +440,139 @@ describe('HostEventClient', () => {
318
440
  });
319
441
  });
320
442
  });
443
+
444
+ describe('getDataWithPassthroughFallback', () => {
445
+ const callMethod = (client: HostEventClient, ...args: any[]) => (client as any).getDataWithPassthroughFallback(...args);
446
+
447
+ it('should return unwrapped value when passthrough succeeds', async () => {
448
+ const { client } = createHostEventClient();
449
+ mockProcessTrigger.mockResolvedValue([{ value: { session: 's1', embedAnswerData: { id: 'a1' } } }]);
450
+
451
+ const result = await callMethod(
452
+ client, UIPassthroughEvent.GetAnswerSession, HostEvent.GetAnswerSession, { vizId: '1' },
453
+ );
454
+
455
+ expect(result).toEqual({ session: 's1', embedAnswerData: { id: 'a1' } });
456
+ expect(mockProcessTrigger).toHaveBeenCalledTimes(1);
457
+ });
458
+
459
+ it('should fall back to legacy when passthrough returns empty array', async () => {
460
+ const { client } = createHostEventClient();
461
+ const legacyResponse = { session: 'legacy' };
462
+ mockProcessTrigger
463
+ .mockResolvedValueOnce([])
464
+ .mockResolvedValueOnce(legacyResponse);
465
+
466
+ const result = await callMethod(
467
+ client, UIPassthroughEvent.GetAnswerSession, HostEvent.GetAnswerSession, { vizId: '1' },
468
+ );
469
+
470
+ expect(mockProcessTrigger).toHaveBeenCalledTimes(2);
471
+ expect(result).toEqual(legacyResponse);
472
+ });
473
+
474
+ it('should fall back to legacy when passthrough returns null', async () => {
475
+ const { client } = createHostEventClient();
476
+ const legacyResponse = { parameters: [] as any[] };
477
+ mockProcessTrigger
478
+ .mockResolvedValueOnce(null)
479
+ .mockResolvedValueOnce(legacyResponse);
480
+
481
+ const result = await callMethod(
482
+ client, UIPassthroughEvent.GetParameters, HostEvent.GetParameters, {},
483
+ );
484
+
485
+ expect(mockProcessTrigger).toHaveBeenCalledTimes(2);
486
+ expect(result).toEqual(legacyResponse);
487
+ });
488
+
489
+ it('should fall back to legacy when passthrough returns undefined', async () => {
490
+ const { client } = createHostEventClient();
491
+ const legacyResponse = { iframeUrl: 'https://ts.example.com' };
492
+ mockProcessTrigger
493
+ .mockResolvedValueOnce(undefined)
494
+ .mockResolvedValueOnce(legacyResponse);
495
+
496
+ const result = await callMethod(
497
+ client, UIPassthroughEvent.GetIframeUrl, HostEvent.GetIframeUrl, {},
498
+ );
499
+
500
+ expect(mockProcessTrigger).toHaveBeenCalledTimes(2);
501
+ expect(result).toEqual(legacyResponse);
502
+ });
503
+
504
+ it('should fall back when response array has no matching entries', async () => {
505
+ const { client } = createHostEventClient();
506
+ const legacyResponse = { v2Content: 'data' };
507
+ mockProcessTrigger
508
+ .mockResolvedValueOnce([{ refId: 'r1' }])
509
+ .mockResolvedValueOnce(legacyResponse);
510
+
511
+ const result = await callMethod(
512
+ client, UIPassthroughEvent.GetExportRequestForCurrentPinboard,
513
+ HostEvent.getExportRequestForCurrentPinboard, {},
514
+ );
515
+
516
+ expect(mockProcessTrigger).toHaveBeenCalledTimes(2);
517
+ expect(result).toEqual(legacyResponse);
518
+ });
519
+
520
+ it('should throw when response has error field', async () => {
521
+ const { client } = createHostEventClient();
522
+ mockProcessTrigger.mockResolvedValue([{ error: 'Permission denied' }]);
523
+
524
+ await expect(callMethod(
525
+ client, UIPassthroughEvent.GetFilters, HostEvent.GetFilters, {},
526
+ )).rejects.toThrow('Permission denied');
527
+ expect(mockProcessTrigger).toHaveBeenCalledTimes(1);
528
+ });
529
+
530
+ it('should throw when response value contains errors field', async () => {
531
+ const { client } = createHostEventClient();
532
+ mockProcessTrigger.mockResolvedValue([{ value: { errors: 'Invalid vizId' } }]);
533
+
534
+ await expect(callMethod(
535
+ client, UIPassthroughEvent.GetTML, HostEvent.GetTML, { vizId: 'bad' },
536
+ )).rejects.toThrow('Invalid vizId');
537
+ expect(mockProcessTrigger).toHaveBeenCalledTimes(1);
538
+ });
539
+
540
+ it('should throw when response value contains error field', async () => {
541
+ const { client } = createHostEventClient();
542
+ mockProcessTrigger.mockResolvedValue([{ value: { error: 'Not found' } }]);
543
+
544
+ await expect(callMethod(
545
+ client, UIPassthroughEvent.GetTabs, HostEvent.GetTabs, {},
546
+ )).rejects.toThrow('Not found');
547
+ expect(mockProcessTrigger).toHaveBeenCalledTimes(1);
548
+ });
549
+
550
+ it('should stringify object errors instead of producing [object Object]', async () => {
551
+ const { client } = createHostEventClient();
552
+ const errorObj = { code: 403, reason: 'Forbidden' };
553
+ mockProcessTrigger.mockResolvedValue([{ error: errorObj }]);
554
+
555
+ await expect(callMethod(
556
+ client, UIPassthroughEvent.GetFilters, HostEvent.GetFilters, {},
557
+ )).rejects.toThrow(JSON.stringify(errorObj));
558
+ expect(mockProcessTrigger).toHaveBeenCalledTimes(1);
559
+ });
560
+
561
+ it('should default payload to empty object when null', async () => {
562
+ const { client, mockIframe } = createHostEventClient();
563
+ mockProcessTrigger.mockResolvedValue([{ value: { iframeUrl: 'https://ts.example.com' } }]);
564
+
565
+ await callMethod(
566
+ client, UIPassthroughEvent.GetIframeUrl, HostEvent.GetIframeUrl, null,
567
+ );
568
+
569
+ expect(mockProcessTrigger).toHaveBeenCalledWith(
570
+ mockIframe,
571
+ HostEvent.UIPassthrough,
572
+ mockThoughtSpotHost,
573
+ { type: UIPassthroughEvent.GetIframeUrl, parameters: {} },
574
+ undefined,
575
+ );
576
+ });
577
+ });
321
578
  });
@@ -3,18 +3,42 @@ import { processTrigger as processTriggerService } from '../../utils/processTrig
3
3
  import { getEmbedConfig } from '../embedConfig';
4
4
  import {
5
5
  UIPassthroughArrayResponse,
6
- UIPassthroughEvent, HostEventRequest, HostEventResponse,
6
+ UIPassthroughEvent,
7
+ HostEventRequest,
8
+ HostEventResponse,
7
9
  UIPassthroughRequest,
8
10
  UIPassthroughResponse,
9
11
  TriggerPayload,
10
12
  TriggerResponse,
11
13
  } from './contracts';
12
14
 
15
+ /** Host events that use getDataWithPassthroughFallback (getter-style APIs) */
16
+ const HOST_EVENT_PASSTHROUGH_MAP: Partial<Record<HostEvent, UIPassthroughEvent>> = {
17
+ [HostEvent.GetAnswerSession]: UIPassthroughEvent.GetAnswerSession,
18
+ [HostEvent.GetFilters]: UIPassthroughEvent.GetFilters,
19
+ [HostEvent.GetIframeUrl]: UIPassthroughEvent.GetIframeUrl,
20
+ [HostEvent.GetParameters]: UIPassthroughEvent.GetParameters,
21
+ [HostEvent.GetTML]: UIPassthroughEvent.GetTML,
22
+ [HostEvent.GetTabs]: UIPassthroughEvent.GetTabs,
23
+ [HostEvent.getExportRequestForCurrentPinboard]: UIPassthroughEvent.GetExportRequestForCurrentPinboard,
24
+ };
25
+
13
26
  export class HostEventClient {
14
27
  iFrame: HTMLIFrameElement;
15
28
 
29
+ /** Host events with custom handlers
30
+ * (setters or special logic) -
31
+ * bound to instance for protected method access */
32
+ private readonly customHandlers: Partial<
33
+ Record<HostEvent, (payload: any, context?: ContextType) => Promise<any>>
34
+ >;
35
+
16
36
  constructor(iFrame?: HTMLIFrameElement) {
17
37
  this.iFrame = iFrame;
38
+ this.customHandlers = {
39
+ [HostEvent.Pin]: (p, c) => this.handlePinEvent(p, c),
40
+ [HostEvent.SaveAnswer]: (p, c) => this.handleSaveAnswerEvent(p, c),
41
+ };
18
42
  }
19
43
 
20
44
  /**
@@ -44,11 +68,10 @@ export class HostEventClient {
44
68
  context?: ContextType,
45
69
  ): Promise<UIPassthroughResponse<UIPassthroughEventT>> {
46
70
  const response = (await this.triggerUIPassthroughApi(apiName, parameters, context))
47
- ?.filter?.((r) => r.error || r.value)[0];
71
+ ?.find?.((r) => r.error || r.value);
48
72
 
49
73
  if (!response) {
50
74
  const error = `No answer found${parameters.vizId ? ` for vizId: ${parameters.vizId}` : ''}.`;
51
-
52
75
  throw { error };
53
76
  }
54
77
 
@@ -57,8 +80,8 @@ export class HostEventClient {
57
80
  || (response.value as any)?.error;
58
81
 
59
82
  if (errors) {
60
-
61
- throw { error: response.error };
83
+ const message = typeof errors === 'string' ? errors : JSON.stringify(errors);
84
+ throw { error: message };
62
85
  }
63
86
 
64
87
  return { ...response.value };
@@ -72,6 +95,36 @@ export class HostEventClient {
72
95
  return this.processTrigger(hostEvent, data, context);
73
96
  }
74
97
 
98
+ /**
99
+ * For getter events that return data. Tries UI passthrough first;
100
+ * if the app doesn't support it (no response data), falls back to
101
+ * the legacy host event channel. Real errors are thrown as-is.
102
+ */
103
+ private async getDataWithPassthroughFallback<UIPassthroughEventT extends UIPassthroughEvent>(
104
+ passthroughEvent: UIPassthroughEventT,
105
+ hostEvent: HostEvent,
106
+ payload: any,
107
+ context?: ContextType,
108
+ ): Promise<UIPassthroughResponse<UIPassthroughEventT>> {
109
+ const response = await this.triggerUIPassthroughApi(
110
+ passthroughEvent, payload || {}, context,
111
+ );
112
+ const matched = response?.find?.((r) => r.error || r.value);
113
+ if (!matched) {
114
+ return this.hostEventFallback(hostEvent, payload, context);
115
+ }
116
+
117
+ const errors = matched.error
118
+ || (matched.value as any)?.errors
119
+ || (matched.value as any)?.error;
120
+ if (errors) {
121
+ const message = typeof errors === 'string' ? errors : JSON.stringify(errors);
122
+ throw new Error(message);
123
+ }
124
+
125
+ return { ...matched.value };
126
+ }
127
+
75
128
  /**
76
129
  * Setter for the iframe element used for host events
77
130
  * @param {HTMLIFrameElement} iFrame - the iframe element to set
@@ -146,16 +199,18 @@ export class HostEventClient {
146
199
  payload?: TriggerPayload<PayloadT, HostEventT>,
147
200
  context?: ContextT,
148
201
  ): Promise<TriggerResponse<PayloadT, HostEventT, ContextType>> {
149
- switch (hostEvent) {
150
- case HostEvent.Pin:
151
- return this.handlePinEvent(payload as HostEventRequest<HostEvent.Pin>, context as ContextType) as any;
152
- case HostEvent.SaveAnswer:
153
- return this.handleSaveAnswerEvent(
154
- payload as HostEventRequest<HostEvent.SaveAnswer>,
155
- context as ContextType,
156
- ) as any;
157
- default:
158
- return this.hostEventFallback(hostEvent, payload, context);
202
+ const customHandler = this.customHandlers[hostEvent];
203
+ if (customHandler) {
204
+ return customHandler(payload, context as ContextType) as any;
205
+ }
206
+
207
+ const passthroughEvent = HOST_EVENT_PASSTHROUGH_MAP[hostEvent];
208
+ if (passthroughEvent) {
209
+ return this.getDataWithPassthroughFallback(
210
+ passthroughEvent, hostEvent, payload, context as ContextType,
211
+ ) as any;
159
212
  }
213
+
214
+ return this.hostEventFallback(hostEvent, payload, context);
160
215
  }
161
216
  }
@@ -94,6 +94,37 @@ describe('Liveboard/viz embed tests', () => {
94
94
  });
95
95
  });
96
96
 
97
+ test('should set disabled actions using PersonalizedViewsDropdown alias', async () => {
98
+ const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
99
+ disabledActions: [Action.PersonalizedViewsDropdown],
100
+ disabledActionReason: 'Action denied',
101
+ ...defaultViewConfig,
102
+ liveboardId,
103
+ } as LiveboardViewConfig);
104
+ liveboardEmbed.render();
105
+ await executeAfterWait(() => {
106
+ expectUrlMatchesWithParams(
107
+ getIFrameSrc(),
108
+ `http://${thoughtSpotHost}/?embedApp=true&${defaultParamsWithoutHiddenActions}&disableAction=[%22${Action.PersonalisedViewsDropdown}%22]&disableHint=Action%20denied&hideAction=[%22${Action.ReportError}%22]${prefixParams}#/embed/viz/${liveboardId}`,
109
+ );
110
+ });
111
+ });
112
+
113
+ test('should set hidden actions using OrganizeFavorites alias', async () => {
114
+ const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
115
+ hiddenActions: [Action.OrganizeFavorites],
116
+ ...defaultViewConfig,
117
+ liveboardId,
118
+ } as LiveboardViewConfig);
119
+ liveboardEmbed.render();
120
+ await executeAfterWait(() => {
121
+ expectUrlMatchesWithParams(
122
+ getIFrameSrc(),
123
+ `http://${thoughtSpotHost}/?embedApp=true&${defaultParamsWithoutHiddenActions}&hideAction=[%22${Action.ReportError}%22,%22${Action.OrganiseFavourites}%22]${prefixParams}#/embed/viz/${liveboardId}`,
124
+ );
125
+ });
126
+ });
127
+
97
128
  test('should set disabled actions', async () => {
98
129
  const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
99
130
  disabledActions: [Action.DownloadAsCsv, Action.DownloadAsPdf, Action.DownloadAsXlsx],
@@ -1011,8 +1042,8 @@ describe('Liveboard/viz embed tests', () => {
1011
1042
  } as LiveboardViewConfig);
1012
1043
  liveboardEmbed.render();
1013
1044
  await executeAfterWait(() => {
1014
- // URL: #/embed/viz/{id}/tab/{tabId}?view={viewId} (view at
1015
- // END, not middle)
1045
+ // URL: #/embed/viz/{id}/tab/{tabId}?view={viewId}
1046
+ // (view at END, not middle)
1016
1047
  expect(getIFrameSrc()).toMatch(
1017
1048
  new RegExp(
1018
1049
  `#/embed/viz/${liveboardId}/tab/${activeTabId}\\?view=${workaroundViewId}`,
@@ -1944,6 +1975,99 @@ describe('Liveboard/viz embed tests', () => {
1944
1975
  });
1945
1976
  });
1946
1977
 
1978
+ describe('updateIFrameHeight threshold handling', () => {
1979
+ let mockIFrame: HTMLIFrameElement;
1980
+
1981
+ beforeEach(() => {
1982
+ mockIFrame = document.createElement('iframe');
1983
+ mockIFrame.getBoundingClientRect = jest.fn().mockReturnValue({
1984
+ top: 0,
1985
+ left: 0,
1986
+ bottom: 500,
1987
+ right: 800,
1988
+ width: 800,
1989
+ height: 500,
1990
+ });
1991
+ });
1992
+
1993
+ test('should skip height update when change is below threshold', async () => {
1994
+ const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
1995
+ liveboardId,
1996
+ ...defaultViewConfig,
1997
+ fullHeight: true,
1998
+ }) as any;
1999
+
2000
+ liveboardEmbed.iFrame = mockIFrame;
2001
+ document.body.appendChild(mockIFrame);
2002
+ await liveboardEmbed.render();
2003
+
2004
+ const spySetIFrameHeight = jest.spyOn(liveboardEmbed, 'setIFrameHeight');
2005
+
2006
+ // currentHeight is 500; heightToSet = max(510, 500) = 510; change = 10 < 30
2007
+ liveboardEmbed.updateIFrameHeight({ data: 510, type: EmbedEvent.EmbedHeight });
2008
+
2009
+ expect(spySetIFrameHeight).not.toHaveBeenCalled();
2010
+ });
2011
+
2012
+ test('should update height when change meets threshold', async () => {
2013
+ const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
2014
+ liveboardId,
2015
+ ...defaultViewConfig,
2016
+ fullHeight: true,
2017
+ }) as any;
2018
+
2019
+ liveboardEmbed.iFrame = mockIFrame;
2020
+ document.body.appendChild(mockIFrame);
2021
+ await liveboardEmbed.render();
2022
+
2023
+ const spySetIFrameHeight = jest.spyOn(liveboardEmbed, 'setIFrameHeight');
2024
+
2025
+ // currentHeight is 500; heightToSet = max(700, 500) = 700; change = 200 >= 30
2026
+ liveboardEmbed.updateIFrameHeight({ data: 700, type: EmbedEvent.EmbedHeight });
2027
+
2028
+ expect(spySetIFrameHeight).toHaveBeenCalledWith(700);
2029
+ });
2030
+
2031
+ test('should use defaultHeight when data is below it and apply threshold', async () => {
2032
+ const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
2033
+ liveboardId,
2034
+ ...defaultViewConfig,
2035
+ fullHeight: true,
2036
+ minimumHeight: 800,
2037
+ }) as any;
2038
+
2039
+ liveboardEmbed.iFrame = mockIFrame;
2040
+ document.body.appendChild(mockIFrame);
2041
+ await liveboardEmbed.render();
2042
+
2043
+ const spySetIFrameHeight = jest.spyOn(liveboardEmbed, 'setIFrameHeight');
2044
+
2045
+ // currentHeight is 500; heightToSet = max(100, 800) = 800; change = 300 >= 30
2046
+ liveboardEmbed.updateIFrameHeight({ data: 100, type: EmbedEvent.EmbedHeight });
2047
+
2048
+ expect(spySetIFrameHeight).toHaveBeenCalledWith(800);
2049
+ });
2050
+
2051
+ test('should skip update when height change is exactly at threshold boundary', async () => {
2052
+ const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
2053
+ liveboardId,
2054
+ ...defaultViewConfig,
2055
+ fullHeight: true,
2056
+ }) as any;
2057
+
2058
+ liveboardEmbed.iFrame = mockIFrame;
2059
+ document.body.appendChild(mockIFrame);
2060
+ await liveboardEmbed.render();
2061
+
2062
+ const spySetIFrameHeight = jest.spyOn(liveboardEmbed, 'setIFrameHeight');
2063
+
2064
+ // currentHeight is 500; heightToSet = max(529, 500) = 529; change = 29 < 30
2065
+ liveboardEmbed.updateIFrameHeight({ data: 529, type: EmbedEvent.EmbedHeight });
2066
+
2067
+ expect(spySetIFrameHeight).not.toHaveBeenCalled();
2068
+ });
2069
+ });
2070
+
1947
2071
  describe('Liveboard Embed Default Height and Minimum Height Handling', () => {
1948
2072
  test('should set default height to 800 when minimum height is provided', async () => {
1949
2073
  const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
@@ -732,7 +732,8 @@ export class LiveboardEmbed extends V1Embed {
732
732
  personalizedViewId?: string,
733
733
  ) {
734
734
  // Extract view from liveboardId if passed along with it (legacy
735
- // approach) View must be appended as query param at the end, not
735
+ // approach)
736
+ // View must be appended as query param at the end, not
736
737
  // embedded in path
737
738
  let liveboardGuid = liveboardId;
738
739
  let legacyViewId: string | undefined;
@@ -805,13 +806,21 @@ export class LiveboardEmbed extends V1Embed {
805
806
  )}`;
806
807
  }
807
808
 
809
+ private HEIGHT_CHANAGE_THRESHOLD = 30;
808
810
  /**
809
811
  * Set the iframe height as per the computed height received
810
812
  * from the ThoughtSpot app.
811
813
  * @param data The event payload
812
814
  */
813
815
  private updateIFrameHeight = (data: MessagePayload) => {
814
- this.setIFrameHeight(Math.max(data.data, this.defaultHeight));
816
+ const currentHeight = this.iFrame.getBoundingClientRect().height;
817
+ const heightToSet = Math.max(data.data, this.defaultHeight);
818
+ const heightChange = Math.abs(heightToSet - currentHeight);
819
+ if (heightChange < this.HEIGHT_CHANAGE_THRESHOLD) {
820
+ logger.info('Height change is less than the threshold, skipping height update', { heightChange, heightToSet, currentHeight });
821
+ return;
822
+ }
823
+ this.setIFrameHeight(heightToSet);
815
824
  this.sendFullHeightLazyLoadData();
816
825
  };
817
826
 
package/src/embed/sage.ts CHANGED
@@ -147,7 +147,6 @@ export class SageEmbed extends V1Embed {
147
147
  */
148
148
  protected viewConfig: SageViewConfig;
149
149
 
150
-
151
150
  constructor(domSelector: DOMSelector, viewConfig: SageViewConfig) {
152
151
  viewConfig.embedComponentType = 'SageEmbed';
153
152
  super(domSelector, viewConfig);
@@ -527,7 +527,6 @@ describe('Search embed tests', () => {
527
527
  test('should set dataPanelCustomGroupsAccordionInitialState to EXPAND_FIRST when passed', async () => {
528
528
  const searchEmbed = new SearchBarEmbed(getRootEl() as any, {
529
529
  ...defaultViewConfig,
530
-
531
530
  });
532
531
  searchEmbed.render();
533
532
  await executeAfterWait(() => {
@@ -541,7 +540,6 @@ describe('Search embed tests', () => {
541
540
  test('should set dataPanelCustomGroupsAccordionInitialState to EXPAND_FIRST when passed', async () => {
542
541
  const searchEmbed = new SearchEmbed(getRootEl(), {
543
542
  ...defaultViewConfig,
544
-
545
543
  dataPanelCustomGroupsAccordionInitialState:
546
544
  DataPanelCustomColumnGroupsAccordionState.EXPAND_FIRST,
547
545
  });
@@ -34,7 +34,8 @@ import {
34
34
  setStyleProperties,
35
35
  removeStyleProperties,
36
36
  isUndefined,
37
- getHostEventsConfig } from '../utils';
37
+ getHostEventsConfig,
38
+ } from '../utils';
38
39
  import { getCustomActions } from '../utils/custom-actions';
39
40
  import {
40
41
  getThoughtSpotHost,
@@ -1392,7 +1393,21 @@ export class TsEmbed {
1392
1393
  * Triggers an event to the embedded app
1393
1394
  * @param {HostEvent} messageType The event type
1394
1395
  * @param {any} data The payload to send with the message
1396
+ * @param {ContextType} context Optional context type to specify the context from which the event is triggered.
1397
+ * Use ContextType.Search for search answer context, ContextType.Answer for answer/explore context,
1398
+ * ContextType.Liveboard for liveboard context, or ContextType.Spotter for spotter context.
1399
+ * Available from SDK version 1.45.2 | ThoughtSpot: 26.3.0.cl
1395
1400
  * @returns A promise that resolves with the response from the embedded app
1401
+ * @example
1402
+ * ```js
1403
+ * // Trigger Pin event with context (SDK: 1.45.2+)
1404
+ * import { HostEvent, ContextType } from '@thoughtspot/visual-embed-sdk';
1405
+ * embed.trigger(HostEvent.Pin, {
1406
+ * vizId: "123",
1407
+ * liveboardId: "456"
1408
+ * }, ContextType.Search);
1409
+ * ```
1410
+ * @version SDK: 1.45.2 | ThoughtSpot: 26.3.0.cl (for context parameter)
1396
1411
  */
1397
1412
  public async trigger<HostEventT extends HostEvent, PayloadT, ContextT extends ContextType>(
1398
1413
  messageType: HostEventT,
@@ -1474,8 +1489,33 @@ export class TsEmbed {
1474
1489
  }
1475
1490
 
1476
1491
  /**
1477
- * Get the current context of the embedded TS component.
1478
- * @returns The current context object containing the page type and object ids.
1492
+ * Context object for the embedded component.
1493
+ * @returns {ContextObject} The current context object containing the page type and object ids.
1494
+ * @example
1495
+ * ```js
1496
+ * const context = await embed.getCurrentContext();
1497
+ * console.log(context);
1498
+ *
1499
+ * // Example output
1500
+ * {
1501
+ * stack: [
1502
+ * {
1503
+ * name: 'Liveboard',
1504
+ * type: ContextType.Liveboard,
1505
+ * objectIds: {
1506
+ * liveboardId: '123',
1507
+ * },
1508
+ * },
1509
+ * ],
1510
+ * currentContext: {
1511
+ * name: 'Liveboard',
1512
+ * type: ContextType.Liveboard,
1513
+ * objectIds: {
1514
+ * liveboardId: '123',
1515
+ * },
1516
+ * },
1517
+ * }
1518
+ * ```
1479
1519
  * @version SDK: 1.45.2 | ThoughtSpot: 26.3.0.cl
1480
1520
  */
1481
1521
  public async getCurrentContext(): Promise<ContextObject> {