Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/embed/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -761,7 +761,8 @@ export class AppEmbed extends V1Embed {
* to be embedded.
*/
public async render(): Promise<AppEmbed> {
super.render();
await super.render();

const src = this.getIFrameSrc();
await this.renderV1Embed(src);

Expand Down
42 changes: 41 additions & 1 deletion src/embed/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ import {
AuthEventEmitter,
postLoginService,
} from '../auth';
import '../utils/with-resolvers-polyfill';
import { uploadMixpanelEvent, MIXPANEL_EVENT } from '../mixpanel-service';
import { getEmbedConfig, setEmbedConfig } from './embedConfig';
import { getQueryParamString } from '../utils';
import { getQueryParamString, getValueFromWindow, storeValueInWindow } from '../utils';
import { resetAllCachedServices } from '../utils/resetServices';

const CONFIG_DEFAULTS: Partial<EmbedConfig> = {
Expand Down Expand Up @@ -171,6 +172,40 @@ function backwardCompat(embedConfig: EmbedConfig): EmbedConfig {
return newConfig;
}

type InitFlagStore = {
initPromise: Promise<ReturnType<typeof init>>;
isInitCalled: boolean;
initPromiseResolve: (value: ReturnType<typeof init>) => void;
}
const initFlagKey = 'initFlagKey';

export const createAndSetInitPromise = (): void => {
const {
promise: initPromise,
resolve: initPromiseResolve,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
} = Promise.withResolvers<AuthEventEmitter>();
const initFlagStore: InitFlagStore = {
initPromise,
isInitCalled: false,
initPromiseResolve,
};
storeValueInWindow(initFlagKey, initFlagStore, {
// In case of diff imports the promise might be already set
ignoreIfAlreadyExists: true,
});
};

createAndSetInitPromise();

export const getInitPromise = ():
Promise<
ReturnType<typeof init>
> => getValueFromWindow<InitFlagStore>(initFlagKey)?.initPromise;

export const getIsInitCalled = (): boolean => !!getValueFromWindow(initFlagKey)?.isInitCalled;

/**
* Initializes the Visual Embed SDK globally and perform
* authentication if applicable. This function needs to be called before any ThoughtSpot
Expand Down Expand Up @@ -223,6 +258,11 @@ export const init = (embedConfig: EmbedConfig): AuthEventEmitter => {
if (getEmbedConfig().callPrefetch) {
prefetch(getEmbedConfig().thoughtSpotHost);
}

// Resolves the promise created in the initPromiseKey
getValueFromWindow<InitFlagStore>(initFlagKey).initPromiseResolve(authEE);
getValueFromWindow<InitFlagStore>(initFlagKey).isInitCalled = true;

return authEE as AuthEventEmitter;
};

Expand Down
3 changes: 2 additions & 1 deletion src/embed/bodyless-conversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ class ConversationMessage extends TsEmbed {
}

public async render(): Promise<ConversationMessage> {
super.render();
await super.render();

const src = this.getIframeSrc();
await this.renderIFrame(src);
return this;
Expand Down
3 changes: 2 additions & 1 deletion src/embed/conversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,8 @@ export class ConversationEmbed extends TsEmbed {
}

public async render(): Promise<ConversationEmbed> {
super.render();
await super.render();

const src = this.getIframeSrc();
await this.renderIFrame(src);
return this;
Expand Down
2 changes: 1 addition & 1 deletion src/embed/embedConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const configKey = 'embedConfig';
* @version SDK: 1.19.0 | ThoughtSpot: *
* @group Global methods
*/
export const getEmbedConfig = (): EmbedConfig => getValueFromWindow(configKey) || {};
export const getEmbedConfig = (): EmbedConfig => getValueFromWindow(configKey) || ({} as any);

/**
* Sets the configuration embed was initialized with.
Expand Down
22 changes: 11 additions & 11 deletions src/embed/liveboard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -553,13 +553,13 @@ describe('Liveboard/viz embed tests', () => {
});
});

test('navigateToLiveboard should trigger the navigate event with the correct path', (done) => {
test('navigateToLiveboard should trigger the navigate event with the correct path', async (done) => {
mockMessageChannel();
const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
...defaultViewConfig,
} as LiveboardViewConfig);
const onSpy = jest.spyOn(liveboardEmbed, 'trigger');
liveboardEmbed.prerenderGeneric();
await liveboardEmbed.prerenderGeneric();
executeAfterWait(() => {
const iframe = getIFrameEl();
postMessageToParent(iframe.contentWindow, {
Expand All @@ -573,14 +573,14 @@ describe('Liveboard/viz embed tests', () => {
});
});

test('navigateToLiveboard with preRender', (done) => {
test('navigateToLiveboard with preRender', async (done) => {
mockMessageChannel();
const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
...defaultViewConfig,
preRenderId: 'test',
} as LiveboardViewConfig);
const onSpy = jest.spyOn(liveboardEmbed, 'trigger');
liveboardEmbed.prerenderGeneric();
await liveboardEmbed.prerenderGeneric();
executeAfterWait(() => {
const iframe = getIFrameEl();
postMessageToParent(iframe.contentWindow, {
Expand All @@ -604,7 +604,7 @@ describe('Liveboard/viz embed tests', () => {
},
],
} as LiveboardViewConfig);
liveboardEmbed.render();
await liveboardEmbed.render();
await executeAfterWait(() => {
expectUrlMatchesWithParams(
getIFrameSrc(),
Expand Down Expand Up @@ -637,7 +637,7 @@ describe('Liveboard/viz embed tests', () => {
...defaultViewConfig,
liveboardId,
} as LiveboardViewConfig);
liveboardEmbed.render();
await liveboardEmbed.render();
await executeAfterWait(() => {
const result = liveboardEmbed.trigger(HostEvent.SetActiveTab, {
tabId: newActiveTabId,
Expand All @@ -650,13 +650,13 @@ describe('Liveboard/viz embed tests', () => {
});

describe('PreRender flow for liveboard embed', () => {
test('it should preRender generic with liveboard id is not passed', (done) => {
test('it should preRender generic with liveboard id is not passed', async (done) => {
const consoleSpy = jest.spyOn(console, 'error');
const libEmbed = new LiveboardEmbed(getRootEl(), {
preRenderId: 'testPreRender',
});
const prerenderGenericSpy = jest.spyOn(libEmbed, 'prerenderGeneric');
libEmbed.preRender();
await libEmbed.preRender();
executeAfterWait(() => {
const iFrame = document.getElementById(
libEmbed.getPreRenderIds().child,
Expand Down Expand Up @@ -735,7 +735,7 @@ describe('Liveboard/viz embed tests', () => {
};
});

libEmbed.preRender();
await libEmbed.preRender();

await waitFor(() => !!getIFrameEl());

Expand All @@ -750,7 +750,7 @@ describe('Liveboard/viz embed tests', () => {
liveboardId: testLiveboardId,
});
const navigateToLiveboardSpy = jest.spyOn(newLibEmbed, 'navigateToLiveboard');
newLibEmbed.showPreRender();
await newLibEmbed.showPreRender();

executeAfterWait(() => {
const iFrame = document.getElementById(
Expand All @@ -776,7 +776,7 @@ describe('Liveboard/viz embed tests', () => {
...defaultViewConfig,
vizId: 'testViz',
});
liveboardEmbed.render();
await liveboardEmbed.render();
mockProcessTrigger.mockResolvedValue({ session: 'test' });
await executeAfterWait(async () => {
await liveboardEmbed.trigger(
Expand Down
11 changes: 5 additions & 6 deletions src/embed/liveboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
} from '../types';
import { getQueryParamString, isUndefined } from '../utils';
import { getAuthPromise } from './base';
import { V1Embed } from './ts-embed';
import { TsEmbed, V1Embed } from './ts-embed';
import { addPreviewStylesIfNotPresent } from '../utils/global-styles';
import { TriggerPayload, TriggerResponse } from './hostEventClient/contracts';

Expand Down Expand Up @@ -635,12 +635,11 @@ export class LiveboardEmbed extends V1Embed {
}
}

protected handleRenderForPrerender(): void {
protected async handleRenderForPrerender(): Promise<TsEmbed> {
if (isUndefined(this.viewConfig.liveboardId)) {
this.prerenderGeneric();
return;
return this.prerenderGeneric();
}
super.handleRenderForPrerender();
return super.handleRenderForPrerender();
}

/**
Expand Down Expand Up @@ -670,7 +669,7 @@ export class LiveboardEmbed extends V1Embed {
* visualization ID and the runtime filters.
*/
public async render(): Promise<LiveboardEmbed> {
super.render();
await super.render();

const src = this.getIFrameSrc();
await this.renderV1Embed(src);
Expand Down
3 changes: 1 addition & 2 deletions src/embed/sage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,7 @@ export class SageEmbed extends V1Embed {
* @returns {SageEmbed} Eureka/Sage embed
*/
public async render(): Promise<SageEmbed> {
super.render();

await super.render();
const src = this.getIFrameSrc();
await this.renderV1Embed(src);

Expand Down
3 changes: 1 addition & 2 deletions src/embed/search-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,7 @@ export class SearchBarEmbed extends TsEmbed {
* Render the embedded ThoughtSpot search
*/
public async render(): Promise<SearchBarEmbed> {
super.render();

await super.render();
const src = this.getIFrameSrc();
await this.renderIFrame(src);
return this;
Expand Down
3 changes: 1 addition & 2 deletions src/embed/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -482,9 +482,8 @@ export class SearchEmbed extends TsEmbed {
* Render the embedded ThoughtSpot search
*/
public async render(): Promise<SearchEmbed> {
super.render();
await super.render();
const { answerId } = this.viewConfig;

const src = this.getIFrameSrc();
await this.renderIFrame(src);
getAuthPromise().then(() => {
Expand Down
88 changes: 83 additions & 5 deletions src/embed/ts-embed.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/* eslint-disable dot-notation */
import { resetValueFromWindow } from '../utils';
import { ERROR_MESSAGE } from '../errors';
import { resetCachedAuthToken } from '../authToken';
import {
AuthType,
Expand Down Expand Up @@ -1249,14 +1251,14 @@ describe('Unit test case for ts embed', () => {
});
});

beforeEach(() => {
beforeEach(async () => {
jest.spyOn(baseInstance, 'getAuthPromise').mockRejectedValueOnce(false);
const tsEmbed = new SearchEmbed(getRootEl(), {});
const iFrame: any = document.createElement('div');
iFrame.contentWindow = null;
jest.spyOn(document, 'createElement').mockReturnValueOnce(iFrame);
spyOn(logger, 'error');
tsEmbed.render();
await tsEmbed.render();
});

test('mixpanel should call with VISUAL_SDK_RENDER_FAILED', () => {
Expand Down Expand Up @@ -1433,7 +1435,7 @@ describe('Unit test case for ts embed', () => {
test('Error should be true', async () => {
spyOn(logger, 'error');
const tsEmbed = new SearchEmbed(getRootEl(), {});
tsEmbed.render();
await tsEmbed.render();
expect(tsEmbed['isError']).toBe(true);
expect(logger.error).toHaveBeenCalledWith(
'You need to init the ThoughtSpot SDK module first',
Expand All @@ -1446,11 +1448,11 @@ describe('Unit test case for ts embed', () => {
jest.spyOn(config, 'getThoughtSpotHost').mockImplementation(() => 'http://tshost');
});

test('when isRendered is true than isError will be true', () => {
test('when isRendered is true than isError will be true', async () => {
spyOn(logger, 'warn');
const viEmbedIns = new tsEmbedInstance.V1Embed(getRootEl(), defaultViewConfig);
expect(viEmbedIns['isError']).toBe(false);
viEmbedIns.render();
await viEmbedIns.render();
viEmbedIns.on(EmbedEvent.CustomAction, jest.fn()).render();
expect(logger.warn).toHaveBeenCalledWith(
'Please register event handlers before calling render',
Expand Down Expand Up @@ -2382,4 +2384,80 @@ describe('Unit test case for ts embed', () => {
jest.spyOn(baseInstance, 'notifyAuthFailure').mockClear();
});
});

describe('Renders should wait for init to completed', () => {
const errorSpy = jest.spyOn(logger, 'error').mockResolvedValue(true);
beforeEach(() => {
errorSpy.mockClear();
resetValueFromWindow('initFlagKey');
baseInstance.createAndSetInitPromise();
document.body.innerHTML = getDocumentBody();
});
test('Pre-render should wait for init to complete', async () => {
const lib = new LiveboardEmbed(getRootEl(), { preRenderId: 'test', liveboardId: 'test' });
lib.preRender();
await executeAfterWait(() => {
expect(errorSpy).toHaveBeenCalledWith(ERROR_MESSAGE.RENDER_CALLED_BEFORE_INIT);
expect(getRootEl().innerHTML).toContain('');
});

const iframeBeforeInit = getIFrameEl();
expect(iframeBeforeInit).toBe(null);

init({
thoughtSpotHost: 'tshost',
authType: AuthType.None,
});

await waitFor(() => !!getIFrameEl());
const preRenderId = lib.getPreRenderIds().wrapper;
expect(document.getElementById(preRenderId)).not.toBe(null);
const iframeAfterInit = getIFrameEl();
expect(iframeAfterInit).not.toBe(null);
});

test('Render should wait for init to complete', async () => {
const lib = new LiveboardEmbed(getRootEl(), { liveboardId: 'test' });
lib.render();
await executeAfterWait(() => {
expect(errorSpy).toHaveBeenCalledWith(ERROR_MESSAGE.RENDER_CALLED_BEFORE_INIT);
expect(getRootEl().innerHTML).toContain('');
});

const iframeBeforeInit = getIFrameEl();
expect(iframeBeforeInit).toBe(null);

init({
thoughtSpotHost: 'tshost',
authType: AuthType.None,
});

await waitFor(() => !!getIFrameEl());
expect(getRootEl()).not.toBe(null);
const iframeAfterInit = getIFrameEl();
expect(iframeAfterInit).not.toBe(null);
});

test('Pre Render Generic should wait for init to complete', async () => {
const lib = new LiveboardEmbed(getRootEl(), {});
lib.prerenderGeneric();
await executeAfterWait(() => {
expect(errorSpy).toHaveBeenCalledWith(ERROR_MESSAGE.RENDER_CALLED_BEFORE_INIT);
expect(getRootEl().innerHTML).toContain('');
});

const iframeBeforeInit = getIFrameEl();
expect(iframeBeforeInit).toBe(null);

init({
thoughtSpotHost: 'tshost',
authType: AuthType.None,
});

await waitFor(() => !!getIFrameEl());
expect(getRootEl()).not.toBe(null);
const iframeAfterInit = getIFrameEl();
expect(iframeAfterInit).not.toBe(null);
});
});
});
Loading
Loading