@thoughtspot/visual-embed-sdk 1.46.5-beta.1 → 1.46.5

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 (180) hide show
  1. package/cjs/package.json +4 -4
  2. package/cjs/src/css-variables.d.ts +36 -0
  3. package/cjs/src/css-variables.d.ts.map +1 -1
  4. package/cjs/src/embed/app.d.ts +41 -2
  5. package/cjs/src/embed/app.d.ts.map +1 -1
  6. package/cjs/src/embed/app.js +26 -46
  7. package/cjs/src/embed/app.js.map +1 -1
  8. package/cjs/src/embed/app.spec.js +36 -69
  9. package/cjs/src/embed/app.spec.js.map +1 -1
  10. package/cjs/src/embed/conversation.d.ts +23 -1
  11. package/cjs/src/embed/conversation.d.ts.map +1 -1
  12. package/cjs/src/embed/conversation.js +18 -33
  13. package/cjs/src/embed/conversation.js.map +1 -1
  14. package/cjs/src/embed/conversation.spec.js +129 -97
  15. package/cjs/src/embed/conversation.spec.js.map +1 -1
  16. package/cjs/src/embed/hostEventClient/contracts.d.ts +31 -0
  17. package/cjs/src/embed/hostEventClient/contracts.d.ts.map +1 -1
  18. package/cjs/src/embed/hostEventClient/contracts.js +2 -0
  19. package/cjs/src/embed/hostEventClient/contracts.js.map +1 -1
  20. package/cjs/src/embed/hostEventClient/host-event-client.d.ts +18 -0
  21. package/cjs/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
  22. package/cjs/src/embed/hostEventClient/host-event-client.js +69 -9
  23. package/cjs/src/embed/hostEventClient/host-event-client.js.map +1 -1
  24. package/cjs/src/embed/hostEventClient/host-event-client.spec.js +185 -19
  25. package/cjs/src/embed/hostEventClient/host-event-client.spec.js.map +1 -1
  26. package/cjs/src/embed/hostEventClient/utils.d.ts +22 -0
  27. package/cjs/src/embed/hostEventClient/utils.d.ts.map +1 -0
  28. package/cjs/src/embed/hostEventClient/utils.js +51 -0
  29. package/cjs/src/embed/hostEventClient/utils.js.map +1 -0
  30. package/cjs/src/embed/hostEventClient/utils.spec.d.ts +2 -0
  31. package/cjs/src/embed/hostEventClient/utils.spec.d.ts.map +1 -0
  32. package/cjs/src/embed/hostEventClient/utils.spec.js +115 -0
  33. package/cjs/src/embed/hostEventClient/utils.spec.js.map +1 -0
  34. package/cjs/src/embed/liveboard.d.ts +18 -1
  35. package/cjs/src/embed/liveboard.d.ts.map +1 -1
  36. package/cjs/src/embed/liveboard.js +9 -11
  37. package/cjs/src/embed/liveboard.js.map +1 -1
  38. package/cjs/src/embed/liveboard.spec.js +29 -71
  39. package/cjs/src/embed/liveboard.spec.js.map +1 -1
  40. package/cjs/src/embed/spotter-utils.d.ts +20 -0
  41. package/cjs/src/embed/spotter-utils.d.ts.map +1 -0
  42. package/cjs/src/embed/spotter-utils.js +52 -0
  43. package/cjs/src/embed/spotter-utils.js.map +1 -0
  44. package/cjs/src/embed/spotter-utils.spec.d.ts +2 -0
  45. package/cjs/src/embed/spotter-utils.spec.d.ts.map +1 -0
  46. package/cjs/src/embed/spotter-utils.spec.js +54 -0
  47. package/cjs/src/embed/spotter-utils.spec.js.map +1 -0
  48. package/cjs/src/embed/ts-embed.d.ts.map +1 -1
  49. package/cjs/src/embed/ts-embed.js +13 -1
  50. package/cjs/src/embed/ts-embed.js.map +1 -1
  51. package/cjs/src/errors.d.ts +2 -0
  52. package/cjs/src/errors.d.ts.map +1 -1
  53. package/cjs/src/errors.js +2 -0
  54. package/cjs/src/errors.js.map +1 -1
  55. package/cjs/src/types.d.ts +102 -1
  56. package/cjs/src/types.d.ts.map +1 -1
  57. package/cjs/src/types.js +101 -0
  58. package/cjs/src/types.js.map +1 -1
  59. package/cjs/src/utils.d.ts +0 -9
  60. package/cjs/src/utils.d.ts.map +1 -1
  61. package/cjs/src/utils.js +1 -10
  62. package/cjs/src/utils.js.map +1 -1
  63. package/dist/index-ChNydfIz.js +7371 -0
  64. package/dist/index-DGV_zh53.js +7371 -0
  65. package/dist/src/css-variables.d.ts +36 -0
  66. package/dist/src/css-variables.d.ts.map +1 -1
  67. package/dist/src/embed/app.d.ts +41 -2
  68. package/dist/src/embed/app.d.ts.map +1 -1
  69. package/dist/src/embed/conversation.d.ts +23 -1
  70. package/dist/src/embed/conversation.d.ts.map +1 -1
  71. package/dist/src/embed/hostEventClient/contracts.d.ts +31 -0
  72. package/dist/src/embed/hostEventClient/contracts.d.ts.map +1 -1
  73. package/dist/src/embed/hostEventClient/host-event-client.d.ts +18 -0
  74. package/dist/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
  75. package/dist/src/embed/hostEventClient/utils.d.ts +22 -0
  76. package/dist/src/embed/hostEventClient/utils.d.ts.map +1 -0
  77. package/dist/src/embed/hostEventClient/utils.spec.d.ts +2 -0
  78. package/dist/src/embed/hostEventClient/utils.spec.d.ts.map +1 -0
  79. package/dist/src/embed/liveboard.d.ts +18 -1
  80. package/dist/src/embed/liveboard.d.ts.map +1 -1
  81. package/dist/src/embed/spotter-utils.d.ts +20 -0
  82. package/dist/src/embed/spotter-utils.d.ts.map +1 -0
  83. package/dist/src/embed/spotter-utils.spec.d.ts +2 -0
  84. package/dist/src/embed/spotter-utils.spec.d.ts.map +1 -0
  85. package/dist/src/embed/ts-embed.d.ts.map +1 -1
  86. package/dist/src/errors.d.ts +2 -0
  87. package/dist/src/errors.d.ts.map +1 -1
  88. package/dist/src/types.d.ts +102 -1
  89. package/dist/src/types.d.ts.map +1 -1
  90. package/dist/src/utils.d.ts +0 -9
  91. package/dist/src/utils.d.ts.map +1 -1
  92. package/dist/tsembed-react.es.js +324 -110
  93. package/dist/tsembed-react.js +323 -109
  94. package/dist/tsembed.es.js +324 -110
  95. package/dist/tsembed.js +323 -109
  96. package/dist/visual-embed-sdk-react-full.d.ts +266 -3
  97. package/dist/visual-embed-sdk-react.d.ts +266 -3
  98. package/dist/visual-embed-sdk.d.ts +266 -3
  99. package/lib/package.json +4 -4
  100. package/lib/src/css-variables.d.ts +36 -0
  101. package/lib/src/css-variables.d.ts.map +1 -1
  102. package/lib/src/embed/app.d.ts +41 -2
  103. package/lib/src/embed/app.d.ts.map +1 -1
  104. package/lib/src/embed/app.js +28 -48
  105. package/lib/src/embed/app.js.map +1 -1
  106. package/lib/src/embed/app.spec.js +36 -69
  107. package/lib/src/embed/app.spec.js.map +1 -1
  108. package/lib/src/embed/conversation.d.ts +23 -1
  109. package/lib/src/embed/conversation.d.ts.map +1 -1
  110. package/lib/src/embed/conversation.js +19 -34
  111. package/lib/src/embed/conversation.js.map +1 -1
  112. package/lib/src/embed/conversation.spec.js +131 -99
  113. package/lib/src/embed/conversation.spec.js.map +1 -1
  114. package/lib/src/embed/hostEventClient/contracts.d.ts +31 -0
  115. package/lib/src/embed/hostEventClient/contracts.d.ts.map +1 -1
  116. package/lib/src/embed/hostEventClient/contracts.js +2 -0
  117. package/lib/src/embed/hostEventClient/contracts.js.map +1 -1
  118. package/lib/src/embed/hostEventClient/host-event-client.d.ts +18 -0
  119. package/lib/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
  120. package/lib/src/embed/hostEventClient/host-event-client.js +69 -9
  121. package/lib/src/embed/hostEventClient/host-event-client.js.map +1 -1
  122. package/lib/src/embed/hostEventClient/host-event-client.spec.js +185 -19
  123. package/lib/src/embed/hostEventClient/host-event-client.spec.js.map +1 -1
  124. package/lib/src/embed/hostEventClient/utils.d.ts +22 -0
  125. package/lib/src/embed/hostEventClient/utils.d.ts.map +1 -0
  126. package/lib/src/embed/hostEventClient/utils.js +43 -0
  127. package/lib/src/embed/hostEventClient/utils.js.map +1 -0
  128. package/lib/src/embed/hostEventClient/utils.spec.d.ts +2 -0
  129. package/lib/src/embed/hostEventClient/utils.spec.d.ts.map +1 -0
  130. package/lib/src/embed/hostEventClient/utils.spec.js +113 -0
  131. package/lib/src/embed/hostEventClient/utils.spec.js.map +1 -0
  132. package/lib/src/embed/liveboard.d.ts +18 -1
  133. package/lib/src/embed/liveboard.d.ts.map +1 -1
  134. package/lib/src/embed/liveboard.js +9 -11
  135. package/lib/src/embed/liveboard.js.map +1 -1
  136. package/lib/src/embed/liveboard.spec.js +29 -71
  137. package/lib/src/embed/liveboard.spec.js.map +1 -1
  138. package/lib/src/embed/spotter-utils.d.ts +20 -0
  139. package/lib/src/embed/spotter-utils.d.ts.map +1 -0
  140. package/lib/src/embed/spotter-utils.js +47 -0
  141. package/lib/src/embed/spotter-utils.js.map +1 -0
  142. package/lib/src/embed/spotter-utils.spec.d.ts +2 -0
  143. package/lib/src/embed/spotter-utils.spec.d.ts.map +1 -0
  144. package/lib/src/embed/spotter-utils.spec.js +52 -0
  145. package/lib/src/embed/spotter-utils.spec.js.map +1 -0
  146. package/lib/src/embed/ts-embed.d.ts.map +1 -1
  147. package/lib/src/embed/ts-embed.js +13 -1
  148. package/lib/src/embed/ts-embed.js.map +1 -1
  149. package/lib/src/errors.d.ts +2 -0
  150. package/lib/src/errors.d.ts.map +1 -1
  151. package/lib/src/errors.js +2 -0
  152. package/lib/src/errors.js.map +1 -1
  153. package/lib/src/types.d.ts +102 -1
  154. package/lib/src/types.d.ts.map +1 -1
  155. package/lib/src/types.js +101 -0
  156. package/lib/src/types.js.map +1 -1
  157. package/lib/src/utils.d.ts +0 -9
  158. package/lib/src/utils.d.ts.map +1 -1
  159. package/lib/src/utils.js +0 -8
  160. package/lib/src/utils.js.map +1 -1
  161. package/lib/src/visual-embed-sdk.d.ts +266 -3
  162. package/package.json +4 -4
  163. package/src/css-variables.ts +45 -0
  164. package/src/embed/app.spec.ts +51 -92
  165. package/src/embed/app.ts +60 -64
  166. package/src/embed/conversation.spec.ts +150 -119
  167. package/src/embed/conversation.ts +30 -54
  168. package/src/embed/hostEventClient/contracts.ts +31 -0
  169. package/src/embed/hostEventClient/host-event-client.spec.ts +260 -19
  170. package/src/embed/hostEventClient/host-event-client.ts +87 -11
  171. package/src/embed/hostEventClient/utils.spec.ts +137 -0
  172. package/src/embed/hostEventClient/utils.ts +61 -0
  173. package/src/embed/liveboard.spec.ts +38 -93
  174. package/src/embed/liveboard.ts +28 -10
  175. package/src/embed/spotter-utils.spec.ts +56 -0
  176. package/src/embed/spotter-utils.ts +65 -0
  177. package/src/embed/ts-embed.ts +15 -1
  178. package/src/errors.ts +2 -0
  179. package/src/types.ts +104 -0
  180. package/src/utils.ts +0 -14
