@thoughtspot/visual-embed-sdk 1.52.1 → 1.52.2-beta.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.
Files changed (217) hide show
  1. package/cjs/package.json +10 -3
  2. package/cjs/src/contracts/host-event-contracts.d.ts +49 -11
  3. package/cjs/src/contracts/host-event-contracts.d.ts.map +1 -1
  4. package/cjs/src/embed/app.d.ts +4 -3
  5. package/cjs/src/embed/app.d.ts.map +1 -1
  6. package/cjs/src/embed/app.js.map +1 -1
  7. package/cjs/src/embed/app.spec.js +10 -0
  8. package/cjs/src/embed/app.spec.js.map +1 -1
  9. package/cjs/src/embed/auto-frame-renderer.spec.js +24 -13
  10. package/cjs/src/embed/auto-frame-renderer.spec.js.map +1 -1
  11. package/cjs/src/embed/conversation.d.ts +1 -0
  12. package/cjs/src/embed/conversation.d.ts.map +1 -1
  13. package/cjs/src/embed/conversation.js +3 -3
  14. package/cjs/src/embed/conversation.js.map +1 -1
  15. package/cjs/src/embed/conversation.spec.js +35 -24
  16. package/cjs/src/embed/conversation.spec.js.map +1 -1
  17. package/cjs/src/embed/embedConfig.spec.js +2 -1
  18. package/cjs/src/embed/embedConfig.spec.js.map +1 -1
  19. package/cjs/src/embed/hostEventClient/host-event-client.d.ts +2 -2
  20. package/cjs/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
  21. package/cjs/src/embed/hostEventClient/host-event-client.js +16 -10
  22. package/cjs/src/embed/hostEventClient/host-event-client.js.map +1 -1
  23. package/cjs/src/embed/hostEventClient/host-event-client.spec.js +14 -8
  24. package/cjs/src/embed/hostEventClient/host-event-client.spec.js.map +1 -1
  25. package/cjs/src/embed/hostEventClient/utils.d.ts +9 -0
  26. package/cjs/src/embed/hostEventClient/utils.d.ts.map +1 -1
  27. package/cjs/src/embed/hostEventClient/utils.js +49 -3
  28. package/cjs/src/embed/hostEventClient/utils.js.map +1 -1
  29. package/cjs/src/embed/hostEventClient/utils.spec.js +151 -0
  30. package/cjs/src/embed/hostEventClient/utils.spec.js.map +1 -1
  31. package/cjs/src/embed/liveboard.d.ts +4 -3
  32. package/cjs/src/embed/liveboard.d.ts.map +1 -1
  33. package/cjs/src/embed/liveboard.js +17 -12
  34. package/cjs/src/embed/liveboard.js.map +1 -1
  35. package/cjs/src/embed/liveboard.spec.js +2 -1
  36. package/cjs/src/embed/liveboard.spec.js.map +1 -1
  37. package/cjs/src/embed/spotter-utils.d.ts +8 -1
  38. package/cjs/src/embed/spotter-utils.d.ts.map +1 -1
  39. package/cjs/src/embed/spotter-utils.js +14 -1
  40. package/cjs/src/embed/spotter-utils.js.map +1 -1
  41. package/cjs/src/embed/spotter-utils.spec.js +26 -0
  42. package/cjs/src/embed/spotter-utils.spec.js.map +1 -1
  43. package/cjs/src/embed/ts-embed.d.ts +35 -10
  44. package/cjs/src/embed/ts-embed.d.ts.map +1 -1
  45. package/cjs/src/embed/ts-embed.js +108 -73
  46. package/cjs/src/embed/ts-embed.js.map +1 -1
  47. package/cjs/src/embed/ts-embed.spec.d.ts.map +1 -1
  48. package/cjs/src/embed/ts-embed.spec.js +203 -30
  49. package/cjs/src/embed/ts-embed.spec.js.map +1 -1
  50. package/cjs/src/errors.js +1 -1
  51. package/cjs/src/errors.js.map +1 -1
  52. package/cjs/src/index.d.ts +2 -1
  53. package/cjs/src/index.d.ts.map +1 -1
  54. package/cjs/src/index.js +3 -1
  55. package/cjs/src/index.js.map +1 -1
  56. package/cjs/src/react/all-types-export.d.ts +1 -1
  57. package/cjs/src/react/all-types-export.d.ts.map +1 -1
  58. package/cjs/src/react/all-types-export.js +2 -1
  59. package/cjs/src/react/all-types-export.js.map +1 -1
  60. package/cjs/src/types.d.ts +45 -22
  61. package/cjs/src/types.d.ts.map +1 -1
  62. package/cjs/src/types.js +44 -21
  63. package/cjs/src/types.js.map +1 -1
  64. package/cjs/src/utils/authService/tokenizedAuthService.spec.js +2 -1
  65. package/cjs/src/utils/authService/tokenizedAuthService.spec.js.map +1 -1
  66. package/cjs/src/utils/custom-actions.spec.js +4 -2
  67. package/cjs/src/utils/custom-actions.spec.js.map +1 -1
  68. package/cjs/src/utils/filterConverter.d.ts +106 -0
  69. package/cjs/src/utils/filterConverter.d.ts.map +1 -0
  70. package/cjs/src/utils/filterConverter.js +215 -0
  71. package/cjs/src/utils/filterConverter.js.map +1 -0
  72. package/cjs/src/utils/filterConverter.spec.d.ts +2 -0
  73. package/cjs/src/utils/filterConverter.spec.d.ts.map +1 -0
  74. package/cjs/src/utils/filterConverter.spec.js +709 -0
  75. package/cjs/src/utils/filterConverter.spec.js.map +1 -0
  76. package/cjs/src/utils/processData.spec.js +2 -1
  77. package/cjs/src/utils/processData.spec.js.map +1 -1
  78. package/dist/{index-C1pmYb5H.js → index-BrJMbt8r.js} +1 -1
  79. package/dist/src/contracts/host-event-contracts.d.ts +49 -11
  80. package/dist/src/contracts/host-event-contracts.d.ts.map +1 -1
  81. package/dist/src/embed/app.d.ts +4 -3
  82. package/dist/src/embed/app.d.ts.map +1 -1
  83. package/dist/src/embed/conversation.d.ts +1 -0
  84. package/dist/src/embed/conversation.d.ts.map +1 -1
  85. package/dist/src/embed/hostEventClient/host-event-client.d.ts +2 -2
  86. package/dist/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
  87. package/dist/src/embed/hostEventClient/utils.d.ts +9 -0
  88. package/dist/src/embed/hostEventClient/utils.d.ts.map +1 -1
  89. package/dist/src/embed/liveboard.d.ts +4 -3
  90. package/dist/src/embed/liveboard.d.ts.map +1 -1
  91. package/dist/src/embed/spotter-utils.d.ts +8 -1
  92. package/dist/src/embed/spotter-utils.d.ts.map +1 -1
  93. package/dist/src/embed/ts-embed.d.ts +35 -10
  94. package/dist/src/embed/ts-embed.d.ts.map +1 -1
  95. package/dist/src/embed/ts-embed.spec.d.ts.map +1 -1
  96. package/dist/src/index.d.ts +2 -1
  97. package/dist/src/index.d.ts.map +1 -1
  98. package/dist/src/react/all-types-export.d.ts +1 -1
  99. package/dist/src/react/all-types-export.d.ts.map +1 -1
  100. package/dist/src/types.d.ts +45 -22
  101. package/dist/src/types.d.ts.map +1 -1
  102. package/dist/src/utils/filterConverter.d.ts +106 -0
  103. package/dist/src/utils/filterConverter.d.ts.map +1 -0
  104. package/dist/src/utils/filterConverter.spec.d.ts +2 -0
  105. package/dist/src/utils/filterConverter.spec.d.ts.map +1 -0
  106. package/dist/tsembed-react.es.js +252 -126
  107. package/dist/tsembed-react.js +251 -125
  108. package/dist/tsembed.es.js +460 -127
  109. package/dist/tsembed.js +459 -125
  110. package/dist/visual-embed-sdk-react-full.d.ts +233 -49
  111. package/dist/visual-embed-sdk-react.d.ts +139 -49
  112. package/dist/visual-embed-sdk.d.ts +233 -49
  113. package/lib/package.json +10 -3
  114. package/lib/src/contracts/host-event-contracts.d.ts +49 -11
  115. package/lib/src/contracts/host-event-contracts.d.ts.map +1 -1
  116. package/lib/src/embed/app.d.ts +4 -3
  117. package/lib/src/embed/app.d.ts.map +1 -1
  118. package/lib/src/embed/app.js.map +1 -1
  119. package/lib/src/embed/app.spec.js +10 -0
  120. package/lib/src/embed/app.spec.js.map +1 -1
  121. package/lib/src/embed/auto-frame-renderer.spec.js +24 -13
  122. package/lib/src/embed/auto-frame-renderer.spec.js.map +1 -1
  123. package/lib/src/embed/conversation.d.ts +1 -0
  124. package/lib/src/embed/conversation.d.ts.map +1 -1
  125. package/lib/src/embed/conversation.js +4 -4
  126. package/lib/src/embed/conversation.js.map +1 -1
  127. package/lib/src/embed/conversation.spec.js +35 -24
  128. package/lib/src/embed/conversation.spec.js.map +1 -1
  129. package/lib/src/embed/embedConfig.spec.js +2 -1
  130. package/lib/src/embed/embedConfig.spec.js.map +1 -1
  131. package/lib/src/embed/hostEventClient/host-event-client.d.ts +2 -2
  132. package/lib/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
  133. package/lib/src/embed/hostEventClient/host-event-client.js +17 -11
  134. package/lib/src/embed/hostEventClient/host-event-client.js.map +1 -1
  135. package/lib/src/embed/hostEventClient/host-event-client.spec.js +14 -8
  136. package/lib/src/embed/hostEventClient/host-event-client.spec.js.map +1 -1
  137. package/lib/src/embed/hostEventClient/utils.d.ts +9 -0
  138. package/lib/src/embed/hostEventClient/utils.d.ts.map +1 -1
  139. package/lib/src/embed/hostEventClient/utils.js +47 -2
  140. package/lib/src/embed/hostEventClient/utils.js.map +1 -1
  141. package/lib/src/embed/hostEventClient/utils.spec.js +152 -1
  142. package/lib/src/embed/hostEventClient/utils.spec.js.map +1 -1
  143. package/lib/src/embed/liveboard.d.ts +4 -3
  144. package/lib/src/embed/liveboard.d.ts.map +1 -1
  145. package/lib/src/embed/liveboard.js +17 -12
  146. package/lib/src/embed/liveboard.js.map +1 -1
  147. package/lib/src/embed/liveboard.spec.js +2 -1
  148. package/lib/src/embed/liveboard.spec.js.map +1 -1
  149. package/lib/src/embed/spotter-utils.d.ts +8 -1
  150. package/lib/src/embed/spotter-utils.d.ts.map +1 -1
  151. package/lib/src/embed/spotter-utils.js +12 -0
  152. package/lib/src/embed/spotter-utils.js.map +1 -1
  153. package/lib/src/embed/spotter-utils.spec.js +27 -1
  154. package/lib/src/embed/spotter-utils.spec.js.map +1 -1
  155. package/lib/src/embed/ts-embed.d.ts +35 -10
  156. package/lib/src/embed/ts-embed.d.ts.map +1 -1
  157. package/lib/src/embed/ts-embed.js +109 -74
  158. package/lib/src/embed/ts-embed.js.map +1 -1
  159. package/lib/src/embed/ts-embed.spec.d.ts.map +1 -1
  160. package/lib/src/embed/ts-embed.spec.js +203 -30
  161. package/lib/src/embed/ts-embed.spec.js.map +1 -1
  162. package/lib/src/errors.js +1 -1
  163. package/lib/src/errors.js.map +1 -1
  164. package/lib/src/index.d.ts +2 -1
  165. package/lib/src/index.d.ts.map +1 -1
  166. package/lib/src/index.js +2 -1
  167. package/lib/src/index.js.map +1 -1
  168. package/lib/src/react/all-types-export.d.ts +1 -1
  169. package/lib/src/react/all-types-export.d.ts.map +1 -1
  170. package/lib/src/react/all-types-export.js +1 -1
  171. package/lib/src/react/all-types-export.js.map +1 -1
  172. package/lib/src/types.d.ts +45 -22
  173. package/lib/src/types.d.ts.map +1 -1
  174. package/lib/src/types.js +44 -21
  175. package/lib/src/types.js.map +1 -1
  176. package/lib/src/utils/authService/tokenizedAuthService.spec.js +2 -1
  177. package/lib/src/utils/authService/tokenizedAuthService.spec.js.map +1 -1
  178. package/lib/src/utils/custom-actions.spec.js +4 -2
  179. package/lib/src/utils/custom-actions.spec.js.map +1 -1
  180. package/lib/src/utils/filterConverter.d.ts +106 -0
  181. package/lib/src/utils/filterConverter.d.ts.map +1 -0
  182. package/lib/src/utils/filterConverter.js +210 -0
  183. package/lib/src/utils/filterConverter.js.map +1 -0
  184. package/lib/src/utils/filterConverter.spec.d.ts +2 -0
  185. package/lib/src/utils/filterConverter.spec.d.ts.map +1 -0
  186. package/lib/src/utils/filterConverter.spec.js +707 -0
  187. package/lib/src/utils/filterConverter.spec.js.map +1 -0
  188. package/lib/src/utils/processData.spec.js +2 -1
  189. package/lib/src/utils/processData.spec.js.map +1 -1
  190. package/lib/src/visual-embed-sdk.d.ts +233 -49
  191. package/package.json +10 -3
  192. package/src/contracts/host-event-contracts.ts +55 -11
  193. package/src/embed/app.spec.ts +15 -0
  194. package/src/embed/app.ts +4 -3
  195. package/src/embed/auto-frame-renderer.spec.ts +24 -13
  196. package/src/embed/conversation.spec.ts +38 -27
  197. package/src/embed/conversation.ts +4 -4
  198. package/src/embed/embedConfig.spec.ts +2 -1
  199. package/src/embed/hostEventClient/host-event-client.spec.ts +14 -8
  200. package/src/embed/hostEventClient/host-event-client.ts +18 -10
  201. package/src/embed/hostEventClient/utils.spec.ts +176 -1
  202. package/src/embed/hostEventClient/utils.ts +51 -2
  203. package/src/embed/liveboard.spec.ts +4 -3
  204. package/src/embed/liveboard.ts +21 -15
  205. package/src/embed/spotter-utils.spec.ts +29 -0
  206. package/src/embed/spotter-utils.ts +16 -0
  207. package/src/embed/ts-embed.spec.ts +245 -30
  208. package/src/embed/ts-embed.ts +107 -74
  209. package/src/errors.ts +1 -1
  210. package/src/index.ts +10 -0
  211. package/src/react/all-types-export.ts +4 -0
  212. package/src/types.ts +44 -21
  213. package/src/utils/authService/tokenizedAuthService.spec.ts +2 -1
  214. package/src/utils/custom-actions.spec.ts +4 -2
  215. package/src/utils/filterConverter.spec.ts +801 -0
  216. package/src/utils/filterConverter.ts +304 -0
  217. package/src/utils/processData.spec.ts +2 -1
