@thoughtspot/visual-embed-sdk 1.49.2 → 1.50.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 (153) hide show
  1. package/cjs/package.json +4 -10
  2. package/cjs/src/authToken.spec.js +5 -0
  3. package/cjs/src/authToken.spec.js.map +1 -1
  4. package/cjs/src/embed/app.d.ts +7 -1
  5. package/cjs/src/embed/app.d.ts.map +1 -1
  6. package/cjs/src/embed/app.js +6 -0
  7. package/cjs/src/embed/app.js.map +1 -1
  8. package/cjs/src/embed/app.spec.js +11 -2
  9. package/cjs/src/embed/app.spec.js.map +1 -1
  10. package/cjs/src/embed/conversation.d.ts +12 -0
  11. package/cjs/src/embed/conversation.d.ts.map +1 -1
  12. package/cjs/src/embed/conversation.js.map +1 -1
  13. package/cjs/src/embed/conversation.spec.js +30 -0
  14. package/cjs/src/embed/conversation.spec.js.map +1 -1
  15. package/cjs/src/embed/embedConfig.spec.d.ts +2 -0
  16. package/cjs/src/embed/embedConfig.spec.d.ts.map +1 -0
  17. package/cjs/src/embed/embedConfig.spec.js +74 -0
  18. package/cjs/src/embed/embedConfig.spec.js.map +1 -0
  19. package/cjs/src/embed/host-events.spec.d.ts +2 -0
  20. package/cjs/src/embed/host-events.spec.d.ts.map +1 -0
  21. package/cjs/src/embed/host-events.spec.js +1219 -0
  22. package/cjs/src/embed/host-events.spec.js.map +1 -0
  23. package/cjs/src/embed/liveboard.d.ts +6 -0
  24. package/cjs/src/embed/liveboard.d.ts.map +1 -1
  25. package/cjs/src/embed/liveboard.js.map +1 -1
  26. package/cjs/src/embed/spotter-viz-utils.d.ts +29 -0
  27. package/cjs/src/embed/spotter-viz-utils.d.ts.map +1 -1
  28. package/cjs/src/embed/spotter-viz-utils.js.map +1 -1
  29. package/cjs/src/index.d.ts +2 -2
  30. package/cjs/src/index.d.ts.map +1 -1
  31. package/cjs/src/index.js.map +1 -1
  32. package/cjs/src/mixpanel-service.spec.js +14 -0
  33. package/cjs/src/mixpanel-service.spec.js.map +1 -1
  34. package/cjs/src/types.d.ts +93 -6
  35. package/cjs/src/types.d.ts.map +1 -1
  36. package/cjs/src/types.js +88 -3
  37. package/cjs/src/types.js.map +1 -1
  38. package/cjs/src/utils/custom-actions.spec.js +26 -0
  39. package/cjs/src/utils/custom-actions.spec.js.map +1 -1
  40. package/cjs/src/utils/graphql/answerService/answerService.spec.js +15 -0
  41. package/cjs/src/utils/graphql/answerService/answerService.spec.js.map +1 -1
  42. package/cjs/src/utils/processData.spec.js +30 -0
  43. package/cjs/src/utils/processData.spec.js.map +1 -1
  44. package/cjs/src/utils/reporting.spec.js +21 -0
  45. package/cjs/src/utils/reporting.spec.js.map +1 -1
  46. package/cjs/src/utils/sessionInfoService.spec.d.ts +2 -0
  47. package/cjs/src/utils/sessionInfoService.spec.d.ts.map +1 -0
  48. package/cjs/src/utils/sessionInfoService.spec.js +130 -0
  49. package/cjs/src/utils/sessionInfoService.spec.js.map +1 -0
  50. package/cjs/src/utils.js +2 -3
  51. package/cjs/src/utils.js.map +1 -1
  52. package/cjs/src/utils.spec.js +158 -0
  53. package/cjs/src/utils.spec.js.map +1 -1
  54. package/dist/{index-CS7yJ2Jj.js → index-B5xoN6Cp.js} +303 -187
  55. package/dist/src/embed/app.d.ts +7 -1
  56. package/dist/src/embed/app.d.ts.map +1 -1
  57. package/dist/src/embed/conversation.d.ts +12 -0
  58. package/dist/src/embed/conversation.d.ts.map +1 -1
  59. package/dist/src/embed/embedConfig.spec.d.ts +2 -0
  60. package/dist/src/embed/embedConfig.spec.d.ts.map +1 -0
  61. package/dist/src/embed/host-events.spec.d.ts +2 -0
  62. package/dist/src/embed/host-events.spec.d.ts.map +1 -0
  63. package/dist/src/embed/liveboard.d.ts +6 -0
  64. package/dist/src/embed/liveboard.d.ts.map +1 -1
  65. package/dist/src/embed/spotter-viz-utils.d.ts +29 -0
  66. package/dist/src/embed/spotter-viz-utils.d.ts.map +1 -1
  67. package/dist/src/index.d.ts +2 -2
  68. package/dist/src/index.d.ts.map +1 -1
  69. package/dist/src/types.d.ts +93 -6
  70. package/dist/src/types.d.ts.map +1 -1
  71. package/dist/src/utils/sessionInfoService.spec.d.ts +2 -0
  72. package/dist/src/utils/sessionInfoService.spec.d.ts.map +1 -0
  73. package/dist/tsembed-react.es.js +126 -18
  74. package/dist/tsembed-react.js +427 -203
  75. package/dist/tsembed.es.js +126 -18
  76. package/dist/tsembed.js +427 -203
  77. package/dist/visual-embed-sdk-react-full.d.ts +147 -7
  78. package/dist/visual-embed-sdk-react.d.ts +147 -7
  79. package/dist/visual-embed-sdk.d.ts +147 -7
  80. package/lib/package.json +4 -10
  81. package/lib/src/authToken.spec.js +5 -0
  82. package/lib/src/authToken.spec.js.map +1 -1
  83. package/lib/src/embed/app.d.ts +7 -1
  84. package/lib/src/embed/app.d.ts.map +1 -1
  85. package/lib/src/embed/app.js +6 -0
  86. package/lib/src/embed/app.js.map +1 -1
  87. package/lib/src/embed/app.spec.js +11 -2
  88. package/lib/src/embed/app.spec.js.map +1 -1
  89. package/lib/src/embed/conversation.d.ts +12 -0
  90. package/lib/src/embed/conversation.d.ts.map +1 -1
  91. package/lib/src/embed/conversation.js.map +1 -1
  92. package/lib/src/embed/conversation.spec.js +30 -0
  93. package/lib/src/embed/conversation.spec.js.map +1 -1
  94. package/lib/src/embed/embedConfig.spec.d.ts +2 -0
  95. package/lib/src/embed/embedConfig.spec.d.ts.map +1 -0
  96. package/lib/src/embed/embedConfig.spec.js +72 -0
  97. package/lib/src/embed/embedConfig.spec.js.map +1 -0
  98. package/lib/src/embed/host-events.spec.d.ts +2 -0
  99. package/lib/src/embed/host-events.spec.d.ts.map +1 -0
  100. package/lib/src/embed/host-events.spec.js +1216 -0
  101. package/lib/src/embed/host-events.spec.js.map +1 -0
  102. package/lib/src/embed/liveboard.d.ts +6 -0
  103. package/lib/src/embed/liveboard.d.ts.map +1 -1
  104. package/lib/src/embed/liveboard.js.map +1 -1
  105. package/lib/src/embed/spotter-viz-utils.d.ts +29 -0
  106. package/lib/src/embed/spotter-viz-utils.d.ts.map +1 -1
  107. package/lib/src/embed/spotter-viz-utils.js.map +1 -1
  108. package/lib/src/index.d.ts +2 -2
  109. package/lib/src/index.d.ts.map +1 -1
  110. package/lib/src/index.js.map +1 -1
  111. package/lib/src/mixpanel-service.spec.js +14 -0
  112. package/lib/src/mixpanel-service.spec.js.map +1 -1
  113. package/lib/src/types.d.ts +93 -6
  114. package/lib/src/types.d.ts.map +1 -1
  115. package/lib/src/types.js +88 -3
  116. package/lib/src/types.js.map +1 -1
  117. package/lib/src/utils/custom-actions.spec.js +26 -0
  118. package/lib/src/utils/custom-actions.spec.js.map +1 -1
  119. package/lib/src/utils/graphql/answerService/answerService.spec.js +15 -0
  120. package/lib/src/utils/graphql/answerService/answerService.spec.js.map +1 -1
  121. package/lib/src/utils/processData.spec.js +30 -0
  122. package/lib/src/utils/processData.spec.js.map +1 -1
  123. package/lib/src/utils/reporting.spec.js +21 -0
  124. package/lib/src/utils/reporting.spec.js.map +1 -1
  125. package/lib/src/utils/sessionInfoService.spec.d.ts +2 -0
  126. package/lib/src/utils/sessionInfoService.spec.d.ts.map +1 -0
  127. package/lib/src/utils/sessionInfoService.spec.js +127 -0
  128. package/lib/src/utils/sessionInfoService.spec.js.map +1 -0
  129. package/lib/src/utils.js +1 -1
  130. package/lib/src/utils.js.map +1 -1
  131. package/lib/src/utils.spec.js +159 -1
  132. package/lib/src/utils.spec.js.map +1 -1
  133. package/lib/src/visual-embed-sdk.d.ts +147 -7
  134. package/package.json +4 -10
  135. package/src/authToken.spec.ts +6 -0
  136. package/src/embed/app.spec.ts +15 -2
  137. package/src/embed/app.ts +13 -1
  138. package/src/embed/conversation.spec.ts +32 -0
  139. package/src/embed/conversation.ts +12 -0
  140. package/src/embed/embedConfig.spec.ts +79 -0
  141. package/src/embed/host-events.spec.ts +1635 -0
  142. package/src/embed/liveboard.ts +6 -0
  143. package/src/embed/spotter-viz-utils.ts +30 -0
  144. package/src/index.ts +2 -1
  145. package/src/mixpanel-service.spec.ts +15 -0
  146. package/src/types.ts +96 -6
  147. package/src/utils/custom-actions.spec.ts +28 -0
  148. package/src/utils/graphql/answerService/answerService.spec.ts +18 -0
  149. package/src/utils/processData.spec.ts +32 -0
  150. package/src/utils/reporting.spec.ts +27 -0
  151. package/src/utils/sessionInfoService.spec.ts +151 -0
  152. package/src/utils.spec.ts +195 -0
  153. package/src/utils.ts +1 -1
