Skip to content

Commit 3e8d55b

Browse files
committed
SCAL-222310 : Init
1 parent b135cfd commit 3e8d55b

File tree

6 files changed

+227
-1
lines changed

6 files changed

+227
-1
lines changed

src/embed/embedPuppeteer.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { logger } from 'src/utils/logger';
2+
import { TsEmbed } from './ts-embed';
3+
import { Action, HostEvent } from 'src/types';
4+
5+
class PuppetString {
6+
private embedInstance: TsEmbed;
7+
8+
constructor(embedInstance: TsEmbed) {
9+
this.embedInstance = embedInstance;
10+
}
11+
12+
public async clickPinButton() {
13+
const res = await this.embedInstance.trigger('EMBED_PUPPET_MOVE', {
14+
element: 'dual-intent-open-modal',
15+
action: 'click',
16+
});
17+
18+
if (!res.data.success) {
19+
throw Error('Failed to click pin button');
20+
}
21+
22+
return this;
23+
}
24+
25+
public async filterPinboardInput(text: string) {
26+
const res = await this.embedInstance.trigger('EMBED_PUPPET_MOVE', {
27+
element: 'filter-pinboard',
28+
action: 'click',
29+
});
30+
31+
if (res.data.success) {
32+
const res = await this.embedInstance.trigger('EMBED_PUPPET_MOVE', {
33+
element: 'filter-pinboard',
34+
action: 'fill',
35+
text,
36+
});
37+
}
38+
39+
throw Error('Failed to Filter pinboard');
40+
}
41+
}
42+
43+
class EmbedPuppeteer {
44+
private embedInstance: TsEmbed;
45+
46+
private isPuppetEnabled = false;
47+
48+
private puppet: PuppetString;
49+
50+
constructor(embedInstance: TsEmbed) {
51+
this.puppet = new PuppetString(embedInstance);
52+
this.embedInstance = embedInstance;
53+
}
54+
55+
protected async checkIfPuppetEnabled() {
56+
const res = await this.embedInstance.trigger(HostEvent.EMBED_PUPPET, {
57+
puppetCheck: true,
58+
});
59+
if (res.data.puppetEnabled) {
60+
this.isPuppetEnabled = true;
61+
return;
62+
}
63+
this.isPuppetEnabled = false;
64+
}
65+
66+
public async pinVizToLiveboard(liveboardName: string) {
67+
if (!this.isPuppetEnabled) {
68+
logger.warn('Embed puppet is not enabled. Please enable puppeteer to use this feature.');
69+
return false;
70+
}
71+
72+
(await this.puppet.clickPinButton()).filterPinboardInput(liveboardName).catch((e) => {
73+
logger.error('Failed to pin viz to liveboard', e);
74+
});
75+
}
76+
}

src/embed/ts-embed.ts

Lines changed: 6 additions & 0 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 { isEmbedApiEvent, processEmbedApiEvent } from '../utils/embedApi/processEmbedApi';
1213
import { logger } from '../utils/logger';
1314
import { getAuthenticationToken } from '../authToken';
1415
import { AnswerService } from '../utils/graphql/answerService/answerService';
@@ -990,6 +991,11 @@ export class TsEmbed {
990991
this.handleError('Host event type is undefined');
991992
return null;
992993
}
994+
995+
if (!isEmbedApiEvent(messageType, data)) {
996+
return processEmbedApiEvent(this.iFrame, messageType, this.thoughtSpotHost, data);
997+
}
998+
993999
return processTrigger(this.iFrame, messageType, this.thoughtSpotHost, data);
9941000
}
9951001

src/types.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3180,7 +3180,14 @@ export enum HostEvent {
31803180
* @version SDK: 1.29.0 | Thoughtspot: 10.1.0.cl
31813181
*/
31823182
GetParameters = 'GetParameters',
3183-
}
3183+
3184+
/**
3185+
* EmbedApi
3186+
*
3187+
* @hidden
3188+
*/
3189+
EmbedApi = 'EmbedApi',
3190+
}
31843191

