@thoughtspot/visual-embed-sdk 1.52.1 → 1.52.2-beta

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 (241) hide show
  1. package/cjs/package.json +11 -4
  2. package/cjs/src/contracts/host-event-contracts.d.ts +49 -11
  3. package/cjs/src/contracts/host-event-contracts.d.ts.map +1 -1
  4. package/cjs/src/embed/app.d.ts +4 -3
  5. package/cjs/src/embed/app.d.ts.map +1 -1
  6. package/cjs/src/embed/app.js +1 -1
  7. package/cjs/src/embed/app.js.map +1 -1
  8. package/cjs/src/embed/app.spec.js +10 -0
  9. package/cjs/src/embed/app.spec.js.map +1 -1
  10. package/cjs/src/embed/auto-frame-renderer.js +1 -1
  11. package/cjs/src/embed/auto-frame-renderer.js.map +1 -1
  12. package/cjs/src/embed/auto-frame-renderer.spec.js +24 -13
  13. package/cjs/src/embed/auto-frame-renderer.spec.js.map +1 -1
  14. package/cjs/src/embed/bodyless-conversation.js +1 -1
  15. package/cjs/src/embed/bodyless-conversation.js.map +1 -1
  16. package/cjs/src/embed/conversation.d.ts +1 -0
  17. package/cjs/src/embed/conversation.d.ts.map +1 -1
  18. package/cjs/src/embed/conversation.js +4 -4
  19. package/cjs/src/embed/conversation.js.map +1 -1
  20. package/cjs/src/embed/conversation.spec.js +35 -24
  21. package/cjs/src/embed/conversation.spec.js.map +1 -1
  22. package/cjs/src/embed/embedConfig.spec.js +2 -1
  23. package/cjs/src/embed/embedConfig.spec.js.map +1 -1
  24. package/cjs/src/embed/hostEventClient/host-event-client.d.ts +2 -2
  25. package/cjs/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
  26. package/cjs/src/embed/hostEventClient/host-event-client.js +16 -10
  27. package/cjs/src/embed/hostEventClient/host-event-client.js.map +1 -1
  28. package/cjs/src/embed/hostEventClient/host-event-client.spec.js +14 -8
  29. package/cjs/src/embed/hostEventClient/host-event-client.spec.js.map +1 -1
  30. package/cjs/src/embed/hostEventClient/utils.d.ts +9 -0
  31. package/cjs/src/embed/hostEventClient/utils.d.ts.map +1 -1
  32. package/cjs/src/embed/hostEventClient/utils.js +49 -3
  33. package/cjs/src/embed/hostEventClient/utils.js.map +1 -1
  34. package/cjs/src/embed/hostEventClient/utils.spec.js +151 -0
  35. package/cjs/src/embed/hostEventClient/utils.spec.js.map +1 -1
  36. package/cjs/src/embed/liveboard.d.ts +4 -3
  37. package/cjs/src/embed/liveboard.d.ts.map +1 -1
  38. package/cjs/src/embed/liveboard.js +18 -13
  39. package/cjs/src/embed/liveboard.js.map +1 -1
  40. package/cjs/src/embed/liveboard.spec.js +2 -1
  41. package/cjs/src/embed/liveboard.spec.js.map +1 -1
  42. package/cjs/src/embed/search-bar.js +1 -1
  43. package/cjs/src/embed/search-bar.js.map +1 -1
  44. package/cjs/src/embed/search.js +1 -1
  45. package/cjs/src/embed/search.js.map +1 -1
  46. package/cjs/src/embed/search.spec.js +20 -0
  47. package/cjs/src/embed/search.spec.js.map +1 -1
  48. package/cjs/src/embed/spotter-utils.d.ts +8 -1
  49. package/cjs/src/embed/spotter-utils.d.ts.map +1 -1
  50. package/cjs/src/embed/spotter-utils.js +14 -1
  51. package/cjs/src/embed/spotter-utils.js.map +1 -1
  52. package/cjs/src/embed/spotter-utils.spec.js +26 -0
  53. package/cjs/src/embed/spotter-utils.spec.js.map +1 -1
  54. package/cjs/src/embed/ts-embed.d.ts +3 -0
  55. package/cjs/src/embed/ts-embed.d.ts.map +1 -1
  56. package/cjs/src/embed/ts-embed.js +108 -29
  57. package/cjs/src/embed/ts-embed.js.map +1 -1
  58. package/cjs/src/embed/ts-embed.spec.js +163 -23
  59. package/cjs/src/embed/ts-embed.spec.js.map +1 -1
  60. package/cjs/src/errors.js +1 -1
  61. package/cjs/src/errors.js.map +1 -1
  62. package/cjs/src/index.d.ts +2 -1
  63. package/cjs/src/index.d.ts.map +1 -1
  64. package/cjs/src/index.js +3 -1
  65. package/cjs/src/index.js.map +1 -1
  66. package/cjs/src/react/all-types-export.d.ts +1 -1
  67. package/cjs/src/react/all-types-export.d.ts.map +1 -1
  68. package/cjs/src/react/all-types-export.js +2 -1
  69. package/cjs/src/react/all-types-export.js.map +1 -1
  70. package/cjs/src/types.d.ts +45 -22
  71. package/cjs/src/types.d.ts.map +1 -1
  72. package/cjs/src/types.js +44 -21
  73. package/cjs/src/types.js.map +1 -1
  74. package/cjs/src/utils/authService/tokenizedAuthService.spec.js +2 -1
  75. package/cjs/src/utils/authService/tokenizedAuthService.spec.js.map +1 -1
  76. package/cjs/src/utils/custom-actions.spec.js +4 -2
  77. package/cjs/src/utils/custom-actions.spec.js.map +1 -1
  78. package/cjs/src/utils/filterConverter.d.ts +106 -0
  79. package/cjs/src/utils/filterConverter.d.ts.map +1 -0
  80. package/cjs/src/utils/filterConverter.js +215 -0
  81. package/cjs/src/utils/filterConverter.js.map +1 -0
  82. package/cjs/src/utils/filterConverter.spec.d.ts +2 -0
  83. package/cjs/src/utils/filterConverter.spec.d.ts.map +1 -0
  84. package/cjs/src/utils/filterConverter.spec.js +709 -0
  85. package/cjs/src/utils/filterConverter.spec.js.map +1 -0
  86. package/cjs/src/utils/processData.spec.js +2 -1
  87. package/cjs/src/utils/processData.spec.js.map +1 -1
  88. package/dist/{index-C1pmYb5H.js → index-CcBYlGo9.js} +1 -1
  89. package/dist/src/contracts/host-event-contracts.d.ts +49 -11
  90. package/dist/src/contracts/host-event-contracts.d.ts.map +1 -1
  91. package/dist/src/embed/app.d.ts +4 -3
  92. package/dist/src/embed/app.d.ts.map +1 -1
  93. package/dist/src/embed/conversation.d.ts +1 -0
  94. package/dist/src/embed/conversation.d.ts.map +1 -1
  95. package/dist/src/embed/hostEventClient/host-event-client.d.ts +2 -2
  96. package/dist/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
  97. package/dist/src/embed/hostEventClient/utils.d.ts +9 -0
  98. package/dist/src/embed/hostEventClient/utils.d.ts.map +1 -1
  99. package/dist/src/embed/liveboard.d.ts +4 -3
  100. package/dist/src/embed/liveboard.d.ts.map +1 -1
  101. package/dist/src/embed/spotter-utils.d.ts +8 -1
  102. package/dist/src/embed/spotter-utils.d.ts.map +1 -1
  103. package/dist/src/embed/ts-embed.d.ts +3 -0
  104. package/dist/src/embed/ts-embed.d.ts.map +1 -1
  105. package/dist/src/index.d.ts +2 -1
  106. package/dist/src/index.d.ts.map +1 -1
  107. package/dist/src/react/all-types-export.d.ts +1 -1
  108. package/dist/src/react/all-types-export.d.ts.map +1 -1
  109. package/dist/src/types.d.ts +45 -22
  110. package/dist/src/types.d.ts.map +1 -1
  111. package/dist/src/utils/filterConverter.d.ts +106 -0
  112. package/dist/src/utils/filterConverter.d.ts.map +1 -0
  113. package/dist/src/utils/filterConverter.spec.d.ts +2 -0
  114. package/dist/src/utils/filterConverter.spec.d.ts.map +1 -0
  115. package/dist/tsembed-react.es.js +257 -87
  116. package/dist/tsembed-react.js +256 -86
  117. package/dist/tsembed.es.js +466 -89
  118. package/dist/tsembed.js +465 -87
  119. package/dist/visual-embed-sdk-react-full.d.ts +202 -40
  120. package/dist/visual-embed-sdk-react.d.ts +108 -40
  121. package/dist/visual-embed-sdk.d.ts +202 -40
  122. package/lib/package.json +11 -4
  123. package/lib/src/contracts/host-event-contracts.d.ts +49 -11
  124. package/lib/src/contracts/host-event-contracts.d.ts.map +1 -1
  125. package/lib/src/embed/app.d.ts +4 -3
  126. package/lib/src/embed/app.d.ts.map +1 -1
  127. package/lib/src/embed/app.js +1 -1
  128. package/lib/src/embed/app.js.map +1 -1
  129. package/lib/src/embed/app.spec.js +10 -0
  130. package/lib/src/embed/app.spec.js.map +1 -1
  131. package/lib/src/embed/auto-frame-renderer.js +1 -1
  132. package/lib/src/embed/auto-frame-renderer.js.map +1 -1
  133. package/lib/src/embed/auto-frame-renderer.spec.js +24 -13
  134. package/lib/src/embed/auto-frame-renderer.spec.js.map +1 -1
  135. package/lib/src/embed/bodyless-conversation.js +1 -1
  136. package/lib/src/embed/bodyless-conversation.js.map +1 -1
  137. package/lib/src/embed/conversation.d.ts +1 -0
  138. package/lib/src/embed/conversation.d.ts.map +1 -1
  139. package/lib/src/embed/conversation.js +5 -5
  140. package/lib/src/embed/conversation.js.map +1 -1
  141. package/lib/src/embed/conversation.spec.js +35 -24
  142. package/lib/src/embed/conversation.spec.js.map +1 -1
  143. package/lib/src/embed/embedConfig.spec.js +2 -1
  144. package/lib/src/embed/embedConfig.spec.js.map +1 -1
  145. package/lib/src/embed/hostEventClient/host-event-client.d.ts +2 -2
  146. package/lib/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
  147. package/lib/src/embed/hostEventClient/host-event-client.js +17 -11
  148. package/lib/src/embed/hostEventClient/host-event-client.js.map +1 -1
  149. package/lib/src/embed/hostEventClient/host-event-client.spec.js +14 -8
  150. package/lib/src/embed/hostEventClient/host-event-client.spec.js.map +1 -1
  151. package/lib/src/embed/hostEventClient/utils.d.ts +9 -0
  152. package/lib/src/embed/hostEventClient/utils.d.ts.map +1 -1
  153. package/lib/src/embed/hostEventClient/utils.js +47 -2
  154. package/lib/src/embed/hostEventClient/utils.js.map +1 -1
  155. package/lib/src/embed/hostEventClient/utils.spec.js +152 -1
  156. package/lib/src/embed/hostEventClient/utils.spec.js.map +1 -1
  157. package/lib/src/embed/liveboard.d.ts +4 -3
  158. package/lib/src/embed/liveboard.d.ts.map +1 -1
  159. package/lib/src/embed/liveboard.js +18 -13
  160. package/lib/src/embed/liveboard.js.map +1 -1
  161. package/lib/src/embed/liveboard.spec.js +2 -1
  162. package/lib/src/embed/liveboard.spec.js.map +1 -1
  163. package/lib/src/embed/search-bar.js +1 -1
  164. package/lib/src/embed/search-bar.js.map +1 -1
  165. package/lib/src/embed/search.js +1 -1
  166. package/lib/src/embed/search.js.map +1 -1
  167. package/lib/src/embed/search.spec.js +20 -0
  168. package/lib/src/embed/search.spec.js.map +1 -1
  169. package/lib/src/embed/spotter-utils.d.ts +8 -1
  170. package/lib/src/embed/spotter-utils.d.ts.map +1 -1
  171. package/lib/src/embed/spotter-utils.js +12 -0
  172. package/lib/src/embed/spotter-utils.js.map +1 -1
  173. package/lib/src/embed/spotter-utils.spec.js +27 -1
  174. package/lib/src/embed/spotter-utils.spec.js.map +1 -1
  175. package/lib/src/embed/ts-embed.d.ts +3 -0
  176. package/lib/src/embed/ts-embed.d.ts.map +1 -1
  177. package/lib/src/embed/ts-embed.js +108 -29
  178. package/lib/src/embed/ts-embed.js.map +1 -1
  179. package/lib/src/embed/ts-embed.spec.js +164 -24
  180. package/lib/src/embed/ts-embed.spec.js.map +1 -1
  181. package/lib/src/errors.js +1 -1
  182. package/lib/src/errors.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 +2 -1
  186. package/lib/src/index.js.map +1 -1
  187. package/lib/src/react/all-types-export.d.ts +1 -1
  188. package/lib/src/react/all-types-export.d.ts.map +1 -1
  189. package/lib/src/react/all-types-export.js +1 -1
  190. package/lib/src/react/all-types-export.js.map +1 -1
  191. package/lib/src/types.d.ts +45 -22
  192. package/lib/src/types.d.ts.map +1 -1
  193. package/lib/src/types.js +44 -21
  194. package/lib/src/types.js.map +1 -1
  195. package/lib/src/utils/authService/tokenizedAuthService.spec.js +2 -1
  196. package/lib/src/utils/authService/tokenizedAuthService.spec.js.map +1 -1
  197. package/lib/src/utils/custom-actions.spec.js +4 -2
  198. package/lib/src/utils/custom-actions.spec.js.map +1 -1
  199. package/lib/src/utils/filterConverter.d.ts +106 -0
  200. package/lib/src/utils/filterConverter.d.ts.map +1 -0
  201. package/lib/src/utils/filterConverter.js +210 -0
  202. package/lib/src/utils/filterConverter.js.map +1 -0
  203. package/lib/src/utils/filterConverter.spec.d.ts +2 -0
  204. package/lib/src/utils/filterConverter.spec.d.ts.map +1 -0
  205. package/lib/src/utils/filterConverter.spec.js +707 -0
  206. package/lib/src/utils/filterConverter.spec.js.map +1 -0
  207. package/lib/src/utils/processData.spec.js +2 -1
  208. package/lib/src/utils/processData.spec.js.map +1 -1
  209. package/lib/src/visual-embed-sdk.d.ts +202 -40
  210. package/package.json +11 -4
  211. package/src/contracts/host-event-contracts.ts +55 -11
  212. package/src/embed/app.spec.ts +15 -0
  213. package/src/embed/app.ts +5 -4
  214. package/src/embed/auto-frame-renderer.spec.ts +24 -13
  215. package/src/embed/auto-frame-renderer.ts +1 -1
  216. package/src/embed/bodyless-conversation.ts +1 -1
  217. package/src/embed/conversation.spec.ts +38 -27
  218. package/src/embed/conversation.ts +5 -5
  219. package/src/embed/embedConfig.spec.ts +2 -1
  220. package/src/embed/hostEventClient/host-event-client.spec.ts +14 -8
  221. package/src/embed/hostEventClient/host-event-client.ts +18 -10
  222. package/src/embed/hostEventClient/utils.spec.ts +176 -1
  223. package/src/embed/hostEventClient/utils.ts +51 -2
  224. package/src/embed/liveboard.spec.ts +4 -3
  225. package/src/embed/liveboard.ts +22 -16
  226. package/src/embed/search-bar.tsx +1 -1
  227. package/src/embed/search.spec.ts +23 -0
  228. package/src/embed/search.ts +1 -1
  229. package/src/embed/spotter-utils.spec.ts +29 -0
  230. package/src/embed/spotter-utils.ts +16 -0
  231. package/src/embed/ts-embed.spec.ts +204 -23
  232. package/src/embed/ts-embed.ts +114 -28
  233. package/src/errors.ts +1 -1
  234. package/src/index.ts +10 -0
  235. package/src/react/all-types-export.ts +4 -0
  236. package/src/types.ts +44 -21
  237. package/src/utils/authService/tokenizedAuthService.spec.ts +2 -1
  238. package/src/utils/custom-actions.spec.ts +4 -2
  239. package/src/utils/filterConverter.spec.ts +801 -0
  240. package/src/utils/filterConverter.ts +304 -0
  241. package/src/utils/processData.spec.ts +2 -1
