Skip to content

SCAL-313306: refactor declarative embedParams building in V1Embed#547

Open
yinstardev wants to merge 1 commit into
mainfrom
SCAL-313306-embedParams-declarative
Open

SCAL-313306: refactor declarative embedParams building in V1Embed#547
yinstardev wants to merge 1 commit into
mainfrom
SCAL-313306-embedParams-declarative

Conversation

@yinstardev
Copy link
Copy Markdown
Contributor

Replace imperative embedParams construction with declarative pattern to eliminate mutation chains and improve maintainability. Each embed type now declares contributions separately, merged once in base class.

Changes:

  • Create new embedParams-builder.ts with EmbedParamsContribution interface
  • Add getEmbedParamsContributions() method to TsEmbed (returns empty by default)
  • Update getDefaultAppInitData() to collect and merge contributions declaratively
  • Refactor AppEmbed: replaces getAppInitData() with getEmbedParamsContributions()
  • Refactor LiveboardEmbed: replaces getAppInitData() with getEmbedParamsContributions()
  • Refactor SearchEmbed: adds getEmbedParamsContributions(), preserves searchOptions in getAppInitData()
  • Refactor SpotterEmbed: replaces getAppInitData() with getEmbedParamsContributions()
  • Add contribution builders to spotter-utils and spotter-viz-utils
  • Keep old builder functions for backward compatibility

Benefits:

  • Eliminates spread operator chains causing hidden mutations
  • Type-safe embedParams with explicit interface
  • Easier to test contributions in isolation
  • Cleaner code with single responsibility
  • Maintains 100% backward compatibility

Tests: all 1103 tests pass

