@thoughtspot/visual-embed-sdk 1.10.0 → 1.10.3

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 (76) hide show
  1. package/dist/src/auth.d.ts +18 -5
  2. package/dist/src/embed/base.d.ts +36 -6
  3. package/dist/src/embed/pinboard.d.ts +91 -0
  4. package/dist/src/index.d.ts +3 -2
  5. package/dist/src/types.d.ts +24 -0
  6. package/dist/src/utils/authService.d.ts +1 -0
  7. package/dist/src/utils/plugin.d.ts +0 -0
  8. package/dist/src/utils/processData.d.ts +1 -1
  9. package/dist/src/utils.d.ts +1 -0
  10. package/dist/src/v1/api.d.ts +19 -0
  11. package/dist/tsembed.es.js +544 -35
  12. package/dist/tsembed.js +542 -34
  13. package/lib/package.json +2 -1
  14. package/lib/src/auth.d.ts +18 -5
  15. package/lib/src/auth.js +51 -12
  16. package/lib/src/auth.js.map +1 -1
  17. package/lib/src/auth.spec.js +69 -11
  18. package/lib/src/auth.spec.js.map +1 -1
  19. package/lib/src/embed/base.d.ts +36 -6
  20. package/lib/src/embed/base.js +79 -11
  21. package/lib/src/embed/base.js.map +1 -1
  22. package/lib/src/embed/base.spec.js +51 -3
  23. package/lib/src/embed/base.spec.js.map +1 -1
  24. package/lib/src/embed/embed.spec.js +1 -1
  25. package/lib/src/embed/embed.spec.js.map +1 -1
  26. package/lib/src/embed/pinboard.d.ts +91 -0
  27. package/lib/src/embed/pinboard.js +110 -0
  28. package/lib/src/embed/pinboard.js.map +1 -0
  29. package/lib/src/embed/ts-embed.js +9 -10
  30. package/lib/src/embed/ts-embed.js.map +1 -1
  31. package/lib/src/embed/ts-embed.spec.js +16 -6
  32. package/lib/src/embed/ts-embed.spec.js.map +1 -1
  33. package/lib/src/index.d.ts +3 -2
  34. package/lib/src/index.js +3 -2
  35. package/lib/src/index.js.map +1 -1
  36. package/lib/src/test/test-utils.js +1 -1
  37. package/lib/src/test/test-utils.js.map +1 -1
  38. package/lib/src/types.d.ts +24 -0
  39. package/lib/src/types.js +10 -0
  40. package/lib/src/types.js.map +1 -1
  41. package/lib/src/utils/authService.d.ts +1 -0
  42. package/lib/src/utils/authService.js +23 -3
  43. package/lib/src/utils/authService.js.map +1 -1
  44. package/lib/src/utils/authService.spec.js +21 -5
  45. package/lib/src/utils/authService.spec.js.map +1 -1
  46. package/lib/src/utils/plugin.d.ts +0 -0
  47. package/lib/src/utils/plugin.js +1 -0
  48. package/lib/src/utils/plugin.js.map +1 -0
  49. package/lib/src/utils/processData.d.ts +1 -1
  50. package/lib/src/utils/processData.js +37 -3
  51. package/lib/src/utils/processData.js.map +1 -1
  52. package/lib/src/utils/processData.spec.js +106 -4
  53. package/lib/src/utils/processData.spec.js.map +1 -1
  54. package/lib/src/utils.d.ts +1 -0
  55. package/lib/src/utils.js +4 -0
  56. package/lib/src/utils.js.map +1 -1
  57. package/lib/src/utils.spec.js +14 -1
  58. package/lib/src/utils.spec.js.map +1 -1
  59. package/lib/src/visual-embed-sdk.d.ts +124 -8
  60. package/package.json +2 -1
  61. package/src/auth.spec.ts +90 -11
  62. package/src/auth.ts +74 -16
  63. package/src/embed/base.spec.ts +58 -4
  64. package/src/embed/base.ts +98 -17
  65. package/src/embed/embed.spec.ts +1 -1
  66. package/src/embed/ts-embed.spec.ts +19 -9
  67. package/src/embed/ts-embed.ts +15 -12
  68. package/src/index.ts +5 -1
  69. package/src/test/test-utils.ts +1 -1
  70. package/src/types.ts +26 -0
  71. package/src/utils/authService.spec.ts +31 -5
  72. package/src/utils/authService.ts +29 -3
  73. package/src/utils/processData.spec.ts +139 -4
  74. package/src/utils/processData.ts +54 -4
  75. package/src/utils.spec.ts +26 -0
  76. package/src/utils.ts +5 -0