@@ -574,6 +574,12 @@ export interface LiveboardViewConfig extends BaseViewConfig, LiveboardOtherViewC
574
574
  * inputChatPlaceholder: 'Ask a question...',
575
575
  * hideStarterPrompts: false,
576
576
  * customStarterPrompts: [{ id: '1', displayText: 'Top products', fullPrompt: 'What are the top products by revenue?' }],
577
+ * // loaderHeadline and loaderTips require SDK: 1.51.0 | ThoughtSpot Cloud: 26.8.0.cl
578
+ * loaderHeadline: 'Crunching the numbers...',
579
+ * loaderTips: [
580
+ * { label: 'Tip', text: 'try asking about revenue by region' },
581
+ * { label: 'Tip', text: 'use natural language' },
582
+ * ],
577
583
  * },
578
584
  * })
579
585
  * ```
@@ -32,6 +32,12 @@ export interface SpotterVizStarterPrompt {
32
32
  * customStarterPrompts: [
33
33
  * { id: '1', displayText: 'Top products', fullPrompt: 'What are the top products by revenue?' }
34
34
  * ],
35
+ * // loaderHeadline and loaderTips require SDK: 1.51.0 | ThoughtSpot Cloud: 26.8.0.cl
36
+ * loaderHeadline: 'Crunching the numbers...',
37
+ * loaderTips: [
38
+ * { label: 'Tip', text: 'try asking about revenue by region' },
39
+ * { label: 'Tip', text: 'use natural language' },
40
+ * ],
35
41
  * },
36
42
  * })
37
43
  * ```
