@thoughtspot/visual-embed-sdk 1.46.5-beta.1 → 1.47.0
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 -17
- package/cjs/src/embed/app.d.ts.map +1 -1
- package/cjs/src/embed/app.js +26 -47
- package/cjs/src/embed/app.js.map +1 -1
- package/cjs/src/embed/app.spec.js +36 -79
- package/cjs/src/embed/app.spec.js.map +1 -1
- package/cjs/src/embed/conversation.d.ts +24 -2
- 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 +57 -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 +190 -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 +25 -5
- package/cjs/src/embed/ts-embed.js.map +1 -1
- package/cjs/src/embed/ts-embed.spec.d.ts.map +1 -1
- package/cjs/src/embed/ts-embed.spec.js +28 -67
- package/cjs/src/embed/ts-embed.spec.js.map +1 -1
- package/cjs/src/errors.d.ts +2 -1
- package/cjs/src/errors.d.ts.map +1 -1
- package/cjs/src/errors.js +2 -1
- package/cjs/src/errors.js.map +1 -1
- package/cjs/src/index.d.ts +1 -2
- package/cjs/src/index.d.ts.map +1 -1
- package/cjs/src/index.js +2 -4
- package/cjs/src/index.js.map +1 -1
- package/cjs/src/react/all-types-export.d.ts +2 -2
- package/cjs/src/react/all-types-export.d.ts.map +1 -1
- package/cjs/src/react/all-types-export.js +1 -3
- package/cjs/src/react/all-types-export.js.map +1 -1
- package/cjs/src/react/index.d.ts +1 -36
- package/cjs/src/react/index.d.ts.map +1 -1
- package/cjs/src/react/index.js +1 -34
- package/cjs/src/react/index.js.map +1 -1
- package/cjs/src/types.d.ts +143 -76
- package/cjs/src/types.d.ts.map +1 -1
- package/cjs/src/types.js +118 -47
- 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-DW2wEHqy.js → index-Bm2Hck8q.js} +1 -1
- 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 -17
- package/dist/src/embed/app.d.ts.map +1 -1
- package/dist/src/embed/conversation.d.ts +24 -2
- 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/embed/ts-embed.spec.d.ts.map +1 -1
- package/dist/src/errors.d.ts +2 -1
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/index.d.ts +1 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/react/all-types-export.d.ts +2 -2
- package/dist/src/react/all-types-export.d.ts.map +1 -1
- package/dist/src/react/index.d.ts +1 -36
- package/dist/src/react/index.d.ts.map +1 -1
- package/dist/src/types.d.ts +143 -76
- 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 +378 -304
- package/dist/tsembed-react.js +366 -294
- package/dist/tsembed.es.js +393 -285
- package/dist/tsembed.js +30798 -30691
- package/dist/visual-embed-sdk-react-full.d.ts +310 -273
- package/dist/visual-embed-sdk-react.d.ts +310 -273
- package/dist/visual-embed-sdk.d.ts +308 -243
- 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 -17
- package/lib/src/embed/app.d.ts.map +1 -1
- package/lib/src/embed/app.js +28 -49
- package/lib/src/embed/app.js.map +1 -1
- package/lib/src/embed/app.spec.js +36 -79
- package/lib/src/embed/app.spec.js.map +1 -1
- package/lib/src/embed/conversation.d.ts +24 -2
- 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 +49 -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 +188 -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 +25 -5
- package/lib/src/embed/ts-embed.js.map +1 -1
- package/lib/src/embed/ts-embed.spec.d.ts.map +1 -1
- package/lib/src/embed/ts-embed.spec.js +30 -69
- package/lib/src/embed/ts-embed.spec.js.map +1 -1
- package/lib/src/errors.d.ts +2 -1
- package/lib/src/errors.d.ts.map +1 -1
- package/lib/src/errors.js +2 -1
- package/lib/src/errors.js.map +1 -1
- package/lib/src/index.d.ts +1 -2
- package/lib/src/index.d.ts.map +1 -1
- package/lib/src/index.js +1 -2
- package/lib/src/index.js.map +1 -1
- package/lib/src/react/all-types-export.d.ts +2 -2
- package/lib/src/react/all-types-export.d.ts.map +1 -1
- package/lib/src/react/all-types-export.js +1 -1
- package/lib/src/react/all-types-export.js.map +1 -1
- package/lib/src/react/index.d.ts +1 -36
- package/lib/src/react/index.d.ts.map +1 -1
- package/lib/src/react/index.js +0 -33
- package/lib/src/react/index.js.map +1 -1
- package/lib/src/types.d.ts +143 -76
- package/lib/src/types.d.ts.map +1 -1
- package/lib/src/types.js +118 -47
- 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 +308 -243
- package/package.json +4 -4
- package/src/css-variables.ts +45 -0
- package/src/embed/app.spec.ts +51 -107
- package/src/embed/app.ts +60 -82
- package/src/embed/conversation.spec.ts +150 -119
- package/src/embed/conversation.ts +34 -58
- 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 +225 -0
- package/src/embed/hostEventClient/utils.ts +74 -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.spec.ts +56 -108
- package/src/embed/ts-embed.ts +25 -4
- package/src/errors.ts +2 -1
- package/src/index.ts +2 -5
- package/src/react/all-types-export.ts +0 -3
- package/src/react/index.tsx +15 -59
- package/src/types.ts +206 -138
- package/src/utils.ts +0 -14
- package/cjs/src/embed/sage.d.ts +0 -164
- package/cjs/src/embed/sage.d.ts.map +0 -1
- package/cjs/src/embed/sage.js +0 -88
- package/cjs/src/embed/sage.js.map +0 -1
- package/cjs/src/embed/sage.spec.d.ts +0 -2
- package/cjs/src/embed/sage.spec.d.ts.map +0 -1
- package/cjs/src/embed/sage.spec.js +0 -151
- package/cjs/src/embed/sage.spec.js.map +0 -1
- package/dist/src/embed/sage.d.ts +0 -164
- package/dist/src/embed/sage.d.ts.map +0 -1
- package/dist/src/embed/sage.spec.d.ts +0 -2
- package/dist/src/embed/sage.spec.d.ts.map +0 -1
- package/lib/src/embed/sage.d.ts +0 -164
- package/lib/src/embed/sage.d.ts.map +0 -1
- package/lib/src/embed/sage.js +0 -84
- package/lib/src/embed/sage.js.map +0 -1
- package/lib/src/embed/sage.spec.d.ts +0 -2
- package/lib/src/embed/sage.spec.d.ts.map +0 -1
- package/lib/src/embed/sage.spec.js +0 -148
- package/lib/src/embed/sage.spec.js.map +0 -1
- package/src/embed/sage.spec.ts +0 -206
- package/src/embed/sage.ts +0 -231
|
@@ -0,0 +1,225 @@
|
|
|
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
|
+
|
|
14
|
+
// =========================
|
|
15
|
+
// UpdateFilters Validation
|
|
16
|
+
// =========================
|
|
17
|
+
describe('isValidUpdateFiltersPayload', () => {
|
|
18
|
+
it('returns false for undefined', () => {
|
|
19
|
+
expect(isValidUpdateFiltersPayload(undefined)).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('returns false for empty payload', () => {
|
|
23
|
+
expect(isValidUpdateFiltersPayload({})).toBe(false);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('returns true for valid filter with column', () => {
|
|
27
|
+
expect(isValidUpdateFiltersPayload({
|
|
28
|
+
filter: { column: 'region', oper: 'EQ', values: ['North'] },
|
|
29
|
+
} as any)).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('returns true for valid filter with columnName', () => {
|
|
33
|
+
expect(isValidUpdateFiltersPayload({
|
|
34
|
+
filter: { columnName: 'region', oper: 'EQ', values: ['North'] },
|
|
35
|
+
} as any)).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('returns true for filter with operator instead of oper', () => {
|
|
39
|
+
expect(isValidUpdateFiltersPayload({
|
|
40
|
+
filter: { column: 'region', operator: 'EQ', values: ['North'] },
|
|
41
|
+
} as any)).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('returns true for valid filters array', () => {
|
|
45
|
+
expect(isValidUpdateFiltersPayload({
|
|
46
|
+
filters: [
|
|
47
|
+
{ column: 'x', oper: 'IN', values: ['a', 'b'] },
|
|
48
|
+
{ column: 'y', oper: 'EQ', values: ['c'] },
|
|
49
|
+
],
|
|
50
|
+
} as any)).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('returns true for valid filters array with columnName', () => {
|
|
54
|
+
expect(isValidUpdateFiltersPayload({
|
|
55
|
+
filters: [
|
|
56
|
+
{ columnName: 'x', oper: 'IN', values: ['a', 'b'] },
|
|
57
|
+
{ columnName: 'y', oper: 'EQ', values: ['c'] },
|
|
58
|
+
],
|
|
59
|
+
} as any)).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('returns false if one filter in filters array is invalid', () => {
|
|
63
|
+
expect(isValidUpdateFiltersPayload({
|
|
64
|
+
filters: [
|
|
65
|
+
{ column: 'x', oper: 'EQ', values: ['a'] },
|
|
66
|
+
{ column: 'y', values: ['b'] }, // invalid
|
|
67
|
+
],
|
|
68
|
+
} as any)).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('returns false for filter with missing column/columnName', () => {
|
|
72
|
+
expect(isValidUpdateFiltersPayload({
|
|
73
|
+
filter: { oper: 'EQ', values: ['a'] },
|
|
74
|
+
} as any)).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('returns false for filter with missing operator', () => {
|
|
78
|
+
expect(isValidUpdateFiltersPayload({
|
|
79
|
+
filter: { column: 'x', values: ['a'] },
|
|
80
|
+
} as any)).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('returns false for filter with non-array values', () => {
|
|
84
|
+
expect(isValidUpdateFiltersPayload({
|
|
85
|
+
filter: { column: 'x', oper: 'EQ', values: 'a' },
|
|
86
|
+
} as any)).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('returns false for filter with non-string type', () => {
|
|
90
|
+
expect(isValidUpdateFiltersPayload({
|
|
91
|
+
filter: { column: 'x', oper: 'EQ', values: ['a'], type: 123 },
|
|
92
|
+
} as any)).toBe(false);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('returns true for filter with valid string type', () => {
|
|
96
|
+
expect(isValidUpdateFiltersPayload({
|
|
97
|
+
filter: { column: 'x', oper: 'EQ', values: ['a'], type: 'STRING' },
|
|
98
|
+
} as any)).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('returns false for empty filters array', () => {
|
|
102
|
+
expect(isValidUpdateFiltersPayload({ filters: [] } as any)).toBe(false);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('returns false if filters is not an array', () => {
|
|
106
|
+
expect(isValidUpdateFiltersPayload({
|
|
107
|
+
filters: 'invalid',
|
|
108
|
+
} as any)).toBe(false);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('returns true if filter is valid even when filters is invalid', () => {
|
|
112
|
+
expect(isValidUpdateFiltersPayload({
|
|
113
|
+
filter: { column: 'x', oper: 'EQ', values: ['a'] },
|
|
114
|
+
filters: [{ column: 'y' }], // invalid
|
|
115
|
+
} as any)).toBe(true);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// =========================
|
|
120
|
+
// DrillDown Validation
|
|
121
|
+
// =========================
|
|
122
|
+
describe('isValidDrillDownPayload', () => {
|
|
123
|
+
it('returns false for undefined', () => {
|
|
124
|
+
expect(isValidDrillDownPayload(undefined)).toBe(false);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('returns false for empty payload', () => {
|
|
128
|
+
expect(isValidDrillDownPayload({})).toBe(false);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('returns false for empty points object', () => {
|
|
132
|
+
expect(isValidDrillDownPayload({ points: {} } as any)).toBe(false);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('returns false for non-object points', () => {
|
|
136
|
+
expect(isValidDrillDownPayload({ points: 'invalid' } as any)).toBe(false);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('returns true for clickedPoint', () => {
|
|
140
|
+
expect(isValidDrillDownPayload({
|
|
141
|
+
points: { clickedPoint: 'point-1' },
|
|
142
|
+
} as any)).toBe(true);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('returns true for selectedPoints', () => {
|
|
146
|
+
expect(isValidDrillDownPayload({
|
|
147
|
+
points: { selectedPoints: ['p1', 'p2'] },
|
|
148
|
+
} as any)).toBe(true);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('returns true for both clickedPoint and selectedPoints', () => {
|
|
152
|
+
expect(isValidDrillDownPayload({
|
|
153
|
+
points: { clickedPoint: 'p1', selectedPoints: ['p2'] },
|
|
154
|
+
} as any)).toBe(true);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('returns false for selectedPoints empty array', () => {
|
|
158
|
+
expect(isValidDrillDownPayload({
|
|
159
|
+
points: { selectedPoints: [] },
|
|
160
|
+
} as any)).toBe(false);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('returns false if clickedPoint is null', () => {
|
|
164
|
+
expect(isValidDrillDownPayload({
|
|
165
|
+
points: { clickedPoint: null },
|
|
166
|
+
} as any)).toBe(false);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('returns false if selectedPoints is not an array', () => {
|
|
170
|
+
expect(isValidDrillDownPayload({
|
|
171
|
+
points: { selectedPoints: 'invalid' },
|
|
172
|
+
} as any)).toBe(false);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('returns false if both clickedPoint and selectedPoints are invalid', () => {
|
|
176
|
+
expect(isValidDrillDownPayload({
|
|
177
|
+
points: { clickedPoint: null, selectedPoints: [] },
|
|
178
|
+
} as any)).toBe(false);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// =========================
|
|
183
|
+
// Error Handling
|
|
184
|
+
// =========================
|
|
185
|
+
describe('createValidationError', () => {
|
|
186
|
+
it('throws with message', () => {
|
|
187
|
+
expect(() => createValidationError('test error')).toThrow('test error');
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('throws Error instance with metadata', () => {
|
|
191
|
+
try {
|
|
192
|
+
createValidationError('custom msg');
|
|
193
|
+
} catch (err: any) {
|
|
194
|
+
expect(err).toBeInstanceOf(Error);
|
|
195
|
+
expect(err.isValidationError).toBe(true);
|
|
196
|
+
expect(err.embedErrorDetails).toBeDefined();
|
|
197
|
+
|
|
198
|
+
expect(err.embedErrorDetails).toMatchObject({
|
|
199
|
+
type: EmbedEvent.Error,
|
|
200
|
+
data: {
|
|
201
|
+
errorType: 'VALIDATION_ERROR',
|
|
202
|
+
message: 'custom msg',
|
|
203
|
+
code: 'HOST_EVENT_VALIDATION',
|
|
204
|
+
error: 'custom msg',
|
|
205
|
+
},
|
|
206
|
+
status: embedEventStatus.END,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe('throwUpdateFiltersValidationError', () => {
|
|
213
|
+
it('throws with UPDATEFILTERS_INVALID_PAYLOAD message', () => {
|
|
214
|
+
expect(() => throwUpdateFiltersValidationError())
|
|
215
|
+
.toThrow(ERROR_MESSAGE.UPDATEFILTERS_INVALID_PAYLOAD);
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
describe('throwDrillDownValidationError', () => {
|
|
220
|
+
it('throws with DRILLDOWN_INVALID_PAYLOAD message', () => {
|
|
221
|
+
expect(() => throwDrillDownValidationError())
|
|
222
|
+
.toThrow(ERROR_MESSAGE.DRILLDOWN_INVALID_PAYLOAD);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
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: {
|
|
12
|
+
column?: string;
|
|
13
|
+
oper?: string;
|
|
14
|
+
values?: unknown[];
|
|
15
|
+
type?: string;
|
|
16
|
+
columnName?: string;
|
|
17
|
+
operator?: string
|
|
18
|
+
}) => {
|
|
19
|
+
const hasColumn = typeof f.column === 'string' || typeof f.columnName === 'string';
|
|
20
|
+
const hasOperator = typeof f.oper === 'string' || typeof f.operator === 'string';
|
|
21
|
+
const hasValues = Array.isArray(f.values);
|
|
22
|
+
const validType = !f.type || typeof f.type === 'string';
|
|
23
|
+
|
|
24
|
+
return hasColumn && hasOperator && hasValues && validType;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const hasValidFilter = payload.filter && isValidFilter(payload.filter);
|
|
28
|
+
const hasValidFilters = Array.isArray(payload.filters) && payload.filters.length > 0 && payload.filters.every(isValidFilter);
|
|
29
|
+
|
|
30
|
+
return !!(hasValidFilter || hasValidFilters);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function isValidDrillDownPayload(
|
|
34
|
+
payload: HostEventRequest<HostEvent.DrillDown> | undefined,
|
|
35
|
+
): boolean {
|
|
36
|
+
if (!payload) return false;
|
|
37
|
+
|
|
38
|
+
const points = payload.points;
|
|
39
|
+
if (!points || typeof points !== 'object') return false;
|
|
40
|
+
|
|
41
|
+
const hasClickedPoint = 'clickedPoint' in points && points.clickedPoint != null;
|
|
42
|
+
const hasSelectedPoints = Array.isArray(points.selectedPoints) && points.selectedPoints.length > 0;
|
|
43
|
+
|
|
44
|
+
return hasClickedPoint || hasSelectedPoints;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type ValidationError = Error & {
|
|
48
|
+
isValidationError?: boolean;
|
|
49
|
+
embedErrorDetails?: { type: EmbedEvent.Error; data: { errorType: ErrorDetailsTypes; message: string; code: EmbedErrorCodes; error: string }; status: typeof embedEventStatus.END };
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export function createValidationError(message: string): never {
|
|
53
|
+
const err = new Error(message) as ValidationError;
|
|
54
|
+
err.isValidationError = true;
|
|
55
|
+
err.embedErrorDetails = {
|
|
56
|
+
type: EmbedEvent.Error,
|
|
57
|
+
data:{
|
|
58
|
+
errorType: ErrorDetailsTypes.VALIDATION_ERROR,
|
|
59
|
+
message,
|
|
60
|
+
code: EmbedErrorCodes.HOST_EVENT_VALIDATION,
|
|
61
|
+
error: message
|
|
62
|
+
},
|
|
63
|
+
status:embedEventStatus.END
|
|
64
|
+
};
|
|
65
|
+
throw err;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function throwUpdateFiltersValidationError(): never {
|
|
69
|
+
createValidationError(ERROR_MESSAGE.UPDATEFILTERS_INVALID_PAYLOAD);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function throwDrillDownValidationError(): never {
|
|
73
|
+
createValidationError(ERROR_MESSAGE.DRILLDOWN_INVALID_PAYLOAD);
|
|
74
|
+
}
|
|
@@ -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
|
+
}
|