package/src/embed/base.ts CHANGED
@@ -7,31 +7,72 @@
7
7
  * @summary Base classes
8
8
  * @author Ayon Ghosh <ayon.ghosh@thoughtspot.com>
9
9
  */
10
+ import EventEmitter from 'eventemitter3';
10
11
  import { getThoughtSpotHost } from '../config';
11
- import { EmbedConfig } from '../types';
12
- import { authenticate } from '../auth';
12
+ import { AuthType, EmbedConfig } from '../types';
13
+ import {
14
+ authenticate,
15
+ logout as _logout,
16
+ AuthFailureType,
17
+ AuthStatus,
18
+ } from '../auth';
13
19
  import { uploadMixpanelEvent, MIXPANEL_EVENT } from '../mixpanel-service';
14
20
 
15
21
  let config = {} as EmbedConfig;
22
+ const CONFIG_DEFAULTS: Partial<EmbedConfig> = {
23
+ loginFailedMessage: 'Not logged in',
24
+ authType: AuthType.None,
25
+ };
26
+
27
+ export let authPromise: Promise<boolean>;
28
+
29
+ export const getEmbedConfig = (): EmbedConfig => config;
30
+
31
+ export const getAuthPromise = (): Promise<boolean> => authPromise;
32
+
33
+ let authEE: EventEmitter;
34
+
35
+ export function notifyAuthSuccess(): void {
36
+ if (!authEE) {
37
+ console.error('SDK not initialized');
38
+ return;
39
+ }
40
+ authEE.emit(AuthStatus.SUCCESS);
41
+ }
16
42
 
17
- export let authPromise: Promise<void>;
43
+ export function notifyAuthFailure(failureType: AuthFailureType): void {
44
+ if (!authEE) {
45
+ console.error('SDK not initialized');
46
+ return;
47
+ }
48
+ authEE.emit(AuthStatus.FAILURE, failureType);
49
+ }
18
50
 
51
+ export function notifyLogout(): void {
52
+ if (!authEE) {
53
+ console.error('SDK not initialized');
54
+ return;
55
+ }
56
+ authEE.emit(AuthStatus.LOGOUT);
57
+ }
19
58
  /**
20
59
  * Perform authentication on the ThoughtSpot app as applicable.
21
60
  */
