@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
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  isValidUpdateFiltersPayload,
3
+ resolveUpdateFiltersAliases,
3
4
  isValidUpdateParametersPayload,
4
5
  isValidDrillDownPayload,
5
6
  createValidationError,
@@ -7,6 +8,7 @@ import {
7
8
  throwUpdateParametersValidationError,
8
9
  throwDrillDownValidationError,
9
10
  } from './utils';
11
+ import { HostFilterUpdate as FilterUpdate } from '../../contracts/host-event-contracts';
10
12
  import { ERROR_MESSAGE } from '../../errors';
11
13
  import { EmbedEvent } from '../../types';
12
14
  import { embedEventStatus } from '../../utils';
@@ -426,4 +428,177 @@ describe('hostEventClient utils', () => {
426
428
  .toThrow(ERROR_MESSAGE.DRILLDOWN_INVALID_PAYLOAD);
427
429
  });
428
430
  });
429
- });
431
+ // =========================
432
+ // FilterUpdate type shape
433
+ // =========================
434
+ // The @ts-expect-error assertions below are enforced by tsc: an unused one
435
+ // is itself an error, so re-loosening the type fails the build.
436
+ describe('FilterUpdate type', () => {
437
+ it('accepts the documented columnName/operator spelling', () => {
438
+ const filter: FilterUpdate = { columnName: 'region', operator: 'EQ', values: ['west'] };
439
+ expect(isValidUpdateFiltersPayload({ filter })).toBe(true);
440
+ });
441
+
442
+ it('still accepts the deprecated column/oper aliases', () => {
443
+ const filter: FilterUpdate = { column: 'region', oper: 'EQ', values: ['west'] };
444
+ expect(isValidUpdateFiltersPayload({ filter })).toBe(true);
445
+ });
446
+
447
+ it('accepts a mix of documented and deprecated spellings', () => {
448
+ const filter: FilterUpdate = { columnName: 'region', oper: 'EQ', values: ['west'] };
449
+ expect(isValidUpdateFiltersPayload({ filter })).toBe(true);
450
+ });
451
+
452
+ it('accepts a worksheet-qualified column name', () => {
453
+ const filter: FilterUpdate = {
454
+ columnName: '(Sample) Retail - Apparel::city',
455
+ operator: 'IN',
456
+ values: ['atlanta'],
457
+ };
458
+ expect(isValidUpdateFiltersPayload({ filter })).toBe(true);
459
+ });
460
+
461
+ it('rejects a filter with no column at compile time and at runtime', () => {
462
+ // @ts-expect-error one of columnName/column is required
463
+ const filter: FilterUpdate = { operator: 'EQ', values: ['west'] };
464
+ expect(isValidUpdateFiltersPayload({ filter })).toBe(false);
465
+ });
466
+
467
+ it('rejects a filter with no operator at compile time and at runtime', () => {
468
+ // @ts-expect-error one of operator/oper is required
469
+ const filter: FilterUpdate = { columnName: 'region', values: ['west'] };
470
+ expect(isValidUpdateFiltersPayload({ filter })).toBe(false);
471
+ });
472
+
473
+ it('rejects both column spellings at compile time', () => {
474
+ // @ts-expect-error columnName and column are mutually exclusive
475
+ const filter: FilterUpdate = {
476
+ columnName: 'region', column: 'region', operator: 'EQ', values: ['west'],
477
+ };
478
+ // The runtime validator is laxer than the type - it reads whichever
479
+ // spelling is present - so this still passes at runtime. The point
480
+ // of the type is to stop the ambiguity reaching it.
481
+ expect(isValidUpdateFiltersPayload({ filter })).toBe(true);
482
+ });
483
+
484
+ it('rejects both operator spellings at compile time', () => {
485
+ // @ts-expect-error operator and oper are mutually exclusive
486
+ const filter: FilterUpdate = {
487
+ columnName: 'region', operator: 'EQ', oper: 'EQ', values: ['west'],
488
+ };
489
+ expect(isValidUpdateFiltersPayload({ filter })).toBe(true);
490
+ });
491
+
492
+ // The fields SCAL-325540 added to the host-event contract have to keep
493
+ // working alongside the exclusive alias pairs - the union must widen
494
+ // the column and operator halves only, and leave the rest of the
495
+ // filter shape alone.
496
+ it('accepts the fields added by the host-event contract', () => {
497
+ const filter: FilterUpdate = {
498
+ columnName: 'date',
499
+ columnId: 'col-guid',
500
+ operator: 'EQ',
501
+ values: ['2023-07-31'],
502
+ type: 'EXACT_DATE',
503
+ datePeriod: 'DAY',
504
+ negate: true,
505
+ applicability: { level: 'TAB', targetId: 'tab-guid' },
506
+ };
507
+ expect(isValidUpdateFiltersPayload({ filter })).toBe(true);
508
+ });
509
+
510
+ it('accepts non-string values alongside the documented spelling', () => {
511
+ const filter: FilterUpdate = {
512
+ columnName: 'quantity', operator: 'GE', values: [5, true],
513
+ };
514
+ expect(isValidUpdateFiltersPayload({ filter })).toBe(true);
515
+ });
516
+
517
+ it('accepts the request-level scoping fields', () => {
518
+ const payload = {
519
+ filters: [{ columnName: 'region', operator: 'IN', values: ['west'] }],
520
+ visualizationId: 'viz-guid',
521
+ liveboardId: 'lb-guid',
522
+ };
523
+ expect(isValidUpdateFiltersPayload(payload)).toBe(true);
524
+ });
525
+ });
526
+ describe('resolveUpdateFiltersAliases', () => {
527
+ it('rewrites columnName/operator to column/oper on a single filter', () => {
528
+ const out = resolveUpdateFiltersAliases({
529
+ filter: { columnName: 'region', operator: 'EQ', values: ['west'] },
530
+ } as any);
531
+
532
+ expect(out.filter).toEqual({ column: 'region', oper: 'EQ', values: ['west'] });
533
+ expect(out.filter).not.toHaveProperty('columnName');
534
+ expect(out.filter).not.toHaveProperty('operator');
535
+ });
536
+
537
+ it('rewrites every entry in a filters array', () => {
538
+ const out = resolveUpdateFiltersAliases({
539
+ filters: [
540
+ { columnName: 'region', operator: 'EQ', values: ['west'] },
541
+ { columnName: 'item type', operator: 'IN', values: ['bags'] },
542
+ ],
543
+ } as any);
544
+
545
+ expect(out.filters).toEqual([
546
+ { column: 'region', oper: 'EQ', values: ['west'] },
547
+ { column: 'item type', oper: 'IN', values: ['bags'] },
548
+ ]);
549
+ });
550
+
551
+ it('leaves a payload that already uses column/oper untouched', () => {
552
+ const payload = { filter: { column: 'region', oper: 'EQ', values: ['west'] } } as any;
553
+
554
+ expect(resolveUpdateFiltersAliases(payload)).toEqual(payload);
555
+ });
556
+
557
+ it('prefers columnName/operator when both spellings are present', () => {
558
+ const out = resolveUpdateFiltersAliases({
559
+ filter: {
560
+ column: 'from-column', columnName: 'from-columnName',
561
+ oper: 'EQ', operator: 'IN',
562
+ values: ['x'],
563
+ },
564
+ } as any);
565
+
566
+ // The value survives under the wire name, but it is the documented
567
+ // spelling's value that survives.
568
+ expect(out.filter).toEqual({ column: 'from-columnName', oper: 'IN', values: ['x'] });
569
+ });
570
+
571
+ it('preserves the other filter fields, including applicability', () => {
572
+ const out = resolveUpdateFiltersAliases({
573
+ filter: {
574
+ columnName: 'date',
575
+ operator: 'EQ',
576
+ values: [1700000000],
577
+ type: 'EXACT_DATE',
578
+ applicability: { level: 'TAB', targetId: 'tab-guid' },
579
+ },
580
+ } as any);
581
+
582
+ expect(out.filter).toEqual({
583
+ column: 'date',
584
+ oper: 'EQ',
585
+ values: [1700000000],
586
+ type: 'EXACT_DATE',
587
+ applicability: { level: 'TAB', targetId: 'tab-guid' },
588
+ });
589
+ });
590
+
591
+ it('produces a payload that still passes validation', () => {
592
+ const out = resolveUpdateFiltersAliases({
593
+ filters: [{ columnName: 'region', operator: 'EQ', values: ['west'] }],
594
+ } as any);
595
+
596
+ expect(isValidUpdateFiltersPayload(out as any)).toBe(true);
597
+ });
598
+
599
+ it('is a no-op on a non-object payload', () => {
600
+ expect(resolveUpdateFiltersAliases(undefined as any)).toBeUndefined();
601
+ expect(resolveUpdateFiltersAliases(null as any)).toBeNull();
602
+ });
603
+ });
604
+ });
@@ -10,7 +10,8 @@ import { embedEventStatus } from '../../utils';
10
10
 