@@ -76,6 +82,30 @@ export interface SpotterVizConfig {
76
82
  * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl
77
83
  */
78
84
  inputChatPlaceholder?: string;
85
+ /**
86
+ * Custom headline text shown in the SpotterViz loading state.
87
+ * Replaces the default loading headline.
88
+ * @version SDK: 1.51.0 | ThoughtSpot Cloud: 26.8.0.cl
89
+ */
90
+ loaderHeadline?: string;
91
+ /**
92
+ * Custom tips shown in the SpotterViz loading state.
93
+ * Replaces the default loading tips with the provided list.
94
+ * @version SDK: 1.51.0 | ThoughtSpot Cloud: 26.8.0.cl
95
+ */
96
+ loaderTips?: SpotterVizLoaderTip[];
97
+ }
98
+
99
+ /**
100
+ * A single tip shown in the SpotterViz loading state.
101
+ * @version SDK: 1.51.0 | ThoughtSpot Cloud: 26.8.0.cl
102
+ * @group Embed components
103
+ */
104
+ export interface SpotterVizLoaderTip {
105
+ /** Short label rendered alongside the tip (e.g. "Tip"). */
106
+ label: string;
107
+ /** Tip body text. */
108
+ text: string;
79
109
  }
80
110
 
81
111
  export function buildSpotterVizAppInitData<T extends DefaultAppInitData>(
package/src/index.ts CHANGED
@@ -33,7 +33,7 @@ import { SearchEmbed, SearchViewConfig } from './embed/search';
33
33
  import { SearchBarEmbed, SearchBarViewConfig } from './embed/search-bar';
34
34
  import { SpotterAgentEmbed, SpotterAgentEmbedViewConfig, BodylessConversation, BodylessConversationViewConfig} from './embed/bodyless-conversation';
35
35
  import { SpotterEmbed, SpotterEmbedViewConfig, SpotterChatViewConfig, SpotterSidebarViewConfig, ConversationEmbed, ConversationViewConfig } from './embed/conversation';
36
- import { SpotterVizConfig, SpotterVizStarterPrompt } from './embed/spotter-viz-utils';
36
+ import { SpotterVizConfig, SpotterVizStarterPrompt, SpotterVizLoaderTip } from './embed/spotter-viz-utils';
37
37
  import {
38
38
  AuthFailureType, AuthStatus, AuthEvent, AuthEventEmitter,
39
39
  } from './auth';
@@ -143,6 +143,7 @@ export {
143
143
  LiveboardViewConfig,
144
144
  SpotterVizConfig,
145
145
  SpotterVizStarterPrompt,
146
+ SpotterVizLoaderTip,
146
147
  AppViewConfig,
147
148
  PrefetchFeatures,
148
149
  FrameParams,
@@ -89,4 +89,19 @@ describe('Unit test for mixpanel', () => {
89
89
  expect(logger.error).toHaveBeenCalled();
90
90
  expect(mixpanel.register_once).not.toHaveBeenCalled();
91
91
  });
92
+
93
+ test('logs error when mixpanel.init throws (covers catch block)', () => {
94
+ testResetMixpanel();
95
+ jest.spyOn(logger, 'error').mockImplementation(() => {});
96
+ (mixpanel.init as jest.Mock).mockImplementationOnce(() => {
97
+ throw new Error('mixpanel init failed');
98
+ });
99
+ const sessionInfo = {
100
+ mixpanelToken: 'abc123',
101
+ userGUID: '12345',
102
+ isPublicUser: false,
103
+ } as SessionInfo;
104
+ initMixpanel(sessionInfo);
105
+ expect(logger.error).toHaveBeenCalledWith('Error initializing mixpanel', expect.any(Error));
106
+ });
92
107
  });
