@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.
- package/cjs/package.json +4 -4
- package/cjs/src/css-variables.d.ts +36 -0
- package/cjs/src/css-variables.d.ts.map +1 -1
- package/cjs/src/embed/app.d.ts +41 -2
- package/cjs/src/embed/app.d.ts.map +1 -1
- package/cjs/src/embed/app.js +26 -46
- package/cjs/src/embed/app.js.map +1 -1
- package/cjs/src/embed/app.spec.js +36 -69
- package/cjs/src/embed/app.spec.js.map +1 -1
- package/cjs/src/embed/conversation.d.ts +23 -1
- package/cjs/src/embed/conversation.d.ts.map +1 -1
- package/cjs/src/embed/conversation.js +18 -33
- package/cjs/src/embed/conversation.js.map +1 -1
- package/cjs/src/embed/conversation.spec.js +129 -97
- package/cjs/src/embed/conversation.spec.js.map +1 -1
- package/cjs/src/embed/hostEventClient/contracts.d.ts +31 -0
- package/cjs/src/embed/hostEventClient/contracts.d.ts.map +1 -1
- package/cjs/src/embed/hostEventClient/contracts.js +2 -0
- package/cjs/src/embed/hostEventClient/contracts.js.map +1 -1
- package/cjs/src/embed/hostEventClient/host-event-client.d.ts +18 -0
- package/cjs/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
- package/cjs/src/embed/hostEventClient/host-event-client.js +69 -9
- package/cjs/src/embed/hostEventClient/host-event-client.js.map +1 -1
- package/cjs/src/embed/hostEventClient/host-event-client.spec.js +185 -19
- package/cjs/src/embed/hostEventClient/host-event-client.spec.js.map +1 -1
- package/cjs/src/embed/hostEventClient/utils.d.ts +22 -0
- package/cjs/src/embed/hostEventClient/utils.d.ts.map +1 -0
- package/cjs/src/embed/hostEventClient/utils.js +51 -0
- package/cjs/src/embed/hostEventClient/utils.js.map +1 -0
- package/cjs/src/embed/hostEventClient/utils.spec.d.ts +2 -0
- package/cjs/src/embed/hostEventClient/utils.spec.d.ts.map +1 -0
- package/cjs/src/embed/hostEventClient/utils.spec.js +115 -0
- package/cjs/src/embed/hostEventClient/utils.spec.js.map +1 -0
- package/cjs/src/embed/liveboard.d.ts +18 -1
- package/cjs/src/embed/liveboard.d.ts.map +1 -1
- package/cjs/src/embed/liveboard.js +9 -11
- package/cjs/src/embed/liveboard.js.map +1 -1
- package/cjs/src/embed/liveboard.spec.js +29 -71
- package/cjs/src/embed/liveboard.spec.js.map +1 -1
- package/cjs/src/embed/spotter-utils.d.ts +20 -0
- package/cjs/src/embed/spotter-utils.d.ts.map +1 -0
- package/cjs/src/embed/spotter-utils.js +52 -0
- package/cjs/src/embed/spotter-utils.js.map +1 -0
- package/cjs/src/embed/spotter-utils.spec.d.ts +2 -0
- package/cjs/src/embed/spotter-utils.spec.d.ts.map +1 -0
- package/cjs/src/embed/spotter-utils.spec.js +54 -0
- package/cjs/src/embed/spotter-utils.spec.js.map +1 -0
- package/cjs/src/embed/ts-embed.d.ts.map +1 -1
- package/cjs/src/embed/ts-embed.js +13 -1
- package/cjs/src/embed/ts-embed.js.map +1 -1
- package/cjs/src/errors.d.ts +2 -0
- package/cjs/src/errors.d.ts.map +1 -1
- package/cjs/src/errors.js +2 -0
- package/cjs/src/errors.js.map +1 -1
- package/cjs/src/types.d.ts +102 -1
- package/cjs/src/types.d.ts.map +1 -1
- package/cjs/src/types.js +101 -0
- package/cjs/src/types.js.map +1 -1
- package/cjs/src/utils.d.ts +0 -9
- package/cjs/src/utils.d.ts.map +1 -1
- package/cjs/src/utils.js +1 -10
- package/cjs/src/utils.js.map +1 -1
- package/dist/index-ChNydfIz.js +7371 -0
- package/dist/index-DGV_zh53.js +7371 -0
- package/dist/src/css-variables.d.ts +36 -0
- package/dist/src/css-variables.d.ts.map +1 -1
- package/dist/src/embed/app.d.ts +41 -2
- package/dist/src/embed/app.d.ts.map +1 -1
- package/dist/src/embed/conversation.d.ts +23 -1
- package/dist/src/embed/conversation.d.ts.map +1 -1
- package/dist/src/embed/hostEventClient/contracts.d.ts +31 -0
- package/dist/src/embed/hostEventClient/contracts.d.ts.map +1 -1
- package/dist/src/embed/hostEventClient/host-event-client.d.ts +18 -0
- package/dist/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
- package/dist/src/embed/hostEventClient/utils.d.ts +22 -0
- package/dist/src/embed/hostEventClient/utils.d.ts.map +1 -0
- package/dist/src/embed/hostEventClient/utils.spec.d.ts +2 -0
- package/dist/src/embed/hostEventClient/utils.spec.d.ts.map +1 -0
- package/dist/src/embed/liveboard.d.ts +18 -1
- package/dist/src/embed/liveboard.d.ts.map +1 -1
- package/dist/src/embed/spotter-utils.d.ts +20 -0
- package/dist/src/embed/spotter-utils.d.ts.map +1 -0
- package/dist/src/embed/spotter-utils.spec.d.ts +2 -0
- package/dist/src/embed/spotter-utils.spec.d.ts.map +1 -0
- package/dist/src/embed/ts-embed.d.ts.map +1 -1
- package/dist/src/errors.d.ts +2 -0
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/types.d.ts +102 -1
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils.d.ts +0 -9
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/tsembed-react.es.js +324 -110
- package/dist/tsembed-react.js +323 -109
- package/dist/tsembed.es.js +324 -110
- package/dist/tsembed.js +323 -109
- package/dist/visual-embed-sdk-react-full.d.ts +266 -3
- package/dist/visual-embed-sdk-react.d.ts +266 -3
- package/dist/visual-embed-sdk.d.ts +266 -3
- package/lib/package.json +4 -4
- package/lib/src/css-variables.d.ts +36 -0
- package/lib/src/css-variables.d.ts.map +1 -1
- package/lib/src/embed/app.d.ts +41 -2
- package/lib/src/embed/app.d.ts.map +1 -1
- package/lib/src/embed/app.js +28 -48
- package/lib/src/embed/app.js.map +1 -1
- package/lib/src/embed/app.spec.js +36 -69
- package/lib/src/embed/app.spec.js.map +1 -1
- package/lib/src/embed/conversation.d.ts +23 -1
- package/lib/src/embed/conversation.d.ts.map +1 -1
- package/lib/src/embed/conversation.js +19 -34
- package/lib/src/embed/conversation.js.map +1 -1
- package/lib/src/embed/conversation.spec.js +131 -99
- package/lib/src/embed/conversation.spec.js.map +1 -1
- package/lib/src/embed/hostEventClient/contracts.d.ts +31 -0
- package/lib/src/embed/hostEventClient/contracts.d.ts.map +1 -1
- package/lib/src/embed/hostEventClient/contracts.js +2 -0
- package/lib/src/embed/hostEventClient/contracts.js.map +1 -1
- package/lib/src/embed/hostEventClient/host-event-client.d.ts +18 -0
- package/lib/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
- package/lib/src/embed/hostEventClient/host-event-client.js +69 -9
- package/lib/src/embed/hostEventClient/host-event-client.js.map +1 -1
- package/lib/src/embed/hostEventClient/host-event-client.spec.js +185 -19
- package/lib/src/embed/hostEventClient/host-event-client.spec.js.map +1 -1
- package/lib/src/embed/hostEventClient/utils.d.ts +22 -0
- package/lib/src/embed/hostEventClient/utils.d.ts.map +1 -0
- package/lib/src/embed/hostEventClient/utils.js +43 -0
- package/lib/src/embed/hostEventClient/utils.js.map +1 -0
- package/lib/src/embed/hostEventClient/utils.spec.d.ts +2 -0
- package/lib/src/embed/hostEventClient/utils.spec.d.ts.map +1 -0
- package/lib/src/embed/hostEventClient/utils.spec.js +113 -0
- package/lib/src/embed/hostEventClient/utils.spec.js.map +1 -0
- package/lib/src/embed/liveboard.d.ts +18 -1
- package/lib/src/embed/liveboard.d.ts.map +1 -1
- package/lib/src/embed/liveboard.js +9 -11
- package/lib/src/embed/liveboard.js.map +1 -1
- package/lib/src/embed/liveboard.spec.js +29 -71
- package/lib/src/embed/liveboard.spec.js.map +1 -1
- package/lib/src/embed/spotter-utils.d.ts +20 -0
- package/lib/src/embed/spotter-utils.d.ts.map +1 -0
- package/lib/src/embed/spotter-utils.js +47 -0
- package/lib/src/embed/spotter-utils.js.map +1 -0
- package/lib/src/embed/spotter-utils.spec.d.ts +2 -0
- package/lib/src/embed/spotter-utils.spec.d.ts.map +1 -0
- package/lib/src/embed/spotter-utils.spec.js +52 -0
- package/lib/src/embed/spotter-utils.spec.js.map +1 -0
- package/lib/src/embed/ts-embed.d.ts.map +1 -1
- package/lib/src/embed/ts-embed.js +13 -1
- package/lib/src/embed/ts-embed.js.map +1 -1
- package/lib/src/errors.d.ts +2 -0
- package/lib/src/errors.d.ts.map +1 -1
- package/lib/src/errors.js +2 -0
- package/lib/src/errors.js.map +1 -1
- package/lib/src/types.d.ts +102 -1
- package/lib/src/types.d.ts.map +1 -1
- package/lib/src/types.js +101 -0
- package/lib/src/types.js.map +1 -1
- package/lib/src/utils.d.ts +0 -9
- package/lib/src/utils.d.ts.map +1 -1
- package/lib/src/utils.js +0 -8
- package/lib/src/utils.js.map +1 -1
- package/lib/src/visual-embed-sdk.d.ts +266 -3
- package/package.json +4 -4
- package/src/css-variables.ts +45 -0
- package/src/embed/app.spec.ts +51 -92
- package/src/embed/app.ts +60 -64
- package/src/embed/conversation.spec.ts +150 -119
- package/src/embed/conversation.ts +30 -54
- package/src/embed/hostEventClient/contracts.ts +31 -0
- package/src/embed/hostEventClient/host-event-client.spec.ts +260 -19
- package/src/embed/hostEventClient/host-event-client.ts +87 -11
- package/src/embed/hostEventClient/utils.spec.ts +137 -0
- package/src/embed/hostEventClient/utils.ts +61 -0
- package/src/embed/liveboard.spec.ts +38 -93
- package/src/embed/liveboard.ts +28 -10
- package/src/embed/spotter-utils.spec.ts +56 -0
- package/src/embed/spotter-utils.ts +65 -0
- package/src/embed/ts-embed.ts +15 -1
- package/src/errors.ts +2 -0
- package/src/types.ts +104 -0
- 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
|
|
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
|
|
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
|
|
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).
|
|
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(
|
|
405
|
+
expect(mockProcessTrigger).toHaveBeenCalledTimes(3);
|
|
234
406
|
expect(mockProcessTrigger).toHaveBeenNthCalledWith(
|
|
235
|
-
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
16
|
-
|
|
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
|
-
|
|
204
|
-
return customHandler(payload, context as ContextType) as any;
|
|
205
|
-
}
|
|
276
|
+
const passthroughEvent = PASSTHROUGH_MAP[hostEvent];
|
|
206
277
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
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
|
}
|