@thoughtspot/visual-embed-sdk 1.42.1-alpha.1 → 1.42.1-alpha.3

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 (39) hide show
  1. package/cjs/package.json +1 -1
  2. package/cjs/src/api-intercept.d.ts +12 -7
  3. package/cjs/src/api-intercept.d.ts.map +1 -1
  4. package/cjs/src/api-intercept.js +8 -5
  5. package/cjs/src/api-intercept.js.map +1 -1
  6. package/cjs/src/api-intercept.spec.d.ts +2 -0
  7. package/cjs/src/api-intercept.spec.d.ts.map +1 -0
  8. package/cjs/src/api-intercept.spec.js +122 -0
  9. package/cjs/src/api-intercept.spec.js.map +1 -0
  10. package/cjs/src/embed/ts-embed.spec.d.ts.map +1 -1
  11. package/cjs/src/embed/ts-embed.spec.js +85 -0
  12. package/cjs/src/embed/ts-embed.spec.js.map +1 -1
  13. package/dist/index-BCC3Z072.js +7371 -0
  14. package/dist/index-BaESA9rq.js +7371 -0
  15. package/dist/src/api-intercept.d.ts +12 -7
  16. package/dist/src/api-intercept.d.ts.map +1 -1
  17. package/dist/src/api-intercept.spec.d.ts +2 -0
  18. package/dist/src/api-intercept.spec.d.ts.map +1 -0
  19. package/dist/src/embed/ts-embed.spec.d.ts.map +1 -1
  20. package/dist/tsembed-react.es.js +11 -8
  21. package/dist/tsembed-react.js +10 -7
  22. package/dist/tsembed.es.js +11 -8
  23. package/dist/tsembed.js +10 -7
  24. package/lib/package.json +1 -1
  25. package/lib/src/api-intercept.d.ts +12 -7
  26. package/lib/src/api-intercept.d.ts.map +1 -1
  27. package/lib/src/api-intercept.js +8 -5
  28. package/lib/src/api-intercept.js.map +1 -1
  29. package/lib/src/api-intercept.spec.d.ts +2 -0
  30. package/lib/src/api-intercept.spec.d.ts.map +1 -0
  31. package/lib/src/api-intercept.spec.js +119 -0
  32. package/lib/src/api-intercept.spec.js.map +1 -0
  33. package/lib/src/embed/ts-embed.spec.d.ts.map +1 -1
  34. package/lib/src/embed/ts-embed.spec.js +85 -0
  35. package/lib/src/embed/ts-embed.spec.js.map +1 -1
  36. package/package.json +1 -1
  37. package/src/api-intercept.spec.ts +147 -0
  38. package/src/api-intercept.ts +14 -11
  39. package/src/embed/ts-embed.spec.ts +105 -0
