@thoughtspot/visual-embed-sdk 1.48.0 → 1.49.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. package/cjs/package.json +1 -1
  2. package/cjs/src/css-variables.d.ts +140 -0
  3. package/cjs/src/css-variables.d.ts.map +1 -1
  4. package/cjs/src/embed/app.d.ts +62 -1
  5. package/cjs/src/embed/app.d.ts.map +1 -1
  6. package/cjs/src/embed/app.js +57 -6
  7. package/cjs/src/embed/app.js.map +1 -1
  8. package/cjs/src/embed/app.spec.js +191 -1
  9. package/cjs/src/embed/app.spec.js.map +1 -1
  10. package/cjs/src/embed/auto-frame-renderer.js +7 -2
  11. package/cjs/src/embed/auto-frame-renderer.js.map +1 -1
  12. package/cjs/src/embed/auto-frame-renderer.spec.js +385 -6
  13. package/cjs/src/embed/auto-frame-renderer.spec.js.map +1 -1
  14. package/cjs/src/embed/base.d.ts +1 -0
  15. package/cjs/src/embed/base.d.ts.map +1 -1
  16. package/cjs/src/embed/base.js +13 -1
  17. package/cjs/src/embed/base.js.map +1 -1
  18. package/cjs/src/embed/base.spec.js +21 -0
  19. package/cjs/src/embed/base.spec.js.map +1 -1
  20. package/cjs/src/embed/bodyless-conversation.spec.js +86 -0
  21. package/cjs/src/embed/bodyless-conversation.spec.js.map +1 -1
  22. package/cjs/src/embed/conversation.d.ts +16 -1
  23. package/cjs/src/embed/conversation.d.ts.map +1 -1
  24. package/cjs/src/embed/conversation.js +5 -1
  25. package/cjs/src/embed/conversation.js.map +1 -1
  26. package/cjs/src/embed/conversation.spec.js +26 -0
  27. package/cjs/src/embed/conversation.spec.js.map +1 -1
  28. package/cjs/src/embed/liveboard.d.ts +47 -1
  29. package/cjs/src/embed/liveboard.d.ts.map +1 -1
  30. package/cjs/src/embed/liveboard.js +47 -6
  31. package/cjs/src/embed/liveboard.js.map +1 -1
  32. package/cjs/src/embed/liveboard.spec.js +129 -1
  33. package/cjs/src/embed/liveboard.spec.js.map +1 -1
  34. package/cjs/src/embed/spotter-viz-utils.d.ts +85 -0
  35. package/cjs/src/embed/spotter-viz-utils.d.ts.map +1 -0
  36. package/cjs/src/embed/spotter-viz-utils.js +17 -0
  37. package/cjs/src/embed/spotter-viz-utils.js.map +1 -0
  38. package/cjs/src/embed/spotter-viz-utils.spec.d.ts +2 -0
  39. package/cjs/src/embed/spotter-viz-utils.spec.d.ts.map +1 -0
  40. package/cjs/src/embed/spotter-viz-utils.spec.js +31 -0
  41. package/cjs/src/embed/spotter-viz-utils.spec.js.map +1 -0
  42. package/cjs/src/embed/ts-embed.d.ts +58 -38
  43. package/cjs/src/embed/ts-embed.d.ts.map +1 -1
  44. package/cjs/src/embed/ts-embed.js +247 -151
  45. package/cjs/src/embed/ts-embed.js.map +1 -1
  46. package/cjs/src/embed/ts-embed.spec.js +369 -123
  47. package/cjs/src/embed/ts-embed.spec.js.map +1 -1
  48. package/cjs/src/index.d.ts +2 -1
  49. package/cjs/src/index.d.ts.map +1 -1
  50. package/cjs/src/index.js.map +1 -1
  51. package/cjs/src/mixpanel-service.js +2 -2
  52. package/cjs/src/mixpanel-service.js.map +1 -1
  53. package/cjs/src/react/index.d.ts.map +1 -1
  54. package/cjs/src/react/index.js +3 -0
  55. package/cjs/src/react/index.js.map +1 -1
  56. package/cjs/src/types.d.ts +267 -27
  57. package/cjs/src/types.d.ts.map +1 -1
  58. package/cjs/src/types.js +223 -19
  59. package/cjs/src/types.js.map +1 -1
  60. package/cjs/src/utils/authService/tokenizedAuthService.spec.js +6 -7
  61. package/cjs/src/utils/authService/tokenizedAuthService.spec.js.map +1 -1
  62. package/cjs/src/utils/logger.js +2 -1
  63. package/cjs/src/utils/logger.js.map +1 -1
  64. package/cjs/src/utils/logger.spec.d.ts +1 -0
  65. package/cjs/src/utils/logger.spec.d.ts.map +1 -1
  66. package/cjs/src/utils/logger.spec.js +10 -9
  67. package/cjs/src/utils/logger.spec.js.map +1 -1
  68. package/cjs/src/utils/sdk-version.d.ts +2 -0
  69. package/cjs/src/utils/sdk-version.d.ts.map +1 -0
  70. package/cjs/src/utils/sdk-version.js +7 -0
  71. package/cjs/src/utils/sdk-version.js.map +1 -0
  72. package/cjs/src/utils.d.ts +4 -1
  73. package/cjs/src/utils.d.ts.map +1 -1
  74. package/cjs/src/utils.js +107 -10
  75. package/cjs/src/utils.js.map +1 -1
  76. package/cjs/src/utils.spec.js +163 -4
  77. package/cjs/src/utils.spec.js.map +1 -1
  78. package/dist/{index-Ck-r09gt.js → index-B6Rn561t.js} +1 -1
  79. package/dist/src/css-variables.d.ts +140 -0
  80. package/dist/src/css-variables.d.ts.map +1 -1
  81. package/dist/src/embed/app.d.ts +62 -1
  82. package/dist/src/embed/app.d.ts.map +1 -1
  83. package/dist/src/embed/base.d.ts +1 -0
  84. package/dist/src/embed/base.d.ts.map +1 -1
  85. package/dist/src/embed/conversation.d.ts +16 -1
  86. package/dist/src/embed/conversation.d.ts.map +1 -1
  87. package/dist/src/embed/liveboard.d.ts +47 -1
  88. package/dist/src/embed/liveboard.d.ts.map +1 -1
  89. package/dist/src/embed/spotter-viz-utils.d.ts +85 -0
  90. package/dist/src/embed/spotter-viz-utils.d.ts.map +1 -0
  91. package/dist/src/embed/spotter-viz-utils.spec.d.ts +2 -0
  92. package/dist/src/embed/spotter-viz-utils.spec.d.ts.map +1 -0
  93. package/dist/src/embed/ts-embed.d.ts +58 -38
  94. package/dist/src/embed/ts-embed.d.ts.map +1 -1
  95. package/dist/src/index.d.ts +2 -1
  96. package/dist/src/index.d.ts.map +1 -1
  97. package/dist/src/react/index.d.ts.map +1 -1
  98. package/dist/src/types.d.ts +267 -27
  99. package/dist/src/types.d.ts.map +1 -1
  100. package/dist/src/utils/logger.spec.d.ts +1 -0
  101. package/dist/src/utils/logger.spec.d.ts.map +1 -1
  102. package/dist/src/utils/sdk-version.d.ts +2 -0
  103. package/dist/src/utils/sdk-version.d.ts.map +1 -0
  104. package/dist/src/utils.d.ts +4 -1
  105. package/dist/src/utils.d.ts.map +1 -1
  106. package/dist/tsembed-react.es.js +3710 -3226
  107. package/dist/tsembed-react.js +3360 -2876
  108. package/dist/tsembed.es.js +3715 -3229
  109. package/dist/tsembed.js +3710 -3224
  110. package/dist/visual-embed-sdk-react-full.d.ts +643 -63
  111. package/dist/visual-embed-sdk-react.d.ts +643 -63
  112. package/dist/visual-embed-sdk.d.ts +658 -65
  113. package/lib/package.json +1 -1
  114. package/lib/src/css-variables.d.ts +140 -0
  115. package/lib/src/css-variables.d.ts.map +1 -1
  116. package/lib/src/embed/app.d.ts +62 -1
  117. package/lib/src/embed/app.d.ts.map +1 -1
  118. package/lib/src/embed/app.js +58 -7
  119. package/lib/src/embed/app.js.map +1 -1
  120. package/lib/src/embed/app.spec.js +192 -2
  121. package/lib/src/embed/app.spec.js.map +1 -1
  122. package/lib/src/embed/auto-frame-renderer.js +7 -2
  123. package/lib/src/embed/auto-frame-renderer.js.map +1 -1
  124. package/lib/src/embed/auto-frame-renderer.spec.js +387 -8
  125. package/lib/src/embed/auto-frame-renderer.spec.js.map +1 -1
  126. package/lib/src/embed/base.d.ts +1 -0
  127. package/lib/src/embed/base.d.ts.map +1 -1
  128. package/lib/src/embed/base.js +11 -0
  129. package/lib/src/embed/base.js.map +1 -1
  130. package/lib/src/embed/base.spec.js +22 -1
  131. package/lib/src/embed/base.spec.js.map +1 -1
  132. package/lib/src/embed/bodyless-conversation.spec.js +86 -0
  133. package/lib/src/embed/bodyless-conversation.spec.js.map +1 -1
  134. package/lib/src/embed/conversation.d.ts +16 -1
  135. package/lib/src/embed/conversation.d.ts.map +1 -1
  136. package/lib/src/embed/conversation.js +5 -1
  137. package/lib/src/embed/conversation.js.map +1 -1
  138. package/lib/src/embed/conversation.spec.js +27 -1
  139. package/lib/src/embed/conversation.spec.js.map +1 -1
  140. package/lib/src/embed/liveboard.d.ts +47 -1
  141. package/lib/src/embed/liveboard.d.ts.map +1 -1
  142. package/lib/src/embed/liveboard.js +48 -7
  143. package/lib/src/embed/liveboard.js.map +1 -1
  144. package/lib/src/embed/liveboard.spec.js +129 -1
  145. package/lib/src/embed/liveboard.spec.js.map +1 -1
  146. package/lib/src/embed/spotter-viz-utils.d.ts +85 -0
  147. package/lib/src/embed/spotter-viz-utils.d.ts.map +1 -0
  148. package/lib/src/embed/spotter-viz-utils.js +13 -0
  149. package/lib/src/embed/spotter-viz-utils.js.map +1 -0
  150. package/lib/src/embed/spotter-viz-utils.spec.d.ts +2 -0
  151. package/lib/src/embed/spotter-viz-utils.spec.d.ts.map +1 -0
  152. package/lib/src/embed/spotter-viz-utils.spec.js +29 -0
  153. package/lib/src/embed/spotter-viz-utils.spec.js.map +1 -0
  154. package/lib/src/embed/ts-embed.d.ts +58 -38
  155. package/lib/src/embed/ts-embed.d.ts.map +1 -1
  156. package/lib/src/embed/ts-embed.js +249 -153
  157. package/lib/src/embed/ts-embed.js.map +1 -1
  158. package/lib/src/embed/ts-embed.spec.js +369 -123
  159. package/lib/src/embed/ts-embed.spec.js.map +1 -1
  160. package/lib/src/index.d.ts +2 -1
  161. package/lib/src/index.d.ts.map +1 -1
  162. package/lib/src/index.js.map +1 -1
  163. package/lib/src/mixpanel-service.js +1 -1
  164. package/lib/src/mixpanel-service.js.map +1 -1
  165. package/lib/src/react/index.d.ts.map +1 -1
  166. package/lib/src/react/index.js +3 -0
  167. package/lib/src/react/index.js.map +1 -1
  168. package/lib/src/types.d.ts +267 -27
  169. package/lib/src/types.d.ts.map +1 -1
  170. package/lib/src/types.js +223 -19
  171. package/lib/src/types.js.map +1 -1
  172. package/lib/src/utils/authService/tokenizedAuthService.spec.js +6 -7
  173. package/lib/src/utils/authService/tokenizedAuthService.spec.js.map +1 -1
  174. package/lib/src/utils/logger.js +2 -1
  175. package/lib/src/utils/logger.js.map +1 -1
  176. package/lib/src/utils/logger.spec.d.ts +1 -0
  177. package/lib/src/utils/logger.spec.d.ts.map +1 -1
  178. package/lib/src/utils/logger.spec.js +10 -9
  179. package/lib/src/utils/logger.spec.js.map +1 -1
  180. package/lib/src/utils/sdk-version.d.ts +2 -0
  181. package/lib/src/utils/sdk-version.d.ts.map +1 -0
  182. package/lib/src/utils/sdk-version.js +3 -0
  183. package/lib/src/utils/sdk-version.js.map +1 -0
  184. package/lib/src/utils.d.ts +4 -1
  185. package/lib/src/utils.d.ts.map +1 -1
  186. package/lib/src/utils.js +103 -9
  187. package/lib/src/utils.js.map +1 -1
  188. package/lib/src/utils.spec.js +164 -5
  189. package/lib/src/utils.spec.js.map +1 -1
  190. package/lib/src/visual-embed-sdk.d.ts +658 -65
  191. package/package.json +1 -1
  192. package/src/css-variables.ts +175 -1
  193. package/src/embed/app.spec.ts +247 -3
  194. package/src/embed/app.ts +125 -5
  195. package/src/embed/auto-frame-renderer.spec.ts +457 -58
  196. package/src/embed/auto-frame-renderer.ts +7 -2
  197. package/src/embed/base.spec.ts +25 -1
  198. package/src/embed/base.ts +19 -5
  199. package/src/embed/bodyless-conversation.spec.ts +93 -0
  200. package/src/embed/conversation.spec.ts +34 -0
  201. package/src/embed/conversation.ts +22 -1
  202. package/src/embed/liveboard.spec.ts +149 -1
  203. package/src/embed/liveboard.ts +102 -6
  204. package/src/embed/spotter-viz-utils.spec.ts +30 -0
  205. package/src/embed/spotter-viz-utils.ts +94 -0
  206. package/src/embed/ts-embed.spec.ts +532 -234
  207. package/src/embed/ts-embed.ts +384 -258
  208. package/src/index.ts +3 -0
  209. package/src/mixpanel-service.ts +1 -1
  210. package/src/react/index.tsx +3 -0
  211. package/src/types.ts +284 -23
  212. package/src/utils/authService/tokenizedAuthService.spec.ts +6 -6
  213. package/src/utils/logger.spec.ts +11 -9
  214. package/src/utils/logger.ts +2 -2
  215. package/src/utils/sdk-version.ts +3 -0
  216. package/src/utils.spec.ts +200 -4
  217. package/src/utils.ts +128 -9
