@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.
- package/cjs/package.json +4 -4
- package/cjs/src/css-variables.d.ts +36 -0
- package/cjs/src/css-variables.d.ts.map +1 -1
- package/cjs/src/embed/app.d.ts +41 -2
- package/cjs/src/embed/app.d.ts.map +1 -1
- package/cjs/src/embed/app.js +26 -46
- package/cjs/src/embed/app.js.map +1 -1
- package/cjs/src/embed/app.spec.js +36 -69
- package/cjs/src/embed/app.spec.js.map +1 -1
- package/cjs/src/embed/conversation.d.ts +23 -1
- package/cjs/src/embed/conversation.d.ts.map +1 -1
- package/cjs/src/embed/conversation.js +18 -33
- package/cjs/src/embed/conversation.js.map +1 -1
- package/cjs/src/embed/conversation.spec.js +129 -97
- package/cjs/src/embed/conversation.spec.js.map +1 -1
- package/cjs/src/embed/hostEventClient/contracts.d.ts +31 -0
- package/cjs/src/embed/hostEventClient/contracts.d.ts.map +1 -1
- package/cjs/src/embed/hostEventClient/contracts.js +2 -0
- package/cjs/src/embed/hostEventClient/contracts.js.map +1 -1
- package/cjs/src/embed/hostEventClient/host-event-client.d.ts +18 -0
- package/cjs/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
- package/cjs/src/embed/hostEventClient/host-event-client.js +69 -9
- package/cjs/src/embed/hostEventClient/host-event-client.js.map +1 -1
- package/cjs/src/embed/hostEventClient/host-event-client.spec.js +185 -19
- package/cjs/src/embed/hostEventClient/host-event-client.spec.js.map +1 -1
- package/cjs/src/embed/hostEventClient/utils.d.ts +22 -0
- package/cjs/src/embed/hostEventClient/utils.d.ts.map +1 -0
- package/cjs/src/embed/hostEventClient/utils.js +51 -0
- package/cjs/src/embed/hostEventClient/utils.js.map +1 -0
- package/cjs/src/embed/hostEventClient/utils.spec.d.ts +2 -0
- package/cjs/src/embed/hostEventClient/utils.spec.d.ts.map +1 -0
- package/cjs/src/embed/hostEventClient/utils.spec.js +115 -0
- package/cjs/src/embed/hostEventClient/utils.spec.js.map +1 -0
- package/cjs/src/embed/liveboard.d.ts +18 -1
- package/cjs/src/embed/liveboard.d.ts.map +1 -1
- package/cjs/src/embed/liveboard.js +9 -11
- package/cjs/src/embed/liveboard.js.map +1 -1
- package/cjs/src/embed/liveboard.spec.js +29 -71
- package/cjs/src/embed/liveboard.spec.js.map +1 -1
- package/cjs/src/embed/spotter-utils.d.ts +20 -0
- package/cjs/src/embed/spotter-utils.d.ts.map +1 -0
- package/cjs/src/embed/spotter-utils.js +52 -0
- package/cjs/src/embed/spotter-utils.js.map +1 -0
- package/cjs/src/embed/spotter-utils.spec.d.ts +2 -0
- package/cjs/src/embed/spotter-utils.spec.d.ts.map +1 -0
- package/cjs/src/embed/spotter-utils.spec.js +54 -0
- package/cjs/src/embed/spotter-utils.spec.js.map +1 -0
- package/cjs/src/embed/ts-embed.d.ts.map +1 -1
- package/cjs/src/embed/ts-embed.js +13 -1
- package/cjs/src/embed/ts-embed.js.map +1 -1
- package/cjs/src/errors.d.ts +2 -0
- package/cjs/src/errors.d.ts.map +1 -1
- package/cjs/src/errors.js +2 -0
- package/cjs/src/errors.js.map +1 -1
- package/cjs/src/types.d.ts +102 -1
- package/cjs/src/types.d.ts.map +1 -1
- package/cjs/src/types.js +101 -0
- package/cjs/src/types.js.map +1 -1
- package/cjs/src/utils.d.ts +0 -9
- package/cjs/src/utils.d.ts.map +1 -1
- package/cjs/src/utils.js +1 -10
- package/cjs/src/utils.js.map +1 -1
- package/dist/index-ChNydfIz.js +7371 -0
- package/dist/index-DGV_zh53.js +7371 -0
- package/dist/src/css-variables.d.ts +36 -0
- package/dist/src/css-variables.d.ts.map +1 -1
- package/dist/src/embed/app.d.ts +41 -2
- package/dist/src/embed/app.d.ts.map +1 -1
- package/dist/src/embed/conversation.d.ts +23 -1
- package/dist/src/embed/conversation.d.ts.map +1 -1
- package/dist/src/embed/hostEventClient/contracts.d.ts +31 -0
- package/dist/src/embed/hostEventClient/contracts.d.ts.map +1 -1
- package/dist/src/embed/hostEventClient/host-event-client.d.ts +18 -0
- package/dist/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
- package/dist/src/embed/hostEventClient/utils.d.ts +22 -0
- package/dist/src/embed/hostEventClient/utils.d.ts.map +1 -0
- package/dist/src/embed/hostEventClient/utils.spec.d.ts +2 -0
- package/dist/src/embed/hostEventClient/utils.spec.d.ts.map +1 -0
- package/dist/src/embed/liveboard.d.ts +18 -1
- package/dist/src/embed/liveboard.d.ts.map +1 -1
- package/dist/src/embed/spotter-utils.d.ts +20 -0
- package/dist/src/embed/spotter-utils.d.ts.map +1 -0
- package/dist/src/embed/spotter-utils.spec.d.ts +2 -0
- package/dist/src/embed/spotter-utils.spec.d.ts.map +1 -0
- package/dist/src/embed/ts-embed.d.ts.map +1 -1
- package/dist/src/errors.d.ts +2 -0
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/types.d.ts +102 -1
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils.d.ts +0 -9
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/tsembed-react.es.js +324 -110
- package/dist/tsembed-react.js +323 -109
- package/dist/tsembed.es.js +324 -110
- package/dist/tsembed.js +323 -109
- package/dist/visual-embed-sdk-react-full.d.ts +266 -3
- package/dist/visual-embed-sdk-react.d.ts +266 -3
- package/dist/visual-embed-sdk.d.ts +266 -3
- package/lib/package.json +4 -4
- package/lib/src/css-variables.d.ts +36 -0
- package/lib/src/css-variables.d.ts.map +1 -1
- package/lib/src/embed/app.d.ts +41 -2
- package/lib/src/embed/app.d.ts.map +1 -1
- package/lib/src/embed/app.js +28 -48
- package/lib/src/embed/app.js.map +1 -1
- package/lib/src/embed/app.spec.js +36 -69
- package/lib/src/embed/app.spec.js.map +1 -1
- package/lib/src/embed/conversation.d.ts +23 -1
- package/lib/src/embed/conversation.d.ts.map +1 -1
- package/lib/src/embed/conversation.js +19 -34
- package/lib/src/embed/conversation.js.map +1 -1
- package/lib/src/embed/conversation.spec.js +131 -99
- package/lib/src/embed/conversation.spec.js.map +1 -1
- package/lib/src/embed/hostEventClient/contracts.d.ts +31 -0
- package/lib/src/embed/hostEventClient/contracts.d.ts.map +1 -1
- package/lib/src/embed/hostEventClient/contracts.js +2 -0
- package/lib/src/embed/hostEventClient/contracts.js.map +1 -1
- package/lib/src/embed/hostEventClient/host-event-client.d.ts +18 -0
- package/lib/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
- package/lib/src/embed/hostEventClient/host-event-client.js +69 -9
- package/lib/src/embed/hostEventClient/host-event-client.js.map +1 -1
- package/lib/src/embed/hostEventClient/host-event-client.spec.js +185 -19
- package/lib/src/embed/hostEventClient/host-event-client.spec.js.map +1 -1
- package/lib/src/embed/hostEventClient/utils.d.ts +22 -0
- package/lib/src/embed/hostEventClient/utils.d.ts.map +1 -0
- package/lib/src/embed/hostEventClient/utils.js +43 -0
- package/lib/src/embed/hostEventClient/utils.js.map +1 -0
- package/lib/src/embed/hostEventClient/utils.spec.d.ts +2 -0
- package/lib/src/embed/hostEventClient/utils.spec.d.ts.map +1 -0
- package/lib/src/embed/hostEventClient/utils.spec.js +113 -0
- package/lib/src/embed/hostEventClient/utils.spec.js.map +1 -0
- package/lib/src/embed/liveboard.d.ts +18 -1
- package/lib/src/embed/liveboard.d.ts.map +1 -1
- package/lib/src/embed/liveboard.js +9 -11
- package/lib/src/embed/liveboard.js.map +1 -1
- package/lib/src/embed/liveboard.spec.js +29 -71
- package/lib/src/embed/liveboard.spec.js.map +1 -1
- package/lib/src/embed/spotter-utils.d.ts +20 -0
- package/lib/src/embed/spotter-utils.d.ts.map +1 -0
- package/lib/src/embed/spotter-utils.js +47 -0
- package/lib/src/embed/spotter-utils.js.map +1 -0
- package/lib/src/embed/spotter-utils.spec.d.ts +2 -0
- package/lib/src/embed/spotter-utils.spec.d.ts.map +1 -0
- package/lib/src/embed/spotter-utils.spec.js +52 -0
- package/lib/src/embed/spotter-utils.spec.js.map +1 -0
- package/lib/src/embed/ts-embed.d.ts.map +1 -1
- package/lib/src/embed/ts-embed.js +13 -1
- package/lib/src/embed/ts-embed.js.map +1 -1
- package/lib/src/errors.d.ts +2 -0
- package/lib/src/errors.d.ts.map +1 -1
- package/lib/src/errors.js +2 -0
- package/lib/src/errors.js.map +1 -1
- package/lib/src/types.d.ts +102 -1
- package/lib/src/types.d.ts.map +1 -1
- package/lib/src/types.js +101 -0
- package/lib/src/types.js.map +1 -1
- package/lib/src/utils.d.ts +0 -9
- package/lib/src/utils.d.ts.map +1 -1
- package/lib/src/utils.js +0 -8
- package/lib/src/utils.js.map +1 -1
- package/lib/src/visual-embed-sdk.d.ts +266 -3
- package/package.json +4 -4
- package/src/css-variables.ts +45 -0
- package/src/embed/app.spec.ts +51 -92
- package/src/embed/app.ts +60 -64
- package/src/embed/conversation.spec.ts +150 -119
- package/src/embed/conversation.ts +30 -54
- package/src/embed/hostEventClient/contracts.ts +31 -0
- package/src/embed/hostEventClient/host-event-client.spec.ts +260 -19
- package/src/embed/hostEventClient/host-event-client.ts +87 -11
- package/src/embed/hostEventClient/utils.spec.ts +137 -0
- package/src/embed/hostEventClient/utils.ts +61 -0
- package/src/embed/liveboard.spec.ts +38 -93
- package/src/embed/liveboard.ts +28 -10
- package/src/embed/spotter-utils.spec.ts +56 -0
- package/src/embed/spotter-utils.ts +65 -0
- package/src/embed/ts-embed.ts +15 -1
- package/src/errors.ts +2 -0
- package/src/types.ts +104 -0
- 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(), {
|
package/src/embed/liveboard.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
+
}
|
package/src/embed/ts-embed.ts
CHANGED
|
@@ -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 = {
|