Skip to content

Commit cfbf639

Browse files
committed
some more doc updates
1 parent 4d5a7dc commit cfbf639

6 files changed

Lines changed: 271 additions & 88 deletions

File tree

rest-api/import-user-multiple-orgs/README.md

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,42 @@
11
<!-- search-meta
22
tags: [REST-API, user-management, multi-org, TypeScript, NodeJS]
3-
apis: [createUser, assignUserToOrg, listOrgs, REST-API-v2]
3+
apis: [importUsers, org_identifiers, REST-API-v2]
44
questions:
5-
- How do I create users in multiple ThoughtSpot organizations via REST API?
5+
- How do I import users into multiple ThoughtSpot organizations via REST API?
66
- How do I provision users to specific orgs in ThoughtSpot?
77
- How do I manage users across ThoughtSpot orgs using the REST API?
8-
- How do I bulk create users in ThoughtSpot with org assignment?
8+
- How do I bulk import users in ThoughtSpot with org assignment?
99
-->
1010

11-
# create-user-cross-org
11+
# import-user-multiple-orgs
1212

13-
This repository provides an example of how to create user in any org (not necessarily to be current logged in org) via tenant admin by using ThoughtSpot APIs. This API allows tenant admin to create user in any or multiple as well.
13+
This repository provides an example of how to import a user into any org (not necessarily the current logged-in org) via tenant admin by using ThoughtSpot APIs. This API allows tenant admin to import users into one or multiple orgs.
1414

1515
## Key Usage
1616

1717
```typescript
18-
// Create a user and assign them to multiple ThoughtSpot orgs
19-
// POST /api/rest/2.0/users/create
20-
const user = await fetch(`${THOUGHTSPOT_HOST}/api/rest/2.0/users/create`, {
18+
// Import a user and assign them to multiple ThoughtSpot orgs
19+
// POST /api/rest/2.0/users/import
20+
const response = await fetch(`${THOUGHTSPOT_HOST}/api/rest/2.0/users/import`, {
2121
method: "POST",
2222
headers: {
2323
"Content-Type": "application/json",
24-
Authorization: `Bearer ${bearerToken}`,
24+
Authorization: bearerToken,
2525
},
2626
body: JSON.stringify({
27-
name: "new-user@example.com",
28-
display_name: "New User",
29-
password: "temporary-password",
30-
org_identifiers: ["org-id-1", "org-id-2"], // assign to multiple orgs
27+
users: [
28+
{
29+
user_identifier: "new-user@example.com",
30+
display_name: "New User",
31+
password: "Cloud123!",
32+
account_type: "LOCAL_USER",
33+
account_status: "ACTIVE",
34+
email: "new-user@example.com",
35+
org_identifiers: ["org-id-1", "org-id-2"], // assign to multiple orgs
36+
},
37+
],
38+
dry_run: false,
39+
delete_unspecified_users: false,
3140
}),
3241
});
3342
```

rest-api/typescript-sdk/express-example/README.md

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,59 @@
11
<!-- search-meta
22
tags: [typescript-sdk, REST-API, Express, NodeJS, TypeScript, rest-api-sdk]
3-
apis: [ThoughtSpotRestApi, createBearerAuthenticationConfig, searchLiveboards, REST-API-v2]
3+
apis: [ThoughtSpotRestApi, createBearerAuthenticationConfig, getCurrentUserInfo, searchMetadata, REST-API-v2]
44
questions:
55
- How do I use the ThoughtSpot REST API TypeScript SDK with Express?
66
- How do I set up a Node.js Express server with the ThoughtSpot REST API SDK?
7-
- How do I authenticate with ThoughtSpot REST API SDK in Node.js?
8-
- How do I fetch liveboards using the ThoughtSpot TypeScript REST API SDK?
7+
- How do I authenticate with ThoughtSpot REST API SDK per user in Node.js?
8+
- How do I fetch metadata using the ThoughtSpot TypeScript REST API SDK?
99
-->
1010

1111
# express-example - Typescript SDK
1212

13-
This example demonstrates how to use the @thoughtspot/rest-api-sdk with Express.js in a TypeScript environment.
13+
This example demonstrates how to use the @thoughtspot/rest-api-sdk with Express.js in a TypeScript environment. It shows a per-user authentication pattern where each request gets its own authenticated ThoughtSpot client based on the username passed in the request header.
1414

