Skip to content

SCAL-336321 Navigate a shared pre-render only after its UpdateEmbedParams lands - #659

Open
sastaachar wants to merge 4 commits into
mainfrom
SCAL-336321-navigate-after-params
Open

SCAL-336321 Navigate a shared pre-render only after its UpdateEmbedParams lands#659
sastaachar wants to merge 4 commits into
mainfrom
SCAL-336321-navigate-after-params

Conversation

@sastaachar

@sastaachar sastaachar commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Problem

On a shared pre-render, a runtimeFilters value from one liveboard sticks when the host switches to another liveboard, and runtimeFilters: [] does not reset it. The container fix attempted in blink-v2 ([SCAL-333947], PR #68332) did not close it and has been reverted.

Root cause — the host events go out in the wrong order

LiveboardEmbed.beforePrerenderVisible() queues two container-ready callbacks:

  1. super.beforePrerenderVisible() → posts HostEvent.UpdateEmbedParams
  2. its own → posts HostEvent.Navigate

Both are invoked in registration order, but the first one suspends on await this.getUpdateEmbedParamsObject() (which awaits getAppInitData()). It yields at that await, callback 2 runs to completion, and the real order on the wire is:

["Navigate", "updateEmbedParams", "UpdateRuntimeFilters"]

That is observed output, not a reading of the code — it is what the new test records when the fix is removed.

So the container starts loading the new liveboard while still holding the previous config's runtimeFilterParams, and the params that arrive mid-load are dropped. Hence the filter that "sticks", and the tile that keeps the previous filter's data.

Fix

TsEmbed.beforePrerenderVisible() now publishes preRenderParamsApplied, a promise that resolves once UpdateEmbedParams (and the reconcileRuntimeParams follow-up) has been posted and the container has had UPDATE_EMBED_PARAMS_SETTLE_MS (200ms) to apply it. LiveboardEmbed awaits that before triggering Navigate.

The settle window is deliberate rather than an ack-wait: the container applies the payload through React state, so the post being delivered is not the same as the new params being in effect, and awaiting trigger() would risk stalling navigation on the 30s trigger timeout.

Two properties worth checking in review:

  • the promise is created synchronously in beforePrerenderVisible(), outside executeAfterEmbedContainerLoaded, so the navigation callback has something to await whether the container is already loaded or not;
  • it resolves in a finally, so a params failure delays navigation but never blocks it. Both callbacks are gated on the same container-ready signal, so neither can strand the other.

Also included (previously unreviewed, on the same branch): reconcileRuntimeParams(), which re-sends the new config's runtime filters after UpdateEmbedParams, or — when the new config has none — re-sends the previous config's filters with values: [], which is what actually resets them. That is the reset the bogus-column workaround was standing in for. It only works if it lands before Navigate, which is what this PR makes true.

Verification

  • Ordering test is proven to have diagnostic power: with await this.preRenderParamsApplied removed, should trigger Navigate only after UpdateEmbedParams has settled fails with the received array above. It is not a test that passes either way.
  • Full SDK suite: 45/45 suites, 1752 passed, 4 skipped, no coverage threshold violations.
  • tsc --noEmit clean; eslint 0 errors and no new warnings (20 on the touched files, same as baseline).
  • 8 existing pre-render tests were updated, not deleted: they asserted on the navigate spy synchronously, which is exactly the behaviour this PR changes. Each now waits out the settle window. The AuthInit test needed 1305ms rather than 1005ms — 1000ms container-ready fallback plus the 200ms window.

Notes for the reviewer

  • Supersedes SCAL-333947 : Parma #654, which carries only the reconcileRuntimeParams half of this change. Close that one in favour of this.
  • if (this.viewConfig.runtimeFilters) is truthy for [], so a caller passing runtimeFilters: [] takes the first branch and sends an empty array rather than falling through to the empty-values reset. Whether blink treats an empty array as a clear decides if the customer's literal runtimeFilters: [] case is closed by this PR or still needs the container-side change. Flagging rather than changing it here, since it alters behaviour rather than making the existing intent work.
  • Only LiveboardEmbed navigates its pre-render on show; AppEmbed does not override beforePrerenderVisible, so it is unaffected.

Refs SCAL-336321, SCAL-333947.

sastaachar and others added 4 commits September 8, 2026 15:29
…ests

reconcileRuntimeParams triggers UpdateRuntimeFilters after
UpdateEmbedParams, so UpdateEmbedParams is no longer the last
processTrigger call. Two showPreRender tests asserted it with
toHaveBeenLastCalledWith and failed; they now assert the same payload
with toHaveBeenCalledWith.

Make the fallback branch null-safe. getPreRenderObj() reads an untyped
property off the pre-render wrapper node, so it can return an object
with no viewConfig; reading viewConfig.runtimeFilters off it threw a
TypeError that the surrounding catch turned into a logger.error, which
jest-setup escalates to a fatal error and surfaced on an unrelated
liveboard.spec test.

Add coverage for the three reconcile paths — filters from the new
config, clearing the filters left by the previous config, and no
filters at all — plus the missing-viewConfig case.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ts params land

`LiveboardEmbed.beforePrerenderVisible()` queued two container-ready
callbacks: the base one that posts `UpdateEmbedParams`, then its own that
posts `HostEvent.Navigate`. The first suspends on
`await getUpdateEmbedParamsObject()` (which awaits `getAppInitData()`), so
the second ran to completion first and the real order on the wire was
`Navigate`, `UpdateEmbedParams`, `UpdateRuntimeFilters`.

The container therefore started loading the new liveboard while still
holding the previous config's `runtimeFilterParams`, and the params that
arrived mid-load were dropped — the reported symptom of a filter sticking
across a shared pre-render and `runtimeFilters: []` failing to reset it.

`beforePrerenderVisible()` now publishes `preRenderParamsApplied`, which
resolves once the params (and the `reconcileRuntimeParams` follow-up) have
been posted and the container has had 200ms to apply them; the liveboard
navigation awaits it. It resolves on the failure path too, so navigation is
delayed but never blocked, and both callbacks are gated on the same
container-ready signal, so neither can strand the other.

Ordering is pinned by a test verified to fail without the change: it
observed `["Navigate", "updateEmbedParams", "UpdateRuntimeFilters"]`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sastaachar
sastaachar requested a review from a team as a code owner September 8, 2026 10:03

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

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.

Code Review

This pull request addresses race conditions in pre-rendered embeds by delaying navigation until the UpdateEmbedParams payload has settled and by reconciling runtime filters on shared pre-renders. The review feedback highlights two key improvements: safely guarding the .map() call on prevRuntimeFilters with Array.isArray to prevent runtime TypeErrors, and explicitly asserting the presence of UpdateEmbedParams in test assertions to avoid false positives from indexOf returning -1.

Comment thread src/embed/ts-embed.ts
Comment on lines +2083 to +2084
const prevRuntimeFilters = this.getPreRenderObj()?.viewConfig?.runtimeFilters;
if (!prevRuntimeFilters) return;

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

Since getPreRenderObj() reads untyped properties off the DOM wrapper node, prevRuntimeFilters is not guaranteed to be an array at runtime. To prevent potential runtime TypeErrors if prevRuntimeFilters is truthy but not an array, use Array.isArray to safely guard the .map() call.

Suggested change
const prevRuntimeFilters = this.getPreRenderObj()?.viewConfig?.runtimeFilters;
if (!prevRuntimeFilters) return;
const prevRuntimeFilters = this.getPreRenderObj()?.viewConfig?.runtimeFilters;
if (!Array.isArray(prevRuntimeFilters)) return;

Comment on lines +5793 to +5795
expect(eventTypes.indexOf(HostEvent.UpdateEmbedParams)).toBeLessThan(
eventTypes.indexOf(HostEvent.UpdateRuntimeFilters),
);

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 HostEvent.UpdateEmbedParams is not triggered, indexOf returns -1. Since indexOf(HostEvent.UpdateRuntimeFilters) is >= 0, the assertion expect(...).toBeLessThan(...) will pass even if UpdateEmbedParams was never triggered. To make the test robust and prevent false positives, explicitly assert that UpdateEmbedParams is present in eventTypes.

            expect(eventTypes).toContain(HostEvent.UpdateEmbedParams);
            expect(eventTypes.indexOf(HostEvent.UpdateEmbedParams)).toBeLessThan(
                eventTypes.indexOf(HostEvent.UpdateRuntimeFilters),
            );

@pkg-pr-new

pkg-pr-new Bot commented Sep 8, 2026

Copy link
Copy Markdown

Open in StackBlitz

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

commit: 2a61d7f

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