@@ -0,0 +1,137 @@
1
+ import {
2
+ isValidUpdateFiltersPayload,
3
+ isValidDrillDownPayload,
4
+ createValidationError,
5
+ throwUpdateFiltersValidationError,
6
+ throwDrillDownValidationError,
7
+ } from './utils';
8
+ import { ERROR_MESSAGE } from '../../errors';
9
+ import { EmbedEvent } from '../../types';
10
+ import { embedEventStatus } from '../../utils';
11
+
12
+ describe('hostEventClient utils', () => {
13
+ describe('isValidUpdateFiltersPayload', () => {
14
+ it('returns false for undefined', () => {
15
+ expect(isValidUpdateFiltersPayload(undefined)).toBe(false);
16
+ });
17
+
18
+ it('returns false for empty payload', () => {
19
+ expect(isValidUpdateFiltersPayload({})).toBe(false);
20
+ });
21
+
22
+ it('returns true for valid filter', () => {
23
+ expect(isValidUpdateFiltersPayload({
24
+ filter: { column: 'region', oper: 'EQ', values: ['North'] },
25
+ } as any)).toBe(true);
26
+ });
27
+
28
+ it('returns true for valid filters array', () => {
29
+ expect(isValidUpdateFiltersPayload({
30
+ filters: [
31
+ { column: 'x', oper: 'IN', values: ['a', 'b'] },
32
+ { column: 'y', oper: 'EQ', values: ['c'] },
33
+ ],
34
+ } as any)).toBe(true);
35
+ });
36
+
37
+ it('returns false for filter with missing column', () => {
38
+ expect(isValidUpdateFiltersPayload({
39
+ filter: { oper: 'EQ', values: ['a'] },
40
+ } as any)).toBe(false);
41
+ });
42
+
43
+ it('returns false for filter with missing oper', () => {
44
+ expect(isValidUpdateFiltersPayload({
45
+ filter: { column: 'x', values: ['a'] },
46
+ } as any)).toBe(false);
47
+ });
48
+
49
+ it('returns false for filter with non-array values', () => {
50
+ expect(isValidUpdateFiltersPayload({
51
+ filter: { column: 'x', oper: 'EQ', values: 'a' },
52
+ } as any)).toBe(false);
53
+ });
54
+
55
+ it('returns false for empty filters array', () => {
56
+ expect(isValidUpdateFiltersPayload({ filters: [] } as any)).toBe(false);
57
+ });
58
+ });
59
+
60
+ describe('isValidDrillDownPayload', () => {
61
+ it('returns false for undefined', () => {
62
+ expect(isValidDrillDownPayload(undefined)).toBe(false);
63
+ });
64
+
65
+ it('returns false for empty payload', () => {
66
+ expect(isValidDrillDownPayload({})).toBe(false);
67
+ });
68
+
69
+ it('returns false for empty points', () => {
70
+ expect(isValidDrillDownPayload({ points: {} } as any)).toBe(false);
71
+ });
72
+
73
+ it('returns false for non-object points', () => {
74
+ expect(isValidDrillDownPayload({ points: 'invalid' } as any)).toBe(false);
75
+ });
76
+
77
+ it('returns true for clickedPoint', () => {
78
+ expect(isValidDrillDownPayload({
79
+ points: { clickedPoint: 'point-1' },
80
+ } as any)).toBe(true);
81
+ });
82
+
83
+ it('returns true for selectedPoints', () => {
84
+ expect(isValidDrillDownPayload({
85
+ points: { selectedPoints: ['p1', 'p2'] },
86
+ } as any)).toBe(true);
87
+ });
88
+
89
+ it('returns true for both clickedPoint and selectedPoints', () => {
90
+ expect(isValidDrillDownPayload({
91
+ points: { clickedPoint: 'p1', selectedPoints: ['p2'] },
92
+ } as any)).toBe(true);
93
+ });
94
+
95
+ it('returns false for selectedPoints empty array', () => {
96
+ expect(isValidDrillDownPayload({
97
+ points: { selectedPoints: [] },
98
+ } as any)).toBe(false);
99
+ });
100
+ });
101
+
102
+ describe('createValidationError', () => {
103
+ it('throws with message and embedErrorDetails', () => {
104
+ expect(() => createValidationError('test error')).toThrow('test error');
105
+
106
+ try {
107
+ createValidationError('custom msg');
108
+ } catch (err: any) {
109
+ expect(err.isValidationError).toBe(true);
110
+ expect(err.embedErrorDetails).toMatchObject({
111
+ type: EmbedEvent.Error,
112
+ data: {
113
+ errorType: 'VALIDATION_ERROR',
114
+ message: 'custom msg',
115
+ code: 'HOST_EVENT_VALIDATION',
116
+ error: 'custom msg',
117
+ },
118
+ status: embedEventStatus.END,
119
+ });
120
+ }
121
+ });
122
+ });
123
+
124
+ describe('throwUpdateFiltersValidationError', () => {
125
+ it('throws with UPDATEFILTERS_INVALID_PAYLOAD message', () => {
126
+ expect(() => throwUpdateFiltersValidationError())
127
+ .toThrow(ERROR_MESSAGE.UPDATEFILTERS_INVALID_PAYLOAD);
128
+ });
129
+ });
130
+
131
+ describe('throwDrillDownValidationError', () => {
132
+ it('throws with DRILLDOWN_INVALID_PAYLOAD message', () => {
133
+ expect(() => throwDrillDownValidationError())
134
+ .toThrow(ERROR_MESSAGE.DRILLDOWN_INVALID_PAYLOAD);
135
+ });
136
+ });
137
+ });
@@ -0,0 +1,61 @@
1
+ import { EmbedErrorCodes, EmbedEvent, ErrorDetailsTypes, HostEvent } from '../../types';
2
+ import { ERROR_MESSAGE } from '../../errors';
3
+ import { HostEventRequest } from './contracts';
4
+ import { embedEventStatus } from '../../utils';
5
+
6
+ export function isValidUpdateFiltersPayload(
7
+ payload: HostEventRequest<HostEvent.UpdateFilters> | undefined,
8
+ ): boolean {
9
+ if (!payload) return false;
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);
13
+
14
+ const hasValidFilter = payload.filter && isValidFilter(payload.filter);
15
+ const hasValidFilters = Array.isArray(payload.filters) && payload.filters.length > 0 && payload.filters.every(isValidFilter);
16
+
17
+ return !!(hasValidFilter || hasValidFilters);
18
+ }
19
+
20
+ export function isValidDrillDownPayload(
21
+ payload: HostEventRequest<HostEvent.DrillDown> | undefined,
22
+ ): boolean {
23
+ if (!payload) return false;
24
+
25
+ const points = payload.points;
26
+ if (!points || typeof points !== 'object') return false;
27
+
28
+ const hasClickedPoint = 'clickedPoint' in points && points.clickedPoint != null;
29
+ const hasSelectedPoints = Array.isArray(points.selectedPoints) && points.selectedPoints.length > 0;
30
+
31
+ return hasClickedPoint || hasSelectedPoints;
32
+ }
33
+
34
+ export type ValidationError = Error & {
35
+ isValidationError?: boolean;
36
+ embedErrorDetails?: { type: EmbedEvent.Error; data: { errorType: ErrorDetailsTypes; message: string; code: EmbedErrorCodes; error: string }; status: typeof embedEventStatus.END };
37
+ };
38
+
39
+ export function createValidationError(message: string): never {
40
+ const err = new Error(message) as ValidationError;
41
+ err.isValidationError = true;
42
+ err.embedErrorDetails = {
43
+ type: EmbedEvent.Error,
44
+ data:{
45
+ errorType: ErrorDetailsTypes.VALIDATION_ERROR,
46
+ message,
47
+ code: EmbedErrorCodes.HOST_EVENT_VALIDATION,
48
+ error: message
49
+ },
50
+ status:embedEventStatus.END
51
+ };
52
+ throw err;
53
+ }
54
+
55
+ export function throwUpdateFiltersValidationError(): never {
56
+ createValidationError(ERROR_MESSAGE.UPDATEFILTERS_INVALID_PAYLOAD);
57
+ }
58
+
59
+ export function throwDrillDownValidationError(): never {
60
+ createValidationError(ERROR_MESSAGE.DRILLDOWN_INVALID_PAYLOAD);
61
+ }
@@ -260,6 +260,21 @@ describe('Liveboard/viz embed tests', () => {
260
260
  });