1515
## Key Usage
1616

1717
```typescript
1818
import { createBearerAuthenticationConfig, ThoughtSpotRestApi } from "@thoughtspot/rest-api-sdk";
19-
import express from "express";
20-
21-
const config = createBearerAuthenticationConfig(
22-
"https://your-instance.thoughtspot.cloud",
23-
async () => "your-bearer-token",
24-
);
25-
const tsClient = new ThoughtSpotRestApi(config);
19+
import express, { RequestHandler } from "express";
20+
21+
// Create a per-user authenticated ThoughtSpot client
22+
const getAuthenticatedClient = (username: string) => {
23+
const config = createBearerAuthenticationConfig(
24+
"https://your-instance.thoughtspot.cloud",
25+
() => getCachedAuthToken(username), // fetch token per user
26+
);
27+
return new ThoughtSpotRestApi(config);
28+
};
29+
30+
// Middleware: attach ThoughtSpot client to each request
31+
const addThoughtSpotClient: RequestHandler = async (req, res, next) => {
32+
const username = req.headers['x-my-username'] as string;
33+
if (!username) return res.status(401).json({ error: 'Username required' });
34+
(req as any).thoughtSpotClient = getAuthenticatedClient(username);
35+
next();
36+
};
2637

2738
const app = express();
39+
app.use(addThoughtSpotClient);
40+
41+
// Get current user info
42+
app.get('/endpoint-1', async (req, res) => {
43+
const client: ThoughtSpotRestApi = (req as any).thoughtSpotClient;
44+
const userInfo = await client.getCurrentUserInfo();
45+
res.json(userInfo);
46+
});
2847

29-
app.get("/liveboards", async (req, res) => {
30-
const liveboards = await tsClient.searchLiveboards({});
31-
res.json(liveboards);
48+
// Search metadata (liveboards and answers)
49+
app.get('/endpoint-2', async (req, res) => {
50+
const client: ThoughtSpotRestApi = (req as any).thoughtSpotClient;
51+
const metadata = await client.searchMetadata({
52+
metadata: [{ type: 'LIVEBOARD' }, { type: 'ANSWER' }],
53+
record_offset: 0,
54+
record_size: 50,
55+
});
56+
res.json(metadata);
3257
});
3358

3459
app.listen(3000);

starters/kpi-monitor/slack-webhook-python/README.md

Lines changed: 69 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,95 @@
11
<!-- search-meta
22
tags: [KPI-monitor, webhook, Slack, Python, FastAPI, alerts, notifications]
3-
apis: [KPIMonitorWebhook, SlackWebhookAPI, FastAPI]
3+
apis: [KPIMonitorWebhookPayload, SlackWebhookAPI, FastAPI, notificationType, scheduledMetricUpdateWebhookNotification]
44
questions:
55
- How do I send ThoughtSpot KPI monitor alerts to Slack using Python?
66
- How do I build a Python FastAPI webhook for ThoughtSpot KPI alerts?
77
- How do I handle ThoughtSpot KPI webhook payloads in Python?
88
- How do I integrate ThoughtSpot KPI monitor with Slack using FastAPI?
9+
- How do I filter ThoughtSpot webhook notification types in Python?
10+
- How do I send a rich Slack block kit message from a ThoughtSpot KPI alert?
911
-->
1012

1113
# Slack webhook (Python)
1214

13-
A simple webhook made with Python and FastAPI that forwards a KPI monitor alert through webhooks to a Slack channel. The message would look like this:
15+
A webhook built with Python and FastAPI that forwards ThoughtSpot KPI monitor alerts to a Slack channel as a rich block kit message. The message would look like this:
1416

1517
![Slack message](./img/slack-message.png)
1618

17-
This app exposes a single endpoint `/send-to-slack` that accepts POST requests from KPI Monitor. It then builds a Slack message and sends it to a Slack channel configured in the `.env` file.
19+
This app exposes a single endpoint `/send-to-slack` that accepts POST requests from KPI Monitor. It filters by notification type, extracts metric details from the payload, and sends a formatted Slack message with metric info and action buttons.
1820

1921
## Key Usage
2022

2123
```python
22-
from fastapi import FastAPI
24+
from fastapi import FastAPI, HTTPException
2325
from slack_sdk import WebClient
26+
from pydantic import BaseModel
27+
from typing import Dict, Any
28+
import os
2429