@@ -0,0 +1,147 @@
1
+ import { getInterceptInitData, processApiIntercept, handleInterceptEvent, processLegacyInterceptResponse } from './api-intercept';
2
+ import { EmbedEvent, InterceptedApiType } from './types';
3
+ import * as embedCfg from './embed/embedConfig';
4
+ import * as cfg from './config';
5
+ import { logger } from './utils/logger';
6
+
7
+ describe('api-intercept', () => {
8
+ beforeEach(() => {
9
+ jest.restoreAllMocks();
10
+ jest.clearAllMocks();
11
+ });
12
+
13
+ describe('getInterceptInitData', () => {
14
+ test('returns disabled when not enabled in either config', () => {
15
+ const init = getInterceptInitData({} as any, {} as any);
16
+ expect(init).toEqual({ enableApiIntercept: false });
17
+ });
18
+
19
+ test('merges urls and expands api types, formats with host', () => {
20
+ jest.spyOn(embedCfg, 'getEmbedConfig').mockReturnValue({ thoughtSpotHost: 'http://tshost' } as any);
21
+ jest.spyOn(cfg, 'getThoughtSpotHost').mockReturnValue('http://tshost');
22
+
23
+ const embedConfig: any = { enableApiIntercept: true };
24
+ const viewConfig: any = { interceptUrls: [InterceptedApiType.METADATA, '/custom/path'] };
25
+
26
+ const init = getInterceptInitData(embedConfig, viewConfig);
27
+
28
+ expect(init.enableApiIntercept).toBe(true);
29
+ expect(init.interceptUrls).toEqual(expect.arrayContaining([
30
+ 'http://tshost/prism/?op=CreateAnswerSession',
31
+ 'http://tshost/prism/?op=GetV2SourceDetail',
32
+ 'http://tshost/custom/path',
33
+ ]));
34
+ });
35
+
36
+ test('honors InterceptedApiType.ALL and interceptTimeout', () => {
37
+ const embedConfig: any = { enableApiIntercept: true, interceptTimeout: 2500 };
38
+ const viewConfig: any = { interceptUrls: [InterceptedApiType.ALL] };
39
+
40
+ const init = getInterceptInitData(embedConfig, viewConfig);
41
+
42
+ expect(init).toEqual({
43
+ enableApiIntercept: true,
44
+ interceptUrls: [InterceptedApiType.ALL],
45
+ interceptTimeout: 2500,
46
+ });
47
+ });
48
+
49
+ test('legacy flag isOnBeforeGetVizDataInterceptEnabled adds ANSWER_DATA', () => {
50
+ jest.spyOn(embedCfg, 'getEmbedConfig').mockReturnValue({ thoughtSpotHost: 'http://tshost' } as any);
51
+ jest.spyOn(cfg, 'getThoughtSpotHost').mockReturnValue('http://tshost');
52
+
53
+ const embedConfig: any = { enableApiIntercept: true };
54
+ const viewConfig: any = { isOnBeforeGetVizDataInterceptEnabled: true, interceptUrls: [] };
55
+
56
+ const init = getInterceptInitData(embedConfig, viewConfig);
57
+
58
+ // Should expand to concrete ANSWER_DATA URLs
59
+ expect(init.interceptUrls).toEqual(expect.arrayContaining([
60
+ 'http://tshost/prism/?op=GetChartWithData',
61
+ 'http://tshost/prism/?op=GetTableWithHeadlineData',
62
+ ]));
63
+ });
64
+
65
+ test('viewConfig enableApiIntercept=false overrides embed config', () => {
66
+ const embedConfig: any = { enableApiIntercept: true };
67
+ const viewConfig: any = { enableApiIntercept: false };
68
+ const init = getInterceptInitData(embedConfig, viewConfig);
69
+ expect(init).toEqual({ enableApiIntercept: false });
70
+ });
71
+ });
72
+
73
+ describe('processApiIntercept', () => {
74
+ test('parses data JSON string', async () => {
75
+ const result = await processApiIntercept({ data: JSON.stringify({ foo: 'bar' }) });
76
+ expect(result).toEqual({ foo: 'bar' });
77
+ });
78
+ });
79
+
80
+ describe('handleInterceptEvent', () => {
81
+ test('emits error on body parse failure', async () => {
82
+ const executeEvent = jest.fn();
83
+ const loggerErrSpy = jest.spyOn(logger, 'error').mockImplementation(() => undefined as any);
84
+ await handleInterceptEvent({
85
+ eventData: { data: 'not-a-json' },
86
+ executeEvent,
87
+ embedConfig: {} as any,
88
+ viewConfig: {} as any,
89
+ getUnsavedAnswerTml: jest.fn(),
90
+ });
91
+ expect(executeEvent).toHaveBeenCalledWith(EmbedEvent.Error, { error: 'Error parsing api intercept body' });
92
+ loggerErrSpy.mockReset();
93
+ });
94
+
95
+ test('calls getUnsavedAnswerTml and emits legacy + ApiIntercept events when applicable', async () => {
96
+ const executeEvent = jest.fn();
97
+ const getUnsavedAnswerTml = jest.fn().mockResolvedValue({ tml: 'TML_STRING' });
98
+
99
+ const eventPayload = {
100
+ input: '/prism/?op=GetChartWithData',
101
+ init: {
102
+ body: JSON.stringify({
103
+ variables: {
104
+ session: { sessionId: 'S1' },
105
+ contextBookId: 'V1',
106
+ },
107
+ }),
108
+ },
109
+ };
110
+
111
+ await handleInterceptEvent({
112
+ eventData: { data: JSON.stringify(eventPayload) },
113
+ executeEvent,
114
+ embedConfig: {} as any,
115
+ viewConfig: { isOnBeforeGetVizDataInterceptEnabled: true } as any,
116
+ getUnsavedAnswerTml,
117
+ });
118
+
119
+ expect(getUnsavedAnswerTml).toHaveBeenCalledWith({ sessionId: 'S1', vizId: 'V1' });
120
+ expect(executeEvent).toHaveBeenCalledWith(
121
+ EmbedEvent.OnBeforeGetVizDataIntercept,
122
+ { data: { data: { tml: 'TML_STRING' } } },
123
+ );
124
+ expect(executeEvent).toHaveBeenCalledWith(
125
+ EmbedEvent.ApiIntercept,
126
+ expect.objectContaining({ input: '/prism/?op=GetChartWithData' }),
127
+ );
128
+ });
129
+ });
130
+
131
+ describe('processLegacyInterceptResponse', () => {
132
+ test('wraps legacy error payload as expected', () => {
133
+ const payload = { data: { errorText: 'Title', errorDescription: 'Desc' } } as any;
134
+ const res = processLegacyInterceptResponse(payload);
135
+ expect(Array.isArray(res)).toBe(true);
136
+ expect(res[0]).toEqual(expect.objectContaining({
137
+ errors: [
138
+ expect.objectContaining({
139
+ errorObj: expect.objectContaining({ title: 'Title', desc: 'Desc' }),
140
+ }),
141
+ ],
142
+ }));
143
+ });
144
+ });
145
+ });
146
+
147
+
@@ -118,17 +118,20 @@ export const processLegacyInterceptResponse = (payload: any) => {
118
118
  const title = payload?.data?.errorText;
119
119
  const desc = payload?.data?.errorDescription;
120
120
 
121
- const payloadToSend = [{
122
- data: {},
123
- errors: [
124
- {
125
- errorObj: {
126
- title,
127
- desc
121
+ const payloadToSend = {
122
+ execute: payload?.data?.execute,
123
+ body: {
124
+ errors: [
125
+ {
126
+ errorObj: {
127
+ title,
128
+ desc
129
+ }
128
130
  }
129
- }
130
- ],
131
- }];
131
+ ],
132
+ },
133
+ status: 200,
134
+ };
132
135
 
133
- return payloadToSend;
136
+ return { data: payloadToSend };
134
137
  }
@@ -61,6 +61,14 @@ import { UIPassthroughEvent } from './hostEventClient/contracts';
61
61
  import * as sessionInfoService from '../utils/sessionInfoService';
62
62
  import * as authToken from '../authToken';
63
63
 
64
+ // Mock api-intercept by default to avoid altering existing APP_INIT payload
65
+ // expectations
66
+ jest.mock('../api-intercept', () => ({
67
+ getInterceptInitData: jest.fn(() => ({})),
68
+ handleInterceptEvent: jest.fn(),
69
+ processLegacyInterceptResponse: jest.fn((x: any) => x),
70
+ }));
71
+
64
72
  jest.mock('../utils/processTrigger');
65
73
 
66
74
  const mockProcessTrigger = processTrigger as jest.Mock;
@@ -3457,3 +3465,100 @@ describe('Unit test case for ts embed', () => {
3457
3465
  });
3458
3466
  });
