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

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 (180) hide show
  1. package/cjs/package.json +4 -4
  2. package/cjs/src/css-variables.d.ts +36 -0
  3. package/cjs/src/css-variables.d.ts.map +1 -1
  4. package/cjs/src/embed/app.d.ts +41 -2
  5. package/cjs/src/embed/app.d.ts.map +1 -1
  6. package/cjs/src/embed/app.js +26 -46
  7. package/cjs/src/embed/app.js.map +1 -1
  8. package/cjs/src/embed/app.spec.js +36 -69
  9. package/cjs/src/embed/app.spec.js.map +1 -1
  10. package/cjs/src/embed/conversation.d.ts +23 -1
  11. package/cjs/src/embed/conversation.d.ts.map +1 -1
  12. package/cjs/src/embed/conversation.js +18 -33
  13. package/cjs/src/embed/conversation.js.map +1 -1
  14. package/cjs/src/embed/conversation.spec.js +129 -97
  15. package/cjs/src/embed/conversation.spec.js.map +1 -1
  16. package/cjs/src/embed/hostEventClient/contracts.d.ts +31 -0
  17. package/cjs/src/embed/hostEventClient/contracts.d.ts.map +1 -1
  18. package/cjs/src/embed/hostEventClient/contracts.js +2 -0
  19. package/cjs/src/embed/hostEventClient/contracts.js.map +1 -1
  20. package/cjs/src/embed/hostEventClient/host-event-client.d.ts +18 -0
  21. package/cjs/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
  22. package/cjs/src/embed/hostEventClient/host-event-client.js +69 -9
  23. package/cjs/src/embed/hostEventClient/host-event-client.js.map +1 -1
  24. package/cjs/src/embed/hostEventClient/host-event-client.spec.js +185 -19
  25. package/cjs/src/embed/hostEventClient/host-event-client.spec.js.map +1 -1
  26. package/cjs/src/embed/hostEventClient/utils.d.ts +22 -0
  27. package/cjs/src/embed/hostEventClient/utils.d.ts.map +1 -0
  28. package/cjs/src/embed/hostEventClient/utils.js +51 -0
  29. package/cjs/src/embed/hostEventClient/utils.js.map +1 -0
  30. package/cjs/src/embed/hostEventClient/utils.spec.d.ts +2 -0
  31. package/cjs/src/embed/hostEventClient/utils.spec.d.ts.map +1 -0
  32. package/cjs/src/embed/hostEventClient/utils.spec.js +115 -0
  33. package/cjs/src/embed/hostEventClient/utils.spec.js.map +1 -0
  34. package/cjs/src/embed/liveboard.d.ts +18 -1
  35. package/cjs/src/embed/liveboard.d.ts.map +1 -1
  36. package/cjs/src/embed/liveboard.js +9 -11
  37. package/cjs/src/embed/liveboard.js.map +1 -1
  38. package/cjs/src/embed/liveboard.spec.js +29 -71
  39. package/cjs/src/embed/liveboard.spec.js.map +1 -1
  40. package/cjs/src/embed/spotter-utils.d.ts +20 -0
  41. package/cjs/src/embed/spotter-utils.d.ts.map +1 -0
  42. package/cjs/src/embed/spotter-utils.js +52 -0
  43. package/cjs/src/embed/spotter-utils.js.map +1 -0
  44. package/cjs/src/embed/spotter-utils.spec.d.ts +2 -0
  45. package/cjs/src/embed/spotter-utils.spec.d.ts.map +1 -0
  46. package/cjs/src/embed/spotter-utils.spec.js +54 -0
  47. package/cjs/src/embed/spotter-utils.spec.js.map +1 -0
  48. package/cjs/src/embed/ts-embed.d.ts.map +1 -1
  49. package/cjs/src/embed/ts-embed.js +13 -1
  50. package/cjs/src/embed/ts-embed.js.map +1 -1
  51. package/cjs/src/errors.d.ts +2 -0
  52. package/cjs/src/errors.d.ts.map +1 -1
  53. package/cjs/src/errors.js +2 -0
  54. package/cjs/src/errors.js.map +1 -1
  55. package/cjs/src/types.d.ts +102 -1
  56. package/cjs/src/types.d.ts.map +1 -1
  57. package/cjs/src/types.js +101 -0
  58. package/cjs/src/types.js.map +1 -1
  59. package/cjs/src/utils.d.ts +0 -9
  60. package/cjs/src/utils.d.ts.map +1 -1
  61. package/cjs/src/utils.js +1 -10
  62. package/cjs/src/utils.js.map +1 -1
  63. package/dist/index-ChNydfIz.js +7371 -0
  64. package/dist/index-DGV_zh53.js +7371 -0
  65. package/dist/src/css-variables.d.ts +36 -0
  66. package/dist/src/css-variables.d.ts.map +1 -1
  67. package/dist/src/embed/app.d.ts +41 -2
  68. package/dist/src/embed/app.d.ts.map +1 -1
  69. package/dist/src/embed/conversation.d.ts +23 -1
  70. package/dist/src/embed/conversation.d.ts.map +1 -1
  71. package/dist/src/embed/hostEventClient/contracts.d.ts +31 -0
  72. package/dist/src/embed/hostEventClient/contracts.d.ts.map +1 -1
  73. package/dist/src/embed/hostEventClient/host-event-client.d.ts +18 -0
  74. package/dist/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
  75. package/dist/src/embed/hostEventClient/utils.d.ts +22 -0
  76. package/dist/src/embed/hostEventClient/utils.d.ts.map +1 -0
  77. package/dist/src/embed/hostEventClient/utils.spec.d.ts +2 -0
  78. package/dist/src/embed/hostEventClient/utils.spec.d.ts.map +1 -0
  79. package/dist/src/embed/liveboard.d.ts +18 -1
  80. package/dist/src/embed/liveboard.d.ts.map +1 -1
  81. package/dist/src/embed/spotter-utils.d.ts +20 -0
  82. package/dist/src/embed/spotter-utils.d.ts.map +1 -0
  83. package/dist/src/embed/spotter-utils.spec.d.ts +2 -0
  84. package/dist/src/embed/spotter-utils.spec.d.ts.map +1 -0
  85. package/dist/src/embed/ts-embed.d.ts.map +1 -1
  86. package/dist/src/errors.d.ts +2 -0
  87. package/dist/src/errors.d.ts.map +1 -1
  88. package/dist/src/types.d.ts +102 -1
  89. package/dist/src/types.d.ts.map +1 -1
  90. package/dist/src/utils.d.ts +0 -9
  91. package/dist/src/utils.d.ts.map +1 -1
  92. package/dist/tsembed-react.es.js +324 -110
  93. package/dist/tsembed-react.js +323 -109
  94. package/dist/tsembed.es.js +324 -110
  95. package/dist/tsembed.js +323 -109
  96. package/dist/visual-embed-sdk-react-full.d.ts +266 -3
  97. package/dist/visual-embed-sdk-react.d.ts +266 -3
  98. package/dist/visual-embed-sdk.d.ts +266 -3
  99. package/lib/package.json +4 -4
  100. package/lib/src/css-variables.d.ts +36 -0
  101. package/lib/src/css-variables.d.ts.map +1 -1
  102. package/lib/src/embed/app.d.ts +41 -2
  103. package/lib/src/embed/app.d.ts.map +1 -1
  104. package/lib/src/embed/app.js +28 -48
  105. package/lib/src/embed/app.js.map +1 -1
  106. package/lib/src/embed/app.spec.js +36 -69
  107. package/lib/src/embed/app.spec.js.map +1 -1
  108. package/lib/src/embed/conversation.d.ts +23 -1
  109. package/lib/src/embed/conversation.d.ts.map +1 -1
  110. package/lib/src/embed/conversation.js +19 -34
  111. package/lib/src/embed/conversation.js.map +1 -1
  112. package/lib/src/embed/conversation.spec.js +131 -99
  113. package/lib/src/embed/conversation.spec.js.map +1 -1
  114. package/lib/src/embed/hostEventClient/contracts.d.ts +31 -0
  115. package/lib/src/embed/hostEventClient/contracts.d.ts.map +1 -1
  116. package/lib/src/embed/hostEventClient/contracts.js +2 -0
  117. package/lib/src/embed/hostEventClient/contracts.js.map +1 -1
  118. package/lib/src/embed/hostEventClient/host-event-client.d.ts +18 -0
  119. package/lib/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
  120. package/lib/src/embed/hostEventClient/host-event-client.js +69 -9
  121. package/lib/src/embed/hostEventClient/host-event-client.js.map +1 -1
  122. package/lib/src/embed/hostEventClient/host-event-client.spec.js +185 -19
  123. package/lib/src/embed/hostEventClient/host-event-client.spec.js.map +1 -1
  124. package/lib/src/embed/hostEventClient/utils.d.ts +22 -0
  125. package/lib/src/embed/hostEventClient/utils.d.ts.map +1 -0
  126. package/lib/src/embed/hostEventClient/utils.js +43 -0
  127. package/lib/src/embed/hostEventClient/utils.js.map +1 -0
  128. package/lib/src/embed/hostEventClient/utils.spec.d.ts +2 -0
  129. package/lib/src/embed/hostEventClient/utils.spec.d.ts.map +1 -0
  130. package/lib/src/embed/hostEventClient/utils.spec.js +113 -0
  131. package/lib/src/embed/hostEventClient/utils.spec.js.map +1 -0
  132. package/lib/src/embed/liveboard.d.ts +18 -1
  133. package/lib/src/embed/liveboard.d.ts.map +1 -1
  134. package/lib/src/embed/liveboard.js +9 -11
  135. package/lib/src/embed/liveboard.js.map +1 -1
  136. package/lib/src/embed/liveboard.spec.js +29 -71
  137. package/lib/src/embed/liveboard.spec.js.map +1 -1
  138. package/lib/src/embed/spotter-utils.d.ts +20 -0
  139. package/lib/src/embed/spotter-utils.d.ts.map +1 -0
  140. package/lib/src/embed/spotter-utils.js +47 -0
  141. package/lib/src/embed/spotter-utils.js.map +1 -0
  142. package/lib/src/embed/spotter-utils.spec.d.ts +2 -0
  143. package/lib/src/embed/spotter-utils.spec.d.ts.map +1 -0
  144. package/lib/src/embed/spotter-utils.spec.js +52 -0
  145. package/lib/src/embed/spotter-utils.spec.js.map +1 -0
  146. package/lib/src/embed/ts-embed.d.ts.map +1 -1
  147. package/lib/src/embed/ts-embed.js +13 -1
  148. package/lib/src/embed/ts-embed.js.map +1 -1
  149. package/lib/src/errors.d.ts +2 -0
  150. package/lib/src/errors.d.ts.map +1 -1
  151. package/lib/src/errors.js +2 -0
  152. package/lib/src/errors.js.map +1 -1
  153. package/lib/src/types.d.ts +102 -1
  154. package/lib/src/types.d.ts.map +1 -1
  155. package/lib/src/types.js +101 -0
  156. package/lib/src/types.js.map +1 -1
  157. package/lib/src/utils.d.ts +0 -9
  158. package/lib/src/utils.d.ts.map +1 -1
  159. package/lib/src/utils.js +0 -8
  160. package/lib/src/utils.js.map +1 -1
  161. package/lib/src/visual-embed-sdk.d.ts +266 -3
  162. package/package.json +4 -4
  163. package/src/css-variables.ts +45 -0
  164. package/src/embed/app.spec.ts +51 -92
  165. package/src/embed/app.ts +60 -64
  166. package/src/embed/conversation.spec.ts +150 -119
  167. package/src/embed/conversation.ts +30 -54
  168. package/src/embed/hostEventClient/contracts.ts +31 -0
  169. package/src/embed/hostEventClient/host-event-client.spec.ts +260 -19
  170. package/src/embed/hostEventClient/host-event-client.ts +87 -11
  171. package/src/embed/hostEventClient/utils.spec.ts +137 -0
  172. package/src/embed/hostEventClient/utils.ts +61 -0
  173. package/src/embed/liveboard.spec.ts +38 -93
  174. package/src/embed/liveboard.ts +28 -10
  175. package/src/embed/spotter-utils.spec.ts +56 -0
  176. package/src/embed/spotter-utils.ts +65 -0
  177. package/src/embed/ts-embed.ts +15 -1
  178. package/src/errors.ts +2 -0
  179. package/src/types.ts +104 -0
  180. package/src/utils.ts +0 -14
