Skip to content
1 change: 1 addition & 0 deletions src/embed/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
AllEmbedViewConfig,
} from '../types';
import { V1Embed } from './ts-embed';
import { PageContextOptions } from './hostEventClient/contracts';

/**
* Pages within the ThoughtSpot app that can be embedded.
Expand Down
11 changes: 11 additions & 0 deletions src/embed/conversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ERROR_MESSAGE } from '../errors';
import { Param, BaseViewConfig, RuntimeFilter, RuntimeParameter, ErrorDetailsTypes, EmbedErrorCodes } from '../types';
import { TsEmbed } from './ts-embed';
import { getQueryParamString, getFilterQuery, getRuntimeParameters } from '../utils';
import { PageContextOptions } from './hostEventClient/contracts';

/**
* Configuration for search options
Expand Down Expand Up @@ -328,6 +329,16 @@ export class SpotterEmbed extends TsEmbed {
await this.renderIFrame(src);
return this;
}

/**
* Get the current context of the embedded SpotterEmbed.
* @returns The current context object containing the page type and object ids.
* @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl
*/
public async getCurrentContext(): Promise<PageContextOptions> {
const context = await super.getCurrentContext();
return context;
}
}

/**
Expand Down
32 changes: 29 additions & 3 deletions src/embed/hostEventClient/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,39 @@ export type HostEventRequest<HostEventT extends HostEvent> =
? UIPassthroughRequest<EmbedApiHostEventMapping[HostEventT]>
: any;

export type HostEventResponse<HostEventT extends HostEvent> =
export type HostEventResponse<HostEventT extends HostEvent, ContextT extends ContextType> =
HostEventT extends keyof EmbedApiHostEventMapping
? UIPassthroughResponse<EmbedApiHostEventMapping[HostEventT]>
: any;

// trigger response and request
export type TriggerPayload<PayloadT, HostEventT extends HostEvent> =
PayloadT | HostEventRequest<HostEventT>;
export type TriggerResponse<PayloadT, HostEventT extends HostEvent> =
PayloadT extends HostEventRequest<HostEventT> ? HostEventResponse<HostEventT> : any;
export type TriggerResponse<PayloadT, HostEventT extends HostEvent, ContextT extends ContextType> =
PayloadT extends HostEventRequest<HostEventT> ? HostEventResponse<HostEventT, ContextT> : any;

export enum ContextType {
Search = 'search-answer',
Liveboard = 'liveboard',
Answer = 'answer',
Spotter = 'spotter',
Sage = 'sage',
}

export enum PageType {
PAGE = 'page',
DIALOG = 'dialog',
}

interface ObjectIds {
answerId?: string;
liveboardId?: string;
vizIds?: string[];
dataModelIds?: string[];
}

export interface PageContextOptions {
page: ContextType;
pageType: PageType;
objectIds: ObjectIds;
}
8 changes: 7 additions & 1 deletion src/embed/hostEventClient/host-event-client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ describe('HostEventClient', () => {
type: apiName,
parameters,
},
undefined,
);
expect(result).toEqual(await triggerResponse);
});
Expand Down Expand Up @@ -148,6 +149,7 @@ describe('HostEventClient', () => {
HostEvent.UIPassthrough,
'http://localhost',
{ parameters: payload, type: UIPassthroughEvent.PinAnswerToLiveboard },
undefined,
);
expect(result).toEqual(mockResponse.value);
});
Expand Down Expand Up @@ -185,6 +187,7 @@ describe('HostEventClient', () => {
parameters: payload,
type: 'saveAnswer',
},
undefined,
);
expect(result).toEqual({ answerId: 'newAnswer', ...mockResponse[0].value });
});
Expand All @@ -198,7 +201,7 @@ describe('HostEventClient', () => {

const result = await client.triggerHostEvent(hostEvent, payload);

expect(client.hostEventFallback).toHaveBeenCalledWith(hostEvent, payload);
expect(client.hostEventFallback).toHaveBeenCalledWith(hostEvent, payload, undefined);
expect(result).toEqual(mockResponse);
});

Expand All @@ -223,6 +226,7 @@ describe('HostEventClient', () => {
HostEvent.Pin,
mockThoughtSpotHost,
{},
undefined,
);
expect(result).toEqual([mockResponse]);
});
Expand All @@ -248,6 +252,7 @@ describe('HostEventClient', () => {
HostEvent.Save,
mockThoughtSpotHost,
{},
undefined,
);
expect(result).toEqual([mockResponse]);
});
Expand Down Expand Up @@ -303,6 +308,7 @@ describe('HostEventClient', () => {
parameters: { ...payload, pinboardId: 'test', newPinboardName: 'testLiveboard' },
type: 'addVizToPinboard',
},
undefined,
);
expect(result).toEqual({
pinboardId: 'testLiveboard',
Expand Down
32 changes: 22 additions & 10 deletions src/embed/hostEventClient/host-event-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
UIPassthroughResponse,
TriggerPayload,
TriggerResponse,
ContextType,
} from './contracts';

export class HostEventClient {
Expand All @@ -23,7 +24,7 @@ export class HostEventClient {
* @param {any} data Data to send with the host event
* @returns {Promise<any>} - the response from the process trigger
*/
protected async processTrigger(message: HostEvent, data: any): Promise<any> {
protected async processTrigger(message: HostEvent, data: any, context?: ContextType): Promise<any> {
if (!this.iFrame) {
throw new Error('Iframe element is not set');
}
Expand All @@ -34,14 +35,16 @@ export class HostEventClient {
message,
thoughtspotHost,
data,
context,
);
}

public async handleHostEventWithParam<UIPassthroughEventT extends UIPassthroughEvent>(
apiName: UIPassthroughEventT,
parameters: UIPassthroughRequest<UIPassthroughEventT>,
context?: ContextType,
): Promise<UIPassthroughResponse<UIPassthroughEventT>> {
const response = (await this.triggerUIPassthroughApi(apiName, parameters))
const response = (await this.triggerUIPassthroughApi(apiName, parameters, context as ContextType))
?.filter?.((r) => r.error || r.value)[0];

if (!response) {
Expand All @@ -65,8 +68,9 @@ export class HostEventClient {
public async hostEventFallback(
hostEvent: HostEvent,
data: any,
context?: ContextType,
): Promise<any> {
return this.processTrigger(hostEvent, data);
return this.processTrigger(hostEvent, data, context);
}

/**
Expand All @@ -80,20 +84,22 @@ export class HostEventClient {
public async triggerUIPassthroughApi<UIPassthroughEventT extends UIPassthroughEvent>(
apiName: UIPassthroughEventT,
parameters: UIPassthroughRequest<UIPassthroughEventT>,
context?: ContextType,
): Promise<UIPassthroughArrayResponse<UIPassthroughEventT>> {
const res = await this.processTrigger(HostEvent.UIPassthrough, {
type: apiName,
parameters,
});
}, context);

return res;
}

protected async handlePinEvent(
payload: HostEventRequest<HostEvent.Pin>,
): Promise<HostEventResponse<HostEvent.Pin>> {
context?: ContextType,
): Promise<HostEventResponse<HostEvent.Pin, ContextType>> {
if (!payload || !('newVizName' in payload)) {
return this.hostEventFallback(HostEvent.Pin, payload);
return this.hostEventFallback(HostEvent.Pin, payload, context);
}

const formattedPayload = {
Expand All @@ -104,6 +110,7 @@ export class HostEventClient {

const data = await this.handleHostEventWithParam(
UIPassthroughEvent.PinAnswerToLiveboard, formattedPayload,
context as ContextType,
);

return {
Expand All @@ -114,14 +121,16 @@ export class HostEventClient {

protected async handleSaveAnswerEvent(
payload: HostEventRequest<HostEvent.SaveAnswer>,
context?: ContextType,
): Promise<any> {
if (!payload || !('name' in payload) || !('description' in payload)) {
// Save is the fallback for SaveAnswer
return this.hostEventFallback(HostEvent.Save, payload);
return this.hostEventFallback(HostEvent.Save, payload, context);
}

const data = await this.handleHostEventWithParam(
UIPassthroughEvent.SaveAnswer, payload,
context as ContextType,
);
return {
...data,
Expand All @@ -132,19 +141,22 @@ export class HostEventClient {
public async triggerHostEvent<
HostEventT extends HostEvent,
PayloadT,
ContextT extends ContextType,
>(
hostEvent: HostEventT,
payload?: TriggerPayload<PayloadT, HostEventT>,
): Promise<TriggerResponse<PayloadT, HostEventT>> {
context?: ContextT,
): Promise<TriggerResponse<PayloadT, HostEventT, ContextType>> {
switch (hostEvent) {
case HostEvent.Pin:
return this.handlePinEvent(payload as HostEventRequest<HostEvent.Pin>) as any;
return this.handlePinEvent(payload as HostEventRequest<HostEvent.Pin>, context as ContextType) as any;
case HostEvent.SaveAnswer:
return this.handleSaveAnswerEvent(
payload as HostEventRequest<HostEvent.SaveAnswer>,
context as ContextType,
) as any;
default:
return this.hostEventFallback(hostEvent, payload);
return this.hostEventFallback(hostEvent, payload, context);
}
}
}
2 changes: 1 addition & 1 deletion src/embed/liveboard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1495,7 +1495,7 @@ describe('Liveboard/viz embed tests', () => {
await liveboardEmbed.trigger(HostEvent.Save);
expect(mockProcessTrigger).toHaveBeenCalledWith(HostEvent.Save, {
vizId: 'testViz',
});
}, undefined);
});
});
});
Expand Down
19 changes: 15 additions & 4 deletions src/embed/liveboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { calculateVisibleElementData, getQueryParamString, isUndefined, isValidC
import { getAuthPromise } from './base';
import { TsEmbed, V1Embed } from './ts-embed';
import { addPreviewStylesIfNotPresent } from '../utils/global-styles';
import { TriggerPayload, TriggerResponse } from './hostEventClient/contracts';
import { ContextType, TriggerPayload, TriggerResponse, PageContextOptions } from './hostEventClient/contracts';
import { logger } from '../utils/logger';


Expand Down Expand Up @@ -800,10 +800,11 @@ export class LiveboardEmbed extends V1Embed {
* @param {any} data The payload to send with the message
* @returns A promise that resolves with the response from the embedded app
*/
public trigger<HostEventT extends HostEvent, PayloadT>(
public trigger<HostEventT extends HostEvent, PayloadT, ContextT extends ContextType>(
messageType: HostEventT,
data: TriggerPayload<PayloadT, HostEventT> = ({} as any),
): Promise<TriggerResponse<PayloadT, HostEventT>> {
context?: ContextT,
): Promise<TriggerResponse<PayloadT, HostEventT, ContextT>> {
const dataWithVizId: any = data;
if (messageType === HostEvent.SetActiveTab) {
this.setActiveTab(data as { tabId: string });
Expand All @@ -812,7 +813,7 @@ export class LiveboardEmbed extends V1Embed {
if (typeof dataWithVizId === 'object' && this.viewConfig.vizId) {
dataWithVizId.vizId = this.viewConfig.vizId;
}
return super.trigger(messageType, dataWithVizId);
return super.trigger(messageType, dataWithVizId, context);
}
/**
* Destroys the ThoughtSpot embed, and remove any nodes from the DOM.
Expand Down Expand Up @@ -889,6 +890,16 @@ export class LiveboardEmbed extends V1Embed {

return url;
}

/**
* Get the current context of the embedded liveboard.
* @returns The current context object containing the page type and object ids.
* @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl
*/
public async getCurrentContext(): Promise<PageContextOptions> {
const context = await super.getCurrentContext();
return context;
}
}

/**
Expand Down
11 changes: 11 additions & 0 deletions src/embed/sage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* @author Mourya Balabhadra <mourya.balabhadra@thoughtspot.com>
*/

import { PageContextOptions } from './hostEventClient/contracts';
import { DOMSelector, Param, BaseViewConfig, SearchLiveboardCommonViewConfig } from '../types';
import { getQueryParamString } from '../utils';
import { V1Embed } from './ts-embed';
Expand Down Expand Up @@ -229,4 +230,14 @@ export class SageEmbed extends V1Embed {

return this;
}

/**
* Get the current context of the embedded SageEmbed.
* @returns The current context object containing the page type and object ids.
* @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl
*/
public async getCurrentContext(): Promise<PageContextOptions> {
const context = await super.getCurrentContext();
return context;
}
}
11 changes: 11 additions & 0 deletions src/embed/search-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { SearchLiveboardCommonViewConfig, BaseViewConfig, DefaultAppInitData, Pa
import { getQueryParamString } from '../utils';
import { TsEmbed } from './ts-embed';
import { SearchOptions } from './search';
import { PageContextOptions } from './hostEventClient/contracts';

/**
* @group Embed components
Expand Down Expand Up @@ -198,4 +199,14 @@ export class SearchBarEmbed extends TsEmbed {
const defaultAppInitData = await super.getAppInitData();
return { ...defaultAppInitData, ...this.getSearchInitData() };
}

/**
* Get the current context of the embedded search bar.
* @returns The current context object containing the page type and object ids.
* @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl
*/
public async getCurrentContext(): Promise<PageContextOptions> {
const context = await super.getCurrentContext();
return context;
}
}
11 changes: 11 additions & 0 deletions src/embed/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { getAuthPromise } from './base';
import { getReleaseVersion } from '../auth';
import { getEmbedConfig } from './embedConfig';
import { getInterceptInitData } from '../api-intercept';
import { PageContextOptions } from './hostEventClient/contracts';

/**
* Configuration for search options.
Expand Down Expand Up @@ -523,4 +524,14 @@ export class SearchEmbed extends TsEmbed {
});
return this;
}

/**
* Get the current context of the embedded search.
* @returns The current context object containing the page type and object ids.
* @version SDK: 1.46.0 | ThoughtSpot: 26.3.0.cl
*/
public async getCurrentContext(): Promise<PageContextOptions> {
const context = await super.getCurrentContext();
return context;
}
}
Loading
Loading