Skip to content

Commit 7294d9d

Browse files
committed
SCAL-234606 Add forced token generation flags in embed sdk (#68)
1 parent f054a30 commit 7294d9d

File tree

8 files changed

+294
-4
lines changed

8 files changed

+294
-4
lines changed

src/embed/liveboard.spec.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,23 @@ describe('Liveboard/viz embed tests', () => {
172172
});
173173
});
174174

175+
test('should enable viz oAuthPollingInterval true', async () => {
176+
const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
177+
oAuthPollingInterval: 1000,
178+
isForceRedirect: true,
179+
dataSourceId: '12356',
180+
...defaultViewConfig,
181+
liveboardId,
182+
} as LiveboardViewConfig);
183+
liveboardEmbed.render();
184+
await executeAfterWait(() => {
185+
expectUrlMatchesWithParams(
186+
getIFrameSrc(),
187+
`http://${thoughtSpotHost}/?embedApp=true${defaultParams}&oAuthPollingInterval=1000&isForceRedirect=true&dataSourceId=12356${prefixParams}#/embed/viz/${liveboardId}`,
188+
);
189+
});
190+
});
191+
175192
test('should disable viz transformations when enableVizTransformations false', async () => {
176193
const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
177194
enableVizTransformations: false,

src/embed/liveboard.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,45 @@ export interface LiveboardViewConfig
338338
* ```
339339
*/
340340
hideIrrelevantChipsInLiveboardTabs?: boolean;
341+
342+
/**
343+
* The Liveboard to run on regular intervals to fetch the cdw token.
344+
* @hidden
345+
* @version SDK: 1.35.0 | ThoughtSpot:10.6.0.cl
346+
* @example
347+
* ```js
348+
* const embed = new LiveboardEmbed('#embed-container', {
349+
* ... // other options
350+
* oAuthPollingInterval: value in milliseconds,
351+
* })
352+
*/
353+
oAuthPollingInterval?: number;
354+
355+
/**
356+
* The Liveboard is set to force a token fetch during the initial load.
357+
* @hidden
358+
* @version SDK: 1.35.0 | ThoughtSpot:10.6.0.cl
359+
* @example
360+
* ```js
361+
* const embed = new LiveboardEmbed('#embed-container', {
362+
* ... // other options
363+
* isForceRedirect: false,
364+
* })
365+
*/
366+
isForceRedirect?: boolean;
367+
368+
/**
369+
* The source connection ID for authentication.
370+
* @hidden
371+
* @version SDK: 1.35.0 | ThoughtSpot:10.6.0.cl
372+
* @example
373+
* ```js
374+
* const embed = new LiveboardEmbed('#embed-container', {
375+
* ... // other options
376+
* dataSourceId: '',
377+
* })
378+
*/
379+
dataSourceId?: string;
341380
}
342381

343382
/**
@@ -399,6 +438,9 @@ export class LiveboardEmbed extends V1Embed {
399438
enable2ColumnLayout,
400439
dataPanelV2 = false,
401440
enableCustomColumnGroups = false,
441+
oAuthPollingInterval,
442+
isForceRedirect,
443+
dataSourceId,
402444
} = this.viewConfig;
403445

404446
const preventLiveboardFilterRemoval = this.viewConfig.preventLiveboardFilterRemoval
@@ -445,6 +487,18 @@ export class LiveboardEmbed extends V1Embed {
445487
params[Param.enableAskSage] = enableAskSage;
446488
}
447489

490+
if (oAuthPollingInterval !== undefined) {
491+
params[Param.OauthPollingInterval] = oAuthPollingInterval;
492+
}
493+
494+
if (isForceRedirect) {
495+
params[Param.IsForceRedirect] = isForceRedirect;
496+
}
497+
498+
if (dataSourceId !== undefined) {
499+
params[Param.DataSourceId] = dataSourceId;
500+
}
501+
448502
params[Param.LiveboardHeaderSticky] = isLiveboardHeaderSticky;
449503
params[Param.LiveboardHeaderV2] = isLiveboardCompactHeaderEnabled;
450504
params[Param.ShowLiveboardVerifiedBadge] = showLiveboardVerifiedBadge;

src/embed/ts-embed.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import isEqual from 'lodash/isEqual';
1010
import isEmpty from 'lodash/isEmpty';
1111
import isObject from 'lodash/isObject';
12+
import { FlattenType, HostEventRequest, HostEventResponse } from '../utils/embedApi/contracts';
1213
import { logger } from '../utils/logger';
1314
import { getAuthenticationToken } from '../authToken';
1415
import { AnswerService } from '../utils/graphql/answerService/answerService';
@@ -62,6 +63,7 @@ import {
6263
import { AuthFailureType } from '../auth';
6364
import { getEmbedConfig } from './embedConfig';
6465
import { ERROR_MESSAGE } from '../errors';
66+
import { HostEventClient } from '../utils/embedApi/embedApiClient';
6567

6668
const { version } = pkgInfo;
6769

@@ -273,6 +275,7 @@ export class TsEmbed {
273275
const onlineEventListener = (e: Event) => {
274276
this.trigger(HostEvent.Reload);
275277
};
278+
276279
window.addEventListener('online', onlineEventListener);
277280

278281
const offlineEventListener = (e: Event) => {
@@ -983,7 +986,11 @@ export class TsEmbed {
983986
* @param messageType The event type
984987
* @param data The payload to send with the message
985988
*/
986-
public trigger(messageType: HostEvent, data: any = {}): Promise<any> {
989+
public trigger<HostEventT extends HostEvent>(
990+
messageType: HostEventT,
991+
data?: HostEventRequest<HostEventT>,
992+
):
993+
Promise<HostEventResponse<HostEventT>> {
987994
uploadMixpanelEvent(`${MIXPANEL_EVENT.VISUAL_SDK_TRIGGER}-${messageType}`);
988995

989996
if (!this.isRendered) {
@@ -995,7 +1002,9 @@ export class TsEmbed {
9951002
this.handleError('Host event type is undefined');
9961003
return null;
9971004
}
998-
return processTrigger(this.iFrame, messageType, this.thoughtSpotHost, data);
1005+
1006+
const hostEventClient = new HostEventClient(this.iFrame, this.thoughtSpotHost);
1007+
return hostEventClient.executeHostEvent(messageType, data);
9991008
}
10001009

10011010
/**

src/react/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { AppEmbed as _AppEmbed, AppViewConfig } from '../embed/app';
88
import { LiveboardEmbed as _LiveboardEmbed, LiveboardViewConfig } from '../embed/liveboard';
99
import { TsEmbed } from '../embed/ts-embed';
1010

11-
import { EmbedEvent, ViewConfig } from '../types';
11+
import { EmbedEvent, HostEvent, ViewConfig } from '../types';
1212
import { EmbedProps, getViewPropsAndListeners } from './util';
1313
import { ConversationEmbed as _ConversationEmbed, ConversationViewConfig } from '../embed/conversation';
1414

src/types.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3194,7 +3194,21 @@ export enum HostEvent {
31943194
* @version SDK: 1.29.0 | Thoughtspot: 10.1.0.cl
31953195
*/
31963196
GetParameters = 'GetParameters',
3197-
}
3197+
/**
3198+
* Triggers update of persoanlised view for a liveboard
3199+
* ```js
3200+
* liveboardEmbed.trigger(HostEvent.UpdatePersonalisedView, {viewId: '1234'})
3201+
* ```
3202+
* @version SDK: 1.36.0 | Thoughtspot: 10.6.0.cl
3203+
*/
3204+
UpdatePersonalisedView = 'UpdatePersonalisedView',
3205+
/**
3206+
* EmbedApi
3207+
*
3208+
* @hidden
3209+
*/
3210+
UiPassthrough = 'UiPassthrough',
3211+
}
31983212

31993213
/**
32003214
* The different visual modes that the data sources panel within
@@ -3316,6 +3330,9 @@ export enum Param {
33163330
IsUnifiedSearchExperienceEnabled = 'isUnifiedSearchExperienceEnabled',
33173331
OverrideOrgId = 'orgId',
33183332
EnableFlipTooltipToContextMenu = 'flipTooltipToContextMenuEnabled',
3333+
OauthPollingInterval = 'oAuthPollingInterval',
3334+
IsForceRedirect = 'isForceRedirect',
3335+
DataSourceId = 'dataSourceId',
33193336
}
33203337

33213338
/**

src/utils/embedApi/contracts.ts

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { HostEvent } from '../../types';
2+
3+
export enum EmbedApiEvent {
4+
addVizToPinboard = 'addVizToPinboard',
5+
saveAnswer = 'saveAnswer',
6+
getA3AnalysisColumns = 'getA3AnalysisColumns',
7+
getPinboardTabInfo = 'getPinboardTabInfo',
8+
getDiscoverabilityStatus = 'getDiscoverabilityStatus',
9+
getAvailableEmbedApis = 'getAvailableEmbedApis',
10+
getAnswerPageConfig = 'getAnswerPageConfig',
11+
getPinboardPageConfig = 'getPinboardPageConfig',
12+
embedApiEventNotFound = 'embedApiEventNotFound',
13+
}
14+
15+
export type EmbedApiContractBase = {
16+
[EmbedApiEvent.addVizToPinboard]: {
17+
request: {
18+
vizId?: string;
19+
newVizName: string;
20+
newVizDescription?: string;
21+
pinboardId?: string;
22+
tabId?: string;
23+
newPinboardName?: string;
24+
newTabName?: string;
25+
pinFromStore?: boolean;
26+
};
27+
response: {
28+
pinboardId: string extends string ? null : string;
29+
tabId: string;
30+
vizId: string;
31+
errors?: any;
32+
};
33+
};
34+
[EmbedApiEvent.saveAnswer]: {
35+
request: {
36+
name: string;
37+
description: string;
38+
vizId: string;
39+
isDiscoverable?: boolean;
40+
};
41+
response: any;
42+
};
43+
[EmbedApiEvent.getA3AnalysisColumns]: {
44+
request: {
45+
vizId: string;
46+
};
47+
response: {
48+
data?: any;
49+
errors?: any;
50+
};
51+
};
52+
[EmbedApiEvent.getPinboardTabInfo]: {
53+
request: any;
54+
response: any;
55+
};
56+
[EmbedApiEvent.getDiscoverabilityStatus]: {
57+
request: any;
58+
response: {
59+
shouldShowDiscoverability: boolean;
60+
isDiscoverabilityCheckboxUnselectedPerOrg: boolean;
61+
};
62+
};
63+
[EmbedApiEvent.getAvailableEmbedApis]: {
64+
request: any;
65+
response: {
66+
keys: string[];
67+
};
68+
};
69+
[EmbedApiEvent.getAnswerPageConfig]: {
70+
request: {
71+
vizId: string;
72+
};
73+
response: any;
74+
};
75+
[EmbedApiEvent.getPinboardPageConfig]: {
76+
request: any;
77+
response: any;
78+
};
79+
[EmbedApiEvent.embedApiEventNotFound]: {
80+
request: any;
81+
response: any;
82+
};
83+
};
84+
85+
export type EmbedApiRequest<T extends keyof EmbedApiContractBase> = EmbedApiContractBase[T]['request'];
86+
export type EmbedApiResponse<T extends keyof EmbedApiContractBase> = EmbedApiContractBase[T]['response'];
87+
88+
export type EmbedApiArrayResponse<ApiName extends keyof EmbedApiContractBase> =
89+
Promise<Array<{
90+
redId?: string;
91+
value?: EmbedApiResponse<ApiName>;
92+
error?: any;
93+
}>>
94+
95+
export type EmbedApiHostEventMapping = {
96+
[HostEvent.Pin]: EmbedApiEvent.addVizToPinboard;
97+
'hostEventNotMapped': EmbedApiEvent.embedApiEventNotFound;
98+
}
99+
100+
export type FlattenType<T> = T extends infer R ? { [K in keyof R]: R[K] } : never;
101+
102+
export type HostEventRequest<HostEventT extends HostEvent> =
103+
HostEventT extends keyof EmbedApiHostEventMapping ?
104+
FlattenType<EmbedApiRequest<EmbedApiHostEventMapping[HostEventT]>> : any;
105+
106+
export type HostEventResponse<HostEventT extends HostEvent> =
107+
HostEventT extends keyof EmbedApiHostEventMapping ?
108+
{
109+
value?: EmbedApiResponse<EmbedApiHostEventMapping[HostEventT]>
110+
error?: any;
111+
}
112+
: any;
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { HostEvent } from '../../types';
2+
import { processTrigger } from '../processTrigger';
3+
import {
4+
EmbedApiArrayResponse,
5+
EmbedApiContractBase,
6+
EmbedApiEvent, EmbedApiRequest, HostEventRequest, HostEventResponse,
7+
} from './contracts';
8+
9+
export class HostEventClient {
10+
iFrame: HTMLIFrameElement;
11+
12+
thoughtSpotHost: string;
13+
14+
constructor(iFrame: HTMLIFrameElement, thoughtSpotHost: string) {
15+
this.iFrame = iFrame;
16+
this.thoughtSpotHost = thoughtSpotHost;
17+
}
18+
19+
async executeEmbedApi<ApiName extends keyof EmbedApiContractBase>(apiName: ApiName,
20+
parameters: EmbedApiRequest<ApiName>):
21+
EmbedApiArrayResponse<ApiName> {
22+
const res = await processTrigger(this.iFrame, HostEvent.UiPassthrough, this.thoughtSpotHost, {
23+
type: apiName,
24+
parameters,
25+
});
26+
27+
return res;
28+
}
29+
30+
async hostEventFallback(hostEvent: HostEvent, data: any): Promise<any> {
31+
return processTrigger(this.iFrame, hostEvent, this.thoughtSpotHost, data);
32+
}
33+
34+
async handlePinEvent(
35+
payload: HostEventRequest<HostEvent.Pin>,
36+
): Promise<HostEventResponse<HostEvent.Pin>> {
37+
const res = (await this.executeEmbedApi(EmbedApiEvent.addVizToPinboard, payload))
38+
.filter((r) => r.error || r.value)[0];
39+
40+
if (!res) {
41+
const noVizFoundError = `No viz found${payload.vizId ? ` with id ${payload.vizId}` : ''}`;
42+
return { error: noVizFoundError } as HostEventResponse<HostEvent.Pin>;
43+
}
44+
45+
if (res.error) {
46+
return { error: res?.error?.message };
47+
}
48+
if (res.value?.errors?.message) {
49+
return { error: res?.value.errors?.message };
50+
}
51+
52+
return { value: res.value };
53+
}
54+
55+
async executeHostEvent<T extends HostEvent>(hostEvent: HostEvent, payload: HostEventRequest<T>):
56+
Promise<HostEventResponse<HostEvent>> {
57+
if (hostEvent === HostEvent.Pin && typeof payload === 'object') {
58+
return this.handlePinEvent(payload as HostEventRequest<HostEvent.Pin>);
59+
}
60+
61+
return this.hostEventFallback(hostEvent, payload);
62+
}
63+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { EmbedEvent, HostEvent } from '../../types';
2+
import {
3+
EmbedApiEvent, EmbedApiResponse, FlattenType, HostEventRequest, HostEventResponse,
4+
} from './contracts';
5+
import { HostEventClient } from './embedApiClient';
6+
7+
/**
8+
* Check if the event is an embed API event.
9+
* @param messageType
10+
* @param payload
11+
* @returns boolean
12+
*/
13+
export function isEmbedApiEvent(messageType: HostEvent, payload: any): boolean {
14+
if (messageType === HostEvent.Pin && typeof payload === 'object') {
15+
return true;
16+
}
17+
return false;
18+
}

0 commit comments

Comments
 (0)