@@ -115,8 +115,9 @@ class AutoFrameRenderer extends TsEmbed {
115
115
  delete existingQueryParamsObject[Param.Tsmcp];
116
116
 
117
117
  const mergedQueryParams = { ...queryParams, ...existingQueryParamsObject };
118
- const mergedQueryParamsString = getQueryParamString(mergedQueryParams);
119
- const frameSrc = `${this.getEmbedBasePath(mergedQueryParamsString)}${sourceURL.hash.replace('#', '')}`;
118
+ const mergedQueryParamsString = getQueryParamString(mergedQueryParams, true);
119
+ const queryString = mergedQueryParamsString ? `?${mergedQueryParamsString}` : '';
120
+ const frameSrc = `${this.getEmbedBasePath(queryString)}${sourceURL.hash.replace('#', '')}`;
120
121
  return frameSrc;
121
122
  }
122
123
 
@@ -145,8 +146,12 @@ class AutoFrameRenderer extends TsEmbed {
145
146
  */
146
147
  public async replaceIframe(iframe: HTMLIFrameElement): Promise<void> {
147
148
  this.frameToReplace = iframe;
149
+ if (this.shouldWaitForRenderPromise) {
150
+ await this.isReadyForRenderPromise;
151
+ }
148
152
  const src = this.getMCPIframeSrc(iframe.src);
149
153
  await this.renderIFrame(src);
154
+ this.isRendered = true;
150
155
  }
151
156
  }
152
157
 
@@ -10,7 +10,7 @@ import * as base from './base';
10
10
  import * as embedConfigInstance from './embedConfig';
11
11
  import * as resetService from '../utils/resetServices';
12
12
  import * as processTrigger from '../utils/processTrigger';
13
- import { createAndSetInitPromise, getInitPromise, getIsInitCalled, reloadIframe } from './base';
13
+ import { createAndSetInitPromise, getInitPromise, getIsInitCalled, getIsInitCompleted, reloadIframe } from './base';
14
14
 
15
15
  import {
16
16
  executeAfterWait,
@@ -607,4 +607,28 @@ describe('Init Promise Functions', () => {
607
607
  const secondPromise = getInitPromise();
608
608
  expect(firstPromise).toBe(secondPromise);
609
609
  });
610
+
611
+ test('getIsInitCompleted returns false (not undefined) before init resolves', () => {
612
+ // fresh store via beforeEach; init not yet called
613
+ const result = getIsInitCompleted();
614
+ expect(result).toBe(false);
615
+ expect(typeof result).toBe('boolean');
616
+ });
617
+
618
+ test('getIsInitCompleted returns true after init resolves', async () => {
619
+ base.init({
620
+ thoughtSpotHost: 'tshost',
621
+ authType: index.AuthType.None,
622
+ });
623
+ await getInitPromise();
624
+ // flush the finally() microtask
625
+ await new Promise((r) => setTimeout(r, 0));
626
+ expect(getIsInitCompleted()).toBe(true);
627
+ });
628
+
629
+ test('getIsInitCompleted returns false when window store is absent', () => {
630
+ // clear the store from window
631
+ (window as any)._tsEmbedSDK = {};
632
+ expect(getIsInitCompleted()).toBe(false);
633
+ });
610
634
  });
