@thoughtspot/visual-embed-sdk 1.50.0 → 1.50.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 (188) hide show
  1. package/cjs/package.json +1 -1
  2. package/cjs/src/css-variables.d.ts +59 -0
  3. package/cjs/src/css-variables.d.ts.map +1 -1
  4. package/cjs/src/embed/app.d.ts +31 -2
  5. package/cjs/src/embed/app.d.ts.map +1 -1
  6. package/cjs/src/embed/app.js +13 -2
  7. package/cjs/src/embed/app.js.map +1 -1
  8. package/cjs/src/embed/app.spec.js +101 -0
  9. package/cjs/src/embed/app.spec.js.map +1 -1
  10. package/cjs/src/embed/conversation.d.ts +26 -0
  11. package/cjs/src/embed/conversation.d.ts.map +1 -1
  12. package/cjs/src/embed/conversation.js +12 -2
  13. package/cjs/src/embed/conversation.js.map +1 -1
  14. package/cjs/src/embed/conversation.spec.js +23 -0
  15. package/cjs/src/embed/conversation.spec.js.map +1 -1
  16. package/cjs/src/embed/events.spec.js +593 -0
  17. package/cjs/src/embed/events.spec.js.map +1 -1
  18. package/cjs/src/embed/host-events.spec.js +106 -0
  19. package/cjs/src/embed/host-events.spec.js.map +1 -1
  20. package/cjs/src/embed/hostEventClient/contracts.d.ts +36 -14
  21. package/cjs/src/embed/hostEventClient/contracts.d.ts.map +1 -1
  22. package/cjs/src/embed/hostEventClient/contracts.js +10 -1
  23. package/cjs/src/embed/hostEventClient/contracts.js.map +1 -1
  24. package/cjs/src/embed/hostEventClient/host-event-client.d.ts +1 -0
  25. package/cjs/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
  26. package/cjs/src/embed/hostEventClient/host-event-client.js +8 -0
  27. package/cjs/src/embed/hostEventClient/host-event-client.js.map +1 -1
  28. package/cjs/src/embed/hostEventClient/host-event-client.spec.js +209 -0
  29. package/cjs/src/embed/hostEventClient/host-event-client.spec.js.map +1 -1
  30. package/cjs/src/embed/hostEventClient/utils.d.ts +2 -0
  31. package/cjs/src/embed/hostEventClient/utils.d.ts.map +1 -1
  32. package/cjs/src/embed/hostEventClient/utils.js +32 -2
  33. package/cjs/src/embed/hostEventClient/utils.js.map +1 -1
  34. package/cjs/src/embed/hostEventClient/utils.spec.js +178 -0
  35. package/cjs/src/embed/hostEventClient/utils.spec.js.map +1 -1
  36. package/cjs/src/embed/liveboard.d.ts +6 -0
  37. package/cjs/src/embed/liveboard.d.ts.map +1 -1
  38. package/cjs/src/embed/liveboard.js.map +1 -1
  39. package/cjs/src/embed/spotter-viz-utils.d.ts +34 -0
  40. package/cjs/src/embed/spotter-viz-utils.d.ts.map +1 -1
  41. package/cjs/src/embed/spotter-viz-utils.js.map +1 -1
  42. package/cjs/src/embed/spotter-viz-utils.spec.js +17 -0
  43. package/cjs/src/embed/spotter-viz-utils.spec.js.map +1 -1
  44. package/cjs/src/embed/ts-embed.d.ts.map +1 -1
  45. package/cjs/src/embed/ts-embed.js +14 -7
  46. package/cjs/src/embed/ts-embed.js.map +1 -1
  47. package/cjs/src/embed/ts-embed.spec.js +146 -0
  48. package/cjs/src/embed/ts-embed.spec.js.map +1 -1
  49. package/cjs/src/errors.d.ts +1 -0
  50. package/cjs/src/errors.d.ts.map +1 -1
  51. package/cjs/src/errors.js +1 -0
  52. package/cjs/src/errors.js.map +1 -1
  53. package/cjs/src/index.d.ts +2 -2
  54. package/cjs/src/index.d.ts.map +1 -1
  55. package/cjs/src/index.js +3 -2
  56. package/cjs/src/index.js.map +1 -1
  57. package/cjs/src/react/util.spec.js +251 -0
  58. package/cjs/src/react/util.spec.js.map +1 -1
  59. package/cjs/src/types.d.ts +119 -2
  60. package/cjs/src/types.d.ts.map +1 -1
  61. package/cjs/src/types.js +97 -0
  62. package/cjs/src/types.js.map +1 -1
  63. package/cjs/src/utils/processData.spec.js +93 -0
  64. package/cjs/src/utils/processData.spec.js.map +1 -1
  65. package/cjs/src/utils/processTrigger.spec.js +61 -0
  66. package/cjs/src/utils/processTrigger.spec.js.map +1 -1
  67. package/dist/{index-B5xoN6Cp.js → index-fCne7kmU.js} +1 -1
  68. package/dist/src/css-variables.d.ts +59 -0
  69. package/dist/src/css-variables.d.ts.map +1 -1
  70. package/dist/src/embed/app.d.ts +31 -2
  71. package/dist/src/embed/app.d.ts.map +1 -1
  72. package/dist/src/embed/conversation.d.ts +26 -0
  73. package/dist/src/embed/conversation.d.ts.map +1 -1
  74. package/dist/src/embed/hostEventClient/contracts.d.ts +36 -14
  75. package/dist/src/embed/hostEventClient/contracts.d.ts.map +1 -1
  76. package/dist/src/embed/hostEventClient/host-event-client.d.ts +1 -0
  77. package/dist/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
  78. package/dist/src/embed/hostEventClient/utils.d.ts +2 -0
  79. package/dist/src/embed/hostEventClient/utils.d.ts.map +1 -1
  80. package/dist/src/embed/liveboard.d.ts +6 -0
  81. package/dist/src/embed/liveboard.d.ts.map +1 -1
  82. package/dist/src/embed/spotter-viz-utils.d.ts +34 -0
  83. package/dist/src/embed/spotter-viz-utils.d.ts.map +1 -1
  84. package/dist/src/embed/ts-embed.d.ts.map +1 -1
  85. package/dist/src/errors.d.ts +1 -0
  86. package/dist/src/errors.d.ts.map +1 -1
  87. package/dist/src/index.d.ts +2 -2
  88. package/dist/src/index.d.ts.map +1 -1
  89. package/dist/src/types.d.ts +119 -2
  90. package/dist/src/types.d.ts.map +1 -1
  91. package/dist/tsembed-react.es.js +389 -84
  92. package/dist/tsembed-react.js +388 -83
  93. package/dist/tsembed.es.js +390 -85
  94. package/dist/tsembed.js +388 -83
  95. package/dist/visual-embed-sdk-react-full.d.ts +302 -17
  96. package/dist/visual-embed-sdk-react.d.ts +302 -17
  97. package/dist/visual-embed-sdk.d.ts +306 -17
  98. package/lib/package.json +1 -1
  99. package/lib/src/css-variables.d.ts +59 -0
  100. package/lib/src/css-variables.d.ts.map +1 -1
  101. package/lib/src/embed/app.d.ts +31 -2
  102. package/lib/src/embed/app.d.ts.map +1 -1
  103. package/lib/src/embed/app.js +13 -2
  104. package/lib/src/embed/app.js.map +1 -1
  105. package/lib/src/embed/app.spec.js +101 -0
  106. package/lib/src/embed/app.spec.js.map +1 -1
  107. package/lib/src/embed/conversation.d.ts +26 -0
  108. package/lib/src/embed/conversation.d.ts.map +1 -1
  109. package/lib/src/embed/conversation.js +11 -1
  110. package/lib/src/embed/conversation.js.map +1 -1
  111. package/lib/src/embed/conversation.spec.js +24 -1
  112. package/lib/src/embed/conversation.spec.js.map +1 -1
  113. package/lib/src/embed/events.spec.js +593 -0
  114. package/lib/src/embed/events.spec.js.map +1 -1
  115. package/lib/src/embed/host-events.spec.js +107 -1
  116. package/lib/src/embed/host-events.spec.js.map +1 -1
  117. package/lib/src/embed/hostEventClient/contracts.d.ts +36 -14
  118. package/lib/src/embed/hostEventClient/contracts.d.ts.map +1 -1
  119. package/lib/src/embed/hostEventClient/contracts.js +9 -0
  120. package/lib/src/embed/hostEventClient/contracts.js.map +1 -1
  121. package/lib/src/embed/hostEventClient/host-event-client.d.ts +1 -0
  122. package/lib/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
  123. package/lib/src/embed/hostEventClient/host-event-client.js +9 -1
  124. package/lib/src/embed/hostEventClient/host-event-client.js.map +1 -1
  125. package/lib/src/embed/hostEventClient/host-event-client.spec.js +209 -0
  126. package/lib/src/embed/hostEventClient/host-event-client.spec.js.map +1 -1
  127. package/lib/src/embed/hostEventClient/utils.d.ts +2 -0
  128. package/lib/src/embed/hostEventClient/utils.d.ts.map +1 -1
  129. package/lib/src/embed/hostEventClient/utils.js +28 -1
  130. package/lib/src/embed/hostEventClient/utils.js.map +1 -1
  131. package/lib/src/embed/hostEventClient/utils.spec.js +179 -1
  132. package/lib/src/embed/hostEventClient/utils.spec.js.map +1 -1
  133. package/lib/src/embed/liveboard.d.ts +6 -0
  134. package/lib/src/embed/liveboard.d.ts.map +1 -1
  135. package/lib/src/embed/liveboard.js.map +1 -1
  136. package/lib/src/embed/spotter-viz-utils.d.ts +34 -0
  137. package/lib/src/embed/spotter-viz-utils.d.ts.map +1 -1
  138. package/lib/src/embed/spotter-viz-utils.js.map +1 -1
  139. package/lib/src/embed/spotter-viz-utils.spec.js +17 -0
  140. package/lib/src/embed/spotter-viz-utils.spec.js.map +1 -1
  141. package/lib/src/embed/ts-embed.d.ts.map +1 -1
  142. package/lib/src/embed/ts-embed.js +14 -7
  143. package/lib/src/embed/ts-embed.js.map +1 -1
  144. package/lib/src/embed/ts-embed.spec.js +146 -0
  145. package/lib/src/embed/ts-embed.spec.js.map +1 -1
  146. package/lib/src/errors.d.ts +1 -0
  147. package/lib/src/errors.d.ts.map +1 -1
  148. package/lib/src/errors.js +1 -0
  149. package/lib/src/errors.js.map +1 -1
  150. package/lib/src/index.d.ts +2 -2
  151. package/lib/src/index.d.ts.map +1 -1
  152. package/lib/src/index.js +2 -2
  153. package/lib/src/index.js.map +1 -1
  154. package/lib/src/react/util.spec.js +251 -0
  155. package/lib/src/react/util.spec.js.map +1 -1
  156. package/lib/src/types.d.ts +119 -2
  157. package/lib/src/types.d.ts.map +1 -1
  158. package/lib/src/types.js +97 -0
  159. package/lib/src/types.js.map +1 -1
  160. package/lib/src/utils/processData.spec.js +93 -0
  161. package/lib/src/utils/processData.spec.js.map +1 -1
  162. package/lib/src/utils/processTrigger.spec.js +61 -0
  163. package/lib/src/utils/processTrigger.spec.js.map +1 -1
  164. package/lib/src/visual-embed-sdk.d.ts +306 -17
  165. package/package.json +1 -1
  166. package/src/css-variables.ts +73 -0
  167. package/src/embed/app.spec.ts +140 -0
  168. package/src/embed/app.ts +38 -2
  169. package/src/embed/conversation.spec.ts +32 -1
  170. package/src/embed/conversation.ts +29 -0
  171. package/src/embed/events.spec.ts +705 -1
  172. package/src/embed/host-events.spec.ts +124 -0
  173. package/src/embed/hostEventClient/contracts.ts +41 -14
  174. package/src/embed/hostEventClient/host-event-client.spec.ts +326 -0
  175. package/src/embed/hostEventClient/host-event-client.ts +15 -0
  176. package/src/embed/hostEventClient/utils.spec.ts +204 -0
  177. package/src/embed/hostEventClient/utils.ts +37 -10
  178. package/src/embed/liveboard.ts +6 -0
  179. package/src/embed/spotter-viz-utils.spec.ts +17 -0
  180. package/src/embed/spotter-viz-utils.ts +34 -0
  181. package/src/embed/ts-embed.spec.ts +186 -0
  182. package/src/embed/ts-embed.ts +12 -4
  183. package/src/errors.ts +1 -0
  184. package/src/index.ts +2 -1
  185. package/src/react/util.spec.tsx +255 -0
  186. package/src/types.ts +119 -0
  187. package/src/utils/processData.spec.ts +97 -0
  188. package/src/utils/processTrigger.spec.ts +91 -0
