Skip to content

[SCAL-337048] feat(sdk): support per-action disabledActionReason - #711

Merged
jitendra-bhati-ts merged 8 commits into
mainfrom
SCAL-337048-per-action-disabled-reason
Oct 28, 2026
Merged

jitendra-bhati-ts merged 8 commits into
mainfrom
SCAL-337048-per-action-disabled-reason

Conversation

@jitendra-bhati-ts

@jitendra-bhati-ts jitendra-bhati-ts commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

What

disabledActionReason can now be a string (unchanged) or a map that gives each disabled action its own tooltip, with an optional GENERIC_REASON fallback.

import { Action } from '@thoughtspot/visual-embed-sdk';

new LiveboardEmbed('#tsEmbed', {
    disabledActions: [Action.Download, Action.Save, Action.Share],
    disabledActionReason: {
        [Action.Download]: 'Downloads require a Pro license',
        [Action.Save]: 'You have read-only access',
        GENERIC_REASON: 'Not available for your account', // optional
    },
});

Why

Today one string is shown on every disabled action. A host that disables Download for licensing and Save for read-only access can only show one generic message for both.

How

  • Types (types.ts): disabledActionReason?: string | DisabledActionReasonMap; new DisabledActionReasonMap type (Partial<Record<Action | 'GENERIC_REASON', string>>), exported from the main and /react entry points. GENERIC_REASON is a plain key typed in the map, not an exported const, so customers write GENERIC_REASON: '...' and TypeScript still checks it.
  • Validation (utils/actions.ts, new; lodash isString, isPlainObject, pickBy, isEmpty): a string goes out unchanged. A map keeps only keys that are an Action or GENERIC_REASON with a string value. Anything else is dropped with a logger.warn, and the embed never fails over it. The allow-list also blocks __proto__ / constructor keys. An empty map, or a map where every key was dropped, sends nothing, the same as leaving the option unset.
  • Transport (ts-embed.ts): the map is never put on the iframe URL. A map can hold a reason for every action, which is too long for a URL, so it is sent in the APP_INIT payload (embedParams.disableHint) on every embed, the same channel excludeConfigFromURL already uses. APP_INIT_PARAMS maps each param to a check on its value (disableHint → isPlainObject), and getUrlQueryParamsObject moves a param to the init data when its check passes. Another param can be kept off the URL by adding one entry with its own check. The string form stays on the URL, so it keeps working on every cluster.
  • Runtime updates: prerender and UpdateEmbedParams already send the whole config over postMessage, so they carry the map as an object without extra code.
disabledActionReason Sent in
string iframe URL (disableHint), unchanged
map APP_INIT payload (embedParams.disableHint), never the URL

Version

The object form is supported from SDK <TBD> (next release) | ThoughtSpot Cloud 26.12.0.cl. The string form is unchanged (SDK 1.6.0).

Backward compatibility

The string form is unchanged, and all existing tests pass as they are. One behaviour change: a non-string, non-object value (number, array, boolean), which the type never allowed, is now dropped with a warning instead of being sent as-is.

App side (merged)

The tooltip is drawn by the ThoughtSpot app, not the SDK. The app side is merged in scaligent #72606 and #72825. The app needs no change for the APP_INIT transport: it already merges APP_INIT embedParams over the URL params on every load. On a cluster without the app change, a map does not render correctly, which is why the object form needs ThoughtSpot Cloud 26.12.0.cl.

Before app side PR After app side PR After app side example
Screenshot 2026-10-14 at 11 16 01 AM (2) Screenshot 2026-10-14 at 11 30 47 AM (2) Screenshot 2026-10-14 at 11 30 53 AM (2)

SDK map on a cluster without app side changes: tooltip shows [object Object]. (Screenshots were taken while the map still went on the URL; the result is the same with the APP_INIT transport.)

Tests

  • actions.spec.ts covers the string form, full map, partial map with and without GENERIC_REASON, a map with only GENERIC_REASON, empty map, unknown keys, prototype keys, non-string values and invalid types.
  • liveboard.spec.ts checks that a map is kept off the iframe URL, and that an empty map sends no disableHint.
  • ts-embed.spec.ts checks that the map arrives in the APP_INIT payload with and without excludeConfigFromURL (other params stay on the URL), that a string stays on the URL, and the cleaned map in the UpdateEmbedParams payload.
  • Verified end to end in an embed app against a cluster with the app change: the iframe URL has no disableHint, and each disabled action shows its own reason.

npm run tsc:check, npm run lint, npm run lint-versions and npm run test-sdk all pass (2207 passed, 4 skipped). Bundle size 32.69 kB of the 34 kB limit.

Jira: https://thoughtspot.atlassian.net/browse/SCAL-337048

