@thoughtspot/visual-embed-sdk 1.47.1 → 1.47.3

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 (159) hide show
  1. package/cjs/package.json +3 -3
  2. package/cjs/src/embed/app.d.ts +19 -1
  3. package/cjs/src/embed/app.d.ts.map +1 -1
  4. package/cjs/src/embed/app.js +7 -1
  5. package/cjs/src/embed/app.js.map +1 -1
  6. package/cjs/src/embed/app.spec.js +141 -0
  7. package/cjs/src/embed/app.spec.js.map +1 -1
  8. package/cjs/src/embed/conversation.d.ts +2 -1
  9. package/cjs/src/embed/conversation.d.ts.map +1 -1
  10. package/cjs/src/embed/conversation.js.map +1 -1
  11. package/cjs/src/embed/hostEventClient/contracts.d.ts +4 -1
  12. package/cjs/src/embed/hostEventClient/contracts.d.ts.map +1 -1
  13. package/cjs/src/embed/hostEventClient/host-event-client.spec.js +2 -2
  14. package/cjs/src/embed/hostEventClient/host-event-client.spec.js.map +1 -1
  15. package/cjs/src/embed/liveboard.d.ts +13 -0
  16. package/cjs/src/embed/liveboard.d.ts.map +1 -1
  17. package/cjs/src/embed/liveboard.js +7 -1
  18. package/cjs/src/embed/liveboard.js.map +1 -1
  19. package/cjs/src/embed/liveboard.spec.js +54 -0
  20. package/cjs/src/embed/liveboard.spec.js.map +1 -1
  21. package/cjs/src/embed/search.d.ts +24 -1
  22. package/cjs/src/embed/search.d.ts.map +1 -1
  23. package/cjs/src/embed/search.js +15 -2
  24. package/cjs/src/embed/search.js.map +1 -1
  25. package/cjs/src/embed/search.spec.js +99 -0
  26. package/cjs/src/embed/search.spec.js.map +1 -1
  27. package/cjs/src/embed/spotter-utils.d.ts +3 -0
  28. package/cjs/src/embed/spotter-utils.d.ts.map +1 -1
  29. package/cjs/src/embed/spotter-utils.js +11 -3
  30. package/cjs/src/embed/spotter-utils.js.map +1 -1
  31. package/cjs/src/embed/spotter-utils.spec.js +51 -0
  32. package/cjs/src/embed/spotter-utils.spec.js.map +1 -1
  33. package/cjs/src/embed/ts-embed.d.ts +14 -1
  34. package/cjs/src/embed/ts-embed.d.ts.map +1 -1
  35. package/cjs/src/embed/ts-embed.js +29 -3
  36. package/cjs/src/embed/ts-embed.js.map +1 -1
  37. package/cjs/src/embed/ts-embed.spec.js +139 -0
  38. package/cjs/src/embed/ts-embed.spec.js.map +1 -1
  39. package/cjs/src/index.d.ts +2 -2
  40. package/cjs/src/index.d.ts.map +1 -1
  41. package/cjs/src/index.js +8 -1
  42. package/cjs/src/index.js.map +1 -1
  43. package/cjs/src/mixpanel-service.d.ts.map +1 -1
  44. package/cjs/src/mixpanel-service.js +2 -0
  45. package/cjs/src/mixpanel-service.js.map +1 -1
  46. package/cjs/src/mixpanel-service.spec.js +2 -0
  47. package/cjs/src/mixpanel-service.spec.js.map +1 -1
  48. package/cjs/src/test/test-utils.d.ts +1 -0
  49. package/cjs/src/test/test-utils.d.ts.map +1 -1
  50. package/cjs/src/test/test-utils.js +26 -1
  51. package/cjs/src/test/test-utils.js.map +1 -1
  52. package/cjs/src/types.d.ts +532 -6
  53. package/cjs/src/types.d.ts.map +1 -1
  54. package/cjs/src/types.js +225 -3
  55. package/cjs/src/types.js.map +1 -1
  56. package/dist/{index-CUgxBnPm.js → index-DZq20cR6.js} +1 -1
  57. package/dist/src/embed/app.d.ts +19 -1
  58. package/dist/src/embed/app.d.ts.map +1 -1
  59. package/dist/src/embed/conversation.d.ts +2 -1
  60. package/dist/src/embed/conversation.d.ts.map +1 -1
  61. package/dist/src/embed/hostEventClient/contracts.d.ts +4 -1
  62. package/dist/src/embed/hostEventClient/contracts.d.ts.map +1 -1
  63. package/dist/src/embed/liveboard.d.ts +13 -0
  64. package/dist/src/embed/liveboard.d.ts.map +1 -1
  65. package/dist/src/embed/search.d.ts +24 -1
  66. package/dist/src/embed/search.d.ts.map +1 -1
  67. package/dist/src/embed/spotter-utils.d.ts +3 -0
  68. package/dist/src/embed/spotter-utils.d.ts.map +1 -1
  69. package/dist/src/embed/ts-embed.d.ts +14 -1
  70. package/dist/src/embed/ts-embed.d.ts.map +1 -1
  71. package/dist/src/index.d.ts +2 -2
  72. package/dist/src/index.d.ts.map +1 -1
  73. package/dist/src/mixpanel-service.d.ts.map +1 -1
  74. package/dist/src/test/test-utils.d.ts +1 -0
  75. package/dist/src/test/test-utils.d.ts.map +1 -1
  76. package/dist/src/types.d.ts +532 -6
  77. package/dist/src/types.d.ts.map +1 -1
  78. package/dist/tsembed-react.es.js +299 -17
  79. package/dist/tsembed-react.js +298 -16
  80. package/dist/tsembed.es.js +300 -18
  81. package/dist/tsembed.js +298 -16
  82. package/dist/visual-embed-sdk-react-full.d.ts +572 -6
  83. package/dist/visual-embed-sdk-react.d.ts +572 -6
  84. package/dist/visual-embed-sdk.d.ts +600 -6
  85. package/lib/package.json +3 -3
  86. package/lib/src/embed/app.d.ts +19 -1
  87. package/lib/src/embed/app.d.ts.map +1 -1
  88. package/lib/src/embed/app.js +7 -1
  89. package/lib/src/embed/app.js.map +1 -1
  90. package/lib/src/embed/app.spec.js +142 -1
  91. package/lib/src/embed/app.spec.js.map +1 -1
  92. package/lib/src/embed/conversation.d.ts +2 -1
  93. package/lib/src/embed/conversation.d.ts.map +1 -1
  94. package/lib/src/embed/conversation.js.map +1 -1
  95. package/lib/src/embed/hostEventClient/contracts.d.ts +4 -1
  96. package/lib/src/embed/hostEventClient/contracts.d.ts.map +1 -1
  97. package/lib/src/embed/hostEventClient/host-event-client.spec.js +2 -2
  98. package/lib/src/embed/hostEventClient/host-event-client.spec.js.map +1 -1
  99. package/lib/src/embed/liveboard.d.ts +13 -0
  100. package/lib/src/embed/liveboard.d.ts.map +1 -1
  101. package/lib/src/embed/liveboard.js +7 -1
  102. package/lib/src/embed/liveboard.js.map +1 -1
  103. package/lib/src/embed/liveboard.spec.js +54 -0
  104. package/lib/src/embed/liveboard.spec.js.map +1 -1
  105. package/lib/src/embed/search.d.ts +24 -1
  106. package/lib/src/embed/search.d.ts.map +1 -1
  107. package/lib/src/embed/search.js +15 -2
  108. package/lib/src/embed/search.js.map +1 -1
  109. package/lib/src/embed/search.spec.js +100 -1
  110. package/lib/src/embed/search.spec.js.map +1 -1
  111. package/lib/src/embed/spotter-utils.d.ts +3 -0
  112. package/lib/src/embed/spotter-utils.d.ts.map +1 -1
  113. package/lib/src/embed/spotter-utils.js +11 -3
  114. package/lib/src/embed/spotter-utils.js.map +1 -1
  115. package/lib/src/embed/spotter-utils.spec.js +51 -0
  116. package/lib/src/embed/spotter-utils.spec.js.map +1 -1
  117. package/lib/src/embed/ts-embed.d.ts +14 -1
  118. package/lib/src/embed/ts-embed.d.ts.map +1 -1
  119. package/lib/src/embed/ts-embed.js +28 -2
  120. package/lib/src/embed/ts-embed.js.map +1 -1
  121. package/lib/src/embed/ts-embed.spec.js +139 -0
  122. package/lib/src/embed/ts-embed.spec.js.map +1 -1
  123. package/lib/src/index.d.ts +2 -2
  124. package/lib/src/index.d.ts.map +1 -1
  125. package/lib/src/index.js +2 -2
  126. package/lib/src/index.js.map +1 -1
  127. package/lib/src/mixpanel-service.d.ts.map +1 -1
  128. package/lib/src/mixpanel-service.js +2 -0
  129. package/lib/src/mixpanel-service.js.map +1 -1
  130. package/lib/src/mixpanel-service.spec.js +2 -0
  131. package/lib/src/mixpanel-service.spec.js.map +1 -1
  132. package/lib/src/test/test-utils.d.ts +1 -0
  133. package/lib/src/test/test-utils.d.ts.map +1 -1
  134. package/lib/src/test/test-utils.js +25 -1
  135. package/lib/src/test/test-utils.js.map +1 -1
  136. package/lib/src/types.d.ts +532 -6
  137. package/lib/src/types.d.ts.map +1 -1
  138. package/lib/src/types.js +224 -2
  139. package/lib/src/types.js.map +1 -1
  140. package/lib/src/visual-embed-sdk.d.ts +600 -6
  141. package/package.json +3 -3
  142. package/src/embed/app.spec.ts +179 -0
  143. package/src/embed/app.ts +29 -0
  144. package/src/embed/conversation.ts +2 -1
  145. package/src/embed/hostEventClient/contracts.ts +2 -1
  146. package/src/embed/hostEventClient/host-event-client.spec.ts +2 -2
  147. package/src/embed/liveboard.spec.ts +73 -0
  148. package/src/embed/liveboard.ts +23 -0
  149. package/src/embed/search.spec.ts +118 -0
  150. package/src/embed/search.ts +43 -1
  151. package/src/embed/spotter-utils.spec.ts +52 -0
  152. package/src/embed/spotter-utils.ts +19 -3
  153. package/src/embed/ts-embed.spec.ts +185 -0
  154. package/src/embed/ts-embed.ts +29 -3
  155. package/src/index.ts +16 -0
  156. package/src/mixpanel-service.spec.ts +2 -0
  157. package/src/mixpanel-service.ts +2 -0
  158. package/src/test/test-utils.ts +33 -1
  159. package/src/types.ts +561 -2
