@thoughtspot/visual-embed-sdk 1.46.5 → 1.47.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 (158) hide show
  1. package/cjs/package.json +1 -1
  2. package/cjs/src/embed/app.d.ts +9 -0
  3. package/cjs/src/embed/app.d.ts.map +1 -1
  4. package/cjs/src/embed/app.js +7 -2
  5. package/cjs/src/embed/app.js.map +1 -1
  6. package/cjs/src/embed/app.spec.js +22 -2
  7. package/cjs/src/embed/app.spec.js.map +1 -1
  8. package/cjs/src/embed/conversation.d.ts +10 -1
  9. package/cjs/src/embed/conversation.d.ts.map +1 -1
  10. package/cjs/src/embed/conversation.js +2 -1
  11. package/cjs/src/embed/conversation.js.map +1 -1
  12. package/cjs/src/embed/hostEventClient/utils.d.ts.map +1 -1
  13. package/cjs/src/embed/hostEventClient/utils.js +7 -1
  14. package/cjs/src/embed/hostEventClient/utils.js.map +1 -1
  15. package/cjs/src/embed/hostEventClient/utils.spec.js +80 -5
  16. package/cjs/src/embed/hostEventClient/utils.spec.js.map +1 -1
  17. package/cjs/src/embed/liveboard.d.ts +9 -0
  18. package/cjs/src/embed/liveboard.d.ts.map +1 -1
  19. package/cjs/src/embed/liveboard.js +4 -1
  20. package/cjs/src/embed/liveboard.js.map +1 -1
  21. package/cjs/src/embed/ts-embed.d.ts.map +1 -1
  22. package/cjs/src/embed/ts-embed.js +12 -4
  23. package/cjs/src/embed/ts-embed.js.map +1 -1
  24. package/cjs/src/embed/ts-embed.spec.d.ts.map +1 -1
  25. package/cjs/src/embed/ts-embed.spec.js +28 -67
  26. package/cjs/src/embed/ts-embed.spec.js.map +1 -1
  27. package/cjs/src/errors.d.ts +0 -1
  28. package/cjs/src/errors.d.ts.map +1 -1
  29. package/cjs/src/errors.js +0 -1
  30. package/cjs/src/errors.js.map +1 -1
  31. package/cjs/src/index.d.ts +1 -2
  32. package/cjs/src/index.d.ts.map +1 -1
  33. package/cjs/src/index.js +2 -4
  34. package/cjs/src/index.js.map +1 -1
  35. package/cjs/src/react/all-types-export.d.ts +2 -2
  36. package/cjs/src/react/all-types-export.d.ts.map +1 -1
  37. package/cjs/src/react/all-types-export.js +1 -3
  38. package/cjs/src/react/all-types-export.js.map +1 -1
  39. package/cjs/src/react/index.d.ts +1 -36
  40. package/cjs/src/react/index.d.ts.map +1 -1
  41. package/cjs/src/react/index.js +1 -34
  42. package/cjs/src/react/index.js.map +1 -1
  43. package/cjs/src/types.d.ts +42 -74
  44. package/cjs/src/types.d.ts.map +1 -1
  45. package/cjs/src/types.js +18 -46
  46. package/cjs/src/types.js.map +1 -1
  47. package/dist/{index-ChNydfIz.js → index-CUgxBnPm.js} +1 -1
  48. package/dist/src/embed/app.d.ts +9 -0
  49. package/dist/src/embed/app.d.ts.map +1 -1
  50. package/dist/src/embed/conversation.d.ts +10 -1
  51. package/dist/src/embed/conversation.d.ts.map +1 -1
  52. package/dist/src/embed/hostEventClient/utils.d.ts.map +1 -1
  53. package/dist/src/embed/liveboard.d.ts +9 -0
  54. package/dist/src/embed/liveboard.d.ts.map +1 -1
  55. package/dist/src/embed/ts-embed.d.ts.map +1 -1
  56. package/dist/src/embed/ts-embed.spec.d.ts.map +1 -1
  57. package/dist/src/errors.d.ts +0 -1
  58. package/dist/src/errors.d.ts.map +1 -1
  59. package/dist/src/index.d.ts +1 -2
  60. package/dist/src/index.d.ts.map +1 -1
  61. package/dist/src/react/all-types-export.d.ts +2 -2
  62. package/dist/src/react/all-types-export.d.ts.map +1 -1
  63. package/dist/src/react/index.d.ts +1 -36
  64. package/dist/src/react/index.d.ts.map +1 -1
  65. package/dist/src/types.d.ts +42 -74
  66. package/dist/src/types.d.ts.map +1 -1
  67. package/dist/tsembed-react.es.js +72 -200
  68. package/dist/tsembed-react.js +60 -190
  69. package/dist/tsembed.es.js +87 -181
  70. package/dist/tsembed.js +30812 -30907
  71. package/dist/visual-embed-sdk-react-full.d.ts +71 -255
  72. package/dist/visual-embed-sdk-react.d.ts +71 -255
  73. package/dist/visual-embed-sdk.d.ts +69 -225
  74. package/lib/package.json +1 -1
  75. package/lib/src/embed/app.d.ts +9 -0
  76. package/lib/src/embed/app.d.ts.map +1 -1
  77. package/lib/src/embed/app.js +7 -2
  78. package/lib/src/embed/app.js.map +1 -1
  79. package/lib/src/embed/app.spec.js +22 -2
  80. package/lib/src/embed/app.spec.js.map +1 -1
  81. package/lib/src/embed/conversation.d.ts +10 -1
  82. package/lib/src/embed/conversation.d.ts.map +1 -1
  83. package/lib/src/embed/conversation.js +2 -1
  84. package/lib/src/embed/conversation.js.map +1 -1
  85. package/lib/src/embed/hostEventClient/utils.d.ts.map +1 -1
  86. package/lib/src/embed/hostEventClient/utils.js +7 -1
  87. package/lib/src/embed/hostEventClient/utils.js.map +1 -1
  88. package/lib/src/embed/hostEventClient/utils.spec.js +80 -5
  89. package/lib/src/embed/hostEventClient/utils.spec.js.map +1 -1
  90. package/lib/src/embed/liveboard.d.ts +9 -0
  91. package/lib/src/embed/liveboard.d.ts.map +1 -1
  92. package/lib/src/embed/liveboard.js +4 -1
  93. package/lib/src/embed/liveboard.js.map +1 -1
  94. package/lib/src/embed/ts-embed.d.ts.map +1 -1
  95. package/lib/src/embed/ts-embed.js +12 -4
  96. package/lib/src/embed/ts-embed.js.map +1 -1
  97. package/lib/src/embed/ts-embed.spec.d.ts.map +1 -1
  98. package/lib/src/embed/ts-embed.spec.js +30 -69
  99. package/lib/src/embed/ts-embed.spec.js.map +1 -1
  100. package/lib/src/errors.d.ts +0 -1
  101. package/lib/src/errors.d.ts.map +1 -1
  102. package/lib/src/errors.js +0 -1
  103. package/lib/src/errors.js.map +1 -1
  104. package/lib/src/index.d.ts +1 -2
  105. package/lib/src/index.d.ts.map +1 -1
  106. package/lib/src/index.js +1 -2
  107. package/lib/src/index.js.map +1 -1
  108. package/lib/src/react/all-types-export.d.ts +2 -2
  109. package/lib/src/react/all-types-export.d.ts.map +1 -1
  110. package/lib/src/react/all-types-export.js +1 -1
  111. package/lib/src/react/all-types-export.js.map +1 -1
  112. package/lib/src/react/index.d.ts +1 -36
  113. package/lib/src/react/index.d.ts.map +1 -1
  114. package/lib/src/react/index.js +0 -33
  115. package/lib/src/react/index.js.map +1 -1
  116. package/lib/src/types.d.ts +42 -74
  117. package/lib/src/types.d.ts.map +1 -1
  118. package/lib/src/types.js +18 -46
  119. package/lib/src/types.js.map +1 -1
  120. package/lib/src/visual-embed-sdk.d.ts +69 -225
  121. package/package.json +1 -1
  122. package/src/embed/app.spec.ts +33 -3
  123. package/src/embed/app.ts +18 -3
  124. package/src/embed/conversation.ts +15 -4
  125. package/src/embed/hostEventClient/utils.spec.ts +94 -6
  126. package/src/embed/hostEventClient/utils.ts +15 -2
  127. package/src/embed/liveboard.ts +13 -0
  128. package/src/embed/ts-embed.spec.ts +56 -108
  129. package/src/embed/ts-embed.ts +10 -3
  130. package/src/errors.ts +0 -1
  131. package/src/index.ts +2 -5
  132. package/src/react/all-types-export.ts +0 -3
  133. package/src/react/index.tsx +15 -59
  134. package/src/types.ts +103 -137
  135. package/cjs/src/embed/sage.d.ts +0 -164
  136. package/cjs/src/embed/sage.d.ts.map +0 -1
  137. package/cjs/src/embed/sage.js +0 -88
  138. package/cjs/src/embed/sage.js.map +0 -1
  139. package/cjs/src/embed/sage.spec.d.ts +0 -2
  140. package/cjs/src/embed/sage.spec.d.ts.map +0 -1
  141. package/cjs/src/embed/sage.spec.js +0 -151
  142. package/cjs/src/embed/sage.spec.js.map +0 -1
  143. package/dist/index-DGV_zh53.js +0 -7371
  144. package/dist/index-DW2wEHqy.js +0 -7371
  145. package/dist/src/embed/sage.d.ts +0 -164
  146. package/dist/src/embed/sage.d.ts.map +0 -1
  147. package/dist/src/embed/sage.spec.d.ts +0 -2
  148. package/dist/src/embed/sage.spec.d.ts.map +0 -1
  149. package/lib/src/embed/sage.d.ts +0 -164
  150. package/lib/src/embed/sage.d.ts.map +0 -1
  151. package/lib/src/embed/sage.js +0 -84
  152. package/lib/src/embed/sage.js.map +0 -1
  153. package/lib/src/embed/sage.spec.d.ts +0 -2
  154. package/lib/src/embed/sage.spec.d.ts.map +0 -1
  155. package/lib/src/embed/sage.spec.js +0 -148
  156. package/lib/src/embed/sage.spec.js.map +0 -1
  157. package/src/embed/sage.spec.ts +0 -206
  158. package/src/embed/sage.ts +0 -231