2530
app = FastAPI()
26-
slack = WebClient(token=os.environ["SLACK_TOKEN"])
31+
slack_client = WebClient(token=os.getenv("SLACK_TOKEN"))
32+
slack_channel = os.getenv("SLACK_CHANNEL")
33+
34+
# Only forward real alert updates — ignore subscription/test events
35+
UPDATION_NOTIFICATION_TYPES = {
36+
"SCHEDULE_METRIC_UPDATE",
37+
"THRESHOLD_METRIC_UPDATE",
38+
"AUTOMATIC_SELF_SUBSCRIPTION",
39+
"THRESHOLD_BY_ATTRIBUTE",
40+
"SCHEDULED_BY_ATTRIBUTE",
41+
"ANOMALY_METRIC_UPDATE",
42+
}
43+
44+
class WebhookPayload(BaseModel):
45+
data: Dict[str, Any]
2746

28-
# ThoughtSpot KPI Monitor calls this endpoint when an alert fires
2947
@app.post("/send-to-slack")
30-
async def send_to_slack(payload: dict):
31-
data = payload["data"]["scheduledMetricUpdateWebhookNotification"]
32-
rule_name = data["monitorRuleForWebhook"]["ruleName"]
33-
change = data["ruleExecutionDetails"]["percentageChange"]
34-
35-
slack.chat_postMessage(
36-
channel=os.environ["SLACK_CHANNEL"],
37-
text=f"📊 Alert: {rule_name}{change}% change detected",
48+
async def send_to_slack(payload: WebhookPayload):
49+
data = payload.data
50+
notification_type = data.get("notificationType")
51+
52+
if notification_type not in UPDATION_NOTIFICATION_TYPES:
53+
return {"message": "Not forwarding — not an alert update notification"}
54+
55+
notification = data.get("scheduledMetricUpdateWebhookNotification", {})
56+
monitor_rule = notification.get("monitorRuleForWebhook")
57+
rule_execution_details = notification.get("ruleExecutionDetails")
58+
current_user = data.get("currentUser")
59+
modify_url = notification.get("modifyUrl")
60+
unsubscribe_url = notification.get("unsubscribeUrl")
61+
62+
if not all([monitor_rule, rule_execution_details, current_user, modify_url, unsubscribe_url]):
63+
raise HTTPException(status_code=400, detail="Invalid payload structure")
64+
65+
# Rich Slack block kit message with metric details and action buttons
66+
slack_client.chat_postMessage(
67+
channel=slack_channel,
68+
text=f"Alert: {monitor_rule['ruleName']} - {rule_execution_details['percentageChange']} change detected",
69+
blocks=[
70+
{"type": "header", "text": {"type": "plain_text", "text": f"📊 {monitor_rule['ruleName']}", "emoji": True}},
71+
{
72+
"type": "section",
73+
"fields": [
74+
{"type": "mrkdwn", "text": f"*Metric:*\n<{monitor_rule['metricUrl']}|{monitor_rule['metricName']}>"},
75+
{"type": "mrkdwn", "text": f"*Change:*\n{rule_execution_details['percentageChange']}"},
76+
{"type": "mrkdwn", "text": f"*New Value:*\n{rule_execution_details['currentMetricValue']}"},
77+
{"type": "mrkdwn", "text": f"*Period:*\n{rule_execution_details['executionTimestamp']}"},
78+
{"type": "mrkdwn", "text": f"*Schedule:*\n{monitor_rule['scheduleString']}"},
79+
{"type": "mrkdwn", "text": f"*Triggered By:*\n{current_user['displayName']} ({current_user['email']})"},
80+
],
81+
},
82+
{
83+
"type": "actions",
84+
"elements": [
85+
{"type": "button", "text": {"type": "plain_text", "text": "View Metric", "emoji": True}, "url": monitor_rule["metricUrl"]},
86+
{"type": "button", "text": {"type": "plain_text", "text": "Modify Alert", "emoji": True}, "url": modify_url},
87+
{"type": "button", "text": {"type": "plain_text", "text": "Unsubscribe", "emoji": True}, "url": unsubscribe_url, "style": "danger"},
88+
],
89+
},
90+
],
3891
)
39-
return {"ok": True}
92+
return {"message": f"Message forwarded to Slack channel: {slack_channel}"}
4093
```
4194

4295
File structure:
@@ -112,4 +165,4 @@ The above steps will start the server on port 3000 on your local machine. Now we
112165
- Python
113166
- FastAPI
114167
- Slack
115-
- Webhook
168+
- Webhook

starters/kpi-monitor/slack-webhook-typescript/README.md

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
<!-- search-meta
22
tags: [KPI-monitor, webhook, Slack, TypeScript, Express, NodeJS, alerts, notifications]
3-
apis: [KPIMonitorWebhook, SlackWebhookAPI, Express]
3+
apis: [KPIMonitorWebhookPayload, SlackWebhookAPI, Express, notificationType, scheduledMetricUpdateWebhookNotification]
44
questions:
5-
- How do I send ThoughtSpot KPI monitor alerts to Slack?
6-
- How do I build a TypeScript webhook receiver for ThoughtSpot KPI alerts?
7-
- How do I integrate ThoughtSpot KPI monitor with Slack notifications?
8-
- How do I handle ThoughtSpot webhook payloads in Node.js Express?
5+
- How do I send ThoughtSpot KPI monitor alerts to Slack using TypeScript?
6+
- How do I build a TypeScript Express webhook for ThoughtSpot KPI alerts?
7+
- How do I handle ThoughtSpot KPI webhook payloads in Node.js?
8+
- How do I integrate ThoughtSpot KPI monitor with Slack using Express?
9+
- How do I filter ThoughtSpot webhook notification types in TypeScript?
10+
- How do I send a rich Slack block kit message from a ThoughtSpot KPI alert?
911
-->
1012

1113
# Slack webhook (Typescript)
@@ -19,23 +21,69 @@ This app exposes a single endpoint `/send-to-slack` that accepts POST requests f
1921
## Key Usage
2022

2123
```typescript
22-
import express from "express";
24+
import express, { Request, Response } from "express";
2325
import { WebClient } from "@slack/web-api";
2426

2527
const app = express();
26-
const slack = new WebClient(process.env.SLACK_TOKEN);
27-
28-
// ThoughtSpot KPI Monitor calls this endpoint when an alert fires
29-
app.post("/send-to-slack", async (req, res) => {
30-
const { monitorRuleForWebhook, ruleExecutionDetails } = req.body.data
31-
.scheduledMetricUpdateWebhookNotification;
32-
33-
await slack.chat.postMessage({
34-
channel: process.env.SLACK_CHANNEL,
35-
text: `📊 Alert: ${monitorRuleForWebhook.ruleName} — ${ruleExecutionDetails.percentageChange}% change detected`,
28+
const slackClient = new WebClient(process.env.SLACK_TOKEN);
29+
const slackChannel = process.env.SLACK_CHANNEL;
30+
31+
// Only forward real alert updates — ignore subscription/test events
32+
const updationNotificationTypes = new Set([
33+
"SCHEDULE_METRIC_UPDATE",
34+
"THRESHOLD_METRIC_UPDATE",
35+
"AUTOMATIC_SELF_SUBSCRIPTION",
36+
"THRESHOLD_BY_ATTRIBUTE",
37+
"SCHEDULED_BY_ATTRIBUTE",
38+
"ANOMALY_METRIC_UPDATE",
39+
]);
40+
41+
app.post("/send-to-slack", async (req: Request, res: Response) => {
42+
const payload = req.body.data;
43+
const {
44+
currentUser,
45+
scheduledMetricUpdateWebhookNotification: {
46+
ruleExecutionDetails,
47+
modifyUrl,
48+
unsubscribeUrl,
49+
monitorRuleForWebhook,
50+
},
51+
} = payload;
52+
53+
if (!updationNotificationTypes.has(payload.notificationType)) {
54+
res.status(200).json({ message: "Not forwarding — not an alert update notification" });
55+
return;
56+
}
57+
58+
// Rich Slack block kit message with metric details and action buttons
59+
await slackClient.chat.postMessage({
60+
channel: slackChannel,
61+
text: `Alert: ${monitorRuleForWebhook.ruleName} - ${ruleExecutionDetails.percentageChange} change detected`,
62+
blocks: [
63+
{ type: "header", text: { type: "plain_text", text: `📊 ${monitorRuleForWebhook.ruleName}`, emoji: true } },
64+
{
65+
type: "section",
66+
fields: [
67+
{ type: "mrkdwn", text: `*Metric:*\n<${monitorRuleForWebhook.metricUrl}|${monitorRuleForWebhook.metricName}>` },
68+
{ type: "mrkdwn", text: `*Change:*\n${ruleExecutionDetails.percentageChange}` },
69+
{ type: "mrkdwn", text: `*New Value:*\n${ruleExecutionDetails.currentMetricValue}` },
70+
{ type: "mrkdwn", text: `*Period:*\n${ruleExecutionDetails.executionTimestamp}` },
71+
{ type: "mrkdwn", text: `*Schedule:*\n${monitorRuleForWebhook.scheduleString}` },
72+
{ type: "mrkdwn", text: `*Triggered By:*\n${currentUser.displayName} (${currentUser.email})` },
73+
],
74+
},
75+
{
76+
type: "actions",
77+
elements: [
78+
{ type: "button", text: { type: "plain_text", text: "View Metric", emoji: true }, url: monitorRuleForWebhook.metricUrl },
79+
{ type: "button", text: { type: "plain_text", text: "Modify Alert", emoji: true }, url: modifyUrl },
80+
{ type: "button", text: { type: "plain_text", text: "Unsubscribe", emoji: true }, url: unsubscribeUrl, style: "danger" },
81+
],
82+
},
83+
],
3684
});
3785

38-
res.json({ ok: true });
86+
res.status(200).json({ message: `Message forwarded to Slack channel: ${slackChannel}` });
3987
});
4088

4189
app.listen(3000);

visual-embed/answer-service/README.md

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<!-- search-meta
22
tags: [AnswerService, SearchEmbed, React, TypeScript, data-fetch, pagination]
3-
apis: [AnswerService, SearchEmbed, getAnswerService, fetchData, getUnderlyingData, useEmbedRef]
3+
apis: [AnswerService, SearchEmbed, getAnswerService, fetchData, getAnswer, getUnderlyingDataForPoint, useEmbedRef]
44
questions:
55
- How do I fetch raw data from ThoughtSpot programmatically without displaying it?
66
- How do I use AnswerService to get data from a ThoughtSpot search?
@@ -17,14 +17,27 @@ This example demonstrates how to use the AnswerService from the ThoughtSpot Visu
1717
```typescript
1818
import { SearchEmbed, useEmbedRef } from "@thoughtspot/visual-embed-sdk/react";
1919

20-
// Use embedRef to access AnswerService after search
2120
const embedRef = useEmbedRef();
2221

22+
// Fetch paginated raw data from the current answer
2323
const fetchData = async () => {
2424
if (embedRef.current) {
2525
const service = await embedRef.current.getAnswerService();
26-
const data = await service.fetchData(); // paginated raw data
27-
const underlying = await service.getUnderlyingData(); // source rows
26+
const data = await service.fetchData();
27+
console.log(data);
28+
}
29+
};
30+
31+
// Get underlying source data for the first column in the visualization
32+
const getUnderlyingData = async () => {
33+
if (embedRef.current) {
34+
const service = await embedRef.current.getAnswerService();
35+
const answer = await service.getAnswer();
36+
const columnName = answer?.visualizations?.[0]?.columns?.[0]?.column?.referencedColumns?.[0]?.displayName;
37+
if (!columnName) return;
38+
39+
const underlyingData = await service.getUnderlyingDataForPoint([columnName], []);
40+
const data = await underlyingData.fetchData(0, 100);
2841
console.log(data);
2942
}
3043
};
@@ -101,7 +114,8 @@ The AnswerService provides several methods to interact with ThoughtSpot answers:
101114

102115
- `getSourceDetail()`: Get the details about the source used in the answer.
103116
- `fetchData()`: Fetch data from the answer.
104-
- `getUnderlyingDataFromPoint()`: Gets the underlying data of the answer
117+
- `getAnswer()`: Get the answer object, including visualization columns and metadata.
118+
- `getUnderlyingDataForPoint(columnNames, filters)`: Get underlying source data for specific columns. Returns a service with `fetchData(offset, size)` for pagination.
105119

106120
This example demonstrates most of these methods, but you can extend it to use others as needed.
107121

0 commit comments

Comments
 (0)