@@ -1,8 +1,10 @@
1
1
  import {
2
2
  isValidUpdateFiltersPayload,
3
+ isValidUpdateParametersPayload,
3
4
  isValidDrillDownPayload,
4
5
  createValidationError,
5
6
  throwUpdateFiltersValidationError,
7
+ throwUpdateParametersValidationError,
6
8
  throwDrillDownValidationError,
7
9
  } from './utils';
8
10
  import { ERROR_MESSAGE } from '../../errors';
@@ -114,6 +116,201 @@ describe('hostEventClient utils', () => {
114
116
  filters: [{ column: 'y' }], // invalid
115
117
  } as any)).toBe(true);
116
118
  });
119
+
120
+ it('returns true for filter without applicability', () => {
121
+ expect(isValidUpdateFiltersPayload({
122
+ filter: { column: 'x', oper: 'EQ', values: ['a'] },
123
+ } as any)).toBe(true);
124
+ });
125
+
126
+ it('returns true for filter with valid applicability', () => {
127
+ expect(isValidUpdateFiltersPayload({
128
+ filter: {
129
+ column: 'x',
130
+ oper: 'EQ',
131
+ values: ['a'],
132
+ applicability: { level: 'TAB', targetId: 'tab-guid-1' },
133
+ },
134
+ } as any)).toBe(true);
135
+ });
136
+
137
+ it('returns true for filters array with applicability on some filters', () => {
138
+ expect(isValidUpdateFiltersPayload({
139
+ filters: [
140
+ {
141
+ column: 'x',
142
+ oper: 'IN',
143
+ values: ['a', 'b'],
144
+ applicability: { level: 'TAB', targetId: 'tab-guid-1' },
145
+ },
146
+ { column: 'y', oper: 'EQ', values: ['c'] },
147
+ ],
148
+ } as any)).toBe(true);
149
+ });
150
+
151
+ it('returns false for applicability missing targetId', () => {
152
+ expect(isValidUpdateFiltersPayload({
153
+ filter: {
154
+ column: 'x',
155
+ oper: 'EQ',
156
+ values: ['a'],
157
+ applicability: { level: 'TAB' },
158
+ },
159
+ } as any)).toBe(false);
160
+ });
161
+
162
+ it('returns false for applicability with empty targetId', () => {
163
+ expect(isValidUpdateFiltersPayload({
164
+ filter: {
165
+ column: 'x',
166
+ oper: 'EQ',
167
+ values: ['a'],
168
+ applicability: { level: 'TAB', targetId: '' },
169
+ },
170
+ } as any)).toBe(false);
171
+ });
172
+
173
+ it('returns false for null applicability', () => {
174
+ expect(isValidUpdateFiltersPayload({
175
+ filter: {
176
+ column: 'x',
177
+ oper: 'EQ',
178
+ values: ['a'],
179
+ applicability: null,
180
+ },
181
+ } as any)).toBe(false);
182
+ });
183
+
184
+ it('returns false for non-object applicability', () => {
185
+ expect(isValidUpdateFiltersPayload({
186
+ filter: {
187
+ column: 'x',
188
+ oper: 'EQ',
189
+ values: ['a'],
190
+ applicability: 'TAB',
191
+ },
192
+ } as any)).toBe(false);
193
+ });
194
+
195
+ it('returns true for LIVEBOARD level applicability without targetId', () => {
196
+ expect(isValidUpdateFiltersPayload({
197
+ filter: {
198
+ column: 'x',
199
+ oper: 'EQ',
200
+ values: ['a'],
201
+ applicability: { level: 'LIVEBOARD' },
202
+ },
203
+ } as any)).toBe(true);
204
+ });
205
+
206
+ it('returns false for applicability missing level', () => {
207
+ expect(isValidUpdateFiltersPayload({
208
+ filter: {
209
+ column: 'x',
210
+ oper: 'EQ',
211
+ values: ['a'],
212
+ applicability: { targetId: 'tab-guid-1' },
213
+ },
214
+ } as any)).toBe(false);
215
+ });
216
+
217
+ it('returns false for applicability with non-string level', () => {
218
+ expect(isValidUpdateFiltersPayload({
219
+ filter: {
220
+ column: 'x',
221
+ oper: 'EQ',
222
+ values: ['a'],
223
+ applicability: { level: 123, targetId: 'tab-guid-1' },
224
+ },
225
+ } as any)).toBe(false);
226
+ });
227
+
228
+ it('returns false if one filter in filters array has invalid applicability', () => {
229
+ expect(isValidUpdateFiltersPayload({
230
+ filters: [
231
+ { column: 'x', oper: 'EQ', values: ['a'] },
232
+ {
233
+ column: 'y',
234
+ oper: 'EQ',
235
+ values: ['b'],
236
+ applicability: { level: 'TAB' }, // missing targetId
237
+ },
238
+ ],
239
+ } as any)).toBe(false);
240
+ });
241
+ });
242
+
243
+ // =========================
244
+ // UpdateParameters Validation
245
+ // =========================
246
+ describe('isValidUpdateParametersPayload', () => {
247
+ it('returns true for undefined payload', () => {
248
+ expect(isValidUpdateParametersPayload(undefined)).toBe(true);
249
+ });
250
+
251
+ it('returns true for non-array payload', () => {
252
+ expect(isValidUpdateParametersPayload({ name: 'p', value: 1 })).toBe(true);
253
+ });
254
+
255
+ it('returns true for parameters without applicability', () => {
256
+ expect(isValidUpdateParametersPayload([
257
+ { name: 'p1', value: 1 },
258
+ { name: 'p2', value: 'a' },
259
+ ])).toBe(true);
260
+ });
261
+
262
+ it('returns true for null applicability', () => {
263
+ expect(isValidUpdateParametersPayload([
264
+ { name: 'p', value: 1, applicability: null },
265
+ ])).toBe(true);
266
+ });
267
+
268
+ it('returns true for valid TAB applicability with targetId', () => {
269
+ expect(isValidUpdateParametersPayload([
270
+ { name: 'p', value: 1, applicability: { level: 'TAB', targetId: 'tab-guid-1' } },
271
+ ])).toBe(true);
272
+ });
273
+
274
+ it('returns true for LIVEBOARD level applicability without targetId', () => {
275
+ expect(isValidUpdateParametersPayload([
276
+ { name: 'p', value: 1, applicability: { level: 'LIVEBOARD' } },
277
+ ])).toBe(true);
278
+ });
279
+
280
+ it('returns false for applicability missing targetId', () => {
281
+ expect(isValidUpdateParametersPayload([
282
+ { name: 'p', value: 1, applicability: { level: 'TAB' } },
283
+ ])).toBe(false);
284
+ });
285
+
286
+ it('returns false for applicability with empty targetId', () => {
287
+ expect(isValidUpdateParametersPayload([
288
+ { name: 'p', value: 1, applicability: { level: 'GROUP', targetId: ' ' } },
289
+ ])).toBe(false);
290
+ });
291
+
292
+ it('returns false for applicability with invalid level', () => {
293
+ expect(isValidUpdateParametersPayload([
294
+ { name: 'p', value: 1, applicability: { level: 'VIZ', targetId: 'guid-1' } },
295
+ ])).toBe(false);
296
+ });
297
+
298
+ it('returns false for non-object applicability', () => {
299
+ expect(isValidUpdateParametersPayload([
300
+ { name: 'p', value: 1, applicability: 'TAB' },
301
+ ])).toBe(false);
302
+ });
303
+
304
+ it('returns false if one parameter has invalid applicability', () => {
305
+ expect(isValidUpdateParametersPayload([
306
+ { name: 'p1', value: 1 },
307
+ { name: 'p2', value: 2, applicability: { level: 'GROUP' } }, // missing targetId
308
+ ])).toBe(false);
309
+ });
310
+
311
+ it('returns true for non-object entries', () => {
312
+ expect(isValidUpdateParametersPayload(['p', 1, null])).toBe(true);
313
+ });
117
314
  });