@@ -125,6 +125,8 @@ describe('HostEventClient', () => {
125
125
  });
126
126
 
127
127
  describe('executeHostEvent', () => {
128
+ const mockGetAvailablePassthroughs = () => [{ value: { keys: Object.values(UIPassthroughEvent) } }];
129
+
128
130
  it('should call handleUIPassthroughForHostEvent for Pin event', async () => {
129
131
  const { client, mockIframe } = createHostEventClient();
130
132
  const hostEvent = HostEvent.Pin;
@@ -140,7 +142,9 @@ describe('HostEventClient', () => {
140
142
  },
141
143
  };
142
144
 
143
- mockProcessTrigger.mockResolvedValue([mockResponse]);
145
+ mockProcessTrigger
146
+ .mockResolvedValueOnce(mockGetAvailablePassthroughs())
147
+ .mockResolvedValueOnce([mockResponse]);
144
148
 
145
149
  const result = await client.triggerHostEvent(hostEvent, payload);
146
150
 
@@ -176,7 +180,9 @@ describe('HostEventClient', () => {
176
180
  },
177
181
  refId: 'testVizId',
178
182
  }];
179
- mockProcessTrigger.mockResolvedValue(mockResponse);
183
+ mockProcessTrigger
184
+ .mockResolvedValueOnce(mockGetAvailablePassthroughs())
185
+ .mockResolvedValueOnce(mockResponse);
180
186
  const result = await client.triggerHostEvent(hostEvent, payload);
181
187
 
182
188
  expect(mockProcessTrigger).toHaveBeenCalledWith(
@@ -192,6 +198,168 @@ describe('HostEventClient', () => {
192
198
  expect(result).toEqual({ answerId: 'newAnswer', ...mockResponse[0].value });
193
199
  });
194
200
 
201
+ it('should call handleHostEventWithParam for UpdateFilters event', async () => {
202
+ const { client, mockIframe } = createHostEventClient();
203
+ const hostEvent = HostEvent.UpdateFilters;
204
+ const payload: HostEventRequest<typeof hostEvent> = {
205
+ vizId: 'viz-123',
206
+ filter: {
207
+ column: 'region',
208
+ oper: 'EQ',
209
+ values: ['North'],
210
+ },
211
+ } as any;
212
+ const mockResponse = [{ value: { success: true } }];
213
+ mockProcessTrigger
214
+ .mockResolvedValueOnce(mockGetAvailablePassthroughs())
215
+ .mockResolvedValueOnce(mockResponse);
216
+
217
+ const result = await client.triggerHostEvent(hostEvent, payload);
218
+
219
+ expect(mockProcessTrigger).toHaveBeenCalledWith(
220
+ mockIframe,
221
+ HostEvent.UIPassthrough,
222
+ mockThoughtSpotHost,
223
+ {
224
+ type: UIPassthroughEvent.UpdateFilters,
225
+ parameters: payload,
226
+ },
227
+ undefined,
228
+ );
229
+ expect(result).toEqual({ success: true });
230
+ });
231
+
232
+ it('should call handleHostEventWithParam for DrillDown event', async () => {
233
+ const { client, mockIframe } = createHostEventClient();
234
+ const hostEvent = HostEvent.DrillDown;
235
+ const payload: HostEventRequest<typeof hostEvent> = {
236
+ points: { clickedPoint: 'point-1', selectedPoints: ['sel-1'] },
237
+ autoDrillDown: true,
238
+ } as any;
239
+ const mockResponse = [{ value: { drillDownApplied: true } }];
240
+ mockProcessTrigger
241
+ .mockResolvedValueOnce(mockGetAvailablePassthroughs())
242
+ .mockResolvedValueOnce(mockResponse);
243
+
244
+ const result = await client.triggerHostEvent(hostEvent, payload);
245
+
246
+ expect(mockProcessTrigger).toHaveBeenCalledWith(
247
+ mockIframe,
248
+ HostEvent.UIPassthrough,
249
+ mockThoughtSpotHost,
250
+ {
251
+ type: UIPassthroughEvent.Drilldown,
252
+ parameters: payload,
253
+ },
254
+ undefined,
255
+ );
256
+ expect(result).toEqual({ drillDownApplied: true });
257
+ });
258
+
259
+ it('should accept UpdateFilters with filters array', async () => {
260
+ const { client, mockIframe } = createHostEventClient();
261
+ const payload = {
262
+ filters: [
263
+ { column: '(Sample) Retail - Apparel::city', oper: 'IN', values: ['atlanta'] },
264
+ { column: '(Sample) Retail - Apparel::Region', oper: 'IN', values: ['West', 'Midwest'] },
265
+ ],
266
+ } as any;
267
+ const mockResponse = [{ value: { success: true } }];
268
+ mockProcessTrigger
269
+ .mockResolvedValueOnce(mockGetAvailablePassthroughs())
270
+ .mockResolvedValueOnce(mockResponse);
271
+
272
+ const result = await client.triggerHostEvent(HostEvent.UpdateFilters, payload);
273
+
274
+ expect(mockProcessTrigger).toHaveBeenNthCalledWith(
275
+ 2,
276
+ mockIframe,
277
+ HostEvent.UIPassthrough,
278
+ mockThoughtSpotHost,
279
+ { type: UIPassthroughEvent.UpdateFilters, parameters: payload },
280
+ undefined,
281
+ );
282
+ expect(result).toEqual({ success: true });
283
+ });
284
+
285
+ it('should throw when UpdateFilters payload has no valid filter', async () => {
286
+ const { client } = createHostEventClient();
287
+ const invalidPayload = {} as any;
288
+ mockProcessTrigger.mockResolvedValueOnce(mockGetAvailablePassthroughs());
289
+
290
+ await expect(client.triggerHostEvent(HostEvent.UpdateFilters, invalidPayload))
291
+ .rejects.toThrow('UpdateFilters requires a valid filter or filters array');
292
+ expect(mockProcessTrigger).toHaveBeenCalledTimes(1);
293
+ });
294
+
295
+ it('should pass context to UpdateFilters event', async () => {
296
+ const { client, mockIframe } = createHostEventClient();
297
+ const payload = { vizId: 'viz-1', filter: { column: 'x', oper: 'EQ', values: ['a'] } } as any;
298
+ const context = { answerId: 'ans-1' } as any;
299
+ mockProcessTrigger
300
+ .mockResolvedValueOnce(mockGetAvailablePassthroughs())
301
+ .mockResolvedValueOnce([{ value: {} }]);
302
+
303
+ await client.triggerHostEvent(HostEvent.UpdateFilters, payload, context);
304
+
305
+ expect(mockProcessTrigger).toHaveBeenCalledWith(
306
+ mockIframe,
307
+ HostEvent.UIPassthrough,
308
+ mockThoughtSpotHost,
309
+ { type: UIPassthroughEvent.UpdateFilters, parameters: payload },
310
+ context,
311
+ );
312
+ });
313
+
314
+ it('should throw when DrillDown payload has no valid points', async () => {
315
+ const { client } = createHostEventClient();
316
+ const invalidPayload = {} as any;
317
+ mockProcessTrigger.mockResolvedValueOnce(mockGetAvailablePassthroughs());
318
+
319
+ await expect(client.triggerHostEvent(HostEvent.DrillDown, invalidPayload))
320
+ .rejects.toThrow('DrillDown requires a valid points object');
321
+ expect(mockProcessTrigger).toHaveBeenCalledTimes(1);
322
+ });
323
+
324
+ it('should pass context to DrillDown event', async () => {
325
+ const { client, mockIframe } = createHostEventClient();
326
+ const payload = { points: { clickedPoint: 'point-1' }, vizId: 'viz-2' } as any;
327
+ const context = { liveboardId: 'lb-1' } as any;
328
+ mockProcessTrigger
329
+ .mockResolvedValueOnce(mockGetAvailablePassthroughs())
330
+ .mockResolvedValueOnce([{ value: {} }]);
331
+
332
+ await client.triggerHostEvent(HostEvent.DrillDown, payload, context);
333
+
334
+ expect(mockProcessTrigger).toHaveBeenCalledWith(
335
+ mockIframe,
336
+ HostEvent.UIPassthrough,
337
+ mockThoughtSpotHost,
338
+ { type: UIPassthroughEvent.Drilldown, parameters: payload },
339
+ context,
340
+ );
341
+ });
342
+
343
+ it('should skip to fallback when passthrough is not in available keys', async () => {
344
+ const { client, mockIframe } = createHostEventClient();
345
+ mockProcessTrigger
346
+ .mockResolvedValueOnce([{ value: { keys: ['getFilters'] } }])
347
+ .mockResolvedValueOnce({ session: 'legacySession' });
348
+
349
+ const result = await client.triggerHostEvent(HostEvent.GetAnswerSession, { vizId: '123' });
350
+
351
+ expect(mockProcessTrigger).toHaveBeenCalledTimes(2);
352
+ expect(mockProcessTrigger).toHaveBeenNthCalledWith(
353
+ 2,
354
+ mockIframe,
355
+ HostEvent.GetAnswerSession,
356
+ mockThoughtSpotHost,
357
+ { vizId: '123' },
358
+ undefined,
359
+ );
360
+ expect(result).toEqual({ session: 'legacySession' });
361
+ });
362
+
195
363
  it('should call hostEventFallback for unmapped events', async () => {
196
364
  const { client } = createHostEventClient();
197
365
  const hostEvent = 'testEvent' as HostEvent;
@@ -208,11 +376,14 @@ describe('HostEventClient', () => {
208
376
  it('should route GetAnswerSession through passthrough and return data', async () => {
209
377
  const { client, mockIframe } = createHostEventClient();
210
378
  const mockResponse = [{ value: { session: 'testSession', embedAnswerData: { id: '1' } } }];
211
- mockProcessTrigger.mockResolvedValue(mockResponse);
379
+ mockProcessTrigger
380
+ .mockResolvedValueOnce(mockGetAvailablePassthroughs())
381
+ .mockResolvedValueOnce(mockResponse);
212
382
 
213
383
  const result = await client.triggerHostEvent(HostEvent.GetAnswerSession, { vizId: '123' });
214
384
 
215
- expect(mockProcessTrigger).toHaveBeenCalledWith(
385
+ expect(mockProcessTrigger).toHaveBeenNthCalledWith(
386
+ 2,
216
387
  mockIframe,
217
388
  HostEvent.UIPassthrough,
218
389
  mockThoughtSpotHost,
@@ -225,14 +396,15 @@ describe('HostEventClient', () => {
225
396
  it('should fall back to legacy host event when passthrough returns no data for GetAnswerSession', async () => {
226
397
  const { client } = createHostEventClient();
227
398
  mockProcessTrigger
399
+ .mockResolvedValueOnce(mockGetAvailablePassthroughs())
228
400
  .mockResolvedValueOnce([])
229
401
  .mockResolvedValueOnce({ session: 'fallbackSession' });
230
402
 
231
403
  const result = await client.triggerHostEvent(HostEvent.GetAnswerSession, { vizId: '123' });
232
404
 
233
- expect(mockProcessTrigger).toHaveBeenCalledTimes(2);
405
+ expect(mockProcessTrigger).toHaveBeenCalledTimes(3);
234
406
  expect(mockProcessTrigger).toHaveBeenNthCalledWith(
235
- 2,
407
+ 3,
236
408
  expect.anything(),
237
409
  HostEvent.GetAnswerSession,
238
410
  mockThoughtSpotHost,
@@ -244,17 +416,21 @@ describe('HostEventClient', () => {
244
416
 
245
417
  it('should throw real errors from passthrough without falling back', async () => {
246
418
  const { client } = createHostEventClient();
247
- mockProcessTrigger.mockResolvedValue([{ error: 'Permission denied' }]);
419
+ mockProcessTrigger
420
+ .mockResolvedValueOnce(mockGetAvailablePassthroughs())
421
+ .mockResolvedValueOnce([{ error: 'Permission denied' }]);
248
422
 
249
423
  await expect(client.triggerHostEvent(HostEvent.GetAnswerSession, {}))
250
424
  .rejects.toThrow('Permission denied');
251
- expect(mockProcessTrigger).toHaveBeenCalledTimes(1);
425
+ expect(mockProcessTrigger).toHaveBeenCalledTimes(2);
252
426
  });
253
427
 
254
428
  it('should route GetFilters through passthrough and return data', async () => {
255
429
  const { client, mockIframe } = createHostEventClient();
256
430
  const mockResponse = [{ value: { liveboardFilters: [{ id: 'f1' }], runtimeFilters: [] as any[] } }];
257
- mockProcessTrigger.mockResolvedValue(mockResponse);
431
+ mockProcessTrigger
432
+ .mockResolvedValueOnce(mockGetAvailablePassthroughs())
433
+ .mockResolvedValueOnce(mockResponse);
258
434
 
259
435
  const result = await client.triggerHostEvent(HostEvent.GetFilters, {});
260
436
 
@@ -271,7 +447,9 @@ describe('HostEventClient', () => {
271
447
  it('should route GetTabs through passthrough and return data', async () => {
272
448
  const { client } = createHostEventClient();
273
449
  const mockResponse = [{ value: { orderedTabIds: ['t1', 't2'], numberOfTabs: 2, Tabs: [] as any[] } }];
274
- mockProcessTrigger.mockResolvedValue(mockResponse);
450
+ mockProcessTrigger
451
+ .mockResolvedValueOnce(mockGetAvailablePassthroughs())
452
+ .mockResolvedValueOnce(mockResponse);
275
453
 
276
454
  const result = await client.triggerHostEvent(HostEvent.GetTabs, {});
277
455
 
@@ -281,7 +459,9 @@ describe('HostEventClient', () => {
281
459
  it('should route GetTML through passthrough and return data', async () => {
282
460
  const { client } = createHostEventClient();
283
461
  const tmlData = { answer: { search_query: 'revenue by region' } };
284
- mockProcessTrigger.mockResolvedValue([{ value: tmlData }]);
462
+ mockProcessTrigger
463
+ .mockResolvedValueOnce(mockGetAvailablePassthroughs())
464
+ .mockResolvedValueOnce([{ value: tmlData }]);
285
465
 
286
466
  const result = await client.triggerHostEvent(HostEvent.GetTML, {});
287
467
 
@@ -290,7 +470,9 @@ describe('HostEventClient', () => {
290
470
 
291
471
  it('should route GetIframeUrl through passthrough and return data', async () => {
292
472
  const { client } = createHostEventClient();
293
- mockProcessTrigger.mockResolvedValue([{ value: { iframeUrl: 'https://ts.example.com/embed' } }]);
473
+ mockProcessTrigger
474
+ .mockResolvedValueOnce(mockGetAvailablePassthroughs())
475
+ .mockResolvedValueOnce([{ value: { iframeUrl: 'https://ts.example.com/embed' } }]);
294
476
 
295
477
  const result = await client.triggerHostEvent(HostEvent.GetIframeUrl, {});
296
478
 
@@ -299,7 +481,9 @@ describe('HostEventClient', () => {
299
481
 
300
482
  it('should route GetParameters through passthrough and return data', async () => {
301
483
  const { client } = createHostEventClient();
302
- mockProcessTrigger.mockResolvedValue([{ value: { parameters: [{ name: 'p1' }] } }]);
484
+ mockProcessTrigger
485
+ .mockResolvedValueOnce(mockGetAvailablePassthroughs())
486
+ .mockResolvedValueOnce([{ value: { parameters: [{ name: 'p1' }] } }]);
303
487
 
304
488
  const result = await client.triggerHostEvent(HostEvent.GetParameters, {});
305
489
 
@@ -308,7 +492,9 @@ describe('HostEventClient', () => {
308
492
 
309
493
  it('should route getExportRequestForCurrentPinboard through passthrough and return data', async () => {
310
494
  const { client } = createHostEventClient();
311
- mockProcessTrigger.mockResolvedValue([{ value: { v2Content: 'exportData' } }]);
495
+ mockProcessTrigger
496
+ .mockResolvedValueOnce(mockGetAvailablePassthroughs())
497
+ .mockResolvedValueOnce([{ value: { v2Content: 'exportData' } }]);
312
498
 
313
499
  const result = await client.triggerHostEvent(HostEvent.getExportRequestForCurrentPinboard, {});
314
500
 
@@ -318,12 +504,13 @@ describe('HostEventClient', () => {
318
504
  it('should fall back to legacy for GetFilters when passthrough returns null', async () => {
319
505
  const { client } = createHostEventClient();
320
506
  mockProcessTrigger
507
+ .mockResolvedValueOnce(mockGetAvailablePassthroughs())
321
508
  .mockResolvedValueOnce(null)
322
509
  .mockResolvedValueOnce({ liveboardFilters: [], runtimeFilters: [] });
323
510
 
324
511
  const result = await client.triggerHostEvent(HostEvent.GetFilters, {});
325
512
 
326
- expect(mockProcessTrigger).toHaveBeenCalledTimes(2);
513
+ expect(mockProcessTrigger).toHaveBeenCalledTimes(3);
327
514
  expect(result).toEqual({ liveboardFilters: [], runtimeFilters: [] });
328
515
  });
329
516
 
@@ -339,7 +526,9 @@ describe('HostEventClient', () => {
339
526
  },
340
527
  };
341
528
 
342
- mockProcessTrigger.mockResolvedValue([mockResponse]);
529
+ mockProcessTrigger
530
+ .mockResolvedValueOnce(mockGetAvailablePassthroughs())
531
+ .mockResolvedValueOnce([mockResponse]);
343
532
 
344
533
  const result = await client.triggerHostEvent(hostEvent, payload);
345
534
 
@@ -365,7 +554,9 @@ describe('HostEventClient', () => {
365
554
  },
366
555
  };
367
556
 
368
- mockProcessTrigger.mockResolvedValue([mockResponse]);
557
+ mockProcessTrigger
558
+ .mockResolvedValueOnce(mockGetAvailablePassthroughs())
559
+ .mockResolvedValueOnce([mockResponse]);
369
560
 
370
561
  const result = await client.triggerHostEvent(hostEvent, payload);
371
562
 
@@ -397,7 +588,9 @@ describe('HostEventClient', () => {
397
588
  },
398
589
  refId: 'testVizId',
399
590
  }];
400
- mockProcessTrigger.mockResolvedValue(mockResponse);
591
+ mockProcessTrigger
592
+ .mockResolvedValueOnce(mockGetAvailablePassthroughs())
593
+ .mockResolvedValueOnce(mockResponse);
401
594
  const result = await client.triggerHostEvent(hostEvent, payload);
402
595
  expect(result.liveboardId).toBe('testLiveboard');
403
596
  });
@@ -419,7 +612,9 @@ describe('HostEventClient', () => {
419
612
  },
420
613
  refId: 'testVizId',
421
614
  }];
422
- mockProcessTrigger.mockResolvedValue(mockResponse);
615
+ mockProcessTrigger
616
+ .mockResolvedValueOnce(mockGetAvailablePassthroughs())
617
+ .mockResolvedValueOnce(mockResponse);
423
618
  const result = await client.triggerHostEvent(hostEvent, payload);
424
619
  expect(result.liveboardId).toBe('testLiveboard');
425
620
  expect(mockProcessTrigger).toHaveBeenCalledWith(
@@ -575,4 +770,50 @@ describe('HostEventClient', () => {
575
770
  );
576
771
  });
577
772
  });
773
+
774
+ describe('UI passthrough available keys tests', () => {
775
+ const mockKeys = () => [{ value: { keys: Object.values(UIPassthroughEvent) } }];
776
+
777
+ it('triggerHostEvent Pin returns passthrough response', async () => {
778
+ const { client } = createHostEventClient();
779
+ mockProcessTrigger
780
+ .mockResolvedValueOnce(mockKeys())
781
+ .mockResolvedValueOnce([{ value: { pinboardId: 'lb1', tabId: 't1', vizId: 'v1' } }]);
782
+
783
+ const result = await client.triggerHostEvent(HostEvent.Pin, { newVizName: 'Viz' });
784
+
785
+ expect(result).toMatchObject({ pinboardId: 'lb1', liveboardId: 'lb1' });
786
+ });
787
+
788
+ it('triggerHostEvent GetAnswerSession returns session', async () => {
789
+ const { client } = createHostEventClient();
790
+ mockProcessTrigger
791
+ .mockResolvedValueOnce(mockKeys())
792
+ .mockResolvedValueOnce([{ value: { session: 's1' } }]);
793
+
794
+ const result = await client.triggerHostEvent(HostEvent.GetAnswerSession, {});
795
+
796
+ expect(result).toEqual({ session: 's1' });
797
+ });
798
+
799
+ it('triggerHostEvent unmapped event uses fallback', async () => {
800
+ const { client } = createHostEventClient();
801
+ mockProcessTrigger.mockResolvedValue({ data: 'legacy' });
802
+
803
+ const result = await client.triggerHostEvent('unknownEvent' as HostEvent, {});
804
+
805
+ expect(mockProcessTrigger).toHaveBeenCalledWith(expect.anything(), 'unknownEvent', mockThoughtSpotHost, {}, undefined);
806
+ expect(result).toEqual({ data: 'legacy' });
807
+ });
808
+
809
+ it('hostEventFallback delegates to processTrigger', async () => {
810
+ const { client, mockIframe } = createHostEventClient();
811
+ mockProcessTrigger.mockResolvedValue({ ok: true });
812
+
813
+ const result = await client.hostEventFallback(HostEvent.Save, { x: 1 });
814
+
815
+ expect(mockProcessTrigger).toHaveBeenCalledWith(mockIframe, HostEvent.Save, mockThoughtSpotHost, { x: 1 }, undefined);
816
+ expect(result).toEqual({ ok: true });
817
+ });
818
+ });
578
819
  });
@@ -1,6 +1,12 @@
1
1
  import { ContextType, HostEvent } from '../../types';
2
2
  import { processTrigger as processTriggerService } from '../../utils/processTrigger';
3
3
  import { getEmbedConfig } from '../embedConfig';
4
+ import {
5
+ isValidUpdateFiltersPayload,
6
+ isValidDrillDownPayload,
7
+ throwUpdateFiltersValidationError,
8
+ throwDrillDownValidationError,
9
+ } from './utils';
4
10
  import {
5
11
  UIPassthroughArrayResponse,
6
12
  UIPassthroughEvent,
@@ -12,8 +18,18 @@ import {
12
18
  TriggerResponse,
13
19
  } from './contracts';
14
20
 
15
- /** Host events that use getDataWithPassthroughFallback (getter-style APIs) */
16
- const HOST_EVENT_PASSTHROUGH_MAP: Partial<Record<HostEvent, UIPassthroughEvent>> = {
21
+ /**
22
+ * Maps HostEvent to its corresponding UIPassthroughEvent.
23
+ * Includes both custom-handler events (Pin, SaveAnswer, UpdateFilters, DrillDown)
24
+ * and getter events (GetAnswerSession, GetFilters, etc.) that use getDataWithPassthroughFallback.
25
+ */
26
+ const PASSTHROUGH_MAP: Partial<Record<HostEvent, UIPassthroughEvent>> = {
27
+ // Custom handlers (setters with special logic)
28
+ [HostEvent.Pin]: UIPassthroughEvent.PinAnswerToLiveboard,
29
+ [HostEvent.SaveAnswer]: UIPassthroughEvent.SaveAnswer,
30
+ [HostEvent.UpdateFilters]: UIPassthroughEvent.UpdateFilters,
31
+ [HostEvent.DrillDown]: UIPassthroughEvent.Drilldown,
32
+ // Getters (use getDataWithPassthroughFallback)
17
33
  [HostEvent.GetAnswerSession]: UIPassthroughEvent.GetAnswerSession,
18
34
  [HostEvent.GetFilters]: UIPassthroughEvent.GetFilters,
19
35
  [HostEvent.GetIframeUrl]: UIPassthroughEvent.GetIframeUrl,
@@ -26,6 +42,9 @@ const HOST_EVENT_PASSTHROUGH_MAP: Partial<Record<HostEvent, UIPassthroughEvent>>
26
42
  export class HostEventClient {
27
43
  iFrame: HTMLIFrameElement;
28
44
 
45
+ /** Cached list of available UI passthrough keys from the embedded app */
46
+ private availablePassthroughKeysCache: string[] | null = null;
47
+
29
48
  /** Host events with custom handlers
30
49
  * (setters or special logic) -
31
50
  * bound to instance for protected method access */
@@ -38,6 +57,8 @@ export class HostEventClient {
38
57
  this.customHandlers = {
39
58
  [HostEvent.Pin]: (p, c) => this.handlePinEvent(p, c),
40
59
  [HostEvent.SaveAnswer]: (p, c) => this.handleSaveAnswerEvent(p, c),
60
+ [HostEvent.UpdateFilters]: (p, c) => this.handleUpdateFiltersEvent(p, c),
61
+ [HostEvent.DrillDown]: (p, c) => this.handleDrillDownEvent(p, c),
41
62
  };
42
63
  }
43
64
 
@@ -133,6 +154,27 @@ export class HostEventClient {
133
154
  this.iFrame = iFrame;
134
155
  }
135
156
 
157
+ /**
158
+ * Fetches the list of available UI passthrough keys from the embedded app.
159
+ * Result is cached for the session. Returns empty array on failure.
160
+ */
161
+ private async getAvailableUIPassthroughKeys(context?: ContextType): Promise<string[]> {
162
+ if (this.availablePassthroughKeysCache !== null) {
163
+ return this.availablePassthroughKeysCache;
164
+ }
165
+ try {
166
+ const response = await this.triggerUIPassthroughApi(
167
+ UIPassthroughEvent.GetAvailableUIPassthroughs, {}, context,
168
+ );
169
+ const matched = response?.find?.((r) => r.value && !r.error);
170
+ const keys = matched?.value?.keys;
171
+ this.availablePassthroughKeysCache = Array.isArray(keys) ? keys : [];
172
+ return this.availablePassthroughKeysCache;
173
+ } catch {
174
+ return [];
175
+ }
176
+ }
177
+
136
178
  public async triggerUIPassthroughApi<UIPassthroughEventT extends UIPassthroughEvent>(
137
179
  apiName: UIPassthroughEventT,
138
180
  parameters: UIPassthroughRequest<UIPassthroughEventT>,
@@ -190,6 +232,37 @@ export class HostEventClient {
190
232
  };
191
233
  }
192
234
 
235
+ protected handleUpdateFiltersEvent(
236
+ payload: HostEventRequest<HostEvent.UpdateFilters>,
237
+ context?: ContextType,
238
+ ): Promise<any> {
239
+ if (!isValidUpdateFiltersPayload(payload)) {
240
+ throwUpdateFiltersValidationError();
241
+ }
242
+
243
+ return this.handleHostEventWithParam(UIPassthroughEvent.UpdateFilters, payload, context as ContextType);
244
+ }
245
+
246
+ protected handleDrillDownEvent(
247
+ payload: HostEventRequest<HostEvent.DrillDown>,
248
+ context?: ContextType,
249
+ ): Promise<any> {
250
+ if (!isValidDrillDownPayload(payload)) {
251
+ throwDrillDownValidationError();
252
+ }
253
+
254
+ return this.handleHostEventWithParam(UIPassthroughEvent.Drilldown, payload, context as ContextType);
255
+ }
256
+
257
+ /**
258
+ * Dispatches a host event using the appropriate channel:
259
+ * 1. If the embedded app supports UI passthrough for this event, use it (custom handler or getter).
260
+ * 2. Otherwise fall back to the legacy host event channel.
261
+ *
262
+ * @param hostEvent - The host event to trigger
263
+ * @param payload - Optional payload for the event
264
+ * @param context - Optional context (e.g. vizId) for scoped operations
265
+ */
193
266
  public async triggerHostEvent<
194
267
  HostEventT extends HostEvent,
195
268
  PayloadT,
@@ -200,17 +273,20 @@ export class HostEventClient {
200
273
  context?: ContextT,
201
274
  ): Promise<TriggerResponse<PayloadT, HostEventT, ContextType>> {
202
275
  const customHandler = this.customHandlers[hostEvent];
203
- if (customHandler) {
204
- return customHandler(payload, context as ContextType) as any;
205
- }
276
+ const passthroughEvent = PASSTHROUGH_MAP[hostEvent];
206
277
 
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;
278
+ // If embedded app supports passthrough but not this event, use legacy channel
279
+ const keys = passthroughEvent ? await this.getAvailableUIPassthroughKeys(context as ContextType) : [];
280
+ if (passthroughEvent && keys.length > 0 && !keys.includes(passthroughEvent)) {
281
+ return this.hostEventFallback(hostEvent, payload, context) as any;
212
282
  }
213
283
 
214
- return this.hostEventFallback(hostEvent, payload, context);
284
+ // Custom handler (setters) > getter passthrough > legacy fallback
285
+ return (customHandler
286
+ ? customHandler(payload, context as ContextType)
287
+ : passthroughEvent
288
+ ? this.getDataWithPassthroughFallback(passthroughEvent, hostEvent, payload, context as ContextType)
289
+ : this.hostEventFallback(hostEvent, payload, context)
290
+ ) as any;
215
291
  }
216
292
  }