3459
3467
  });
3468
+
3469
+ describe('API Intercept integration in TsEmbed', () => {
3470
+ beforeEach(() => {
3471
+ document.body.innerHTML = getDocumentBody();
3472
+ jest.clearAllMocks();
3473
+ });
3474
+
3475
+ test('APP_INIT includes API intercept init data when enabled', async () => {
3476
+ init({
3477
+ thoughtSpotHost: 'tshost',
3478
+ authType: AuthType.None,
3479
+ enableApiIntercept: true,
3480
+ interceptTimeout: 1234,
3481
+ } as any);
3482
+
3483
+ // Include intercept setup via viewConfig too
3484
+ const { InterceptedApiType } = require('../types');
3485
+
3486
+ // Override mocked getInterceptInitData for this test only
3487
+ const apiInterceptModule = require('../api-intercept');
3488
+ apiInterceptModule.getInterceptInitData.mockReturnValue({
3489
+ enableApiIntercept: true,
3490
+ interceptTimeout: 1234,
3491
+ interceptUrls: [
3492
+ 'http://tshost/prism/?op=CreateAnswerSession',
3493
+ 'http://tshost/prism/?op=GetV2SourceDetail',
3494
+ 'http://tshost/custom/path',
3495
+ ],
3496
+ });
3497
+ const searchEmbed = new SearchEmbed(getRootEl(), {
3498
+ ...defaultViewConfig,
3499
+ enableApiIntercept: true,
3500
+ interceptUrls: [InterceptedApiType.METADATA, '/custom/path'],
3501
+ } as any);
3502
+
3503
+ searchEmbed.render();
3504
+
3505
+ const mockPort: any = { postMessage: jest.fn() };
3506
+ const mockEmbedEventPayload = { type: EmbedEvent.APP_INIT, data: {} };
3507
+
3508
+ await executeAfterWait(() => {
3509
+ const iframe = getIFrameEl();
3510
+ postMessageToParent(iframe.contentWindow, mockEmbedEventPayload, mockPort);
3511
+ });
3512
+
3513
+ await executeAfterWait(() => {
3514
+ expect(mockPort.postMessage).toHaveBeenCalledWith(
3515
+ expect.objectContaining({
3516
+ type: EmbedEvent.APP_INIT,
3517
+ data: expect.objectContaining({
3518
+ enableApiIntercept: true,
3519
+ interceptTimeout: 1234,
3520
+ interceptUrls: expect.arrayContaining([
3521
+ expect.stringContaining('CreateAnswerSession'),
3522
+ expect.stringContaining('GetV2SourceDetail'),
3523
+ expect.stringContaining('custom/path'),
3524
+ ]),
3525
+ }),
3526
+ }),
3527
+ );
3528
+ });
3529
+ });
3530
+
3531
+ test('ApiIntercept event delegates to handleInterceptEvent and bypasses default callbacks', async () => {
3532
+ init({ thoughtSpotHost: 'tshost', authType: AuthType.None } as any);
3533
+
3534
+ const apiInterceptModule = require('../api-intercept');
3535
+ const handleInterceptSpy = jest.spyOn(apiInterceptModule, 'handleInterceptEvent').mockResolvedValue(undefined);
3536
+
3537
+ const searchEmbed = new SearchEmbed(getRootEl(), {
3538
+ ...defaultViewConfig,
3539
+ enableApiIntercept: true,
3540
+ } as any);
3541
+
3542
+ const apiCb = jest.fn();
3543
+ searchEmbed.on(EmbedEvent.ApiIntercept as any, apiCb as any);
3544
+
3545
+ searchEmbed.render();
3546
+
3547
+ const mockPort: any = { postMessage: jest.fn() };
3548
+ const eventPayload = {
3549
+ type: EmbedEvent.ApiIntercept,
3550
+ data: JSON.stringify({ input: '/prism/?op=GetChartWithData', init: { body: '{}' } }),
3551
+ };
3552
+
3553
+ await executeAfterWait(() => {
3554
+ const iframe = getIFrameEl();
3555
+ postMessageToParent(iframe.contentWindow, eventPayload, mockPort);
3556
+ });
3557
+
3558
+ await executeAfterWait(() => {
3559
+ expect(handleInterceptSpy).toHaveBeenCalled();
3560
+ expect(apiCb).not.toHaveBeenCalled();
3561
+ });
3562
+ });
3563
+ });
3564
+