package/src/types.ts CHANGED
@@ -963,7 +963,8 @@ export interface BaseViewConfig extends ApiInterceptFlags {
963
963
  * The list of actions to completely remove from the embedded view.
964
964
  * Hidden actions are not visible to the user at all (fully removed from the UI).
965
965
  * Use this when you want to remove an action entirely.
966
- * To keep an action visible but non-interactive (grayed out), use {@link disabledActions} instead.
966
+ * To keep an action visible but non-interactive (grayed out), use {@link
967
+ * disabledActions} instead.
967
968
  *
968
969
  * Supported embed types: `AppEmbed`, `LiveboardEmbed`, `SearchEmbed`, `SpotterAgentEmbed`, `SpotterEmbed`, `SearchBarEmbed`
969
970
  * @version SDK: 1.6.0 | ThoughtSpot: ts8.nov.cl, 8.4.1.sw
@@ -983,7 +984,8 @@ export interface BaseViewConfig extends ApiInterceptFlags {
983
984
  * (...), and the contextual menu. These will be only actions that
984
985
  * are visible to the user.
985
986
  * Use this as an allowlist — only the actions listed here will be shown.
986
- * All other actions will be hidden. Use either this or {@link hiddenActions}, not both.
987
+ * All other actions will be hidden. Use either this or {@link hiddenActions}, not
988
+ * both.
987
989
  *
988
990
  * Supported embed types: `AppEmbed`, `LiveboardEmbed`, `SearchEmbed`, `SpotterAgentEmbed`, `SpotterEmbed`, `SearchBarEmbed`
989
991
  * @version SDK: 1.6.0 | ThoughtSpot: ts8.nov.cl, 8.4.1.sw
@@ -3760,10 +3762,11 @@ export enum EmbedEvent {
3760
3762
  EmbedPageContextChanged = 'EmbedPageContextChanged',
3761
3763
 
3762
3764
  /**
3763
- * Represents a special embed event that is triggered whenever any host event is subscribed.
3765
+ * Represents a special embed event that is triggered whenever any host event is
3766
+ * subscribed.
3764
3767
  *
3765
- * You can listen to this event when you need to dispatch a host event during load or render,
3766
- * particularly in situations where timing issues may occur.
3768
+ * You can listen to this event when you need to dispatch a host event during load or
3769
+ * render, particularly in situations where timing issues may occur.
3767
3770
  *
3768
3771
  * @example
3769
3772
  * ```js
@@ -6016,6 +6019,27 @@ export enum HostEvent {
6016
6019
  * ```
6017
6020
  */
6018
6021
  InitSpotterVizConversation = 'InitSpotterVizConversation',
6022
+
6023
+ /**
6024
+ * Opens the SpotterViz panel.
6025
+ * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl
6026
+ * @example
6027
+ * ```js
6028
+ * liveboardEmbed.trigger(HostEvent.OpenSpotterVizPanel);
6029
+ * ```
6030
+ */
6031
+ OpenSpotterVizPanel = 'OpenSpotterVizPanel',
6032
+
6033
+ /**
6034
+ * Closes the SpotterViz panel.
6035
+ * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl
6036
+ * @example
6037
+ * ```js
6038
+ * liveboardEmbed.trigger(HostEvent.CloseSpotterVizPanel);
6039
+ * ```
6040
+ */
6041
+ CloseSpotterVizPanel = 'CloseSpotterVizPanel',
6042
+
6019
6043
  /**
6020
6044
  * Clears browser cache and fetches new data for liveboard ChartViz Containers.
6021
6045
  * Requires `enableLiveboardDataCache` to be enabled.
@@ -7972,7 +7996,73 @@ export enum Action {
7972
7996
  * ```
7973
7997
  * @version SDK: 1.49.0 | ThoughtSpot Cloud: 26.6.0.cl
7974
7998
  */
7975
- RefreshLiveboardBrowserCache = 'refreshLiveboardBrowserCache',
7999
+ RefreshLiveboardBrowserCache = 'refreshLiveboardBrowserCache',
8000
+ /**
8001
+ * Controls visibility and disable state of the share action
8002
+ * in the Spotter Analyst interface.
8003
+ * @version SDK: 1.51.0 | ThoughtSpot Cloud: 26.8.0.cl
8004
+ * @example
8005
+ * ```js
8006
+ * hiddenActions: [Action.SpotterAnalystShare]
8007
+ * disabledActions: [Action.SpotterAnalystShare]
8008
+ * ```
8009
+ */
8010
+ SpotterAnalystShare = 'spotterAnalystShare',
8011
+ /**
8012
+ * Controls visibility and disable state of the edit action
8013
+ * in the Spotter Analyst interface.
8014
+ * @version SDK: 1.51.0 | ThoughtSpot Cloud: 26.8.0.cl
8015
+ * @example
8016
+ * ```js
8017
+ * hiddenActions: [Action.SpotterAnalystEdit]
8018
+ * disabledActions: [Action.SpotterAnalystEdit]
8019
+ * ```
8020
+ */
8021
+ SpotterAnalystEdit = 'spotterAnalystEdit',
8022
+ /**
8023
+ * Controls visibility and disable state of the create action
8024
+ * in the Spotter Analyst interface.
8025
+ * @version SDK: 1.51.0 | ThoughtSpot Cloud: 26.8.0.cl
8026
+ * @example
8027
+ * ```js
8028
+ * hiddenActions: [Action.SpotterAnalystCreate]
8029
+ * disabledActions: [Action.SpotterAnalystCreate]
8030
+ * ```
8031
+ */
8032
+ SpotterAnalystCreate = 'spotterAnalystCreate',
8033
+ /**
8034
+ * Controls visibility and disable state of the delete action
8035
+ * in the Spotter Analyst interface.
8036
+ * @version SDK: 1.51.0 | ThoughtSpot Cloud: 26.8.0.cl
8037
+ * @example
8038
+ * ```js
8039
+ * hiddenActions: [Action.SpotterAnalystDelete]
8040
+ * disabledActions: [Action.SpotterAnalystDelete]
8041
+ * ```
8042
+ */
8043
+ SpotterAnalystDelete = 'spotterAnalystDelete',
8044
+ /**
8045
+ * Controls visibility and disable state of the make a copy action
8046
+ * in the Spotter Analyst interface.
8047
+ * @version SDK: 1.51.0 | ThoughtSpot Cloud: 26.8.0.cl
8048
+ * @example
8049
+ * ```js
8050
+ * hiddenActions: [Action.SpotterAnalystMakeACopy]
8051
+ * disabledActions: [Action.SpotterAnalystMakeACopy]
8052
+ * ```
8053
+ */
8054
+ SpotterAnalystMakeACopy = 'spotterAnalystMakeACopy',
8055
+ /**
8056
+ * Controls visibility and disable state of the sidebar
8057
+ * in the Spotter Analyst interface.
8058
+ * @version SDK: 1.51.0 | ThoughtSpot Cloud: 26.8.0.cl
8059
+ * @example
8060
+ * ```js
8061
+ * hiddenActions: [Action.SpotterAnalystSidebar]
8062
+ * disabledActions: [Action.SpotterAnalystSidebar]
8063
+ * ```
8064
+ */
8065
+ SpotterAnalystSidebar = 'spotterAnalystSidebar',
7976
8066
  }
7977
8067
  export interface AnswerServiceType {
7978
8068
  getAnswer?: (offset: number, batchSize: number) => any;
@@ -429,6 +429,34 @@ describe('getCustomActions function', () => {
429
429
  });
430
430
  });
431
431
 
432
+ describe('Empty allowed IDs edge cases', () => {
433
+ test('should show "none" as supported metadata IDs when target has no allowed metadata IDs (SPOTTER)', () => {
434
+ const action = {
435
+ id: 'test-id',
436
+ name: 'Test Action',
437
+ target: CustomActionTarget.SPOTTER,
438
+ position: CustomActionsPosition.MENU,
439
+ metadataIds: { liveboardIds: ['lb-1'] },
440
+ } as any;
441
+ const result = getCustomActions([action]);
442
+ // Expect an error containing 'none' for unsupported metadataIds on SPOTTER
443
+ expect(result.errors.some((e) => e.includes('none'))).toBe(true);
444
+ });
445
+
446
+ test('should show "none" as supported data model IDs when target has no allowed data model IDs (LIVEBOARD)', () => {
447
+ const action = {
448
+ id: 'test-id',
449
+ name: 'Test Action',
450
+ target: CustomActionTarget.LIVEBOARD,
451
+ position: CustomActionsPosition.PRIMARY,
452
+ dataModelIds: { modelIds: ['model-1'] },
453
+ } as any;
454
+ const result = getCustomActions([action]);
455
+ // Expect an error containing 'none' for unsupported dataModelIds on LIVEBOARD
456
+ expect(result.errors.some((e) => e.includes('none'))).toBe(true);
457
+ });
458
+ });
459
+
432
460
  describe('Warnings', () => {
433
461
  test('should warn when action name length exceeds 30 characters', () => {
434
462
  // Arrange
@@ -707,4 +707,22 @@ describe('Answer service tests', () => {
707
707
  expect(answer.name).toBe('name');
708
708
  expect(answer.id).toBe('bar');
709
709
  });
710
+
711
+ test('getTML parses nested objects and arrays from the YAML edoc', async () => {
712
+ const edoc = [
713
+ 'answer:',
714
+ ' name: Revenue answer',
715
+ ' visualizations:',
716
+ ' - id: viz-1',
717
+ ' type: TABLE',
718
+ ].join('\n');
719
+ fetchMock.mockResponseOnce(JSON.stringify({
720
+ data: { UnsavedAnswer_getTML: { object: [{ edoc }] } },
721
+ }));
722
+
723
+ const { answer } = await createAnswerService().getTML();
724
+
725
+ expect(answer.name).toBe('Revenue answer');
726
+ expect(answer.visualizations).toEqual([{ id: 'viz-1', type: 'TABLE' }]);
727
+ });
710
728
  });
@@ -279,6 +279,38 @@ describe('Unit test for process data', () => {
279
279
  mockHandleExitPresentMode.mockReset();
280
280
  });
281
281
 
282
+ test('AuthInit with payload.userGUID fallback when data.userGUID is absent', () => {
283
+ const e = {
284
+ type: EmbedEvent.AuthInit,
285
+ payload: { userGUID: 'payloadGUID' },
286
+ };
287
+ jest.spyOn(base, 'notifyAuthSuccess');
288
+ expect(processDataInstance.processEventData(e.type, e, '', null)).toEqual({
289
+ type: e.type,
290
+ payload: { userGUID: 'payloadGUID' },
291
+ data: { userGUID: 'payloadGUID' },
292
+ });
293
+ expect(base.notifyAuthSuccess).toHaveBeenCalled();
294
+ });
295
+
296
+ test('process authFailure EmbeddedSSO with UNAUTHENTICATED_FAILURE suppresses login page', () => {
297
+ const e = {
298
+ type: EmbedEvent.AuthFailure,
299
+ data: { type: auth.AuthFailureType.UNAUTHENTICATED_FAILURE },
300
+ };
301
+ jest.spyOn(base, 'notifyAuthFailure');
302
+ jest.spyOn(embedConfigInstance, 'getEmbedConfig').mockReturnValue({
303
+ loginFailedMessage: 'Hello',
304
+ authType: AuthType.EmbeddedSSO,
305
+ disableLoginFailurePage: false,
306
+ } as any);
307
+ const el: any = {};
308
+ processDataInstance.processEventData(e.type, e, '', el);
309
+ // isEmbeddedSSOInfoFailure=true so neither branch fires, innerHTML stays unset
310
+ expect(el.innerHTML).toBeUndefined();
311
+ expect(base.notifyAuthFailure).not.toHaveBeenCalled();
312
+ });
313
+
282
314
  test('should handle ClearInfoCache', () => {
283
315
  const mockResetCachedPreauthInfo = jest.spyOn(sessionInfoService, 'resetCachedPreauthInfo').mockImplementation(() => {});
284
316
  const mockResetCachedSessionInfo = jest.spyOn(sessionInfoService, 'resetCachedSessionInfo').mockImplementation(() => {});
@@ -88,4 +88,31 @@ describe('ReportingObserver', () => {
88
88
 
89
89
  (window as any).ReportingObserver = currentObserver;
90
90
  });
91
+
92
+ test('should suppress alert but still log error when suppressErrorAlerts is true', () => {
93
+ let callBackPassed: any;
94
+ const NewMockRO = jest.fn().mockImplementation((callback: any) => {
95
+ callBackPassed = callback;
96
+ return ({ observe: jest.fn(), disconnect: jest.fn(), takeRecords: jest.fn() });
97
+ });
98
+ const currentObserver = (window as any).ReportingObserver;
99
+ (window as any).ReportingObserver = NewMockRO;
100
+
101
+ setEmbedConfig({
102
+ thoughtSpotHost: 'testHost',
103
+ authType: AuthType.None,
104
+ suppressErrorAlerts: true,
105
+ } as any);
106
+
107
+ const mockAlert = jest.fn();
108
+ window.alert = mockAlert;
109
+
110
+ registerReportingObserver(true);
111
+ callBackPassed([{ type: 'csp-violation', url: 'testHost', body: { effectiveDirective: 'frame-ancestors' } }]);
112
+
113
+ expect(mockAlert).not.toHaveBeenCalled();
114
+ expect(logger.error).toHaveBeenCalledWith(ERROR_MESSAGE.CSP_FRAME_HOST_VIOLATION_LOG_MESSAGE);
115
+
116
+ (window as any).ReportingObserver = currentObserver;
117
+ });
91
118
  });
@@ -0,0 +1,151 @@
1
+ import * as authServiceModule from './authService';
2
+ import * as embedConfigModule from '../embed/embedConfig';
3
+ import {
4
+ formatPreauthInfo,
5
+ getPreauthInfo,
6
+ getSessionInfo,
7
+ resetCachedPreauthInfo,
8
+ resetCachedSessionInfo,
9
+ getCachedSessionInfo,
10
+ getSessionDetails,
11
+ } from './sessionInfoService';
12
+
13
+ describe('sessionInfoService', () => {
14
+ beforeEach(() => {
15
+ resetCachedPreauthInfo();
16
+ resetCachedSessionInfo();
17
+ jest.clearAllMocks();
18
+ });
19
+
20
+ describe('formatPreauthInfo', () => {
21
+ it('parses headers and JSON body from a response', async () => {
22
+ const headers = new Headers({ 'content-type': 'application/json' });
23
+ const mockResp = {
24
+ headers,
25
+ json: jest.fn().mockResolvedValue({ info: { userGUID: 'u1' } }),
26
+ };
27
+ const result = await formatPreauthInfo(mockResp);
28
+ expect(result.info.userGUID).toBe('u1');
29
+ expect(result.status).toBe(200);
30
+ expect(result.headers['content-type']).toBe('application/json');
31
+ });
32
+
33
+ it('returns null when response.json() throws (covers catch block)', async () => {
34
+ const mockResp = {
35
+ headers: null as any,
36
+ json: jest.fn().mockRejectedValue(new Error('parse error')),
37
+ };
38
+ const result = await formatPreauthInfo(mockResp);
39
+ expect(result).toBeNull();
40
+ });
41
+
42
+ it('handles response where headers is null (covers headers?.forEach branch)', async () => {
43
+ const mockResp = {
44
+ headers: null as any,
45
+ json: jest.fn().mockResolvedValue({ info: {} }),
46
+ };
47
+ const result = await formatPreauthInfo(mockResp);
48
+ expect(result.headers).toEqual({});
49
+ });
50
+ });
51
+
52
+ describe('getPreauthInfo', () => {
53
+ it('fetches and caches preauthInfo on first call', async () => {
54
+ const mockResp = {
55
+ headers: new Headers({ 'x-caller': 'test' }),
56
+ json: jest.fn().mockResolvedValue({ info: { userGUID: 'u1' } }),
57
+ };
58
+ jest.spyOn(embedConfigModule, 'getEmbedConfig').mockReturnValue({ thoughtSpotHost: 'https://ts' } as any);
59
+ jest.spyOn(authServiceModule, 'fetchPreauthInfoService').mockResolvedValue(mockResp as any);
60
+
61
+ const result = await getPreauthInfo();
62
+ expect(result.info.userGUID).toBe('u1');
63
+ });
64
+
65
+ it('returns cached preauthInfo on subsequent calls (covers allowCache=true && preauthInfo cached branch)', async () => {
66
+ const mockResp = {
67
+ headers: new Headers(),
68
+ json: jest.fn().mockResolvedValue({ info: { userGUID: 'cached-user' } }),
69
+ };
70
+ jest.spyOn(embedConfigModule, 'getEmbedConfig').mockReturnValue({ thoughtSpotHost: 'https://ts' } as any);
71
+ const fetchSpy = jest.spyOn(authServiceModule, 'fetchPreauthInfoService').mockResolvedValue(mockResp as any);
72
+
73
+ await getPreauthInfo(); // first call — fetches and caches
74
+ await getPreauthInfo(); // second call — should use cache
75
+
76
+ expect(fetchSpy).toHaveBeenCalledTimes(1);
77
+ });
78
+
79
+ it('returns null when fetch throws (covers catch block)', async () => {
80
+ jest.spyOn(embedConfigModule, 'getEmbedConfig').mockReturnValue({ thoughtSpotHost: 'https://ts' } as any);
81
+ jest.spyOn(authServiceModule, 'fetchPreauthInfoService').mockRejectedValue(new Error('network error'));
82
+
83
+ const result = await getPreauthInfo();
84
+ expect(result).toBeNull();
85
+ });
86
+ });
87
+
88
+ describe('getSessionInfo', () => {
89
+ it('fetches and returns session info', async () => {
90
+ const sessionInfoResp = {
91
+ userGUID: 'u1',
92
+ releaseVersion: '1.0',
93
+ configInfo: {
94
+ mixpanelConfig: { devSdkKey: 'dev', prodSdkKey: 'prod', production: false },
95
+ isPublicUser: false,
96
+ selfClusterId: 'c1',
97
+ selfClusterName: 'cluster1',
98
+ },
99
+ };
100
+ jest.spyOn(embedConfigModule, 'getEmbedConfig').mockReturnValue({ thoughtSpotHost: 'https://ts' } as any);
101
+ jest.spyOn(authServiceModule, 'fetchSessionInfoService').mockResolvedValue(sessionInfoResp as any);
102
+
103
+ const result = await getSessionInfo();
104
+ expect(result.userGUID).toBe('u1');
105
+ expect(result.mixpanelToken).toBe('dev');
106
+ });
107
+
108
+ it('returns cached session info on second call', async () => {
109
+ const sessionInfoResp = {
110
+ userGUID: 'u2',
111
+ releaseVersion: '2.0',
112
+ configInfo: {
113
+ mixpanelConfig: { devSdkKey: 'dev2', prodSdkKey: 'prod2', production: true },
114
+ isPublicUser: false,
115
+ selfClusterId: 'c2',
116
+ selfClusterName: 'cluster2',
117
+ },
118
+ };
119
+ jest.spyOn(embedConfigModule, 'getEmbedConfig').mockReturnValue({ thoughtSpotHost: 'https://ts' } as any);
120
+ const fetchSpy = jest.spyOn(authServiceModule, 'fetchSessionInfoService').mockResolvedValue(sessionInfoResp as any);
121
+
122
+ await getSessionInfo();
123
+ await getSessionInfo();
124
+ expect(fetchSpy).toHaveBeenCalledTimes(1);
125
+ });
126
+ });
127
+
128
+ describe('getCachedSessionInfo', () => {
129
+ it('returns null before any fetch', () => {
130
+ expect(getCachedSessionInfo()).toBeNull();
131
+ });
132
+ });
133
+
134
+ describe('getSessionDetails', () => {
135
+ it('uses prodSdkKey when production flag is true', () => {
136
+ const resp = {
137
+ userGUID: 'u3',
138
+ releaseVersion: '3.0',
139
+ configInfo: {
140
+ mixpanelConfig: { devSdkKey: 'dev', prodSdkKey: 'prod', production: true },
141
+ isPublicUser: true,
142
+ selfClusterId: 'c3',
143
+ selfClusterName: 'cluster3',
144
+ },
145
+ };
146
+ const result = getSessionDetails(resp);
147
+ expect(result.mixpanelToken).toBe('prod');
148
+ expect(result.isPublicUser).toBe(true);
149
+ });
150
+ });
151
+ });