@@ -10,7 +10,8 @@ import { embedEventStatus } from '../../utils';
10
10
 
11
11
  const isValidApplicability = (a?: { level?: string; targetId?: string }) => {
12
12
  if (isUndefined(a)) return true;
13
- // targetId is not required at LIVEBOARD level, since the filter applies to the whole Liveboard
13
+ // targetId is not required at LIVEBOARD level, since the filter applies to
14
+ // the whole Liveboard
14
15
  return isPlainObject(a)
15
16
  && Object.values(ApplicabilityLevel).includes(a.level as ApplicabilityLevel)
16
17
  && (a.level === ApplicabilityLevel.Liveboard || (isString(a.targetId) && a.targetId.trim().length > 0));
@@ -44,8 +45,56 @@ export function isValidUpdateFiltersPayload(
44
45
  return !!(hasValidFilter || hasValidFilters);
45
46
  }
46
47
 
48
+ /**
49
+ * Rewrites one filter's `columnName`/`operator` to `column`/`oper`.
50
+ *
51
+ * Callers may use either spelling: `columnName`/`operator` match
52
+ * {@link RuntimeFilter} and the `EmbedEvent.FilterChanged` payload, so a
53
+ * converted payload can be replayed as-is. But the payload is forwarded to the
54
+ * embedded app unchanged, and only `column`/`oper` are known to be understood
55
+ * there - so the alias must be swapped here, or it would pass validation and
56
+ * then be silently ignored.
57
+ *
58
+ * If a caller supplies both spellings of a field - which TypeScript rejects,
59
+ * but JavaScript callers can still do - the documented `columnName`/`operator`
60
+ * wins. Deferring to the deprecated spelling would quietly discard the value
61
+ * the rest of the SDK tells people to use.
62
+ * @param filter One entry from the UpdateFilters payload.
63
+ */
64
+ function resolveFilterAliases<T extends { column?: string; columnName?: string; oper?: string; operator?: string }>(
65
+ filter: T,
66
+ ): T {
67
+ if (!isPlainObject(filter)) return filter;
68
+
69
+ const column = filter.columnName ?? filter.column;
70
+ const oper = filter.operator ?? filter.oper;
71
+ const { columnName, operator, ...rest } = filter;
72
+
73
+ return {
74
+ ...rest,
75
+ ...(isUndefined(column) ? {} : { column }),
76
+ ...(isUndefined(oper) ? {} : { oper }),
77
+ } as T;
78
+ }
79
+
80
+ /**
81
+ * Rewrites every filter in an UpdateFilters payload to use `column`/`oper`,
82
+ * whichever spelling the caller supplied. Call after validation.
83
+ * @param payload The UpdateFilters host event payload.
84
+ */
85
+ export function resolveUpdateFiltersAliases<T extends { filter?: any; filters?: any[] }>(payload: T): T {
86
+ if (!isPlainObject(payload)) return payload;
87
+
88
+ return {
89
+ ...payload,
90
+ ...(payload.filter ? { filter: resolveFilterAliases(payload.filter) } : {}),
91
+ ...(Array.isArray(payload.filters) ? { filters: payload.filters.map(resolveFilterAliases) } : {}),
92
+ };
93
+ }
94
+
47
95
  export function isValidUpdateParametersPayload(payload: unknown): boolean {
48
- // Only validates the applicability of each parameter (null treated as absent); the rest is forwarded as-is for backward compatibility.
96
+ // Only validates the applicability of each parameter (null treated as
97
+ // absent); the rest is forwarded as-is for backward compatibility.
49
98
  if (!Array.isArray(payload)) return true;
50
99
  return payload.every((p) => {
51
100
  if (!isPlainObject(p)) return true;
@@ -3054,8 +3054,8 @@ describe('LiveboardEmbed updatedSpotterExperience tests', () => {
3054
3054
 
3055
3055
  describe('trigger typing contracts (compile-time)', () => {
3056
3056
  // Enforced by ts-jest at compile time. LiveboardEmbed OVERRIDES
3057
- // TsEmbed.trigger, so its generic defaults must be asserted separately —
3058
- // a bare `PayloadT` on the override silently collapses no-payload
3057
+ // TsEmbed.trigger, so its generic defaults must be asserted separately
3058
+ // — a bare `PayloadT` on the override silently collapses no-payload
3059
3059
  // responses back to `any` for every LiveboardEmbed consumer.
3060
3060
  type IsAny<T> = 0 extends 1 & T ? true : false;
3061
3061
  type Extends<A, B> = A extends B ? true : false;
@@ -3073,7 +3073,8 @@ describe('LiveboardEmbed updatedSpotterExperience tests', () => {
3073
3073
  { numberOfTabs: number; orderedTabIds: string[] }
3074
3074
  > = true;
3075
3075
  const probeUnknownField = (r: TabsResponse) =>
3076
- // @ts-expect-error — field not on the GetTabs contract (was silent when `any`)
3076
+ // @ts-expect-error — field not on the GetTabs contract (was
3077
+ // silent when `any`)
3077
3078
  r.tabCount;
3078
3079
  void probeUnknownField;
3079
3080
 
@@ -37,8 +37,9 @@ import { SpotterChatViewConfig, StarterPromptsConfig } from './conversation';
37
37
  import { buildStarterPromptsAppInitData } from './spotter-utils';
38
38
  import { SpotterVizConfig, buildSpotterVizAppInitData } from './spotter-viz-utils';
39
39
 
40
- // Home unmounts the liveboard container, which is how its state gets cleared. The
41
- // settle window gives the container time to do it before we ask for the liveboard back.
40
+ // Home unmounts the liveboard container, which is how its state gets cleared.
41
+ // The settle window gives the container time to do it before we ask for the
42
+ // liveboard back.
42
43
  const HOME_ROUTE = 'home';
43
44
  const HOME_SETTLE_MS = 200;
44
45
 
@@ -354,9 +355,10 @@ export interface LiveboardViewConfig
354
355
  */
355
356
  isPNGInScheduledEmailsEnabled?: boolean;
356
357
  /**
357
- * Enables the 'what you see is what you get' PDF export for Liveboards. Each tab is rendered on a single page
358
- * following the exact UI layout, instead of splitting visualizations across multiple A4 pages.
359
- * This feature is GA from SDK version 1.51.0 and ThoughtSpot version 26.8.0.cl.
358
+ * Enables the 'what you see is what you get' PDF export for Liveboards. Each tab is
359
+ * rendered on a single page following the exact UI layout, instead of splitting
360
+ * visualizations across multiple A4 pages. This feature is GA from SDK version
361
+ * 1.51.0 and ThoughtSpot version 26.8.0.cl.
360
362
  *
361
363
  * Supported embed types: `AppEmbed`, `LiveboardEmbed`
362
364
  * @type {boolean}
@@ -597,7 +599,7 @@ export class LiveboardEmbed extends V1Embed {
597
599
  * embedded Liveboard or visualization.
598
600
  */
599
601
  protected getEmbedParams() {
600
- const params = this.getEmbedParamsObject();
602
+ const params = this.getUrlQueryParamsObject();
601
603
  const queryParams = getQueryParamString(params, true);
602
604
  return queryParams;
603
605
  }
@@ -952,22 +954,25 @@ export class LiveboardEmbed extends V1Embed {
952
954
  protected beforePrerenderVisible(): void {
953
955
  super.beforePrerenderVisible();
954
956
 
955
- // Captured before showPreRender() hands the wrapper over to this instance.
956
- // Itself means a hide/show, not a hand-over, so there is nothing stale to clear.
957
+ // Captured before showPreRender() hands the wrapper over to this
958
+ // instance. Itself means a hide/show, not a hand-over, so there is
959
+ // nothing stale to clear.
957
960
  const previous = this.getPreRenderObj<LiveboardEmbed>();
958
961
  const showing = previous === this ? undefined : previous?.currentLiveboardState;
959
962
 
960
963
  this.executeAfterEmbedContainerLoaded(async () => {
961
- // Without this the params callback suspends on its await and Navigate
962
- // goes out first, so the container loads with the previous config's filters.
964
+ // Without this the params callback suspends on its await and
965
+ // Navigate goes out first, so the container loads with the previous
966
+ // config's filters.
963
967
  await this.preRenderParamsApplied;
964
968
  if (this.isShowingLiveboardRoute(showing)) {
965
- // Navigate to the route we are already on is a no-op, so the liveboard
966
- // keeps its state. Home unmounts the container, which clears it.
969
+ // Navigate to the route we are already on is a no-op, so the
970
+ // liveboard keeps its state. Home unmounts the container, which
971
+ // clears it.
967
972
  this.trigger(HostEvent.Navigate, HOME_ROUTE)
968
973
  .catch((error) => logger.warn('Could not route via home', error));
969
- // Not awaited above: the container never acks Navigate, so that promise
970
- // only settles on the 30s trigger timeout.
974
+ // Not awaited above: the container never acks Navigate, so
975
+ // that promise only settles on the 30s trigger timeout.
971
976
  await new Promise((resolve) => {
972
977
  setTimeout(resolve, HOME_SETTLE_MS);
973
978
  });
@@ -987,8 +992,9 @@ export class LiveboardEmbed extends V1Embed {
987
992
  });
988
993
  }
989
994
 
990
- // The whole route, not just the liveboard id: the path is built from the viz, tab
991
- // and view too, so a same-liveboard show onto another tab is a real navigation.
995
+ // The whole route, not just the liveboard id: the path is built from the
996
+ // viz, tab and view too, so a same-liveboard show onto another tab is a
997
+ // real navigation.
992
998
  private isShowingLiveboardRoute(showing?: LiveboardEmbed['currentLiveboardState']): boolean {
993
999
  if (!showing?.liveboardId) return false;
994
1000
  const {
@@ -163,7 +163,7 @@ export class SearchBarEmbed extends TsEmbed {
163
163
  * @param dataSources A list of data source GUIDs
164
164
  */
165
165
  private getIFrameSrc() {
166
- const queryParams = this.getEmbedParamsObject();
166
+ const queryParams = this.getUrlQueryParamsObject();
167
167
  const path = 'search-bar-embed';
168
168
 
169
169
  let query = '';
@@ -854,6 +854,29 @@ test('should pass forceTable parameter when forceTable is true', async () => {
854
854
  });
855
855
 
856
856
  describe('SearchBarEmbed tests', () => {
857
+ test('should keep view config off the URL when excludeConfigFromURL is set', async () => {
858
+ const searchBarEmbed = new SearchBarEmbed(getRootEl() as any, {
859
+ ...defaultViewConfig,
860
+ dataSources: ['source-1', 'source-2'],
861
+ additionalFlags: { excludeConfigFromURL: true },
862
+ } as any);
863
+ searchBarEmbed.render();
864
+ await executeAfterWait(() => {
865
+ const iframeSrc = getIFrameSrc();
866
+
867
+ // Bootstrap params stay: the handshake, the embed marker that
868
+ // picks the app shell, and the data sources it boots against.
869
+ expect(iframeSrc).toContain('hostAppUrl=');
870
+ expect(iframeSrc).toContain('isSearchEmbed=true');
871
+ expect(iframeSrc).toContain('dataSources');
872
+
873
+ // The rest of the view config goes over
874
+ // HostEvent.UpdateEmbedParams.
875
+ expect(iframeSrc).not.toContain('hideAction');
876
+ expect(iframeSrc).not.toContain('useLastSelectedDataSource');
877
+ });
878
+ });
879
+
857
880
  test('should pass dataSources parameter when dataSources array is provided', async () => {
858
881
  const searchBarEmbed = new SearchBarEmbed(getRootEl() as any, {
859
882
  ...defaultViewConfig,
@@ -546,7 +546,7 @@ export class SearchEmbed extends TsEmbed {
546
546
  excludeRuntimeParametersfromURL,
547
547
  excludeRuntimeFiltersfromURL,
548
548
  } = this.viewConfig;
549
- const queryParams = this.getEmbedParamsObject();
549
+ const queryParams = this.getUrlQueryParamsObject();
550
550
  let query = '';
551
551
  const queryParamsString = getQueryParamString(queryParams, true);
552
552
  if (queryParamsString) {
@@ -3,6 +3,7 @@ import {
3
3
  buildSpotterSidebarAppInitData,
4
4
  buildSpotterShareConversationAppInitData,
5
5
  buildStarterPromptsAppInitData,
6
+ buildSpotterAnalystAppInitData,
6
7
  } from './spotter-utils';
7
8
  import { ErrorDetailsTypes, EmbedErrorCodes } from '../types';
8
9
  import { ERROR_MESSAGE } from '../errors';
@@ -245,3 +246,31 @@ describe('buildStarterPromptsAppInitData', () => {
245
246
  expect(result.embedParams?.starterPrompts).toEqual({ enable: true, liveboard });
246
247
  });
247
248
  });
249
+
250
+ describe('buildSpotterAnalystAppInitData', () => {
251
+ const base = { type: 'APP_INIT' } as any;
252
+
253
+ it('returns the payload unchanged when analystId is absent', () => {
254
+ expect(buildSpotterAnalystAppInitData(base, {})).toBe(base);
255
+ expect(buildSpotterAnalystAppInitData(base, { spotterAnalystConfig: {} })).toBe(base);
256
+ });
257
+
258
+ it('nests spotterAnalystConfig under embedParams', () => {
259
+ const result = buildSpotterAnalystAppInitData(base, {
260
+ spotterAnalystConfig: { analystId: 'analyst-id-1234' },
261
+ });
262
+ expect(result.embedParams?.spotterAnalystConfig).toEqual({ analystId: 'analyst-id-1234' });
263
+ });
264
+
265
+ it('preserves existing embedParams keys', () => {
266
+ const withExisting = {
267
+ ...base,
268
+ embedParams: { spotterSidebarConfig: { enablePastConversationsSidebar: true } },
269
+ } as any;
270
+ const result = buildSpotterAnalystAppInitData(withExisting, {
271
+ spotterAnalystConfig: { analystId: 'analyst-id-1234' },
272
+ });
273
+ expect(result.embedParams?.spotterSidebarConfig).toEqual({ enablePastConversationsSidebar: true });
274
+ expect(result.embedParams?.spotterAnalystConfig).toEqual({ analystId: 'analyst-id-1234' });
275
+ });
276
+ });
@@ -6,6 +6,7 @@ import type {
6
6
  SpotterShareConversationConfig,
7
7
  SpotterChatViewConfig,
8
8
  StarterPromptsConfig,
9
+ SpotterAnalystConfig,
9
10
  } from './conversation';
10
11
  import type { VisualizationOverrides } from '../types';
11
12
 
@@ -114,3 +115,18 @@ export function buildStarterPromptsAppInitData<T extends DefaultAppInitData>(
114
115
  },
115
116
  };
116
117
  }
118
+
119
+ export function buildSpotterAnalystAppInitData<T extends DefaultAppInitData>(
120
+ initData: T,
121
+ viewConfig: { spotterAnalystConfig?: SpotterAnalystConfig },
122
+ ): T & { embedParams?: { spotterAnalystConfig?: SpotterAnalystConfig } } {
123
+ const { spotterAnalystConfig } = viewConfig;
124
+ if (!spotterAnalystConfig?.analystId) return initData;
125
+ return {
126
+ ...initData,
127
+ embedParams: {
128
+ ...((initData as T & { embedParams?: Record<string, unknown> }).embedParams || {}),
129
+ spotterAnalystConfig,
130
+ },
131
+ };
132
+ }
@@ -2964,8 +2964,8 @@ describe('Unit test case for ts embed', () => {
2964
2964
 
2965
2965
  // Reverts to the exact value the container had before.
2966
2966
  expect(customContainer.style.position).toBe('static');
2967
- // The custom container reference is dropped (back to the default
2968
- // body) so a destroyed embed does not pin the (possibly
2967
+ // The custom container reference is dropped (back to the
2968
+ // default body) so a destroyed embed does not pin the (possibly
2969
2969
  // detached) element in memory.
2970
2970
  expect((libEmbed as any).preRenderContainerEl).toBe(document.body);
2971
2971
 
@@ -3019,8 +3019,8 @@ describe('Unit test case for ts embed', () => {
3019
3019
 
3020
3020
  libEmbed.destroy();
3021
3021
 
3022
- // The body sentinel must never be treated as a custom container:
3023
- // no position override to apply and none to restore.
3022
+ // The body sentinel must never be treated as a custom
3023
+ // container: no position override to apply and none to restore.
3024
3024
  expect(document.body.style.position).toBe(originalBodyPosition);
3025
3025
  expect(document.body.dataset.tsEmbedOriginalPosition).toBeUndefined();
3026
3026
  });
@@ -3155,10 +3155,11 @@ describe('Unit test case for ts embed', () => {
3155
3155
 
3156
3156
  libEmbed.syncPreRenderStyle();
3157
3157
 
3158
- // applyPreRenderContainerPositioning() reads preRenderContainerEl
3159
- // instead of taking the container as an argument, so reconcile has
3160
- // to assign the field before calling it — otherwise the override
3161
- // lands on the detached node and the fresh one stays static.
3158
+ // applyPreRenderContainerPositioning() reads
3159
+ // preRenderContainerEl instead of taking the container as an
3160
+ // argument, so reconcile has to assign the field before calling
3161
+ // it — otherwise the override lands on the detached node and
3162
+ // the fresh one stays static.
3162
3163
  expect((libEmbed as any).preRenderContainerEl).toBe(newContainer);
3163
3164
  expect(newContainer.style.position).toBe('relative');
3164
3165
  expect(newContainer.dataset.tsEmbedOriginalPosition).toBe('static');
@@ -3289,7 +3290,8 @@ describe('Unit test case for ts embed', () => {
3289
3290
  await firstEmbed.preRender();
3290
3291
  await waitFor(() => !!getIFrameEl());
3291
3292
 
3292
- // Reset position to static so the connecting embed also triggers the guard.
3293
+ // Reset position to static so the connecting embed also
3294
+ // triggers the guard.
3293
3295
  customContainer.style.position = 'static';
3294
3296
  delete customContainer.dataset['preRenderContainerOriginalPosition'];
3295
3297
 
@@ -3393,8 +3395,8 @@ describe('Unit test case for ts embed', () => {
3393
3395
  await firstEmbed.preRender();
3394
3396
  await waitFor(() => !!getIFrameEl());
3395
3397
 
3396
- // Same config on both components — the value is redundant but
3397
- // not wrong, so nagging about it would be noise.
3398
+ // Same config on both components — the value is redundant
3399
+ // but not wrong, so nagging about it would be noise.
3398
3400
  const connectingEmbed = new LiveboardEmbed('#tsEmbedDiv', {
3399
3401
  preRenderId: 'connect-same-element',
3400
3402
  liveboardId: 'myLiveboardId',
@@ -3427,8 +3429,9 @@ describe('Unit test case for ts embed', () => {
3427
3429
  await firstEmbed.preRender();
3428
3430
  await waitFor(() => !!getIFrameEl());
3429
3431
 
3430
- // A selector that resolves to the owner's element is the same
3431
- // container, so the comparison must be by resolved node.
3432
+ // A selector that resolves to the owner's element is the
3433
+ // same container, so the comparison must be by resolved
3434
+ // node.
3432
3435
  const connectingEmbed = new LiveboardEmbed('#tsEmbedDiv', {
3433
3436
  preRenderId: 'connect-same-selector',
3434
3437
  liveboardId: 'myLiveboardId',
@@ -3492,8 +3495,8 @@ describe('Unit test case for ts embed', () => {
3492
3495
  await waitFor(() => !!getIFrameEl());
3493
3496
 
3494
3497
  // Simulate a wrapper with no live embed behind it: the
3495
- // __tsEmbed stamp used to locate the owner is gone, so there
3496
- // is no container to inherit.
3498
+ // __tsEmbed stamp used to locate the owner is gone, so
3499
+ // there is no container to inherit.
3497
3500
  const wrapper = document.getElementById(
3498
3501
  firstEmbed.getPreRenderIds().wrapper,
3499
3502
  );
@@ -5361,12 +5364,14 @@ describe('Trigger method edge cases', () => {
5361
5364
  const handleErrorSpy = jest.spyOn(searchEmbed as any, 'handleError').mockImplementation(() => {});
5362
5365
  await searchEmbed.render();
5363
5366
 
5364
- // First processTrigger call is getAvailableUIPassthroughKeys for DrillDown
5367
+ // First processTrigger call is getAvailableUIPassthroughKeys for
5368
+ // DrillDown
5365
5369
  mockProcessTrigger.mockResolvedValueOnce([
5366
5370
  { value: { keys: Object.values(UIPassthroughEvent) } },
5367
5371
  ]);
5368
5372
 
5369
- // DrillDown with no points → throwDrillDownValidationError → isValidationError=true
5373
+ // DrillDown with no points → throwDrillDownValidationError →
5374
+ // isValidationError=true
5370
5375
  await expect(
5371
5376
  searchEmbed.trigger(HostEvent.DrillDown, {} as any),
5372
5377
  ).rejects.toThrow('DrillDown requires a valid points object');
@@ -5541,7 +5546,8 @@ describe('Fullscreen change handler behavior', () => {
5541
5546
  init({
5542
5547
  thoughtSpotHost: 'tshost',
5543
5548
  authType: AuthType.None,
5544
- // disableFullscreenPresentation not set → defaults to true via ?? true
5549
+ // disableFullscreenPresentation not set → defaults to true via ??
5550
+ // true
5545
5551
  });
5546
5552
 
5547
5553
  const embed = new SearchEmbed(getRootEl(), defaultViewConfig);
@@ -5567,7 +5573,8 @@ describe('Fullscreen change handler behavior', () => {
5567
5573
 
5568
5574
  embed['setupFullscreenChangeHandler']();
5569
5575
 
5570
- // Capture the registered handler directly instead of dispatching to document
5576
+ // Capture the registered handler directly instead of dispatching to
5577
+ // document
5571
5578
  const fullscreenCall = addEventListenerSpy.mock.calls.find((c) => c[0] === 'fullscreenchange');
5572
5579
  expect(fullscreenCall).toBeTruthy();
5573
5580
  const handler = fullscreenCall[1] as EventListener;
@@ -6039,8 +6046,8 @@ describe('ShowPreRender with UpdateEmbedParams', () => {
6039
6046
  undefined,
6040
6047
  );
6041
6048
 
6042
- // A parameter always carries a value, so the payload says everything
6043
- // there is to say — no follow-up event.
6049
+ // A parameter always carries a value, so the payload says
6050
+ // everything there is to say — no follow-up event.
6044
6051
  const parameterCalls = mockProcessTrigger.mock.calls.filter(
6045
6052
  (call: any[]) => call[1] === HostEvent.UpdateParameters,
6046
6053
  );
@@ -6641,7 +6648,8 @@ describe('ShowPreRender with UpdateEmbedParams', () => {
6641
6648
  expect(hostElement.querySelector(`#${placeHolderId}`)).toBe(staleNestedPlaceholder);
6642
6649
  // Before the fix this threw NotFoundError.
6643
6650
  await expect(embed.showPreRender()).resolves.toBeDefined();
6644
- // The stale nested placeholder is gone; the fresh one is a direct child.
6651
+ // The stale nested placeholder is gone; the fresh one is a direct
6652
+ // child.
6645
6653
  expect(staleNestedPlaceholder.isConnected).toBe(false);
6646
6654
  const currentPlaceholder = (
6647
6655
  embed as any
@@ -6673,7 +6681,8 @@ describe('ShowPreRender with UpdateEmbedParams', () => {
6673
6681
  { numberOfTabs: number; orderedTabIds: string[] }
6674
6682
  > = true;
6675
6683
  const probeUnknownField = (r: TabsResponse) =>
6676
- // @ts-expect-error — field not on the GetTabs contract (was silent when `any`)
6684
+ // @ts-expect-error — field not on the GetTabs contract (was
6685
+ // silent when `any`)
6677
6686
  r.tabCount;
6678
6687
  void probeUnknownField;
6679
6688
 
@@ -6686,3 +6695,175 @@ describe('ShowPreRender with UpdateEmbedParams', () => {
6686
6695
  });
6687
6696
  });
6688
6697
  });
6698
+
6699
+ describe('excludeConfigFromURL', () => {
6700
+ const lbConfig = {
6701
+ liveboardId,
6702
+ hiddenActions: [Action.Download],
6703
+ additionalFlags: { internalBlinkFlag: true },
6704
+ liveboardV2: true,
6705
+ };
6706
+
6707
+ const withPostMessage = (viewConfig: any) => ({
6708
+ ...viewConfig,
6709
+ additionalFlags: { ...viewConfig.additionalFlags, excludeConfigFromURL: true },
6710
+ });
6711
+
6712
+ const renderAndGetSrc = async (viewConfig: any) => {
6713
+ const embed = new LiveboardEmbed(getRootEl(), { ...defaultViewConfig, ...viewConfig });
6714
+ await embed.render();
6715
+ await waitFor(() => !!getIFrameEl());
6716
+ return { embed, src: getIFrameSrc() };
6717
+ };
6718
+
6719
+ const signalFrameReady = () => {
6720
+ postMessageToParent(getIFrameEl().contentWindow, {
6721
+ type: EmbedEvent.EmbedListenerReady,
6722
+ });
6723
+ };
6724
+
6725
+ beforeEach(() => {
6726
+ document.body.innerHTML = getDocumentBody();
6727
+ mockProcessTrigger.mockReset();
6728
+ mockProcessTrigger.mockResolvedValue({});
6729
+ init({
6730
+ thoughtSpotHost,
6731
+ authType: AuthType.None,
6732
+ });
6733
+ });
6734
+
6735
+ test('leaves the URL untouched when the flag is not set', async () => {
6736
+ const { src } = await renderAndGetSrc(lbConfig);
6737
+
6738
+ expect(src).toContain('hideAction=');
6739
+ expect(src).toContain('internalBlinkFlag=true');
6740
+ expect(src).toContain('isPinboardV2Enabled=true');
6741
+ });
6742
+
6743
+ test('keeps only the bootstrap params on the URL when the flag is set', async () => {
6744
+ const { src } = await renderAndGetSrc(withPostMessage(lbConfig));
6745
+
6746
+ // Boot and auth handshake stays on the URL.
6747
+ expect(src).toContain('hostAppUrl=');
6748
+ expect(src).toContain(`authType=${AuthType.None}`);
6749
+ expect(src).toContain(`sdkVersion=${version}`);
6750
+ expect(src).toContain('blockNonEmbedFullAppAccess=true');
6751
+ expect(src).toContain('viewPortHeight=');
6752
+
6753
+ // View configuration and internal flags do not.
6754
+ expect(src).not.toContain('hideAction=');
6755
+ expect(src).not.toContain('internalBlinkFlag');
6756
+ expect(src).not.toContain('isPinboardV2Enabled');
6757
+ expect(src).not.toContain('enableDataPanelV2');
6758
+ });
6759
+
6760
+ test('strips a non-bootstrap additionalFlag from the URL when the flag is set', async () => {
6761
+ const { src } = await renderAndGetSrc({
6762
+ ...lbConfig,
6763
+ additionalFlags: {
6764
+ internalBlinkFlag: true,
6765
+ someCustomFlag: 'abc',
6766
+ excludeConfigFromURL: true,
6767
+ },
6768
+ });
6769
+
6770
+ expect(src).not.toContain('internalBlinkFlag');
6771
+ expect(src).not.toContain('someCustomFlag');
6772
+ });
6773
+
6774
+ test('keeps an additionalFlag that overrides a bootstrap param on the URL', async () => {
6775
+ const { src } = await renderAndGetSrc({
6776
+ ...lbConfig,
6777
+ additionalFlags: { [Param.OverrideOrgId]: 42, excludeConfigFromURL: true },
6778
+ });
6779
+
6780
+ expect(src).toContain(`${Param.OverrideOrgId}=42`);
6781
+ });
6782
+
6783
+ test('keeps the deep-link route on the URL when the flag is set', async () => {
6784
+ const { src } = await renderAndGetSrc(withPostMessage(lbConfig));
6785
+
6786
+ expect(src).toContain(`/embed/viz/${liveboardId}`);
6787
+ });
6788
+
6789
+ test('sends the full config over UpdateEmbedParams once the frame is ready', async () => {
6790
+ await renderAndGetSrc(withPostMessage(lbConfig));
6791
+
6792
+ signalFrameReady();
6793
+
6794
+ await executeAfterWait(() => {
6795
+ expect(mockProcessTrigger).toHaveBeenCalledWith(
6796
+ expect.any(Object),
6797
+ HostEvent.UpdateEmbedParams,
6798
+ expect.any(String),
6799
+ expect.objectContaining({
6800
+ liveboardId,
6801
+ hiddenActions: [Action.Download],
6802
+ internalBlinkFlag: true,
6803
+ [Param.HideActions]: expect.arrayContaining([Action.Download]),
6804
+ }),
6805
+ undefined,
6806
+ );
6807
+ });
6808
+ });
6809
+
6810
+ test('sends UpdateEmbedParams again every time the frame signals ready', async () => {
6811
+ await renderAndGetSrc(withPostMessage(lbConfig));
6812
+ signalFrameReady();
6813
+ signalFrameReady();
6814
+
6815
+ await executeAfterWait(() => {
6816
+ const updateCalls = mockProcessTrigger.mock.calls.filter(
6817
+ (call) => call[1] === HostEvent.UpdateEmbedParams,
6818
+ );
6819
+ expect(updateCalls).toHaveLength(2);
6820
+ });
6821
+ });
6822
+
6823
+ test('does not send UpdateEmbedParams when the flag is not set', async () => {
6824
+ await renderAndGetSrc(lbConfig);
6825
+
6826
+ signalFrameReady();
6827
+
6828
+ await executeAfterWait(() => {
6829
+ const updateCalls = mockProcessTrigger.mock.calls.filter(
6830
+ (call) => call[1] === HostEvent.UpdateEmbedParams,
6831
+ );
6832
+ expect(updateCalls).toHaveLength(0);
6833
+ });
6834
+ });
6835
+
6836
+ test('does not double-send for a pre-rendered embed that is shown', async () => {
6837
+ mockMessageChannel();
6838
+ (window as any).ResizeObserver =
6839
+ window.ResizeObserver ||
6840
+ jest.fn().mockImplementation(() => ({
6841
+ disconnect: jest.fn(),
6842
+ observe: jest.fn(),
6843
+ unobserve: jest.fn(),
6844
+ }));
6845
+
6846
+ const embed = new LiveboardEmbed(getRootEl(), {
6847
+ ...defaultViewConfig,
6848
+ ...withPostMessage(lbConfig),
6849
+ preRenderId: 'send-config-post-message',
6850
+ });
6851
+ await embed.preRender();
6852
+ await waitFor(() => !!getIFrameEl());
6853
+
6854
+ signalFrameReady();
6855
+ await executeAfterWait(() => {
6856
+ expect(embed.isEmbedContainerLoaded).toBe(true);
6857
+ });
6858
+
6859
+ mockProcessTrigger.mockClear();
6860
+ await embed.showPreRender();
6861
+
6862
+ await executeAfterWait(() => {
6863
+ const updateCalls = mockProcessTrigger.mock.calls.filter(
6864
+ (call) => call[1] === HostEvent.UpdateEmbedParams,
6865
+ );
6866
+ expect(updateCalls).toHaveLength(1);
6867
+ });
6868
+ });
6869
+ });