@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.
- package/cjs/package.json +1 -1
- package/cjs/src/api-intercept.d.ts +12 -7
- package/cjs/src/api-intercept.d.ts.map +1 -1
- package/cjs/src/api-intercept.js +8 -5
- package/cjs/src/api-intercept.js.map +1 -1
- package/cjs/src/api-intercept.spec.d.ts +2 -0
- package/cjs/src/api-intercept.spec.d.ts.map +1 -0
- package/cjs/src/api-intercept.spec.js +122 -0
- package/cjs/src/api-intercept.spec.js.map +1 -0
- package/cjs/src/embed/ts-embed.spec.d.ts.map +1 -1
- package/cjs/src/embed/ts-embed.spec.js +85 -0
- package/cjs/src/embed/ts-embed.spec.js.map +1 -1
- package/dist/index-BCC3Z072.js +7371 -0
- package/dist/index-BaESA9rq.js +7371 -0
- package/dist/src/api-intercept.d.ts +12 -7
- package/dist/src/api-intercept.d.ts.map +1 -1
- package/dist/src/api-intercept.spec.d.ts +2 -0
- package/dist/src/api-intercept.spec.d.ts.map +1 -0
- package/dist/src/embed/ts-embed.spec.d.ts.map +1 -1
- package/dist/tsembed-react.es.js +11 -8
- package/dist/tsembed-react.js +10 -7
- package/dist/tsembed.es.js +11 -8
- package/dist/tsembed.js +10 -7
- package/lib/package.json +1 -1
- package/lib/src/api-intercept.d.ts +12 -7
- package/lib/src/api-intercept.d.ts.map +1 -1
- package/lib/src/api-intercept.js +8 -5
- package/lib/src/api-intercept.js.map +1 -1
- package/lib/src/api-intercept.spec.d.ts +2 -0
- package/lib/src/api-intercept.spec.d.ts.map +1 -0
- package/lib/src/api-intercept.spec.js +119 -0
- package/lib/src/api-intercept.spec.js.map +1 -0
- package/lib/src/embed/ts-embed.spec.d.ts.map +1 -1
- package/lib/src/embed/ts-embed.spec.js +85 -0
- package/lib/src/embed/ts-embed.spec.js.map +1 -1
- package/package.json +1 -1
- package/src/api-intercept.spec.ts +147 -0
- package/src/api-intercept.ts +14 -11
- 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
|
+
|
package/src/api-intercept.ts
CHANGED
|
@@ -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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
+
|