diff --git a/src/embed/app.ts b/src/embed/app.ts index 8e4ef640..71e7f1cb 100644 --- a/src/embed/app.ts +++ b/src/embed/app.ts @@ -23,8 +23,7 @@ import { } from '../types'; import { V1Embed } from './ts-embed'; import { SpotterChatViewConfig, SpotterSidebarViewConfig } from './conversation'; -import { buildSpotterSidebarAppInitData } from './spotter-utils'; -import { SpotterVizConfig, buildSpotterVizAppInitData } from './spotter-viz-utils'; +import { SpotterVizConfig } from './spotter-viz-utils'; /** * Pages within the ThoughtSpot app that can be embedded. @@ -905,27 +904,6 @@ export class AppEmbed extends V1Embed { } } - /** - * Extends the default APP_INIT payload with `embedParams.spotterSidebarConfig` - * so the conv-assist app can read sidebar configuration on initialisation. - * - * Precedence for `enablePastConversationsSidebar`: - * `spotterSidebarConfig.enablePastConversationsSidebar` wins over the - * deprecated top-level `enablePastConversationsSidebar` flag; if the former - * is absent the latter is used as a fallback. - * - * An invalid `spotterDocumentationUrl` triggers a validation error and is - * excluded from the payload rather than forwarded to the app. - */ - protected async getAppInitData(): Promise { - const defaultAppInitData = await super.getAppInitData(); - const sidebarInitData = buildSpotterSidebarAppInitData( - defaultAppInitData, - this.viewConfig, - this.handleError.bind(this), - ); - return buildSpotterVizAppInitData(sidebarInitData, this.viewConfig); - } /** * Constructs a map of parameters to be passed on to the diff --git a/src/embed/conversation.ts b/src/embed/conversation.ts index 705d7758..9db4c775 100644 --- a/src/embed/conversation.ts +++ b/src/embed/conversation.ts @@ -2,7 +2,6 @@ import isUndefined from 'lodash/isUndefined'; import { ERROR_MESSAGE } from '../errors'; import { Param, BaseViewConfig, RuntimeFilter, RuntimeParameter, ErrorDetailsTypes, EmbedErrorCodes, DefaultAppInitData, VisualizationOverrides, SpotterFileUploadFileTypes } from '../types'; import { TsEmbed } from './ts-embed'; -import { buildSpotterSidebarAppInitData } from './spotter-utils'; import { getQueryParamString, getFilterQuery, getRuntimeParameters, setParamIfDefined } from '../utils'; /** @@ -405,22 +404,6 @@ export class SpotterEmbed extends TsEmbed { super(container, viewConfig); } - /** - * Extends the default APP_INIT payload with `embedParams.spotterSidebarConfig` - * so the conv-assist app can read sidebar configuration on initialisation. - * - * Precedence for `enablePastConversationsSidebar`: - * `spotterSidebarConfig.enablePastConversationsSidebar` wins over the - * deprecated top-level `enablePastConversationsSidebar` flag; if the former - * is absent the latter is used as a fallback. - * - * An invalid `spotterDocumentationUrl` triggers a validation error and is - * excluded from the payload rather than forwarded to the app. - */ - protected async getAppInitData(): Promise { - const defaultAppInitData = await super.getAppInitData(); - return buildSpotterSidebarAppInitData(defaultAppInitData, this.viewConfig, this.handleError.bind(this)); - } protected getEmbedParamsObject() { const { diff --git a/src/embed/embedParams-builder.spec.ts b/src/embed/embedParams-builder.spec.ts new file mode 100644 index 00000000..3f03d502 --- /dev/null +++ b/src/embed/embedParams-builder.spec.ts @@ -0,0 +1,103 @@ +import { buildEmbedParamsPayload } from './embedParams-builder'; +import { ErrorDetailsTypes, EmbedErrorCodes } from '../types'; +import { ERROR_MESSAGE } from '../errors'; + +describe('buildEmbedParamsPayload', () => { + const noopError = jest.fn(); + + it('returns undefined when nothing applies', () => { + expect(buildEmbedParamsPayload({}, noopError)).toBeUndefined(); + }); + + describe('spotterViz', () => { + it('maps spotterViz to spotterVizConfig', () => { + const spotterViz = { brandName: 'MyBrand', description: 'Desc', inputChatPlaceholder: 'Ask...' }; + const result = buildEmbedParamsPayload({ spotterViz }, noopError); + expect(result?.spotterVizConfig).toEqual(spotterViz); + }); + + it('omits spotterVizConfig when spotterViz is absent', () => { + const result = buildEmbedParamsPayload({ visualOverrides: { chart: {} } }, noopError); + expect(result?.spotterVizConfig).toBeUndefined(); + }); + }); + + describe('visualOverrides', () => { + it('maps visualOverrides to visualOverridesParams', () => { + const visualOverrides = { chart: { legend: { show: true, position: 'bottom' as const } } }; + const result = buildEmbedParamsPayload({ visualOverrides }, noopError); + expect(result?.visualOverridesParams).toEqual(visualOverrides); + }); + + it('omits visualOverridesParams when visualOverrides is absent', () => { + const result = buildEmbedParamsPayload({ spotterViz: { brandName: 'X' } }, noopError); + expect(result?.visualOverridesParams).toBeUndefined(); + }); + }); + + describe('spotterSidebarConfig', () => { + it('returns empty/undefined when no sidebar config or standalone flag', () => { + expect(buildEmbedParamsPayload({}, noopError)).toBeUndefined(); + }); + + it('passes spotterSidebarConfig through', () => { + const result = buildEmbedParamsPayload({ + spotterSidebarConfig: { enablePastConversationsSidebar: true, spotterSidebarTitle: 'Chats' }, + }, noopError); + expect(result?.spotterSidebarConfig).toEqual({ + enablePastConversationsSidebar: true, + spotterSidebarTitle: 'Chats', + }); + }); + + it('promotes the standalone enablePastConversationsSidebar flag into spotterSidebarConfig', () => { + const result = buildEmbedParamsPayload({ enablePastConversationsSidebar: true }, noopError); + expect(result?.spotterSidebarConfig?.enablePastConversationsSidebar).toBe(true); + }); + + it('lets spotterSidebarConfig value take precedence over the standalone flag', () => { + const result = buildEmbedParamsPayload({ + enablePastConversationsSidebar: false, + spotterSidebarConfig: { enablePastConversationsSidebar: true }, + }, noopError); + expect(result?.spotterSidebarConfig?.enablePastConversationsSidebar).toBe(true); + }); + + it('calls handleError and strips spotterDocumentationUrl when invalid', () => { + const handleError = jest.fn(); + const result = buildEmbedParamsPayload({ + spotterSidebarConfig: { spotterDocumentationUrl: 'not-a-url' }, + }, handleError); + expect(handleError).toHaveBeenCalledWith(expect.objectContaining({ + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message: ERROR_MESSAGE.INVALID_SPOTTER_DOCUMENTATION_URL, + code: EmbedErrorCodes.INVALID_URL, + })); + expect(result?.spotterSidebarConfig?.spotterDocumentationUrl).toBeUndefined(); + }); + + it('keeps a valid spotterDocumentationUrl', () => { + const handleError = jest.fn(); + const result = buildEmbedParamsPayload({ + spotterSidebarConfig: { spotterDocumentationUrl: 'https://docs.example.com' }, + }, handleError); + expect(handleError).not.toHaveBeenCalled(); + expect(result?.spotterSidebarConfig?.spotterDocumentationUrl).toBe('https://docs.example.com'); + }); + }); + + describe('merging multiple fields', () => { + it('merges sidebar, viz and visualOverrides into one payload', () => { + const visualOverrides = { table: { display: { tableTheme: 'ZEBRA' } } }; + const spotterViz = { brandName: 'MyBrand' }; + const result = buildEmbedParamsPayload({ + spotterSidebarConfig: { enablePastConversationsSidebar: true }, + spotterViz, + visualOverrides, + }, noopError); + expect(result?.spotterSidebarConfig?.enablePastConversationsSidebar).toBe(true); + expect(result?.spotterVizConfig).toEqual(spotterViz); + expect(result?.visualOverridesParams).toEqual(visualOverrides); + }); + }); +}); diff --git a/src/embed/embedParams-builder.ts b/src/embed/embedParams-builder.ts new file mode 100644 index 00000000..7825cae1 --- /dev/null +++ b/src/embed/embedParams-builder.ts @@ -0,0 +1,135 @@ +/** + * Declarative viewConfig -> embedParams mapping. + * + * This is the single place that knows which viewConfig fields become which + * embedParams keys in the APP_INIT payload. Each field is one row in + * EMBED_PARAM_FIELDS: trivial fields are pass-throughs; a field that needs + * transformation/validation gets its own resolver. Adding a new embedParams + * field is a one-row change here - the base class and embed subclasses are + * untouched. + * + * The base class (TsEmbed.getDefaultAppInitData) calls buildEmbedParamsPayload() + * exactly once for every embed type. + */ + +import { ErrorDetailsTypes, EmbedErrorCodes } from '../types'; +import type { VisualizationOverrides } from '../types'; +import { validateHttpUrl } from '../utils'; +import { ERROR_MESSAGE } from '../errors'; +import type { SpotterVizConfig } from './spotter-viz-utils'; +import type { SpotterSidebarViewConfig } from './conversation'; +import { resolveEnablePastConversationsSidebar } from './spotter-utils'; + +/** + * The shape of the `embedParams` object sent inside APP_INIT. These keys are a + * contract with the consuming application - rename with care. + */ +export interface EmbedParamsPayload { + spotterSidebarConfig?: SpotterSidebarViewConfig; + spotterVizConfig?: SpotterVizConfig; + visualOverridesParams?: VisualizationOverrides | null; +} + +/** + * The viewConfig fields that feed embedParams. All optional, so any embed's + * viewConfig is structurally assignable here (one cast at the base-class + * boundary covers the fact that the base ViewConfig type omits these). + */ +export interface EmbedParamsSourceConfig { + spotterSidebarConfig?: SpotterSidebarViewConfig; + enablePastConversationsSidebar?: boolean; + visualOverrides?: VisualizationOverrides; + spotterViz?: SpotterVizConfig; +} + +/** + * Pulls a value for one embedParams field out of the viewConfig. Returning + * `undefined` omits the field from the payload. + */ +type EmbedParamResolver = ( + viewConfig: EmbedParamsSourceConfig, + handleError: (err: any) => void, +) => unknown; + +/** + * Pass-through resolver: emit viewConfig[from] unchanged. `null` is treated as + * "not set" so the field is omitted rather than sent as null. + */ +const passthrough = (from: keyof EmbedParamsSourceConfig): EmbedParamResolver => + (viewConfig) => viewConfig[from] ?? undefined; + +/** + * Resolver for spotterSidebarConfig - the one field with real logic. It folds + * the legacy standalone `enablePastConversationsSidebar` flag into the config + * and validates `spotterDocumentationUrl`, stripping it and reporting an error + * when the URL is invalid. Returns `undefined` when no sidebar config applies. + */ +const resolveSpotterSidebarConfig: EmbedParamResolver = (viewConfig, handleError) => { + const { spotterSidebarConfig, enablePastConversationsSidebar } = viewConfig; + + const resolvedEnablePastConversations = resolveEnablePastConversationsSidebar({ + spotterSidebarConfigValue: spotterSidebarConfig?.enablePastConversationsSidebar, + standaloneValue: enablePastConversationsSidebar, + }); + + if (!spotterSidebarConfig && resolvedEnablePastConversations === undefined) { + return undefined; + } + + const resolved: SpotterSidebarViewConfig = { + ...spotterSidebarConfig, + ...(resolvedEnablePastConversations !== undefined && { + enablePastConversationsSidebar: resolvedEnablePastConversations, + }), + }; + + if (resolved.spotterDocumentationUrl !== undefined) { + const [isValid, validationError] = validateHttpUrl(resolved.spotterDocumentationUrl); + if (!isValid) { + handleError({ + errorType: ErrorDetailsTypes.VALIDATION_ERROR, + message: ERROR_MESSAGE.INVALID_SPOTTER_DOCUMENTATION_URL, + code: EmbedErrorCodes.INVALID_URL, + error: validationError?.message || ERROR_MESSAGE.INVALID_SPOTTER_DOCUMENTATION_URL, + }); + delete resolved.spotterDocumentationUrl; + } + } + + return resolved; +}; + +/** + * The declarative table: one row per embedParams field. `key` is the output + * key in the payload; `resolve` produces its value from the viewConfig. Add a + * field by adding a row - nothing else changes. + */ +const EMBED_PARAM_FIELDS: ReadonlyArray<{ + key: keyof EmbedParamsPayload; + resolve: EmbedParamResolver; +}> = [ + { key: 'spotterVizConfig', resolve: passthrough('spotterViz') }, + { key: 'visualOverridesParams', resolve: passthrough('visualOverrides') }, + { key: 'spotterSidebarConfig', resolve: resolveSpotterSidebarConfig }, +]; + +/** + * Builds the embedParams payload for a viewConfig by running every field + * resolver and keeping the ones that produced a value. Returns `undefined` when + * nothing applies, so embedParams is omitted from APP_INIT entirely. + */ +export function buildEmbedParamsPayload( + viewConfig: EmbedParamsSourceConfig, + handleError: (err: any) => void, +): EmbedParamsPayload | undefined { + const payload: EmbedParamsPayload = {}; + + for (const { key, resolve } of EMBED_PARAM_FIELDS) { + const value = resolve(viewConfig, handleError); + if (value !== undefined) { + payload[key] = value as never; + } + } + + return Object.keys(payload).length > 0 ? payload : undefined; +} diff --git a/src/embed/liveboard.ts b/src/embed/liveboard.ts index 9d870aac..a6d1248c 100644 --- a/src/embed/liveboard.ts +++ b/src/embed/liveboard.ts @@ -32,7 +32,7 @@ import { addPreviewStylesIfNotPresent } from '../utils/global-styles'; import { TriggerPayload, TriggerResponse } from './hostEventClient/contracts'; import { logger } from '../utils/logger'; import { SpotterChatViewConfig } from './conversation'; -import { SpotterVizConfig, buildSpotterVizAppInitData } from './spotter-viz-utils'; +import { SpotterVizConfig } from './spotter-viz-utils'; /** * APP_INIT data shape for LiveboardEmbed. @@ -635,10 +635,6 @@ export class LiveboardEmbed extends V1Embed { } } - protected async getAppInitData(): Promise { - const defaultAppInitData = await super.getAppInitData(); - return buildSpotterVizAppInitData(defaultAppInitData, this.viewConfig); - } /** * Construct a map of params to be passed on to the diff --git a/src/embed/search.ts b/src/embed/search.ts index 680b42bb..2c3ec0ed 100644 --- a/src/embed/search.ts +++ b/src/embed/search.ts @@ -405,19 +405,10 @@ export class SearchEmbed extends TsEmbed { protected async getAppInitData(): Promise { const defaultAppInitData = await super.getAppInitData(); - const result: SearchAppInitData = { + return { ...defaultAppInitData, ...this.getSearchInitData(), }; - - if (this.viewConfig.visualOverrides) { - result.embedParams = { - ...((defaultAppInitData as any).embedParams || {}), - visualOverridesParams: this.viewConfig.visualOverrides, - }; - } - - return result; } protected getEmbedParamsObject() { diff --git a/src/embed/spotter-utils.spec.ts b/src/embed/spotter-utils.spec.ts index 495d5afb..057ef9b3 100644 --- a/src/embed/spotter-utils.spec.ts +++ b/src/embed/spotter-utils.spec.ts @@ -1,6 +1,4 @@ -import { resolveEnablePastConversationsSidebar, buildSpotterSidebarAppInitData } from './spotter-utils'; -import { ErrorDetailsTypes, EmbedErrorCodes } from '../types'; -import { ERROR_MESSAGE } from '../errors'; +import { resolveEnablePastConversationsSidebar } from './spotter-utils'; describe('resolveEnablePastConversationsSidebar', () => { it('prefers spotterSidebarConfig value over standalone', () => { @@ -16,93 +14,3 @@ describe('resolveEnablePastConversationsSidebar', () => { expect(resolveEnablePastConversationsSidebar({})).toBeUndefined(); }); }); - -describe('buildSpotterSidebarAppInitData', () => { - const base = { type: 'APP_INIT' } as any; - const noopError = jest.fn(); - - it('returns base unchanged when no sidebar config or standalone flag', () => { - const result = buildSpotterSidebarAppInitData(base, {}, noopError); - expect(result).toBe(base); - }); - - it('nests spotterSidebarConfig under embedParams', () => { - const result = buildSpotterSidebarAppInitData(base, { - spotterSidebarConfig: { enablePastConversationsSidebar: true, spotterSidebarTitle: 'Chats' }, - }, noopError); - expect(result.embedParams?.spotterSidebarConfig).toEqual({ - enablePastConversationsSidebar: true, - spotterSidebarTitle: 'Chats', - }); - }); - - it('promotes standalone flag into spotterSidebarConfig.enablePastConversationsSidebar', () => { - const result = buildSpotterSidebarAppInitData(base, { enablePastConversationsSidebar: true }, noopError); - expect(result.embedParams?.spotterSidebarConfig?.enablePastConversationsSidebar).toBe(true); - }); - - it('calls handleError and strips spotterDocumentationUrl when invalid', () => { - const handleError = jest.fn(); - const result = buildSpotterSidebarAppInitData(base, { - spotterSidebarConfig: { spotterDocumentationUrl: 'not-a-url' }, - }, handleError); - expect(handleError).toHaveBeenCalledWith(expect.objectContaining({ - errorType: ErrorDetailsTypes.VALIDATION_ERROR, - message: ERROR_MESSAGE.INVALID_SPOTTER_DOCUMENTATION_URL, - code: EmbedErrorCodes.INVALID_URL, - })); - expect(result.embedParams?.spotterSidebarConfig?.spotterDocumentationUrl).toBeUndefined(); - }); - - it('returns base with visualOverridesParams when only visualOverrides is provided', () => { - const visualOverrides = { - chart: { - legend: { show: true, position: 'bottom' as const }, - }, - }; - const result = buildSpotterSidebarAppInitData(base, { - visualOverrides, - }, noopError); - expect(result).toEqual({ - ...base, - embedParams: { visualOverridesParams: visualOverrides }, - }); - }); - - it('includes visualOverridesParams with spotterSidebarConfig', () => { - const visualOverrides = { - table: { - display: { tableTheme: 'ZEBRA' }, - }, - }; - const result = buildSpotterSidebarAppInitData(base, { - spotterSidebarConfig: { enablePastConversationsSidebar: true }, - visualOverrides, - }, noopError); - expect(result.embedParams?.spotterSidebarConfig?.enablePastConversationsSidebar).toBe(true); - expect(result.embedParams?.visualOverridesParams).toEqual(visualOverrides); - }); - - it('includes visualOverridesParams with standalone enablePastConversationsSidebar flag', () => { - const visualOverrides = { - chart: { - legend: { show: false }, - }, - }; - const result = buildSpotterSidebarAppInitData(base, { - enablePastConversationsSidebar: true, - visualOverrides, - }, noopError); - expect(result.embedParams?.spotterSidebarConfig?.enablePastConversationsSidebar).toBe(true); - expect(result.embedParams?.visualOverridesParams).toEqual(visualOverrides); - }); - - it('does not include visualOverridesParams when it is undefined', () => { - const result = buildSpotterSidebarAppInitData(base, { - spotterSidebarConfig: { enablePastConversationsSidebar: true }, - visualOverrides: undefined, - }, noopError); - expect(result.embedParams?.visualOverridesParams).toBeUndefined(); - expect(result.embedParams?.spotterSidebarConfig?.enablePastConversationsSidebar).toBe(true); - }); -}); diff --git a/src/embed/spotter-utils.ts b/src/embed/spotter-utils.ts index a31b5717..436705dd 100644 --- a/src/embed/spotter-utils.ts +++ b/src/embed/spotter-utils.ts @@ -1,9 +1,3 @@ -import { DefaultAppInitData, ErrorDetailsTypes, EmbedErrorCodes } from '../types'; -import { validateHttpUrl } from '../utils'; -import { ERROR_MESSAGE } from '../errors'; -import type { SpotterSidebarViewConfig } from './conversation'; -import type { VisualizationOverrides } from '../types'; - /** * Resolves enablePastConversationsSidebar with * spotterSidebarConfig taking precedence over the @@ -17,65 +11,3 @@ export const resolveEnablePastConversationsSidebar = (params: { ? params.spotterSidebarConfigValue : params.standaloneValue ); - -export function buildSpotterSidebarAppInitData( - defaultAppInitData: T, - viewConfig: { - spotterSidebarConfig?: SpotterSidebarViewConfig; - enablePastConversationsSidebar?: boolean; - visualOverrides?: VisualizationOverrides; - }, - handleError: (err: any) => void, -): T & { - embedParams?: { - spotterSidebarConfig?: SpotterSidebarViewConfig; - visualOverridesParams?: VisualizationOverrides | null; - }; -} { - const { spotterSidebarConfig, enablePastConversationsSidebar, visualOverrides } = viewConfig; - - const resolvedEnablePastConversations = resolveEnablePastConversationsSidebar({ - spotterSidebarConfigValue: spotterSidebarConfig?.enablePastConversationsSidebar, - standaloneValue: enablePastConversationsSidebar, - }); - - const hasConfig = spotterSidebarConfig || resolvedEnablePastConversations !== undefined; - if (!hasConfig) { - if (visualOverrides === undefined) { - return defaultAppInitData; - } - return { - ...defaultAppInitData, - embedParams: { visualOverridesParams: visualOverrides }, - }; - } - - const resolvedSidebarConfig: SpotterSidebarViewConfig = { - ...spotterSidebarConfig, - ...(resolvedEnablePastConversations !== undefined && { - enablePastConversationsSidebar: resolvedEnablePastConversations, - }), - }; - - if (resolvedSidebarConfig.spotterDocumentationUrl !== undefined) { - const [isValid, validationError] = validateHttpUrl(resolvedSidebarConfig.spotterDocumentationUrl); - if (!isValid) { - handleError({ - errorType: ErrorDetailsTypes.VALIDATION_ERROR, - message: ERROR_MESSAGE.INVALID_SPOTTER_DOCUMENTATION_URL, - code: EmbedErrorCodes.INVALID_URL, - error: validationError?.message || ERROR_MESSAGE.INVALID_SPOTTER_DOCUMENTATION_URL, - }); - delete resolvedSidebarConfig.spotterDocumentationUrl; - } - } - - return { - ...defaultAppInitData, - embedParams: { - ...((defaultAppInitData as any).embedParams || {}), - spotterSidebarConfig: resolvedSidebarConfig, - ...(visualOverrides !== undefined ? { visualOverridesParams: visualOverrides } : {}), - }, - }; -} diff --git a/src/embed/spotter-viz-utils.spec.ts b/src/embed/spotter-viz-utils.spec.ts deleted file mode 100644 index 7699c21c..00000000 --- a/src/embed/spotter-viz-utils.spec.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { buildSpotterVizAppInitData } from './spotter-viz-utils'; - -describe('buildSpotterVizAppInitData', () => { - const base = { type: 'APP_INIT' } as any; - - it('returns initData unchanged when spotterViz is not provided', () => { - const result = buildSpotterVizAppInitData(base, {}); - expect(result).toBe(base); - }); - - it('nests spotterViz under embedParams.spotterVizConfig', () => { - const spotterViz = { brandName: 'MyBrand', description: 'Desc', inputChatPlaceholder: 'Ask...' }; - const result = buildSpotterVizAppInitData(base, { spotterViz }); - expect(result.embedParams?.spotterVizConfig).toEqual(spotterViz); - }); - - it('passes brandHeadline through spotterVizConfig', () => { - const spotterViz = { brandName: 'MyBrand', brandHeadline: "Hi, there! I'm" }; - const result = buildSpotterVizAppInitData(base, { spotterViz }); - expect(result.embedParams?.spotterVizConfig?.brandHeadline).toBe("Hi, there! I'm"); - }); - - it('preserves existing embedParams when adding spotterVizConfig', () => { - const existing = { ...base, embedParams: { spotterSidebarConfig: { enablePastConversationsSidebar: true } } }; - const spotterViz = { brandName: 'MyBrand' }; - const result = buildSpotterVizAppInitData(existing, { spotterViz }); - expect(result.embedParams?.spotterVizConfig).toEqual(spotterViz); - expect(result.embedParams?.spotterSidebarConfig?.enablePastConversationsSidebar).toBe(true); - }); -}); diff --git a/src/embed/spotter-viz-utils.ts b/src/embed/spotter-viz-utils.ts index dc2f0811..39a16383 100644 --- a/src/embed/spotter-viz-utils.ts +++ b/src/embed/spotter-viz-utils.ts @@ -1,5 +1,3 @@ -import { DefaultAppInitData } from '../types'; - /** * Defines starter prompts displayed in the SpotterViz interface. * @version SDK: 1.50.0 | ThoughtSpot Cloud: 26.7.0.cl @@ -78,17 +76,3 @@ export interface SpotterVizConfig { inputChatPlaceholder?: string; } -export function buildSpotterVizAppInitData( - initData: T, - viewConfig: { spotterViz?: SpotterVizConfig }, -): T & { embedParams?: { spotterVizConfig?: SpotterVizConfig } } { - const { spotterViz } = viewConfig; - if (!spotterViz) return initData; - return { - ...initData, - embedParams: { - ...((initData as T & { embedParams?: Record }).embedParams || {}), - spotterVizConfig: spotterViz, - }, - }; -} diff --git a/src/embed/ts-embed.ts b/src/embed/ts-embed.ts index 41360569..a8529c94 100644 --- a/src/embed/ts-embed.ts +++ b/src/embed/ts-embed.ts @@ -89,6 +89,7 @@ import { processApiInterceptResponse, processLegacyInterceptResponse, } from '../api-intercept'; +import { buildEmbedParamsPayload, type EmbedParamsSourceConfig } from './embedParams-builder'; /** * Global prefix for all ThoughtSpot postHash Params. @@ -511,7 +512,14 @@ export class TsEmbed { }, }); } - const baseInitData = { + // Subclass viewConfigs carry the embedParams source fields; the base + // ViewConfig type does not declare them, so narrow here. + const embedParams = buildEmbedParamsPayload( + this.viewConfig as EmbedParamsSourceConfig, + this.handleError.bind(this), + ); + + const baseInitData: DefaultAppInitData = { customisations: getCustomisations(this.embedConfig, this.viewConfig), authToken, runtimeFilterParams: this.viewConfig.excludeRuntimeFiltersfromURL @@ -533,6 +541,7 @@ export class TsEmbed { embedExpiryInAuthToken: this.viewConfig.refreshAuthTokenOnNearExpiry ?? true, ...getInterceptInitData(this.viewConfig), ...getHostEventsConfig(this.viewConfig), + ...(embedParams && { embedParams }), }; return baseInitData;