@thoughtspot/visual-embed-sdk 1.42.0 → 1.42.1-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cjs/package.json +2 -2
- package/cjs/src/api-intercept.d.ts +25 -0
- package/cjs/src/api-intercept.d.ts.map +1 -0
- package/cjs/src/api-intercept.js +115 -0
- package/cjs/src/api-intercept.js.map +1 -0
- package/cjs/src/embed/app.d.ts.map +1 -1
- package/cjs/src/embed/app.js +7 -2
- package/cjs/src/embed/app.js.map +1 -1
- package/cjs/src/embed/app.spec.js +20 -0
- package/cjs/src/embed/app.spec.js.map +1 -1
- package/cjs/src/embed/hostEventClient/contracts.d.ts +11 -1
- package/cjs/src/embed/hostEventClient/contracts.d.ts.map +1 -1
- package/cjs/src/embed/hostEventClient/contracts.js +1 -0
- package/cjs/src/embed/hostEventClient/contracts.js.map +1 -1
- package/cjs/src/embed/liveboard.d.ts.map +1 -1
- package/cjs/src/embed/liveboard.js +4 -1
- package/cjs/src/embed/liveboard.js.map +1 -1
- package/cjs/src/embed/liveboard.spec.js +22 -0
- package/cjs/src/embed/liveboard.spec.js.map +1 -1
- package/cjs/src/embed/search.d.ts.map +1 -1
- package/cjs/src/embed/search.js +3 -1
- package/cjs/src/embed/search.js.map +1 -1
- package/cjs/src/embed/ts-embed.d.ts +15 -0
- package/cjs/src/embed/ts-embed.d.ts.map +1 -1
- package/cjs/src/embed/ts-embed.js +94 -26
- package/cjs/src/embed/ts-embed.js.map +1 -1
- package/cjs/src/embed/ts-embed.spec.js +83 -0
- package/cjs/src/embed/ts-embed.spec.js.map +1 -1
- package/cjs/src/errors.d.ts +1 -0
- package/cjs/src/errors.d.ts.map +1 -1
- package/cjs/src/errors.js +1 -0
- package/cjs/src/errors.js.map +1 -1
- package/cjs/src/index.d.ts +2 -2
- package/cjs/src/index.d.ts.map +1 -1
- package/cjs/src/index.js +2 -1
- package/cjs/src/index.js.map +1 -1
- package/cjs/src/react/all-types-export.d.ts +1 -1
- package/cjs/src/react/all-types-export.d.ts.map +1 -1
- package/cjs/src/react/all-types-export.js +2 -1
- package/cjs/src/react/all-types-export.js.map +1 -1
- package/cjs/src/types.d.ts +100 -4
- package/cjs/src/types.d.ts.map +1 -1
- package/cjs/src/types.js +39 -1
- package/cjs/src/types.js.map +1 -1
- package/cjs/src/utils/processData.d.ts +1 -1
- package/cjs/src/utils/processData.d.ts.map +1 -1
- package/cjs/src/utils/processData.js +8 -8
- package/cjs/src/utils/processData.js.map +1 -1
- package/dist/index-BEzW4MDA.js +7371 -0
- package/dist/{index-BpSohedu.js → index-DvNA626T.js} +1 -1
- package/dist/src/api-intercept.d.ts +25 -0
- package/dist/src/api-intercept.d.ts.map +1 -0
- package/dist/src/embed/app.d.ts.map +1 -1
- package/dist/src/embed/hostEventClient/contracts.d.ts +11 -1
- package/dist/src/embed/hostEventClient/contracts.d.ts.map +1 -1
- package/dist/src/embed/liveboard.d.ts.map +1 -1
- package/dist/src/embed/search.d.ts.map +1 -1
- package/dist/src/embed/ts-embed.d.ts +15 -0
- package/dist/src/embed/ts-embed.d.ts.map +1 -1
- package/dist/src/errors.d.ts +1 -0
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/react/all-types-export.d.ts +1 -1
- package/dist/src/react/all-types-export.d.ts.map +1 -1
- package/dist/src/types.d.ts +100 -4
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils/processData.d.ts +1 -1
- package/dist/src/utils/processData.d.ts.map +1 -1
- package/dist/tsembed-react.es.js +266 -51
- package/dist/tsembed-react.js +265 -50
- package/dist/tsembed.es.js +267 -52
- package/dist/tsembed.js +265 -50
- package/dist/visual-embed-sdk-react-full.d.ts +124 -4
- package/dist/visual-embed-sdk-react.d.ts +121 -4
- package/dist/visual-embed-sdk.d.ts +124 -4
- package/lib/package.json +2 -2
- package/lib/src/api-intercept.d.ts +25 -0
- package/lib/src/api-intercept.d.ts.map +1 -0
- package/lib/src/api-intercept.js +108 -0
- package/lib/src/api-intercept.js.map +1 -0
- package/lib/src/embed/app.d.ts.map +1 -1
- package/lib/src/embed/app.js +7 -2
- package/lib/src/embed/app.js.map +1 -1
- package/lib/src/embed/app.spec.js +20 -0
- package/lib/src/embed/app.spec.js.map +1 -1
- package/lib/src/embed/hostEventClient/contracts.d.ts +11 -1
- package/lib/src/embed/hostEventClient/contracts.d.ts.map +1 -1
- package/lib/src/embed/hostEventClient/contracts.js +1 -0
- package/lib/src/embed/hostEventClient/contracts.js.map +1 -1
- package/lib/src/embed/liveboard.d.ts.map +1 -1
- package/lib/src/embed/liveboard.js +4 -1
- package/lib/src/embed/liveboard.js.map +1 -1
- package/lib/src/embed/liveboard.spec.js +22 -0
- package/lib/src/embed/liveboard.spec.js.map +1 -1
- package/lib/src/embed/search.d.ts.map +1 -1
- package/lib/src/embed/search.js +3 -1
- package/lib/src/embed/search.js.map +1 -1
- package/lib/src/embed/ts-embed.d.ts +15 -0
- package/lib/src/embed/ts-embed.d.ts.map +1 -1
- package/lib/src/embed/ts-embed.js +94 -26
- package/lib/src/embed/ts-embed.js.map +1 -1
- package/lib/src/embed/ts-embed.spec.js +83 -0
- package/lib/src/embed/ts-embed.spec.js.map +1 -1
- package/lib/src/errors.d.ts +1 -0
- package/lib/src/errors.d.ts.map +1 -1
- package/lib/src/errors.js +1 -0
- package/lib/src/errors.js.map +1 -1
- package/lib/src/index.d.ts +2 -2
- package/lib/src/index.d.ts.map +1 -1
- package/lib/src/index.js +2 -2
- package/lib/src/index.js.map +1 -1
- package/lib/src/react/all-types-export.d.ts +1 -1
- package/lib/src/react/all-types-export.d.ts.map +1 -1
- package/lib/src/react/all-types-export.js +1 -1
- package/lib/src/react/all-types-export.js.map +1 -1
- package/lib/src/types.d.ts +100 -4
- package/lib/src/types.d.ts.map +1 -1
- package/lib/src/types.js +38 -0
- package/lib/src/types.js.map +1 -1
- package/lib/src/utils/processData.d.ts +1 -1
- package/lib/src/utils/processData.d.ts.map +1 -1
- package/lib/src/utils/processData.js +8 -8
- package/lib/src/utils/processData.js.map +1 -1
- package/package.json +2 -2
- package/src/api-intercept.ts +134 -0
- package/src/embed/app.spec.ts +28 -0
- package/src/embed/app.ts +9 -1
- package/src/embed/hostEventClient/contracts.ts +10 -0
- package/src/embed/liveboard.spec.ts +30 -0
- package/src/embed/liveboard.ts +5 -0
- package/src/embed/search.ts +3 -1
- package/src/embed/ts-embed.spec.ts +116 -5
- package/src/embed/ts-embed.ts +129 -43
- package/src/errors.ts +1 -0
- package/src/index.ts +2 -0
- package/src/react/all-types-export.ts +1 -0
- package/src/types.ts +102 -3
- package/src/utils/processData.ts +11 -11
package/src/embed/app.spec.ts
CHANGED
|
@@ -358,6 +358,34 @@ describe('App embed tests', () => {
|
|
|
358
358
|
});
|
|
359
359
|
});
|
|
360
360
|
|
|
361
|
+
test('should set isLinkParametersEnabled to true in url', async () => {
|
|
362
|
+
const appEmbed = new AppEmbed(getRootEl(), {
|
|
363
|
+
...defaultViewConfig,
|
|
364
|
+
isLinkParametersEnabled: true,
|
|
365
|
+
} as AppViewConfig);
|
|
366
|
+
appEmbed.render();
|
|
367
|
+
await executeAfterWait(() => {
|
|
368
|
+
expectUrlMatchesWithParams(
|
|
369
|
+
getIFrameSrc(),
|
|
370
|
+
`http://${thoughtSpotHost}/?embedApp=true&profileAndHelpInNavBarHidden=false&isLinkParametersEnabled=true${defaultParamsPost}#/home`,
|
|
371
|
+
);
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
test('should set isLinkParametersEnabled to false in url', async () => {
|
|
376
|
+
const appEmbed = new AppEmbed(getRootEl(), {
|
|
377
|
+
...defaultViewConfig,
|
|
378
|
+
isLinkParametersEnabled: false,
|
|
379
|
+
} as AppViewConfig);
|
|
380
|
+
appEmbed.render();
|
|
381
|
+
await executeAfterWait(() => {
|
|
382
|
+
expectUrlMatchesWithParams(
|
|
383
|
+
getIFrameSrc(),
|
|
384
|
+
`http://${thoughtSpotHost}/?embedApp=true&profileAndHelpInNavBarHidden=false&isLinkParametersEnabled=false${defaultParamsPost}#/home`,
|
|
385
|
+
);
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
|
|
361
389
|
test('should set liveboardXLSXCSVDownload to true in url', async () => {
|
|
362
390
|
const appEmbed = new AppEmbed(getRootEl(), {
|
|
363
391
|
...defaultViewConfig,
|
package/src/embed/app.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
AllEmbedViewConfig,
|
|
20
20
|
} from '../types';
|
|
21
21
|
import { V1Embed } from './ts-embed';
|
|
22
|
+
import { getInterceptInitData } from '../api-intercept';
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* Pages within the ThoughtSpot app that can be embedded.
|
|
@@ -663,6 +664,7 @@ export class AppEmbed extends V1Embed {
|
|
|
663
664
|
liveboardXLSXCSVDownload = false,
|
|
664
665
|
isLiveboardStylingAndGroupingEnabled,
|
|
665
666
|
isPNGInScheduledEmailsEnabled = false,
|
|
667
|
+
isLinkParametersEnabled,
|
|
666
668
|
} = this.viewConfig;
|
|
667
669
|
|
|
668
670
|
let params: any = {};
|
|
@@ -727,7 +729,9 @@ export class AppEmbed extends V1Embed {
|
|
|
727
729
|
params[Param.enableAskSage] = enableAskSage;
|
|
728
730
|
}
|
|
729
731
|
|
|
730
|
-
|
|
732
|
+
const { enableApiIntercept } = getInterceptInitData(this.embedConfig, this.viewConfig);
|
|
733
|
+
|
|
734
|
+
if (isOnBeforeGetVizDataInterceptEnabled && !enableApiIntercept) {
|
|
731
735
|
|
|
732
736
|
params[
|
|
733
737
|
Param.IsOnBeforeGetVizDataInterceptEnabled
|
|
@@ -750,6 +754,10 @@ export class AppEmbed extends V1Embed {
|
|
|
750
754
|
params[Param.isPNGInScheduledEmailsEnabled] = isPNGInScheduledEmailsEnabled;
|
|
751
755
|
}
|
|
752
756
|
|
|
757
|
+
if (isLinkParametersEnabled !== undefined) {
|
|
758
|
+
params[Param.isLinkParametersEnabled] = isLinkParametersEnabled;
|
|
759
|
+
}
|
|
760
|
+
|
|
753
761
|
params[Param.DataPanelV2Enabled] = dataPanelV2;
|
|
754
762
|
params[Param.HideHomepageLeftNav] = hideHomepageLeftNav;
|
|
755
763
|
params[Param.ModularHomeExperienceEnabled] = modularHomeExperience;
|
|
@@ -7,6 +7,7 @@ export enum UIPassthroughEvent {
|
|
|
7
7
|
GetAvailableUIPassthroughs = 'getAvailableUiPassthroughs',
|
|
8
8
|
GetAnswerConfig = 'getAnswerPageConfig',
|
|
9
9
|
GetLiveboardConfig = 'getPinboardPageConfig',
|
|
10
|
+
GetUnsavedAnswerTML = 'getUnsavedAnswerTML',
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
// UI Passthrough Contract
|
|
@@ -63,6 +64,15 @@ export type UIPassthroughContractBase = {
|
|
|
63
64
|
request: any;
|
|
64
65
|
response: any;
|
|
65
66
|
};
|
|
67
|
+
[UIPassthroughEvent.GetUnsavedAnswerTML]: {
|
|
68
|
+
request: {
|
|
69
|
+
sessionId?: string;
|
|
70
|
+
vizId?: string;
|
|
71
|
+
};
|
|
72
|
+
response: {
|
|
73
|
+
tml: string;
|
|
74
|
+
};
|
|
75
|
+
};
|
|
66
76
|
};
|
|
67
77
|
|
|
68
78
|
// UI Passthrough Request and Response
|
|
@@ -183,6 +183,36 @@ describe('Liveboard/viz embed tests', () => {
|
|
|
183
183
|
});
|
|
184
184
|
});
|
|
185
185
|
|
|
186
|
+
test('should set isLinkParametersEnabled to true in url', async () => {
|
|
187
|
+
const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
|
|
188
|
+
isLinkParametersEnabled: true,
|
|
189
|
+
...defaultViewConfig,
|
|
190
|
+
liveboardId,
|
|
191
|
+
} as LiveboardViewConfig);
|
|
192
|
+
liveboardEmbed.render();
|
|
193
|
+
await executeAfterWait(() => {
|
|
194
|
+
expectUrlMatchesWithParams(
|
|
195
|
+
getIFrameSrc(),
|
|
196
|
+
`http://${thoughtSpotHost}/?embedApp=true${defaultParams}&isLinkParametersEnabled=true${prefixParams}#/embed/viz/${liveboardId}`,
|
|
197
|
+
);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('should set isLinkParametersEnabled to false in url', async () => {
|
|
202
|
+
const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
|
|
203
|
+
isLinkParametersEnabled: false,
|
|
204
|
+
...defaultViewConfig,
|
|
205
|
+
liveboardId,
|
|
206
|
+
} as LiveboardViewConfig);
|
|
207
|
+
liveboardEmbed.render();
|
|
208
|
+
await executeAfterWait(() => {
|
|
209
|
+
expectUrlMatchesWithParams(
|
|
210
|
+
getIFrameSrc(),
|
|
211
|
+
`http://${thoughtSpotHost}/?embedApp=true${defaultParams}&isLinkParametersEnabled=false${prefixParams}#/embed/viz/${liveboardId}`,
|
|
212
|
+
);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
186
216
|
test('should set visible actions as empty array', async () => {
|
|
187
217
|
const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
|
|
188
218
|
visibleActions: [],
|
package/src/embed/liveboard.ts
CHANGED
|
@@ -477,6 +477,7 @@ export class LiveboardEmbed extends V1Embed {
|
|
|
477
477
|
isLiveboardStylingAndGroupingEnabled,
|
|
478
478
|
isPNGInScheduledEmailsEnabled = false,
|
|
479
479
|
showSpotterLimitations,
|
|
480
|
+
isLinkParametersEnabled,
|
|
480
481
|
} = this.viewConfig;
|
|
481
482
|
|
|
482
483
|
const preventLiveboardFilterRemoval = this.viewConfig.preventLiveboardFilterRemoval
|
|
@@ -552,6 +553,10 @@ export class LiveboardEmbed extends V1Embed {
|
|
|
552
553
|
params[Param.ShowSpotterLimitations] = showSpotterLimitations;
|
|
553
554
|
}
|
|
554
555
|
|
|
556
|
+
if (isLinkParametersEnabled !== undefined) {
|
|
557
|
+
params[Param.isLinkParametersEnabled] = isLinkParametersEnabled;
|
|
558
|
+
}
|
|
559
|
+
|
|
555
560
|
params[Param.LiveboardHeaderSticky] = isLiveboardHeaderSticky;
|
|
556
561
|
params[Param.LiveboardHeaderV2] = isLiveboardCompactHeaderEnabled;
|
|
557
562
|
params[Param.ShowLiveboardVerifiedBadge] = showLiveboardVerifiedBadge;
|
package/src/embed/search.ts
CHANGED
|
@@ -26,6 +26,7 @@ import { ERROR_MESSAGE } from '../errors';
|
|
|
26
26
|
import { getAuthPromise } from './base';
|
|
27
27
|
import { getReleaseVersion } from '../auth';
|
|
28
28
|
import { getEmbedConfig } from './embedConfig';
|
|
29
|
+
import { getInterceptInitData } from '../api-intercept';
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
32
|
* Configuration for search options.
|
|
@@ -442,7 +443,8 @@ export class SearchEmbed extends TsEmbed {
|
|
|
442
443
|
queryParams[Param.HideSearchBar] = true;
|
|
443
444
|
}
|
|
444
445
|
|
|
445
|
-
|
|
446
|
+
const { enableApiIntercept } = getInterceptInitData(this.embedConfig, this.viewConfig);
|
|
447
|
+
if (isOnBeforeGetVizDataInterceptEnabled && !enableApiIntercept) {
|
|
446
448
|
|
|
447
449
|
queryParams[Param.IsOnBeforeGetVizDataInterceptEnabled] = isOnBeforeGetVizDataInterceptEnabled;
|
|
448
450
|
}
|
|
@@ -1045,7 +1045,7 @@ describe('Unit test case for ts embed', () => {
|
|
|
1045
1045
|
type: EmbedEvent.APP_INIT,
|
|
1046
1046
|
data: {},
|
|
1047
1047
|
};
|
|
1048
|
-
|
|
1048
|
+
|
|
1049
1049
|
// Create a SearchEmbed with valid custom actions to test
|
|
1050
1050
|
// CustomActionsValidationResult
|
|
1051
1051
|
const searchEmbed = new SearchEmbed(getRootEl(), {
|
|
@@ -1067,7 +1067,7 @@ describe('Unit test case for ts embed', () => {
|
|
|
1067
1067
|
}
|
|
1068
1068
|
]
|
|
1069
1069
|
});
|
|
1070
|
-
|
|
1070
|
+
|
|
1071
1071
|
searchEmbed.render();
|
|
1072
1072
|
const mockPort: any = {
|
|
1073
1073
|
postMessage: jest.fn(),
|
|
@@ -1116,7 +1116,7 @@ describe('Unit test case for ts embed', () => {
|
|
|
1116
1116
|
customVariablesForThirdPartyTools: {},
|
|
1117
1117
|
},
|
|
1118
1118
|
});
|
|
1119
|
-
|
|
1119
|
+
|
|
1120
1120
|
// Verify that CustomActionsValidationResult structure is
|
|
1121
1121
|
// correct
|
|
1122
1122
|
const appInitData = mockPort.postMessage.mock.calls[0][0].data;
|
|
@@ -1137,7 +1137,7 @@ describe('Unit test case for ts embed', () => {
|
|
|
1137
1137
|
})
|
|
1138
1138
|
])
|
|
1139
1139
|
);
|
|
1140
|
-
|
|
1140
|
+
|
|
1141
1141
|
// Verify actions are sorted by name (alphabetically)
|
|
1142
1142
|
expect(appInitData.customActions[0].name).toBe('Another Valid Action');
|
|
1143
1143
|
expect(appInitData.customActions[1].name).toBe('Valid Action');
|
|
@@ -2488,7 +2488,7 @@ describe('Unit test case for ts embed', () => {
|
|
|
2488
2488
|
});
|
|
2489
2489
|
|
|
2490
2490
|
afterAll((): void => {
|
|
2491
|
-
window.location = location
|
|
2491
|
+
(window.location as any) = location;
|
|
2492
2492
|
});
|
|
2493
2493
|
|
|
2494
2494
|
it('get url params for TS', () => {
|
|
@@ -3345,4 +3345,115 @@ describe('Unit test case for ts embed', () => {
|
|
|
3345
3345
|
expect(searchEmbed.isEmbedContainerLoaded).toBe(true);
|
|
3346
3346
|
});
|
|
3347
3347
|
});
|
|
3348
|
+
|
|
3349
|
+
describe('Online event listener registration after auth failure', () => {
|
|
3350
|
+
beforeAll(() => {
|
|
3351
|
+
init({
|
|
3352
|
+
thoughtSpotHost: 'tshost',
|
|
3353
|
+
authType: AuthType.None,
|
|
3354
|
+
loginFailedMessage: 'Not logged in',
|
|
3355
|
+
});
|
|
3356
|
+
});
|
|
3357
|
+
|
|
3358
|
+
test('should register online event listener when authentication fails', async () => {
|
|
3359
|
+
const addEventListenerSpy = jest.spyOn(window, 'addEventListener');
|
|
3360
|
+
jest.spyOn(baseInstance, 'getAuthPromise').mockRejectedValueOnce(
|
|
3361
|
+
new Error('Auth failed'),
|
|
3362
|
+
);
|
|
3363
|
+
const searchEmbed = new SearchEmbed(getRootEl(), defaultViewConfig);
|
|
3364
|
+
addEventListenerSpy.mockClear();
|
|
3365
|
+
await searchEmbed.render();
|
|
3366
|
+
await executeAfterWait(() => {
|
|
3367
|
+
expect(getRootEl().innerHTML).toContain('Not logged in');
|
|
3368
|
+
const onlineListenerCalls = addEventListenerSpy.mock.calls.filter(
|
|
3369
|
+
(call) => call[0] === 'online',
|
|
3370
|
+
);
|
|
3371
|
+
expect(onlineListenerCalls).toHaveLength(1);
|
|
3372
|
+
const offlineListenerCalls = addEventListenerSpy.mock.calls.filter(
|
|
3373
|
+
(call) => call[0] === 'offline',
|
|
3374
|
+
);
|
|
3375
|
+
expect(offlineListenerCalls).toHaveLength(1);
|
|
3376
|
+
const messageListenerCalls = addEventListenerSpy.mock.calls.filter(
|
|
3377
|
+
(call) => call[0] === 'message',
|
|
3378
|
+
);
|
|
3379
|
+
expect(messageListenerCalls).toHaveLength(0);
|
|
3380
|
+
});
|
|
3381
|
+
|
|
3382
|
+
addEventListenerSpy.mockRestore();
|
|
3383
|
+
});
|
|
3384
|
+
|
|
3385
|
+
test('should attempt to trigger reload when online event occurs after auth failure', async () => {
|
|
3386
|
+
jest.spyOn(baseInstance, 'getAuthPromise').mockRejectedValueOnce(
|
|
3387
|
+
new Error('Auth failed'),
|
|
3388
|
+
);
|
|
3389
|
+
const searchEmbed = new SearchEmbed(getRootEl(), defaultViewConfig);
|
|
3390
|
+
const triggerSpy = jest.spyOn(searchEmbed, 'trigger').mockResolvedValue(null);
|
|
3391
|
+
await searchEmbed.render();
|
|
3392
|
+
|
|
3393
|
+
await executeAfterWait(() => {
|
|
3394
|
+
expect(getRootEl().innerHTML).toContain('Not logged in');
|
|
3395
|
+
triggerSpy.mockClear();
|
|
3396
|
+
const onlineEvent = new Event('online');
|
|
3397
|
+
window.dispatchEvent(onlineEvent);
|
|
3398
|
+
expect(triggerSpy).toHaveBeenCalledWith(HostEvent.Reload);
|
|
3399
|
+
});
|
|
3400
|
+
|
|
3401
|
+
triggerSpy.mockReset();
|
|
3402
|
+
});
|
|
3403
|
+
|
|
3404
|
+
test('should handle online event gracefully when no iframe exists', async () => {
|
|
3405
|
+
jest.spyOn(baseInstance, 'getAuthPromise').mockRejectedValueOnce(
|
|
3406
|
+
new Error('Auth failed'),
|
|
3407
|
+
);
|
|
3408
|
+
const searchEmbed = new SearchEmbed(getRootEl(), defaultViewConfig);
|
|
3409
|
+
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
|
|
3410
|
+
await searchEmbed.render();
|
|
3411
|
+
await executeAfterWait(() => {
|
|
3412
|
+
expect(getRootEl().innerHTML).toContain('Not logged in');
|
|
3413
|
+
const onlineEvent = new Event('online');
|
|
3414
|
+
expect(() => {
|
|
3415
|
+
window.dispatchEvent(onlineEvent);
|
|
3416
|
+
}).not.toThrow();
|
|
3417
|
+
});
|
|
3418
|
+
|
|
3419
|
+
errorSpy.mockReset();
|
|
3420
|
+
});
|
|
3421
|
+
|
|
3422
|
+
test('should register all event listeners when authentication succeeds', async () => {
|
|
3423
|
+
const addEventListenerSpy = jest.spyOn(window, 'addEventListener');
|
|
3424
|
+
jest.spyOn(baseInstance, 'getAuthPromise').mockResolvedValueOnce(true);
|
|
3425
|
+
const searchEmbed = new SearchEmbed(getRootEl(), defaultViewConfig);
|
|
3426
|
+
addEventListenerSpy.mockClear();
|
|
3427
|
+
await searchEmbed.render();
|
|
3428
|
+
await executeAfterWait(() => {
|
|
3429
|
+
const onlineListenerCalls = addEventListenerSpy.mock.calls.filter(
|
|
3430
|
+
(call) => call[0] === 'online',
|
|
3431
|
+
);
|
|
3432
|
+
expect(onlineListenerCalls).toHaveLength(1);
|
|
3433
|
+
const offlineListenerCalls = addEventListenerSpy.mock.calls.filter(
|
|
3434
|
+
(call) => call[0] === 'offline',
|
|
3435
|
+
);
|
|
3436
|
+
expect(offlineListenerCalls).toHaveLength(1);
|
|
3437
|
+
const messageListenerCalls = addEventListenerSpy.mock.calls.filter(
|
|
3438
|
+
(call) => call[0] === 'message',
|
|
3439
|
+
);
|
|
3440
|
+
expect(messageListenerCalls).toHaveLength(1);
|
|
3441
|
+
});
|
|
3442
|
+
|
|
3443
|
+
addEventListenerSpy.mockRestore();
|
|
3444
|
+
});
|
|
3445
|
+
test('should successfully trigger reload when online event occurs after auth success', async () => {
|
|
3446
|
+
jest.spyOn(baseInstance, 'getAuthPromise').mockResolvedValueOnce(true);
|
|
3447
|
+
const searchEmbed = new SearchEmbed(getRootEl(), defaultViewConfig);
|
|
3448
|
+
const triggerSpy = jest.spyOn(searchEmbed, 'trigger').mockResolvedValue({} as any);
|
|
3449
|
+
await searchEmbed.render();
|
|
3450
|
+
await executeAfterWait(() => {
|
|
3451
|
+
triggerSpy.mockClear();
|
|
3452
|
+
const onlineEvent = new Event('online');
|
|
3453
|
+
window.dispatchEvent(onlineEvent);
|
|
3454
|
+
expect(triggerSpy).toHaveBeenCalledWith(HostEvent.Reload);
|
|
3455
|
+
});
|
|
3456
|
+
triggerSpy.mockReset();
|
|
3457
|
+
});
|
|
3458
|
+
});
|
|
3348
3459
|
});
|
package/src/embed/ts-embed.ts
CHANGED
|
@@ -71,6 +71,7 @@ import { getEmbedConfig } from './embedConfig';
|
|
|
71
71
|
import { ERROR_MESSAGE } from '../errors';
|
|
72
72
|
import { getPreauthInfo } from '../utils/sessionInfoService';
|
|
73
73
|
import { HostEventClient } from './hostEventClient/host-event-client';
|
|
74
|
+
import { getInterceptInitData, handleInterceptEvent, processLegacyInterceptResponse } from '../api-intercept';
|
|
74
75
|
|
|
75
76
|
const { version } = pkgInfo;
|
|
76
77
|
|
|
@@ -201,7 +202,7 @@ export class TsEmbed {
|
|
|
201
202
|
});
|
|
202
203
|
const embedConfig = getEmbedConfig();
|
|
203
204
|
this.embedConfig = embedConfig;
|
|
204
|
-
|
|
205
|
+
|
|
205
206
|
this.hostEventClient = new HostEventClient(this.iFrame);
|
|
206
207
|
this.isReadyForRenderPromise = getInitPromise().then(async () => {
|
|
207
208
|
if (!embedConfig.authTriggerContainer && !embedConfig.useEventForSAMLPopup) {
|
|
@@ -312,31 +313,11 @@ export class TsEmbed {
|
|
|
312
313
|
private subscribedListeners: Record<string, any> = {};
|
|
313
314
|
|
|
314
315
|
/**
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
* embed instance through an identifier contained in the payload,
|
|
318
|
-
* and executes the registered callbacks accordingly.
|
|
316
|
+
* Subscribe to network events (online/offline) that should
|
|
317
|
+
* work regardless of auth status
|
|
319
318
|
*/
|
|
320
|
-
private
|
|
321
|
-
this.
|
|
322
|
-
const messageEventListener = (event: MessageEvent<any>) => {
|
|
323
|
-
const eventType = this.getEventType(event);
|
|
324
|
-
const eventPort = this.getEventPort(event);
|
|
325
|
-
const eventData = this.formatEventData(event, eventType);
|
|
326
|
-
if (event.source === this.iFrame.contentWindow) {
|
|
327
|
-
this.executeCallbacks(
|
|
328
|
-
eventType,
|
|
329
|
-
processEventData(
|
|
330
|
-
eventType,
|
|
331
|
-
eventData,
|
|
332
|
-
this.thoughtSpotHost,
|
|
333
|
-
this.isPreRendered ? this.preRenderWrapper : this.el,
|
|
334
|
-
),
|
|
335
|
-
eventPort,
|
|
336
|
-
);
|
|
337
|
-
}
|
|
338
|
-
};
|
|
339
|
-
window.addEventListener('message', messageEventListener);
|
|
319
|
+
private subscribeToNetworkEvents() {
|
|
320
|
+
this.unsubscribeToNetworkEvents();
|
|
340
321
|
|
|
341
322
|
const onlineEventListener = (e: Event) => {
|
|
342
323
|
this.trigger(HostEvent.Reload);
|
|
@@ -344,7 +325,7 @@ export class TsEmbed {
|
|
|
344
325
|
window.addEventListener('online', onlineEventListener);
|
|
345
326
|
|
|
346
327
|
const offlineEventListener = (e: Event) => {
|
|
347
|
-
const offlineWarning =
|
|
328
|
+
const offlineWarning = ERROR_MESSAGE.OFFLINE_WARNING;
|
|
348
329
|
this.executeCallbacks(EmbedEvent.Error, {
|
|
349
330
|
offlineWarning,
|
|
350
331
|
});
|
|
@@ -352,11 +333,84 @@ export class TsEmbed {
|
|
|
352
333
|
};
|
|
353
334
|
window.addEventListener('offline', offlineEventListener);
|
|
354
335
|
|
|
355
|
-
this.subscribedListeners =
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
336
|
+
this.subscribedListeners.online = onlineEventListener;
|
|
337
|
+
this.subscribedListeners.offline = offlineEventListener;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
private messageEventListener = async (event: MessageEvent<any>) => {
|
|
341
|
+
const eventType = this.getEventType(event);
|
|
342
|
+
const eventPort = this.getEventPort(event);
|
|
343
|
+
const eventData = this.formatEventData(event, eventType);
|
|
344
|
+
if (event.source === this.iFrame.contentWindow) {
|
|
345
|
+
const processedEventData = await processEventData(
|
|
346
|
+
eventType,
|
|
347
|
+
eventData,
|
|
348
|
+
this.thoughtSpotHost,
|
|
349
|
+
this.isPreRendered ? this.preRenderWrapper : this.el,
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
const executeEvent = (_eventType: EmbedEvent, data: any) => {
|
|
353
|
+
this.executeCallbacks(_eventType, data, eventPort);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (eventType === EmbedEvent.ApiIntercept && this.viewConfig.enableApiIntercept) {
|
|
357
|
+
const getUnsavedAnswerTml = async (props: { sessionId?: string, vizId?: string }) => {
|
|
358
|
+
const response = await this.triggerUIPassThrough(UIPassthroughEvent.GetUnsavedAnswerTML, props);
|
|
359
|
+
return response[0]?.value;
|
|
360
|
+
}
|
|
361
|
+
handleInterceptEvent({ eventData: processedEventData, executeEvent, embedConfig: this.embedConfig, viewConfig: this.viewConfig, getUnsavedAnswerTml });
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
this.executeCallbacks(
|
|
366
|
+
eventType,
|
|
367
|
+
processedEventData,
|
|
368
|
+
eventPort,
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
/**
|
|
373
|
+
* Subscribe to message events that depend on successful iframe setup
|
|
374
|
+
*/
|
|
375
|
+
private subscribeToMessageEvents() {
|
|
376
|
+
this.unsubscribeToMessageEvents();
|
|
377
|
+
|
|
378
|
+
window.addEventListener('message', this.messageEventListener);
|
|
379
|
+
|
|
380
|
+
this.subscribedListeners.message = this.messageEventListener;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Adds event listeners for both network and message events.
|
|
386
|
+
* This maintains backward compatibility with the existing method.
|
|
387
|
+
* Adds a global event listener to window for "message" events.
|
|
388
|
+
* ThoughtSpot detects if a particular event is targeted to this
|
|
389
|
+
* embed instance through an identifier contained in the payload,
|
|
390
|
+
* and executes the registered callbacks accordingly.
|
|
391
|
+
*/
|
|
392
|
+
private subscribeToEvents() {
|
|
393
|
+
this.subscribeToNetworkEvents();
|
|
394
|
+
this.subscribeToMessageEvents();
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
private unsubscribeToNetworkEvents() {
|
|
399
|
+
if (this.subscribedListeners.online) {
|
|
400
|
+
window.removeEventListener('online', this.subscribedListeners.online);
|
|
401
|
+
delete this.subscribedListeners.online;
|
|
402
|
+
}
|
|
403
|
+
if (this.subscribedListeners.offline) {
|
|
404
|
+
window.removeEventListener('offline', this.subscribedListeners.offline);
|
|
405
|
+
delete this.subscribedListeners.offline;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
private unsubscribeToMessageEvents() {
|
|
410
|
+
if (this.subscribedListeners.message) {
|
|
411
|
+
window.removeEventListener('message', this.subscribedListeners.message);
|
|
412
|
+
delete this.subscribedListeners.message;
|
|
413
|
+
}
|
|
360
414
|
}
|
|
361
415
|
|
|
362
416
|
private unsubscribeToEvents() {
|
|
@@ -391,7 +445,7 @@ export class TsEmbed {
|
|
|
391
445
|
message: customActionsResult.errors,
|
|
392
446
|
});
|
|
393
447
|
}
|
|
394
|
-
|
|
448
|
+
const baseInitData = {
|
|
395
449
|
customisations: getCustomisations(this.embedConfig, this.viewConfig),
|
|
396
450
|
authToken,
|
|
397
451
|
runtimeFilterParams: this.viewConfig.excludeRuntimeFiltersfromURL
|
|
@@ -410,7 +464,10 @@ export class TsEmbed {
|
|
|
410
464
|
this.embedConfig.customVariablesForThirdPartyTools || {},
|
|
411
465
|
hiddenListColumns: this.viewConfig.hiddenListColumns || [],
|
|
412
466
|
customActions: customActionsResult.actions,
|
|
467
|
+
...getInterceptInitData(this.embedConfig, this.viewConfig),
|
|
413
468
|
};
|
|
469
|
+
|
|
470
|
+
return baseInitData;
|
|
414
471
|
}
|
|
415
472
|
|
|
416
473
|
protected async getAppInitData() {
|
|
@@ -798,6 +855,9 @@ export class TsEmbed {
|
|
|
798
855
|
|
|
799
856
|
uploadMixpanelEvent(MIXPANEL_EVENT.VISUAL_SDK_RENDER_START);
|
|
800
857
|
|
|
858
|
+
// Always subscribe to network events, regardless of auth status
|
|
859
|
+
this.subscribeToNetworkEvents();
|
|
860
|
+
|
|
801
861
|
return getAuthPromise()
|
|
802
862
|
?.then((isLoggedIn: boolean) => {
|
|
803
863
|
if (!isLoggedIn) {
|
|
@@ -843,7 +903,9 @@ export class TsEmbed {
|
|
|
843
903
|
el.remove();
|
|
844
904
|
});
|
|
845
905
|
}
|
|
846
|
-
|
|
906
|
+
// Subscribe to message events only after successful
|
|
907
|
+
// auth and iframe setup
|
|
908
|
+
this.subscribeToMessageEvents();
|
|
847
909
|
})
|
|
848
910
|
.catch((error) => {
|
|
849
911
|
nextInQueue();
|
|
@@ -983,6 +1045,21 @@ export class TsEmbed {
|
|
|
983
1045
|
this.iFrame.style.height = getCssDimension(height);
|
|
984
1046
|
}
|
|
985
1047
|
|
|
1048
|
+
protected createEmbedEventResponder = (eventPort: MessagePort | void, eventType: EmbedEvent) => {
|
|
1049
|
+
|
|
1050
|
+
const { enableApiIntercept } = getInterceptInitData(this.embedConfig, this.viewConfig);
|
|
1051
|
+
if (eventType === EmbedEvent.OnBeforeGetVizDataIntercept && enableApiIntercept) {
|
|
1052
|
+
return (payload: any) => {
|
|
1053
|
+
const payloadToSend = processLegacyInterceptResponse(payload);
|
|
1054
|
+
this.triggerEventOnPort(eventPort, payloadToSend);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
return (payload: any) => {
|
|
1059
|
+
this.triggerEventOnPort(eventPort, payload);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
986
1063
|
/**
|
|
987
1064
|
* Executes all registered event handlers for a particular event type
|
|
988
1065
|
* @param eventType The event type
|
|
@@ -1007,9 +1084,8 @@ export class TsEmbed {
|
|
|
1007
1084
|
// payload
|
|
1008
1085
|
|| (!callbackObj.options.start && dataStatus === embedEventStatus.END)
|
|
1009
1086
|
) {
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
});
|
|
1087
|
+
const responder = this.createEmbedEventResponder(eventPort, eventType);
|
|
1088
|
+
callbackObj.callback(data, responder);
|
|
1013
1089
|
}
|
|
1014
1090
|
});
|
|
1015
1091
|
}
|
|
@@ -1151,12 +1227,12 @@ export class TsEmbed {
|
|
|
1151
1227
|
}
|
|
1152
1228
|
}
|
|
1153
1229
|
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1230
|
+
/**
|
|
1231
|
+
* @hidden
|
|
1232
|
+
* Internal state to track if the embed container is loaded.
|
|
1233
|
+
* This is used to trigger events after the embed container is loaded.
|
|
1234
|
+
*/
|
|
1235
|
+
public isEmbedContainerLoaded = false;
|
|
1160
1236
|
|
|
1161
1237
|
/**
|
|
1162
1238
|
* @hidden
|
|
@@ -1243,6 +1319,16 @@ export class TsEmbed {
|
|
|
1243
1319
|
this.handleError('Host event type is undefined');
|
|
1244
1320
|
return null;
|
|
1245
1321
|
}
|
|
1322
|
+
|
|
1323
|
+
// Check if iframe exists before triggering -
|
|
1324
|
+
// this prevents the error when auth fails
|
|
1325
|
+
if (!this.iFrame) {
|
|
1326
|
+
logger.debug(
|
|
1327
|
+
`Cannot trigger ${messageType} - iframe not available (likely due to auth failure)`,
|
|
1328
|
+
);
|
|
1329
|
+
return null;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1246
1332
|
// send an empty object, this is needed for liveboard default handlers
|
|
1247
1333
|
return this.hostEventClient.triggerHostEvent(messageType, data);
|
|
1248
1334
|
}
|
|
@@ -1298,7 +1384,7 @@ export class TsEmbed {
|
|
|
1298
1384
|
}
|
|
1299
1385
|
this.isPreRendered = true;
|
|
1300
1386
|
this.showPreRenderByDefault = showPreRenderByDefault;
|
|
1301
|
-
|
|
1387
|
+
|
|
1302
1388
|
const isAlreadyRendered = this.connectPreRendered();
|
|
1303
1389
|
if (isAlreadyRendered && !replaceExistingPreRender) {
|
|
1304
1390
|
return this;
|
package/src/errors.ts
CHANGED
|
@@ -19,6 +19,7 @@ export const ERROR_MESSAGE = {
|
|
|
19
19
|
MISSING_REPORTING_OBSERVER: 'ReportingObserver not supported',
|
|
20
20
|
RENDER_CALLED_BEFORE_INIT: 'Looks like render was called before calling init, the render won\'t start until init is called.\nFor more info check\n1. https://developers.thoughtspot.com/docs/Function_init#_init\n2.https://developers.thoughtspot.com/docs/getting-started#initSdk',
|
|
21
21
|
SPOTTER_AGENT_NOT_INITIALIZED: 'SpotterAgent not initialized',
|
|
22
|
+
OFFLINE_WARNING : 'Network not Detected. Embed is offline. Please reconnect and refresh',
|
|
22
23
|
};
|
|
23
24
|
|
|
24
25
|
export const CUSTOM_ACTIONS_ERROR_MESSAGE = {
|
package/src/index.ts
CHANGED
|
@@ -64,6 +64,7 @@ import {
|
|
|
64
64
|
ListPageColumns,
|
|
65
65
|
CustomActionsPosition,
|
|
66
66
|
CustomActionTarget,
|
|
67
|
+
InterceptedApiType,
|
|
67
68
|
} from './types';
|
|
68
69
|
import { CustomCssVariables } from './css-variables';
|
|
69
70
|
import { SageEmbed, SageViewConfig } from './embed/sage';
|
|
@@ -152,6 +153,7 @@ export {
|
|
|
152
153
|
DataPanelCustomColumnGroupsAccordionState,
|
|
153
154
|
CustomActionsPosition,
|
|
154
155
|
CustomActionTarget,
|
|
156
|
+
InterceptedApiType,
|
|
155
157
|
};
|
|
156
158
|
|
|
157
159
|
export { resetCachedAuthToken } from './authToken';
|