@thoughtspot/visual-embed-sdk 1.10.0-alpha.2 → 1.10.0
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 +14 -2
- package/README.md +22 -3
- package/dist/src/embed/app.d.ts +9 -3
- package/dist/src/embed/search.d.ts +4 -0
- package/dist/src/embed/ts-embed.d.ts +18 -4
- package/dist/src/types.d.ts +139 -3
- package/dist/src/utils.d.ts +4 -0
- package/dist/tsembed.es.js +179 -21
- package/dist/tsembed.js +179 -21
- package/lib/package.json +2 -2
- package/lib/src/embed/app.d.ts +9 -3
- package/lib/src/embed/app.js +23 -7
- package/lib/src/embed/app.js.map +1 -1
- package/lib/src/embed/app.spec.js +36 -2
- package/lib/src/embed/app.spec.js.map +1 -1
- package/lib/src/embed/base.spec.js +13 -1
- package/lib/src/embed/base.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/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 +18 -4
- package/lib/src/embed/ts-embed.js +36 -14
- package/lib/src/embed/ts-embed.js.map +1 -1
- package/lib/src/embed/ts-embed.spec.js +123 -14
- package/lib/src/embed/ts-embed.spec.js.map +1 -1
- package/lib/src/types.d.ts +139 -3
- package/lib/src/types.js +116 -0
- package/lib/src/types.js.map +1 -1
- package/lib/src/utils.d.ts +4 -0
- package/lib/src/utils.js +4 -0
- package/lib/src/utils.js.map +1 -1
- package/lib/src/visual-embed-sdk.d.ts +170 -10
- package/package.json +2 -2
- package/src/embed/app.spec.ts +49 -1
- package/src/embed/app.ts +24 -6
- package/src/embed/base.spec.ts +14 -0
- package/src/embed/events.spec.ts +32 -0
- package/src/embed/search.ts +5 -0
- package/src/embed/ts-embed.spec.ts +166 -14
- package/src/embed/ts-embed.ts +56 -16
- package/src/types.ts +148 -1
- package/src/utils.ts +5 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thoughtspot/visual-embed-sdk",
|
|
3
|
-
"version": "1.10.0
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "ThoughtSpot Embed SDK",
|
|
5
5
|
"module": "lib/src/index.js",
|
|
6
6
|
"main": "dist/tsembed.js",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"test-sdk": "jest -c jest.config.sdk.js",
|
|
33
33
|
"test-docs": "jest -c jest.config.docs.js",
|
|
34
34
|
"test": "npm run test-sdk && npm run test-docs && npx istanbul-merge --out ./coverage/coverage.json ./coverage/docs/coverage-final.json ./coverage/sdk/coverage-final.json && npx istanbul report --include ./coverage/coverage.json --dir ./coverage lcov",
|
|
35
|
-
"posttest": "cat ./coverage/lcov.info | coveralls",
|
|
35
|
+
"posttest": "cat ./coverage/sdk/lcov.info | coveralls",
|
|
36
36
|
"prepublishOnly": "npm run test; npm run tsc; npm run bundle-dts; npm run build",
|
|
37
37
|
"publish-dev": "npm publish --tag dev",
|
|
38
38
|
"publish-prod": "npm publish --tag latest"
|
package/src/embed/app.spec.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { AppEmbed, AppViewConfig, Page } from './app';
|
|
2
2
|
import { init } from '../index';
|
|
3
|
-
import { Action, AuthType, RuntimeFilterOp } from '../types';
|
|
3
|
+
import { Action, AuthType, HostEvent, RuntimeFilterOp } from '../types';
|
|
4
4
|
import {
|
|
5
5
|
executeAfterWait,
|
|
6
6
|
getDocumentBody,
|
|
7
7
|
getIFrameSrc,
|
|
8
8
|
getRootEl,
|
|
9
|
+
getIFrameEl,
|
|
9
10
|
} from '../test/test-utils';
|
|
10
11
|
import { version } from '../../package.json';
|
|
11
12
|
import * as config from '../config';
|
|
@@ -82,6 +83,7 @@ describe('App embed tests', () => {
|
|
|
82
83
|
[Page.Liveboards]: 'pinboards',
|
|
83
84
|
[Page.Data]: 'data/tables',
|
|
84
85
|
[Page.Home]: 'home',
|
|
86
|
+
[Page.SpotIQ]: 'insights/results',
|
|
85
87
|
};
|
|
86
88
|
|
|
87
89
|
const pageIds = Object.keys(pageRouteMap);
|
|
@@ -194,6 +196,52 @@ describe('App embed tests', () => {
|
|
|
194
196
|
);
|
|
195
197
|
});
|
|
196
198
|
|
|
199
|
+
test('navigateToPage with noReload should trigger the appropriate event', async () => {
|
|
200
|
+
const appEmbed = new AppEmbed(getRootEl(), {
|
|
201
|
+
frameParams: {
|
|
202
|
+
width: '100%',
|
|
203
|
+
height: '100%',
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
await appEmbed.render();
|
|
207
|
+
|
|
208
|
+
const iframe = getIFrameEl();
|
|
209
|
+
iframe.contentWindow.postMessage = jest.fn();
|
|
210
|
+
appEmbed.navigateToPage(path, true);
|
|
211
|
+
|
|
212
|
+
expect(iframe.contentWindow.postMessage).toHaveBeenCalledWith(
|
|
213
|
+
expect.objectContaining({
|
|
214
|
+
type: HostEvent.Navigate,
|
|
215
|
+
data: path,
|
|
216
|
+
}),
|
|
217
|
+
`http://${thoughtSpotHost}`,
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
appEmbed.navigateToPage(-1, true);
|
|
221
|
+
expect(iframe.contentWindow.postMessage).toHaveBeenCalledWith(
|
|
222
|
+
expect.objectContaining({
|
|
223
|
+
type: HostEvent.Navigate,
|
|
224
|
+
data: -1,
|
|
225
|
+
}),
|
|
226
|
+
`http://${thoughtSpotHost}`,
|
|
227
|
+
);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test('Do not allow number path without noReload in navigateToPage', async () => {
|
|
231
|
+
const appEmbed = new AppEmbed(getRootEl(), {
|
|
232
|
+
frameParams: {
|
|
233
|
+
width: '100%',
|
|
234
|
+
height: '100%',
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
await appEmbed.render();
|
|
238
|
+
spyOn(console, 'warn');
|
|
239
|
+
appEmbed.navigateToPage(-1);
|
|
240
|
+
expect(console.warn).toHaveBeenCalledWith(
|
|
241
|
+
'Path can only by a string when triggered without noReload',
|
|
242
|
+
);
|
|
243
|
+
});
|
|
244
|
+
|
|
197
245
|
test('navigateToPage function use before render', async () => {
|
|
198
246
|
spyOn(console, 'log');
|
|
199
247
|
const appEmbed = new AppEmbed(getRootEl(), {
|
package/src/embed/app.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { getFilterQuery, getQueryParamString } from '../utils';
|
|
13
|
-
import { Param, RuntimeFilter, DOMSelector } from '../types';
|
|
13
|
+
import { Param, RuntimeFilter, DOMSelector, HostEvent } from '../types';
|
|
14
14
|
import { V1Embed, ViewConfig } from './ts-embed';
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -42,6 +42,10 @@ export enum Page {
|
|
|
42
42
|
* Data management page
|
|
43
43
|
*/
|
|
44
44
|
Data = 'data',
|
|
45
|
+
/**
|
|
46
|
+
* SpotIQ listing page
|
|
47
|
+
*/
|
|
48
|
+
SpotIQ = 'spotiq',
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
/**
|
|
@@ -161,6 +165,8 @@ export class AppEmbed extends V1Embed {
|
|
|
161
165
|
return 'pinboards';
|
|
162
166
|
case Page.Data:
|
|
163
167
|
return 'data/tables';
|
|
168
|
+
case Page.SpotIQ:
|
|
169
|
+
return 'insights/results';
|
|
164
170
|
case Page.Home:
|
|
165
171
|
default:
|
|
166
172
|
return 'home';
|
|
@@ -188,19 +194,31 @@ export class AppEmbed extends V1Embed {
|
|
|
188
194
|
/**
|
|
189
195
|
* Navigate to particular page for app embed. eg:answers/pinboards/home
|
|
190
196
|
* This is used for embedding answers, pinboards, visualizations and full application only.
|
|
191
|
-
* @param path The string, set to iframe src and navigate to new page
|
|
197
|
+
* @param path string | number The string, set to iframe src and navigate to new page
|
|
192
198
|
* eg: appEmbed.navigateToPage('pinboards')
|
|
199
|
+
* When used with `noReload` this can also be a number like 1/-1 to go forward/back.
|
|
200
|
+
* @param noReload boolean Trigger the navigation without reloading the page (version: 1.12.0 | 8.4.0.cl)
|
|
193
201
|
*/
|
|
194
|
-
public navigateToPage(path: string): void {
|
|
195
|
-
if (this.iFrame) {
|
|
202
|
+
public navigateToPage(path: string | number, noReload = false): void {
|
|
203
|
+
if (!this.iFrame) {
|
|
204
|
+
console.log('Please call render before invoking this method');
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (noReload) {
|
|
208
|
+
this.trigger(HostEvent.Navigate, path);
|
|
209
|
+
} else {
|
|
210
|
+
if (typeof path !== 'string') {
|
|
211
|
+
console.warn(
|
|
212
|
+
'Path can only by a string when triggered without noReload',
|
|
213
|
+
);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
196
216
|
const iframeSrc = this.iFrame.src;
|
|
197
217
|
const embedPath = '#/embed';
|
|
198
218
|
const currentPath = iframeSrc.includes(embedPath) ? embedPath : '#';
|
|
199
219
|
this.iFrame.src = `${
|
|
200
220
|
iframeSrc.split(currentPath)[0]
|
|
201
221
|
}${currentPath}/${path.replace(/^\/?#?\//, '')}`;
|
|
202
|
-
} else {
|
|
203
|
-
console.log('Please call render before invoking this method');
|
|
204
222
|
}
|
|
205
223
|
}
|
|
206
224
|
|
package/src/embed/base.spec.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
getDocumentBody,
|
|
6
6
|
getRootEl,
|
|
7
7
|
getRootEl2,
|
|
8
|
+
getIFrameSrc,
|
|
8
9
|
} from '../test/test-utils';
|
|
9
10
|
|
|
10
11
|
const thoughtSpotHost = 'tshost';
|
|
@@ -78,4 +79,17 @@ describe('Base TS Embed', () => {
|
|
|
78
79
|
|
|
79
80
|
expect(prefetch).toHaveBeenCalledTimes(0);
|
|
80
81
|
});
|
|
82
|
+
|
|
83
|
+
test('Sets the disableLoginRedirect param when autoLogin is true', async () => {
|
|
84
|
+
index.init({
|
|
85
|
+
thoughtSpotHost,
|
|
86
|
+
authType: index.AuthType.None,
|
|
87
|
+
autoLogin: true,
|
|
88
|
+
});
|
|
89
|
+
const tsEmbed = new index.AppEmbed(getRootEl(), {});
|
|
90
|
+
await tsEmbed.render();
|
|
91
|
+
await executeAfterWait(() => {
|
|
92
|
+
expect(getIFrameSrc()).toContain('disableLoginRedirect=true');
|
|
93
|
+
});
|
|
94
|
+
});
|
|
81
95
|
});
|
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,
|
|
@@ -11,10 +11,12 @@ import {
|
|
|
11
11
|
} from '../index';
|
|
12
12
|
import { Action } from '../types';
|
|
13
13
|
import {
|
|
14
|
+
executeAfterWait,
|
|
14
15
|
getDocumentBody,
|
|
15
16
|
getIFrameEl,
|
|
16
17
|
getIFrameSrc,
|
|
17
18
|
getRootEl,
|
|
19
|
+
postMessageToParent,
|
|
18
20
|
} from '../test/test-utils';
|
|
19
21
|
import * as config from '../config';
|
|
20
22
|
import * as tsEmbedInstance from './ts-embed';
|
|
@@ -43,6 +45,126 @@ describe('Unit test case for ts embed', () => {
|
|
|
43
45
|
beforeEach(() => {
|
|
44
46
|
document.body.innerHTML = getDocumentBody();
|
|
45
47
|
});
|
|
48
|
+
|
|
49
|
+
afterEach(() => {
|
|
50
|
+
jest.clearAllMocks();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('Called Embed event status for start and end', () => {
|
|
54
|
+
beforeAll(() => {
|
|
55
|
+
init({
|
|
56
|
+
thoughtSpotHost: 'tshost',
|
|
57
|
+
authType: AuthType.None,
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('when Embed event status have start status', (done) => {
|
|
62
|
+
const mockEmbedEventPayload = {
|
|
63
|
+
type: EmbedEvent.Save,
|
|
64
|
+
data: { answerId: '123' },
|
|
65
|
+
status: 'start',
|
|
66
|
+
};
|
|
67
|
+
const searchEmbed = new SearchEmbed(getRootEl(), defaultViewConfig);
|
|
68
|
+
searchEmbed
|
|
69
|
+
.on(
|
|
70
|
+
EmbedEvent.Save,
|
|
71
|
+
(payload) => {
|
|
72
|
+
expect(payload).toEqual(mockEmbedEventPayload);
|
|
73
|
+
done();
|
|
74
|
+
},
|
|
75
|
+
{ start: true },
|
|
76
|
+
)
|
|
77
|
+
.render();
|
|
78
|
+
|
|
79
|
+
executeAfterWait(() => {
|
|
80
|
+
const iframe = getIFrameEl();
|
|
81
|
+
postMessageToParent(
|
|
82
|
+
iframe.contentWindow,
|
|
83
|
+
mockEmbedEventPayload,
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('should not called post message, when Embed event status have start and start option as false', () => {
|
|
89
|
+
const mockEmbedEventPayload = {
|
|
90
|
+
type: EmbedEvent.Save,
|
|
91
|
+
data: { answerId: '123' },
|
|
92
|
+
status: 'start',
|
|
93
|
+
};
|
|
94
|
+
const searchEmbed = new SearchEmbed(getRootEl(), defaultViewConfig);
|
|
95
|
+
searchEmbed
|
|
96
|
+
.on(EmbedEvent.Save, () => {
|
|
97
|
+
console.log('non callable');
|
|
98
|
+
})
|
|
99
|
+
.render();
|
|
100
|
+
|
|
101
|
+
executeAfterWait(() => {
|
|
102
|
+
const iframe = getIFrameEl();
|
|
103
|
+
iframe.contentWindow.postMessage = jest.fn();
|
|
104
|
+
postMessageToParent(
|
|
105
|
+
iframe.contentWindow,
|
|
106
|
+
mockEmbedEventPayload,
|
|
107
|
+
);
|
|
108
|
+
expect(iframe.contentWindow.postMessage).toHaveBeenCalledTimes(
|
|
109
|
+
0,
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('when Embed event status have end status', (done) => {
|
|
115
|
+
const mockEmbedEventPayload = {
|
|
116
|
+
type: EmbedEvent.Save,
|
|
117
|
+
data: { answerId: '123' },
|
|
118
|
+
status: 'end',
|
|
119
|
+
};
|
|
120
|
+
const searchEmbed = new SearchEmbed(getRootEl(), defaultViewConfig);
|
|
121
|
+
searchEmbed
|
|
122
|
+
.on(EmbedEvent.Save, (payload) => {
|
|
123
|
+
expect(payload).toEqual(mockEmbedEventPayload);
|
|
124
|
+
done();
|
|
125
|
+
})
|
|
126
|
+
.render();
|
|
127
|
+
|
|
128
|
+
executeAfterWait(() => {
|
|
129
|
+
const iframe = getIFrameEl();
|
|
130
|
+
postMessageToParent(
|
|
131
|
+
iframe.contentWindow,
|
|
132
|
+
mockEmbedEventPayload,
|
|
133
|
+
);
|
|
134
|
+
}, 1000);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('should not called post message, when Embed event status have end status and start is true', () => {
|
|
138
|
+
const mockEmbedEventPayload = {
|
|
139
|
+
type: EmbedEvent.Save,
|
|
140
|
+
data: { answerId: '123' },
|
|
141
|
+
status: 'end',
|
|
142
|
+
};
|
|
143
|
+
const searchEmbed = new SearchEmbed(getRootEl(), defaultViewConfig);
|
|
144
|
+
searchEmbed
|
|
145
|
+
.on(
|
|
146
|
+
EmbedEvent.Save,
|
|
147
|
+
() => {
|
|
148
|
+
console.log('non callable');
|
|
149
|
+
},
|
|
150
|
+
{ start: true },
|
|
151
|
+
)
|
|
152
|
+
.render();
|
|
153
|
+
|
|
154
|
+
executeAfterWait(() => {
|
|
155
|
+
const iframe = getIFrameEl();
|
|
156
|
+
iframe.contentWindow.postMessage = jest.fn();
|
|
157
|
+
postMessageToParent(
|
|
158
|
+
iframe.contentWindow,
|
|
159
|
+
mockEmbedEventPayload,
|
|
160
|
+
);
|
|
161
|
+
expect(iframe.contentWindow.postMessage).toHaveBeenCalledTimes(
|
|
162
|
+
0,
|
|
163
|
+
);
|
|
164
|
+
}, 1000);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
46
168
|
describe('when thoughtSpotHost have value and authPromise return success response', () => {
|
|
47
169
|
beforeAll(() => {
|
|
48
170
|
init({
|
|
@@ -266,19 +388,6 @@ describe('Unit test case for ts embed', () => {
|
|
|
266
388
|
);
|
|
267
389
|
});
|
|
268
390
|
|
|
269
|
-
test('Set Frame params to the iframe as attributes', async () => {
|
|
270
|
-
const appEmbed = new AppEmbed(getRootEl(), {
|
|
271
|
-
frameParams: {
|
|
272
|
-
width: '100%',
|
|
273
|
-
height: '100%',
|
|
274
|
-
allowtransparency: true,
|
|
275
|
-
},
|
|
276
|
-
});
|
|
277
|
-
await appEmbed.render();
|
|
278
|
-
const iframe = getIFrameEl();
|
|
279
|
-
expect(iframe.getAttribute('allowtransparency')).toBe('true');
|
|
280
|
-
});
|
|
281
|
-
|
|
282
391
|
test('navigateToPage function use before render', async () => {
|
|
283
392
|
spyOn(console, 'log');
|
|
284
393
|
const appEmbed = new AppEmbed(getRootEl(), {
|
|
@@ -317,12 +426,26 @@ describe('Unit test case for ts embed', () => {
|
|
|
317
426
|
});
|
|
318
427
|
});
|
|
319
428
|
|
|
320
|
-
describe('
|
|
429
|
+
describe('Iframe flags', () => {
|
|
321
430
|
beforeEach(() => {
|
|
322
431
|
jest.spyOn(config, 'getThoughtSpotHost').mockImplementation(
|
|
323
432
|
() => 'http://tshost',
|
|
324
433
|
);
|
|
325
434
|
});
|
|
435
|
+
|
|
436
|
+
test('Set Frame params to the iframe as attributes', async () => {
|
|
437
|
+
const appEmbed = new AppEmbed(getRootEl(), {
|
|
438
|
+
frameParams: {
|
|
439
|
+
width: '100%',
|
|
440
|
+
height: '100%',
|
|
441
|
+
allowtransparency: true,
|
|
442
|
+
},
|
|
443
|
+
});
|
|
444
|
+
await appEmbed.render();
|
|
445
|
+
const iframe = getIFrameEl();
|
|
446
|
+
expect(iframe.getAttribute('allowtransparency')).toBe('true');
|
|
447
|
+
});
|
|
448
|
+
|
|
326
449
|
it('should set the additional flags correctly on the iframe src', async () => {
|
|
327
450
|
const appEmbed = new AppEmbed(getRootEl(), {
|
|
328
451
|
frameParams: {
|
|
@@ -341,6 +464,35 @@ describe('Unit test case for ts embed', () => {
|
|
|
341
464
|
`&foo=bar&baz=1&bool=true${defaultParamsPost}#/home`,
|
|
342
465
|
);
|
|
343
466
|
});
|
|
467
|
+
|
|
468
|
+
it('Sets the showAlerts param', async () => {
|
|
469
|
+
const appEmbed = new AppEmbed(getRootEl(), {
|
|
470
|
+
frameParams: {
|
|
471
|
+
width: '100%',
|
|
472
|
+
height: '100%',
|
|
473
|
+
},
|
|
474
|
+
showAlerts: true,
|
|
475
|
+
});
|
|
476
|
+
await appEmbed.render();
|
|
477
|
+
expect(getIFrameSrc()).toBe(
|
|
478
|
+
`http://${thoughtSpotHost}/?embedApp=true&primaryNavHidden=true&profileAndHelpInNavBarHidden=false&${defaultParamsForPinboardEmbed}` +
|
|
479
|
+
`&showAlerts=true${defaultParamsPost}#/home`,
|
|
480
|
+
);
|
|
481
|
+
});
|
|
482
|
+
it('Sets the locale param', async () => {
|
|
483
|
+
const appEmbed = new AppEmbed(getRootEl(), {
|
|
484
|
+
frameParams: {
|
|
485
|
+
width: '100%',
|
|
486
|
+
height: '100%',
|
|
487
|
+
},
|
|
488
|
+
locale: 'ja-JP',
|
|
489
|
+
});
|
|
490
|
+
await appEmbed.render();
|
|
491
|
+
expect(getIFrameSrc()).toBe(
|
|
492
|
+
`http://${thoughtSpotHost}/?embedApp=true&primaryNavHidden=true&profileAndHelpInNavBarHidden=false&${defaultParamsForPinboardEmbed}` +
|
|
493
|
+
`&locale=ja-JP${defaultParamsPost}#/home`,
|
|
494
|
+
);
|
|
495
|
+
});
|
|
344
496
|
});
|
|
345
497
|
|
|
346
498
|
describe('validate getThoughtSpotPostUrlParams', () => {
|
package/src/embed/ts-embed.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
getEncodedQueryParamsString,
|
|
12
12
|
getCssDimension,
|
|
13
13
|
getOffsetTop,
|
|
14
|
+
embedEventStatus,
|
|
14
15
|
setAttributes,
|
|
15
16
|
} from '../utils';
|
|
16
17
|
import {
|
|
@@ -29,6 +30,8 @@ import {
|
|
|
29
30
|
RuntimeFilter,
|
|
30
31
|
Param,
|
|
31
32
|
EmbedConfig,
|
|
33
|
+
MessageOptions,
|
|
34
|
+
MessageCallbackObj,
|
|
32
35
|
} from '../types';
|
|
33
36
|
import { uploadMixpanelEvent, MIXPANEL_EVENT } from '../mixpanel-service';
|
|
34
37
|
import { getProcessData } from '../utils/processData';
|
|
@@ -116,15 +119,28 @@ export interface ViewConfig {
|
|
|
116
119
|
* @version 1.6.0 or later
|
|
117
120
|
*/
|
|
118
121
|
visibleActions?: Action[];
|
|
122
|
+
/**
|
|
123
|
+
* Show alert messages and toast messages in the embedded view.
|
|
124
|
+
* @version 1.11.0 | ThoughtSpot: 8.3.0.cl
|
|
125
|
+
*/
|
|
126
|
+
showAlerts?: boolean;
|
|
119
127
|
/**
|
|
120
128
|
* The list of runtime filters to apply to a search answer,
|
|
121
129
|
* visualization, or Liveboard.
|
|
122
130
|
*/
|
|
123
131
|
runtimeFilters?: RuntimeFilter[];
|
|
132
|
+
/**
|
|
133
|
+
* The locale/language to use for the embedded view.
|
|
134
|
+
* @version 1.9.4 or later
|
|
135
|
+
*/
|
|
136
|
+
locale?: string;
|
|
124
137
|
/**
|
|
125
138
|
* This is an object (key/val) of override flags which will be applied
|
|
126
139
|
* to the internal embedded object. This can be used to add any
|
|
127
140
|
* URL flag.
|
|
141
|
+
* Warning: This option is for advanced use only and is used internally
|
|
142
|
+
* to control embed behavior in non-regular ways. We do not publish the
|
|
143
|
+
* list of supported keys and values associated with each.
|
|
128
144
|
* @version SDK: 1.9.0 | ThoughtSpot: 8.1.0.cl
|
|
129
145
|
*/
|
|
130
146
|
additionalFlags?: { [key: string]: string | number | boolean };
|
|
@@ -166,7 +182,7 @@ export class TsEmbed {
|
|
|
166
182
|
* by the embedded app; multiple event handlers can be registered
|
|
167
183
|
* against a particular message type.
|
|
168
184
|
*/
|
|
169
|
-
private eventHandlerMap: Map<string,
|
|
185
|
+
private eventHandlerMap: Map<string, MessageCallbackObj[]>;
|
|
170
186
|
|
|
171
187
|
/**
|
|
172
188
|
* A flag that is set to true post render.
|
|
@@ -263,9 +279,10 @@ export class TsEmbed {
|
|
|
263
279
|
* will be removed for ts7.oct.cl
|
|
264
280
|
* @hidden
|
|
265
281
|
*/
|
|
266
|
-
private formatEventData(event: MessageEvent) {
|
|
282
|
+
private formatEventData(event: MessageEvent, eventType: string) {
|
|
267
283
|
const eventData = {
|
|
268
284
|
...event.data,
|
|
285
|
+
type: eventType,
|
|
269
286
|
};
|
|
270
287
|
if (!eventData.data) {
|
|
271
288
|
eventData.data = event.data.payload;
|
|
@@ -283,7 +300,7 @@ export class TsEmbed {
|
|
|
283
300
|
window.addEventListener('message', (event) => {
|
|
284
301
|
const eventType = this.getEventType(event);
|
|
285
302
|
const eventPort = this.getEventPort(event);
|
|
286
|
-
const eventData = this.formatEventData(event);
|
|
303
|
+
const eventData = this.formatEventData(event, eventType);
|
|
287
304
|
if (event.source === this.iFrame.contentWindow) {
|
|
288
305
|
this.executeCallbacks(
|
|
289
306
|
eventType,
|
|
@@ -335,10 +352,11 @@ export class TsEmbed {
|
|
|
335
352
|
queryParams[Param.ViewPortHeight] = window.innerHeight;
|
|
336
353
|
queryParams[Param.ViewPortWidth] = window.innerWidth;
|
|
337
354
|
queryParams[Param.Version] = version;
|
|
338
|
-
if (
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
355
|
+
if (
|
|
356
|
+
this.embedConfig.disableLoginRedirect === true ||
|
|
357
|
+
this.embedConfig.autoLogin === true
|
|
358
|
+
) {
|
|
359
|
+
queryParams[Param.DisableLoginRedirect] = true;
|
|
342
360
|
}
|
|
343
361
|
if (this.embedConfig.customCssUrl) {
|
|
344
362
|
queryParams[Param.CustomCSSUrl] = this.embedConfig.customCssUrl;
|
|
@@ -349,7 +367,9 @@ export class TsEmbed {
|
|
|
349
367
|
disabledActionReason,
|
|
350
368
|
hiddenActions,
|
|
351
369
|
visibleActions,
|
|
370
|
+
showAlerts,
|
|
352
371
|
additionalFlags,
|
|
372
|
+
locale,
|
|
353
373
|
} = this.viewConfig;
|
|
354
374
|
|
|
355
375
|
if (Array.isArray(visibleActions) && Array.isArray(hiddenActions)) {
|
|
@@ -371,6 +391,12 @@ export class TsEmbed {
|
|
|
371
391
|
if (Array.isArray(visibleActions)) {
|
|
372
392
|
queryParams[Param.VisibleActions] = visibleActions;
|
|
373
393
|
}
|
|
394
|
+
if (showAlerts !== undefined) {
|
|
395
|
+
queryParams[Param.ShowAlerts] = showAlerts;
|
|
396
|
+
}
|
|
397
|
+
if (locale !== undefined) {
|
|
398
|
+
queryParams[Param.Locale] = locale;
|
|
399
|
+
}
|
|
374
400
|
if (additionalFlags && additionalFlags.constructor.name === 'Object') {
|
|
375
401
|
Object.assign(queryParams, additionalFlags);
|
|
376
402
|
}
|
|
@@ -432,6 +458,7 @@ export class TsEmbed {
|
|
|
432
458
|
data: {
|
|
433
459
|
timestamp: initTimestamp,
|
|
434
460
|
},
|
|
461
|
+
type: EmbedEvent.Init,
|
|
435
462
|
});
|
|
436
463
|
|
|
437
464
|
uploadMixpanelEvent(MIXPANEL_EVENT.VISUAL_SDK_RENDER_START);
|
|
@@ -480,6 +507,7 @@ export class TsEmbed {
|
|
|
480
507
|
data: {
|
|
481
508
|
timestamp: loadTimestamp,
|
|
482
509
|
},
|
|
510
|
+
type: EmbedEvent.Load,
|
|
483
511
|
});
|
|
484
512
|
uploadMixpanelEvent(
|
|
485
513
|
MIXPANEL_EVENT.VISUAL_SDK_IFRAME_LOAD_PERFORMANCE,
|
|
@@ -533,11 +561,21 @@ export class TsEmbed {
|
|
|
533
561
|
eventPort?: MessagePort | void,
|
|
534
562
|
): void {
|
|
535
563
|
const callbacks = this.eventHandlerMap.get(eventType) || [];
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
564
|
+
const allHandlers = this.eventHandlerMap.get(EmbedEvent.ALL) || [];
|
|
565
|
+
callbacks.push(...allHandlers);
|
|
566
|
+
const dataStatus = data?.status || embedEventStatus.END;
|
|
567
|
+
callbacks.forEach((callbackObj) => {
|
|
568
|
+
if (
|
|
569
|
+
(callbackObj.options.start &&
|
|
570
|
+
dataStatus === embedEventStatus.START) || // When start status is true it trigger only start releated payload
|
|
571
|
+
(!callbackObj.options.start &&
|
|
572
|
+
dataStatus === embedEventStatus.END) // When start status is false it trigger only end releated payload
|
|
573
|
+
) {
|
|
574
|
+
callbackObj.callback(data, (payload) => {
|
|
575
|
+
this.triggerEventOnPort(eventPort, payload);
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
});
|
|
541
579
|
}
|
|
542
580
|
|
|
543
581
|
/**
|
|
@@ -604,20 +642,21 @@ export class TsEmbed {
|
|
|
604
642
|
* sends an event of a particular message type to the host application.
|
|
605
643
|
*
|
|
606
644
|
* @param messageType The message type
|
|
607
|
-
* @param callback A callback function
|
|
645
|
+
* @param callback A callback as a function
|
|
646
|
+
* @param options The message options
|
|
608
647
|
*/
|
|
609
648
|
public on(
|
|
610
649
|
messageType: EmbedEvent,
|
|
611
650
|
callback: MessageCallback,
|
|
651
|
+
options: MessageOptions = { start: false },
|
|
612
652
|
): typeof TsEmbed.prototype {
|
|
613
653
|
if (this.isRendered) {
|
|
614
654
|
this.handleError(
|
|
615
655
|
'Please register event handlers before calling render',
|
|
616
656
|
);
|
|
617
657
|
}
|
|
618
|
-
|
|
619
658
|
const callbacks = this.eventHandlerMap.get(messageType) || [];
|
|
620
|
-
callbacks.push(callback);
|
|
659
|
+
callbacks.push({ options, callback });
|
|
621
660
|
this.eventHandlerMap.set(messageType, callbacks);
|
|
622
661
|
return this;
|
|
623
662
|
}
|
|
@@ -726,9 +765,10 @@ export class V1Embed extends TsEmbed {
|
|
|
726
765
|
public on(
|
|
727
766
|
messageType: EmbedEvent,
|
|
728
767
|
callback: MessageCallback,
|
|
768
|
+
options: MessageOptions = { start: false },
|
|
729
769
|
): typeof TsEmbed.prototype {
|
|
730
770
|
const eventType = this.getCompatibleEventType(messageType);
|
|
731
771
|
|
|
732
|
-
return super.on(eventType, callback);
|
|
772
|
+
return super.on(eventType, callback, options);
|
|
733
773
|
}
|
|
734
774
|
}
|