@thoughtspot/visual-embed-sdk 1.24.0 → 1.24.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/cjs/package.json +2 -3
- package/cjs/src/embed/app.d.ts +23 -1
- package/cjs/src/embed/app.d.ts.map +1 -1
- package/cjs/src/embed/app.js +5 -1
- package/cjs/src/embed/app.js.map +1 -1
- package/cjs/src/embed/base.d.ts +3 -4
- package/cjs/src/embed/base.d.ts.map +1 -1
- package/cjs/src/embed/base.js +3 -4
- package/cjs/src/embed/base.js.map +1 -1
- package/cjs/src/embed/liveboard.d.ts +23 -1
- package/cjs/src/embed/liveboard.d.ts.map +1 -1
- package/cjs/src/embed/liveboard.js +11 -1
- package/cjs/src/embed/liveboard.js.map +1 -1
- package/cjs/src/embed/sage.d.ts +2 -1
- package/cjs/src/embed/sage.d.ts.map +1 -1
- package/cjs/src/embed/sage.js +1 -0
- package/cjs/src/embed/sage.js.map +1 -1
- package/cjs/src/embed/search-bar.d.ts +2 -1
- package/cjs/src/embed/search-bar.d.ts.map +1 -1
- package/cjs/src/embed/search-bar.js +1 -0
- package/cjs/src/embed/search-bar.js.map +1 -1
- package/cjs/src/embed/search.d.ts +2 -1
- package/cjs/src/embed/search.d.ts.map +1 -1
- package/cjs/src/embed/search.js +1 -0
- package/cjs/src/embed/search.js.map +1 -1
- package/cjs/src/embed/ts-embed.d.ts +1 -0
- package/cjs/src/embed/ts-embed.d.ts.map +1 -1
- package/cjs/src/embed/ts-embed.js +4 -10
- package/cjs/src/embed/ts-embed.js.map +1 -1
- package/cjs/src/embed/ts-embed.spec.js +49 -2
- package/cjs/src/embed/ts-embed.spec.js.map +1 -1
- package/cjs/src/index.d.ts +3 -2
- package/cjs/src/index.d.ts.map +1 -1
- package/cjs/src/index.js +3 -1
- package/cjs/src/index.js.map +1 -1
- package/cjs/src/mixpanel-service.d.ts.map +1 -1
- package/cjs/src/mixpanel-service.js +2 -0
- package/cjs/src/mixpanel-service.js.map +1 -1
- package/cjs/src/mixpanel-service.spec.js +1 -0
- package/cjs/src/mixpanel-service.spec.js.map +1 -1
- package/cjs/src/types.d.ts +46 -39
- package/cjs/src/types.d.ts.map +1 -1
- package/cjs/src/types.js +7 -13
- package/cjs/src/types.js.map +1 -1
- package/cjs/src/utils/graphql/answerService/answer-queries.d.ts +5 -0
- package/cjs/src/utils/graphql/answerService/answer-queries.d.ts.map +1 -0
- package/cjs/src/utils/graphql/answerService/answer-queries.js +80 -0
- package/cjs/src/utils/graphql/answerService/answer-queries.js.map +1 -0
- package/cjs/src/utils/graphql/answerService/answerService.d.ts +61 -0
- package/cjs/src/utils/graphql/answerService/answerService.d.ts.map +1 -0
- package/cjs/src/utils/graphql/answerService/answerService.js +182 -0
- package/cjs/src/utils/graphql/answerService/answerService.js.map +1 -0
- package/cjs/src/utils/graphql/answerService/answerService.spec.d.ts +2 -0
- package/cjs/src/utils/graphql/answerService/answerService.spec.d.ts.map +1 -0
- package/cjs/src/utils/graphql/answerService/answerService.spec.js +201 -0
- package/cjs/src/utils/graphql/answerService/answerService.spec.js.map +1 -0
- package/cjs/src/utils/graphql/graphql-request.d.ts +15 -0
- package/cjs/src/utils/graphql/graphql-request.d.ts.map +1 -0
- package/cjs/src/utils/graphql/graphql-request.js +40 -0
- package/cjs/src/utils/graphql/graphql-request.js.map +1 -0
- package/cjs/src/utils/graphql/sourceService.d.ts +8 -0
- package/cjs/src/utils/graphql/sourceService.d.ts.map +1 -0
- package/cjs/src/utils/graphql/sourceService.js +69 -0
- package/cjs/src/utils/graphql/sourceService.js.map +1 -0
- package/cjs/src/utils/graphql/sourceService.spec.d.ts +2 -0
- package/cjs/src/utils/graphql/sourceService.spec.d.ts.map +1 -0
- package/cjs/src/utils/graphql/sourceService.spec.js +12 -0
- package/cjs/src/utils/graphql/sourceService.spec.js.map +1 -0
- package/cjs/src/utils/processData.d.ts.map +1 -1
- package/cjs/src/utils/processData.js +7 -11
- package/cjs/src/utils/processData.js.map +1 -1
- package/cjs/src/utils/processData.spec.js +13 -17
- package/cjs/src/utils/processData.spec.js.map +1 -1
- package/cjs/src/utils.d.ts +6 -0
- package/cjs/src/utils.d.ts.map +1 -1
- package/cjs/src/utils.js +27 -2
- package/cjs/src/utils.js.map +1 -1
- package/cjs/src/utils.spec.js +10 -0
- package/cjs/src/utils.spec.js.map +1 -1
- package/dist/src/embed/app.d.ts +23 -1
- package/dist/src/embed/app.d.ts.map +1 -1
- package/dist/src/embed/base.d.ts +3 -4
- package/dist/src/embed/base.d.ts.map +1 -1
- package/dist/src/embed/liveboard.d.ts +23 -1
- package/dist/src/embed/liveboard.d.ts.map +1 -1
- package/dist/src/embed/sage.d.ts +2 -1
- package/dist/src/embed/sage.d.ts.map +1 -1
- package/dist/src/embed/search-bar.d.ts +2 -1
- package/dist/src/embed/search-bar.d.ts.map +1 -1
- package/dist/src/embed/search.d.ts +2 -1
- package/dist/src/embed/search.d.ts.map +1 -1
- package/dist/src/embed/ts-embed.d.ts +1 -0
- package/dist/src/embed/ts-embed.d.ts.map +1 -1
- package/dist/src/index.d.ts +3 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/mixpanel-service.d.ts.map +1 -1
- package/dist/src/types.d.ts +46 -39
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils/graphql/answerService/answer-queries.d.ts +5 -0
- package/dist/src/utils/graphql/answerService/answer-queries.d.ts.map +1 -0
- package/dist/src/utils/graphql/answerService/answerService.d.ts +61 -0
- package/dist/src/utils/graphql/answerService/answerService.d.ts.map +1 -0
- package/dist/src/utils/graphql/answerService/answerService.spec.d.ts +2 -0
- package/dist/src/utils/graphql/answerService/answerService.spec.d.ts.map +1 -0
- package/dist/src/utils/graphql/graphql-request.d.ts +15 -0
- package/dist/src/utils/graphql/graphql-request.d.ts.map +1 -0
- package/dist/src/utils/graphql/sourceService.d.ts +8 -0
- package/dist/src/utils/graphql/sourceService.d.ts.map +1 -0
- package/dist/src/utils/graphql/sourceService.spec.d.ts +2 -0
- package/dist/src/utils/graphql/sourceService.spec.d.ts.map +1 -0
- package/dist/src/utils/processData.d.ts.map +1 -1
- package/dist/src/utils.d.ts +6 -0
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/tsembed-react.es.js +400 -81
- package/dist/tsembed-react.js +400 -81
- package/dist/tsembed.es.js +452 -89
- package/dist/tsembed.js +452 -88
- package/dist/visual-embed-sdk-react-full.d.ts +158 -49
- package/dist/visual-embed-sdk-react.d.ts +158 -49
- package/dist/visual-embed-sdk.d.ts +158 -49
- package/lib/package.json +2 -3
- package/lib/src/embed/app.d.ts +23 -1
- package/lib/src/embed/app.d.ts.map +1 -1
- package/lib/src/embed/app.js +5 -1
- package/lib/src/embed/app.js.map +1 -1
- package/lib/src/embed/base.d.ts +3 -4
- package/lib/src/embed/base.d.ts.map +1 -1
- package/lib/src/embed/base.js +3 -4
- package/lib/src/embed/base.js.map +1 -1
- package/lib/src/embed/liveboard.d.ts +23 -1
- package/lib/src/embed/liveboard.d.ts.map +1 -1
- package/lib/src/embed/liveboard.js +11 -1
- package/lib/src/embed/liveboard.js.map +1 -1
- package/lib/src/embed/sage.d.ts +2 -1
- package/lib/src/embed/sage.d.ts.map +1 -1
- package/lib/src/embed/sage.js +1 -0
- package/lib/src/embed/sage.js.map +1 -1
- package/lib/src/embed/search-bar.d.ts +2 -1
- package/lib/src/embed/search-bar.d.ts.map +1 -1
- package/lib/src/embed/search-bar.js +1 -0
- package/lib/src/embed/search-bar.js.map +1 -1
- package/lib/src/embed/search.d.ts +2 -1
- package/lib/src/embed/search.d.ts.map +1 -1
- package/lib/src/embed/search.js +1 -0
- package/lib/src/embed/search.js.map +1 -1
- package/lib/src/embed/ts-embed.d.ts +1 -0
- package/lib/src/embed/ts-embed.d.ts.map +1 -1
- package/lib/src/embed/ts-embed.js +4 -10
- package/lib/src/embed/ts-embed.js.map +1 -1
- package/lib/src/embed/ts-embed.spec.js +49 -2
- package/lib/src/embed/ts-embed.spec.js.map +1 -1
- package/lib/src/index.d.ts +3 -2
- package/lib/src/index.d.ts.map +1 -1
- package/lib/src/index.js +2 -1
- package/lib/src/index.js.map +1 -1
- package/lib/src/mixpanel-service.d.ts.map +1 -1
- package/lib/src/mixpanel-service.js +2 -0
- package/lib/src/mixpanel-service.js.map +1 -1
- package/lib/src/mixpanel-service.spec.js +1 -0
- package/lib/src/mixpanel-service.spec.js.map +1 -1
- package/lib/src/types.d.ts +46 -39
- package/lib/src/types.d.ts.map +1 -1
- package/lib/src/types.js +6 -12
- package/lib/src/types.js.map +1 -1
- package/lib/src/utils/graphql/answerService/answer-queries.d.ts +5 -0
- package/lib/src/utils/graphql/answerService/answer-queries.d.ts.map +1 -0
- package/lib/src/utils/graphql/answerService/answer-queries.js +77 -0
- package/lib/src/utils/graphql/answerService/answer-queries.js.map +1 -0
- package/lib/src/utils/graphql/answerService/answerService.d.ts +61 -0
- package/lib/src/utils/graphql/answerService/answerService.d.ts.map +1 -0
- package/lib/src/utils/graphql/answerService/answerService.js +177 -0
- package/lib/src/utils/graphql/answerService/answerService.js.map +1 -0
- package/lib/src/utils/graphql/answerService/answerService.spec.d.ts +2 -0
- package/lib/src/utils/graphql/answerService/answerService.spec.d.ts.map +1 -0
- package/lib/src/utils/graphql/answerService/answerService.spec.js +199 -0
- package/lib/src/utils/graphql/answerService/answerService.spec.js.map +1 -0
- package/lib/src/utils/graphql/graphql-request.d.ts +15 -0
- package/lib/src/utils/graphql/graphql-request.d.ts.map +1 -0
- package/lib/src/utils/graphql/graphql-request.js +36 -0
- package/lib/src/utils/graphql/graphql-request.js.map +1 -0
- package/lib/src/utils/graphql/sourceService.d.ts +8 -0
- package/lib/src/utils/graphql/sourceService.d.ts.map +1 -0
- package/lib/src/utils/graphql/sourceService.js +65 -0
- package/lib/src/utils/graphql/sourceService.js.map +1 -0
- package/lib/src/utils/graphql/sourceService.spec.d.ts +2 -0
- package/lib/src/utils/graphql/sourceService.spec.d.ts.map +1 -0
- package/lib/src/utils/graphql/sourceService.spec.js +10 -0
- package/lib/src/utils/graphql/sourceService.spec.js.map +1 -0
- package/lib/src/utils/processData.d.ts.map +1 -1
- package/lib/src/utils/processData.js +8 -12
- package/lib/src/utils/processData.js.map +1 -1
- package/lib/src/utils/processData.spec.js +14 -18
- package/lib/src/utils/processData.spec.js.map +1 -1
- package/lib/src/utils.d.ts +6 -0
- package/lib/src/utils.d.ts.map +1 -1
- package/lib/src/utils.js +24 -1
- package/lib/src/utils.js.map +1 -1
- package/lib/src/utils.spec.js +10 -0
- package/lib/src/utils.spec.js.map +1 -1
- package/lib/src/visual-embed-sdk.d.ts +164 -50
- package/package.json +4 -5
- package/src/embed/app.ts +30 -1
- package/src/embed/base.ts +3 -4
- package/src/embed/liveboard.ts +36 -1
- package/src/embed/sage.ts +9 -4
- package/src/embed/search-bar.tsx +12 -1
- package/src/embed/search.ts +7 -1
- package/src/embed/ts-embed.spec.ts +51 -2
- package/src/embed/ts-embed.ts +4 -12
- package/src/index.ts +5 -0
- package/src/mixpanel-service.spec.ts +1 -0
- package/src/mixpanel-service.ts +1 -0
- package/src/types.ts +50 -40
- package/src/utils/graphql/answerService/answer-queries.ts +80 -0
- package/src/utils/graphql/answerService/answerService.spec.ts +231 -0
- package/src/utils/graphql/answerService/answerService.ts +234 -0
- package/src/utils/graphql/graphql-request.ts +45 -0
- package/src/utils/graphql/sourceService.spec.ts +10 -0
- package/src/utils/graphql/sourceService.ts +71 -0
- package/src/utils/processData.spec.ts +15 -25
- package/src/utils/processData.ts +13 -15
- package/src/utils.spec.ts +13 -0
- package/src/utils.ts +25 -1
- package/cjs/src/utils/answerService.d.ts +0 -10
- package/cjs/src/utils/answerService.d.ts.map +0 -1
- package/cjs/src/utils/answerService.js +0 -61
- package/cjs/src/utils/answerService.js.map +0 -1
- package/cjs/src/utils/answerService.spec.d.ts +0 -2
- package/cjs/src/utils/answerService.spec.d.ts.map +0 -1
- package/cjs/src/utils/answerService.spec.js +0 -31
- package/cjs/src/utils/answerService.spec.js.map +0 -1
- package/dist/src/utils/answerService.d.ts +0 -10
- package/dist/src/utils/answerService.d.ts.map +0 -1
- package/dist/src/utils/answerService.spec.d.ts +0 -2
- package/dist/src/utils/answerService.spec.d.ts.map +0 -1
- package/lib/src/utils/answerService.d.ts +0 -10
- package/lib/src/utils/answerService.d.ts.map +0 -1
- package/lib/src/utils/answerService.js +0 -57
- package/lib/src/utils/answerService.js.map +0 -1
- package/lib/src/utils/answerService.spec.d.ts +0 -2
- package/lib/src/utils/answerService.spec.d.ts.map +0 -1
- package/lib/src/utils/answerService.spec.js +0 -29
- package/lib/src/utils/answerService.spec.js.map +0 -1
- package/src/utils/answerService.spec.ts +0 -41
- package/src/utils/answerService.ts +0 -63
|
@@ -113,6 +113,8 @@ describe('Unit test case for ts embed', () => {
|
|
|
113
113
|
runtimeFilterParams: null,
|
|
114
114
|
hiddenHomeLeftNavItems: [],
|
|
115
115
|
hiddenHomepageModules: [],
|
|
116
|
+
hostConfig: undefined,
|
|
117
|
+
reorderedHomepageModules: [],
|
|
116
118
|
},
|
|
117
119
|
});
|
|
118
120
|
});
|
|
@@ -142,6 +144,8 @@ describe('Unit test case for ts embed', () => {
|
|
|
142
144
|
runtimeFilterParams: null,
|
|
143
145
|
hiddenHomeLeftNavItems: [],
|
|
144
146
|
hiddenHomepageModules: [],
|
|
147
|
+
hostConfig: undefined,
|
|
148
|
+
reorderedHomepageModules: [],
|
|
145
149
|
},
|
|
146
150
|
});
|
|
147
151
|
expect(getIFrameSrc()).toContain(
|
|
@@ -159,7 +163,7 @@ describe('Unit test case for ts embed', () => {
|
|
|
159
163
|
HomepageModule.Learning,
|
|
160
164
|
];
|
|
161
165
|
|
|
162
|
-
const searchEmbed = new
|
|
166
|
+
const searchEmbed = new AppEmbed(getRootEl(), {
|
|
163
167
|
...defaultViewConfig,
|
|
164
168
|
hiddenHomepageModules: mockedHiddenHomepageModules,
|
|
165
169
|
});
|
|
@@ -181,6 +185,44 @@ describe('Unit test case for ts embed', () => {
|
|
|
181
185
|
hiddenHomeLeftNavItems: [],
|
|
182
186
|
hiddenHomepageModules: [HomepageModule.MyLibrary,
|
|
183
187
|
HomepageModule.Learning],
|
|
188
|
+
reorderedHomepageModules: [],
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test('Reordering the home page modules from view Config should be part of app_init payload', async () => {
|
|
194
|
+
const mockEmbedEventPayload = {
|
|
195
|
+
type: EmbedEvent.APP_INIT,
|
|
196
|
+
data: {},
|
|
197
|
+
};
|
|
198
|
+
const mockedReorderedHomepageModules: HomepageModule[] = [
|
|
199
|
+
HomepageModule.MyLibrary,
|
|
200
|
+
HomepageModule.Watchlist,
|
|
201
|
+
];
|
|
202
|
+
|
|
203
|
+
const searchEmbed = new SearchEmbed(getRootEl(), {
|
|
204
|
+
...defaultViewConfig,
|
|
205
|
+
reorderedHomepageModules: mockedReorderedHomepageModules,
|
|
206
|
+
});
|
|
207
|
+
searchEmbed.render();
|
|
208
|
+
const mockPort: any = {
|
|
209
|
+
postMessage: jest.fn(),
|
|
210
|
+
};
|
|
211
|
+
await executeAfterWait(() => {
|
|
212
|
+
const iframe = getIFrameEl();
|
|
213
|
+
postMessageToParent(iframe.contentWindow, mockEmbedEventPayload, mockPort);
|
|
214
|
+
});
|
|
215
|
+
expect(mockPort.postMessage).toHaveBeenCalledWith({
|
|
216
|
+
type: EmbedEvent.APP_INIT,
|
|
217
|
+
data: {
|
|
218
|
+
customisations,
|
|
219
|
+
authToken: '',
|
|
220
|
+
hostConfig: undefined,
|
|
221
|
+
runtimeFilterParams: null,
|
|
222
|
+
hiddenHomeLeftNavItems: [],
|
|
223
|
+
hiddenHomepageModules: [],
|
|
224
|
+
reorderedHomepageModules: [HomepageModule.MyLibrary,
|
|
225
|
+
HomepageModule.Watchlist],
|
|
184
226
|
},
|
|
185
227
|
});
|
|
186
228
|
});
|
|
@@ -219,6 +261,8 @@ describe('Unit test case for ts embed', () => {
|
|
|
219
261
|
runtimeFilterParams: 'col1=color&op1=EQ&val1=blue',
|
|
220
262
|
hiddenHomeLeftNavItems: [],
|
|
221
263
|
hiddenHomepageModules: [],
|
|
264
|
+
hostConfig: undefined,
|
|
265
|
+
reorderedHomepageModules: [],
|
|
222
266
|
},
|
|
223
267
|
});
|
|
224
268
|
});
|
|
@@ -257,6 +301,8 @@ describe('Unit test case for ts embed', () => {
|
|
|
257
301
|
runtimeFilterParams: null,
|
|
258
302
|
hiddenHomeLeftNavItems: [],
|
|
259
303
|
hiddenHomepageModules: [],
|
|
304
|
+
hostConfig: undefined,
|
|
305
|
+
reorderedHomepageModules: [],
|
|
260
306
|
},
|
|
261
307
|
});
|
|
262
308
|
});
|
|
@@ -271,7 +317,7 @@ describe('Unit test case for ts embed', () => {
|
|
|
271
317
|
HomeLeftNavItem.Documentation,
|
|
272
318
|
];
|
|
273
319
|
|
|
274
|
-
const searchEmbed = new
|
|
320
|
+
const searchEmbed = new AppEmbed(getRootEl(), {
|
|
275
321
|
...defaultViewConfig,
|
|
276
322
|
hiddenHomeLeftNavItems: mockedHiddenHomeLeftNavItems,
|
|
277
323
|
});
|
|
@@ -293,6 +339,7 @@ describe('Unit test case for ts embed', () => {
|
|
|
293
339
|
hiddenHomeLeftNavItems: [HomeLeftNavItem.Home,
|
|
294
340
|
HomeLeftNavItem.Documentation],
|
|
295
341
|
hiddenHomepageModules: [],
|
|
342
|
+
reorderedHomepageModules: [],
|
|
296
343
|
},
|
|
297
344
|
});
|
|
298
345
|
});
|
|
@@ -448,6 +495,8 @@ describe('Unit test case for ts embed', () => {
|
|
|
448
495
|
runtimeFilterParams: null,
|
|
449
496
|
hiddenHomeLeftNavItems: [],
|
|
450
497
|
hiddenHomepageModules: [],
|
|
498
|
+
hostConfig: undefined,
|
|
499
|
+
reorderedHomepageModules: [],
|
|
451
500
|
},
|
|
452
501
|
});
|
|
453
502
|
});
|
package/src/embed/ts-embed.ts
CHANGED
|
@@ -115,6 +115,8 @@ export class TsEmbed {
|
|
|
115
115
|
*/
|
|
116
116
|
protected thoughtSpotV2Base: string;
|
|
117
117
|
|
|
118
|
+
protected embedComponentType = 'TsEmbed';
|
|
119
|
+
|
|
118
120
|
/**
|
|
119
121
|
* A map of event handlers for particular message types triggered
|
|
120
122
|
* by the embedded app; multiple event handlers can be registered
|
|
@@ -159,6 +161,7 @@ export class TsEmbed {
|
|
|
159
161
|
this.registerAppInit();
|
|
160
162
|
uploadMixpanelEvent(MIXPANEL_EVENT.VISUAL_SDK_EMBED_CREATE, {
|
|
161
163
|
...viewConfig,
|
|
164
|
+
embedComponentType: this.embedComponentType,
|
|
162
165
|
});
|
|
163
166
|
}
|
|
164
167
|
|
|
@@ -277,6 +280,7 @@ export class TsEmbed {
|
|
|
277
280
|
? getRuntimeFilters(this.viewConfig.runtimeFilters)
|
|
278
281
|
: null,
|
|
279
282
|
hiddenHomepageModules: this.viewConfig.hiddenHomepageModules || [],
|
|
283
|
+
reorderedHomepageModules: this.viewConfig.reorderedHomepageModules || [],
|
|
280
284
|
hostConfig: this.embedConfig.hostConfig,
|
|
281
285
|
hiddenHomeLeftNavItems: this.viewConfig?.hiddenHomeLeftNavItems
|
|
282
286
|
? this.viewConfig?.hiddenHomeLeftNavItems
|
|
@@ -380,9 +384,6 @@ export class TsEmbed {
|
|
|
380
384
|
contextMenuTrigger,
|
|
381
385
|
linkOverride,
|
|
382
386
|
insertInToSlide,
|
|
383
|
-
hideLiveboardHeader,
|
|
384
|
-
showLiveboardDescription,
|
|
385
|
-
showLiveboardTitle,
|
|
386
387
|
} = this.viewConfig;
|
|
387
388
|
|
|
388
389
|
if (Array.isArray(visibleActions) && Array.isArray(hiddenActions)) {
|
|
@@ -450,15 +451,6 @@ export class TsEmbed {
|
|
|
450
451
|
if (insertInToSlide) {
|
|
451
452
|
queryParams[Param.ShowInsertToSlide] = insertInToSlide;
|
|
452
453
|
}
|
|
453
|
-
if (hideLiveboardHeader) {
|
|
454
|
-
queryParams[Param.HideLiveboardHeader] = hideLiveboardHeader;
|
|
455
|
-
}
|
|
456
|
-
if (showLiveboardDescription) {
|
|
457
|
-
queryParams[Param.ShowLiveboardDescription] = showLiveboardDescription;
|
|
458
|
-
}
|
|
459
|
-
if (showLiveboardTitle) {
|
|
460
|
-
queryParams[Param.ShowLiveboardTitle] = showLiveboardTitle;
|
|
461
|
-
}
|
|
462
454
|
|
|
463
455
|
return queryParams;
|
|
464
456
|
}
|
package/src/index.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { AppEmbed, Page, AppViewConfig } from './embed/app';
|
|
11
11
|
import {
|
|
12
12
|
init, prefetch, logout, getEmbedConfig, executeTML, exportTML,
|
|
13
|
+
executeTMLInput, exportTMLInput,
|
|
13
14
|
} from './embed/base';
|
|
14
15
|
import { PinboardEmbed, LiveboardViewConfig, LiveboardEmbed } from './embed/liveboard';
|
|
15
16
|
import { SearchEmbed, SearchViewConfig } from './embed/search';
|
|
@@ -41,6 +42,7 @@ import {
|
|
|
41
42
|
} from './types';
|
|
42
43
|
import { CustomCssVariables } from './css-variables';
|
|
43
44
|
import { SageEmbed, SageViewConfig } from './embed/sage';
|
|
45
|
+
import { AnswerService } from './utils/graphql/answerService/answerService';
|
|
44
46
|
|
|
45
47
|
export {
|
|
46
48
|
init,
|
|
@@ -48,6 +50,8 @@ export {
|
|
|
48
50
|
prefetch,
|
|
49
51
|
executeTML,
|
|
50
52
|
exportTML,
|
|
53
|
+
executeTMLInput,
|
|
54
|
+
exportTMLInput,
|
|
51
55
|
getEmbedConfig as getInitConfig,
|
|
52
56
|
getSessionInfo,
|
|
53
57
|
SearchEmbed,
|
|
@@ -60,6 +64,7 @@ export {
|
|
|
60
64
|
AuthStatus,
|
|
61
65
|
AuthEvent,
|
|
62
66
|
AuthEventEmitter,
|
|
67
|
+
AnswerService,
|
|
63
68
|
// types
|
|
64
69
|
Page,
|
|
65
70
|
AuthType,
|
|
@@ -57,6 +57,7 @@ describe('Unit test for mixpanel', () => {
|
|
|
57
57
|
clusterId: sessionInfo.clusterId,
|
|
58
58
|
clusterName: sessionInfo.clusterName,
|
|
59
59
|
releaseVersion: sessionInfo.releaseVersion,
|
|
60
|
+
hostAppUrl: 'localhost',
|
|
60
61
|
});
|
|
61
62
|
expect(mixpanel.identify).not.toHaveBeenCalledWith(sessionInfo.userGUID);
|
|
62
63
|
});
|
package/src/mixpanel-service.ts
CHANGED
|
@@ -71,6 +71,7 @@ export function initMixpanel(sessionInfo: any): void {
|
|
|
71
71
|
clusterId: sessionInfo.clusterId,
|
|
72
72
|
clusterName: sessionInfo.clusterName,
|
|
73
73
|
releaseVersion: sessionInfo.releaseVersion,
|
|
74
|
+
hostAppUrl: window?.location?.host || '',
|
|
74
75
|
});
|
|
75
76
|
isMixpanelInitialized = true;
|
|
76
77
|
emptyQueue();
|
package/src/types.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { CustomCssVariables } from './css-variables';
|
|
11
|
+
import type { SessionInterface } from './utils/graphql/answerService/answerService';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* The authentication mechanism for allowing access to the
|
|
@@ -533,7 +534,7 @@ export interface EmbedConfig {
|
|
|
533
534
|
}
|
|
534
535
|
|
|
535
536
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
536
|
-
export interface LayoutConfig {}
|
|
537
|
+
export interface LayoutConfig { }
|
|
537
538
|
|
|
538
539
|
/**
|
|
539
540
|
* Embedded iFrame configuration
|
|
@@ -712,27 +713,6 @@ export interface ViewConfig {
|
|
|
712
713
|
* @hidden
|
|
713
714
|
*/
|
|
714
715
|
excludeRuntimeFiltersfromURL?: boolean;
|
|
715
|
-
/**
|
|
716
|
-
* Boolean to hide liveboard header
|
|
717
|
-
*
|
|
718
|
-
* @version SDK: 1.26.0 | Thoughtspot: 9.7.0.cl
|
|
719
|
-
* @default false
|
|
720
|
-
*/
|
|
721
|
-
hideLiveboardHeader?: boolean;
|
|
722
|
-
/**
|
|
723
|
-
* Boolean to show liveboard title
|
|
724
|
-
*
|
|
725
|
-
* @version SDK: 1.26.0 | Thoughtspot: 9.7.0.cl
|
|
726
|
-
* @default false
|
|
727
|
-
*/
|
|
728
|
-
showLiveboardTitle?: boolean;
|
|
729
|
-
/**
|
|
730
|
-
* Boolean to show liveboard description
|
|
731
|
-
*
|
|
732
|
-
* @version SDK: 1.26.0 | Thoughtspot: 9.7.0.cl
|
|
733
|
-
* @default false
|
|
734
|
-
*/
|
|
735
|
-
showLiveboardDescription?: boolean;
|
|
736
716
|
/**
|
|
737
717
|
* The list of tab IDs to hide from the embedded.
|
|
738
718
|
* This Tabs will be hidden from their respective LBs.
|
|
@@ -757,6 +737,12 @@ export interface ViewConfig {
|
|
|
757
737
|
* @version SDK: 1.27.0 | Thoughtspot: 9.8.0.cl
|
|
758
738
|
*/
|
|
759
739
|
hiddenHomepageModules?: HomepageModule[];
|
|
740
|
+
/**
|
|
741
|
+
* reordering the home page modules
|
|
742
|
+
* eg: reorderedHomepageModules = [HomepageModule.MyLibrary, HomepageModule.Watchlist]
|
|
743
|
+
* @version SDK: 1.28.0 | Thoughtspot: 9.9.0.cl
|
|
744
|
+
*/
|
|
745
|
+
reorderedHomepageModules?: HomepageModule[];
|
|
760
746
|
/**
|
|
761
747
|
* The list of tab IDs to show in the embedded.
|
|
762
748
|
* Only this Tabs will be shown in their respective LBs.
|
|
@@ -917,27 +903,27 @@ export enum HomepageModule {
|
|
|
917
903
|
/**
|
|
918
904
|
* Search bar
|
|
919
905
|
*/
|
|
920
|
-
Search = '
|
|
906
|
+
Search = 'SEARCH',
|
|
921
907
|
/**
|
|
922
908
|
* kPI watchlist module
|
|
923
909
|
*/
|
|
924
|
-
Watchlist = '
|
|
910
|
+
Watchlist = 'WATCHLIST',
|
|
925
911
|
/**
|
|
926
912
|
* favorite objects
|
|
927
913
|
*/
|
|
928
|
-
Favorite = '
|
|
914
|
+
Favorite = 'FAVORITE',
|
|
929
915
|
/**
|
|
930
916
|
* List of answers and liveboards
|
|
931
917
|
*/
|
|
932
|
-
MyLibrary = '
|
|
918
|
+
MyLibrary = 'MY_LIBRARY',
|
|
933
919
|
/**
|
|
934
920
|
* Trending list
|
|
935
921
|
*/
|
|
936
|
-
Trending = '
|
|
922
|
+
Trending = 'TRENDING',
|
|
937
923
|
/**
|
|
938
924
|
* Learning videos
|
|
939
925
|
*/
|
|
940
|
-
Learning = '
|
|
926
|
+
Learning = 'LEARNING',
|
|
941
927
|
}
|
|
942
928
|
|
|
943
929
|
/**
|
|
@@ -3122,18 +3108,6 @@ export enum Action {
|
|
|
3122
3108
|
PersonalisedViewsDropdown = 'personalisedViewsDropdown',
|
|
3123
3109
|
}
|
|
3124
3110
|
|
|
3125
|
-
export interface SessionInterface {
|
|
3126
|
-
sessionId: string;
|
|
3127
|
-
genNo: number;
|
|
3128
|
-
acSession: { sessionId: string; genNo: number };
|
|
3129
|
-
}
|
|
3130
|
-
|
|
3131
|
-
// eslint-disable-next-line no-shadow
|
|
3132
|
-
export enum OperationType {
|
|
3133
|
-
GetChartWithData = 'GetChartWithData',
|
|
3134
|
-
GetTableWithHeadlineData = 'GetTableWithHeadlineData',
|
|
3135
|
-
}
|
|
3136
|
-
|
|
3137
3111
|
export interface AnswerServiceType {
|
|
3138
3112
|
getAnswer?: (offset: number, batchSize: number) => any;
|
|
3139
3113
|
}
|
|
@@ -3152,3 +3126,39 @@ export enum ContextMenuTriggerOptions {
|
|
|
3152
3126
|
LEFT_CLICK = 'left-click',
|
|
3153
3127
|
RIGHT_CLICK = 'right-click',
|
|
3154
3128
|
}
|
|
3129
|
+
|
|
3130
|
+
export interface ColumnValue {
|
|
3131
|
+
column: {
|
|
3132
|
+
id: string,
|
|
3133
|
+
name: string,
|
|
3134
|
+
dataType: string,
|
|
3135
|
+
[key: string]: any
|
|
3136
|
+
},
|
|
3137
|
+
value: string | number | boolean;
|
|
3138
|
+
}
|
|
3139
|
+
|
|
3140
|
+
export interface VizPoint {
|
|
3141
|
+
selectedAttributes: ColumnValue[],
|
|
3142
|
+
selectedMeasures: ColumnValue[]
|
|
3143
|
+
}
|
|
3144
|
+
|
|
3145
|
+
export interface CustomActionPayload {
|
|
3146
|
+
contextMenuPoints?: {
|
|
3147
|
+
clickedPoint: VizPoint
|
|
3148
|
+
selectedPoints: VizPoint[]
|
|
3149
|
+
};
|
|
3150
|
+
embedAnswerData: {
|
|
3151
|
+
name: string,
|
|
3152
|
+
id: string,
|
|
3153
|
+
sources: {
|
|
3154
|
+
header: {
|
|
3155
|
+
guid: string
|
|
3156
|
+
}
|
|
3157
|
+
},
|
|
3158
|
+
columns: any[],
|
|
3159
|
+
data: any[],
|
|
3160
|
+
[key: string]: any
|
|
3161
|
+
};
|
|
3162
|
+
session: SessionInterface;
|
|
3163
|
+
vizId?: string;
|
|
3164
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const bachSessionId = `
|
|
2
|
+
id {
|
|
3
|
+
sessionId
|
|
4
|
+
genNo
|
|
5
|
+
acSession {
|
|
6
|
+
sessionId
|
|
7
|
+
genNo
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
`;
|
|
11
|
+
|
|
12
|
+
export const getUnaggregatedAnswerSession = `
|
|
13
|
+
mutation GetUnAggregatedAnswerSession($session: BachSessionIdInput!, $columns: [UserPointSelectionInput!]!) {
|
|
14
|
+
Answer__getUnaggregatedAnswer(session: $session, columns: $columns) {
|
|
15
|
+
${bachSessionId}
|
|
16
|
+
answer {
|
|
17
|
+
visualizations {
|
|
18
|
+
... on TableViz {
|
|
19
|
+
columns {
|
|
20
|
+
column {
|
|
21
|
+
id
|
|
22
|
+
name
|
|
23
|
+
referencedColumns {
|
|
24
|
+
guid
|
|
25
|
+
displayName
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
export const removeColumns = `
|
|
37
|
+
mutation RemoveColumns($session: BachSessionIdInput!, $logicalColumnIds: [GUID!], $columnIds: [GUID!]) {
|
|
38
|
+
Answer__removeColumns(
|
|
39
|
+
session: $session
|
|
40
|
+
logicalColumnIds: $logicalColumnIds
|
|
41
|
+
columnIds: $columnIds
|
|
42
|
+
) {
|
|
43
|
+
${bachSessionId}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
export const addColumns = `
|
|
49
|
+
mutation AddColumns($session: BachSessionIdInput!, $columns: [AnswerColumnInfo!]!) {
|
|
50
|
+
Answer__addColumn(session: $session, columns: $columns) {
|
|
51
|
+
${bachSessionId}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
`;
|
|
55
|
+
|
|
56
|
+
export const getAnswerData = `
|
|
57
|
+
query GetTableWithHeadlineData($session: BachSessionIdInput!, $deadline: Int!, $dataPaginationParams: DataPaginationParamsInput!) {
|
|
58
|
+
getAnswer(session: $session) {
|
|
59
|
+
${bachSessionId}
|
|
60
|
+
answer {
|
|
61
|
+
id
|
|
62
|
+
visualizations {
|
|
63
|
+
id
|
|
64
|
+
... on TableViz {
|
|
65
|
+
columns {
|
|
66
|
+
column {
|
|
67
|
+
id
|
|
68
|
+
name
|
|
69
|
+
type
|
|
70
|
+
aggregationType
|
|
71
|
+
dataType
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
data(deadline: $deadline, pagination: $dataPaginationParams)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
`;
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import 'jest-fetch-mock';
|
|
2
|
+
import { VizPoint } from 'src/types';
|
|
3
|
+
import { AnswerService } from './answerService';
|
|
4
|
+
import { getAnswerData, removeColumns } from './answer-queries';
|
|
5
|
+
|
|
6
|
+
const defaultSession = {
|
|
7
|
+
sessionId: 'id',
|
|
8
|
+
genNo: 1,
|
|
9
|
+
acSession: {
|
|
10
|
+
sessionId: 'ac',
|
|
11
|
+
genNo: 0,
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
*
|
|
16
|
+
* @param answer
|
|
17
|
+
* @param point
|
|
18
|
+
*/
|
|
19
|
+
function createAnswerService(answer = {}, point?: VizPoint[]) {
|
|
20
|
+
return new AnswerService(
|
|
21
|
+
defaultSession,
|
|
22
|
+
answer,
|
|
23
|
+
'https://tshost',
|
|
24
|
+
point,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe('Answer service tests', () => {
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
fetchMock.resetMocks();
|
|
31
|
+
});
|
|
32
|
+
test('Execute query should execute the supplied graphql on the session', async () => {
|
|
33
|
+
fetchMock.mockResponseOnce(JSON.stringify({
|
|
34
|
+
data: {
|
|
35
|
+
Bla: {
|
|
36
|
+
id: {},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
}));
|
|
40
|
+
const answerService = createAnswerService();
|
|
41
|
+
answerService.executeQuery(
|
|
42
|
+
'query Bla {}',
|
|
43
|
+
{ a: 1 },
|
|
44
|
+
);
|
|
45
|
+
expect(fetchMock).toBeCalledWith('https://tshost/prism/?op=Bla', expect.objectContaining({
|
|
46
|
+
body: JSON.stringify({
|
|
47
|
+
operationName: 'Bla',
|
|
48
|
+
query: 'query Bla {}',
|
|
49
|
+
variables: {
|
|
50
|
+
session: defaultSession,
|
|
51
|
+
a: 1,
|
|
52
|
+
},
|
|
53
|
+
}),
|
|
54
|
+
}));
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('Should return error when failure', async () => {
|
|
58
|
+
fetchMock.mockRejectOnce(new Error('testError'));
|
|
59
|
+
const answerService = createAnswerService();
|
|
60
|
+
const data = await answerService.executeQuery(
|
|
61
|
+
'query Bla {}',
|
|
62
|
+
{ a: 1 },
|
|
63
|
+
);
|
|
64
|
+
expect(data.message).toBe('testError');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('fetchData should call the right graphql, and return data', async () => {
|
|
68
|
+
fetchMock.mockResponseOnce(JSON.stringify({
|
|
69
|
+
data: {
|
|
70
|
+
getAnswer: {
|
|
71
|
+
id: {},
|
|
72
|
+
answer: {
|
|
73
|
+
visualizations: [{
|
|
74
|
+
columns: [{
|
|
75
|
+
column: {
|
|
76
|
+
id: 'id1',
|
|
77
|
+
},
|
|
78
|
+
}],
|
|
79
|
+
data: {
|
|
80
|
+
foo: 1,
|
|
81
|
+
},
|
|
82
|
+
}],
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
}));
|
|
87
|
+
const answerService = createAnswerService();
|
|
88
|
+
const data = await answerService.fetchData(20, 10);
|
|
89
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
90
|
+
'https://tshost/prism/?op=GetTableWithHeadlineData',
|
|
91
|
+
expect.objectContaining({
|
|
92
|
+
body: JSON.stringify({
|
|
93
|
+
operationName: 'GetTableWithHeadlineData',
|
|
94
|
+
query: getAnswerData,
|
|
95
|
+
variables: {
|
|
96
|
+
session: defaultSession,
|
|
97
|
+
deadline: 0,
|
|
98
|
+
dataPaginationParams: {
|
|
99
|
+
isClientPaginated: true,
|
|
100
|
+
offset: 20,
|
|
101
|
+
size: 10,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
}),
|
|
105
|
+
}),
|
|
106
|
+
);
|
|
107
|
+
expect(data.columns[0].column.id).toBe('id1');
|
|
108
|
+
expect(data.data.foo).toBe(1);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('fetchCSVBlob should call the right API', async () => {
|
|
112
|
+
fetchMock.once('Bla');
|
|
113
|
+
const answerService = createAnswerService();
|
|
114
|
+
answerService.fetchCSVBlob(undefined, true);
|
|
115
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
116
|
+
`https://tshost/prism/download/answer/csv?sessionId=${defaultSession.sessionId}&genNo=${defaultSession.genNo}&userLocale=en-us&exportFileName=data&omitInfo=true`,
|
|
117
|
+
expect.objectContaining({}),
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('getUnderlyingDataForPoint should call the right APIs', async () => {
|
|
122
|
+
fetchMock.mockResponses(
|
|
123
|
+
JSON.stringify({
|
|
124
|
+
data: {
|
|
125
|
+
getSourceDetailById: [{
|
|
126
|
+
columns: [{
|
|
127
|
+
id: 'id1',
|
|
128
|
+
name: 'col1',
|
|
129
|
+
}, {
|
|
130
|
+
id: 'id2',
|
|
131
|
+
name: 'col2',
|
|
132
|
+
}, {
|
|
133
|
+
id: 'id3',
|
|
134
|
+
name: 'col3',
|
|
135
|
+
}],
|
|
136
|
+
}],
|
|
137
|
+
},
|
|
138
|
+
}),
|
|
139
|
+
JSON.stringify({
|
|
140
|
+
data: {
|
|
141
|
+
Answer__getUnaggregatedAnswer: {
|
|
142
|
+
id: {
|
|
143
|
+
...defaultSession,
|
|
144
|
+
},
|
|
145
|
+
answer: {
|
|
146
|
+
visualizations: [{
|
|
147
|
+
columns: [{
|
|
148
|
+
column: {
|
|
149
|
+
id: 'oid1',
|
|
150
|
+
name: 'col1',
|
|
151
|
+
referencedColumns: [{
|
|
152
|
+
guid: 'id1',
|
|
153
|
+
}],
|
|
154
|
+
},
|
|
155
|
+
}],
|
|
156
|
+
}],
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
}),
|
|
161
|
+
JSON.stringify({
|
|
162
|
+
data: {
|
|
163
|
+
Answer__addColumn: {
|
|
164
|
+
id: {
|
|
165
|
+
genNo: 2,
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
}),
|
|
170
|
+
JSON.stringify({
|
|
171
|
+
data: {
|
|
172
|
+
Answer__removeColumns: {
|
|
173
|
+
id: {
|
|
174
|
+
genNo: 3,
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
}),
|
|
179
|
+
);
|
|
180
|
+
const answerService = createAnswerService({
|
|
181
|
+
sources: [{
|
|
182
|
+
header: {
|
|
183
|
+
guid: 'source1',
|
|
184
|
+
},
|
|
185
|
+
}],
|
|
186
|
+
}, [{
|
|
187
|
+
selectedAttributes: [{
|
|
188
|
+
column: {
|
|
189
|
+
id: 'oid1', // output column id
|
|
190
|
+
name: 'col1',
|
|
191
|
+
dataType: 'CHAR',
|
|
192
|
+
},
|
|
193
|
+
value: '1',
|
|
194
|
+
}, {
|
|
195
|
+
column: {
|
|
196
|
+
id: 'oid3',
|
|
197
|
+
name: 'col3',
|
|
198
|
+
dataType: 'DATE',
|
|
199
|
+
},
|
|
200
|
+
value: 12345,
|
|
201
|
+
}],
|
|
202
|
+
selectedMeasures: [],
|
|
203
|
+
}]);
|
|
204
|
+
const underlying = await answerService.getUnderlyingDataForPoint(['col2']);
|
|
205
|
+
expect(fetchMock).toHaveBeenCalledTimes(4);
|
|
206
|
+
expect(underlying.getSession().genNo).toBe(3);
|
|
207
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
208
|
+
'https://tshost/prism/?op=RemoveColumns',
|
|
209
|
+
expect.objectContaining({
|
|
210
|
+
body: JSON.stringify({
|
|
211
|
+
operationName: 'RemoveColumns',
|
|
212
|
+
query: removeColumns,
|
|
213
|
+
variables: {
|
|
214
|
+
session: {
|
|
215
|
+
...defaultSession,
|
|
216
|
+
genNo: 2,
|
|
217
|
+
},
|
|
218
|
+
logicalColumnIds: [
|
|
219
|
+
'id1',
|
|
220
|
+
],
|
|
221
|
+
},
|
|
222
|
+
}),
|
|
223
|
+
}),
|
|
224
|
+
);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test('getUnderlyingDataForPoint should throw when no point is selected', async () => {
|
|
228
|
+
const answerService = createAnswerService({}, null);
|
|
229
|
+
await expect(answerService.getUnderlyingDataForPoint(['col2'])).rejects.toThrow();
|
|
230
|
+
});
|
|
231
|
+
});
|