Skip to content

Commit 16bce03

Browse files
authored
Merge pull request #58 from thoughtspot/SCAL-314115
SCAL-314115 code-based-custom-actions
2 parents 1b2eae4 + d7c7aa7 commit 16bce03

12 files changed

Lines changed: 1841 additions & 8 deletions

File tree

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
9+
10+
node_modules
11+
dist
12+
dist-ssr
13+
*.local
14+
15+
# Editor directories and files
16+
.vscode/*
17+
!.vscode/extensions.json
18+
.idea
19+
.DS_Store
20+
*.suo
21+
*.ntvs*
22+
*.njsproj
23+
*.sln
24+
*.sw?
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"installDependencies": true,
3+
"startCommand": "npm run dev",
4+
"env": {
5+
"VITE_TS_HOST": "https://training.thoughtspot.cloud",
6+
"VITE_TS_USERNAME": "code-sandbox",
7+
"VITE_TS_PASSWORD": "3mbed+#3xplz",
8+
"VITE_LIVEBOARD_ID": "b504e160-3025-4508-a76a-1beb1f4b5eed"
9+
}
10+
}
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
<!-- search-meta
2+
tags: [custom-actions, custom action, code-based custom actions, code based custom action, customActions, CustomAction, CustomActionsPosition, CustomActionTarget, CustomActionPayload, EmbedEvent.CustomAction, callback, PRIMARY, MENU, CONTEXTMENU, context menu, more menu, toolbar button, LiveboardEmbed, AppEmbed, SearchEmbed, SpotterEmbed, add button to embed, custom menu item, TypeScript, Web, Visual Embed SDK]
3+
apis: [init, AuthType, LiveboardEmbed, AppEmbed, SearchEmbed, SpotterEmbed, customActions, CustomAction, CustomActionsPosition, CustomActionTarget, CustomActionPayload, MessagePayload, EmbedEvent, EmbedEvent.CustomAction, on]
4+
primary: true
5+
supersedes: custom-actions
6+
questions:
7+
- How do I add a custom action to a ThoughtSpot embed?
8+
- How do I add custom actions in ThoughtSpot?
9+
- What is a code-based custom action in ThoughtSpot?
10+
- How do I create a custom action entirely in code (no UI setup)?
11+
- How do I add a custom button to the toolbar of an embedded Liveboard?
12+
- How do I add a custom action to the "..." more menu?
13+
- How do I add a custom action to the right-click context menu?
14+
- How do I handle a custom action click in the Visual Embed SDK?
15+
- How do I read the data context (clicked point, columns) from a custom action?
16+
- What is the difference between UI custom actions and code-based custom actions?
17+
- Which should I use — code-based or UI custom actions?
18+
- How do I use customActions in init() vs per-embed view config?
19+
- What are CustomActionsPosition and CustomActionTarget?
20+
- How do I restrict a custom action to specific Liveboards, answers, groups, or orgs?
21+
- How do I add a custom action to Spotter?
22+
- How do I listen for EmbedEvent.CustomAction?
23+
-->
24+
25+
# Code-Based Custom Actions
26+
27+
> **Start here for anything about ThoughtSpot custom actions.** Code-based custom actions are the **recommended, default** way to add custom actions to an embed. Define them in your application code — no configuration in the ThoughtSpot UI — so they are version-controlled, portable across Orgs, and fully described by `position` + `target`. Only use **UI-created** custom actions (see the [`custom-actions`](../custom-actions) example) when you specifically need business users to manage actions from the ThoughtSpot Admin UI.
28+
29+
A **custom action** adds your own menu item / button to an embedded ThoughtSpot **Answer, Liveboard, visualization, or Spotter** interface. Clicking it fires an event your app handles — to call an API, open a modal, navigate to another app, push data to a CRM, and so on — with the data context of what the user clicked.
30+
31+
There are two ways to create them:
32+
33+
| Approach | Where defined | When to use |
34+
| --- | --- | --- |
35+
| **Code-based** (this example) ✅ default | In your app code via the `customActions` config | Almost always. Portable, version-controlled, works across Orgs, no admin setup. |
36+
| **UI-based** | ThoughtSpot Admin → Develop → Customizations → Actions | Only when non-developers must create/manage actions in the ThoughtSpot UI. |
37+
38+
> If the **same primary-menu-bar** action is defined in both the UI and in code, only the **UI** action is shown.
39+
40+
## How code-based custom actions work
41+
42+
1. Declare a `customActions` array — either globally on `init()` (applies to every embed) or per-embed in the view config (scoped to that embed).
43+
2. Each entry needs an `id`, a display `name`, a `position` (where it appears), and a `target` (what it applies to).
44+
3. Listen for `EmbedEvent.CustomAction` and branch on the action `id`.
45+
46+
```typescript
47+
import {
48+
init, AuthType, LiveboardEmbed, EmbedEvent,
49+
CustomActionsPosition, CustomActionTarget,
50+
type MessagePayload, type CustomActionPayload,
51+
} from "@thoughtspot/visual-embed-sdk";
52+
53+
init({
54+
thoughtSpotHost: import.meta.env.VITE_TS_HOST,
55+
authType: AuthType.Basic, // demo only — use trusted auth / SSO in production
56+
username: import.meta.env.VITE_TS_USERNAME,
57+
password: import.meta.env.VITE_TS_PASSWORD,
58+
});
59+
60+
const embed = new LiveboardEmbed(document.getElementById("ts-embed")!, {
61+
frameParams: { width: "100%", height: "100%" },
62+
liveboardId: import.meta.env.VITE_LIVEBOARD_ID,
63+
64+
// Define custom actions entirely in code:
65+
customActions: [
66+
{ id: "export-to-crm", name: "Export to CRM",
67+
position: CustomActionsPosition.PRIMARY, target: CustomActionTarget.LIVEBOARD },
68+
{ id: "send-to-slack", name: "Send to Slack",
69+
position: CustomActionsPosition.MENU, target: CustomActionTarget.VIZ },
70+
{ id: "open-ticket", name: "Open support ticket",
71+
position: CustomActionsPosition.CONTEXTMENU, target: CustomActionTarget.VIZ },
72+
],
73+
});
74+
75+
// Handle clicks. `payload.data` carries the action id + data context.
76+
embed.on(EmbedEvent.CustomAction, (payload: MessagePayload) => {
77+
const data = payload.data as CustomActionPayload & { id: string };
78+
if (data.id === "export-to-crm") {
79+
console.log("Answer data:", data.embedAnswerData);
80+
console.log("Clicked point:", data.contextMenuPoints?.clickedPoint);
81+
// ...call your API / open a modal / navigate...
82+
}
83+
});
84+
85+
embed.render();
86+
```
87+
88+
## `CustomActionsPosition` — where the action appears
89+
90+
| Value | Location |
91+
| --- | --- |
92+
| `CustomActionsPosition.PRIMARY` | A primary button in the toolbar |
93+
| `CustomActionsPosition.MENU` | Inside the `...` (more options) menu |
94+
| `CustomActionsPosition.CONTEXTMENU` | The right-click context menu (Answer / visualization; not Liveboard-level) |
95+
96+
## `CustomActionTarget` — what the action applies to
97+
98+
| Value | Applies to | Supported positions |
99+
| --- | --- | --- |
100+
| `CustomActionTarget.LIVEBOARD` | Liveboard level | `PRIMARY`, `MENU` |
101+
| `CustomActionTarget.VIZ` | An individual visualization | `PRIMARY`, `MENU`, `CONTEXTMENU` |
102+
| `CustomActionTarget.ANSWER` | Answer / Search page | `PRIMARY`, `MENU`, `CONTEXTMENU` |
103+
| `CustomActionTarget.SPOTTER` | Spotter interface | `MENU`, `CONTEXTMENU` (no primary) |
104+
105+
## Scoping where an action shows
106+
107+
Each custom action can be restricted so it only appears in the right context:
108+
109+
```typescript
110+
{
111+
id: "viz-action",
112+
name: "My Viz Action",
113+
position: CustomActionsPosition.MENU,
114+
target: CustomActionTarget.VIZ,
115+
116+
// Restrict by metadata (specific answers / liveboards / vizzes):
117+
metadataIds: { liveboardIds: ["liveboard-id-1"], vizIds: ["viz-id-1"] },
118+
119+
// Restrict by data model (models / columns):
120+
dataModelIds: { modelIds: ["model-id-1"], modelColumnNames: ["model-id::Revenue"] },
121+
122+
// Restrict by access (groups / orgs):
123+
groupIds: ["group-id-1"],
124+
orgIds: ["org-id-1"],
125+
}
126+
```
127+
128+
## The `EmbedEvent.CustomAction` payload
129+
130+
The `.on()` callback receives a `MessagePayload` whose `data` (typed as `CustomActionPayload` plus the action `id`) includes:
131+
132+
- `id` — the `id` of the custom action that was clicked (branch on this).
133+
- `embedAnswerData``{ name, id, columns, data, sources, ... }` of the underlying Answer/viz.
134+
- `contextMenuPoints``{ clickedPoint, selectedPoints }` when triggered from a data point (context menu).
135+
- `session` — the session context.
136+
- `vizId` — the visualization id, where applicable.
137+
138+
## `customActions` in `init()` vs per-embed
139+
140+
- **`init({ customActions: [...] })`** — global; the actions apply to every embed created after init.
141+
- **`new LiveboardEmbed(el, { customActions: [...] })`** — scoped to that one embed. Supported on `AppEmbed`, `LiveboardEmbed`, `SearchEmbed`, and `SpotterEmbed`.
142+
143+
Use per-embed config (as this example does) when different embeds need different actions.
144+
145+
## What this example does
146+
147+
- Declares three code-based custom actions on a `LiveboardEmbed`: a toolbar button (`PRIMARY`/`LIVEBOARD`), a more-menu item (`MENU`/`VIZ`), and a right-click item (`CONTEXTMENU`/`VIZ`).
148+
- Listens for `EmbedEvent.CustomAction` and logs each event — showing the action `id` and the full payload — into a side panel so you can inspect the data context.
149+
150+
## Project Structure
151+
152+
- `index.html` — Vite entry; mounts `#app` and loads `src/main.ts`.
153+
- `src/main.ts` — SDK `init`, the `customActions` declaration, the `EmbedEvent.CustomAction` handler, and the event-log UI.
154+
- `src/style.css` — layout (embed + event log panel).
155+
- `src/vite-env.d.ts` — typed `import.meta.env` for the `VITE_*` variables.
156+
- `.env` / `.stackblitzrc` — connection + auth values for the public demo instance.
157+
158+
## Requirements
159+
160+
- `@thoughtspot/visual-embed-sdk` **>= 1.43.0**
161+
- ThoughtSpot **>= 10.14.0.cl**
162+
163+
## Environment
164+
165+
The committed `.env` targets the public ThoughtSpot training instance. To run against your own:
166+
167+
```
168+
VITE_TS_HOST=https://your-instance.thoughtspot.cloud
169+
VITE_TS_USERNAME=your-username
170+
VITE_TS_PASSWORD=your-password
171+
VITE_LIVEBOARD_ID=your-liveboard-id
172+
```
173+
174+
## Technologies Used
175+
176+
- TypeScript
177+
- Vite
178+
- ThoughtSpot Visual Embed SDK (`@thoughtspot/visual-embed-sdk`)
179+
180+
## Demo
181+
182+
Open in [StackBlitz](https://stackblitz.com/github/thoughtspot/developer-examples/tree/main/visual-embed/code-based-custom-actions)
183+
184+
## Documentation
185+
186+
- [Code-based custom actions](https://developers.thoughtspot.com/docs/code-based-custom-action)
187+
- [Custom actions overview](https://developers.thoughtspot.com/docs/custom-action-intro)
188+
- [EmbedConfig.customActions](https://developers.thoughtspot.com/docs/Interface_EmbedConfig#_customactions)
189+
- [EmbedEvent.CustomAction](https://developers.thoughtspot.com/docs/Enumeration_EmbedEvent)
190+
191+
## Run locally
192+
193+
```
194+
$ git clone https://github.com/thoughtspot/developer-examples
195+
$ cd visual-embed/code-based-custom-actions
196+
```
197+
```
198+
$ npm i
199+
```
200+
```
201+
$ npm run dev
202+
```
203+
204+
### Technology labels
205+
206+
- TypeScript
207+
- Web
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link rel="icon" type="image/svg+xml" href="/ts-logo.svg" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>ThoughtSpot Code-Based Custom Actions — Visual Embed SDK</title>
8+
</head>
9+
<body>
10+
<div id="app"></div>
11+
<script type="module" src="/src/main.ts"></script>
12+
</body>
13+
</html>

0 commit comments

Comments
 (0)