22
- export const handleAuth = (): Promise<void> => {
23
- const authConfig = {
24
- ...config,
25
- thoughtSpotHost: getThoughtSpotHost(config),
26
- };
27
- authPromise = authenticate(authConfig);
61
+ export const handleAuth = (): Promise<boolean> => {
62
+ authPromise = authenticate(config);
63
+ authPromise.then(
64
+ (isLoggedIn) => {
65
+ if (!isLoggedIn) {
66
+ notifyAuthFailure(AuthFailureType.SDK);
67
+ }
68
+ },
69
+ () => {
70
+ notifyAuthFailure(AuthFailureType.SDK);
71
+ },
72
+ );
28
73
  return authPromise;
29
74
  };
30
75
 
31
- export const getEmbedConfig = (): EmbedConfig => config;
32
-
33
- export const getAuthPromise = (): Promise<void> => authPromise;
34
-
35
76
  /**
36
77
  * Prefetches static resources from the specified URL. Web browsers can then cache the prefetched resources and serve them from the user's local disk to provide faster access to your app.
37
78
  * @param url The URL provided for prefetch
@@ -57,10 +98,18 @@ export const prefetch = (url?: string): void => {
57
98
  * @param embedConfig The configuration object containing ThoughtSpot host,
58
99
  * authentication mechanism and so on.
59
100
  *
60
- * @returns authPromise Promise which resolves when authentication is complete.
101
+ * eg: authStatus = init(config);
102
+ * authStatus.on(AuthStatus.FAILURE, (reason) => { // do something here });
103
+ *
104
+ * @returns event emitter which emits events on authentication success, failure and logout. {@link AuthStatus}
61
105
  */
62
- export const init = (embedConfig: EmbedConfig): Promise<void> => {
63
- config = embedConfig;
106
+ export const init = (embedConfig: EmbedConfig): EventEmitter => {
107
+ config = {
108
+ ...CONFIG_DEFAULTS,
109
+ ...embedConfig,
110
+ thoughtSpotHost: getThoughtSpotHost(embedConfig),
111
+ };
112
+ authEE = new EventEmitter();
64
113
  handleAuth();
65
114
 
66
115
  uploadMixpanelEvent(MIXPANEL_EVENT.VISUAL_SDK_CALLED_INIT, {
@@ -71,7 +120,32 @@ export const init = (embedConfig: EmbedConfig): Promise<void> => {
71
120
  if (config.callPrefetch) {
72
121
  prefetch(config.thoughtSpotHost);
73
122
  }
74
- return authPromise;
123
+ return authEE;
124
+ };
125
+
126
+ export function disableAutoLogin(): void {
127
+ config.autoLogin = false;
128
+ }
129
+
130
+ /**
131
+ * Logout from ThoughtSpot. This also sets the autoLogin flag to false, to prevent
132
+ * the SDK from automatically logging in again.
133
+ *
134
+ * You can call the `init` method again to re login, if autoLogin is set to true in this
135
+ * second call it will be honored.
136
+ *
137
+ * @param doNotDisableAutoLogin This flag when passed will not disable autoLogin
138
+ * @returns Promise which resolves when logout completes.
139
+ * @version SDK: 1.10.1 | ThoughtSpot: *
140
+ */
141
+ export const logout = (doNotDisableAutoLogin = false): Promise<boolean> => {
142
+ if (!doNotDisableAutoLogin) {
143
+ disableAutoLogin();
144
+ }
145
+ return _logout(config).then((isLoggedIn) => {
146
+ notifyLogout();
147
+ return isLoggedIn;
148
+ });
75
149
  };
76
150
 
77
151
  let renderQueue: Promise<any> = Promise.resolve();
@@ -89,3 +163,10 @@ export const renderInQueue = (fn: (next?: (val?: any) => void) => void) => {
89
163
  fn(() => {}); // eslint-disable-line @typescript-eslint/no-empty-function
90
164
  }
91
165
  };
166
+
167
+ // For testing purposes only
168
+ export function reset(): void {
169
+ config = {} as any;
170
+ authEE = null;
171
+ authPromise = null;
172
+ }
@@ -71,7 +71,7 @@ describe('Custom CSS Url', () => {
71
71
  document.body.innerHTML = getDocumentBody();
72
72
  });
73
73
 
74
- test.only('passing customCssUrl should set the correct query params on the iframe', async (done) => {
74
+ test('passing customCssUrl should set the correct query params on the iframe', async (done) => {
75
75
  init({
76
76
  thoughtSpotHost,
77
77
  authType: AuthType.None,
@@ -165,15 +165,16 @@ describe('Unit test case for ts embed', () => {
165
165
  });
166
166
  });
167
167
 
168
- describe('when thoughtSpotHost have value and authPromise return success response', () => {
168
+ describe('when thoughtSpotHost have value and authPromise return response true/false', () => {
169
169
  beforeAll(() => {
170
170
  init({
171
171
  thoughtSpotHost,
172
172
  authType: AuthType.None,
173
+ loginFailedMessage: 'Failed to Login',
173
174
  });
174
175
  });
175
176
 
176
- beforeEach(() => {
177
+ const setup = async (isLoggedIn = false) => {
177
178
  jest.spyOn(window, 'addEventListener').mockImplementationOnce(
178
179
  (event, handler, options) => {
179
180
  handler({
@@ -186,10 +187,9 @@ describe('Unit test case for ts embed', () => {
186
187
  },
187
188
  );
188
189
  const iFrame: any = document.createElement('div');
189
- jest.spyOn(
190
- baseInstance,
191
- 'getAuthPromise',
192
- ).mockResolvedValueOnce(() => Promise.resolve());
190
+ jest.spyOn(baseInstance, 'getAuthPromise').mockResolvedValueOnce(
191
+ isLoggedIn,
192
+ );
193
193
  const tsEmbed = new SearchEmbed(getRootEl(), {});
194
194
  iFrame.contentWindow = null;
195
195
  tsEmbed.on(EmbedEvent.CustomAction, jest.fn());
@@ -199,10 +199,11 @@ describe('Unit test case for ts embed', () => {
199
199
  },
200
200
  );
201
201
  jest.spyOn(document, 'createElement').mockReturnValueOnce(iFrame);
202
- tsEmbed.render();
203
- });
202
+ await tsEmbed.render();
203
+ };
204
204
 
205
- test('mixpanel should call with VISUAL_SDK_RENDER_COMPLETE', () => {
205
+ test('mixpanel should call with VISUAL_SDK_RENDER_COMPLETE', async () => {
206
+ await setup(true);
206
207
  expect(mockMixPanelEvent).toBeCalledWith(
207
208
  MIXPANEL_EVENT.VISUAL_SDK_RENDER_START,
208
209
  );
@@ -212,11 +213,20 @@ describe('Unit test case for ts embed', () => {
212
213
  });
213
214
 
214
215
  test('Should remove prefetch iframe', async () => {
216
+ await setup(true);
215
217
  const prefetchIframe = document.querySelectorAll<HTMLIFrameElement>(
216
218
  '.prefetchIframe',
217
219
  );
218
220
  expect(prefetchIframe.length).toBe(0);
219
221
  });
222
+
223
+ test('Should render failure when login fails', async (done) => {
224
+ setup(false);
225
+ executeAfterWait(() => {
226
+ expect(getRootEl().innerHTML).toContain('Failed to Login');
227
+ done();
228
+ });
229
+ });
220
230
  });
221
231
 
222
232
  describe('when thoughtSpotHost have value and authPromise return error', () => {
@@ -34,7 +34,7 @@ import {
34
34
  MessageCallbackObj,
35
35
  } from '../types';
36
36
  import { uploadMixpanelEvent, MIXPANEL_EVENT } from '../mixpanel-service';
37
- import { getProcessData } from '../utils/processData';
37
+ import { processEventData } from '../utils/processData';
38
38
  import { processTrigger } from '../utils/processTrigger';
39
39
  import pkgInfo from '../../package.json';
40
40
  import { getAuthPromise, getEmbedConfig, renderInQueue } from './base';
@@ -212,14 +212,6 @@ export class TsEmbed {
212
212
  this.isError = false;
213
213
  this.viewConfig = viewConfig;
214
214
  this.shouldEncodeUrlQueryParams = this.embedConfig.shouldEncodeUrlQueryParams;
215
- if (!this.embedConfig.suppressNoCookieAccessAlert) {
216
- this.on(EmbedEvent.NoCookieAccess, () => {
217
- // eslint-disable-next-line no-alert
218
- alert(
219
- 'Third party cookie access is blocked on this browser, please allow third party cookies for ThoughtSpot to work properly',
220
- );
221
- });
222
- }
223
215
  }
224
216
 
225
217
  /**
@@ -304,7 +296,12 @@ export class TsEmbed {
304
296
  if (event.source === this.iFrame.contentWindow) {
305
297
  this.executeCallbacks(
306
298
  eventType,
307
- getProcessData(eventType, eventData, this.thoughtSpotHost),
299
+ processEventData(
300
+ eventType,
301
+ eventData,
302
+ this.thoughtSpotHost,
303
+ this.el,
304
+ ),
308
305
  eventPort,
309
306
  );
310
307
  }
@@ -464,7 +461,12 @@ export class TsEmbed {
464
461
  uploadMixpanelEvent(MIXPANEL_EVENT.VISUAL_SDK_RENDER_START);
465
462
 
466
463
  getAuthPromise()
467
- ?.then(() => {
464
+ ?.then((isLoggedIn: boolean) => {
465
+ if (!isLoggedIn) {
466
+ this.el.innerHTML = this.embedConfig.loginFailedMessage;
467
+ return;
468
+ }
469
+
468
470
  uploadMixpanelEvent(
469
471
  MIXPANEL_EVENT.VISUAL_SDK_RENDER_COMPLETE,
470
472
  );
@@ -492,7 +494,7 @@ export class TsEmbed {
492
494
  frameWidth || DEFAULT_EMBED_WIDTH,
493
495
  );
494
496
  const height = getCssDimension(
495
- frameWidth || DEFAULT_EMBED_HEIGHT,
497
+ frameHeight || DEFAULT_EMBED_HEIGHT,
496
498
  );
497
499
  setAttributes(this.iFrame, restParams);
498
500
 
@@ -536,6 +538,7 @@ export class TsEmbed {
536
538
  uploadMixpanelEvent(
537
539
  MIXPANEL_EVENT.VISUAL_SDK_RENDER_FAILED,
538
540
  );
541
+ this.el.innerHTML = this.embedConfig.loginFailedMessage;
539
542
  this.handleError(error);
540
543
  });
541
544
  });
package/src/index.ts CHANGED
@@ -9,13 +9,14 @@
9
9
  */
10
10
 
11
11
  import { AppEmbed, Page, AppViewConfig } from './embed/app';
12
- import { init, prefetch } from './embed/base';
12
+ import { init, prefetch, logout } from './embed/base';
13
13
  import {
14
14
  PinboardEmbed,
15
15
  LiveboardViewConfig,
16
16
  LiveboardEmbed,
17
17
  } from './embed/liveboard';
18
18
  import { SearchEmbed, SearchViewConfig } from './embed/search';
19
+ import { AuthFailureType, AuthStatus } from './auth';
19
20
  import {
20
21
  AuthType,
21
22
  RuntimeFilter,
@@ -29,11 +30,14 @@ import {
29
30
 
30
31
  export {
31
32
  init,
33
+ logout,
32
34
  prefetch,
33
35
  SearchEmbed,
34
36
  PinboardEmbed,
35
37
  LiveboardEmbed,
36
38
  AppEmbed,
39
+ AuthFailureType,
40
+ AuthStatus,
37
41
  // types
38
42
  Page,
39
43
  AuthType,
@@ -14,7 +14,7 @@ type DOMElement = HTMLElement | Document;
14
14
 
15
15
  export const getRootEl = () => document.getElementById('embed');
16
16
 
17
- export const getRootEl2 = () => document.getElementById('emebed-2');
17
+ export const getRootEl2 = () => document.getElementById('embed-2');
18
18
 
19
19
  export const getIFrameEl = (container: DOMElement = document) =>
20
20
  container.querySelector('iframe');
package/src/types.ts CHANGED
@@ -97,6 +97,16 @@ export interface EmbedConfig {
97
97
  */
98
98
  noRedirect?: boolean;
99
99
 
100
+ /**
101
+ * [SSO] For SSO Authentication, one can supply an optional path param,
102
+ * this will be the path on the host origin where the SAML flow will be
103
+ * terminated.
104
+ *
105
+ * Eg: "/dashboard", "#/foo" [Do not include the host]
106
+ * @version SDK: 1.10.2 | ThoughtSpot: *
107
+ */
108
+ redirectPath?: string;
109
+
100
110
  /** @internal */
101
111
  basepath?: string;
102
112
 
@@ -131,6 +141,12 @@ export interface EmbedConfig {
131
141
  */
132
142
  disableLoginRedirect?: boolean;
133
143
 
144
+ /**
145
+ * This message is displayed on the embed view when the login fails.
146
+ * @version 1.10.1 | ThoughtSpot: *
147
+ */
148
+ loginFailedMessage?: string;
149
+
134
150
  /**
135
151
  * Calls the prefetch method internally when set to true
136
152
  * @default false
@@ -365,6 +381,16 @@ export enum EmbedEvent {
365
381
  * The ThoughtSpot auth session has expired.
366
382
  */
367
383
  AuthExpire = 'ThoughtspotAuthExpired',
384
+ /**
385
+ * ThoughtSpot failed to validate the auth session.
386
+ * @hidden
387
+ */
388
+ AuthFailure = 'ThoughtspotAuthFailure',
389
+ /**
390
+ * ThoughtSpot failed to validate the auth session.
391
+ * @hidden
392
+ */
393
+ AuthLogout = 'ThoughtspotAuthLogout',
368
394
  /**
369
395
  * The height of the embedded Liveboard or visualization has been computed.
370
396
  * @return data - The height of the embedded Liveboard or visualization
@@ -4,6 +4,7 @@ import {
4
4
  fetchAuthService,
5
5
  fetchBasicAuthService,
6
6
  } from './authService';
7
+ import { EndPoints } from '../auth';
7
8
 
8
9
  const thoughtSpotHost = 'http://10.79.135.124:3000';
9
10
 
@@ -21,6 +22,7 @@ describe('Unit test for authService', () => {
21
22
  Promise.resolve({
22
23
  json: () => ({ success: true }),
23
24
  status: 200,
25
+ ok: true,
24
26
  }),
25
27
  );
26
28
  const response = await fetchSessionInfoService(authVerificationUrl);
@@ -32,6 +34,7 @@ describe('Unit test for authService', () => {
32
34
  global.fetch = jest.fn(() =>
33
35
  Promise.resolve({
34
36
  text: () => ({ success: true }),
37
+ ok: true,
35
38
  }),
36
39
  );
37
40
  const response = await fetchAuthTokenService(authEndpoint);
@@ -40,14 +43,37 @@ describe('Unit test for authService', () => {
40
43
  });
41
44
 
42
45
  test('fetchAuthService', async () => {
43
- global.fetch = jest.fn(() => Promise.resolve({ success: true }));
44
- await fetchAuthService(authVerificationUrl, username, authToken);
45
- expect(fetch).toBeCalled();
46
+ global.fetch = jest.fn(() =>
47
+ Promise.resolve({ success: true, ok: true }),
48
+ );
49
+ await fetchAuthService(thoughtSpotHost, username, authToken);
50
+ expect(fetch).toBeCalledWith(
51
+ `${thoughtSpotHost}${EndPoints.TOKEN_LOGIN}?username=${username}&auth_token=${authToken}`,
52
+ {
53
+ credentials: 'include',
54
+ redirect: 'manual',
55
+ },
56
+ );
46
57
  });
47
58
 
48
- test('fetchBasicAuthService', async () => {
49
- global.fetch = jest.fn(() => Promise.resolve({ success: true }));
59
+ test('fetchBasicAuthService called with manual redirect', async () => {
60
+ global.fetch = jest.fn(() =>
61
+ Promise.resolve({ success: true, ok: true }),
62
+ );
50
63
  await fetchBasicAuthService(thoughtSpotHost, username, password);
51
64
  expect(fetch).toBeCalled();
52
65
  });
66
+
67
+ test('log error on API failures', async () => {
68
+ jest.spyOn(global.console, 'error').mockImplementation(() => undefined);
69
+ global.fetch = jest.fn(() =>
70
+ Promise.resolve({
71
+ text: () => Promise.resolve('error'),
72
+ status: 500,
73
+ ok: false,
74
+ }),
75
+ );
76
+ await fetchSessionInfoService(authVerificationUrl);
77
+ expect(global.console.error).toHaveBeenCalledWith('Failure', 'error');
78
+ });
53
79
  });
@@ -1,10 +1,22 @@
1
1
  // eslint-disable-next-line import/no-cycle
2
2
  import { EndPoints } from '../auth';
3
3
 
4
+ function failureLoggedFetch(
5
+ url: string,
6
+ options: RequestInit = {},
7
+ ): Promise<Response> {
8
+ return fetch(url, options).then(async (r) => {
9
+ if (!r.ok && r.type !== 'opaqueredirect' && r.type !== 'opaque') {
10
+ console.error('Failure', await r.text?.());
11
+ }
12
+ return r;
13
+ });
14
+ }
15
+
4
16
  export function fetchSessionInfoService(
5
17
  authVerificationUrl: string,
6
18
  ): Promise<any> {
7
- return fetch(authVerificationUrl, {
19
+ return failureLoggedFetch(authVerificationUrl, {
8
20
  credentials: 'include',
9
21
  });
10
22
  }
@@ -20,10 +32,12 @@ export async function fetchAuthService(
20
32
  username: string,
21
33
  authToken: string,
22
34
  ): Promise<any> {
23
- return fetch(
35
+ return failureLoggedFetch(
24
36
  `${thoughtSpotHost}${EndPoints.TOKEN_LOGIN}?username=${username}&auth_token=${authToken}`,
25
37
  {
26
38
  credentials: 'include',
39
+ // We do not want to follow the redirect, as it starts giving a CORS error
40
+ redirect: 'manual',
27
41
  },
28
42
  );
29
43
  }
@@ -33,7 +47,7 @@ export async function fetchBasicAuthService(
33
47
  username: string,
34
48
  password: string,
35
49
  ): Promise<any> {
36
- return fetch(`${thoughtSpotHost}${EndPoints.BASIC_LOGIN}`, {
50
+ return failureLoggedFetch(`${thoughtSpotHost}${EndPoints.BASIC_LOGIN}`, {
37
51
  method: 'POST',
38
52
  headers: {
39
53
  'content-type': 'application/x-www-form-urlencoded',
@@ -45,3 +59,15 @@ export async function fetchBasicAuthService(
45
59
  credentials: 'include',
46
60
  });
47
61
  }
62
+
63
+ export async function fetchLogoutService(
64
+ thoughtSpotHost: string,
65
+ ): Promise<any> {
66
+ return failureLoggedFetch(`${thoughtSpotHost}${EndPoints.LOGOUT}`, {
67
+ credentials: 'include',
68
+ method: 'POST',
69
+ headers: {
70
+ 'x-requested-by': 'ThoughtSpot',
71
+ },
72
+ });
73
+ }