@yinstardev yinstardev requested a review from a team as a code owner June 2, 2026 04:43
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a centralized, declarative embedParams builder system (embedParams-builder.ts) to aggregate configuration contributions across various embed classes (AppEmbed, SpotterEmbed, LiveboardEmbed, and SearchEmbed), replacing the previous imperative getAppInitData overrides. The review feedback recommends updating buildEmbedParams to return undefined instead of an empty object when no contributions exist, which avoids bloating the payload. Following this change, the subclass implementations of getEmbedParamsContributions can be simplified to be fully declarative, eliminating redundant boilerplate checks.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +16 to +25
export function buildEmbedParams(
contributions: Partial<EmbedParamsContribution>[]
): Partial<EmbedParamsContribution> | undefined {
if (contributions.length === 0) return undefined;

return contributions.reduce((acc, contrib) => {
if (!contrib || Object.keys(contrib).length === 0) return acc;
return { ...acc, ...contrib };
}, {} as Partial<EmbedParamsContribution>);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If all contributions are empty, buildEmbedParams currently returns an empty object {}. Since {} is truthy, this results in an empty embedParams object being added to the payload (e.g., embedParams: {}), which unnecessarily bloats the payload. Returning undefined when the merged object is empty prevents this.

Suggested change
export function buildEmbedParams(
contributions: Partial<EmbedParamsContribution>[]
): Partial<EmbedParamsContribution> | undefined {
if (contributions.length === 0) return undefined;
return contributions.reduce((acc, contrib) => {
if (!contrib || Object.keys(contrib).length === 0) return acc;
return { ...acc, ...contrib };
}, {} as Partial<EmbedParamsContribution>);
}
export function buildEmbedParams(
contributions: Partial<EmbedParamsContribution>[]
): Partial<EmbedParamsContribution> | undefined {
const merged = contributions.reduce((acc, contrib) => {
if (!contrib || Object.keys(contrib).length === 0) return acc;
return { ...acc, ...contrib };
}, {} as Partial<EmbedParamsContribution>);
return Object.keys(merged).length > 0 ? merged : undefined;
}
References
  1. Prefer omitting empty or unset parameters entirely from the payload/URL to avoid bloating. Returning undefined when the merged embedParams object is empty ensures it is omitted. (link)
  2. When a utility function processes optional configuration and its output is forwarded without immediate narrowing, it is acceptable to keep the return type optional to match the input's optionality, especially if a tighter type wouldn't benefit downstream consumers.

Comment thread src/embed/app.ts Outdated
Comment on lines 909 to 926
protected async getEmbedParamsContributions(): Promise<Partial<EmbedParamsContribution>[]> {
const contributions: Partial<EmbedParamsContribution>[] = [];

const sidebarContribution = buildSpotterSidebarEmbedParamsContribution(
this.viewConfig,
this.handleError.bind(this),
);
return buildSpotterVizAppInitData(sidebarInitData, this.viewConfig);
if (Object.keys(sidebarContribution).length > 0) {
contributions.push(sidebarContribution);
}

const vizContribution = buildSpotterVizEmbedParamsContribution(this.viewConfig);
if (Object.keys(vizContribution).length > 0) {
contributions.push(vizContribution);
}

return contributions;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

With buildEmbedParams updated to safely filter out empty contributions and return undefined when empty, we can simplify this method to be completely declarative and eliminate the imperative boilerplate of checking Object.keys().length and pushing to an array.

    protected async getEmbedParamsContributions(): Promise<Partial<EmbedParamsContribution>[]> {
        return [
            buildSpotterSidebarEmbedParamsContribution(
                this.viewConfig,
                this.handleError.bind(this),
            ),
            buildSpotterVizEmbedParamsContribution(this.viewConfig),
        ];
    }

Comment thread src/embed/conversation.ts Outdated
Comment on lines 409 to 421
protected async getEmbedParamsContributions(): Promise<Partial<EmbedParamsContribution>[]> {
const contributions: Partial<EmbedParamsContribution>[] = [];

const sidebarContribution = buildSpotterSidebarEmbedParamsContribution(
this.viewConfig,
this.handleError.bind(this),
);
if (Object.keys(sidebarContribution).length > 0) {
contributions.push(sidebarContribution);
}

return contributions;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

With buildEmbedParams updated to safely filter out empty contributions and return undefined when empty, we can simplify this method to be completely declarative and eliminate the imperative boilerplate of checking Object.keys().length and pushing to an array.

    protected async getEmbedParamsContributions(): Promise<Partial<EmbedParamsContribution>[]> {
        return [
            buildSpotterSidebarEmbedParamsContribution(
                this.viewConfig,
                this.handleError.bind(this),
            ),
        ];
    }

Comment thread src/embed/liveboard.ts Outdated
Comment on lines 639 to 648
protected async getEmbedParamsContributions(): Promise<Partial<EmbedParamsContribution>[]> {
const contributions: Partial<EmbedParamsContribution>[] = [];

const vizContribution = buildSpotterVizEmbedParamsContribution(this.viewConfig);
if (Object.keys(vizContribution).length > 0) {
contributions.push(vizContribution);
}

return contributions;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

With buildEmbedParams updated to safely filter out empty contributions and return undefined when empty, we can simplify this method to be completely declarative and eliminate the imperative boilerplate of checking Object.keys().length and pushing to an array.

    protected async getEmbedParamsContributions(): Promise<Partial<EmbedParamsContribution>[]> {
        return [buildSpotterVizEmbedParamsContribution(this.viewConfig)];
    }

Comment thread src/embed/search.ts Outdated
Comment on lines +407 to +415
protected async getEmbedParamsContributions(): Promise<Partial<EmbedParamsContribution>[]> {
const contributions: Partial<EmbedParamsContribution>[] = [];

if (this.viewConfig.visualOverrides) {
contributions.push({ visualOverridesParams: this.viewConfig.visualOverrides });
}

return contributions;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

With buildEmbedParams updated to safely filter out empty contributions and return undefined when empty, we can simplify this method to be completely declarative and eliminate the imperative boilerplate of checking Object.keys().length and pushing to an array.

    protected async getEmbedParamsContributions(): Promise<Partial<EmbedParamsContribution>[]> {
        return [
            this.viewConfig.visualOverrides
                ? { visualOverridesParams: this.viewConfig.visualOverrides }
                : {},
        ];
    }

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Jun 2, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@thoughtspot/visual-embed-sdk@547

commit: 8e0ac2b

Replace imperative embedParams construction with a declarative pattern to
eliminate spread-based mutation chains and remove per-subclass wiring.

Changes:
- Add embedParams-builder.ts as the single facade that maps viewConfig fields
  to embedParams payloads (collectEmbedParamsPayloads + buildEmbedParams).
- TsEmbed.getDefaultAppInitData() calls collectEmbedParamsPayloads() once and
  spreads the merged result; the base class depends only on the facade, not on
  the individual feature modules.
- Add contribution builders buildSpotterSidebarEmbedParamsContribution and
  buildSpotterVizEmbedParamsContribution; delete the old spread-based
  buildSpotterSidebarAppInitData / buildSpotterVizAppInitData.
- Remove now-redundant getAppInitData overrides from AppEmbed, LiveboardEmbed,
  and SpotterEmbed; SearchEmbed keeps only its top-level searchOptions merge.
- Rewrite spotter-utils and spotter-viz-utils specs for the new builders.

Adding a new payload type now touches only embedParams-builder.ts. tsc clean;
full SDK suite passes (1102 passed, 4 skipped).
@yinstardev yinstardev force-pushed the SCAL-313306-embedParams-declarative branch from a1f7dc1 to 8e0ac2b Compare June 2, 2026 07:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant