@thoughtspot/visual-embed-sdk 1.47.3 → 1.49.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.
Files changed (244) hide show
  1. package/cjs/package.json +1 -1
  2. package/cjs/src/auth.d.ts.map +1 -1
  3. package/cjs/src/auth.js +11 -1
  4. package/cjs/src/auth.js.map +1 -1
  5. package/cjs/src/auth.spec.js +38 -0
  6. package/cjs/src/auth.spec.js.map +1 -1
  7. package/cjs/src/authToken.d.ts +2 -0
  8. package/cjs/src/authToken.d.ts.map +1 -1
  9. package/cjs/src/authToken.js +7 -5
  10. package/cjs/src/authToken.js.map +1 -1
  11. package/cjs/src/css-variables.d.ts +140 -0
  12. package/cjs/src/css-variables.d.ts.map +1 -1
  13. package/cjs/src/embed/app.d.ts +63 -2
  14. package/cjs/src/embed/app.d.ts.map +1 -1
  15. package/cjs/src/embed/app.js +57 -6
  16. package/cjs/src/embed/app.js.map +1 -1
  17. package/cjs/src/embed/app.spec.js +200 -1
  18. package/cjs/src/embed/app.spec.js.map +1 -1
  19. package/cjs/src/embed/auto-frame-renderer.js +7 -2
  20. package/cjs/src/embed/auto-frame-renderer.js.map +1 -1
  21. package/cjs/src/embed/auto-frame-renderer.spec.js +385 -6
  22. package/cjs/src/embed/auto-frame-renderer.spec.js.map +1 -1
  23. package/cjs/src/embed/base.d.ts +1 -0
  24. package/cjs/src/embed/base.d.ts.map +1 -1
  25. package/cjs/src/embed/base.js +13 -1
  26. package/cjs/src/embed/base.js.map +1 -1
  27. package/cjs/src/embed/base.spec.js +21 -0
  28. package/cjs/src/embed/base.spec.js.map +1 -1
  29. package/cjs/src/embed/bodyless-conversation.spec.js +86 -0
  30. package/cjs/src/embed/bodyless-conversation.spec.js.map +1 -1
  31. package/cjs/src/embed/conversation.d.ts +16 -1
  32. package/cjs/src/embed/conversation.d.ts.map +1 -1
  33. package/cjs/src/embed/conversation.js +5 -1
  34. package/cjs/src/embed/conversation.js.map +1 -1
  35. package/cjs/src/embed/conversation.spec.js +26 -0
  36. package/cjs/src/embed/conversation.spec.js.map +1 -1
  37. package/cjs/src/embed/liveboard.d.ts +48 -2
  38. package/cjs/src/embed/liveboard.d.ts.map +1 -1
  39. package/cjs/src/embed/liveboard.js +48 -7
  40. package/cjs/src/embed/liveboard.js.map +1 -1
  41. package/cjs/src/embed/liveboard.spec.js +139 -1
  42. package/cjs/src/embed/liveboard.spec.js.map +1 -1
  43. package/cjs/src/embed/spotter-viz-utils.d.ts +85 -0
  44. package/cjs/src/embed/spotter-viz-utils.d.ts.map +1 -0
  45. package/cjs/src/embed/spotter-viz-utils.js +17 -0
  46. package/cjs/src/embed/spotter-viz-utils.js.map +1 -0
  47. package/cjs/src/embed/spotter-viz-utils.spec.d.ts +2 -0
  48. package/cjs/src/embed/spotter-viz-utils.spec.d.ts.map +1 -0
  49. package/cjs/src/embed/spotter-viz-utils.spec.js +31 -0
  50. package/cjs/src/embed/spotter-viz-utils.spec.js.map +1 -0
  51. package/cjs/src/embed/ts-embed.d.ts +58 -38
  52. package/cjs/src/embed/ts-embed.d.ts.map +1 -1
  53. package/cjs/src/embed/ts-embed.js +247 -151
  54. package/cjs/src/embed/ts-embed.js.map +1 -1
  55. package/cjs/src/embed/ts-embed.spec.js +397 -122
  56. package/cjs/src/embed/ts-embed.spec.js.map +1 -1
  57. package/cjs/src/index.d.ts +2 -1
  58. package/cjs/src/index.d.ts.map +1 -1
  59. package/cjs/src/index.js.map +1 -1
  60. package/cjs/src/react/index.d.ts.map +1 -1
  61. package/cjs/src/react/index.js +3 -0
  62. package/cjs/src/react/index.js.map +1 -1
  63. package/cjs/src/tokenizedFetch.d.ts.map +1 -1
  64. package/cjs/src/tokenizedFetch.js +12 -9
  65. package/cjs/src/tokenizedFetch.js.map +1 -1
  66. package/cjs/src/tokenizedFetch.spec.d.ts +2 -0
  67. package/cjs/src/tokenizedFetch.spec.d.ts.map +1 -0
  68. package/cjs/src/tokenizedFetch.spec.js +68 -0
  69. package/cjs/src/tokenizedFetch.spec.js.map +1 -0
  70. package/cjs/src/types.d.ts +309 -40
  71. package/cjs/src/types.d.ts.map +1 -1
  72. package/cjs/src/types.js +251 -23
  73. package/cjs/src/types.js.map +1 -1
  74. package/cjs/src/utils/authService/tokenizedAuthService.spec.js +6 -7
  75. package/cjs/src/utils/authService/tokenizedAuthService.spec.js.map +1 -1
  76. package/cjs/src/utils/logger.js +2 -1
  77. package/cjs/src/utils/logger.js.map +1 -1
  78. package/cjs/src/utils/logger.spec.d.ts +1 -0
  79. package/cjs/src/utils/logger.spec.d.ts.map +1 -1
  80. package/cjs/src/utils/logger.spec.js +10 -9
  81. package/cjs/src/utils/logger.spec.js.map +1 -1
  82. package/cjs/src/utils.d.ts +4 -1
  83. package/cjs/src/utils.d.ts.map +1 -1
  84. package/cjs/src/utils.js +107 -10
  85. package/cjs/src/utils.js.map +1 -1
  86. package/cjs/src/utils.spec.js +163 -4
  87. package/cjs/src/utils.spec.js.map +1 -1
  88. package/dist/{index-DZq20cR6.js → index-_UGCSSDR.js} +1 -1
  89. package/dist/src/auth.d.ts.map +1 -1
  90. package/dist/src/authToken.d.ts +2 -0
  91. package/dist/src/authToken.d.ts.map +1 -1
  92. package/dist/src/css-variables.d.ts +140 -0
  93. package/dist/src/css-variables.d.ts.map +1 -1
  94. package/dist/src/embed/app.d.ts +63 -2
  95. package/dist/src/embed/app.d.ts.map +1 -1
  96. package/dist/src/embed/base.d.ts +1 -0
  97. package/dist/src/embed/base.d.ts.map +1 -1
  98. package/dist/src/embed/conversation.d.ts +16 -1
  99. package/dist/src/embed/conversation.d.ts.map +1 -1
  100. package/dist/src/embed/liveboard.d.ts +48 -2
  101. package/dist/src/embed/liveboard.d.ts.map +1 -1
  102. package/dist/src/embed/spotter-viz-utils.d.ts +85 -0
  103. package/dist/src/embed/spotter-viz-utils.d.ts.map +1 -0
  104. package/dist/src/embed/spotter-viz-utils.spec.d.ts +2 -0
  105. package/dist/src/embed/spotter-viz-utils.spec.d.ts.map +1 -0
  106. package/dist/src/embed/ts-embed.d.ts +58 -38
  107. package/dist/src/embed/ts-embed.d.ts.map +1 -1
  108. package/dist/src/index.d.ts +2 -1
  109. package/dist/src/index.d.ts.map +1 -1
  110. package/dist/src/react/index.d.ts.map +1 -1
  111. package/dist/src/tokenizedFetch.d.ts.map +1 -1
  112. package/dist/src/tokenizedFetch.spec.d.ts +2 -0
  113. package/dist/src/tokenizedFetch.spec.d.ts.map +1 -0
  114. package/dist/src/types.d.ts +309 -40
  115. package/dist/src/types.d.ts.map +1 -1
  116. package/dist/src/utils/logger.spec.d.ts +1 -0
  117. package/dist/src/utils/logger.spec.d.ts.map +1 -1
  118. package/dist/src/utils.d.ts +4 -1
  119. package/dist/src/utils.d.ts.map +1 -1
  120. package/dist/tsembed-react.es.js +3418 -2899
  121. package/dist/tsembed-react.js +3420 -2901
  122. package/dist/tsembed.es.js +3426 -2905
  123. package/dist/tsembed.js +3419 -2898
  124. package/dist/visual-embed-sdk-react-full.d.ts +687 -78
  125. package/dist/visual-embed-sdk-react.d.ts +687 -78
  126. package/dist/visual-embed-sdk.d.ts +702 -80
  127. package/lib/package.json +1 -1
  128. package/lib/src/auth.d.ts.map +1 -1
  129. package/lib/src/auth.js +12 -2
  130. package/lib/src/auth.js.map +1 -1
  131. package/lib/src/auth.spec.js +38 -0
  132. package/lib/src/auth.spec.js.map +1 -1
  133. package/lib/src/authToken.d.ts +2 -0
  134. package/lib/src/authToken.d.ts.map +1 -1
  135. package/lib/src/authToken.js +2 -2
  136. package/lib/src/authToken.js.map +1 -1
  137. package/lib/src/css-variables.d.ts +140 -0
  138. package/lib/src/css-variables.d.ts.map +1 -1
  139. package/lib/src/embed/app.d.ts +63 -2
  140. package/lib/src/embed/app.d.ts.map +1 -1
  141. package/lib/src/embed/app.js +58 -7
  142. package/lib/src/embed/app.js.map +1 -1
  143. package/lib/src/embed/app.spec.js +201 -2
  144. package/lib/src/embed/app.spec.js.map +1 -1
  145. package/lib/src/embed/auto-frame-renderer.js +7 -2
  146. package/lib/src/embed/auto-frame-renderer.js.map +1 -1
  147. package/lib/src/embed/auto-frame-renderer.spec.js +387 -8
  148. package/lib/src/embed/auto-frame-renderer.spec.js.map +1 -1
  149. package/lib/src/embed/base.d.ts +1 -0
  150. package/lib/src/embed/base.d.ts.map +1 -1
  151. package/lib/src/embed/base.js +11 -0
  152. package/lib/src/embed/base.js.map +1 -1
  153. package/lib/src/embed/base.spec.js +22 -1
  154. package/lib/src/embed/base.spec.js.map +1 -1
  155. package/lib/src/embed/bodyless-conversation.spec.js +86 -0
  156. package/lib/src/embed/bodyless-conversation.spec.js.map +1 -1
  157. package/lib/src/embed/conversation.d.ts +16 -1
  158. package/lib/src/embed/conversation.d.ts.map +1 -1
  159. package/lib/src/embed/conversation.js +5 -1
  160. package/lib/src/embed/conversation.js.map +1 -1
  161. package/lib/src/embed/conversation.spec.js +27 -1
  162. package/lib/src/embed/conversation.spec.js.map +1 -1
  163. package/lib/src/embed/liveboard.d.ts +48 -2
  164. package/lib/src/embed/liveboard.d.ts.map +1 -1
  165. package/lib/src/embed/liveboard.js +49 -8
  166. package/lib/src/embed/liveboard.js.map +1 -1
  167. package/lib/src/embed/liveboard.spec.js +139 -1
  168. package/lib/src/embed/liveboard.spec.js.map +1 -1
  169. package/lib/src/embed/spotter-viz-utils.d.ts +85 -0
  170. package/lib/src/embed/spotter-viz-utils.d.ts.map +1 -0
  171. package/lib/src/embed/spotter-viz-utils.js +13 -0
  172. package/lib/src/embed/spotter-viz-utils.js.map +1 -0
  173. package/lib/src/embed/spotter-viz-utils.spec.d.ts +2 -0
  174. package/lib/src/embed/spotter-viz-utils.spec.d.ts.map +1 -0
  175. package/lib/src/embed/spotter-viz-utils.spec.js +29 -0
  176. package/lib/src/embed/spotter-viz-utils.spec.js.map +1 -0
  177. package/lib/src/embed/ts-embed.d.ts +58 -38
  178. package/lib/src/embed/ts-embed.d.ts.map +1 -1
  179. package/lib/src/embed/ts-embed.js +250 -154
  180. package/lib/src/embed/ts-embed.js.map +1 -1
  181. package/lib/src/embed/ts-embed.spec.js +397 -122
  182. package/lib/src/embed/ts-embed.spec.js.map +1 -1
  183. package/lib/src/index.d.ts +2 -1
  184. package/lib/src/index.d.ts.map +1 -1
  185. package/lib/src/index.js.map +1 -1
  186. package/lib/src/react/index.d.ts.map +1 -1
  187. package/lib/src/react/index.js +3 -0
  188. package/lib/src/react/index.js.map +1 -1
  189. package/lib/src/tokenizedFetch.d.ts.map +1 -1
  190. package/lib/src/tokenizedFetch.js +13 -10
  191. package/lib/src/tokenizedFetch.js.map +1 -1
  192. package/lib/src/tokenizedFetch.spec.d.ts +2 -0
  193. package/lib/src/tokenizedFetch.spec.d.ts.map +1 -0
  194. package/lib/src/tokenizedFetch.spec.js +65 -0
  195. package/lib/src/tokenizedFetch.spec.js.map +1 -0
  196. package/lib/src/types.d.ts +309 -40
  197. package/lib/src/types.d.ts.map +1 -1
  198. package/lib/src/types.js +251 -23
  199. package/lib/src/types.js.map +1 -1
  200. package/lib/src/utils/authService/tokenizedAuthService.spec.js +6 -7
  201. package/lib/src/utils/authService/tokenizedAuthService.spec.js.map +1 -1
  202. package/lib/src/utils/logger.js +2 -1
  203. package/lib/src/utils/logger.js.map +1 -1
  204. package/lib/src/utils/logger.spec.d.ts +1 -0
  205. package/lib/src/utils/logger.spec.d.ts.map +1 -1
  206. package/lib/src/utils/logger.spec.js +10 -9
  207. package/lib/src/utils/logger.spec.js.map +1 -1
  208. package/lib/src/utils.d.ts +4 -1
  209. package/lib/src/utils.d.ts.map +1 -1
  210. package/lib/src/utils.js +103 -9
  211. package/lib/src/utils.js.map +1 -1
  212. package/lib/src/utils.spec.js +164 -5
  213. package/lib/src/utils.spec.js.map +1 -1
  214. package/lib/src/visual-embed-sdk.d.ts +702 -80
  215. package/package.json +1 -1
  216. package/src/auth.spec.ts +55 -1
  217. package/src/auth.ts +11 -2
  218. package/src/authToken.ts +2 -2
  219. package/src/css-variables.ts +175 -1
  220. package/src/embed/app.spec.ts +260 -3
  221. package/src/embed/app.ts +127 -7
  222. package/src/embed/auto-frame-renderer.spec.ts +457 -58
  223. package/src/embed/auto-frame-renderer.ts +7 -2
  224. package/src/embed/base.spec.ts +25 -1
  225. package/src/embed/base.ts +19 -5
  226. package/src/embed/bodyless-conversation.spec.ts +93 -0
  227. package/src/embed/conversation.spec.ts +34 -0
  228. package/src/embed/conversation.ts +22 -1
  229. package/src/embed/liveboard.spec.ts +163 -1
  230. package/src/embed/liveboard.ts +106 -10
  231. package/src/embed/spotter-viz-utils.spec.ts +30 -0
  232. package/src/embed/spotter-viz-utils.ts +94 -0
  233. package/src/embed/ts-embed.spec.ts +564 -231
  234. package/src/embed/ts-embed.ts +384 -258
  235. package/src/index.ts +3 -0
  236. package/src/react/index.tsx +3 -0
  237. package/src/tokenizedFetch.spec.ts +81 -0
  238. package/src/tokenizedFetch.ts +14 -11
  239. package/src/types.ts +326 -36
  240. package/src/utils/authService/tokenizedAuthService.spec.ts +6 -6
  241. package/src/utils/logger.spec.ts +11 -9
  242. package/src/utils/logger.ts +2 -2
  243. package/src/utils.spec.ts +200 -4
  244. 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;
@@ -275,6 +275,20 @@ describe('Liveboard/viz embed tests', () => {
275
275
  });
276
276
  });
277
277
 
278
+ test('should disable isWYSIWYGLiveboardPDFEnabled by default in url', async () => {
279
+ const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
280
+ ...defaultViewConfig,
281
+ liveboardId,
282
+ } as LiveboardViewConfig);
283
+ liveboardEmbed.render();
284
+ await executeAfterWait(() => {
285
+ expectUrlMatchesWithParams(
286
+ getIFrameSrc(),
287
+ `http://${thoughtSpotHost}/?embedApp=true${defaultParams}&isWYSIWYGLiveboardPDFEnabled=false${prefixParams}#/embed/viz/${liveboardId}`,
288
+ );
289
+ });
290
+ });
291
+
278
292
  test('should set isLiveboardXLSXCSVDownloadEnabled to true in url', async () => {
279
293
  const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
280
294
  isLiveboardXLSXCSVDownloadEnabled: true,
@@ -1252,6 +1266,35 @@ describe('Liveboard/viz embed tests', () => {
1252
1266
  });
1253
1267
  });
1254
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
+
1255
1298
  test('should render the liveboard embed with updatedSpotterChatPrompt', async () => {
1256
1299
  const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
1257
1300
  ...defaultViewConfig,
@@ -1315,6 +1358,94 @@ describe('Liveboard/viz embed tests', () => {
1315
1358
  });
1316
1359
  });
1317
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
+
1318
1449
  test('SetActiveTab Hostevent should not trigger the navigate event with the correct path, for vizEmbed', async () => {
1319
1450
  const mockProcessTrigger = jest.spyOn(tsEmbed.TsEmbed.prototype, 'trigger');
1320
1451
  const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
@@ -1821,6 +1952,37 @@ describe('Liveboard/viz embed tests', () => {
1821
1952
  addEventListenerSpy.mockRestore();
1822
1953
  });
1823
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
+
1824
1986
  test('should remove window event listeners on destroy when fullHeight and lazyLoadingForFullHeight are enabled', async () => {
1825
1987
  const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener');
1826
1988
 
@@ -1836,7 +1998,7 @@ describe('Liveboard/viz embed tests', () => {
1836
1998
  liveboardEmbed.destroy();
1837
1999
 
1838
2000
  expect(removeEventListenerSpy).toHaveBeenCalledWith('resize', expect.anything());
1839
- expect(removeEventListenerSpy).toHaveBeenCalledWith('scroll', expect.anything());
2001
+ expect(removeEventListenerSpy).toHaveBeenCalledWith('scroll', expect.anything(), true);
1840
2002
 
1841
2003
  removeEventListenerSpy.mockRestore();
1842
2004
  });