package/src/embed/base.ts CHANGED
@@ -182,9 +182,10 @@ function backwardCompat(embedConfig: EmbedConfig): EmbedConfig {
182
182
  }
183
183
 
184
184
  type InitFlagStore = {
185
- initPromise: Promise<ReturnType<typeof init>>;
186
- isInitCalled: boolean;
187
- initPromiseResolve: (value: ReturnType<typeof init>) => void;
185
+ initPromise: Promise<ReturnType<typeof init>>;
186
+ isInitCalled: boolean;
187
+ isInitCompleted: boolean;
188
+ initPromiseResolve: (value: ReturnType<typeof init>) => void;
188
189
  }
189
190
  const initFlagKey = 'initFlagKey';
190
191
 
@@ -199,21 +200,34 @@ export const createAndSetInitPromise = (): void => {
199
200
  const initFlagStore: InitFlagStore = {
200
201
  initPromise,
201
202
  isInitCalled: false,
203
+ isInitCompleted: false,
202
204
  initPromiseResolve,
203
205
  };
204
206
  storeValueInWindow(initFlagKey, initFlagStore, {
205
207
  // In case of diff imports the promise might be already set
206
208
  ignoreIfAlreadyExists: true,
207
209
  });
210
+ initPromise.finally(() => {
211
+ const curVal = getValueFromWindow<InitFlagStore>(initFlagKey);
212
+ if (!curVal) {
213
+ logger.error('initFlagStore missing when marking init complete');
214
+ return;
215
+ }
216
+ curVal.isInitCompleted = true;
217
+ storeValueInWindow(initFlagKey, curVal);
218
+ });
208
219
  };
