@thoughtspot/visual-embed-sdk 1.10.0-alpha.4 → 1.10.2
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/CHANGELOG.md +2 -2
- package/README.md +1 -1
- package/dist/src/auth.d.ts +18 -5
- package/dist/src/embed/base.d.ts +21 -5
- package/dist/src/embed/pinboard.d.ts +6 -0
- package/dist/src/embed/search.d.ts +4 -0
- package/dist/src/embed/ts-embed.d.ts +1 -1
- package/dist/src/index.d.ts +3 -2
- package/dist/src/types.d.ts +29 -0
- package/dist/src/utils/authService.d.ts +1 -0
- package/dist/src/utils/plugin.d.ts +0 -0
- package/dist/src/utils/processData.d.ts +1 -1
- package/dist/src/utils.d.ts +1 -0
- package/dist/src/v1/api.d.ts +19 -0
- package/dist/tsembed.es.js +543 -39
- package/dist/tsembed.js +541 -38
- package/lib/package.json +2 -1
- package/lib/src/auth.d.ts +18 -5
- package/lib/src/auth.js +51 -12
- package/lib/src/auth.js.map +1 -1
- package/lib/src/auth.spec.js +69 -11
- package/lib/src/auth.spec.js.map +1 -1
- package/lib/src/embed/base.d.ts +21 -5
- package/lib/src/embed/base.js +64 -10
- package/lib/src/embed/base.js.map +1 -1
- package/lib/src/embed/base.spec.js +49 -3
- package/lib/src/embed/base.spec.js.map +1 -1
- package/lib/src/embed/embed.spec.js +1 -1
- package/lib/src/embed/embed.spec.js.map +1 -1
- package/lib/src/embed/events.spec.js +30 -1
- package/lib/src/embed/events.spec.js.map +1 -1
- package/lib/src/embed/pinboard.d.ts +6 -0
- package/lib/src/embed/pinboard.js +4 -1
- package/lib/src/embed/pinboard.js.map +1 -1
- package/lib/src/embed/search.d.ts +4 -0
- package/lib/src/embed/search.js +1 -1
- package/lib/src/embed/search.js.map +1 -1
- package/lib/src/embed/ts-embed.d.ts +1 -1
- package/lib/src/embed/ts-embed.js +18 -14
- package/lib/src/embed/ts-embed.js.map +1 -1
- package/lib/src/embed/ts-embed.spec.js +16 -6
- package/lib/src/embed/ts-embed.spec.js.map +1 -1
- package/lib/src/index.d.ts +3 -2
- package/lib/src/index.js +3 -2
- package/lib/src/index.js.map +1 -1
- package/lib/src/test/test-utils.js +1 -1
- package/lib/src/test/test-utils.js.map +1 -1
- package/lib/src/types.d.ts +29 -0
- package/lib/src/types.js +16 -0
- package/lib/src/types.js.map +1 -1
- package/lib/src/utils/authService.d.ts +1 -0
- package/lib/src/utils/authService.js +21 -3
- package/lib/src/utils/authService.js.map +1 -1
- package/lib/src/utils/authService.spec.js +21 -5
- package/lib/src/utils/authService.spec.js.map +1 -1
- package/lib/src/utils/plugin.d.ts +0 -0
- package/lib/src/utils/plugin.js +1 -0
- package/lib/src/utils/plugin.js.map +1 -0
- package/lib/src/utils/processData.d.ts +1 -1
- package/lib/src/utils/processData.js +37 -3
- package/lib/src/utils/processData.js.map +1 -1
- package/lib/src/utils/processData.spec.js +106 -4
- package/lib/src/utils/processData.spec.js.map +1 -1
- package/lib/src/utils.d.ts +1 -0
- package/lib/src/utils.js +4 -0
- package/lib/src/utils.js.map +1 -1
- package/lib/src/utils.spec.js +14 -1
- package/lib/src/utils.spec.js.map +1 -1
- package/lib/src/visual-embed-sdk.d.ts +119 -8
- package/package.json +2 -1
- package/src/auth.spec.ts +90 -11
- package/src/auth.ts +74 -16
- package/src/embed/base.spec.ts +56 -4
- package/src/embed/base.ts +83 -16
- package/src/embed/embed.spec.ts +1 -1
- package/src/embed/events.spec.ts +32 -0
- package/src/embed/search.ts +5 -0
- package/src/embed/ts-embed.spec.ts +19 -9
- package/src/embed/ts-embed.ts +24 -15
- package/src/index.ts +5 -1
- package/src/test/test-utils.ts +1 -1
- package/src/types.ts +31 -0
- package/src/utils/authService.spec.ts +31 -5
- package/src/utils/authService.ts +27 -3
- package/src/utils/processData.spec.ts +139 -4
- package/src/utils/processData.ts +54 -4
- package/src/utils.spec.ts +26 -0
- package/src/utils.ts +5 -0
- package/lib/src/utils/fetchAnswers.d.ts +0 -3
- package/lib/src/utils/fetchAnswers.js +0 -49
- package/lib/src/utils/fetchAnswers.js.map +0 -1
package/src/embed/base.spec.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import EventEmitter from 'eventemitter3';
|
|
2
|
+
import * as auth from '../auth';
|
|
1
3
|
import * as index from '../index';
|
|
4
|
+
import * as base from './base';
|
|
2
5
|
import {
|
|
3
6
|
executeAfterWait,
|
|
4
7
|
getAllIframeEl,
|
|
@@ -9,10 +12,11 @@ import {
|
|
|
9
12
|
} from '../test/test-utils';
|
|
10
13
|
|
|
11
14
|
const thoughtSpotHost = 'tshost';
|
|
15
|
+
let authEE: EventEmitter;
|
|
12
16
|
|
|
13
17
|
describe('Base TS Embed', () => {
|
|
14
18
|
beforeAll(() => {
|
|
15
|
-
index.init({
|
|
19
|
+
authEE = index.init({
|
|
16
20
|
thoughtSpotHost,
|
|
17
21
|
authType: index.AuthType.None,
|
|
18
22
|
});
|
|
@@ -38,10 +42,12 @@ describe('Base TS Embed', () => {
|
|
|
38
42
|
},
|
|
39
43
|
'*',
|
|
40
44
|
);
|
|
41
|
-
|
|
42
|
-
jest.spyOn(window, 'alert').mockImplementation(() =>
|
|
45
|
+
jest.spyOn(window, 'alert').mockReset();
|
|
46
|
+
jest.spyOn(window, 'alert').mockImplementation(() => undefined);
|
|
47
|
+
authEE.on(auth.AuthStatus.FAILURE, (reason) => {
|
|
48
|
+
expect(reason).toEqual(auth.AuthFailureType.NO_COOKIE_ACCESS);
|
|
43
49
|
expect(window.alert).toBeCalledWith(
|
|
44
|
-
'Third party cookie access is blocked on this browser, please allow third party cookies for
|
|
50
|
+
'Third party cookie access is blocked on this browser, please allow third party cookies for this to work properly. \nYou can use `suppressNoCookieAccessAlert` to suppress this message.',
|
|
45
51
|
);
|
|
46
52
|
done();
|
|
47
53
|
});
|
|
@@ -92,4 +98,50 @@ describe('Base TS Embed', () => {
|
|
|
92
98
|
expect(getIFrameSrc()).toContain('disableLoginRedirect=true');
|
|
93
99
|
});
|
|
94
100
|
});
|
|
101
|
+
|
|
102
|
+
test('handleAuth notifies for SDK auth failure', (done) => {
|
|
103
|
+
jest.spyOn(auth, 'authenticate').mockResolvedValue(false);
|
|
104
|
+
const authEmitter = index.init({
|
|
105
|
+
thoughtSpotHost,
|
|
106
|
+
authType: index.AuthType.Basic,
|
|
107
|
+
username: 'test',
|
|
108
|
+
password: 'test',
|
|
109
|
+
});
|
|
110
|
+
authEmitter.on(auth.AuthStatus.FAILURE, (reason) => {
|
|
111
|
+
expect(reason).toBe(auth.AuthFailureType.SDK);
|
|
112
|
+
done();
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('Logout method should disable autoLogin', () => {
|
|
117
|
+
jest.spyOn(window, 'fetch').mockResolvedValue({
|
|
118
|
+
type: 'opaque',
|
|
119
|
+
});
|
|
120
|
+
index.init({
|
|
121
|
+
thoughtSpotHost,
|
|
122
|
+
authType: index.AuthType.None,
|
|
123
|
+
autoLogin: true,
|
|
124
|
+
});
|
|
125
|
+
index.logout();
|
|
126
|
+
expect(window.fetch).toHaveBeenCalledWith(
|
|
127
|
+
`http://${thoughtSpotHost}${auth.EndPoints.LOGOUT}`,
|
|
128
|
+
{
|
|
129
|
+
credentials: 'include',
|
|
130
|
+
mode: 'no-cors',
|
|
131
|
+
method: 'POST',
|
|
132
|
+
},
|
|
133
|
+
);
|
|
134
|
+
expect(base.getEmbedConfig().autoLogin).toBe(false);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('Base without init', () => {
|
|
139
|
+
test('notify should error when called without init', () => {
|
|
140
|
+
base.reset();
|
|
141
|
+
jest.spyOn(global.console, 'error').mockImplementation(() => undefined);
|
|
142
|
+
base.notifyAuthSuccess();
|
|
143
|
+
base.notifyAuthFailure(auth.AuthFailureType.SDK);
|
|
144
|
+
base.notifyLogout();
|
|
145
|
+
expect(global.console.error).toHaveBeenCalledTimes(3);
|
|
146
|
+
});
|
|
95
147
|
});
|
package/src/embed/base.ts
CHANGED
|
@@ -7,31 +7,72 @@
|
|
|
7
7
|
* @summary Base classes
|
|
8
8
|
* @author Ayon Ghosh <ayon.ghosh@thoughtspot.com>
|
|
9
9
|
*/
|
|
10
|
+
import EventEmitter from 'eventemitter3';
|
|
10
11
|
import { getThoughtSpotHost } from '../config';
|
|
11
|
-
import { EmbedConfig } from '../types';
|
|
12
|
-
import {
|
|
12
|
+
import { AuthType, EmbedConfig } from '../types';
|
|
13
|
+
import {
|
|
14
|
+
authenticate,
|
|
15
|
+
logout as _logout,
|
|
16
|
+
AuthFailureType,
|
|
17
|
+
AuthStatus,
|
|
18
|
+
} from '../auth';
|
|
13
19
|
import { uploadMixpanelEvent, MIXPANEL_EVENT } from '../mixpanel-service';
|
|
14
20
|
|
|
15
21
|
let config = {} as EmbedConfig;
|
|
22
|
+
const CONFIG_DEFAULTS: Partial<EmbedConfig> = {
|
|
23
|
+
loginFailedMessage: 'Not logged in',
|
|
24
|
+
authType: AuthType.None,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export let authPromise: Promise<boolean>;
|
|
28
|
+
|
|
29
|
+
export const getEmbedConfig = (): EmbedConfig => config;
|
|
30
|
+
|
|
31
|
+
export const getAuthPromise = (): Promise<boolean> => authPromise;
|
|
16
32
|
|
|
17
|
-
|
|
33
|
+
let authEE: EventEmitter;
|
|
18
34
|
|
|
35
|
+
export function notifyAuthSuccess(): void {
|
|
36
|
+
if (!authEE) {
|
|
37
|
+
console.error('SDK not initialized');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
authEE.emit(AuthStatus.SUCCESS);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function notifyAuthFailure(failureType: AuthFailureType): void {
|
|
44
|
+
if (!authEE) {
|
|
45
|
+
console.error('SDK not initialized');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
authEE.emit(AuthStatus.FAILURE, failureType);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function notifyLogout(): void {
|
|
52
|
+
if (!authEE) {
|
|
53
|
+
console.error('SDK not initialized');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
authEE.emit(AuthStatus.LOGOUT);
|
|
57
|
+
}
|
|
19
58
|
/**
|
|
20
59
|
* Perform authentication on the ThoughtSpot app as applicable.
|
|
21
60
|
*/
|
|
22
|
-
export const handleAuth = (): Promise<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
61
|
+
export const handleAuth = (): Promise<boolean> => {
|
|
62
|
+
authPromise = authenticate(config);
|
|
63
|
+
authPromise.then(
|
|
64
|
+
(isLoggedIn) => {
|
|
65
|
+
if (!isLoggedIn) {
|
|
66
|
+
notifyAuthFailure(AuthFailureType.SDK);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
() => {
|
|
70
|
+
notifyAuthFailure(AuthFailureType.SDK);
|
|
71
|
+
},
|
|
72
|
+
);
|
|
28
73
|
return authPromise;
|
|
29
74
|
};
|
|
30
75
|
|
|
31
|
-
export const getEmbedConfig = (): EmbedConfig => config;
|
|
32
|
-
|
|
33
|
-
export const getAuthPromise = (): Promise<void> => authPromise;
|
|
34
|
-
|
|
35
76
|
/**
|
|
36
77
|
* Prefetches static resources from the specified URL. Web browsers can then cache the prefetched resources and serve them from the user's local disk to provide faster access to your app.
|
|
37
78
|
* @param url The URL provided for prefetch
|
|
@@ -59,8 +100,13 @@ export const prefetch = (url?: string): void => {
|
|
|
59
100
|
*
|
|
60
101
|
* @returns authPromise Promise which resolves when authentication is complete.
|
|
61
102
|
*/
|
|
62
|
-
export const init = (embedConfig: EmbedConfig):
|
|
63
|
-
config =
|
|
103
|
+
export const init = (embedConfig: EmbedConfig): EventEmitter => {
|
|
104
|
+
config = {
|
|
105
|
+
...CONFIG_DEFAULTS,
|
|
106
|
+
...embedConfig,
|
|
107
|
+
thoughtSpotHost: getThoughtSpotHost(embedConfig),
|
|
108
|
+
};
|
|
109
|
+
authEE = new EventEmitter();
|
|
64
110
|
handleAuth();
|
|
65
111
|
|
|
66
112
|
uploadMixpanelEvent(MIXPANEL_EVENT.VISUAL_SDK_CALLED_INIT, {
|
|
@@ -71,7 +117,21 @@ export const init = (embedConfig: EmbedConfig): Promise<void> => {
|
|
|
71
117
|
if (config.callPrefetch) {
|
|
72
118
|
prefetch(config.thoughtSpotHost);
|
|
73
119
|
}
|
|
74
|
-
return
|
|
120
|
+
return authEE;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export function disableAutoLogin(): void {
|
|
124
|
+
config.autoLogin = false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export const logout = (doNotDisableAutoLogin = false): Promise<boolean> => {
|
|
128
|
+
if (!doNotDisableAutoLogin) {
|
|
129
|
+
disableAutoLogin();
|
|
130
|
+
}
|
|
131
|
+
return _logout(config).then((isLoggedIn) => {
|
|
132
|
+
notifyLogout();
|
|
133
|
+
return isLoggedIn;
|
|
134
|
+
});
|
|
75
135
|
};
|
|
76
136
|
|
|
77
137
|
let renderQueue: Promise<any> = Promise.resolve();
|
|
@@ -89,3 +149,10 @@ export const renderInQueue = (fn: (next?: (val?: any) => void) => void) => {
|
|
|
89
149
|
fn(() => {}); // eslint-disable-line @typescript-eslint/no-empty-function
|
|
90
150
|
}
|
|
91
151
|
};
|
|
152
|
+
|
|
153
|
+
// For testing purposes only
|
|
154
|
+
export function reset(): void {
|
|
155
|
+
config = {} as any;
|
|
156
|
+
authEE = null;
|
|
157
|
+
authPromise = null;
|
|
158
|
+
}
|
package/src/embed/embed.spec.ts
CHANGED
|
@@ -71,7 +71,7 @@ describe('Custom CSS Url', () => {
|
|
|
71
71
|
document.body.innerHTML = getDocumentBody();
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
-
test
|
|
74
|
+
test('passing customCssUrl should set the correct query params on the iframe', async (done) => {
|
|
75
75
|
init({
|
|
76
76
|
thoughtSpotHost,
|
|
77
77
|
authType: AuthType.None,
|
package/src/embed/events.spec.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
SearchEmbed,
|
|
6
6
|
PinboardEmbed,
|
|
7
7
|
LiveboardEmbed,
|
|
8
|
+
AppEmbed,
|
|
8
9
|
HostEvent,
|
|
9
10
|
} from '../index';
|
|
10
11
|
import {
|
|
@@ -247,4 +248,35 @@ describe('test communication between host app and ThoughtSpot', () => {
|
|
|
247
248
|
};
|
|
248
249
|
expect(mockPort.postMessage).toHaveBeenCalledWith(heightObj);
|
|
249
250
|
});
|
|
251
|
+
test('ALL event listener should fire for all events with the event type set correctly', async () => {
|
|
252
|
+
const embed = new AppEmbed(getRootEl(), defaultViewConfig);
|
|
253
|
+
const spy = jest.fn();
|
|
254
|
+
embed.on(EmbedEvent.ALL, spy);
|
|
255
|
+
embed.render();
|
|
256
|
+
|
|
257
|
+
await executeAfterWait(() => {
|
|
258
|
+
const iframe = getIFrameEl();
|
|
259
|
+
postMessageToParent(iframe.contentWindow, {
|
|
260
|
+
type: EmbedEvent.CustomAction,
|
|
261
|
+
data: PAYLOAD,
|
|
262
|
+
});
|
|
263
|
+
postMessageToParent(iframe.contentWindow, {
|
|
264
|
+
type: EmbedEvent.DialogOpen,
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
await executeAfterWait(() => {
|
|
269
|
+
expect(spy).toHaveBeenCalledTimes(3);
|
|
270
|
+
expect(spy.mock.calls[0][0]).toMatchObject({
|
|
271
|
+
type: EmbedEvent.Init,
|
|
272
|
+
});
|
|
273
|
+
expect(spy.mock.calls[1][0]).toMatchObject({
|
|
274
|
+
type: EmbedEvent.CustomAction,
|
|
275
|
+
data: PAYLOAD,
|
|
276
|
+
});
|
|
277
|
+
expect(spy.mock.calls[2][0]).toMatchObject({
|
|
278
|
+
type: EmbedEvent.DialogOpen,
|
|
279
|
+
});
|
|
280
|
+
}, EVENT_WAIT_TIME);
|
|
281
|
+
});
|
|
250
282
|
});
|
package/src/embed/search.ts
CHANGED
|
@@ -49,6 +49,10 @@ export interface SearchViewConfig extends ViewConfig {
|
|
|
49
49
|
* using raw answer data.
|
|
50
50
|
*/
|
|
51
51
|
hideResults?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* If set to true, expands all the data sources panel.
|
|
54
|
+
*/
|
|
55
|
+
expandAllDataSource?: boolean;
|
|
52
56
|
/**
|
|
53
57
|
* If set to true, the Search Assist feature is enabled.
|
|
54
58
|
*/
|
|
@@ -126,6 +130,7 @@ export class SearchEmbed extends TsEmbed {
|
|
|
126
130
|
private getIFrameSrc(answerId: string, dataSources?: string[]) {
|
|
127
131
|
const {
|
|
128
132
|
hideResults,
|
|
133
|
+
expandAllDataSource,
|
|
129
134
|
enableSearchAssist,
|
|
130
135
|
forceTable,
|
|
131
136
|
searchOptions,
|
|
@@ -165,15 +165,16 @@ describe('Unit test case for ts embed', () => {
|
|
|
165
165
|
});
|
|
166
166
|
});
|
|
167
167
|
|
|
168
|
-
describe('when thoughtSpotHost have value and authPromise return
|
|
168
|
+
describe('when thoughtSpotHost have value and authPromise return response true/false', () => {
|
|
169
169
|
beforeAll(() => {
|
|
170
170
|
init({
|
|
171
171
|
thoughtSpotHost,
|
|
172
172
|
authType: AuthType.None,
|
|
173
|
+
loginFailedMessage: 'Failed to Login',
|
|
173
174
|
});
|
|
174
175
|
});
|
|
175
176
|
|
|
176
|
-
|
|
177
|
+
const setup = async (isLoggedIn = false) => {
|
|
177
178
|
jest.spyOn(window, 'addEventListener').mockImplementationOnce(
|
|
178
179
|
(event, handler, options) => {
|
|
179
180
|
handler({
|
|
@@ -186,10 +187,9 @@ describe('Unit test case for ts embed', () => {
|
|
|
186
187
|
},
|
|
187
188
|
);
|
|
188
189
|
const iFrame: any = document.createElement('div');
|
|
189
|
-
jest.spyOn(
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
).mockResolvedValueOnce(() => Promise.resolve());
|
|
190
|
+
jest.spyOn(baseInstance, 'getAuthPromise').mockResolvedValueOnce(
|
|
191
|
+
isLoggedIn,
|
|
192
|
+
);
|
|
193
193
|
const tsEmbed = new SearchEmbed(getRootEl(), {});
|
|
194
194
|
iFrame.contentWindow = null;
|
|
195
195
|
tsEmbed.on(EmbedEvent.CustomAction, jest.fn());
|
|
@@ -199,10 +199,11 @@ describe('Unit test case for ts embed', () => {
|
|
|
199
199
|
},
|
|
200
200
|
);
|
|
201
201
|
jest.spyOn(document, 'createElement').mockReturnValueOnce(iFrame);
|
|
202
|
-
tsEmbed.render();
|
|
203
|
-
}
|
|
202
|
+
await tsEmbed.render();
|
|
203
|
+
};
|
|
204
204
|
|
|
205
|
-
test('mixpanel should call with VISUAL_SDK_RENDER_COMPLETE', () => {
|
|
205
|
+
test('mixpanel should call with VISUAL_SDK_RENDER_COMPLETE', async () => {
|
|
206
|
+
await setup(true);
|
|
206
207
|
expect(mockMixPanelEvent).toBeCalledWith(
|
|
207
208
|
MIXPANEL_EVENT.VISUAL_SDK_RENDER_START,
|
|
208
209
|
);
|
|
@@ -212,11 +213,20 @@ describe('Unit test case for ts embed', () => {
|
|
|
212
213
|
});
|
|
213
214
|
|
|
214
215
|
test('Should remove prefetch iframe', async () => {
|
|
216
|
+
await setup(true);
|
|
215
217
|
const prefetchIframe = document.querySelectorAll<HTMLIFrameElement>(
|
|
216
218
|
'.prefetchIframe',
|
|
217
219
|
);
|
|
218
220
|
expect(prefetchIframe.length).toBe(0);
|
|
219
221
|
});
|
|
222
|
+
|
|
223
|
+
test('Should render failure when login fails', async (done) => {
|
|
224
|
+
setup(false);
|
|
225
|
+
executeAfterWait(() => {
|
|
226
|
+
expect(getRootEl().innerHTML).toContain('Failed to Login');
|
|
227
|
+
done();
|
|
228
|
+
});
|
|
229
|
+
});
|
|
220
230
|
});
|
|
221
231
|
|
|
222
232
|
describe('when thoughtSpotHost have value and authPromise return error', () => {
|
package/src/embed/ts-embed.ts
CHANGED
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
MessageCallbackObj,
|
|
35
35
|
} from '../types';
|
|
36
36
|
import { uploadMixpanelEvent, MIXPANEL_EVENT } from '../mixpanel-service';
|
|
37
|
-
import {
|
|
37
|
+
import { processEventData } from '../utils/processData';
|
|
38
38
|
import { processTrigger } from '../utils/processTrigger';
|
|
39
39
|
import pkgInfo from '../../package.json';
|
|
40
40
|
import { getAuthPromise, getEmbedConfig, renderInQueue } from './base';
|
|
@@ -212,14 +212,6 @@ export class TsEmbed {
|
|
|
212
212
|
this.isError = false;
|
|
213
213
|
this.viewConfig = viewConfig;
|
|
214
214
|
this.shouldEncodeUrlQueryParams = this.embedConfig.shouldEncodeUrlQueryParams;
|
|
215
|
-
if (!this.embedConfig.suppressNoCookieAccessAlert) {
|
|
216
|
-
this.on(EmbedEvent.NoCookieAccess, () => {
|
|
217
|
-
// eslint-disable-next-line no-alert
|
|
218
|
-
alert(
|
|
219
|
-
'Third party cookie access is blocked on this browser, please allow third party cookies for ThoughtSpot to work properly',
|
|
220
|
-
);
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
215
|
}
|
|
224
216
|
|
|
225
217
|
/**
|
|
@@ -279,9 +271,10 @@ export class TsEmbed {
|
|
|
279
271
|
* will be removed for ts7.oct.cl
|
|
280
272
|
* @hidden
|
|
281
273
|
*/
|
|
282
|
-
private formatEventData(event: MessageEvent) {
|
|
274
|
+
private formatEventData(event: MessageEvent, eventType: string) {
|
|
283
275
|
const eventData = {
|
|
284
276
|
...event.data,
|
|
277
|
+
type: eventType,
|
|
285
278
|
};
|
|
286
279
|
if (!eventData.data) {
|
|
287
280
|
eventData.data = event.data.payload;
|
|
@@ -299,11 +292,16 @@ export class TsEmbed {
|
|
|
299
292
|
window.addEventListener('message', (event) => {
|
|
300
293
|
const eventType = this.getEventType(event);
|
|
301
294
|
const eventPort = this.getEventPort(event);
|
|
302
|
-
const eventData = this.formatEventData(event);
|
|
295
|
+
const eventData = this.formatEventData(event, eventType);
|
|
303
296
|
if (event.source === this.iFrame.contentWindow) {
|
|
304
297
|
this.executeCallbacks(
|
|
305
298
|
eventType,
|
|
306
|
-
|
|
299
|
+
processEventData(
|
|
300
|
+
eventType,
|
|
301
|
+
eventData,
|
|
302
|
+
this.thoughtSpotHost,
|
|
303
|
+
this.el,
|
|
304
|
+
),
|
|
307
305
|
eventPort,
|
|
308
306
|
);
|
|
309
307
|
}
|
|
@@ -457,12 +455,18 @@ export class TsEmbed {
|
|
|
457
455
|
data: {
|
|
458
456
|
timestamp: initTimestamp,
|
|
459
457
|
},
|
|
458
|
+
type: EmbedEvent.Init,
|
|
460
459
|
});
|
|
461
460
|
|
|
462
461
|
uploadMixpanelEvent(MIXPANEL_EVENT.VISUAL_SDK_RENDER_START);
|
|
463
462
|
|
|
464
463
|
getAuthPromise()
|
|
465
|
-
?.then(() => {
|
|
464
|
+
?.then((isLoggedIn: boolean) => {
|
|
465
|
+
if (!isLoggedIn) {
|
|
466
|
+
this.el.innerHTML = this.embedConfig.loginFailedMessage;
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
|
|
466
470
|
uploadMixpanelEvent(
|
|
467
471
|
MIXPANEL_EVENT.VISUAL_SDK_RENDER_COMPLETE,
|
|
468
472
|
);
|
|
@@ -490,7 +494,7 @@ export class TsEmbed {
|
|
|
490
494
|
frameWidth || DEFAULT_EMBED_WIDTH,
|
|
491
495
|
);
|
|
492
496
|
const height = getCssDimension(
|
|
493
|
-
|
|
497
|
+
frameHeight || DEFAULT_EMBED_HEIGHT,
|
|
494
498
|
);
|
|
495
499
|
setAttributes(this.iFrame, restParams);
|
|
496
500
|
|
|
@@ -505,6 +509,7 @@ export class TsEmbed {
|
|
|
505
509
|
data: {
|
|
506
510
|
timestamp: loadTimestamp,
|
|
507
511
|
},
|
|
512
|
+
type: EmbedEvent.Load,
|
|
508
513
|
});
|
|
509
514
|
uploadMixpanelEvent(
|
|
510
515
|
MIXPANEL_EVENT.VISUAL_SDK_IFRAME_LOAD_PERFORMANCE,
|
|
@@ -533,6 +538,7 @@ export class TsEmbed {
|
|
|
533
538
|
uploadMixpanelEvent(
|
|
534
539
|
MIXPANEL_EVENT.VISUAL_SDK_RENDER_FAILED,
|
|
535
540
|
);
|
|
541
|
+
this.el.innerHTML = this.embedConfig.loginFailedMessage;
|
|
536
542
|
this.handleError(error);
|
|
537
543
|
});
|
|
538
544
|
});
|
|
@@ -558,6 +564,8 @@ export class TsEmbed {
|
|
|
558
564
|
eventPort?: MessagePort | void,
|
|
559
565
|
): void {
|
|
560
566
|
const callbacks = this.eventHandlerMap.get(eventType) || [];
|
|
567
|
+
const allHandlers = this.eventHandlerMap.get(EmbedEvent.ALL) || [];
|
|
568
|
+
callbacks.push(...allHandlers);
|
|
561
569
|
const dataStatus = data?.status || embedEventStatus.END;
|
|
562
570
|
callbacks.forEach((callbackObj) => {
|
|
563
571
|
if (
|
|
@@ -760,9 +768,10 @@ export class V1Embed extends TsEmbed {
|
|
|
760
768
|
public on(
|
|
761
769
|
messageType: EmbedEvent,
|
|
762
770
|
callback: MessageCallback,
|
|
771
|
+
options: MessageOptions = { start: false },
|
|
763
772
|
): typeof TsEmbed.prototype {
|
|
764
773
|
const eventType = this.getCompatibleEventType(messageType);
|
|
765
774
|
|
|
766
|
-
return super.on(eventType, callback);
|
|
775
|
+
return super.on(eventType, callback, options);
|
|
767
776
|
}
|
|
768
777
|
}
|
package/src/index.ts
CHANGED
|
@@ -9,13 +9,14 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { AppEmbed, Page, AppViewConfig } from './embed/app';
|
|
12
|
-
import { init, prefetch } from './embed/base';
|
|
12
|
+
import { init, prefetch, logout } from './embed/base';
|
|
13
13
|
import {
|
|
14
14
|
PinboardEmbed,
|
|
15
15
|
LiveboardViewConfig,
|
|
16
16
|
LiveboardEmbed,
|
|
17
17
|
} from './embed/liveboard';
|
|
18
18
|
import { SearchEmbed, SearchViewConfig } from './embed/search';
|
|
19
|
+
import { AuthFailureType, AuthStatus } from './auth';
|
|
19
20
|
import {
|
|
20
21
|
AuthType,
|
|
21
22
|
RuntimeFilter,
|
|
@@ -29,11 +30,14 @@ import {
|
|
|
29
30
|
|
|
30
31
|
export {
|
|
31
32
|
init,
|
|
33
|
+
logout,
|
|
32
34
|
prefetch,
|
|
33
35
|
SearchEmbed,
|
|
34
36
|
PinboardEmbed,
|
|
35
37
|
LiveboardEmbed,
|
|
36
38
|
AppEmbed,
|
|
39
|
+
AuthFailureType,
|
|
40
|
+
AuthStatus,
|
|
37
41
|
// types
|
|
38
42
|
Page,
|
|
39
43
|
AuthType,
|
package/src/test/test-utils.ts
CHANGED
|
@@ -14,7 +14,7 @@ type DOMElement = HTMLElement | Document;
|
|
|
14
14
|
|
|
15
15
|
export const getRootEl = () => document.getElementById('embed');
|
|
16
16
|
|
|
17
|
-
export const getRootEl2 = () => document.getElementById('
|
|
17
|
+
export const getRootEl2 = () => document.getElementById('embed-2');
|
|
18
18
|
|
|
19
19
|
export const getIFrameEl = (container: DOMElement = document) =>
|
|
20
20
|
container.querySelector('iframe');
|
package/src/types.ts
CHANGED
|
@@ -97,6 +97,15 @@ export interface EmbedConfig {
|
|
|
97
97
|
*/
|
|
98
98
|
noRedirect?: boolean;
|
|
99
99
|
|
|
100
|
+
/**
|
|
101
|
+
* [SSO] For SSO Authentication, one can supply an optional path param,
|
|
102
|
+
* this will be the path on the host origin where the SAML flow will be
|
|
103
|
+
* terminated.
|
|
104
|
+
*
|
|
105
|
+
* @version SDK: 1.10.2 | ThoughtSpot: *
|
|
106
|
+
*/
|
|
107
|
+
redirectPath?: string;
|
|
108
|
+
|
|
100
109
|
/** @internal */
|
|
101
110
|
basepath?: string;
|
|
102
111
|
|
|
@@ -131,6 +140,12 @@ export interface EmbedConfig {
|
|
|
131
140
|
*/
|
|
132
141
|
disableLoginRedirect?: boolean;
|
|
133
142
|
|
|
143
|
+
/**
|
|
144
|
+
* This message is displayed on the embed view when the login fails.
|
|
145
|
+
* @version 1.10.1 | ThoughtSpot: *
|
|
146
|
+
*/
|
|
147
|
+
loginFailedMessage?: string;
|
|
148
|
+
|
|
134
149
|
/**
|
|
135
150
|
* Calls the prefetch method internally when set to true
|
|
136
151
|
* @default false
|
|
@@ -365,6 +380,16 @@ export enum EmbedEvent {
|
|
|
365
380
|
* The ThoughtSpot auth session has expired.
|
|
366
381
|
*/
|
|
367
382
|
AuthExpire = 'ThoughtspotAuthExpired',
|
|
383
|
+
/**
|
|
384
|
+
* ThoughtSpot failed to validate the auth session.
|
|
385
|
+
* @hidden
|
|
386
|
+
*/
|
|
387
|
+
AuthFailure = 'ThoughtspotAuthFailure',
|
|
388
|
+
/**
|
|
389
|
+
* ThoughtSpot failed to validate the auth session.
|
|
390
|
+
* @hidden
|
|
391
|
+
*/
|
|
392
|
+
AuthLogout = 'ThoughtspotAuthLogout',
|
|
368
393
|
/**
|
|
369
394
|
* The height of the embedded Liveboard or visualization has been computed.
|
|
370
395
|
* @return data - The height of the embedded Liveboard or visualization
|
|
@@ -416,6 +441,12 @@ export enum EmbedEvent {
|
|
|
416
441
|
* @version 1.9.1 or later
|
|
417
442
|
*/
|
|
418
443
|
LiveboardRendered = 'PinboardRendered',
|
|
444
|
+
/**
|
|
445
|
+
* This can be used to register an event listener which
|
|
446
|
+
* is triggered on all events.
|
|
447
|
+
* @version SDK: 1.10.0 | ThoughtSpot: any
|
|
448
|
+
*/
|
|
449
|
+
ALL = '*',
|
|
419
450
|
/**
|
|
420
451
|
* Emitted when answer is saved in the app
|
|
421
452
|
* @version SDK: 1.11.0 | ThoughtSpot: 8.3.0.cl
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
fetchAuthService,
|
|
5
5
|
fetchBasicAuthService,
|
|
6
6
|
} from './authService';
|
|
7
|
+
import { EndPoints } from '../auth';
|
|
7
8
|
|
|
8
9
|
const thoughtSpotHost = 'http://10.79.135.124:3000';
|
|
9
10
|
|
|
@@ -21,6 +22,7 @@ describe('Unit test for authService', () => {
|
|
|
21
22
|
Promise.resolve({
|
|
22
23
|
json: () => ({ success: true }),
|
|
23
24
|
status: 200,
|
|
25
|
+
ok: true,
|
|
24
26
|
}),
|
|
25
27
|
);
|
|
26
28
|
const response = await fetchSessionInfoService(authVerificationUrl);
|
|
@@ -32,6 +34,7 @@ describe('Unit test for authService', () => {
|
|
|
32
34
|
global.fetch = jest.fn(() =>
|
|
33
35
|
Promise.resolve({
|
|
34
36
|
text: () => ({ success: true }),
|
|
37
|
+
ok: true,
|
|
35
38
|
}),
|
|
36
39
|
);
|
|
37
40
|
const response = await fetchAuthTokenService(authEndpoint);
|
|
@@ -40,14 +43,37 @@ describe('Unit test for authService', () => {
|
|
|
40
43
|
});
|
|
41
44
|
|
|
42
45
|
test('fetchAuthService', async () => {
|
|
43
|
-
global.fetch = jest.fn(() =>
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
global.fetch = jest.fn(() =>
|
|
47
|
+
Promise.resolve({ success: true, ok: true }),
|
|
48
|
+
);
|
|
49
|
+
await fetchAuthService(thoughtSpotHost, username, authToken);
|
|
50
|
+
expect(fetch).toBeCalledWith(
|
|
51
|
+
`${thoughtSpotHost}${EndPoints.TOKEN_LOGIN}?username=${username}&auth_token=${authToken}`,
|
|
52
|
+
{
|
|
53
|
+
credentials: 'include',
|
|
54
|
+
redirect: 'manual',
|
|
55
|
+
},
|
|
56
|
+
);
|
|
46
57
|
});
|
|
47
58
|
|
|
48
|
-
test('fetchBasicAuthService', async () => {
|
|
49
|
-
global.fetch = jest.fn(() =>
|
|
59
|
+
test('fetchBasicAuthService called with manual redirect', async () => {
|
|
60
|
+
global.fetch = jest.fn(() =>
|
|
61
|
+
Promise.resolve({ success: true, ok: true }),
|
|
62
|
+
);
|
|
50
63
|
await fetchBasicAuthService(thoughtSpotHost, username, password);
|
|
51
64
|
expect(fetch).toBeCalled();
|
|
52
65
|
});
|
|
66
|
+
|
|
67
|
+
test('log error on API failures', async () => {
|
|
68
|
+
jest.spyOn(global.console, 'error').mockImplementation(() => undefined);
|
|
69
|
+
global.fetch = jest.fn(() =>
|
|
70
|
+
Promise.resolve({
|
|
71
|
+
text: () => Promise.resolve('error'),
|
|
72
|
+
status: 500,
|
|
73
|
+
ok: false,
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
76
|
+
await fetchSessionInfoService(authVerificationUrl);
|
|
77
|
+
expect(global.console.error).toHaveBeenCalledWith('Failure', 'error');
|
|
78
|
+
});
|
|
53
79
|
});
|