@@ -15,6 +15,7 @@ import {
15
15
  SearchLiveboardCommonViewConfig,
16
16
  DefaultAppInitData,
17
17
  BaseViewConfig,
18
+ VisualizationOverrides,
18
19
  } from '../types';
19
20
  import {
20
21
  getQueryParamString,
@@ -317,6 +318,27 @@ export interface SearchViewConfig
317
318
  * ```
318
319
  */
319
320
  focusSearchBarOnRender?: boolean;
321
+ /**
322
+ * Enable or disable Muze chart phase 1 GA
323
+ *
324
+ * Supported embed types: `SearchEmbed`
325
+ * @version SDK: 1.49.0 | ThoughtSpot Cloud: 26.6.0.cl
326
+ * @default false
327
+ * @example
328
+ * ```js
329
+ * const embed = new SearchEmbed('#tsEmbed', {
330
+ * ... // other embed view config
331
+ * newChartsLibrary: true,
332
+ * })
333
+ * ```
334
+ */
335
+ newChartsLibrary?: boolean;
336
+
337
+ /**
338
+ * Visual overrides to customize the chart or table properties.
339
+ * @version SDK: 1.49.0 | ThoughtSpot: 26.6.0.cl
340
+ */
341
+ visualOverrides?: VisualizationOverrides;
320
342
  }
321
343
 
322
344
  export const HiddenActionItemByDefaultForSearchEmbed = [
@@ -329,6 +351,9 @@ export const HiddenActionItemByDefaultForSearchEmbed = [
329
351
 
330
352
  export interface SearchAppInitData extends DefaultAppInitData {
331
353
  searchOptions?: SearchOptions;
354
+ embedParams?: {
355
+ visualOverridesParams?: VisualizationOverrides | null;
356
+ };
332
357
  }
333
358
 
334
359
  /**
@@ -380,7 +405,19 @@ export class SearchEmbed extends TsEmbed {
380
405
 
381
406
  protected async getAppInitData(): Promise<SearchAppInitData> {
382
407
  const defaultAppInitData = await super.getAppInitData();
383
- return { ...defaultAppInitData, ...this.getSearchInitData() };
408
+ const result: SearchAppInitData = {
409
+ ...defaultAppInitData,
410
+ ...this.getSearchInitData(),
411
+ };
412
+
413
+ if (this.viewConfig.visualOverrides) {
414
+ result.embedParams = {
415
+ ...((defaultAppInitData as any).embedParams || {}),
416
+ visualOverridesParams: this.viewConfig.visualOverrides,
417
+ };
418
+ }
419
+
420
+ return result;
384
421
  }
385
422
 
386
423
  protected getEmbedParamsObject() {
@@ -405,6 +442,7 @@ export class SearchEmbed extends TsEmbed {
405
442
  excludeSearchTokenStringFromURL,
406
443
  collapseSearchBar = true,
407
444
  isThisPeriodInDateFiltersEnabled,
445
+ newChartsLibrary,
408
446
  } = this.viewConfig;
409
447
  const queryParams = this.getBaseQueryParams();
410
448
 
@@ -451,6 +489,10 @@ export class SearchEmbed extends TsEmbed {
451
489
  queryParams[Param.IsThisPeriodInDateFiltersEnabled] = isThisPeriodInDateFiltersEnabled;
452
490
  }
453
491
 
492
+ if (newChartsLibrary !== undefined) {
493
+ queryParams[Param.EnableNewChartLibrary] = newChartsLibrary;
494
+ }
495
+
454
496
  queryParams[Param.DataPanelV2Enabled] = dataPanelV2;
455
497
  queryParams[Param.DataSourceMode] = this.getDataSourceMode();
456
498
 
@@ -53,4 +53,56 @@ describe('buildSpotterSidebarAppInitData', () => {
53
53
  }));
54
54
  expect(result.embedParams?.spotterSidebarConfig?.spotterDocumentationUrl).toBeUndefined();
55
55
  });
56
+
57
+ it('returns base with visualOverridesParams when only visualOverrides is provided', () => {
58
+ const visualOverrides = {
59
+ chart: {
60
+ legend: { show: true, position: 'bottom' as const },
61
+ },
62
+ };
63
+ const result = buildSpotterSidebarAppInitData(base, {
64
+ visualOverrides,
65
+ }, noopError);
66
+ expect(result).toEqual({
67
+ ...base,
68
+ embedParams: { visualOverridesParams: visualOverrides },
69
+ });
70
+ });
71
+
72
+ it('includes visualOverridesParams with spotterSidebarConfig', () => {
73
+ const visualOverrides = {
74
+ table: {
75
+ display: { tableTheme: 'ZEBRA' },
76
+ },
77
+ };
78
+ const result = buildSpotterSidebarAppInitData(base, {
79
+ spotterSidebarConfig: { enablePastConversationsSidebar: true },
80
+ visualOverrides,
81
+ }, noopError);
82
+ expect(result.embedParams?.spotterSidebarConfig?.enablePastConversationsSidebar).toBe(true);
83
+ expect(result.embedParams?.visualOverridesParams).toEqual(visualOverrides);
84
+ });
85
+
86
+ it('includes visualOverridesParams with standalone enablePastConversationsSidebar flag', () => {
87
+ const visualOverrides = {
88
+ chart: {
89
+ legend: { show: false },
90
+ },
91
+ };
92
+ const result = buildSpotterSidebarAppInitData(base, {
93
+ enablePastConversationsSidebar: true,
94
+ visualOverrides,
95
+ }, noopError);
96
+ expect(result.embedParams?.spotterSidebarConfig?.enablePastConversationsSidebar).toBe(true);
97
+ expect(result.embedParams?.visualOverridesParams).toEqual(visualOverrides);
98
+ });
99
+
100
+ it('does not include visualOverridesParams when it is undefined', () => {
101
+ const result = buildSpotterSidebarAppInitData(base, {
102
+ spotterSidebarConfig: { enablePastConversationsSidebar: true },
103
+ visualOverrides: undefined,
104
+ }, noopError);
105
+ expect(result.embedParams?.visualOverridesParams).toBeUndefined();
106
+ expect(result.embedParams?.spotterSidebarConfig?.enablePastConversationsSidebar).toBe(true);
107
+ });
56
108
  });
@@ -2,6 +2,7 @@ import { DefaultAppInitData, ErrorDetailsTypes, EmbedErrorCodes } from '../types
2
2
  import { validateHttpUrl } from '../utils';
3
3
  import { ERROR_MESSAGE } from '../errors';
4
4
  import type { SpotterSidebarViewConfig } from './conversation';
5
+ import type { VisualizationOverrides } from '../types';
5
6
 
6
7
  /**
7
8
  * Resolves enablePastConversationsSidebar with
@@ -22,10 +23,16 @@ export function buildSpotterSidebarAppInitData<T extends DefaultAppInitData>(
22
23
  viewConfig: {
23
24
  spotterSidebarConfig?: SpotterSidebarViewConfig;
24
25
  enablePastConversationsSidebar?: boolean;
26
+ visualOverrides?: VisualizationOverrides;
25
27
  },
26
28
  handleError: (err: any) => void,
27
- ): T & { embedParams?: { spotterSidebarConfig?: SpotterSidebarViewConfig } } {
28
- const { spotterSidebarConfig, enablePastConversationsSidebar } = viewConfig;
29
+ ): T & {
30
+ embedParams?: {
31
+ spotterSidebarConfig?: SpotterSidebarViewConfig;
32
+ visualOverridesParams?: VisualizationOverrides | null;
33
+ };
34
+ } {
35
+ const { spotterSidebarConfig, enablePastConversationsSidebar, visualOverrides } = viewConfig;
29
36
 
30
37
  const resolvedEnablePastConversations = resolveEnablePastConversationsSidebar({
31
38
  spotterSidebarConfigValue: spotterSidebarConfig?.enablePastConversationsSidebar,
@@ -33,7 +40,15 @@ export function buildSpotterSidebarAppInitData<T extends DefaultAppInitData>(
33
40
  });
34
41
 
35
42
  const hasConfig = spotterSidebarConfig || resolvedEnablePastConversations !== undefined;
36
- if (!hasConfig) return defaultAppInitData;
43
+ if (!hasConfig) {
44
+ if (visualOverrides === undefined) {
45
+ return defaultAppInitData;
46
+ }
47
+ return {
48
+ ...defaultAppInitData,
49
+ embedParams: { visualOverridesParams: visualOverrides },
50
+ };
51
+ }
37
52
 
38
53
  const resolvedSidebarConfig: SpotterSidebarViewConfig = {
39
54
  ...spotterSidebarConfig,
@@ -60,6 +75,7 @@ export function buildSpotterSidebarAppInitData<T extends DefaultAppInitData>(
60
75
  embedParams: {
61
76
  ...((defaultAppInitData as any).embedParams || {}),
62
77
  spotterSidebarConfig: resolvedSidebarConfig,
78
+ ...(visualOverrides !== undefined ? { visualOverridesParams: visualOverrides } : {}),
63
79
  },
64
80
  };
65
81
  }
@@ -4504,4 +4504,189 @@ describe('ShowPreRender with UpdateEmbedParams', () => {
4504
4504
  expect(iframe.allow).toContain('clipboard-read');
4505
4505
  expect(iframe.allow).toContain('clipboard-write');
4506
4506
  });
4507
+
4508
+ describe('shouldSkipEvent', () => {
4509
+ beforeAll(() => {
4510
+ init({
4511
+ thoughtSpotHost: 'tshost',
4512
+ authType: AuthType.None,
4513
+ });
4514
+ });
4515
+
4516
+ // Matches the structure produced by createValidationError / embedErrorDetails
4517
+ const makeNestedValidationData = (message = 'invalid payload') => ({
4518
+ type: EmbedEvent.Error,
4519
+ data: {
4520
+ errorType: 'VALIDATION_ERROR',
4521
+ message,
4522
+ code: EmbedErrorCodes.HOST_EVENT_VALIDATION,
4523
+ error: message,
4524
+ },
4525
+ });
4526
+
4527
+ // Matches the flat structure where errorType sits at the top level of data
4528
+ const makeFlatValidationData = (message = 'invalid payload') => ({
4529
+ errorType: EmbedErrorCodes.HOST_EVENT_VALIDATION,
4530
+ message,
4531
+ code: EmbedErrorCodes.HOST_EVENT_VALIDATION,
4532
+ });
4533
+
4534
+ const makeEmbed = (viewConfig: Partial<SearchViewConfig>) => {
4535
+ const embed = new SearchEmbed(getRootEl(), {
4536
+ ...defaultViewConfig,
4537
+ ...viewConfig,
4538
+ });
4539
+ return embed;
4540
+ };
4541
+
4542
+ test('skips Error event and logs warning when useHostEventsV2 is true and shouldBypassPayloadValidation is true', () => {
4543
+ jest.spyOn(logger, 'warn');
4544
+ const errorHandler = jest.fn();
4545
+ const embed = makeEmbed({ useHostEventsV2: true, shouldBypassPayloadValidation: true });
4546
+ embed.on(EmbedEvent.Error, errorHandler);
4547
+
4548
+ (embed as any).executeCallbacks(EmbedEvent.Error, makeNestedValidationData());
4549
+
4550
+ expect(errorHandler).not.toHaveBeenCalled();
4551
+ expect(logger.warn).toHaveBeenCalledWith(
4552
+ 'Host Event Validation failed: invalid payload',
4553
+ );
4554
+ });
4555
+
4556
+ test('skips Error event when errorType is resolved from data.data.code (nested format)', () => {
4557
+ jest.spyOn(logger, 'warn');
4558
+ const errorHandler = jest.fn();
4559
+ const embed = makeEmbed({ useHostEventsV2: true, shouldBypassPayloadValidation: true });
4560
+ embed.on(EmbedEvent.Error, errorHandler);
4561
+
4562
+ (embed as any).executeCallbacks(EmbedEvent.Error, makeNestedValidationData('nested error'));
4563
+
4564
+ expect(errorHandler).not.toHaveBeenCalled();
4565
+ expect(logger.warn).toHaveBeenCalledWith(
4566
+ 'Host Event Validation failed: nested error',
4567
+ );
4568
+ });
4569
+
4570
+ test('skips Error event when errorType is resolved from data.errorType (flat format)', () => {
4571
+ jest.spyOn(logger, 'warn');
4572
+ const errorHandler = jest.fn();
4573
+ const embed = makeEmbed({ useHostEventsV2: true, shouldBypassPayloadValidation: true });
4574
+ embed.on(EmbedEvent.Error, errorHandler);
4575
+
4576
+ (embed as any).executeCallbacks(EmbedEvent.Error, makeFlatValidationData());
4577
+
4578
+ expect(errorHandler).not.toHaveBeenCalled();
4579
+ });
4580
+
4581
+ test('delivers Error event to handler when useHostEventsV2 is true and shouldBypassPayloadValidation is undefined', () => {
4582
+ const errorHandler = jest.fn();
4583
+ const embed = makeEmbed({ useHostEventsV2: true, shouldBypassPayloadValidation: undefined });
4584
+ embed.on(EmbedEvent.Error, errorHandler);
4585
+
4586
+ (embed as any).executeCallbacks(EmbedEvent.Error, makeNestedValidationData());
4587
+
4588
+ expect(errorHandler).toHaveBeenCalled();
4589
+ });
4590
+
4591
+ test('delivers Error event to handler when useHostEventsV2 is true and shouldBypassPayloadValidation is false', () => {
4592
+ const errorHandler = jest.fn();
4593
+ const embed = makeEmbed({ useHostEventsV2: true, shouldBypassPayloadValidation: false });
4594
+ embed.on(EmbedEvent.Error, errorHandler);
4595
+
4596
+ (embed as any).executeCallbacks(EmbedEvent.Error, makeNestedValidationData());
4597
+
4598
+ expect(errorHandler).toHaveBeenCalled();
4599
+ });
4600
+
4601
+ test('skips Error event when useHostEventsV2 is false regardless of shouldBypassPayloadValidation', () => {
4602
+ jest.spyOn(logger, 'warn');
4603
+ const errorHandler = jest.fn();
4604
+ const embed = makeEmbed({ useHostEventsV2: false, shouldBypassPayloadValidation: undefined });
4605
+ embed.on(EmbedEvent.Error, errorHandler);
4606
+
4607
+ (embed as any).executeCallbacks(EmbedEvent.Error, makeNestedValidationData());
4608
+
4609
+ expect(errorHandler).not.toHaveBeenCalled();
4610
+ expect(logger.warn).toHaveBeenCalledWith(
4611
+ 'Host Event Validation failed: invalid payload',
4612
+ );
4613
+ });
4614
+
4615
+ test('logs warning with undefined message when flat format has no nested data', () => {
4616
+ jest.spyOn(logger, 'warn');
4617
+ const embed = makeEmbed({ useHostEventsV2: true, shouldBypassPayloadValidation: true });
4618
+ embed.on(EmbedEvent.Error, jest.fn());
4619
+
4620
+ (embed as any).executeCallbacks(EmbedEvent.Error, makeFlatValidationData());
4621
+
4622
+ expect(logger.warn).toHaveBeenCalledWith('Host Event Validation failed: undefined');
4623
+ });
4624
+
4625
+ test('skips Error event when useHostEventsV2 is false and shouldBypassPayloadValidation is true', () => {
4626
+ jest.spyOn(logger, 'warn');
4627
+ const errorHandler = jest.fn();
4628
+ const embed = makeEmbed({ useHostEventsV2: false, shouldBypassPayloadValidation: true });
4629
+ embed.on(EmbedEvent.Error, errorHandler);
4630
+
4631
+ (embed as any).executeCallbacks(EmbedEvent.Error, makeNestedValidationData());
4632
+
4633
+ expect(errorHandler).not.toHaveBeenCalled();
4634
+ expect(logger.warn).toHaveBeenCalledWith('Host Event Validation failed: invalid payload');
4635
+ });
4636
+
4637
+ test('skips via handleError when shouldBypassPayloadValidation is true', () => {
4638
+ jest.spyOn(logger, 'warn');
4639
+ const errorHandler = jest.fn();
4640
+ const embed = makeEmbed({ useHostEventsV2: true, shouldBypassPayloadValidation: true });
4641
+ embed.on(EmbedEvent.Error, errorHandler);
4642
+
4643
+ (embed as any).handleError({
4644
+ type: EmbedEvent.Error,
4645
+ data: {
4646
+ errorType: 'VALIDATION_ERROR',
4647
+ message: 'bad payload',
4648
+ code: EmbedErrorCodes.HOST_EVENT_VALIDATION,
4649
+ error: 'bad payload',
4650
+ },
4651
+ });
4652
+
4653
+ expect(errorHandler).not.toHaveBeenCalled();
4654
+ expect(logger.warn).toHaveBeenCalledWith('Host Event Validation failed: bad payload');
4655
+ });
4656
+
4657
+ test('delivers Error event to EmbedEvent.ALL handler when not skipped', () => {
4658
+ const allHandler = jest.fn();
4659
+ const embed = makeEmbed({ useHostEventsV2: true, shouldBypassPayloadValidation: false });
4660
+ embed.on(EmbedEvent.ALL, allHandler);
4661
+
4662
+ (embed as any).executeCallbacks(EmbedEvent.Error, makeNestedValidationData());
4663
+
4664
+ expect(allHandler).toHaveBeenCalled();
4665
+ });
4666
+
4667
+ test('does not skip non-Error events even with HOST_EVENT_VALIDATION error code', () => {
4668
+ const customActionHandler = jest.fn();
4669
+ const embed = makeEmbed({ useHostEventsV2: true, shouldBypassPayloadValidation: false });
4670
+ embed.on(EmbedEvent.CustomAction, customActionHandler);
4671
+
4672
+ (embed as any).executeCallbacks(EmbedEvent.CustomAction, {
4673
+ data: { code: EmbedErrorCodes.HOST_EVENT_VALIDATION },
4674
+ });
4675
+
4676
+ expect(customActionHandler).toHaveBeenCalled();
4677
+ });
4678
+
4679
+ test('does not skip Error events with unrelated error codes', () => {
4680
+ const errorHandler = jest.fn();
4681
+ const embed = makeEmbed({ useHostEventsV2: true, shouldBypassPayloadValidation: false });
4682
+ embed.on(EmbedEvent.Error, errorHandler);
4683
+
4684
+ (embed as any).executeCallbacks(EmbedEvent.Error, {
4685
+ errorType: 'SOME_OTHER_ERROR',
4686
+ message: 'something else failed',
4687
+ });
4688
+
4689
+ expect(errorHandler).toHaveBeenCalled();
4690
+ });
4691
+ });
4507
4692
  });
@@ -67,7 +67,7 @@ import {
67
67
  } from '../types';
68
68
  import { uploadMixpanelEvent, MIXPANEL_EVENT } from '../mixpanel-service';
69
69
  import { processEventData, processAuthFailure } from '../utils/processData';
70
- import pkgInfo from '../../package.json';
70
+ import { version } from '../../package.json';
71
71
  import {
72
72
  getAuthPromise, renderInQueue, handleAuth, notifyAuthFailure,
73
73
  getInitPromise,
@@ -80,8 +80,6 @@ import { getPreauthInfo } from '../utils/sessionInfoService';
80
80
  import { HostEventClient } from './hostEventClient/host-event-client';
81
81
  import { getInterceptInitData, handleInterceptEvent, processApiInterceptResponse, processLegacyInterceptResponse } from '../api-intercept';
82
82
 
83
- const { version } = pkgInfo;
84
-
85
83
  /**
86
84
  * Global prefix for all ThoughtSpot postHash Params.
87
85
  */
@@ -1151,6 +1149,18 @@ export class TsEmbed {
1151
1149
  }
1152
1150
  }
1153
1151
 
1152
+ private shouldSkipEvent(eventType: EmbedEvent, data: any): boolean {
1153
+ const errorType = data?.errorType ?? data?.data?.code;
1154
+ if (
1155
+ eventType === EmbedEvent.Error
1156
+ && errorType === EmbedErrorCodes.HOST_EVENT_VALIDATION
1157
+ && (!getHostEventsConfig(this.viewConfig).useHostEventsV2 || getHostEventsConfig(this.viewConfig).shouldBypassPayloadValidation)
1158
+ ) {
1159
+ logger.warn(`Host Event Validation failed: ${data?.data?.message}`);
1160
+ return true;
1161
+ }
1162
+ return false;
1163
+ }
1154
1164
  /**
1155
1165
  * Executes all registered event handlers for a particular event type
1156
1166
  * @param eventType The event type
@@ -1162,6 +1172,7 @@ export class TsEmbed {
1162
1172
  data: any,
1163
1173
  eventPort?: MessagePort | void,
1164
1174
  ): void {
1175
+ if (this.shouldSkipEvent(eventType, data)) return;
1165
1176
  const eventHandlers = this.eventHandlerMap.get(eventType) || [];
1166
1177
  const allHandlers = this.eventHandlerMap.get(EmbedEvent.ALL) || [];
1167
1178
  const callbacks = [...eventHandlers, ...allHandlers];
@@ -1541,6 +1552,21 @@ export class TsEmbed {
1541
1552
  });
1542
1553
  }
1543
1554
 
1555
+ /**
1556
+ * Generates the event name for a "Subscribed" embed event.
1557
+ *
1558
+ * This helper appends the "Subscribed" suffix to a given host or action event,
1559
+ * allowing you to listen for subscription lifecycle events in a consistent format.
1560
+ *
1561
+ * @param eventName - The host or action event to generate the subscribed event name for.
1562
+ * @returns The formatted event name (e.g., "Save Subscribed").
1563
+ *
1564
+ * @version SDK: 1.47.2 | ThoughtSpot: 26.3.0.cl
1565
+ */
1566
+ public subscribedEvent(eventName: HostEvent | Action): string {
1567
+ return `${eventName} ${EmbedEvent.Subscribed}`;
1568
+ }
1569
+
1544
1570
  /**
1545
1571
  * Creates the preRender shell
1546
1572
  * @param showPreRenderByDefault - Show the preRender after render, hidden by default
package/src/index.ts CHANGED
@@ -71,6 +71,14 @@ import {
71
71
  ErrorDetailsTypes,
72
72
  ContextType,
73
73
  AutoMCPFrameRendererViewConfig,
74
+ LegendPosition,
75
+ BackgroundFormatType,
76
+ ConditionalFormattingComparisonType,
77
+ ConditionalFormattingOperator,
78
+ DataLabelFilterOperator,
79
+ TableTheme,
80
+ TableContentDensity,
81
+ VisualizationOverrides,
74
82
  } from './types';
75
83
  import { CustomCssVariables } from './css-variables';
76
84
  import { AnswerService, SessionInterface, UnderlyingDataPoint } from './utils/graphql/answerService/answerService';
@@ -165,6 +173,14 @@ export {
165
173
  EmbedErrorDetailsEvent,
166
174
  ErrorDetailsTypes,
167
175
  AutoMCPFrameRendererViewConfig,
176
+ VisualizationOverrides,
177
+ LegendPosition,
178
+ BackgroundFormatType,
179
+ ConditionalFormattingComparisonType,
180
+ ConditionalFormattingOperator,
181
+ DataLabelFilterOperator,
182
+ TableTheme,
183
+ TableContentDensity,
168
184
  };
169
185
 
170
186
  export { resetCachedAuthToken } from './authToken';
@@ -8,6 +8,7 @@ import {
8
8
  import { AuthType } from './types';
9
9
  import { SessionInfo } from './utils/sessionInfoService';
10
10
  import { logger } from './utils/logger';
11
+ import { version } from '../package.json';
11
12
 
12
13
  const config = {
13
14
  thoughtSpotHost: 'https://10.87.89.232',
@@ -60,6 +61,7 @@ describe('Unit test for mixpanel', () => {
60
61
  clusterName: sessionInfo.clusterName,
61
62
  releaseVersion: sessionInfo.releaseVersion,
62
63
  hostAppUrl: 'localhost',
64
+ sdkVersion: version,
63
65
  });
64
66
  expect(mixpanel.identify).not.toHaveBeenCalledWith(sessionInfo.userGUID);
65
67
  });
@@ -2,6 +2,7 @@ import * as mixpanel from 'mixpanel-browser';
2
2
  import { logger } from './utils/logger';
3
3
  import { SessionInfo } from './utils/sessionInfoService';
4
4
  import { ERROR_MESSAGE } from './errors';
5
+ import { version } from '../package.json';
5
6
 
6
7
  export const EndPoints = {
7
8
  CONFIG: '/callosum/v1/system/config',
@@ -80,6 +81,7 @@ export function initMixpanel(sessionInfo: SessionInfo): void {
80
81
  clusterName: sessionInfo.clusterName,
81
82
  releaseVersion: sessionInfo.releaseVersion,
82
83
  hostAppUrl: window?.location?.host || '',
84
+ sdkVersion: version,
83
85
  });
84
86
  isMixpanelInitialized = true;
85
87
  emptyQueue();
@@ -1,6 +1,6 @@
1
1
  import { has } from 'lodash';
2
2
  import { version } from '../../package.json';
3
- import { Action, AuthType } from '../types';
3
+ import { Action, AuthType, EmbedEvent } from '../types';
4
4
 
5
5
  /**
6
6
  Initialises fetch to the global object
@@ -166,3 +166,35 @@ export const mockSessionInfo = {
166
166
  genNo: 5,
167
167
  },
168
168
  };
169
+
170
+ export const testVisualOverridesInEmbed = async (
171
+ embed: any,
172
+ visualOverrides: any,
173
+ ) => {
174
+ const mockEmbedEventPayload = {
175
+ type: EmbedEvent.APP_INIT,
176
+ data: {},
177
+ };
178
+
179
+ embed.render();
180
+
181
+ const mockPort: any = {
182
+ postMessage: jest.fn(),
183
+ };
184
+
185
+ await executeAfterWait(() => {
186
+ const iframe = getIFrameEl();
187
+ postMessageToParent(iframe.contentWindow, mockEmbedEventPayload, mockPort);
188
+ });
189
+
190
+ await executeAfterWait(() => {
191
+ expect(mockPort.postMessage).toHaveBeenCalledWith({
192
+ type: EmbedEvent.APP_INIT,
193
+ data: expect.objectContaining({
194
+ embedParams: expect.objectContaining({
195
+ visualOverridesParams: visualOverrides,
196
+ }),
197
+ }),
198
+ });
199
+ });
200
+ };