@@ -166,7 +166,7 @@ export interface SpotterEmbedViewConfig extends Omit<BaseViewConfig, 'primaryAct
166
166
  * @default true
167
167
  * @example
168
168
  * ```js
169
- * // Replace <EmbedComponent> with embed component name. For example, SageEmbed, AppEmbed, or SearchBarEmbed
169
+ * // Replace <EmbedComponent> with embed component name. For example, AppEmbed, or SearchBarEmbed
170
170
  * const embed = new <EmbedComponent>('#tsEmbed', {
171
171
  * ... // other embed view config
172
172
  * dataPanelV2: true,
@@ -228,7 +228,7 @@ export interface SpotterEmbedViewConfig extends Omit<BaseViewConfig, 'primaryAct
228
228
  runtimeFilters?: RuntimeFilter[];
229
229
  /**
230
230
  * Flag to control whether runtime filters should be included in the URL.
231
- * If true, filters will be passed via app initialization payload
231
+ * If true, filters will be passed via app initialization payload
232
232
  * (default behavior from SDK 1.45.0).
233
233
  * If false/undefined, filters are appended to the iframe URL instead.
234
234
  * (default behavior before SDK 1.45.0).
@@ -259,9 +259,9 @@ export interface SpotterEmbedViewConfig extends Omit<BaseViewConfig, 'primaryAct
259
259
  runtimeParameters?: RuntimeParameter[];
260
260
  /**
261
261
  * Flag to control whether runtime parameters should be included in the URL.
262
- * If true, parameters will be passed via app
262
+ * If true, parameters will be passed via app
263
263
  * initialization payload (default behavior from SDK 1.45.0).
264
- * If false/undefined, parameters are appended to
264
+ * If false/undefined, parameters are appended to
265
265
  * the iframe URL instead (default behavior before SDK 1.45.0).
266
266
  *
267
267
  * Supported embed types: `SpotterEmbed`
@@ -284,6 +284,15 @@ export interface SpotterEmbedViewConfig extends Omit<BaseViewConfig, 'primaryAct
284
284
  * ```