118
315
 
119
316
  // =========================
@@ -216,6 +413,13 @@ describe('hostEventClient utils', () => {
216
413
  });
217
414
  });
218
415
 
416
+ describe('throwUpdateParametersValidationError', () => {
417
+ it('throws with UPDATEPARAMETERS_INVALID_PAYLOAD message', () => {
418
+ expect(() => throwUpdateParametersValidationError())
419
+ .toThrow(ERROR_MESSAGE.UPDATEPARAMETERS_INVALID_PAYLOAD);
420
+ });
421
+ });
422
+
219
423
  describe('throwDrillDownValidationError', () => {
220
424
  it('throws with DRILLDOWN_INVALID_PAYLOAD message', () => {
221
425
  expect(() => throwDrillDownValidationError())
@@ -1,27 +1,40 @@
1
+ import isNil from 'lodash/isNil';
2
+ import isPlainObject from 'lodash/isPlainObject';
3
+ import isString from 'lodash/isString';
4
+ import isUndefined from 'lodash/isUndefined';
1
5
  import { EmbedErrorCodes, EmbedEvent, ErrorDetailsTypes, HostEvent } from '../../types';
2
6
  import { ERROR_MESSAGE } from '../../errors';
3
- import { HostEventRequest } from './contracts';
7
+ import { ApplicabilityLevel, HostEventRequest } from './contracts';
4
8
  import { embedEventStatus } from '../../utils';
5
9
 
10
+ const isValidApplicability = (a?: { level?: string; targetId?: string }) => {
11
+ if (isUndefined(a)) return true;
12
+ // targetId is not required at LIVEBOARD level, since the filter applies to the whole Liveboard
13
+ return isPlainObject(a)
14
+ && Object.values(ApplicabilityLevel).includes(a.level as ApplicabilityLevel)
15
+ && (a.level === ApplicabilityLevel.Liveboard || (isString(a.targetId) && a.targetId.trim().length > 0));
16
+ };
17
+
6
18
  export function isValidUpdateFiltersPayload(
7
19
  payload: HostEventRequest<HostEvent.UpdateFilters> | undefined,
8
20
  ): boolean {
9
21
  if (!payload) return false;
10
22
 
11
- const isValidFilter = (f: {
12
- column?: string;
13
- oper?: string;
14
- values?: unknown[];
15
- type?: string;
16
- columnName?: string;
17
- operator?: string
23
+ const isValidFilter = (f: {
24
+ column?: string;
25
+ oper?: string;
26
+ values?: unknown[];
27
+ type?: string;
28
+ columnName?: string;
29
+ operator?: string;
30
+ applicability?: { level?: string; targetId?: string };
18
31
  }) => {
19
32
  const hasColumn = typeof f.column === 'string' || typeof f.columnName === 'string';
20
33
  const hasOperator = typeof f.oper === 'string' || typeof f.operator === 'string';
21
34
  const hasValues = Array.isArray(f.values);
22
35
  const validType = !f.type || typeof f.type === 'string';
23
-
24
- return hasColumn && hasOperator && hasValues && validType;
36
+
37
+ return hasColumn && hasOperator && hasValues && validType && isValidApplicability(f.applicability);
25
38
  };
26
39
 
27
40
  const hasValidFilter = payload.filter && isValidFilter(payload.filter);
@@ -30,6 +43,16 @@ export function isValidUpdateFiltersPayload(
30
43
  return !!(hasValidFilter || hasValidFilters);
31
44
  }
32
45
 
46
+ export function isValidUpdateParametersPayload(payload: unknown): boolean {
47
+ // Only validates the applicability of each parameter (null treated as absent); the rest is forwarded as-is for backward compatibility.
48
+ if (!Array.isArray(payload)) return true;
49
+ return payload.every((p) => {
50
+ if (!isPlainObject(p)) return true;
51
+ const { applicability } = p as { applicability?: { level?: string; targetId?: string } };
52
+ return isNil(applicability) || isValidApplicability(applicability);
53
+ });
54
+ }
55
+
33
56
  export function isValidDrillDownPayload(
34
57
  payload: HostEventRequest<HostEvent.DrillDown> | undefined,
35
58
  ): boolean {
@@ -72,3 +95,7 @@ export function throwUpdateFiltersValidationError(): never {
72
95
  export function throwDrillDownValidationError(): never {
73
96
  createValidationError(ERROR_MESSAGE.DRILLDOWN_INVALID_PAYLOAD);
74
97
  }
98
+
99
+ export function throwUpdateParametersValidationError(): never {
100
+ createValidationError(ERROR_MESSAGE.UPDATEPARAMETERS_INVALID_PAYLOAD);
101
+ }
@@ -580,6 +580,12 @@ export interface LiveboardViewConfig extends BaseViewConfig, LiveboardOtherViewC
580
580
  * { label: 'Tip', text: 'try asking about revenue by region' },
581
581
  * { label: 'Tip', text: 'use natural language' },
582
582
  * ],
583
+ * // liveboardBrandName, spotterBrandName, insightTileBrandName, insightTileViewPlanLabel and insightTileLoaderText require SDK: 1.52.0 | ThoughtSpot Cloud: 26.9.0.cl
584
+ * liveboardBrandName: 'Reports',
585
+ * spotterBrandName: 'Analyst',
586
+ * insightTileBrandName: 'Insight card',
587
+ * insightTileViewPlanLabel: 'View plan',
588
+ * insightTileLoaderText: 'Generating insight',
583
589
  * },
584
590
  * })
585
591
  * ```
@@ -20,6 +20,23 @@ describe('buildSpotterVizAppInitData', () => {
20
20
  expect(result.embedParams?.spotterVizConfig?.brandHeadline).toBe("Hi, there! I'm");
21
21
  });
22
22
 
23
+ it('passes liveboardBrandName, spotterBrandName, insightTileBrandName, insightTileViewPlanLabel and insightTileLoaderText through spotterVizConfig', () => {
24
+ const spotterViz = {
25
+ brandName: 'MyBrand',
26
+ liveboardBrandName: 'Reports',
27
+ spotterBrandName: 'Analyst',
28
+ insightTileBrandName: 'Insight card',
29
+ insightTileViewPlanLabel: 'View plan',
30
+ insightTileLoaderText: 'Generating insight',
31
+ };
32
+ const result = buildSpotterVizAppInitData(base, { spotterViz });
33
+ expect(result.embedParams?.spotterVizConfig?.liveboardBrandName).toBe('Reports');
34
+ expect(result.embedParams?.spotterVizConfig?.spotterBrandName).toBe('Analyst');
35
+ expect(result.embedParams?.spotterVizConfig?.insightTileBrandName).toBe('Insight card');
36
+ expect(result.embedParams?.spotterVizConfig?.insightTileViewPlanLabel).toBe('View plan');
37
+ expect(result.embedParams?.spotterVizConfig?.insightTileLoaderText).toBe('Generating insight');
38
+ });
39
+
23
40
  it('preserves existing embedParams when adding spotterVizConfig', () => {
24
41
  const existing = { ...base, embedParams: { spotterSidebarConfig: { enablePastConversationsSidebar: true } } };
25
42
  const spotterViz = { brandName: 'MyBrand' };
@@ -38,6 +38,12 @@ export interface SpotterVizStarterPrompt {
38
38
  * { label: 'Tip', text: 'try asking about revenue by region' },
39
39
  * { label: 'Tip', text: 'use natural language' },
40
40
  * ],
41
+ * // liveboardBrandName, spotterBrandName, insightTileBrandName, insightTileViewPlanLabel and insightTileLoaderText require SDK: 1.52.0 | ThoughtSpot Cloud: 26.9.0.cl
42
+ * liveboardBrandName: 'Reports',
43
+ * spotterBrandName: 'Analyst',
44
+ * insightTileBrandName: 'Insight card',
45
+ * insightTileViewPlanLabel: 'View plan',
46
+ * insightTileLoaderText: 'Generating insight',
41
47
  * },
42
48
  * })
43
49
  * ```
@@ -94,6 +100,34 @@ export interface SpotterVizConfig {
94
100
  * @version SDK: 1.51.0 | ThoughtSpot Cloud: 26.8.0.cl
95
101
  */
96
102
  loaderTips?: SpotterVizLoaderTip[];
103
+ /**
104
+ * Custom term used to replace "Liveboard" in the agent's responses.
105
+ * @version SDK: 1.52.0 | ThoughtSpot Cloud: 26.9.0.cl
106
+ * @default ''
107
+ */
108
+ liveboardBrandName?: string;
109
+ /**
110
+ * Custom term used to replace "Spotter" in the agent's responses.
111
+ * @version SDK: 1.52.0 | ThoughtSpot Cloud: 26.9.0.cl
112
+ * @default ''
113
+ */
114
+ spotterBrandName?: string;
115
+ /**
116
+ * Custom term used to replace "Insight tile" in the UI and in the agent's responses.
117
+ * @version SDK: 1.52.0 | ThoughtSpot Cloud: 26.9.0.cl
118
+ * @default ''
119
+ */
120
+ insightTileBrandName?: string;
121
+ /**
122
+ * Custom term used to replace "View plan" in the insight tile menu.
123
+ * @version SDK: 1.52.0 | ThoughtSpot Cloud: 26.9.0.cl
124
+ */
125
+ insightTileViewPlanLabel?: string;
126
+ /**
127
+ * Custom loader text shown on the insight tile.
128
+ * @version SDK: 1.52.0 | ThoughtSpot Cloud: 26.9.0.cl
129
+ */
130
+ insightTileLoaderText?: string;
97
131
  }
98
132
 
99
133
  /**
@@ -1409,6 +1409,40 @@ describe('Unit test case for ts embed', () => {
1409
1409
  const { mockGetPreauthInfo } = await setup(true, 123);
1410
1410
  expect(mockGetPreauthInfo).toHaveBeenCalledTimes(0);
1411
1411
  });
1412
+
1413
+ test('should NOT trigger InfoSuccess when getPreauthInfo resolves with no .info field', async () => {
1414
+ jest.spyOn(window, 'addEventListener').mockImplementationOnce(
1415
+ (event, handler) => {
1416
+ (handler as EventListener)({
1417
+ data: { type: 'xyz' },
1418
+ ports: [3000],
1419
+ source: null,
1420
+ } as any);
1421
+ },
1422
+ );
1423
+ mockProcessTrigger.mockResolvedValueOnce({ session: 'test' });
1424
+ jest.spyOn(authService, 'fetchPreauthInfoService').mockResolvedValueOnce({
1425
+ ok: true,
1426
+ headers: new Headers({ 'content-type': 'application/json' }),
1427
+ json: async () => ({}), // no .info field
1428
+ } as any);
1429
+ const iFrame: any = document.createElement('div');
1430
+ jest.spyOn(baseInstance, 'getAuthPromise').mockResolvedValueOnce(true as any);
1431
+ const tsEmbed = new SearchEmbed(getRootEl(), {});
1432
+ iFrame.contentWindow = { postMessage: jest.fn() };
1433
+ jest.spyOn(iFrame, 'addEventListener').mockImplementationOnce(
1434
+ (event, handler) => { (handler as EventListener)({} as Event); },
1435
+ );
1436
+ jest.spyOn(document, 'createElement').mockReturnValueOnce(iFrame);
1437
+ await tsEmbed.render();
1438
+
1439
+ await executeAfterWait(() => {
1440
+ const infoSuccessCall = mockProcessTrigger.mock.calls.find(
1441
+ (call) => call[1] === HostEvent.InfoSuccess,
1442
+ );
1443
+ expect(infoSuccessCall).toBeUndefined();
1444
+ });
1445
+ });
1412
1446
  });
1413
1447
 
1414
1448
  describe('Preauth Cache for FullAppEmbed with PrimaryNavBar', () => {
@@ -2281,6 +2315,46 @@ describe('Unit test case for ts embed', () => {
2281
2315
  });
2282
2316
  });
2283
2317
 
2318
+ it('Sets the overrideHistoryState param', async () => {
2319
+ const appEmbed = new AppEmbed(getRootEl(), {
2320
+ frameParams: {
2321
+ width: '100%',
2322
+ height: '100%',
2323
+ },
2324
+ overrideHistoryState: true,
2325
+ });
2326
+ await appEmbed.render();
2327
+ expectUrlToHaveParamsWithValues(getIFrameSrc(), {
2328
+ overrideHistoryState: true,
2329
+ });
2330
+ });
2331
+
2332
+ it('Sets the overrideHistoryState param to false', async () => {
2333
+ const appEmbed = new AppEmbed(getRootEl(), {
2334
+ frameParams: {
2335
+ width: '100%',
2336
+ height: '100%',
2337
+ },
2338
+ overrideHistoryState: false,
2339
+ });
2340
+ await appEmbed.render();
2341
+ expectUrlToHaveParamsWithValues(getIFrameSrc(), {
2342
+ overrideHistoryState: false,
2343
+ });
2344
+ });
2345
+
2346
+ it('Should not add overrideHistoryState param when it is undefined', async () => {
2347
+ const appEmbed = new AppEmbed(getRootEl(), {
2348
+ frameParams: {
2349
+ width: '100%',
2350
+ height: '100%',
2351
+ },
2352
+ });
2353
+ await appEmbed.render();
2354
+ const url = getIFrameSrc();
2355
+ expect(url).not.toContain('overrideHistoryState');
2356
+ });
2357
+
2284
2358
  it('Should not add contextMenuEnabledOnWhichClick flag to the iframe src when it is not passed', async () => {
2285
2359
  const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
2286
2360
  ...defaultViewConfig,
@@ -4570,6 +4644,28 @@ describe('Trigger method edge cases', () => {
4570
4644
  expect(result).toBeNull();
4571
4645
  });
4572
4646
  });
4647
+
4648
+ test('isValidationError catch: handleError called with embedErrorDetails and error is rethrown', async () => {
4649
+ const searchEmbed = new SearchEmbed(getRootEl(), defaultViewConfig);
4650
+ const handleErrorSpy = jest.spyOn(searchEmbed as any, 'handleError').mockImplementation(() => {});
4651
+ await searchEmbed.render();
4652
+
4653
+ // First processTrigger call is getAvailableUIPassthroughKeys for DrillDown
4654
+ mockProcessTrigger.mockResolvedValueOnce([
4655
+ { value: { keys: Object.values(UIPassthroughEvent) } },
4656
+ ]);
4657
+
4658
+ // DrillDown with no points → throwDrillDownValidationError → isValidationError=true
4659
+ await expect(
4660
+ searchEmbed.trigger(HostEvent.DrillDown, {} as any),
4661
+ ).rejects.toThrow('DrillDown requires a valid points object');
4662
+
4663
+ expect(handleErrorSpy).toHaveBeenCalledWith(
4664
+ expect.objectContaining({
4665
+ data: expect.objectContaining({ code: EmbedErrorCodes.HOST_EVENT_VALIDATION }),
4666
+ }),
4667
+ );
4668
+ });
4573
4669
  });
4574
4670
 
4575
4671
  describe('PreRender replaceExistingPreRender scenarios', () => {
@@ -4580,6 +4676,10 @@ describe('PreRender replaceExistingPreRender scenarios', () => {
4580
4676
  });
4581
4677
  });
4582
4678
 
4679
+ beforeEach(() => {
4680
+ document.body.innerHTML = getDocumentBody();
4681
+ });
4682
+
4583
4683
  afterEach(() => {
4584
4684
  const rootEle = document.getElementById('myRoot');
4585
4685
  rootEle?.remove();
@@ -4725,6 +4825,52 @@ describe('Fullscreen change handler behavior', () => {
4725
4825
  );
4726
4826
  });
4727
4827
  });
4828
+
4829
+ test('disableFullscreenPresentation undefined defaults to true — no fullscreenchange listener registered', () => {
4830
+ init({
4831
+ thoughtSpotHost: 'tshost',
4832
+ authType: AuthType.None,
4833
+ // disableFullscreenPresentation not set → defaults to true via ?? true
4834
+ });
4835
+
4836
+ const embed = new SearchEmbed(getRootEl(), defaultViewConfig);
4837
+ const addEventListenerSpy = jest.spyOn(document, 'addEventListener');
4838
+
4839
+ embed['setupFullscreenChangeHandler']();
4840
+
4841
+ expect(addEventListenerSpy).not.toHaveBeenCalledWith('fullscreenchange', expect.any(Function));
4842
+
4843
+ addEventListenerSpy.mockRestore();
4844
+ // Restore for other tests in this describe
4845
+ init({ thoughtSpotHost: 'tshost', authType: AuthType.None, disableFullscreenPresentation: false });
4846
+ });
4847
+
4848
+ test('fullscreenchange fires but iFrame.contentWindow is null — trigger NOT called, debug log emitted', () => {
4849
+ const embed = new SearchEmbed(getRootEl(), defaultViewConfig);
4850
+ // Give the embed a fake iFrame with null contentWindow
4851
+ (embed as any).iFrame = { contentWindow: null };
4852
+
4853
+ const addEventListenerSpy = jest.spyOn(document, 'addEventListener');
4854
+ const loggerDebugSpy = jest.spyOn(logger, 'debug').mockImplementation(() => {});
4855
+ const triggerSpy = jest.spyOn(embed, 'trigger' as any).mockImplementation(() => Promise.resolve(null));
4856
+
4857
+ embed['setupFullscreenChangeHandler']();
4858
+
4859
+ // Capture the registered handler directly instead of dispatching to document
4860
+ const fullscreenCall = addEventListenerSpy.mock.calls.find((c) => c[0] === 'fullscreenchange');
4861
+ expect(fullscreenCall).toBeTruthy();
4862
+ const handler = fullscreenCall[1] as EventListener;
4863
+
4864
+ Object.defineProperty(document, 'fullscreenElement', { value: null, writable: true, configurable: true });
4865
+ handler(new Event('fullscreenchange'));
4866
+
4867
+ expect(triggerSpy).not.toHaveBeenCalled();
4868
+ expect(loggerDebugSpy).toHaveBeenCalledWith(expect.stringContaining('Skipping ExitPresentMode'));
4869
+
4870
+ addEventListenerSpy.mockRestore();
4871
+ loggerDebugSpy.mockRestore();
4872
+ triggerSpy.mockRestore();
4873
+ });
4728
4874
  });
4729
4875
 
4730
4876
  describe('ShowPreRender with UpdateEmbedParams', () => {
@@ -5315,5 +5461,45 @@ describe('ShowPreRender with UpdateEmbedParams', () => {
5315
5461
 
5316
5462
  embed.destroy();
5317
5463
  });
5464
+
5465
+ // showPreRender located the stale placeholder with a subtree-wide
5466
+ // querySelector but removed it with hostElement.removeChild(), which
5467
+ // only accepts direct children. With fullHeight the placeholder can be
5468
+ // nested deeper than a direct child, so removeChild threw
5469
+ // "NotFoundError: Failed to execute 'removeChild' on 'Node'".
5470
+ it('showPreRender removes a placeholder nested below a direct child without throwing', async () => {
5471
+ createRootEleForEmbed();
5472
+ const embed = new LiveboardEmbed('#tsEmbedDiv', {
5473
+ preRenderId: 'ph-nested-test',
5474
+ liveboardId: 'lb-id',
5475
+ fullHeight: true,
5476
+ } as any);
5477
+ await embed.preRender();
5478
+ await waitFor(
5479
+ () => !!document.querySelector('#tsEmbed-pre-render-child-ph-nested-test'),
5480
+ );
5481
+
5482
+ const hostElement = document.querySelector('#tsEmbedDiv') as HTMLElement;
5483
+ const placeHolderId = 'tsEmbed-pre-render-placeholder-ph-nested-test';
5484
+ // Simulate the fullHeight layout where the placeholder ends up as
5485
+ // a grandchild of the host element rather than a direct child.
5486
+ const intermediateWrapper = document.createElement('div');
5487
+ const staleNestedPlaceholder = document.createElement('div');
5488
+ staleNestedPlaceholder.id = placeHolderId;
5489
+ intermediateWrapper.appendChild(staleNestedPlaceholder);
5490
+ hostElement.appendChild(intermediateWrapper);
5491
+
5492
+ expect(hostElement.querySelector(`#${placeHolderId}`)).toBe(staleNestedPlaceholder);
5493
+ // Before the fix this threw NotFoundError.
5494
+ await expect(embed.showPreRender()).resolves.toBeDefined();
5495
+ // The stale nested placeholder is gone; the fresh one is a direct child.
5496
+ expect(staleNestedPlaceholder.isConnected).toBe(false);
5497
+ const currentPlaceholder = (
5498
+ embed as any
5499
+ ).getPreRenderPlaceHolderElement() as HTMLDivElement;
5500
+ expect(currentPlaceholder.parentElement).toBe(hostElement);
5501
+
5502
+ embed.destroy();
5503
+ });
5318
5504
  });
5319
5505
  });
@@ -771,6 +771,7 @@ export class TsEmbed {
771
771
  insertInToSlide,
772
772
  disableRedirectionLinksInNewTab,
773
773
  overrideOrgId,
774
+ overrideHistoryState,
774
775
  exposeTranslationIDs,
775
776
  primaryAction,
776
777
  } = this.viewConfig;
@@ -869,6 +870,9 @@ export class TsEmbed {
869
870
  if (overrideOrgId !== undefined) {
870
871
  queryParams[Param.OverrideOrgId] = overrideOrgId;
871
872
  }
873
+ if (overrideHistoryState !== undefined) {
874
+ queryParams[Param.OverrideHistoryState] = overrideHistoryState;
875
+ }
872
876
 
873
877
  if (this.isPreAuthCacheEnabled()) {
874
878
  queryParams[Param.preAuthCache] = true;
@@ -1999,10 +2003,14 @@ export class TsEmbed {
1999
2003
  }
2000
2004
 
2001
2005
  const placeHolderId = this.getPreRenderIds().placeHolder;
2002
- const oldEle = this.hostElement.querySelector(`#${placeHolderId}`);
2003
- if (oldEle) {
2004
- this.hostElement.removeChild(oldEle);
2005
- }
2006
+ // Remove any stale placeholder from a previous cycle. It is located
2007
+ // via a subtree-wide querySelector, so it may be nested deeper than a
2008
+ // direct child (E.g.: with fullHeight the host app can wrap it). Use
2009
+ // Element.remove() — which detaches from whatever the real parent is —
2010
+ // rather than hostElement.removeChild(), which throws NotFoundError
2011
+ // when the match is not a direct child. Mirrors the wrapper/child
2012
+ // cleanup in createPreRenderWrapper()/createPreRenderChild().
2013
+ this.hostElement.querySelector(`#${placeHolderId}`)?.remove();
2006
2014
 
2007
2015
  this.hostElement.appendChild(this.insertedDomEl);
2008
2016
 
package/src/errors.ts CHANGED
@@ -31,6 +31,7 @@ export const ERROR_MESSAGE = {
31
31
  INVALID_SPOTTER_DOCUMENTATION_URL: 'Invalid spotterDocumentationUrl. Please provide a valid http or https URL.',
32
32
  UPDATEFILTERS_INVALID_PAYLOAD: 'UpdateFilters requires a valid filter or filters array. Expected: { filter: { column, oper, values } } or { filters: [{ column, oper, values }, ...] }',
33
33
  DRILLDOWN_INVALID_PAYLOAD: 'DrillDown requires a valid points object. Expected: { points: { clickedPoint?, selectedPoints? }, autoDrillDown?, vizId? }',
34
+ UPDATEPARAMETERS_INVALID_PAYLOAD: 'UpdateParameters received an invalid applicability. Expected: { level: LIVEBOARD | TAB | GROUP, targetId } where targetId is required for TAB and GROUP levels',
34
35
  };
35
36
 
36
37
  export const CUSTOM_ACTIONS_ERROR_MESSAGE = {
package/src/index.ts CHANGED
@@ -32,7 +32,7 @@ import { PinboardEmbed, LiveboardViewConfig, LiveboardEmbed } from './embed/live
32
32
  import { SearchEmbed, SearchViewConfig } from './embed/search';
33
33
  import { SearchBarEmbed, SearchBarViewConfig } from './embed/search-bar';
34
34
  import { SpotterAgentEmbed, SpotterAgentEmbedViewConfig, BodylessConversation, BodylessConversationViewConfig} from './embed/bodyless-conversation';
35
- import { SpotterEmbed, SpotterEmbedViewConfig, SpotterChatViewConfig, SpotterSidebarViewConfig, ConversationEmbed, ConversationViewConfig } from './embed/conversation';
35
+ import { SpotterEmbed, SpotterEmbedViewConfig, SpotterChatViewConfig, SpotterSidebarViewConfig, SpotterQueryMode, ConversationEmbed, ConversationViewConfig } from './embed/conversation';
36
36
  import { SpotterVizConfig, SpotterVizStarterPrompt, SpotterVizLoaderTip } from './embed/spotter-viz-utils';
37
37
  import {
38
38
  AuthFailureType, AuthStatus, AuthEvent, AuthEventEmitter,
@@ -117,6 +117,7 @@ export {
117
117
  SpotterEmbedViewConfig,
118
118
  SpotterChatViewConfig,
119
119
  SpotterSidebarViewConfig,
120
+ SpotterQueryMode,
120
121
  ConversationViewConfig,
121
122
  ConversationEmbed,
122
123
  AuthFailureType,