@jitendra-bhati-ts
jitendra-bhati-ts requested a review from a team as a code owner October 13, 2026 09:08

@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 introduces support for a per-action disabledActionReason map (DisabledActionReasonMap) in the Visual Embed SDK, allowing developers to specify custom tooltip reasons for different disabled actions, with an optional GENERIC_REASON fallback. It includes validation utilities, warning logs for invalid types or keys, updated TypeScript types, and comprehensive unit tests. The feedback suggests a more robust implementation of the isPlainObject utility to prevent built-in object types like Map, Set, Date, and RegExp from bypassing the type check and being silently ignored.

Comment thread src/utils/disabled-action-reason.ts Outdated
@pkg-pr-new

pkg-pr-new Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

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

commit: 34c1063

Comment thread src/utils/actions.ts Outdated
Comment thread src/utils/disabled-action-reason.ts Outdated
Comment thread src/utils/disabled-action-reason.ts Outdated
Comment thread src/utils/disabled-action-reason.ts Outdated
Comment thread src/utils/actions.ts Outdated
Comment thread src/types.ts Outdated
Comment thread src/utils/actions.ts Outdated
@jitendra-bhati-ts
jitendra-bhati-ts force-pushed the SCAL-337048-per-action-disabled-reason branch from aae4802 to 15a957a Compare October 23, 2026 07:06
yinstardev
yinstardev previously approved these changes Oct 23, 2026
Comment thread src/utils/actions.ts Outdated
disabledActionReason accepted only one string, shown on every disabled
action, so a host could not explain why each action was disabled.

It now also accepts a map keyed by Action, with an optional
GENERIC_REASON fallback. A string is sent unchanged. A map is cleaned
before it is sent: keys that are not an Action or GENERIC_REASON, and
values that are not strings, are dropped with a console warning, so a bad
entry never fails the embed. What is left goes out JSON-encoded in the
existing disableHint param; an empty result sends nothing, the same as
leaving the option unset.

GENERIC_REASON and DisabledActionReasonMap are exported from the main
and React entry points.
…-337048)

With excludeConfigFromURL, disableHint is held back from the iframe URL
and sent in the APP_INIT embedParams instead. Add tests that a map
arrives there as the cleaned object, without the unknown key, and that a
string arrives unchanged.

Also detect a plain object by its built-in type, so a Map, Date or other
built-in gets the invalid-type warning, named by its real type, instead
of being treated as an empty map.
…337048)

Use lodash isPlainObject and isString, which the SDK already uses,
instead of a hand-written type check. isPlainObject is false for
arrays, Maps, Dates and other built-ins, so they still get the
invalid-type warning. The warning no longer names the value's type.
…AL-337048)

1.53.1 is already released, so the map form ships in the next release.
Use the <TBD> placeholder, which the release bump fills in, and point
the disabledActionReason doc at DisabledActionReasonMap for the SDK
version instead of repeating it.
…7048)

A general name, so other action helpers can live in the same file.
@jitendra-bhati-ts
jitendra-bhati-ts force-pushed the SCAL-337048-per-action-disabled-reason branch from 15a957a to 131cefd Compare October 27, 2026 09:35
@shivam-kumar-ts

Copy link
Copy Markdown
Contributor

this last few comment this will be the breakign changes for the 26.11 or previous version

… (SCAL-337048)

A per-action map can hold a reason for every action, which is too long
for the iframe URL. Send the map in the APP_INIT payload's embedParams on
every embed, the same place excludeConfigFromURL already sends it; the
app merges those embedParams over the URL params, so it needs no change.

The string form stays on the URL, so it keeps working on every cluster.
Prerender and UpdateEmbedParams already carry the map as an object.

Filter the map with lodash pickBy and walk the query params with lodash
forEach, matching the lodash helpers the SDK already uses.
@jitendra-bhati-ts
jitendra-bhati-ts force-pushed the SCAL-337048-per-action-disabled-reason branch from 131cefd to 6784e5a Compare October 27, 2026 19:23
… (SCAL-337048)

Map each param to a check on its value instead of hard-coding the object
check in the loop, so a future param can be kept off the URL by adding
one entry with its own condition. No behaviour change.

@shivam-kumar-ts shivam-kumar-ts 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.

LGTM

Comment thread src/types.ts Outdated
…y (SCAL-337048)

GENERIC_REASON was the only const in types.ts. Type the map key as
Action | 'GENERIC_REASON' instead, so customers write GENERIC_REASON as a
plain key and TypeScript still checks it. The key on the wire is
unchanged, so the merged app side needs nothing. The constant was never
released.
@jitendra-bhati-ts
jitendra-bhati-ts merged commit dba2bcc into main Oct 28, 2026
17 checks passed
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.

4 participants