@@ -0,0 +1,304 @@
1
+ /**
2
+ * Copyright (c) 2026
3
+ *
4
+ * Utility to convert the payload emitted by `EmbedEvent.FilterChanged`
5
+ * into the payload shape expected by `HostEvent.UpdateFilters`, so that
6
+ * the current filter state of a Liveboard can be captured and re-applied
7
+ * later without having to hand-parse the event payload.
8
+ * @summary Filter payload converter
9
+ */
10
+
11
+ import isNil from 'lodash/isNil';
12
+ import { RuntimeFilter, RuntimeFilterOp } from '../types';
13
+ import { Applicability, ApplicabilityLevel } from '../contracts/ui-passthrough-contracts';
14
+
15
+ export interface FilterChangedFilterContentValue {
16
+ key?: string | number | boolean | null;
17
+ }
18
+
19
+ export interface FilterChangedFilterContent {
20
+ filterType?: string;
21
+ negate?: boolean;
22
+ value?: FilterChangedFilterContentValue[];
23
+ }
24
+
25
+ export interface FilterChangedDateFilterValue {
26
+ type?: string;
27
+ op?: string;
28
+ // The payload is JSON from the embedded app, so absent fields can arrive
29
+ // as `null` rather than being omitted.
30
+ epoch?: string | number | null;
31
+ dateRange?: {
32
+ lowEpoch?: string | number | null;
33
+ highEpoch?: string | number | null;
34
+ };
35
+ monthName?: string;
36
+ quarterName?: string;
37
+ yearName?: string;
38
+ number?: number | null;
39
+ datePeriod?: string;
40
+ includeCurrentPeriod?: boolean | null;
41
+ }
42
+
43
+ export interface FilterChangedDateFilterContent {
44
+ negate?: boolean;
45
+ dateFilter?: FilterChangedDateFilterValue;
46
+ }
47
+
48
+ export interface FilterChangedFilter {
49
+ filterContent?: FilterChangedFilterContent[];
50
+ dateFilterContent?: FilterChangedDateFilterContent[];
51
+ }
52
+
53
+ export interface FilterChangedFilterGroup {
54
+ columnInfo?: {
55
+ name?: string;
56
+ };
57
+ filters?: FilterChangedFilter[];
58
+ /**
59
+ * Scope of the filter - `LIVEBOARD`, `TAB` or `GROUP`. Present from
60
+ * SDK 1.53.0 / ThoughtSpot 26.10.0.cl. Omitted means Liveboard level.
61
+ */
62
+ applicability?: Applicability;
63
+ }
64
+
65
+ /**
66
+ * Shape of the payload received via `LiveboardEmbed.on(EmbedEvent.FilterChanged, ...)`.
67
+ */
68
+ export interface FilterChangedPayload {
69
+ liveboardFilters?: FilterChangedFilterGroup[];
70
+ runtimeFilters?: RuntimeFilter[];
71
+ }
72
+
73
+ export interface UpdateFiltersFilterParam {
74
+ columnName: string;
75
+ operator: string;
76
+ values: (string | number | boolean | bigint)[];
77
+ type?: string;
78
+ datePeriod?: string;
79
+ negate?: boolean;
80
+ includeCurrentPeriod?: boolean;
81
+ applicability?: Applicability;
82
+ }
83
+
84
+ /**
85
+ * Shape expected by `liveboardEmbed.trigger(HostEvent.UpdateFilters, ...)`.
86
+ */
87
+ export interface UpdateFiltersPayload {
88
+ filters: UpdateFiltersFilterParam[];
89
+ }
90
+
91
+ type DateFilterValueExtractor = (dateFilter: FilterChangedDateFilterValue) => (string | number)[];
92
+
93
+ /**
94
+ * Coerces an epoch/count field to a number, returning `null` when the value is
95
+ * absent or not a finite number. The payload arrives as JSON from the iframe,
96
+ * so a missing field can be `null` as well as `undefined`, and `Number(null)`
97
+ * or `Number('')` would otherwise silently become a valid-looking `0`
98
+ * (i.e. the Unix epoch, or a zero-length period).
99
+ * @param value Raw value from the date filter payload.
100
+ */
101
+ function toFiniteNumber(value: string | number | undefined | null): number | null {
102
+ if (isNil(value) || value === '') return null;
103
+ const numericValue = Number(value);
104
+ return Number.isFinite(numericValue) ? numericValue : null;
105
+ }
106
+
107
+ const DATE_FILTER_VALUE_EXTRACTORS: Record<string, DateFilterValueExtractor> = {
108
+ EXACT_DATE: (dateFilter) => {
109
+ const epoch = toFiniteNumber(dateFilter.epoch);
110
+ return epoch === null ? [] : [epoch];
111
+ },
112
+ EXACT_DATE_RANGE: (dateFilter) => {
113
+ const { lowEpoch, highEpoch } = dateFilter.dateRange ?? {};
114
+ const low = toFiniteNumber(lowEpoch);
115
+ const high = toFiniteNumber(highEpoch);
116
+ return low === null || high === null ? [] : [low, high];
117
+ },
118
+ MONTH_YEAR: (dateFilter) => (
119
+ dateFilter.monthName && dateFilter.yearName
120
+ ? [dateFilter.monthName, dateFilter.yearName]
121
+ : []
122
+ ),
123
+ QUARTER_YEAR: (dateFilter) => (
124
+ dateFilter.quarterName && dateFilter.yearName
125
+ ? [dateFilter.quarterName, dateFilter.yearName]
126
+ : []
127
+ ),
128
+ YEAR_ONLY: (dateFilter) => (dateFilter.yearName ? [dateFilter.yearName] : []),
129
+ LAST_N_PERIOD: (dateFilter) => {
130
+ const count = toFiniteNumber(dateFilter.number);
131
+ return count === null ? [] : [count];
132
+ },
133
+ NEXT_N_PERIOD: (dateFilter) => {
134
+ const count = toFiniteNumber(dateFilter.number);
135
+ return count === null ? [] : [count];
136
+ },
137
+ };
138
+
139
+ // Date filter types with no `values` (e.g. TODAY needs no operand).
140
+ const PERIOD_ONLY_DATE_FILTER_TYPES = new Set([
141
+ 'THIS_PERIOD', 'PERIOD_TO_DATE', 'TODAY', 'YESTERDAY', 'TOMORROW',
142
+ ]);
143
+
144
+ function convertDateFilterToParam(
145
+ columnName: string,
146
+ dateFilterContent: FilterChangedDateFilterContent,
147
+ ): UpdateFiltersFilterParam | null {
148
+ const dateFilter = dateFilterContent?.dateFilter;
149
+ if (!dateFilter?.type) return null;
150
+
151
+ let values: (string | number)[];
152
+ const getValues = DATE_FILTER_VALUE_EXTRACTORS[dateFilter.type];
153
+ if (getValues) {
154
+ values = getValues(dateFilter);
155
+ // Required fields (e.g. epoch, yearName) are missing - the source data
156
+ // can't be reconstructed faithfully, so skip rather than emit a filter
157
+ // that would clear/corrupt this column when re-applied.
158
+ if (values.length === 0) return null;
159
+ } else if (PERIOD_ONLY_DATE_FILTER_TYPES.has(dateFilter.type)) {
160
+ values = [];
161
+ } else {
162
+ // Unrecognized date filter type - skip rather than guess.
163
+ return null;
164
+ }
165
+
166
+ const param: UpdateFiltersFilterParam = {
167
+ columnName,
168
+ operator: dateFilter.op ?? RuntimeFilterOp.EQ,
169
+ values,
170
+ type: dateFilter.type,
171
+ };
172
+ if (dateFilter.datePeriod) {
173
+ param.datePeriod = dateFilter.datePeriod;
174
+ }
175
+ if (!isNil(dateFilter.includeCurrentPeriod)) {
176
+ param.includeCurrentPeriod = dateFilter.includeCurrentPeriod;
177
+ }
178
+ if (dateFilterContent.negate) {
179
+ param.negate = true;
180
+ }
181
+
182
+ return param;
183
+ }
184
+
185
+ function convertFilterContentToParam(
186
+ columnName: string,
187
+ filterContent: FilterChangedFilterContent,
188
+ ): UpdateFiltersFilterParam | null {
189
+ if (!filterContent?.filterType) return null;
190
+
191
+ const values = (filterContent.value ?? [])
192
+ .map((value) => value?.key)
193
+ .filter((value): value is string | number | boolean => !isNil(value));
194
+
195
+ const param: UpdateFiltersFilterParam = {
196
+ columnName,
197
+ operator: filterContent.filterType,
198
+ values,
199
+ };
200
+ if (filterContent.negate) {
201
+ param.negate = true;
202
+ }
203
+
204
+ return param;
205
+ }
206
+
207
+ function convertFilterToParams(columnName: string, filter: FilterChangedFilter): UpdateFiltersFilterParam[] {
208
+ const dateParams = (filter?.dateFilterContent ?? [])
209
+ .map((dateFilterContent) => convertDateFilterToParam(columnName, dateFilterContent));
210
+ const contentParams = (filter?.filterContent ?? [])
211
+ .map((filterContent) => convertFilterContentToParam(columnName, filterContent));
212
+
213
+ return [...dateParams, ...contentParams]
214
+ .filter((param): param is UpdateFiltersFilterParam => param !== null);
215
+ }
216
+
217
+ /**
218
+ * Runtime filters need no reshaping, but they still come off the wire as JSON,
219
+ * so a malformed entry is possible. Skip it rather than pass it through: an
220
+ * entry missing a column, operator or values array fails
221
+ * `isValidUpdateFiltersPayload`, and that rejects the *whole* `filters` array,
222
+ * so one bad runtime filter would drop every other converted filter with it.
223
+ * @param runtimeFilter A runtime filter from the `FilterChanged` payload.
224
+ */
225
+ function convertRuntimeFilterToParam(runtimeFilter: RuntimeFilter): UpdateFiltersFilterParam | null {
226
+ const { columnName, operator, values } = runtimeFilter ?? {};
227
+ if (typeof columnName !== 'string' || !columnName) return null;
228
+ if (typeof operator !== 'string' || !operator) return null;
229
+ if (!Array.isArray(values)) return null;
230
+
231
+ return { columnName, operator, values };
232
+ }
233
+
234
+ /**
235
+ * Mirrors `isValidApplicability` in `hostEventClient/utils`: a `TAB` or `GROUP`
236
+ * scope is meaningless without a `targetId`, and `UpdateFilters` rejects the
237
+ * whole `filters` array when any entry carries a malformed `applicability`.
238
+ * @param applicability Scope taken from the `FilterChanged` payload.
239
+ */
240
+ function isUsableApplicability(applicability: Applicability): boolean {
241
+ const levels: string[] = Object.values(ApplicabilityLevel);
242
+ if (!levels.includes(applicability.level)) return false;
243
+ if (applicability.level === ApplicabilityLevel.Liveboard) return true;
244
+ return typeof applicability.targetId === 'string' && applicability.targetId.trim().length > 0;
245
+ }
246
+
247
+ function convertFilterGroupToParams(filterGroup: FilterChangedFilterGroup): UpdateFiltersFilterParam[] {
248
+ const columnName = filterGroup?.columnInfo?.name;
249
+ if (!columnName) return [];
250
+
251
+ const { applicability } = filterGroup;
252
+ // A filter scoped to a tab or group has to keep that scope. Replaying it
253
+ // without `applicability` would silently widen it to the whole Liveboard,
254
+ // changing what every other tab shows. If a scope is present but unusable,
255
+ // skip the filter: widening is worse than dropping, and a malformed
256
+ // applicability would fail validation for every other filter alongside it.
257
+ if (!isNil(applicability) && !isUsableApplicability(applicability)) return [];
258
+
259
+ const params = (filterGroup.filters ?? [])
260
+ .flatMap((filter) => convertFilterToParams(columnName, filter));
261
+
262
+ return isNil(applicability) ? params : params.map((param) => ({ ...param, applicability }));
263
+ }
264
+
265
+ /**
266
+ * Converts the payload emitted by {@link EmbedEvent.FilterChanged} into the
267
+ * payload shape expected by {@link HostEvent.UpdateFilters}, so the same
268
+ * filter state can be captured and re-applied later, for example on a
269
+ * subsequent page load.
270
+ *
271
+ * Both the Liveboard filters and the runtime filters present in the
272
+ * `FilterChanged` payload are included in the returned `filters` array.
273
+ *
274
+ * Note: a column with multiple date-filter conditions (e.g. an OR of two
275
+ * date ranges) is not guaranteed to round-trip losslessly - `HostEvent.UpdateFilters`
276
+ * treats date filters as replace-not-merge per column, so only the last
277
+ * converted entry for such a column will apply.
278
+ * @param filterChangedPayload The filter state carried by the
279
+ * `EmbedEvent.FilterChanged` callback, which is `payload.data` - the
280
+ * callback argument is the event envelope, and the filters sit under its
281
+ * `data` key.
282
+ * @example
283
+ * ```js
284
+ * let savedFilters;
285
+ * liveboardEmbed.on(EmbedEvent.FilterChanged, (payload) => {
286
+ * savedFilters = convertFilterChangedToUpdateFiltersPayload(payload.data);
287
+ * });
288
+ *
289
+ * // later, e.g. after a fresh page load
290
+ * liveboardEmbed.trigger(HostEvent.UpdateFilters, savedFilters);
291
+ * ```
292
+ */
293
+ export function convertFilterChangedToUpdateFiltersPayload(
294
+ filterChangedPayload: FilterChangedPayload,
295
+ ): UpdateFiltersPayload {
296
+ const liveboardFilterParams = (filterChangedPayload?.liveboardFilters ?? [])
297
+ .flatMap(convertFilterGroupToParams);
298
+
299
+ const runtimeFilterParams = (filterChangedPayload?.runtimeFilters ?? [])
300
+ .map(convertRuntimeFilterToParam)
301
+ .filter((param): param is UpdateFiltersFilterParam => param !== null);
302
+
303
+ return { filters: [...liveboardFilterParams, ...runtimeFilterParams] };
304
+ }
@@ -311,7 +311,8 @@ describe('Unit test for process data', () => {
311
311
  } as any);
312
312
  const el: any = {};
313
313
  processDataInstance.processEventData(e.type, e, '', el);
314
- // isEmbeddedSSOInfoFailure=true so neither branch fires, innerHTML stays unset
314
+ // isEmbeddedSSOInfoFailure=true so neither branch fires, innerHTML
315
+ // stays unset
315
316
  expect(el.innerHTML).toBeUndefined();
316
317
  expect(base.notifyAuthFailure).not.toHaveBeenCalled();
317
318
  });