diff --git a/visual-embed/actions/README.md b/visual-embed/actions/README.md index 40e1983..d0386e4 100644 --- a/visual-embed/actions/README.md +++ b/visual-embed/actions/README.md @@ -1,6 +1,6 @@ # actions -A TypeScript + Vite example that shows how to control the **built-in action menu** of an embedded ThoughtSpot view using the [`Action`](https://developers.thoughtspot.com/docs/Enumeration_Action) enum. The demo embeds a Liveboard and lets you flip between allow-list, deny-list, and disabled modes so you can watch the toolbar and `...` (more) menu change live. +A TypeScript + Vite example that shows how to control the **built-in action menu** of an embedded ThoughtSpot view using the [`Action`](https://developers.thoughtspot.com/docs/Enumeration_Action) enum — including **cascading (nested) menus** like Sync, TML, Schedule, and Publish. The demo embeds a Liveboard and lets you flip between allow-list, deny-list, disabled, and cascading modes so you can watch the toolbar and `...` (more) menu change live. ## What are "actions"? @@ -73,6 +86,27 @@ const embed = new LiveboardEmbed(document.getElementById("ts-embed")!, { // 3) Greyed-out but visible, with a hover reason. // disabledActions: [Action.DownloadAsPdf, Action.Share], // disabledActionReason: "Not available in this demo", + + // 4) CASCADING (nested) menus — parents are derived from their children; + // there is NO Action.Sync member, control cascades via the children. + // Full parent -> children inventory: + // Sync (viz "..." menu) -> Action.SyncToSheets, Action.SyncToOtherApps, Action.ManagePipelines + // Sync (Liveboard header) -> Action.SyncToSlack, Action.SyncToTeams + // TML (Liveboard header) -> Action.ExportTML, Action.UpdateTML, Action.EditTML + // Schedule (Liveboard header)-> Action.Schedule, Action.SchedulesList + // Publish (Liveboard header) -> Action.ManagePublishing, Action.Unpublish + // Rules: hiding ALL children removes the parent menu entirely; exactly + // ONE visible child replaces the parent inline (no submenu); DISABLING + // children greys them out inside the submenu but the parent stays. + // Only Action.TML and Action.Publish are targetable parents themselves. + // hiddenActions + disabledActions CAN be combined — e.g. hide the whole + // Sync menu everywhere AND disable TML with a tooltip: + // hiddenActions: [ + // Action.SyncToSheets, Action.SyncToOtherApps, Action.ManagePipelines, + // Action.SyncToSlack, Action.SyncToTeams, + // ], + // disabledActions: [Action.ExportTML, Action.UpdateTML, Action.EditTML], + // disabledActionReason: "TML editing is admin-only in this workspace", }); embed.on(EmbedEvent.Error, (e) => console.error("Embed error:", e)); @@ -104,10 +138,55 @@ A quick reference of frequently-curated actions (the enum has 150+ members — s | `Action.MakeACopy` | Make a copy | | `Action.AskAi` / `Action.SpotterViz` | Spotter entry points | +## Cascading (nested) menu actions + +Some menu items are **cascading**: a parent entry (e.g. **Sync ›**) that opens a submenu of child actions. The parent is **derived from its children** — in most cases there is no `Action` member for the parent itself (there is no `Action.Sync`), so you control the cascade entirely through its children: + +| Surface | Parent menu item | Child `Action` members | +| --- | --- | --- | +| Visualization / Answer `...` menu | **Sync ›** | `Action.SyncToSheets`, `Action.SyncToOtherApps`, `Action.ManagePipelines` | +| Liveboard header menu | **Sync ›** | `Action.SyncToSlack`, `Action.SyncToTeams` | +| Liveboard header menu | **TML ›** | `Action.ExportTML`, `Action.UpdateTML`, `Action.EditTML` | +| Liveboard header menu | **Schedule ›** | `Action.Schedule`, `Action.SchedulesList` | +| Liveboard header menu | **Publish ›** | `Action.ManagePublishing`, `Action.Unpublish` | + +Note the **Sync** parent appears on two surfaces with *different* children. Two parents are also directly targetable as actions themselves: `Action.TML` and `Action.Publish` (Liveboard surface only); `Sync` and `Schedule` parents are derived-only. + +### The three cascade rules + +1. **Hiding ALL children removes the parent menu entirely.** `hiddenActions` with every child of a cascade → no **Sync ›** at all. +2. **Exactly ONE visible child replaces the parent inline.** Hide two of the three viz-menu sync children and the survivor (e.g. **Manage pipelines**) renders as a flat menu item — no submenu. +3. **Disabling children never collapses the parent.** `disabledActions` greys children out *inside* the submenu; the parent stays. If you want the menu gone, use `hiddenActions`, not `disabledActions`. + +### Worked example: hide Sync everywhere, disable TML with a reason + +```typescript +const embed = new LiveboardEmbed(container, { + liveboardId: "", + + // HIDE the whole Sync feature. Rule 1: all children hidden -> both + // "Sync >" menus (viz menu AND Liveboard header) disappear entirely. + hiddenActions: [ + Action.SyncToSheets, // viz menu child + Action.SyncToOtherApps, // viz menu child + Action.ManagePipelines, // viz menu child (pipeline management lives under Sync) + Action.SyncToSlack, // Liveboard header child + Action.SyncToTeams, // Liveboard header child + ], + + // DISABLE TML. Rule 3: "TML >" stays in the Liveboard header menu, its + // three children visible but greyed out with a hover tooltip. + disabledActions: [Action.ExportTML, Action.UpdateTML, Action.EditTML], + disabledActionReason: "TML editing is admin-only in this workspace", +}); +``` + +`hiddenActions` + `disabledActions` **can** be combined (only `visibleActions` + `hiddenActions` are mutually exclusive). Want to hide the sync *destinations* but keep pipeline management? Hide only `SyncToSheets` + `SyncToOtherApps` — rule 2 kicks in and **Manage pipelines** becomes a flat item. + ## Features - Plain **TypeScript + Vite** — no framework, minimal dependencies. -- One-click switching between `all`, `visibleActions`, `hiddenActions`, and `disabledActions` modes. +- One-click switching between `all`, `visibleActions`, `hiddenActions`, `disabledActions`, and cascading-menu (Sync/TML) modes. - Re-renders the `LiveboardEmbed` on each switch so the toolbar/menu visibly updates. - Shows the live view-config snippet for the selected mode above the embed. - Listens for `EmbedEvent.Error` and logs to the console. @@ -141,7 +220,7 @@ Just pass the same options to whichever embed component you're constructing. ## Gotchas - **Don't combine** `visibleActions` and `hiddenActions` on the same embed — pick one strategy. -- **Parent vs child actions.** Some actions are children of a parent menu. To control a child like `Action.DownloadAsPdf`, the parent (`Action.Download`) generally needs to be present too. For TML options, the parent `Action.TML` must be included for the cascading menu items to appear. +- **Parent vs child actions.** Some actions are children of a cascading parent menu (see the "Cascading (nested) menu actions" section above for the full parent → children inventory and the three cascade rules). Most cascade parents (like Sync) have no `Action` member — control them through their children; `Action.TML` and `Action.Publish` are the two directly-targetable parents. - **Allow-list is future-proof, deny-list is not.** New ThoughtSpot releases add new actions; with `visibleActions` they stay hidden automatically, with `hiddenActions` they will appear unless you add them. - **An action only shows if the user has permission and the feature is enabled** — hiding it in the SDK is a UI control, not a security boundary. Enforce real authorization server-side / via ThoughtSpot roles. diff --git a/visual-embed/actions/src/main.ts b/visual-embed/actions/src/main.ts index deff24d..e1bdf0b 100644 --- a/visual-embed/actions/src/main.ts +++ b/visual-embed/actions/src/main.ts @@ -39,7 +39,7 @@ init({ const LIVEBOARD_ID = import.meta.env.VITE_LIVEBOARD_ID; -type Mode = 'all' | 'visible' | 'hidden' | 'disabled'; +type Mode = 'all' | 'visible' | 'hidden' | 'disabled' | 'cascading'; const MODES: { id: Mode; label: string; snippet: string }[] = [ { @@ -72,6 +72,20 @@ const MODES: { id: Mode; label: string; snippet: string }[] = [ disabledActionReason: 'Not available in this demo' // stay visible but non-clickable`, }, + { + id: 'cascading', + label: 'Cascading menus (Sync / TML)', + snippet: `// Hide the WHOLE "Sync" cascading menu: hiding ALL children +// of a cascading parent removes the parent item itself. +hiddenActions: [ + Action.SyncToSheets, Action.SyncToOtherApps, Action.ManagePipelines, // viz "..." menu + Action.SyncToSlack, Action.SyncToTeams, // Liveboard header menu +], +// Disable the "TML" cascading menu: disabling children NEVER +// collapses the parent — "TML >" stays, children greyed out. +disabledActions: [Action.ExportTML, Action.UpdateTML, Action.EditTML], +disabledActionReason: 'TML editing is admin-only in this demo'`, + }, ]; // 2. Build the LiveboardEmbed view-config for the selected mode. @@ -98,6 +112,31 @@ function buildConfig(mode: Mode): LiveboardViewConfig { disabledActions: [Action.DownloadAsPdf, Action.Share], disabledActionReason: 'Not available in this demo', }; + case 'cascading': + // Cascading (nested) menus: some actions are CHILDREN of a parent menu + // item (e.g. Sync > Sync to Sheets). The parent is derived from its + // children: + // - hiding ALL children removes the parent menu entirely; + // - with exactly ONE visible child, the child replaces the parent + // inline (no submenu); + // - DISABLING children keeps them in the submenu greyed-out, so the + // parent stays visible. + // hiddenActions + disabledActions CAN be combined (only visibleActions + // and hiddenActions are mutually exclusive). + return { + ...base, + hiddenActions: [ + // Sync cascade on the visualization "..." menu: + Action.SyncToSheets, + Action.SyncToOtherApps, + Action.ManagePipelines, + // Sync cascade on the Liveboard header menu: + Action.SyncToSlack, + Action.SyncToTeams, + ], + disabledActions: [Action.ExportTML, Action.UpdateTML, Action.EditTML], + disabledActionReason: 'TML editing is admin-only in this demo', + }; case 'all': default: return base;