285
285
  */
286
286
  updatedSpotterChatPrompt?: boolean;
287
+ /**
288
+ * Enables the stop answer generation button in the Spotter embed UI,
289
+ * allowing users to interrupt an ongoing answer generation.
290
+ *
291
+ * Supported embed types: `SpotterEmbed`
292
+ * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl
293
+ * @default false
294
+ */
295
+ enableStopAnswerGenerationEmbed?: boolean;
287
296
  /**
288
297
  * Controls the visibility of the past conversations sidebar.
289
298
  *
@@ -411,6 +420,7 @@ export class SpotterEmbed extends TsEmbed {
411
420
  runtimeParameters,
412
421
  excludeRuntimeParametersfromURL,
413
422
  updatedSpotterChatPrompt,
423
+ enableStopAnswerGenerationEmbed,
414
424
  spotterChatConfig,
415
425
  } = this.viewConfig;
416
426
 
@@ -432,6 +442,7 @@ export class SpotterEmbed extends TsEmbed {
432
442
  setParamIfDefined(queryParams, Param.ShowSpotterLimitations, showSpotterLimitations, true);
433
443
  setParamIfDefined(queryParams, Param.HideSampleQuestions, hideSampleQuestions, true);
434
444
  setParamIfDefined(queryParams, Param.UpdatedSpotterChatPrompt, updatedSpotterChatPrompt, true);
445
+ setParamIfDefined(queryParams, Param.EnableStopAnswerGenerationEmbed, enableStopAnswerGenerationEmbed, true);
435
446
 
436
447
  // Handle spotterChatConfig params
437
448
  if (spotterChatConfig) {
@@ -10,6 +10,10 @@ import { EmbedEvent } from '../../types';
10
10
  import { embedEventStatus } from '../../utils';
11
11
 
12
12
  describe('hostEventClient utils', () => {
13
+
14
+ // =========================
15
+ // UpdateFilters Validation
16
+ // =========================
13
17
  describe('isValidUpdateFiltersPayload', () => {
14
18
  it('returns false for undefined', () => {
15
19
  expect(isValidUpdateFiltersPayload(undefined)).toBe(false);
@@ -19,12 +23,24 @@ describe('hostEventClient utils', () => {
19
23
  expect(isValidUpdateFiltersPayload({})).toBe(false);
20
24
  });
21
25
 
22
- it('returns true for valid filter', () => {
26
+ it('returns true for valid filter with column', () => {
23
27
  expect(isValidUpdateFiltersPayload({
24
28
  filter: { column: 'region', oper: 'EQ', values: ['North'] },
25
29
  } as any)).toBe(true);
26
30
  });
27
31
 
32
+ it('returns true for valid filter with columnName', () => {
33
+ expect(isValidUpdateFiltersPayload({
34
+ filter: { columnName: 'region', oper: 'EQ', values: ['North'] },
35
+ } as any)).toBe(true);
36
+ });
37
+
38
+ it('returns true for filter with operator instead of oper', () => {
39
+ expect(isValidUpdateFiltersPayload({
40
+ filter: { column: 'region', operator: 'EQ', values: ['North'] },
41
+ } as any)).toBe(true);
42
+ });
43
+
28
44
  it('returns true for valid filters array', () => {
29
45
  expect(isValidUpdateFiltersPayload({
30
46
  filters: [
@@ -34,13 +50,31 @@ describe('hostEventClient utils', () => {
34
50
  } as any)).toBe(true);
35
51
  });
36
52
 
37
- it('returns false for filter with missing column', () => {
53
+ it('returns true for valid filters array with columnName', () => {
54
+ expect(isValidUpdateFiltersPayload({
55
+ filters: [
56
+ { columnName: 'x', oper: 'IN', values: ['a', 'b'] },
57
+ { columnName: 'y', oper: 'EQ', values: ['c'] },
58
+ ],
59
+ } as any)).toBe(true);
60
+ });
61
+
62
+ it('returns false if one filter in filters array is invalid', () => {
63
+ expect(isValidUpdateFiltersPayload({
64
+ filters: [
65
+ { column: 'x', oper: 'EQ', values: ['a'] },
66
+ { column: 'y', values: ['b'] }, // invalid
67
+ ],
68
+ } as any)).toBe(false);
69
+ });
70
+
71
+ it('returns false for filter with missing column/columnName', () => {
38
72
  expect(isValidUpdateFiltersPayload({
39
73
  filter: { oper: 'EQ', values: ['a'] },
40
74
  } as any)).toBe(false);
41
75
  });
42
76
 
43
- it('returns false for filter with missing oper', () => {
77
+ it('returns false for filter with missing operator', () => {
44
78
  expect(isValidUpdateFiltersPayload({
45
79
  filter: { column: 'x', values: ['a'] },
46
80
  } as any)).toBe(false);
@@ -52,11 +86,39 @@ describe('hostEventClient utils', () => {
52
86
  } as any)).toBe(false);
53
87
  });
54
88
 
89
+ it('returns false for filter with non-string type', () => {
90
+ expect(isValidUpdateFiltersPayload({
91
+ filter: { column: 'x', oper: 'EQ', values: ['a'], type: 123 },
92
+ } as any)).toBe(false);
93
+ });
94
+
95
+ it('returns true for filter with valid string type', () => {
96
+ expect(isValidUpdateFiltersPayload({
97
+ filter: { column: 'x', oper: 'EQ', values: ['a'], type: 'STRING' },
98
+ } as any)).toBe(true);
99
+ });
100
+
55
101
  it('returns false for empty filters array', () => {
56
102
  expect(isValidUpdateFiltersPayload({ filters: [] } as any)).toBe(false);
57
103
  });
104
+
105
+ it('returns false if filters is not an array', () => {
106
+ expect(isValidUpdateFiltersPayload({
107
+ filters: 'invalid',
108
+ } as any)).toBe(false);
109
+ });
110
+
111
+ it('returns true if filter is valid even when filters is invalid', () => {
112
+ expect(isValidUpdateFiltersPayload({
113
+ filter: { column: 'x', oper: 'EQ', values: ['a'] },
114
+ filters: [{ column: 'y' }], // invalid
115
+ } as any)).toBe(true);
116
+ });
58
117
  });
59
118
 
119
+ // =========================
120
+ // DrillDown Validation
121
+ // =========================
60
122
  describe('isValidDrillDownPayload', () => {
61
123
  it('returns false for undefined', () => {
62
124
  expect(isValidDrillDownPayload(undefined)).toBe(false);
@@ -66,7 +128,7 @@ describe('hostEventClient utils', () => {
66
128
  expect(isValidDrillDownPayload({})).toBe(false);
67
129
  });
68
130
 
69
- it('returns false for empty points', () => {
131
+ it('returns false for empty points object', () => {
70
132
  expect(isValidDrillDownPayload({ points: {} } as any)).toBe(false);
71
133
  });
72
134
 
@@ -97,16 +159,42 @@ describe('hostEventClient utils', () => {
97
159
  points: { selectedPoints: [] },
98
160
  } as any)).toBe(false);
99
161
  });
162
+
163
+ it('returns false if clickedPoint is null', () => {
164
+ expect(isValidDrillDownPayload({
165
+ points: { clickedPoint: null },
166
+ } as any)).toBe(false);
167
+ });
168
+
169
+ it('returns false if selectedPoints is not an array', () => {
170
+ expect(isValidDrillDownPayload({
171
+ points: { selectedPoints: 'invalid' },
172
+ } as any)).toBe(false);
173
+ });
174
+
175
+ it('returns false if both clickedPoint and selectedPoints are invalid', () => {
176
+ expect(isValidDrillDownPayload({
177
+ points: { clickedPoint: null, selectedPoints: [] },
178
+ } as any)).toBe(false);
179
+ });
100
180
  });
101
181
 
182
+ // =========================
183
+ // Error Handling
184
+ // =========================
102
185
  describe('createValidationError', () => {
103
- it('throws with message and embedErrorDetails', () => {
186
+ it('throws with message', () => {
104
187
  expect(() => createValidationError('test error')).toThrow('test error');
188
+ });
105
189
 
190
+ it('throws Error instance with metadata', () => {
106
191
  try {
107
192
  createValidationError('custom msg');
108
193
  } catch (err: any) {
194
+ expect(err).toBeInstanceOf(Error);
109
195
  expect(err.isValidationError).toBe(true);
196
+ expect(err.embedErrorDetails).toBeDefined();
197
+
110
198
  expect(err.embedErrorDetails).toMatchObject({
111
199
  type: EmbedEvent.Error,
112
200
  data: {
@@ -134,4 +222,4 @@ describe('hostEventClient utils', () => {
134
222
  .toThrow(ERROR_MESSAGE.DRILLDOWN_INVALID_PAYLOAD);
135
223
  });
136
224
  });
137
- });
225
+ });
@@ -8,8 +8,21 @@ export function isValidUpdateFiltersPayload(
8
8
  ): boolean {
9
9
  if (!payload) return false;
10
10
 
11
- const isValidFilter = (f: { column?: string; oper?: string; values?: unknown[] }) =>
12
- !!f && typeof f.column === 'string' && typeof f.oper === 'string' && Array.isArray(f.values);
11
+ const isValidFilter = (f: {
12
+ column?: string;
13
+ oper?: string;
14
+ values?: unknown[];
15
+ type?: string;
16
+ columnName?: string;
17
+ operator?: string
18
+ }) => {
19
+ const hasColumn = typeof f.column === 'string' || typeof f.columnName === 'string';
20
+ const hasOperator = typeof f.oper === 'string' || typeof f.operator === 'string';
21
+ const hasValues = Array.isArray(f.values);
22
+ const validType = !f.type || typeof f.type === 'string';
23
+
24
+ return hasColumn && hasOperator && hasValues && validType;
25
+ };
13
26
 
14
27
  const hasValidFilter = payload.filter && isValidFilter(payload.filter);
15
28
  const hasValidFilters = Array.isArray(payload.filters) && payload.filters.length > 0 && payload.filters.every(isValidFilter);
@@ -507,6 +507,15 @@ export interface LiveboardViewConfig extends BaseViewConfig, LiveboardOtherViewC
507
507
  * ```
508
508
  */