31853192
/**
31863193
* The different visual modes that the data sources panel within

src/utils/embedApi/contracts.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { HostEvent } from '../../types';
2+
3+
export type EmbedApiContractBase = {
4+
addVizToPinboard: (payload: {
5+
vizId: string;
6+
newVizName: string;
7+
newVizDescription?: string;
8+
pinboardId?: string;
9+
tabId?: string;
10+
newPinboardName?: string;
11+
newTabName?: string;
12+
pinFromStore?: boolean;
13+
}) => {
14+
pinboardId: string;
15+
tabId: string;
16+
vizId: string;
17+
errors?: any;
18+
};
19+
saveAnswer: (payload: {
20+
name: string;
21+
description: string;
22+
vizId: string;
23+
isDiscoverable?: boolean;
24+
}) => any;
25+
getA3AnalysisColumns: (
26+
payload: {
27+
vizId: string;
28+
} & any,
29+
) => {
30+
data?: any;
31+
errors?: any;
32+
};
33+
getPinboardTabInfo: () => any;
34+
getDiscoverabilityStatus: () => {
35+
shouldShowDiscoverability: boolean;
36+
isDiscoverabilityCheckboxUnselectedPerOrg: boolean;
37+
};
38+
getAvailableEmbedApis: () => {
39+
keys: string[];
40+
};
41+
getAnswerPageConfig: (payload: { vizId: string }) => any;
42+
getPinboardPageConfig: () => any;
43+
_notFound: (payload: any) => any;
44+
};
45+
46+
export type EmbedApiName = keyof EmbedApiContractBase;
47+
48+
export type EmbedApiArrayResponse<ApiName extends EmbedApiName> =
49+
Promise<Array<{
50+
redId?: string;
51+
value?: ReturnType<EmbedApiContractBase[ApiName]>;
52+
error?: any;
53+
}>>
54+
55+
export type EmbedApiHostEventMapping = {
56+
[key: string]: string;
57+
[HostEvent.Pin]: 'addVizToPinboard';
58+
}
59+
60+
export type EmbedApiEventResponse<HostEventT extends HostEvent> = ReturnType<EmbedApiContractBase[EmbedApiHostEventMapping[HostEventT] extends keyof EmbedApiContractBase ? EmbedApiHostEventMapping[HostEventT] : '_notFound']>;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { HostEvent } from 'src/types';
2+
import { processTrigger } from '../processTrigger';
3+
import { EmbedApiArrayResponse, EmbedApiContractBase } from './contracts';
4+
5+
export class EmbedApiClient {
6+
iFrame: HTMLIFrameElement;
7+
8+
thoughtSpotHost: string;
9+
10+
constructor(iFrame: HTMLIFrameElement, thoughtSpotHost: string) {
11+
this.iFrame = iFrame;
12+
this.thoughtSpotHost = thoughtSpotHost;
13+
}
14+
15+
async executeEmbedApi<ApiName extends keyof EmbedApiContractBase>(apiName: ApiName,
16+
parameters: Parameters<EmbedApiContractBase[ApiName]>[0]):
17+
EmbedApiArrayResponse<ApiName> {
18+
const res = await processTrigger(this.iFrame, HostEvent.EmbedApi, this.thoughtSpotHost, {
19+
type: apiName,
20+
parameters,
21+
});
22+
23+
return res;
24+
}
25+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { HostEvent } from '../../types';
2+
import { EmbedApiEventResponse } from './contracts';
3+
import { EmbedApiClient } from './embedApiClient';
4+
5+
/**
6+
* Check if the event is an embed API event.
7+
* @param messageType
8+
* @param payload
9+
* @returns boolean
10+
*/
11+
export function isEmbedApiEvent(messageType: HostEvent, payload: any): boolean {
12+
if (messageType === HostEvent.Pin && typeof payload === 'object') {
13+
return true;
14+
}
15+
return false;
16+
}
17+
18+
/**
19+
*
20+
* @param iFrame
21+
* @param messageType
22+
* @param thoughtSpotHost
23+
* @param data
24+
* @returns Promise<any>
25+
*/
26+
export async function processEmbedApiEvent<T extends HostEvent>(
27+
iFrame: HTMLIFrameElement,
28+
messageType: T,
29+
thoughtSpotHost: string,
30+
data: any,
31+
): Promise<{
32+
error?: any;
33+
value?: EmbedApiEventResponse<T>
34+
}> {
35+
if (messageType === HostEvent.Pin) {
36+
const embedApiClient = new EmbedApiClient(iFrame, thoughtSpotHost);
37+
const res = (await embedApiClient.executeEmbedApi('addVizToPinboard', {
38+
...data,
39+
})).filter((r) => r.error || r.value)[0];
40+
if (res.error) {
41+
return { error: res?.error?.message };
42+
}
43+
if (res.value) {
44+
return { error: res?.value.errors?.message };
45+
}
46+
47+
// could not find a type check to tell ts both are same.
48+
return { value: res.value } as any;
49+
}
50+
51+
return { error: 'Invalid Embed API event' };
52+
}

0 commit comments

Comments
 (0)