261
261
  });
262
262
 
263
+ test('should set isWYSIWYGLiveboardPDFEnabled to true in url', async () => {
264
+ const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
265
+ isContinuousLiveboardPDFEnabled: true,
266
+ ...defaultViewConfig,
267
+ liveboardId,
268
+ } as LiveboardViewConfig);
269
+ liveboardEmbed.render();
270
+ await executeAfterWait(() => {
271
+ expectUrlMatchesWithParams(
272
+ getIFrameSrc(),
273
+ `http://${thoughtSpotHost}/?embedApp=true${defaultParams}&isWYSIWYGLiveboardPDFEnabled=true${prefixParams}#/embed/viz/${liveboardId}`,
274
+ );
275
+ });
276
+ });
277
+
263
278
  test('should set isLiveboardXLSXCSVDownloadEnabled to true in url', async () => {
264
279
  const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
265
280
  isLiveboardXLSXCSVDownloadEnabled: true,
@@ -1655,6 +1670,29 @@ describe('Liveboard/viz embed tests', () => {
1655
1670
  });
1656
1671
  });
1657
1672
 
1673
+ test('should send correct visible data when RequestVisibleEmbedCoordinates is triggered', async () => {
1674
+ const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
1675
+ ...defaultViewConfig,
1676
+ liveboardId,
1677
+ fullHeight: true,
1678
+ lazyLoadingMargin: '10px',
1679
+ } as LiveboardViewConfig);
1680
+
1681
+ const mockTrigger = jest.spyOn(liveboardEmbed, 'trigger');
1682
+
1683
+ await liveboardEmbed.render();
1684
+
1685
+ // Trigger the lazy load data calculation
1686
+ (liveboardEmbed as any).sendFullHeightLazyLoadData();
1687
+
1688
+ expect(mockTrigger).not.toHaveBeenCalledWith(HostEvent.VisibleEmbedCoordinates, {
1689
+ top: 0,
1690
+ height: 500,
1691
+ left: 0,
1692
+ width: 650,
1693
+ });
1694
+ });
1695
+
1658
1696
  test('should calculate correct visible data for partially visible full height element', async () => {
1659
1697
  mockIFrame.getBoundingClientRect = jest.fn().mockReturnValue({
1660
1698
  top: -50,
@@ -1975,99 +2013,6 @@ describe('Liveboard/viz embed tests', () => {
1975
2013
  });
1976
2014
  });
1977
2015
 
1978
- describe('updateIFrameHeight threshold handling', () => {
1979
- let mockIFrame: HTMLIFrameElement;
1980
-
1981
- beforeEach(() => {
1982
- mockIFrame = document.createElement('iframe');
1983
- mockIFrame.getBoundingClientRect = jest.fn().mockReturnValue({
1984
- top: 0,
1985
- left: 0,
1986
- bottom: 500,
1987
- right: 800,
1988
- width: 800,
1989
- height: 500,
1990
- });
1991
- });
1992
-
1993
- test('should skip height update when change is below threshold', async () => {
1994
- const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
1995
- liveboardId,
1996
- ...defaultViewConfig,
1997
- fullHeight: true,
1998
- }) as any;
1999
-
2000
- liveboardEmbed.iFrame = mockIFrame;
2001
- document.body.appendChild(mockIFrame);
2002
- await liveboardEmbed.render();
2003
-
2004
- const spySetIFrameHeight = jest.spyOn(liveboardEmbed, 'setIFrameHeight');
2005
-
2006
- // currentHeight is 500; heightToSet = max(510, 500) = 510; change = 10 < 30
2007
- liveboardEmbed.updateIFrameHeight({ data: 510, type: EmbedEvent.EmbedHeight });
2008
-
2009
- expect(spySetIFrameHeight).not.toHaveBeenCalled();
2010
- });
2011
-
2012
- test('should update height when change meets threshold', async () => {
2013
- const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
2014
- liveboardId,
2015
- ...defaultViewConfig,
2016
- fullHeight: true,
2017
- }) as any;
2018
-
2019
- liveboardEmbed.iFrame = mockIFrame;
2020
- document.body.appendChild(mockIFrame);
2021
- await liveboardEmbed.render();
2022
-
2023
- const spySetIFrameHeight = jest.spyOn(liveboardEmbed, 'setIFrameHeight');
2024
-
2025
- // currentHeight is 500; heightToSet = max(700, 500) = 700; change = 200 >= 30
2026
- liveboardEmbed.updateIFrameHeight({ data: 700, type: EmbedEvent.EmbedHeight });
2027
-
2028
- expect(spySetIFrameHeight).toHaveBeenCalledWith(700);
2029
- });
2030
-
2031
- test('should use defaultHeight when data is below it and apply threshold', async () => {
2032
- const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
2033
- liveboardId,
2034
- ...defaultViewConfig,
2035
- fullHeight: true,
2036
- minimumHeight: 800,
2037
- }) as any;
2038
-
2039
- liveboardEmbed.iFrame = mockIFrame;
2040
- document.body.appendChild(mockIFrame);
2041
- await liveboardEmbed.render();
2042
-
2043
- const spySetIFrameHeight = jest.spyOn(liveboardEmbed, 'setIFrameHeight');
2044
-
2045
- // currentHeight is 500; heightToSet = max(100, 800) = 800; change = 300 >= 30
2046
- liveboardEmbed.updateIFrameHeight({ data: 100, type: EmbedEvent.EmbedHeight });
2047
-
2048
- expect(spySetIFrameHeight).toHaveBeenCalledWith(800);
2049
- });
2050
-
2051
- test('should skip update when height change is exactly at threshold boundary', async () => {
2052
- const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
2053
- liveboardId,
2054
- ...defaultViewConfig,
2055
- fullHeight: true,
2056
- }) as any;
2057
-
2058
- liveboardEmbed.iFrame = mockIFrame;
2059
- document.body.appendChild(mockIFrame);
2060
- await liveboardEmbed.render();
2061
-
2062
- const spySetIFrameHeight = jest.spyOn(liveboardEmbed, 'setIFrameHeight');
2063
-
2064
- // currentHeight is 500; heightToSet = max(529, 500) = 529; change = 29 < 30
2065
- liveboardEmbed.updateIFrameHeight({ data: 529, type: EmbedEvent.EmbedHeight });
2066
-
2067
- expect(spySetIFrameHeight).not.toHaveBeenCalled();
2068
- });
2069
- });
2070
-
2071
2016
  describe('Liveboard Embed Default Height and Minimum Height Handling', () => {
2072
2017
  test('should set default height to 800 when minimum height is provided', async () => {
2073
2018
  const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
@@ -387,6 +387,24 @@ export interface LiveboardViewConfig extends BaseViewConfig, LiveboardOtherViewC
387
387
  * ```
388
388
  */
389
389
  isPNGInScheduledEmailsEnabled?: boolean;
390
+ /**
391
+ * Enables the 'what you see is what you get' PDF export for Liveboards. Each tab is rendered on a single page
392
+ * following the exact UI layout, instead of splitting visualizations across multiple A4 pages.
393
+ * This feature is GA from version 26.5.0.cl and is enabled by default on embed deployments.
394
+ *
395
+ * Supported embed types: `AppEmbed`, `LiveboardEmbed`
396
+ * @type {boolean}
397
+ * @version SDK: 1.48.0 | ThoughtSpot: 26.5.0.cl
398
+ * @example
399
+ * ```js
400
+ * // Replace <EmbedComponent> with embed component name. For example, AppEmbed or LiveboardEmbed
401
+ * const embed = new <EmbedComponent>('#tsEmbed', {
402
+ * ... // other embed view config
403
+ * isContinuousLiveboardPDFEnabled: true,
404
+ * })
405
+ * ```
406
+ */
407
+ isContinuousLiveboardPDFEnabled?: boolean;
390
408
  /**
391
409
  * This flag is used to enable/disable the XLSX/CSV download option for Liveboards
392
410
  *
@@ -598,6 +616,7 @@ export class LiveboardEmbed extends V1Embed {
598
616
  updatedSpotterChatPrompt,
599
617
  spotterChatConfig,
600
618
  isThisPeriodInDateFiltersEnabled,
619
+ isContinuousLiveboardPDFEnabled,
601
620
  } = this.viewConfig;
602
621
 
603
622
  const preventLiveboardFilterRemoval = this.viewConfig.preventLiveboardFilterRemoval
@@ -708,6 +727,10 @@ export class LiveboardEmbed extends V1Embed {
708
727
  params[Param.IsThisPeriodInDateFiltersEnabled] = isThisPeriodInDateFiltersEnabled;
709
728
  }
710
729
 
730
+ if (isContinuousLiveboardPDFEnabled !== undefined) {
731
+ params[Param.IsWYSIWYGLiveboardPDFEnabled] = isContinuousLiveboardPDFEnabled;
732
+ }
733
+
711
734
  params[Param.LiveboardHeaderSticky] = isLiveboardHeaderSticky;
712
735
  params[Param.LiveboardHeaderV2] = isLiveboardCompactHeaderEnabled;
713
736
  params[Param.ShowLiveboardVerifiedBadge] = showLiveboardVerifiedBadge;
@@ -767,7 +790,10 @@ export class LiveboardEmbed extends V1Embed {
767
790
 
768
791
  private sendFullHeightLazyLoadData = () => {
769
792
  const data = calculateVisibleElementData(this.iFrame);
770
- this.trigger(HostEvent.VisibleEmbedCoordinates, data);
793
+ // this should be fired only if the lazyLoadingForFullHeight and fullHeight are true
794
+ if(this.viewConfig.lazyLoadingForFullHeight && this.viewConfig.fullHeight){
795
+ this.trigger(HostEvent.VisibleEmbedCoordinates, data);
796
+ }
771
797
  };
772
798
 
773
799
  /**
@@ -806,21 +832,13 @@ export class LiveboardEmbed extends V1Embed {
806
832
  )}`;
807
833
  }
808
834
 
809
- private HEIGHT_CHANAGE_THRESHOLD = 30;
810
835
  /**
811
836
  * Set the iframe height as per the computed height received
812
837
  * from the ThoughtSpot app.
813
838
  * @param data The event payload
814
839
  */
815
840
  private updateIFrameHeight = (data: MessagePayload) => {
816
- const currentHeight = this.iFrame.getBoundingClientRect().height;
817
- const heightToSet = Math.max(data.data, this.defaultHeight);
818
- const heightChange = Math.abs(heightToSet - currentHeight);
819
- if (heightChange < this.HEIGHT_CHANAGE_THRESHOLD) {
820
- logger.info('Height change is less than the threshold, skipping height update', { heightChange, heightToSet, currentHeight });
821
- return;
822
- }
823
- this.setIFrameHeight(heightToSet);
841
+ this.setIFrameHeight(Math.max(data.data, this.defaultHeight));
824
842
  this.sendFullHeightLazyLoadData();
825
843
  };
826
844
 
@@ -0,0 +1,56 @@
1
+ import { resolveEnablePastConversationsSidebar, buildSpotterSidebarAppInitData } from './spotter-utils';
2
+ import { ErrorDetailsTypes, EmbedErrorCodes } from '../types';
3
+ import { ERROR_MESSAGE } from '../errors';
4
+
5
+ describe('resolveEnablePastConversationsSidebar', () => {
6
+ it('prefers spotterSidebarConfig value over standalone', () => {
7
+ expect(resolveEnablePastConversationsSidebar({ spotterSidebarConfigValue: true, standaloneValue: false })).toBe(true);
8
+ expect(resolveEnablePastConversationsSidebar({ spotterSidebarConfigValue: false, standaloneValue: true })).toBe(false);
9
+ });
10
+
11
+ it('falls back to standalone when spotterSidebarConfig value is absent', () => {
12
+ expect(resolveEnablePastConversationsSidebar({ standaloneValue: true })).toBe(true);
13
+ });
14
+
15
+ it('returns undefined when both are absent', () => {
16
+ expect(resolveEnablePastConversationsSidebar({})).toBeUndefined();
17
+ });
18
+ });
19
+
20
+ describe('buildSpotterSidebarAppInitData', () => {
21
+ const base = { type: 'APP_INIT' } as any;
22
+ const noopError = jest.fn();
23
+
24
+ it('returns base unchanged when no sidebar config or standalone flag', () => {
25
+ const result = buildSpotterSidebarAppInitData(base, {}, noopError);
26
+ expect(result).toBe(base);
27
+ });
28
+
29
+ it('nests spotterSidebarConfig under embedParams', () => {
30
+ const result = buildSpotterSidebarAppInitData(base, {
31
+ spotterSidebarConfig: { enablePastConversationsSidebar: true, spotterSidebarTitle: 'Chats' },
32
+ }, noopError);
33
+ expect(result.embedParams?.spotterSidebarConfig).toEqual({
34
+ enablePastConversationsSidebar: true,
35
+ spotterSidebarTitle: 'Chats',
36
+ });
37
+ });
38
+
39
+ it('promotes standalone flag into spotterSidebarConfig.enablePastConversationsSidebar', () => {
40
+ const result = buildSpotterSidebarAppInitData(base, { enablePastConversationsSidebar: true }, noopError);
41
+ expect(result.embedParams?.spotterSidebarConfig?.enablePastConversationsSidebar).toBe(true);
42
+ });
43
+
44
+ it('calls handleError and strips spotterDocumentationUrl when invalid', () => {
45
+ const handleError = jest.fn();
46
+ const result = buildSpotterSidebarAppInitData(base, {
47
+ spotterSidebarConfig: { spotterDocumentationUrl: 'not-a-url' },
48
+ }, handleError);
49
+ expect(handleError).toHaveBeenCalledWith(expect.objectContaining({
50
+ errorType: ErrorDetailsTypes.VALIDATION_ERROR,
51
+ message: ERROR_MESSAGE.INVALID_SPOTTER_DOCUMENTATION_URL,
52
+ code: EmbedErrorCodes.INVALID_URL,
53
+ }));
54
+ expect(result.embedParams?.spotterSidebarConfig?.spotterDocumentationUrl).toBeUndefined();
55
+ });
56
+ });
@@ -0,0 +1,65 @@
1
+ import { DefaultAppInitData, ErrorDetailsTypes, EmbedErrorCodes } from '../types';
2
+ import { validateHttpUrl } from '../utils';
3
+ import { ERROR_MESSAGE } from '../errors';
4
+ import type { SpotterSidebarViewConfig } from './conversation';
5
+
6
+ /**
7
+ * Resolves enablePastConversationsSidebar with
8
+ * spotterSidebarConfig taking precedence over the
9
+ * standalone flag.
10
+ */
11
+ export const resolveEnablePastConversationsSidebar = (params: {
12
+ spotterSidebarConfigValue?: boolean;
13
+ standaloneValue?: boolean;
14
+ }): boolean | undefined => (
15
+ params.spotterSidebarConfigValue !== undefined
16
+ ? params.spotterSidebarConfigValue
17
+ : params.standaloneValue
18
+ );
19
+
20
+ export function buildSpotterSidebarAppInitData<T extends DefaultAppInitData>(
21
+ defaultAppInitData: T,
22
+ viewConfig: {
23
+ spotterSidebarConfig?: SpotterSidebarViewConfig;
24
+ enablePastConversationsSidebar?: boolean;
25
+ },
26
+ handleError: (err: any) => void,
27
+ ): T & { embedParams?: { spotterSidebarConfig?: SpotterSidebarViewConfig } } {
28
+ const { spotterSidebarConfig, enablePastConversationsSidebar } = viewConfig;
29
+
30
+ const resolvedEnablePastConversations = resolveEnablePastConversationsSidebar({
31
+ spotterSidebarConfigValue: spotterSidebarConfig?.enablePastConversationsSidebar,
32
+ standaloneValue: enablePastConversationsSidebar,
33
+ });
34
+
35
+ const hasConfig = spotterSidebarConfig || resolvedEnablePastConversations !== undefined;
36
+ if (!hasConfig) return defaultAppInitData;
37
+
38
+ const resolvedSidebarConfig: SpotterSidebarViewConfig = {
39
+ ...spotterSidebarConfig,
40
+ ...(resolvedEnablePastConversations !== undefined && {
41
+ enablePastConversationsSidebar: resolvedEnablePastConversations,
42
+ }),
43
+ };
44
+
45
+ if (resolvedSidebarConfig.spotterDocumentationUrl !== undefined) {
46
+ const [isValid, validationError] = validateHttpUrl(resolvedSidebarConfig.spotterDocumentationUrl);
47
+ if (!isValid) {
48
+ handleError({
49
+ errorType: ErrorDetailsTypes.VALIDATION_ERROR,
50
+ message: ERROR_MESSAGE.INVALID_SPOTTER_DOCUMENTATION_URL,
51
+ code: EmbedErrorCodes.INVALID_URL,
52
+ error: validationError?.message || ERROR_MESSAGE.INVALID_SPOTTER_DOCUMENTATION_URL,
53
+ });
54
+ delete resolvedSidebarConfig.spotterDocumentationUrl;
55
+ }
56
+ }
57
+
58
+ return {
59
+ ...defaultAppInitData,
60
+ embedParams: {
61
+ ...((defaultAppInitData as any).embedParams || {}),
62
+ spotterSidebarConfig: resolvedSidebarConfig,
63
+ },
64
+ };
65
+ }
@@ -1446,7 +1446,21 @@ export class TsEmbed {
1446
1446
  }
1447
1447
 
1448
1448
  // send an empty object, this is needed for liveboard default handlers
1449
- return this.hostEventClient.triggerHostEvent(messageType, data, context);
1449
+ return this.hostEventClient.triggerHostEvent(messageType, data, context).catch((err: Error & {
1450
+ isValidationError?: boolean;
1451
+ embedErrorDetails?: { errorType: ErrorDetailsTypes; message: string; code: EmbedErrorCodes; error: string };
1452
+ }): Promise<null> => {
1453
+ if (err?.isValidationError) {
1454
+ const errorDetails = err.embedErrorDetails ?? {
1455
+ errorType: ErrorDetailsTypes.VALIDATION_ERROR,
1456
+ message: err.message || ERROR_MESSAGE.UPDATEFILTERS_INVALID_PAYLOAD,
1457
+ code: EmbedErrorCodes.UPDATEFILTERS_INVALID_PAYLOAD,
1458
+ error: err.message,
1459
+ };
1460
+ this.handleError(errorDetails);
1461
+ }
1462
+ throw err;
1463
+ });
1450
1464
  }
1451
1465
 
1452
1466
  /**
package/src/errors.ts CHANGED
@@ -30,6 +30,8 @@ export const ERROR_MESSAGE = {
30
30
  SSR_ENVIRONMENT_ERROR: 'SSR environment detected. This function cannot be called in SSR environment.',
31
31
  UPDATE_PARAMS_FAILED: 'Failed to update embed parameters',
32
32
  INVALID_SPOTTER_DOCUMENTATION_URL: 'Invalid spotterDocumentationUrl. Please provide a valid http or https URL.',
33
+ UPDATEFILTERS_INVALID_PAYLOAD: 'UpdateFilters requires a valid filter or filters array. Expected: { filter: { column, oper, values } } or { filters: [{ column, oper, values }, ...] }',
34
+ DRILLDOWN_INVALID_PAYLOAD: 'DrillDown requires a valid points object. Expected: { points: { clickedPoint?, selectedPoints? }, autoDrillDown?, vizId? }',
33
35
  };
34
36
 
35
37
  export const CUSTOM_ACTIONS_ERROR_MESSAGE = {