509
509
  updatedSpotterChatPrompt?: boolean;
510
+ /**
511
+ * Enables the stop answer generation button in the Spotter embed UI,
512
+ * allowing users to interrupt an ongoing answer generation.
513
+ *
514
+ * Supported embed types: `LiveboardEmbed`
515
+ * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl
516
+ * @default false
517
+ */
518
+ enableStopAnswerGenerationEmbed?: boolean;
510
519
  /**
511
520
  * Configuration for customizing Spotter chat UI
512
521
  * branding in tool response cards.
@@ -614,6 +623,7 @@ export class LiveboardEmbed extends V1Embed {
614
623
  isCentralizedLiveboardFilterUXEnabled = false,
615
624
  isLinkParametersEnabled,
616
625
  updatedSpotterChatPrompt,
626
+ enableStopAnswerGenerationEmbed,
617
627
  spotterChatConfig,
618
628
  isThisPeriodInDateFiltersEnabled,
619
629
  isContinuousLiveboardPDFEnabled,
@@ -641,6 +651,9 @@ export class LiveboardEmbed extends V1Embed {
641
651
  if (!isUndefined(updatedSpotterChatPrompt)) {
642
652
  params[Param.UpdatedSpotterChatPrompt] = !!updatedSpotterChatPrompt;
643
653
  }
654
+ if (!isUndefined(enableStopAnswerGenerationEmbed)) {
655
+ params[Param.EnableStopAnswerGenerationEmbed] = !!enableStopAnswerGenerationEmbed;
656
+ }
644
657
  if (visibleVizs) {
645
658
  params[Param.visibleVizs] = visibleVizs;
646
659
  }
@@ -11,8 +11,6 @@ import {
11
11
  AppEmbed,
12
12
  LiveboardEmbed,
13
13
  AppViewConfig,
14
- SageEmbed,
15
- SageViewConfig,
16
14
  SearchViewConfig,
17
15
  AnswerService,
18
16
  SpotterEmbed,
@@ -2192,28 +2190,6 @@ describe('Unit test case for ts embed', () => {
2192
2190
  `http://${thoughtSpotHost}/?embedApp=true${defaultParams}#/embed/viz/${liveboardId}`,
2193
2191
  );
2194
2192
  });
2195
-
2196
- const defaultConfig: SageViewConfig = {
2197
- disableWorksheetChange: false,
2198
- hideWorksheetSelector: false,
2199
- hideSageAnswerHeader: false,
2200
- hideAutocompleteSuggestions: false,
2201
- hideSampleQuestions: false,
2202
- isProductTour: false,
2203
- dataPanelV2: false,
2204
- };
2205
-
2206
- const sageEmbed = new SageEmbed(getRootEl(), {
2207
- ...defaultConfig,
2208
- } as SageViewConfig);
2209
-
2210
- sageEmbed.render();
2211
- await executeAfterWait(() => {
2212
- expectUrlMatch(
2213
- getIFrameSrc(),
2214
- `http://${thoughtSpotHost}/?embedApp=true&enableDataPanelV2=false&isSageEmbed=true&disableWorksheetChange=false&hideWorksheetSelector=false&hideEurekaSuggestions=false&isProductTour=false&hideSageAnswerHeader=false&hideAction=%5B%22reportError%22%5D#/embed/eureka`,
2215
- );
2216
- });
2217
2193
  });
2218
2194
 
2219
2195
  it('Should add contextMenuEnabledOnWhichClick flag to the iframe with left value', async () => {
@@ -2230,29 +2206,6 @@ describe('Unit test case for ts embed', () => {
2230
2206
  `http://${thoughtSpotHost}/?embedApp=true${defaultParams}&contextMenuEnabledOnWhichClick=left#/embed/viz/${liveboardId}`,
2231
2207
  );
2232
2208
  });
2233
-
2234
- const defaultConfig: SageViewConfig = {
2235
- disableWorksheetChange: false,
2236
- hideWorksheetSelector: false,
2237
- hideSageAnswerHeader: false,
2238
- hideAutocompleteSuggestions: false,
2239
- hideSampleQuestions: false,
2240
- isProductTour: false,
2241
- dataPanelV2: false,
2242
- };
2243
-
2244
- const sageEmbed = new SageEmbed(getRootEl(), {
2245
- ...defaultConfig,
2246
- contextMenuTrigger: ContextMenuTriggerOptions.LEFT_CLICK,
2247
- } as SageViewConfig);
2248
-
2249
- sageEmbed.render();
2250
- await executeAfterWait(() => {
2251
- expectUrlMatch(
2252
- getIFrameSrc(),
2253
- `http://${thoughtSpotHost}/?embedApp=true&enableDataPanelV2=false&contextMenuEnabledOnWhichClick=left&isSageEmbed=true&disableWorksheetChange=false&hideWorksheetSelector=false&hideEurekaSuggestions=false&isProductTour=false&hideSageAnswerHeader=false&hideAction=%5B%22reportError%22%5D#/embed/eureka`,
2254
- );
2255
- });
2256
2209
  });
2257
2210
 
2258
2211
  it('Should add contextMenuEnabledOnWhichClick flag to the iframe with right value', async () => {
@@ -2269,28 +2222,6 @@ describe('Unit test case for ts embed', () => {
2269
2222
  `http://${thoughtSpotHost}/?embedApp=true${defaultParams}&contextMenuEnabledOnWhichClick=right#/embed/viz/${liveboardId}`,
2270
2223
  );
2271
2224
  });
2272
- const defaultConfig: SageViewConfig = {
2273
- disableWorksheetChange: false,
2274
- hideWorksheetSelector: false,
2275
- hideSageAnswerHeader: false,
2276
- hideAutocompleteSuggestions: false,
2277
- hideSampleQuestions: false,
2278
- isProductTour: false,
2279
- dataPanelV2: false,
2280
- };
2281
-
2282
- const sageEmbed = new SageEmbed(getRootEl(), {
2283
- ...defaultConfig,
2284
- contextMenuTrigger: ContextMenuTriggerOptions.RIGHT_CLICK,
2285
- } as SageViewConfig);
2286
-
2287
- sageEmbed.render();
2288
- await executeAfterWait(() => {
2289
- expectUrlMatch(
2290
- getIFrameSrc(),
2291
- `http://${thoughtSpotHost}/?embedApp=true&enableDataPanelV2=false&contextMenuEnabledOnWhichClick=right&isSageEmbed=true&disableWorksheetChange=false&hideWorksheetSelector=false&hideEurekaSuggestions=false&isProductTour=false&hideSageAnswerHeader=false&hideAction=%5B%22reportError%22%5D#/embed/eureka`,
2292
- );
2293
- });
2294
2225
  });
2295
2226
 
2296
2227
  it('Should add contextMenuEnabledOnWhichClick flag to the iframe with both value', async () => {
@@ -2307,28 +2238,6 @@ describe('Unit test case for ts embed', () => {
2307
2238
  `http://${thoughtSpotHost}/?embedApp=true${defaultParams}&contextMenuEnabledOnWhichClick=both#/embed/viz/${liveboardId}`,
2308
2239
  );
2309
2240
  });
2310
- const defaultConfig: SageViewConfig = {
2311
- disableWorksheetChange: false,
2312
- hideWorksheetSelector: false,
2313
- hideSageAnswerHeader: false,
2314
- hideAutocompleteSuggestions: false,
2315
- hideSampleQuestions: false,
2316
- isProductTour: false,
2317
- dataPanelV2: false,
2318
- };
2319
-
2320
- const sageEmbed = new SageEmbed(getRootEl(), {
2321
- ...defaultConfig,
2322
- contextMenuTrigger: ContextMenuTriggerOptions.BOTH_CLICKS,
2323
- } as SageViewConfig);
2324
-
2325
- sageEmbed.render();
2326
- await executeAfterWait(() => {
2327
- expectUrlMatch(
2328
- getIFrameSrc(),
2329
- `http://${thoughtSpotHost}/?embedApp=true&enableDataPanelV2=false&contextMenuEnabledOnWhichClick=both&isSageEmbed=true&disableWorksheetChange=false&hideWorksheetSelector=false&hideEurekaSuggestions=false&isProductTour=false&hideSageAnswerHeader=false&hideAction=%5B%22reportError%22%5D#/embed/eureka`,
2330
- );
2331
- });
2332
2241
  });
2333
2242
  });
2334
2243
 
@@ -3558,6 +3467,45 @@ describe('Unit test case for ts embed', () => {
3558
3467
  expect(getRootEl().nextElementSibling.innerHTML).toBe('');
3559
3468
  });
3560
3469
 
3470
+ it('should not call trigger or remove DOM if destroy is called before render', () => {
3471
+ const appEmbed = new AppEmbed(getRootEl(), {
3472
+ frameParams: { width: '100%', height: '100%' },
3473
+ });
3474
+
3475
+ const triggerSpy = jest.spyOn(appEmbed, 'trigger');
3476
+ const removeChildSpy = jest.spyOn(Node.prototype, 'removeChild');
3477
+
3478
+ appEmbed.destroy();
3479
+
3480
+ expect(triggerSpy).not.toHaveBeenCalled();
3481
+ expect(removeChildSpy).not.toHaveBeenCalled();
3482
+ });
3483
+
3484
+ it('should still remove DOM element when trigger rejects (waitForCleanupOnDestroy: true)', async () => {
3485
+ const originalEmbedConfig = embedConfig.getEmbedConfig();
3486
+ embedConfig.setEmbedConfig({
3487
+ ...originalEmbedConfig,
3488
+ waitForCleanupOnDestroy: true,
3489
+ cleanupTimeout: 1000,
3490
+ });
3491
+
3492
+ const appEmbed = new AppEmbed(getRootEl(), {
3493
+ frameParams: { width: '100%', height: '100%' },
3494
+ });
3495
+ await appEmbed.render();
3496
+
3497
+ jest.spyOn(appEmbed, 'trigger').mockRejectedValue(new Error('trigger failed'));
3498
+ const removeChildSpy = jest.spyOn(Node.prototype, 'removeChild').mockImplementation(() => getRootEl());
3499
+
3500
+ appEmbed.destroy();
3501
+
3502
+ await new Promise(resolve => setTimeout(resolve, 50));
3503
+
3504
+ expect(removeChildSpy).toHaveBeenCalled();
3505
+
3506
+ embedConfig.setEmbedConfig(originalEmbedConfig);
3507
+ });
3508
+
3561
3509
  describe('with waitForCleanupOnDestroy configuration', () => {
3562
3510
  let originalEmbedConfig: any;
3563
3511
 
@@ -4220,9 +4168,9 @@ describe('PreRender replaceExistingPreRender scenarios', () => {
4220
4168
  preRenderId: 'no-replace-test',
4221
4169
  liveboardId: 'lb2',
4222
4170
  });
4223
-
4171
+
4224
4172
  const result = await embed2.preRender(false, false);
4225
-
4173
+
4226
4174
  expect(result).toBe(embed2);
4227
4175
  // The original iframe should still have lb1
4228
4176
  const iframe = getIFrameEl();
@@ -4247,17 +4195,17 @@ describe('Destroy error handling', () => {
4247
4195
  frameParams: { width: '100%', height: '100%' },
4248
4196
  });
4249
4197
  await appEmbed.render();
4250
-
4198
+
4251
4199
  const logSpy = jest.spyOn(logger, 'log').mockImplementation(() => {});
4252
-
4200
+
4253
4201
  jest.spyOn(Node.prototype, 'removeChild').mockImplementationOnce(() => {
4254
4202
  throw new Error('Remove failed');
4255
4203
  });
4256
-
4204
+
4257
4205
  expect(() => {
4258
4206
  appEmbed.destroy();
4259
4207
  }).not.toThrow();
4260
-
4208
+
4261
4209
  expect(logSpy).toHaveBeenCalledWith('Error destroying TS Embed', expect.any(Error));
4262
4210
  logSpy.mockReset();
4263
4211
  });
@@ -4282,25 +4230,25 @@ describe('Fullscreen change handler behavior', () => {
4282
4230
  liveboardId: 'test-lb',
4283
4231
  });
4284
4232
  await liveboardEmbed.render();
4285
-
4233
+
4286
4234
  await executeAfterWait(() => {
4287
4235
  const iframe = getIFrameEl();
4288
4236
  expect(iframe).toBeTruthy();
4289
4237
  });
4290
4238
 
4291
4239
  mockProcessTrigger.mockResolvedValue({});
4292
-
4240
+
4293
4241
  liveboardEmbed['setupFullscreenChangeHandler']();
4294
-
4242
+
4295
4243
  Object.defineProperty(document, 'fullscreenElement', {
4296
4244
  value: null,
4297
4245
  writable: true,
4298
4246
  configurable: true,
4299
4247
  });
4300
-
4248
+
4301
4249
  const event = new Event('fullscreenchange');
4302
4250
  document.dispatchEvent(event);
4303
-
4251
+
4304
4252
  await executeAfterWait(() => {
4305
4253
  expect(mockProcessTrigger).toHaveBeenLastCalledWith(
4306
4254
  expect.any(Object),
@@ -4318,7 +4266,7 @@ describe('Fullscreen change handler behavior', () => {
4318
4266
  liveboardId: 'test-lb-fullscreen',
4319
4267
  });
4320
4268
  await liveboardEmbed.render();
4321
-
4269
+
4322
4270
  await executeAfterWait(() => {
4323
4271
  const iframe = getIFrameEl();
4324
4272
  expect(iframe).toBeTruthy();
@@ -4326,18 +4274,18 @@ describe('Fullscreen change handler behavior', () => {
4326
4274
 
4327
4275
  mockProcessTrigger.mockClear();
4328
4276
  mockProcessTrigger.mockResolvedValue({});
4329
-
4277
+
4330
4278
  liveboardEmbed['setupFullscreenChangeHandler']();
4331
-
4279
+
4332
4280
  Object.defineProperty(document, 'fullscreenElement', {
4333
4281
  value: getIFrameEl(),
4334
4282
  writable: true,
4335
4283
  configurable: true,
4336
4284
  });
4337
-
4285
+
4338
4286
  const event = new Event('fullscreenchange');
4339
4287
  document.dispatchEvent(event);
4340
-
4288
+
4341
4289
  await executeAfterWait(() => {
4342
4290
  expect(mockProcessTrigger).not.toHaveBeenCalledWith(
4343
4291
  expect.any(Object),
@@ -4365,7 +4313,7 @@ describe('ShowPreRender with UpdateEmbedParams', () => {
4365
4313
  preRenderId,
4366
4314
  ...initialConfig,
4367
4315
  });
4368
-
4316
+
4369
4317
  await embed1.preRender();
4370
4318
  await waitFor(() => !!getIFrameEl());
4371
4319
 
@@ -1602,6 +1602,9 @@ export class TsEmbed {
1602
1602
  try {
1603
1603
  this.removeFullscreenChangeHandler();
1604
1604
  this.unsubscribeToEvents();
1605
+ if (!this.isRendered) {
1606
+ return;
1607
+ }
1605
1608
  if (!getEmbedConfig().waitForCleanupOnDestroy) {
1606
1609
  this.trigger(HostEvent.DestroyEmbed)
1607
1610
  this.insertedDomEl?.parentNode?.removeChild(this.insertedDomEl);
@@ -1610,10 +1613,14 @@ export class TsEmbed {
1610
1613
  Promise.race([
1611
1614
  this.trigger(HostEvent.DestroyEmbed),
1612
1615
  new Promise((resolve) => setTimeout(resolve, cleanupTimeout)),
1613
- ]).then(() => {
1614
- this.insertedDomEl?.parentNode?.removeChild(this.insertedDomEl);
1615
- }).catch((e) => {
1616
+ ]).catch((e) => {
1616
1617
  logger.log('Error destroying TS Embed', e);
1618
+ }).finally(() => {
1619
+ try {
1620
+ this.insertedDomEl?.parentNode?.removeChild(this.insertedDomEl);
1621
+ } catch (e) {
1622
+ logger.log('Error removing DOM element on destroy', e);
1623
+ }
1617
1624
  });
1618
1625
  }
1619
1626
  } catch (e) {
package/src/errors.ts CHANGED
@@ -4,7 +4,6 @@ export const ERROR_MESSAGE = {
4
4
  LIVEBOARD_VIZ_ID_VALIDATION: 'Please select a Liveboard to embed.',
5
5
  TRIGGER_TIMED_OUT: 'Trigger timed-out in getting a response',
6
6
  SEARCHEMBED_BETA_WRANING_MESSAGE: 'SearchEmbed is in Beta in this release.',
7
- SAGE_EMBED_BETA_WARNING_MESSAGE: 'SageEmbed is in Beta in this release.',
8
7
  THIRD_PARTY_COOKIE_BLOCKED_ALERT: 'Third-party cookie access is blocked on this browser. Please allow third-party cookies for this to work properly. \nYou can use `suppressNoCookieAccessAlert` to suppress this message.',
9
8
  DUPLICATE_TOKEN_ERR: 'Duplicate token. Please issue a new token every time getAuthToken callback is called. See https://developers.thoughtspot.com/docs/?pageid=embed-auth#trusted-auth-embed for more details.',
10
9
  SDK_NOT_INITIALIZED: 'SDK not initialized',
package/src/index.ts CHANGED
@@ -67,13 +67,12 @@ import {
67
67
  CustomActionTarget,
68
68
  InterceptedApiType,
69
69
  EmbedErrorCodes,
70
- EmbedErrorDetailsEvent,
70
+ EmbedErrorDetailsEvent,
71
71
  ErrorDetailsTypes,
72
72
  ContextType,
73
73
  AutoMCPFrameRendererViewConfig,
74
74
  } from './types';
75
75
  import { CustomCssVariables } from './css-variables';
76
- import { SageEmbed, SageViewConfig } from './embed/sage';
77
76
  import { AnswerService, SessionInterface, UnderlyingDataPoint } from './utils/graphql/answerService/answerService';
78
77
  import { getEmbedConfig } from './embed/embedConfig';
79
78
  import { uploadMixpanelEvent, MIXPANEL_EVENT } from './mixpanel-service';
@@ -100,7 +99,6 @@ export {
100
99
  SearchBarEmbed,
101
100
  PinboardEmbed,
102
101
  LiveboardEmbed,
103
- SageEmbed,
104
102
  AppEmbed,
105
103
  SpotterAgentEmbed,
106
104
  SpotterAgentEmbedViewConfig,
@@ -134,7 +132,6 @@ export {
134
132
  SearchViewConfig,
135
133
  SearchBarViewConfig,
136
134
  LiveboardViewConfig,
137
- SageViewConfig,
138
135
  AppViewConfig,
139
136
  PrefetchFeatures,
140
137
  FrameParams,
@@ -171,4 +168,4 @@ export {
171
168
  };
172
169
 
173
170
  export { resetCachedAuthToken } from './authToken';
174
- export { startAutoMCPFrameRenderer } from './embed/auto-frame-renderer';
171
+ export { startAutoMCPFrameRenderer } from './embed/auto-frame-renderer';
@@ -7,8 +7,6 @@ export {
7
7
  PreRenderedSearchBarEmbed,
8
8
  AppEmbed,
9
9
  PreRenderedAppEmbed,
10
- SageEmbed,
11
- PreRenderedSageEmbed,
12
10
  SpotterEmbed,
13
11
  ConversationEmbed,
14
12
  PreRenderedConversationEmbed,
@@ -44,7 +42,6 @@ export {
44
42
  SearchViewConfig,
45
43
  SearchBarViewConfig,
46
44
  LiveboardViewConfig,
47
- SageViewConfig,
48
45
  AppViewConfig,
49
46
  PrefetchFeatures,
50
47
  FrameParams,