11
11
  const isValidApplicability = (a?: { level?: string; targetId?: string }) => {
12
12
  if (isUndefined(a)) return true;
13
- // targetId is not required at LIVEBOARD level, since the filter applies to the whole Liveboard
13
+ // targetId is not required at LIVEBOARD level, since the filter applies to
14
+ // the whole Liveboard
14
15
  return isPlainObject(a)
15
16
  && Object.values(ApplicabilityLevel).includes(a.level as ApplicabilityLevel)
16
17
  && (a.level === ApplicabilityLevel.Liveboard || (isString(a.targetId) && a.targetId.trim().length > 0));
@@ -44,8 +45,56 @@ export function isValidUpdateFiltersPayload(
44
45
  return !!(hasValidFilter || hasValidFilters);
45
46
  }
46
47
 
48
+ /**
49
+ * Rewrites one filter's `columnName`/`operator` to `column`/`oper`.
50
+ *
51
+ * Callers may use either spelling: `columnName`/`operator` match
52
+ * {@link RuntimeFilter} and the `EmbedEvent.FilterChanged` payload, so a
53
+ * converted payload can be replayed as-is. But the payload is forwarded to the
54
+ * embedded app unchanged, and only `column`/`oper` are known to be understood
55
+ * there - so the alias must be swapped here, or it would pass validation and
56
+ * then be silently ignored.
57
+ *
58
+ * If a caller supplies both spellings of a field - which TypeScript rejects,
59
+ * but JavaScript callers can still do - the documented `columnName`/`operator`
60
+ * wins. Deferring to the deprecated spelling would quietly discard the value
61
+ * the rest of the SDK tells people to use.
62
+ * @param filter One entry from the UpdateFilters payload.
63
+ */
64
+ function resolveFilterAliases<T extends { column?: string; columnName?: string; oper?: string; operator?: string }>(
65
+ filter: T,
66
+ ): T {
67
+ if (!isPlainObject(filter)) return filter;
68
+
69
+ const column = filter.columnName ?? filter.column;
70
+ const oper = filter.operator ?? filter.oper;
71
+ const { columnName, operator, ...rest } = filter;
72
+
73
+ return {
74
+ ...rest,
75
+ ...(isUndefined(column) ? {} : { column }),
76
+ ...(isUndefined(oper) ? {} : { oper }),
77
+ } as T;
78
+ }
79
+
80
+ /**
81
+ * Rewrites every filter in an UpdateFilters payload to use `column`/`oper`,
82
+ * whichever spelling the caller supplied. Call after validation.
83
+ * @param payload The UpdateFilters host event payload.
84
+ */
85
+ export function resolveUpdateFiltersAliases<T extends { filter?: any; filters?: any[] }>(payload: T): T {
86
+ if (!isPlainObject(payload)) return payload;
87
+
88
+ return {
89
+ ...payload,
90
+ ...(payload.filter ? { filter: resolveFilterAliases(payload.filter) } : {}),
91
+ ...(Array.isArray(payload.filters) ? { filters: payload.filters.map(resolveFilterAliases) } : {}),
92
+ };
93
+ }
94
+
47
95
  export function isValidUpdateParametersPayload(payload: unknown): boolean {
48
- // Only validates the applicability of each parameter (null treated as absent); the rest is forwarded as-is for backward compatibility.
96
+ // Only validates the applicability of each parameter (null treated as
97
+ // absent); the rest is forwarded as-is for backward compatibility.
49
98
  if (!Array.isArray(payload)) return true;
50
99
  return payload.every((p) => {
51
100
  if (!isPlainObject(p)) return true;
@@ -3054,8 +3054,8 @@ describe('LiveboardEmbed updatedSpotterExperience tests', () => {
3054
3054
 
3055
3055
  describe('trigger typing contracts (compile-time)', () => {
3056
3056
  // Enforced by ts-jest at compile time. LiveboardEmbed OVERRIDES
3057
- // TsEmbed.trigger, so its generic defaults must be asserted separately —
3058
- // a bare `PayloadT` on the override silently collapses no-payload
3057
+ // TsEmbed.trigger, so its generic defaults must be asserted separately
3058
+ // — a bare `PayloadT` on the override silently collapses no-payload
3059
3059
  // responses back to `any` for every LiveboardEmbed consumer.
3060
3060
  type IsAny<T> = 0 extends 1 & T ? true : false;
3061
3061
  type Extends<A, B> = A extends B ? true : false;
@@ -3073,7 +3073,8 @@ describe('LiveboardEmbed updatedSpotterExperience tests', () => {
3073
3073
  { numberOfTabs: number; orderedTabIds: string[] }
3074
3074
  > = true;
3075
3075
  const probeUnknownField = (r: TabsResponse) =>
3076
- // @ts-expect-error — field not on the GetTabs contract (was silent when `any`)
3076
+ // @ts-expect-error — field not on the GetTabs contract (was
3077
+ // silent when `any`)
3077
3078
  r.tabCount;
3078
3079
  void probeUnknownField;
3079
3080
 
@@ -37,8 +37,9 @@ import { SpotterChatViewConfig, StarterPromptsConfig } from './conversation';
37
37
  import { buildStarterPromptsAppInitData } from './spotter-utils';
38
38
  import { SpotterVizConfig, buildSpotterVizAppInitData } from './spotter-viz-utils';
39
39
 
40
- // Home unmounts the liveboard container, which is how its state gets cleared. The
41
- // settle window gives the container time to do it before we ask for the liveboard back.
40
+ // Home unmounts the liveboard container, which is how its state gets cleared.
41
+ // The settle window gives the container time to do it before we ask for the
42
+ // liveboard back.
42
43
  const HOME_ROUTE = 'home';
43
44
  const HOME_SETTLE_MS = 200;
44
45
 
@@ -354,9 +355,10 @@ export interface LiveboardViewConfig
354
355
  */
355
356
  isPNGInScheduledEmailsEnabled?: boolean;
356
357
  /**
357
- * Enables the 'what you see is what you get' PDF export for Liveboards. Each tab is rendered on a single page
358
- * following the exact UI layout, instead of splitting visualizations across multiple A4 pages.
359
- * This feature is GA from SDK version 1.51.0 and ThoughtSpot version 26.8.0.cl.
358
+ * Enables the 'what you see is what you get' PDF export for Liveboards. Each tab is
359
+ * rendered on a single page following the exact UI layout, instead of splitting
360
+ * visualizations across multiple A4 pages. This feature is GA from SDK version
361
+ * 1.51.0 and ThoughtSpot version 26.8.0.cl.
360
362
  *
361
363
  * Supported embed types: `AppEmbed`, `LiveboardEmbed`
362
364
  * @type {boolean}
@@ -952,22 +954,25 @@ export class LiveboardEmbed extends V1Embed {
952
954
  protected beforePrerenderVisible(): void {
953
955
  super.beforePrerenderVisible();
954
956
 
955
- // Captured before showPreRender() hands the wrapper over to this instance.
956
- // Itself means a hide/show, not a hand-over, so there is nothing stale to clear.
957
+ // Captured before showPreRender() hands the wrapper over to this
958
+ // instance. Itself means a hide/show, not a hand-over, so there is
959
+ // nothing stale to clear.
957
960
  const previous = this.getPreRenderObj<LiveboardEmbed>();
958
961
  const showing = previous === this ? undefined : previous?.currentLiveboardState;
959
962
 
960
963
  this.executeAfterEmbedContainerLoaded(async () => {
961
- // Without this the params callback suspends on its await and Navigate
962
- // goes out first, so the container loads with the previous config's filters.
964
+ // Without this the params callback suspends on its await and
965
+ // Navigate goes out first, so the container loads with the previous
966
+ // config's filters.
963
967
  await this.preRenderParamsApplied;
964
968
  if (this.isShowingLiveboardRoute(showing)) {
965
- // Navigate to the route we are already on is a no-op, so the liveboard
966
- // keeps its state. Home unmounts the container, which clears it.
969
+ // Navigate to the route we are already on is a no-op, so the
970
+ // liveboard keeps its state. Home unmounts the container, which
971
+ // clears it.
967
972
  this.trigger(HostEvent.Navigate, HOME_ROUTE)
968
973
  .catch((error) => logger.warn('Could not route via home', error));
969
- // Not awaited above: the container never acks Navigate, so that promise
970
- // only settles on the 30s trigger timeout.
974
+ // Not awaited above: the container never acks Navigate, so
975
+ // that promise only settles on the 30s trigger timeout.
971
976
  await new Promise((resolve) => {
972
977
  setTimeout(resolve, HOME_SETTLE_MS);
973
978
  });
@@ -987,8 +992,9 @@ export class LiveboardEmbed extends V1Embed {
987
992
  });
988
993
  }
989
994
 
990
- // The whole route, not just the liveboard id: the path is built from the viz, tab
991
- // and view too, so a same-liveboard show onto another tab is a real navigation.
995
+ // The whole route, not just the liveboard id: the path is built from the
996
+ // viz, tab and view too, so a same-liveboard show onto another tab is a
997
+ // real navigation.
992
998
  private isShowingLiveboardRoute(showing?: LiveboardEmbed['currentLiveboardState']): boolean {
993
999
  if (!showing?.liveboardId) return false;
994
1000
  const {
@@ -3,6 +3,7 @@ import {
3
3
  buildSpotterSidebarAppInitData,
4
4
  buildSpotterShareConversationAppInitData,
5
5
  buildStarterPromptsAppInitData,
6
+ buildSpotterAnalystAppInitData,
6
7
  } from './spotter-utils';
7
8
  import { ErrorDetailsTypes, EmbedErrorCodes } from '../types';
8
9
  import { ERROR_MESSAGE } from '../errors';
@@ -245,3 +246,31 @@ describe('buildStarterPromptsAppInitData', () => {
245
246
  expect(result.embedParams?.starterPrompts).toEqual({ enable: true, liveboard });
246
247
  });
247
248
  });
249
+
250
+ describe('buildSpotterAnalystAppInitData', () => {
251
+ const base = { type: 'APP_INIT' } as any;
252
+
253
+ it('returns the payload unchanged when analystId is absent', () => {
254
+ expect(buildSpotterAnalystAppInitData(base, {})).toBe(base);
255
+ expect(buildSpotterAnalystAppInitData(base, { spotterAnalystConfig: {} })).toBe(base);
256
+ });
257
+
258
+ it('nests spotterAnalystConfig under embedParams', () => {
259
+ const result = buildSpotterAnalystAppInitData(base, {
260
+ spotterAnalystConfig: { analystId: 'analyst-id-1234' },
261
+ });
262
+ expect(result.embedParams?.spotterAnalystConfig).toEqual({ analystId: 'analyst-id-1234' });
263
+ });
264
+
265
+ it('preserves existing embedParams keys', () => {
266
+ const withExisting = {
267
+ ...base,
268
+ embedParams: { spotterSidebarConfig: { enablePastConversationsSidebar: true } },
269
+ } as any;
270
+ const result = buildSpotterAnalystAppInitData(withExisting, {
271
+ spotterAnalystConfig: { analystId: 'analyst-id-1234' },
272
+ });
273
+ expect(result.embedParams?.spotterSidebarConfig).toEqual({ enablePastConversationsSidebar: true });
274
+ expect(result.embedParams?.spotterAnalystConfig).toEqual({ analystId: 'analyst-id-1234' });
275
+ });
276
+ });
@@ -6,6 +6,7 @@ import type {
6
6
  SpotterShareConversationConfig,
7
7
  SpotterChatViewConfig,
8
8
  StarterPromptsConfig,
9
+ SpotterAnalystConfig,
9
10
  } from './conversation';
10
11
  import type { VisualizationOverrides } from '../types';
11
12
 
@@ -114,3 +115,18 @@ export function buildStarterPromptsAppInitData<T extends DefaultAppInitData>(
114
115
  },
115
116
  };
116
117
  }
118
+
119
+ export function buildSpotterAnalystAppInitData<T extends DefaultAppInitData>(
120
+ initData: T,
121
+ viewConfig: { spotterAnalystConfig?: SpotterAnalystConfig },
122
+ ): T & { embedParams?: { spotterAnalystConfig?: SpotterAnalystConfig } } {
123
+ const { spotterAnalystConfig } = viewConfig;
124
+ if (!spotterAnalystConfig?.analystId) return initData;
125
+ return {
126
+ ...initData,
127
+ embedParams: {
128
+ ...((initData as T & { embedParams?: Record<string, unknown> }).embedParams || {}),
129
+ spotterAnalystConfig,
130
+ },
131
+ };
132
+ }