209
220
 
210
221
  createAndSetInitPromise();
211
222
 
212
223
  export const getInitPromise = ():
213
224
  Promise<
214
- ReturnType<typeof init>
225
+ ReturnType<typeof init>
215
226
  > => getValueFromWindow<InitFlagStore>(initFlagKey)?.initPromise;
216
227
 
228
+ export const getIsInitCompleted = (): boolean =>
229
+ !!getValueFromWindow<InitFlagStore>(initFlagKey)?.isInitCompleted;
230
+
217
231
  export const getIsInitCalled = (): boolean => !!getValueFromWindow(initFlagKey)?.isInitCalled;
218
232
 
219
233
  /**
@@ -319,7 +333,7 @@ export const renderInQueue = (fn: (next?: (val?: any) => void) => Promise<any>):
319
333
  return renderQueue;
320
334
  }
321
335
  // Sending an empty function to keep it consistent with the above usage.
322
- return fn(() => {});
336
+ return fn(() => { });
323
337
  };
324
338
 
325
339
  /**
@@ -238,6 +238,99 @@ describe('SpotterAgentEmbed', () => {
238
238
  expect(iframeSrc).toContain('hideAction');
239
239
  });
240
240
 
241
+ test('should handle disabledActions and disabledActionReason parameters correctly', async () => {
242
+ fetchMock.mockResponses(
243
+ JSON.stringify({
244
+ data: {
245
+ ConvAssist__createConversation: {
246
+ convId: 'conversationId',
247
+ initialCtx: {
248
+ type: 'TS_ANSWER',
249
+ tsAnsCtx: {
250
+ sessionId: 'sessionId',
251
+ genNo: 1,
252
+ stateKey: {
253
+ transactionId: 'transactionId',
254
+ generationNumber: 1,
255
+ },
256
+ worksheet: {
257
+ worksheetId: 'worksheetId',
258
+ worksheetName: 'GTM',
259
+ },
260
+ },
261
+ },
262
+ },
263
+ },
264
+ }),
265
+ JSON.stringify({
266
+ data: {
267
+ ConvAssist__sendMessage: {
268
+ responses: [
269
+ {
270
+ msgId: 'msgId',
271
+ data: {
272
+ asstRespData: {
273
+ tool: 'TS_NLS',
274
+ asstRespText: '',
275
+ nlsAnsData: {
276
+ sageQuerySuggestions: [
277
+ {
278
+ llmReasoning: {
279
+ assumptions: '',
280
+ clarifications: '',
281
+ interpretation: '',
282
+ __typename: 'eureka_SageQuerySuggestion_LLMReasoning',
283
+ },
284
+ tokens: [],
285
+ tmlTokens: [],
286
+ worksheetId: 'worksheetId',
287
+ description: '',
288
+ title: '',
289
+ cached: false,
290
+ sqlQuery: '',
291
+ sessionId: 'sessionId',
292
+ genNo: 2,
293
+ formulaInfo: [],
294
+ tmlPhrases: [],
295
+ stateKey: {
296
+ transactionId: 'transactionId',
297
+ generationNumber: 1,
298
+ __typename: 'sage_auto_complete_v2_ACStateKey',
299
+ },
300
+ __typename: 'eureka_SageQuerySuggestion',
301
+ },
302
+ ],
303
+ responseType: 'ANSWER',
304
+ __typename: 'convassist_nls_tool_NLSToolAsstRespData',
305
+ },
306
+ __typename: 'convassist_AsstResponseData',
307
+ },
308
+ __typename: 'convassist_MessageData',
309
+ },
310
+ type: 'ASST_RESPONSE',
311
+ __typename: 'convassist_MessagePayload',
312
+ },
313
+ ],
314
+ __typename: 'convassist_SendMessageResponse',
315
+ },
316
+ },
317
+ }),
318
+ );
319
+
320
+ const viewConfig: SpotterAgentEmbedViewConfig = {
321
+ worksheetId: 'worksheetId',
322
+ disabledActions: [Action.Download, Action.Save],
323
+ disabledActionReason: 'Upgrade to Pro!',
324
+ };
325
+
326
+ const spotterAgentEmbed = new SpotterAgentEmbed(viewConfig);
327
+ const result = await spotterAgentEmbed.sendMessage('userMessage');
328
+
329
+ const iframeSrc = getIFrameSrc(result.container);
330
+ expect(iframeSrc).toContain('disableAction');
331
+ expect(iframeSrc).toContain('disableHint');
332
+ });
333
+
241
334
  test('should have sendMessageData method', () => {
242
335
  const viewConfig: SpotterAgentEmbedViewConfig = {
243
336
  worksheetId: 'worksheetId',
@@ -10,6 +10,7 @@ import {
10
10
  getRootEl,
11
11
  defaultParamsWithoutHiddenActions as defaultParams,
12
12
  expectUrlMatchesWithParams,
13
+ expectUrlToHaveParamsWithValues,
13
14
  postMessageToParent,
14
15
  executeAfterWait,
15
16
  } from '../test/test-utils';
@@ -351,6 +352,39 @@ describe('ConversationEmbed', () => {
351
352
  );
352
353
  });
353
354
 
355
+ it('should render the conversation embed with spotterFileUploadEnabled', async () => {
356
+ const viewConfig: SpotterEmbedViewConfig = {
357
+ worksheetId: 'worksheetId',
358
+ searchOptions: {
359
+ searchQuery: 'searchQuery',
360
+ },
361
+ spotterChatConfig: { spotterFileUploadEnabled: true },
362
+ };
363
+
364
+ const conversationEmbed = new SpotterEmbed(getRootEl(), viewConfig);
365
+ await conversationEmbed.render();
366
+ expectUrlMatchesWithParams(
367
+ getIFrameSrc(),
368
+ `http://${thoughtSpotHost}/v2/?${defaultParams}&isSpotterExperienceEnabled=true&spotterFileUploadEnabled=true#/embed/insights/conv-assist?worksheet=worksheetId&query=searchQuery`,
369
+ );
370
+ });
371
+
372
+ it('should render the conversation embed with spotterFileUploadFileTypes', async () => {
373
+ const viewConfig: SpotterEmbedViewConfig = {
374
+ worksheetId: 'worksheetId',
375
+ searchOptions: {
376
+ searchQuery: 'searchQuery',
377
+ },
378
+ spotterChatConfig: { spotterFileUploadFileTypes: { types: ['image/png', 'application/pdf'] } },
379
+ };
380
+
381
+ const conversationEmbed = new SpotterEmbed(getRootEl(), viewConfig);
382
+ await conversationEmbed.render();
383
+ expectUrlToHaveParamsWithValues(getIFrameSrc(), {
384
+ spotterFileUploadFileTypes: JSON.stringify({ types: ['image/png', 'application/pdf'] }),
385
+ });
386
+ });
387
+
354
388
  it('should ensure deprecated ConversationEmbed class maintains same functionality as SpotterEmbed', async () => {
355
389
  const viewConfig: SpotterEmbedViewConfig = {
356
390
  worksheetId: 'worksheetId',
@@ -1,6 +1,6 @@
1
1
  import isUndefined from 'lodash/isUndefined';
2
2
  import { ERROR_MESSAGE } from '../errors';
3
- import { Param, BaseViewConfig, RuntimeFilter, RuntimeParameter, ErrorDetailsTypes, EmbedErrorCodes, DefaultAppInitData, VisualizationOverrides } from '../types';
3
+ import { Param, BaseViewConfig, RuntimeFilter, RuntimeParameter, ErrorDetailsTypes, EmbedErrorCodes, DefaultAppInitData, VisualizationOverrides, SpotterFileUploadFileTypes } from '../types';
4
4
  import { TsEmbed } from './ts-embed';
5
5
  import { buildSpotterSidebarAppInitData } from './spotter-utils';
6
6
  import { getQueryParamString, getFilterQuery, getRuntimeParameters, setParamIfDefined } from '../utils';
@@ -113,6 +113,21 @@ export interface SpotterChatViewConfig {
113
113
  * External MCP tool branding is not affected.
114
114
  */
115
115
  toolResponseCardBrandingLabel?: string;
116
+ /**
117
+ * Enables file upload in the Spotter chat interface.
118
+ *
119
+ * Supported embed types: `SpotterEmbed`, `LiveboardEmbed`, `AppEmbed`
120
+ * @version SDK: 1.49.0 | ThoughtSpot: 26.6.0.cl
121
+ * @default false
122
+ */
123
+ spotterFileUploadEnabled?: boolean;
124
+ /**
125
+ * Restricts the allowed file types for Spotter file upload.
126
+ *
127
+ * Supported embed types: `SpotterEmbed`, `LiveboardEmbed`, `AppEmbed`
128
+ * @version SDK: 1.49.0 | ThoughtSpot: 26.6.0.cl
129
+ */
130
+ spotterFileUploadFileTypes?: SpotterFileUploadFileTypes;
116
131
  }
117
132
 
118
133
  /**
@@ -450,10 +465,16 @@ export class SpotterEmbed extends TsEmbed {
450
465
  const {
451
466
  hideToolResponseCardBranding,
452
467
  toolResponseCardBrandingLabel,
468
+ spotterFileUploadEnabled,
469
+ spotterFileUploadFileTypes,
453
470
  } = spotterChatConfig;
454
471
 
455
472
  setParamIfDefined(queryParams, Param.HideToolResponseCardBranding, hideToolResponseCardBranding, true);
456
473
  setParamIfDefined(queryParams, Param.ToolResponseCardBrandingLabel, toolResponseCardBrandingLabel);
474
+ setParamIfDefined(queryParams, Param.SpotterFileUploadEnabled, spotterFileUploadEnabled, true);
475
+ if (spotterFileUploadFileTypes !== undefined) {
476
+ queryParams[Param.SpotterFileUploadFileTypes] = JSON.stringify(spotterFileUploadFileTypes);
477
+ }
457
478
  }
458
479
 
459
480
  return queryParams;
@@ -1266,6 +1266,35 @@ describe('Liveboard/viz embed tests', () => {
1266
1266
  });
1267
1267
  });
1268
1268
 
1269
+ test('should set spotterFileUploadEnabled parameter in url params', async () => {
1270
+ const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
1271
+ ...defaultViewConfig,
1272
+ liveboardId,
1273
+ spotterChatConfig: { spotterFileUploadEnabled: true },
1274
+ } as LiveboardViewConfig);
1275
+ await liveboardEmbed.render();
1276
+ await executeAfterWait(() => {
1277
+ expectUrlMatchesWithParams(
1278
+ getIFrameSrc(),
1279
+ `http://${thoughtSpotHost}/?embedApp=true${defaultParams}${prefixParams}&spotterFileUploadEnabled=true#/embed/viz/${liveboardId}`,
1280
+ );
1281
+ });
1282
+ });
1283
+
1284
+ test('should set spotterFileUploadFileTypes parameter in url params', async () => {
1285
+ const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
1286
+ ...defaultViewConfig,
1287
+ liveboardId,
1288
+ spotterChatConfig: { spotterFileUploadFileTypes: { types: ['image/png', 'application/pdf'] } },
1289
+ } as LiveboardViewConfig);
1290
+ await liveboardEmbed.render();
1291
+ await executeAfterWait(() => {
1292
+ expectUrlToHaveParamsWithValues(getIFrameSrc(), {
1293
+ spotterFileUploadFileTypes: JSON.stringify({ types: ['image/png', 'application/pdf'] }),
1294
+ });
1295
+ });
1296
+ });
1297
+
1269
1298
  test('should render the liveboard embed with updatedSpotterChatPrompt', async () => {
1270
1299
  const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
1271
1300
  ...defaultViewConfig,
@@ -1329,6 +1358,94 @@ describe('Liveboard/viz embed tests', () => {
1329
1358
  });
1330
1359
  });
1331
1360
 
1361
+ test('should include spotterVizConfig in APP_INIT embedParams when spotterViz is provided', async () => {
1362
+ const spotterViz = {
1363
+ brandName: 'MyBrand',
1364
+ brandHeadline: "Hi, there! I'm",
1365
+ description: 'Ask questions about your data',
1366
+ inputChatPlaceholder: 'Ask a question...',
1367
+ hideStarterPrompts: false,
1368
+ customStarterPrompts: [
1369
+ { id: '1', displayText: 'Show revenue by region', fullPrompt: 'Show revenue by region' },
1370
+ { id: '2', displayText: 'Top customers', fullPrompt: 'Top customers by sales' },
1371
+ ],
1372
+ };
1373
+ const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
1374
+ ...defaultViewConfig,
1375
+ liveboardId,
1376
+ spotterViz,
1377
+ } as LiveboardViewConfig);
1378
+
1379
+ mockMessageChannel();
1380
+ await liveboardEmbed.render();
1381
+
1382
+ const mockPort: any = { postMessage: jest.fn() };
1383
+ await executeAfterWait(() => {
1384
+ postMessageToParent(getIFrameEl().contentWindow, { type: EmbedEvent.APP_INIT, data: {} }, mockPort);
1385
+ });
1386
+ await executeAfterWait(() => {
1387
+ expect(mockPort.postMessage).toHaveBeenCalledWith({
1388
+ type: EmbedEvent.APP_INIT,
1389
+ data: expect.objectContaining({
1390
+ embedParams: expect.objectContaining({
1391
+ spotterVizConfig: spotterViz,
1392
+ }),
1393
+ }),
1394
+ });
1395
+ });
1396
+ });
1397
+
1398
+ test('should pass brandHeadline through spotterVizConfig in APP_INIT', async () => {
1399
+ const spotterViz = { brandName: 'MyBrand', brandHeadline: "Hi, there! I'm" };
1400
+ const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
1401
+ ...defaultViewConfig,
1402
+ liveboardId,
1403
+ spotterViz,
1404
+ } as LiveboardViewConfig);
1405
+
1406
+ mockMessageChannel();
1407
+ await liveboardEmbed.render();
1408
+
1409
+ const mockPort: any = { postMessage: jest.fn() };
1410
+ await executeAfterWait(() => {
1411
+ postMessageToParent(getIFrameEl().contentWindow, { type: EmbedEvent.APP_INIT, data: {} }, mockPort);
1412
+ });
1413
+ await executeAfterWait(() => {
1414
+ expect(mockPort.postMessage).toHaveBeenCalledWith({
1415
+ type: EmbedEvent.APP_INIT,
1416
+ data: expect.objectContaining({
1417
+ embedParams: expect.objectContaining({
1418
+ spotterVizConfig: expect.objectContaining({
1419
+ brandHeadline: "Hi, there! I'm",
1420
+ }),
1421
+ }),
1422
+ }),
1423
+ });
1424
+ });
1425
+ });
1426
+
1427
+ test('should not include spotterVizConfig in APP_INIT when spotterViz is not provided', async () => {
1428
+ const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
1429
+ ...defaultViewConfig,
1430
+ liveboardId,
1431
+ } as LiveboardViewConfig);
1432
+
1433
+ mockMessageChannel();
1434
+ await liveboardEmbed.render();
1435
+
1436
+ const mockPort: any = { postMessage: jest.fn() };
1437
+ await executeAfterWait(() => {
1438
+ postMessageToParent(getIFrameEl().contentWindow, { type: EmbedEvent.APP_INIT, data: {} }, mockPort);
1439
+ });
1440
+ await executeAfterWait(() => {
1441
+ const callArgs = mockPort.postMessage.mock.calls[0][0];
1442
+ expect(callArgs.type).toBe(EmbedEvent.APP_INIT);
1443
+ if (callArgs.data.embedParams) {
1444
+ expect(callArgs.data.embedParams.spotterVizConfig).toBeUndefined();
1445
+ }
1446
+ });
1447
+ });
1448
+
1332
1449
  test('SetActiveTab Hostevent should not trigger the navigate event with the correct path, for vizEmbed', async () => {
1333
1450
  const mockProcessTrigger = jest.spyOn(tsEmbed.TsEmbed.prototype, 'trigger');
1334
1451
  const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
@@ -1835,6 +1952,37 @@ describe('Liveboard/viz embed tests', () => {
1835
1952
  addEventListenerSpy.mockRestore();
1836
1953
  });
1837
1954
 
1955
+ test('should listen to scroll and resize changes from scrollable iframe ancestors', async () => {
1956
+ const scrollContainer = getRootEl();
1957
+ scrollContainer.style.overflow = 'auto';
1958
+
1959
+ const scrollContainerAddEventListenerSpy = jest.spyOn(scrollContainer, 'addEventListener');
1960
+ const resizeObserveSpy = jest.fn();
1961
+ const resizeDisconnectSpy = jest.fn();
1962
+ (window as any).ResizeObserver = jest.fn().mockImplementation(() => ({
1963
+ observe: resizeObserveSpy,
1964
+ disconnect: resizeDisconnectSpy,
1965
+ }));
1966
+
1967
+ const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
1968
+ ...defaultViewConfig,
1969
+ liveboardId,
1970
+ fullHeight: true,
1971
+ lazyLoadingForFullHeight: true,
1972
+ enableScrollableContainerLazyLoading: true,
1973
+ } as LiveboardViewConfig);
1974
+
1975
+ await liveboardEmbed.render();
1976
+
1977
+ await executeAfterWait(() => {
1978
+ expect(scrollContainerAddEventListenerSpy).toHaveBeenCalledWith('scroll', expect.any(Function));
1979
+ expect(resizeObserveSpy).toHaveBeenCalledWith(scrollContainer);
1980
+ }, 100);
1981
+
1982
+ liveboardEmbed.destroy();
1983
+ expect(resizeDisconnectSpy).toHaveBeenCalled();
1984
+ });
1985
+
1838
1986
  test('should remove window event listeners on destroy when fullHeight and lazyLoadingForFullHeight are enabled', async () => {
1839
1987
  const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener');
1840
1988
 
@@ -1850,7 +1998,7 @@ describe('Liveboard/viz embed tests', () => {
1850
1998
  liveboardEmbed.destroy();
1851
1999
 
1852
2000
  expect(removeEventListenerSpy).toHaveBeenCalledWith('resize', expect.anything());
1853
- expect(removeEventListenerSpy).toHaveBeenCalledWith('scroll', expect.anything());
2001
+ expect(removeEventListenerSpy).toHaveBeenCalledWith('scroll', expect.anything(), true);
1854
2002
 
1855
2003
  removeEventListenerSpy.mockRestore();
1856
2004
  });